http://git-wip-us.apache.org/repos/asf/flex-tlf/blob/33df98ab/textLayout/src/flashx/textLayout/elements/ParagraphElement.as ---------------------------------------------------------------------- diff --git a/textLayout/src/flashx/textLayout/elements/ParagraphElement.as b/textLayout/src/flashx/textLayout/elements/ParagraphElement.as index f4579fc..1fec299 100644 --- a/textLayout/src/flashx/textLayout/elements/ParagraphElement.as +++ b/textLayout/src/flashx/textLayout/elements/ParagraphElement.as @@ -31,9 +31,9 @@ package flashx.textLayout.elements import flash.text.engine.TextLine; import flash.text.engine.TextLineValidity; import flash.text.engine.TextRotation; + import flash.utils.Dictionary; import flash.utils.getQualifiedClassName; - import flashx.textLayout.tlf_internal; import flashx.textLayout.compose.TextFlowLine; import flashx.textLayout.container.ContainerController; import flashx.textLayout.debug.Debugging; @@ -50,6 +50,7 @@ package flashx.textLayout.elements import flashx.textLayout.formats.TextJustify; import flashx.textLayout.formats.TextLayoutFormat; import flashx.textLayout.property.Property; + import flashx.textLayout.tlf_internal; import flashx.textLayout.utils.CharacterUtil; import flashx.textLayout.utils.LocaleUtil; @@ -79,7 +80,8 @@ package flashx.textLayout.elements public final class ParagraphElement extends ParagraphFormattedElement { - private var _textBlock:TextBlock; + //private var _textBlock:TextBlock; + private var _textBlockChildren:Dictionary; private var _terminatorSpan:SpanElement; private var _interactiveChildrenCount:int; @@ -95,6 +97,7 @@ package flashx.textLayout.elements super(); _terminatorSpan = null; _interactiveChildrenCount = 0 ; + _textBlockChildren = new Dictionary(); } tlf_internal function get interactiveChildrenCount():int { @@ -106,26 +109,83 @@ package flashx.textLayout.elements { CONFIG::debug { assert(_textBlock == null,"createTextBlock called when there is already a textblock"); } computedFormat; // recreate the format BEFORE the _textBlock is created - _textBlock = new TextBlock(); + var tbs:Vector.<TextBlock> = getTextBlocks(); + //tbs.length = 0; + var tableCount:int = 0; + if(tbs.length == 0 && !(getChildAt(0) is TableElement) ) + tbs.push(new TextBlock()); + //getTextBlocks()[0] = new TextBlock(); CONFIG::debug { Debugging.traceFTECall(_textBlock,null,"new TextBlock()"); } for (var i:int = 0; i < numChildren; i++) { var child:FlowElement = getChildAt(i); + if(child is TableElement) + tableCount++; +// tbs.push(new TextBlock()); + else + { + //child.releaseContentElement(); + //child.createContentElement(); + } + } + while(tableCount >= tbs.length) + tbs.push(new TextBlock()); + + for (i = 0; i < numChildren; i++) + { + child = getChildAt(i); child.createContentElement(); } - updateTextBlock(); + tbs.length = tableCount + 1; + var tb:TextBlock; + for each(tb in tbs){ + updateTextBlock(tb); + } } - - /** @private */ - - tlf_internal function releaseTextBlock():void + private function updateTextBlockDict():void + { + var tbs:Vector.<TextBlock> = getTextBlocks(); + if(tbs.length == 0) + return;//nothing to do + var tbIdx:int = 0; + var tb:TextBlock = tbs[tbIdx]; + var items:Array = []; + var child:FlowElement; + for (var i:int = 0; i < numChildren; i++) + { + child = getChildAt(i); + if(child is TableElement) + { + _textBlockChildren[tb] = items; + tb = tbs[++tbIdx]; + items = []; + continue; + } + items.push(child); + } + _textBlockChildren[tb] = items; + } + private function removeTextBlock(tb:TextBlock):void + { + var tbs:Vector.<TextBlock> = getTextBlocks(); + if(tbs) + { + var idx:int = getTextBlocks().indexOf(tb); + if(idx > -1) + { + tbs.splice(idx,1); + delete _textBlockChildren[tb]; + } + } + } + private function releaseTextBlockInternal(tb:TextBlock):void { - if (!_textBlock) + if (!tb) return; - - if (_textBlock.firstLine) // A TextBlock may have no firstLine if it has previously been released. + + if (tb.firstLine) // A TextBlock may have no firstLine if it has previously been released. { - for (var textLineTest:TextLine = _textBlock.firstLine; textLineTest != null; textLineTest = textLineTest.nextLine) + for (var textLineTest:TextLine = tb.firstLine; textLineTest != null; textLineTest = textLineTest.nextLine) { if(textLineTest.numChildren != 0) { @@ -138,55 +198,169 @@ package flashx.textLayout.elements } } - CONFIG::debug { Debugging.traceFTECall(null,_textBlock,"releaseLines",_textBlock.firstLine, _textBlock.lastLine); } - _textBlock.releaseLines(_textBlock.firstLine, _textBlock.lastLine); + CONFIG::debug { Debugging.traceFTECall(null,tb,"releaseLines",tb.firstLine, tb.lastLine); } + tb.releaseLines(tb.firstLine, tb.lastLine); } - - _textBlock.content = null; - for (var i:int = 0; i < numChildren; i++) + var items:Array = _textBlockChildren[tb]; + var len:int = items.length; + for (var i:int = 0; i < len; i++) { - var child:FlowElement = getChildAt(i); + var child:FlowElement = items[i]; child.releaseContentElement(); } - _textBlock = null; + items.length = 0; + tb.content = null; + removeTextBlock(tb); + } + /** @private */ + tlf_internal function releaseTextBlock(tb:TextBlock=null):void + { + updateTextBlockDict(); + if(tb) + { + releaseTextBlockInternal(tb); + return; + } + var tbs:Vector.<TextBlock> = getTextBlocks(); + for each(var tb:TextBlock in tbs) + { + releaseTextBlockInternal(tb); + } + //_textBlock = null; if (_computedFormat) _computedFormat = null; } - + private var _textBlocks:Vector.<TextBlock>; + tlf_internal function getTextBlocks():Vector.<TextBlock> + { + if(_textBlocks == null) + _textBlocks = new Vector.<TextBlock>(); + return _textBlocks; + } /** TextBlock where the text of the paragraph is kept. @private */ tlf_internal function getTextBlock():TextBlock - { - if (!_textBlock) + { + if (!getTextBlocks().length) + createTextBlock(); + + return getTextBlocks()[0]; + } + /** Last TextBlock where the text of the paragraph is kept. @private */ + tlf_internal function getLastTextBlock():TextBlock + { + var tbs:Vector.<TextBlock> = getTextBlocks(); + if(!tbs.length) createTextBlock(); - return _textBlock; + + return tbs[tbs.length-1]; + } + + /** Get TextBlock at specified position. @private */ + tlf_internal function getTextBlockAtPosition(pos:int):TextBlock + { + var curPos:int = 0; + var posShift:int = 0; + var tables:Vector.<TableElement> = getTables(); + if(!tables.length) + return getTextBlock(); + + for each(var table:TableElement in tables) + { + if(table.getElementRelativeStart(this) < pos) + posShift++; + } + var tbs:Vector.<TextBlock> = getTextBlocks(); + for each(var tb:TextBlock in tbs) + { + if(tb.content == null) + return tb; + curPos += tb.content.rawText.length; + if(curPos + posShift > pos) + { + if(getTextBlockStart(tb) > pos) + return null; + return tb; + } + } + return null; + } + + tlf_internal function getTextBlockAbsoluteStart(tb:TextBlock):int + { + var start:int = getTextBlockStart(tb); + if(start < 0) + start = 0; + return getAbsoluteStart() + start; + } + tlf_internal function getTextBlockStart(tb:TextBlock):int + { + var i:int; + var curPos:int = 0; + var tbs:Vector.<TextBlock> = getTextBlocks(); + if(tbs.length == 0) + return -1; + var tables:Vector.<TableElement> = getTables(); + for each(var curTB:TextBlock in tbs) + { + for each(var table:TableElement in tables) + { + if(table.getElementRelativeStart(this) <= curPos) + { + curPos++; + tables.splice(tables.indexOf(table),1); + } + } + if(tb == curTB) + return curPos; + if(tb.content) + curPos += curTB.content.rawText.length; + } + + return -1; } + private function getTables():Vector.<TableElement> + { + var tables:Vector.<TableElement> = new Vector.<TableElement>(); + for (var i:int = 0; i < numChildren; i++) + { + var child:FlowElement = getChildAt(i); + if(child is TableElement) + tables.push(child as TableElement); + } + return tables; + } + /** TextBlock where the text of the paragraph is kept, or null if we currently don't have one. @private */ tlf_internal function peekTextBlock():TextBlock { - return _textBlock; + return getTextBlocks().length == 0 ? null : getTextBlocks()[0]; } /** @private */ tlf_internal function releaseLineCreationData():void { CONFIG::debug { assert(Configuration.playerEnablesArgoFeatures,"bad call to releaseLineCreationData"); } - if (_textBlock) - _textBlock["releaseLineCreationData"](); + var tbs:Vector.<TextBlock> = getTextBlocks(); + for each(var tb:TextBlock in tbs) + { + tb["releaseLineCreationData"](); + } } /** @private */ - tlf_internal override function createContentAsGroup():GroupElement - { - var group:GroupElement = _textBlock.content as GroupElement; + tlf_internal override function createContentAsGroup(pos:int=0):GroupElement + { + var tb:TextBlock = getTextBlockAtPosition(pos); + var group:GroupElement = tb.content as GroupElement; if (!group) { - var originalContent:ContentElement = _textBlock.content; + var originalContent:ContentElement = tb.content; group = new GroupElement(); CONFIG::debug { Debugging.traceFTECall(group,null,"new GroupElement()"); } - _textBlock.content = group; - CONFIG::debug { Debugging.traceFTEAssign(_textBlock,"content",group); } + tb.content = group; + CONFIG::debug { Debugging.traceFTEAssign(tb,"content",group); } if (originalContent) { @@ -199,7 +373,7 @@ package flashx.textLayout.elements } // Now we've got to force damage the entire paragraph, because we restructured it in FTE. - if (_textBlock.firstLine && textLength) + if (tb.firstLine && textLength) { var textFlow:TextFlow = getTextFlow(); if (textFlow) @@ -212,7 +386,15 @@ package flashx.textLayout.elements /** @private */ tlf_internal override function removeBlockElement(child:FlowElement, block:ContentElement):void { - if (numChildren == 1) + var tb:TextBlock = getTextBlockAtPosition(child.getElementRelativeStart(this)); + if(!tb) + tb = getTextBlock(); + + if(tb.content == null) + return; + var relativeStart:int = child.getElementRelativeStart(this); + + if (getChildrenInTextBlock(relativeStart).length < 2) { if (block is GroupElement) { @@ -221,18 +403,20 @@ package flashx.textLayout.elements CONFIG::debug { assert(_textBlock.content is GroupElement,"removeBlockElement: bad content"); } CONFIG::debug { assert(GroupElement(_textBlock.content).elementCount == 1,"removeBlockElement: bad element count"); } CONFIG::debug { assert(GroupElement(_textBlock.content).getElementAt(0) == block,"removeBlockElement: bad group content"); } - GroupElement(_textBlock.content).replaceElements(0,1,null); + GroupElement(tb.content).replaceElements(0,1,null); CONFIG::debug { Debugging.traceFTECall(null,_textBlock.content,"replaceElements",0,1,null); } } - _textBlock.content = null; + tb.content = null; CONFIG::debug { Debugging.traceFTEAssign(_textBlock,"content",null); } } - else + else if(block.groupElement) { - var idx:int = this.getChildIndex(child); - var group:GroupElement = GroupElement(_textBlock.content); + var idx:int = getChildIndexInBlock(child); + var group:GroupElement = GroupElement(tb.content); CONFIG::debug { assert(group.elementCount == numChildren,"Mismatched group and elementCount"); } group.replaceElements(idx,idx+1,null); + if(group.elementCount == 0) + return; CONFIG::debug { Debugging.traceFTECall(null,group,"replaceElements",idx,idx+1,null); } if (numChildren == 2) // its going to be one so ungroup { @@ -243,18 +427,22 @@ package flashx.textLayout.elements { group.replaceElements(0,1,null); CONFIG::debug { Debugging.traceFTECall(null,group,"replaceElements",0,1,null); } - _textBlock.content = elem; - CONFIG::debug { Debugging.traceFTEAssign(_textBlock,"content",elem); } + tb.content = elem; + CONFIG::debug { Debugging.traceFTEAssign(tb,"content",elem); } } } } + else { + //trace("1"); + //tb.content = null; + } } /** @private */ tlf_internal override function hasBlockElement():Boolean { - return _textBlock != null; + return getTextBlocks().length > 0; } /** @private */ @@ -264,9 +452,42 @@ package flashx.textLayout.elements } /** @private */ + private function getChildrenInTextBlock(pos:int):Array + { + var retVal:Array = []; + if(numChildren == 0) + return retVal; + if(numChildren == 1) + { + retVal.push(getChildAt(0)); + return retVal + } + var chldrn:Array = mxmlChildren.slice(); + for(var i:int = 0; i<chldrn.length;i++) + { + if(chldrn[i] is TableElement) + { + if(chldrn[i].parentRelativeStart == pos) + return [chldrn[i]]; + if(chldrn[i].parentRelativeStart < pos) + { + retVal.length = 0; + continue; + } + if(chldrn[i].parentRelativeStart > pos) + break; + } + retVal.push(chldrn[i]); + } + return retVal; + } + + /** @private */ tlf_internal override function insertBlockElement(child:FlowElement, block:ContentElement):void { - if (_textBlock == null) + var relativeStart:int = child.getElementRelativeStart(this); + var tb:TextBlock = getTextBlockAtPosition(relativeStart); + if (getTextBlocks().length == 0 || !tb) { child.releaseContentElement(); createTextBlock(); // does the whole tree @@ -274,7 +495,7 @@ package flashx.textLayout.elements } var gc:Vector.<ContentElement>; // scratch var var group:GroupElement; // scratch - if (numChildren == 1) + if (getChildrenInTextBlock(relativeStart).length < 2) { if (block is GroupElement) { @@ -285,19 +506,23 @@ package flashx.textLayout.elements CONFIG::debug { Debugging.traceFTECall(null,gc,"push",block); } group = new GroupElement(gc); CONFIG::debug { Debugging.traceFTECall(group,null,"new GroupElement",gc); } - _textBlock.content = group; + tb.content = group; CONFIG::debug { Debugging.traceFTEAssign(_textBlock,"content",group); } } else { - _textBlock.content = block; + if(block.groupElement) + { + block.groupElement.elementCount; + } + tb.content = block; CONFIG::debug { Debugging.traceFTEAssign(_textBlock,"content",block); } } } else { - group = createContentAsGroup(); - var idx:int = this.getChildIndex(child); + group = createContentAsGroup(relativeStart); + var idx:int = getChildIndexInBlock(child); gc = new Vector.<ContentElement>(); CONFIG::debug { Debugging.traceFTECall(gc,null,"new Vector.<ContentElement>") } gc.push(block); @@ -307,6 +532,21 @@ package flashx.textLayout.elements } } + private function getChildIndexInBlock(elem:FlowElement):int + { + var relIdx:int = 0; + for (var i:int = 0; i < numChildren; i++) + { + var child:FlowElement = getChildAt(i); + if(child == elem) + return relIdx; + relIdx++; + if(child is TableElement) + relIdx = 0; + } + return -1; + } + /** @private */ override protected function get abstract():Boolean { return false; } @@ -315,24 +555,63 @@ package flashx.textLayout.elements tlf_internal override function get defaultTypeName():String { return "p"; } + tlf_internal function removeEmptyTerminator():void + { + if(numChildren == 1 && _terminatorSpan && _terminatorSpan.textLength == 1) + { + _terminatorSpan.removeParaTerminator(); + super.replaceChildren(0, 1); + this._terminatorSpan = null; + } + } /** @private */ public override function replaceChildren(beginChildIndex:int,endChildIndex:int,...rest):void { var applyParams:Array; - - // makes a measurable difference - rest.length zero and one are the common cases - if (rest.length == 1) - applyParams = [beginChildIndex, endChildIndex, rest[0]]; - else - { - applyParams = [beginChildIndex, endChildIndex]; - if (rest.length != 0) - applyParams = applyParams.concat.apply(applyParams, rest); - } - super.replaceChildren.apply(this, applyParams); + do{ + if(_terminatorSpan) + { + var termIdx:int = getChildIndex(_terminatorSpan); + if(termIdx !=0 && _terminatorSpan.textLength == 1) + { + super.replaceChildren(termIdx, termIdx+1); + _terminatorSpan = null; + if(beginChildIndex >= termIdx) + { + beginChildIndex--; + if(rest.length == 0) // delete of terminator was already done. + break; + } + if(endChildIndex >= termIdx && beginChildIndex != endChildIndex) + endChildIndex--; + } + } + + // makes a measurable difference - rest.length zero and one are the common cases + if (rest.length == 1) + applyParams = [beginChildIndex, endChildIndex, rest[0]]; + else + { + applyParams = [beginChildIndex, endChildIndex]; + if (rest.length != 0) + applyParams = applyParams.concat.apply(applyParams, rest); + } + + super.replaceChildren.apply(this, applyParams); + + }while(false); ensureTerminatorAfterReplace(); + // ensure correct text blocks + createTextBlock(); + } + + public override function splitAtPosition(relativePosition:int):FlowElement + { + // need to handle multiple TextBlocks + // maybe not. It might be handled in replaceChildren(). + return super.splitAtPosition(relativePosition); } /** @private */ tlf_internal function ensureTerminatorAfterReplace():void @@ -340,27 +619,43 @@ package flashx.textLayout.elements var newLastLeaf:FlowLeafElement = getLastLeaf(); if (_terminatorSpan != newLastLeaf) { - if (_terminatorSpan) + if (newLastLeaf && _terminatorSpan) { _terminatorSpan.removeParaTerminator(); + if(_terminatorSpan.textLength == 0) + { + var termIdx:int = getChildIndex(_terminatorSpan); + super.replaceChildren(termIdx, termIdx+1); + } this._terminatorSpan = null; } - if (newLastLeaf) + if (newLastLeaf is SpanElement) { - if (newLastLeaf is SpanElement) - { - (newLastLeaf as SpanElement).addParaTerminator(); - this._terminatorSpan = newLastLeaf as SpanElement; - } - else - { - var s:SpanElement = new SpanElement(); - super.replaceChildren(numChildren,numChildren,s); - s.format = newLastLeaf.format; - s.addParaTerminator(); - this._terminatorSpan = s; - } + (newLastLeaf as SpanElement).addParaTerminator(); + this._terminatorSpan = newLastLeaf as SpanElement; + } + else + { + var s:SpanElement = new SpanElement(); + super.replaceChildren(numChildren,numChildren,s); + s.format = newLastLeaf ? newLastLeaf.format : _terminatorSpan.format; + s.addParaTerminator(); + this._terminatorSpan = s; + } + } + //merge terminator span to previous if possible + if(_terminatorSpan.textLength == 1) + { + var prev:FlowLeafElement = _terminatorSpan.getPreviousLeaf(this); + if(prev && prev is SpanElement) + { + _terminatorSpan.removeParaTerminator(); + termIdx = getChildIndex(_terminatorSpan); + super.replaceChildren(termIdx, termIdx+1); + s = prev as SpanElement; + s.addParaTerminator(); + this._terminatorSpan = s; } } } @@ -387,7 +682,7 @@ package flashx.textLayout.elements child.bindableElement = true; // Note: calling super.replaceChildren because we don't want to transfer para terminator each time - super.replaceChildren(numChildren, numChildren, child as FlowElement); + super.replaceChildren(numChildren, numChildren, child as FlowElement); } else if (child is String) { @@ -404,6 +699,9 @@ package flashx.textLayout.elements // Now ensure para terminator ensureTerminatorAfterReplace(); + + // recreate text blocks to handle possible TableElement changes + createTextBlock(); } /** @private @@ -411,17 +709,26 @@ package flashx.textLayout.elements public override function getText(relativeStart:int=0, relativeEnd:int=-1, paragraphSeparator:String="\n"):String { // Optimization for getting text of the entire paragraph - if (relativeStart == 0 && (relativeEnd == -1 || relativeEnd >= textLength-1) && _textBlock) + if (relativeStart == 0 && (relativeEnd == -1 || relativeEnd >= textLength-1) && getTextBlocks().length) { - if (_textBlock.content && _textBlock.content.rawText) + var tb:TextBlock; + var tbs:Vector.<TextBlock> = getTextBlocks(); + var text:String = ""; + for each(tb in tbs) { - var text:String = _textBlock.content.rawText; - return text.substring(0, text.length - 1); + text = text + getTextInBlock(tb); } - return ""; // content is null + if(tb.content && tb.content.rawText) + return text.substring(0, text.length - 1); + return text; } return super.getText(relativeStart, relativeEnd, paragraphSeparator); } + private function getTextInBlock(tb:TextBlock):String{ + if(!tb.content || !tb.content.rawText) + return ""; + return tb.content.rawText; + } /** Returns the paragraph that follows this one, or null if there are no more paragraphs. * @@ -478,39 +785,46 @@ package flashx.textLayout.elements public function findPreviousAtomBoundary(relativePosition:int):int { + var tb:TextBlock = getTextBlockAtPosition(relativePosition); + var tbStart:int = getTextBlockStart(tb); + var textBlockPos:int = relativePosition - tbStart; if (ContainerController.tlf_internal::usesDiscretionaryHyphens) { - var textBlock:TextBlock = getTextBlock(); - var tl:TextLine = textBlock.getTextLineAtCharIndex(relativePosition); - var currentAtomIndex:int = tl.getAtomIndexAtCharIndex(relativePosition); + var tl:TextLine = tb.getTextLineAtCharIndex(textBlockPos); + var currentAtomIndex:int = tl.getAtomIndexAtCharIndex(textBlockPos); //trace("relpos", relativePosition, "atomIndex", currentAtomIndex); var isRTL:Boolean = tl.getAtomBidiLevel(currentAtomIndex) == 1; if (isRTL) { - var foo:int = getTextBlock().findPreviousAtomBoundary(relativePosition); + var foo:int = tb.findPreviousAtomBoundary(textBlockPos); if (currentAtomIndex == 0) { // when cursor is left of all characters (end of line) // atomIndex is 0, so compensate if (tl.atomCount > 0) { - while (--relativePosition) + while (--textBlockPos) { - if (tl.getAtomIndexAtCharIndex(relativePosition) != currentAtomIndex) + --relativePosition; + if (tl.getAtomIndexAtCharIndex(textBlockPos) != currentAtomIndex) break; } } } else { - while (--relativePosition) + while (--relativePosition && --textBlockPos) { - if (tl.getAtomIndexAtCharIndex(relativePosition) != currentAtomIndex) + if (tl.getAtomIndexAtCharIndex(textBlockPos) != currentAtomIndex) break; } } if (CharacterUtil.isLowSurrogate(getText(relativePosition, relativePosition + 1).charCodeAt(0))) - relativePosition--; + { + relativePosition--; + textBlockPos--; + } + //trace("previous", relativePosition, foo); } else @@ -521,21 +835,26 @@ package flashx.textLayout.elements if (!tl) return -1; // need this when 0x2028 line separator in use - if (tl.textBlockBeginIndex + tl.rawTextLength == relativePosition) - return tl.textBlockBeginIndex + tl.rawTextLength - 1; - return tl.textBlockBeginIndex + tl.rawTextLength; + if (tl.textBlockBeginIndex + tl.rawTextLength == textBlockPos) + return tl.textBlockBeginIndex + tl.rawTextLength - 1 + tbStart; + return tl.textBlockBeginIndex + tl.rawTextLength + tbStart; } - while (--relativePosition) + while (--relativePosition && --textBlockPos) { - if (tl.getAtomIndexAtCharIndex(relativePosition) < currentAtomIndex) + if (tl.getAtomIndexAtCharIndex(textBlockPos) < currentAtomIndex) break; } if (CharacterUtil.isLowSurrogate(getText(relativePosition, relativePosition + 1).charCodeAt(0))) - relativePosition--; + { + relativePosition--; + textBlockPos--; + } } return relativePosition; } - var pos:int = getTextBlock().findPreviousAtomBoundary(relativePosition); + var pos:int = tb.findPreviousAtomBoundary(textBlockPos); + if(pos >= 0) + pos += tbStart; //trace("previous", relativePosition, pos); return pos; } @@ -560,34 +879,41 @@ package flashx.textLayout.elements public function findNextAtomBoundary(relativePosition:int):int { + var tb:TextBlock = getTextBlockAtPosition(relativePosition); + var tbStart:int = getTextBlockStart(tb); + var textBlockPos:int = relativePosition - tbStart; if (ContainerController.tlf_internal::usesDiscretionaryHyphens) { - var textBlock:TextBlock = getTextBlock(); - var tl:TextLine = textBlock.getTextLineAtCharIndex(relativePosition); - var currentAtomIndex:int = tl.getAtomIndexAtCharIndex(relativePosition); + var tl:TextLine = tb.getTextLineAtCharIndex(textBlockPos); + var currentAtomIndex:int = tl.getAtomIndexAtCharIndex(textBlockPos); //trace("relpos", relativePosition, "atomIndex", currentAtomIndex); var isRTL:Boolean = tl.getAtomBidiLevel(currentAtomIndex) == 1; if (isRTL) { - var foo:int = getTextBlock().findNextAtomBoundary(relativePosition); + var foo:int = tb.findNextAtomBoundary(textBlockPos); if (currentAtomIndex == 0) { - while (++relativePosition) + while (++textBlockPos) { - if (tl.getAtomIndexAtCharIndex(relativePosition) != currentAtomIndex) + ++relativePosition; + if (tl.getAtomIndexAtCharIndex(textBlockPos) != currentAtomIndex) break; } } else { - while (++relativePosition) + while (++textBlockPos) { - if (tl.getAtomIndexAtCharIndex(relativePosition) != currentAtomIndex) + ++relativePosition; + if (tl.getAtomIndexAtCharIndex(textBlockPos) != currentAtomIndex) break; } } if (CharacterUtil.isHighSurrogate(getText(relativePosition, relativePosition + 1).charCodeAt(0))) - relativePosition++; + { + relativePosition++; + textBlockPos++; + } //trace("next", relativePosition, foo); } else @@ -597,19 +923,25 @@ package flashx.textLayout.elements tl = tl.nextLine; if (!tl) return -1; - return tl.textBlockBeginIndex; + return tl.textBlockBeginIndex + tbStart; } - while (++relativePosition) + while (++textBlockPos) { - if (tl.getAtomIndexAtCharIndex(relativePosition) > currentAtomIndex) + ++relativePosition; + if (tl.getAtomIndexAtCharIndex(textBlockPos) > currentAtomIndex) break; } if (CharacterUtil.isHighSurrogate(getText(relativePosition, relativePosition + 1).charCodeAt(0))) - relativePosition++; + { + relativePosition++; + textBlockPos++; + } } return relativePosition; } - var pos:int = getTextBlock().findNextAtomBoundary(relativePosition); + var pos:int = tb.findNextAtomBoundary(textBlockPos); + if(pos >= 0) + pos += tbStart; //trace("next", relativePosition, pos); return pos; } @@ -617,7 +949,27 @@ package flashx.textLayout.elements /** @private */ public override function getCharAtPosition(relativePosition:int):String { - return getTextBlock().content.rawText.charAt(relativePosition); + var foundTB:TextBlock = getTextBlockAtPosition(relativePosition); + if(!foundTB) + return "\u0016"; + var tables:Vector.<TableElement> = getTables(); + var pos:int = relativePosition; + for each(var table:TableElement in tables) + { + if(table.getElementRelativeStart(this) < pos) + relativePosition--; + } + var tbs:Vector.<TextBlock> = getTextBlocks(); + for each(var tb:TextBlock in tbs) + { + if(foundTB == tb) + break; + if(tb) + relativePosition -= tb.content.rawText.length; + else + relativePosition -= 1;this.getText() + } + return foundTB.content.rawText.charAt(relativePosition); } /** @@ -650,7 +1002,13 @@ package flashx.textLayout.elements } return relativePosition; } - return getTextBlock().findPreviousWordBoundary(relativePosition); + var block:TextBlock = getTextBlockAtPosition(relativePosition); + if(block == null) + block = getTextBlockAtPosition(--relativePosition); + var pos:int = getTextBlockStart(block); + if(pos < 0) + pos = 0; + return relativePosition == pos ? pos : pos + block.findPreviousWordBoundary(relativePosition - pos); } /** @@ -683,7 +1041,13 @@ package flashx.textLayout.elements } return relativePosition; } - return getTextBlock().findNextWordBoundary(relativePosition); + var block:TextBlock = getTextBlockAtPosition(relativePosition); + if(block == null) + block = getTextBlockAtPosition(--relativePosition); + var pos:int = getTextBlockStart(block); + if(pos < 0) + pos = 0; + return pos + block.findNextWordBoundary(relativePosition - pos); } static private var _defaultTabStops:Vector.<TabStop>; @@ -697,8 +1061,10 @@ package flashx.textLayout.elements _defaultTabStops[i] = new TabStop(TextAlign.START, defaultTabWidth * i); } - private function updateTextBlock():void + private function updateTextBlock(textBlock:TextBlock=null):void { + if(!textBlock) + textBlock = getTextBlock(); // find the ancestor with a container and use its format for various settings var containerElement:ContainerFormattedElement = getAncestorWithContainer(); if (!containerElement) @@ -746,10 +1112,10 @@ package flashx.textLayout.elements } CONFIG::debug { Debugging.traceFTECall(spaceJustifier,null,"new SpaceJustifier",_computedFormat.locale,lineJust,spaceJustifier.letterSpacing); } - _textBlock.textJustifier = spaceJustifier; - CONFIG::debug { Debugging.traceFTEAssign(_textBlock,"textJustifier",spaceJustifier); } - _textBlock.baselineZero = getLeadingBasis(this.getEffectiveLeadingModel()); - CONFIG::debug { Debugging.traceFTEAssign(_textBlock,"baselineZero",_textBlock.baselineZero); } + textBlock.textJustifier = spaceJustifier; + CONFIG::debug { Debugging.traceFTEAssign(textBlock,"textJustifier",spaceJustifier); } + textBlock.baselineZero = getLeadingBasis(this.getEffectiveLeadingModel()); + CONFIG::debug { Debugging.traceFTEAssign(textBlock,"baselineZero",textBlock.baselineZero); } } else { @@ -758,21 +1124,21 @@ package flashx.textLayout.elements eastAsianJustifier.composeTrailingIdeographicSpaces = true; } CONFIG::debug { Debugging.traceFTECall(eastAsianJustifier,null,"new EastAsianJustifier",_computedFormat.locale,lineJust,makeJustRuleStyle); } - _textBlock.textJustifier = eastAsianJustifier as EastAsianJustifier; - CONFIG::debug { Debugging.traceFTEAssign(_textBlock,"textJustifier",eastAsianJustifier); } - _textBlock.baselineZero = getLeadingBasis(this.getEffectiveLeadingModel()); - CONFIG::debug { Debugging.traceFTEAssign(_textBlock,"baselineZero",_textBlock.baselineZero); } + textBlock.textJustifier = eastAsianJustifier as EastAsianJustifier; + CONFIG::debug { Debugging.traceFTEAssign(textBlock,"textJustifier",eastAsianJustifier); } + textBlock.baselineZero = getLeadingBasis(this.getEffectiveLeadingModel()); + CONFIG::debug { Debugging.traceFTEAssign(textBlock,"baselineZero",textBlock.baselineZero); } } - _textBlock.bidiLevel = _computedFormat.direction == Direction.LTR ? 0 : 1; - CONFIG::debug { Debugging.traceFTEAssign(_textBlock,"bidiLevel",_textBlock.bidiLevel); } + textBlock.bidiLevel = _computedFormat.direction == Direction.LTR ? 0 : 1; + CONFIG::debug { Debugging.traceFTEAssign(textBlock,"bidiLevel",textBlock.bidiLevel); } - _textBlock.lineRotation = containerElementFormat.blockProgression == BlockProgression.RL ? TextRotation.ROTATE_90 : TextRotation.ROTATE_0; - CONFIG::debug { Debugging.traceFTEAssign(_textBlock,"lineRotation",_textBlock.lineRotation); } + textBlock.lineRotation = containerElementFormat.blockProgression == BlockProgression.RL ? TextRotation.ROTATE_90 : TextRotation.ROTATE_0; + CONFIG::debug { Debugging.traceFTEAssign(textBlock,"lineRotation",textBlock.lineRotation); } if (_computedFormat.tabStops && _computedFormat.tabStops.length != 0) { - //create a vector of TabStops and assign it to tabStops in _textBlock + //create a vector of TabStops and assign it to tabStops in textBlock var tabStops:Vector.<TabStop> = new Vector.<TabStop>(); CONFIG::debug { Debugging.traceFTECall(tabStops,null,"new Vector.<TabStop>()"); } for each(var tsa:TabStopFormat in _computedFormat.tabStops) @@ -786,8 +1152,8 @@ package flashx.textLayout.elements tabStops.push(tabStop); CONFIG::debug { Debugging.traceFTECall(null,tabStops,"push",tabStop); } } - _textBlock.tabStops = tabStops; - CONFIG::debug { Debugging.traceFTEAssign(_textBlock,"tabStops",tabStops); } + textBlock.tabStops = tabStops; + CONFIG::debug { Debugging.traceFTEAssign(textBlock,"tabStops",tabStops); } } else if (GlobalSettings.enableDefaultTabStops && !Configuration.playerEnablesArgoFeatures) { @@ -795,13 +1161,13 @@ package flashx.textLayout.elements // is true, TLF will set up default tabStops in the case where there are no tabs defined. if (_defaultTabStops == null) initializeDefaultTabStops(); - _textBlock.tabStops = _defaultTabStops; - CONFIG::debug { Debugging.traceFTEAssign(_textBlock,"tabStops",_defaultTabStops); } + textBlock.tabStops = _defaultTabStops; + CONFIG::debug { Debugging.traceFTEAssign(textBlock,"tabStops",_defaultTabStops); } } else { - _textBlock.tabStops = null; - CONFIG::debug { Debugging.traceFTEAssign(_textBlock,"tabStops",null); } + textBlock.tabStops = null; + CONFIG::debug { Debugging.traceFTEAssign(textBlock,"tabStops",null); } } } @@ -811,8 +1177,10 @@ package flashx.textLayout.elements if (!_computedFormat) { super.computedFormat; - if (_textBlock) - updateTextBlock(); + var tbs:Vector.<TextBlock> = getTextBlocks(); + for each(var tb:TextBlock in tbs) + updateTextBlock(tb); + } return _computedFormat; } @@ -820,7 +1188,7 @@ package flashx.textLayout.elements /** @private */ tlf_internal override function canOwnFlowElement(elem:FlowElement):Boolean { - return elem is FlowLeafElement || elem is SubParagraphGroupElementBase; + return elem is FlowLeafElement || elem is SubParagraphGroupElementBase || elem is TableElement; } /** @private */ @@ -884,32 +1252,6 @@ package flashx.textLayout.elements } } - // mjzhang : new API for table feature, to discuss - public function isInTable():Boolean - { - var parent:FlowElement = this.parent; - while ( parent ) - { - if ( (parent is TableDataCellElement) ) - return true; - parent = parent.parent; - } - - return false; - } - - public function getTableDataCellElement():TableDataCellElement - { - var parent:FlowElement = this.parent; - while ( parent ) - { - if ( (parent is TableDataCellElement) ) - return parent as TableDataCellElement; - parent = parent.parent; - } - - return null; - } /** @private */ tlf_internal function getEffectiveLeadingModel():String { @@ -938,19 +1280,20 @@ package flashx.textLayout.elements /** @private */ CONFIG::debug public override function debugCheckFlowElement(depth:int = 0, extraData:String = ""):int { - var rslt:int = super.debugCheckFlowElement(depth," fte:"+getDebugIdentity(_textBlock)+" "+extraData); + var tb:TextBlock = getTextBlock(); + var rslt:int = super.debugCheckFlowElement(depth," fte:"+getDebugIdentity(tb)+" "+extraData); // now check the character count and then the last character - if (_textBlock) + if (tb) { - var contentLength:int = _textBlock.content && _textBlock.content.rawText ? _textBlock.content.rawText.length : 0; + var contentLength:int = tb.content && tb.content.rawText ? tb.content.rawText.length : 0; rslt += assert(contentLength == textLength,"Bad paragraph length mode:"+textLength.toString()+" _textBlock:" + contentLength.toString()); - var groupElement:GroupElement = _textBlock.content as GroupElement; + var groupElement:GroupElement = tb.content as GroupElement; if (groupElement) assert(groupElement.elementCount == numChildren,"Mismatched group and elementCount"); - else if (_textBlock.content) + else if (tb.content) assert(1 == numChildren,"Mismatched group and elementCount"); else assert(0 == numChildren,"Mismatched group and elementCount"); @@ -1013,5 +1356,11 @@ package flashx.textLayout.elements { return _interactiveChildrenCount != 0 ; } + + tlf_internal function get terminatorSpan():SpanElement + { + return _terminatorSpan; + } + } }
http://git-wip-us.apache.org/repos/asf/flex-tlf/blob/33df98ab/textLayout/src/flashx/textLayout/elements/SpanElement.as ---------------------------------------------------------------------- diff --git a/textLayout/src/flashx/textLayout/elements/SpanElement.as b/textLayout/src/flashx/textLayout/elements/SpanElement.as index c16adff..9e37058 100644 --- a/textLayout/src/flashx/textLayout/elements/SpanElement.as +++ b/textLayout/src/flashx/textLayout/elements/SpanElement.as @@ -395,6 +395,8 @@ package flashx.textLayout.elements assert(_blockElement.rawText.charAt(_blockElement.rawText.length-1) != SpanElement.kParagraphTerminator,"adding para terminator twice"); } + if(_text && _text.substr(-1) == SpanElement.kParagraphTerminator)// terminator exists. Bail out. + return; replaceTextInternal(textLength,textLength,SpanElement.kParagraphTerminator); CONFIG::debug @@ -414,6 +416,9 @@ package flashx.textLayout.elements assert(_text && _text.length && _text.charAt(_text.length-1) == SpanElement.kParagraphTerminator, "attempting to remove para terminator when it doesn't exist"); } + if(!_text || _text.substr(-1) != SpanElement.kParagraphTerminator)// no terminator exists. Bail out. + return; + replaceTextInternal(textLength-1,textLength,""); modelChanged(ModelChange.TEXT_DELETED,this,textLength > 0 ? textLength-1 : 0,1); } @@ -464,7 +469,7 @@ package flashx.textLayout.elements { // optimized version leverages player APIs // TODO: Jeff to add split on TextElement so we don't have to go find a group every time - var group:GroupElement = parent.createContentAsGroup(); + var group:GroupElement = parent.createContentAsGroup(getElementRelativeStart(parent)); var elementIndex:int = group.getElementIndex(_blockElement); http://git-wip-us.apache.org/repos/asf/flex-tlf/blob/33df98ab/textLayout/src/flashx/textLayout/elements/SubParagraphGroupElementBase.as ---------------------------------------------------------------------- diff --git a/textLayout/src/flashx/textLayout/elements/SubParagraphGroupElementBase.as b/textLayout/src/flashx/textLayout/elements/SubParagraphGroupElementBase.as index c65e138..2affda3 100644 --- a/textLayout/src/flashx/textLayout/elements/SubParagraphGroupElementBase.as +++ b/textLayout/src/flashx/textLayout/elements/SubParagraphGroupElementBase.as @@ -190,7 +190,7 @@ package flashx.textLayout.elements } /** @private */ - tlf_internal override function createContentAsGroup():GroupElement + tlf_internal override function createContentAsGroup(pos:int=0):GroupElement { return groupElement; } /** @private */ http://git-wip-us.apache.org/repos/asf/flex-tlf/blob/33df98ab/textLayout/src/flashx/textLayout/elements/TableColElement.as ---------------------------------------------------------------------- diff --git a/textLayout/src/flashx/textLayout/elements/TableColElement.as b/textLayout/src/flashx/textLayout/elements/TableColElement.as index 85a1d9e..342b9ce 100644 --- a/textLayout/src/flashx/textLayout/elements/TableColElement.as +++ b/textLayout/src/flashx/textLayout/elements/TableColElement.as @@ -18,6 +18,7 @@ //////////////////////////////////////////////////////////////////////////////// package flashx.textLayout.elements { + import flashx.textLayout.formats.ITextLayoutFormat; import flashx.textLayout.tlf_internal; use namespace tlf_internal; @@ -36,6 +37,15 @@ package flashx.textLayout.elements { //public var height:Number; public var x:Number; + public var colIndex:int; + + public function TableColElement(format:ITextLayoutFormat=null) + { + super(); + if(format) + this.format = format; + } + /** @private */ override protected function get abstract():Boolean @@ -56,6 +66,29 @@ package flashx.textLayout.elements { super.modelChanged(changeType,elem,changeStart,changeLen,needNormalize,bumpGeneration); } - + + /** + * Get a Vector of cells or null if the column contains no cells + **/ + public function get cells():Vector.<TableCellElement> { + + if (!table) { + return null; + } + + return table.getCellsForColumn(this); + } + + /** + * Returns the number of cells in this column. + **/ + public function get numCells():int { + + if (!table) { + return 0; + } + + return table.getCellsForColumn(this).length; + } } } http://git-wip-us.apache.org/repos/asf/flex-tlf/blob/33df98ab/textLayout/src/flashx/textLayout/elements/TableElement.as ---------------------------------------------------------------------- diff --git a/textLayout/src/flashx/textLayout/elements/TableElement.as b/textLayout/src/flashx/textLayout/elements/TableElement.as index 92cefcc..ed82a28 100644 --- a/textLayout/src/flashx/textLayout/elements/TableElement.as +++ b/textLayout/src/flashx/textLayout/elements/TableElement.as @@ -18,19 +18,17 @@ //////////////////////////////////////////////////////////////////////////////// package flashx.textLayout.elements { - import flash.display.Graphics; - import flash.events.Event; - import flash.events.EventDispatcher; - import flash.events.IEventDispatcher; - import flash.events.MouseEvent; - import flash.geom.Point; - import flash.text.engine.TextBlock; - import flash.text.engine.TextLine; + import flash.display.Sprite; + import flash.text.engine.ContentElement; + import flash.text.engine.GraphicElement; + import flash.utils.Dictionary; - import flashx.textLayout.events.FlowElementEventDispatcher; - import flashx.textLayout.events.FlowElementMouseEventManager; + import flashx.textLayout.compose.TextFlowTableBlock; + import flashx.textLayout.edit.SelectionFormat; import flashx.textLayout.events.ModelChange; - import flashx.textLayout.formats.*; + import flashx.textLayout.formats.FormatValue; + import flashx.textLayout.formats.ITextLayoutFormat; + import flashx.textLayout.formats.TextLayoutFormat; import flashx.textLayout.tlf_internal; use namespace tlf_internal; @@ -41,7 +39,6 @@ package flashx.textLayout.elements * A TableElement's children must be of type TableRowElement, TableColElement, TableColGroupElement, TableBodyElement. * * - * * @playerversion Flash 10 * @playerversion AIR 1.5 * @langversion 3.0 @@ -49,41 +46,38 @@ package flashx.textLayout.elements */ public class TableElement extends TableFormattedElement { - private var _row:int; - private var _column:int; - private var _height:Array = []; // parcel-indexed - public var computedWidth:Number; + private var _computedWidth:Number; public var x:Number; public var y:Number; - //These attributes is from the original loop prototype. Maybe changed later - public var totalRowDepth:Number = undefined; - public var originParcelIndex:Number; - public var numAcrossParcels:int; - public var curRowIdx:int = 0; // this value should be only used while composing - public var outOfLastParcel:Boolean = false; - - private var arColumn:Array = []; + private var columns:Vector.<TableColElement> = new Vector.<TableColElement>(); + private var rows:Vector.<TableRowElement> = new Vector.<TableRowElement>(); + private var damagedColumns:Vector.<TableColElement> = new Vector.<TableColElement>(); + private var damageRows:Vector.<TableRowElement> = new Vector.<TableRowElement>(); + private var _hasCellDamage:Boolean = true; + + private var _headerRowCount:uint = 0; + private var _footerRowCount:uint = 0; + private var _tableRowsComputed:Boolean; + + private var _headerRows:Vector.< Vector.<TableCellElement> >; + private var _footerRows:Vector.< Vector.<TableCellElement> >; + private var _bodyRows:Vector.< Vector.<TableCellElement> >; + private var _composedRowIndex:uint = 0; + + private var _tableBlocks:Vector.<TextFlowTableBlock>; + private var _tableBlockIndex:uint = 0; + private var _tableBlockDict:Dictionary; + + private var _leaf:TableLeafElement; public function TableElement() { super(); } - public function initTableElement(row:Number, column:Number):void - { - _row = row; - _column = column; - - for ( var i:int = 0; i < column; i ++ ) - { - var col:TableColElement = new TableColElement(); - arColumn[i] = col; - } - } - /** @private */ override protected function get abstract():Boolean { return false; } @@ -95,35 +89,714 @@ package flashx.textLayout.elements /** @private */ tlf_internal override function canOwnFlowElement(elem:FlowElement):Boolean { - return (elem is TableBodyElement) || (elem is TableRowElement) || (elem is TableColElement) || (elem is TableColGroupElement); + return (elem is TableCellElement) || (elem is TableRowElement) || (elem is TableColElement);// || (elem is TableBodyElement) || (elem is TableColGroupElement); } /** @private if its in a numbered list expand the damage to all list items - causes the numbers to be regenerated */ tlf_internal override function modelChanged(changeType:String, elem:FlowElement, changeStart:int, changeLen:int, needNormalize:Boolean = true, bumpGeneration:Boolean = true):void { + if (changeType==ModelChange.ELEMENT_ADDED) { + + } + else if (changeType==ModelChange.ELEMENT_REMOVAL) { + if (headerRowCount > 0 || footerRowCount > 0) { + + } + } + super.modelChanged(changeType,elem,changeStart,changeLen,needNormalize,bumpGeneration); } - public function get row():int + override public function set cellSpacing(cellSpacingValue:*):void + { + + markCellsDamaged(); + hasCellDamage = true; + normalizeCells(); + + super.cellSpacing = cellSpacingValue; + } + + public function get numRows():int + { + return rows.length; + } + + public function get numColumns():int + { + return columns.length; + } + + /** + * Total number of cells + **/ + public function get numCells():int { - return _row; + return getCells().length; } - public function get column():int + /** + * Total number of rows in the table. If set to a value lower than + * the current number of rows the rows at the end of the table are removed. + * If the set to a value greater than the current number of rows additional + * rows are added to the table. + **/ + public function set numRows(value:int):void { - return _column; + while(value < numRows){ + rows.pop(); + } + var num:int = numRows; + for(var i:int = num;i<value;i++) { + var row:TableRowElement = createRowElement(i, defaultRowFormat); + rows.push(row); + } } + /** + * Total number of columns in the table. If set to a value lower than + * the current number of columns the columns at the end of the table are removed. + * If the set to a value greater than the current number of columns additional + * columns are added to the table. + **/ + public function set numColumns(value:int):void + { + while(value < numColumns){ + columns.pop(); + } + var num:int = numColumns; + for(var i:int = num;i<value;i++) { + var column:TableColElement = createColumnElement(i, defaultColumnFormat); + columns.push(column); + } + } + private var _defaultRowFormat:ITextLayoutFormat; + + /** + * Gets the row format for new rows. + **/ + public function get defaultRowFormat():ITextLayoutFormat + { + if(!_defaultRowFormat) + _defaultRowFormat = new TextLayoutFormat(computedFormat); + return _defaultRowFormat; + } + + public function set defaultRowFormat(value:ITextLayoutFormat):void + { + _defaultRowFormat = value; + } + + private var _defaultColumnFormat:ITextLayoutFormat; + + /** + * Gets the column format for new columns. + **/ + public function get defaultColumnFormat():ITextLayoutFormat + { + if(!_defaultColumnFormat) + _defaultColumnFormat = new TextLayoutFormat(computedFormat); + return _defaultColumnFormat; + } + + public function set defaultColumnFormat(value:ITextLayoutFormat):void + { + _defaultColumnFormat = value; + } + + /** + * Adds a table cell element to the table. + * @inheritDoc + **/ + override public function addChild(child:FlowElement):FlowElement + { + + if (child is TableFormattedElement) { + TableFormattedElement(child).table = this; + } + + super.addChild(child); + + return child; + } + + /** + * Removes a table cell element from the table. + * @inheritDoc + **/ + override public function removeChild(child:FlowElement):FlowElement + { + super.removeChild(child); + + if (child is TableFormattedElement) { + TableFormattedElement(child).table = null; + } + + return child; + } + + /** + * Add a row at the end of the table. You would use this if you want to add a row + * without changing the table cells. + * @see addRowAt + * @see insertRow + * @see insertRowAt + **/ + public function addRow(format:ITextLayoutFormat=null):void{ + addRowAt(rows.length,format); + } + + /** + * Add a row at the index specified. + * @see addRow + * @see insertRow + * @see insertRowAt + **/ + public function addRowAt(idx:int, format:ITextLayoutFormat=null):void{ + if(idx < 0 || idx > rows.length) + throw RangeError(GlobalSettings.resourceStringFunction("badPropertyValue")); + + var row:TableRowElement = createRowElement(idx, format); + rows.splice(idx, 0, row); + row.composedHeight = row.computedFormat.minCellHeight; + row.isMaxHeight = row.computedFormat.minCellHeight == row.computedFormat.maxCellHeight; + row.setParentAndRelativeStartOnly(this, 1); + } + + /** + * Adds a column. You would use this if you want to add a column without changing the table cells. + * The cells would reflow, so a cell in row 2 might move up to row 1. + * @see addColumnAt + * @see insertColumn + * @see insertColumnAt + **/ + public function addColumn(format:ITextLayoutFormat=null):void{ + addColumnAt(columns.length,format); + } + + /** + * Adds a column at the index specified. + * @see addColumn + * @see insertColumn + * @see insertColumnAt + **/ + public function addColumnAt(idx:int, format:ITextLayoutFormat=null):void{ + if(idx < 0 || idx > columns.length) + throw RangeError(GlobalSettings.resourceStringFunction("badPropertyValue")); + if(!format) { + format = defaultColumnFormat; + } + var column:TableColElement = createColumnElement(idx, format); + + columns.splice(idx, 0, column); + } + + /** + * Returns the column at the index specified or null if the index is out of range. + **/ public function getColumnAt(columnIndex:int):TableColElement { - if ( columnIndex < 0 || columnIndex >= _column ) + if ( columnIndex < 0 || columnIndex >= numColumns ) + return null; + return columns[columnIndex]; + } + + /** + * Returns the row at the index specified or null if the index is out of range. + **/ + public function getRowAt(rowIndex:int):TableRowElement + { + if ( rowIndex < 0 || rowIndex >= numRows ) + return null; + return rows[rowIndex]; + } + + /** + * Return the index of the row provided or -1 if the row is not found. + **/ + public function getRowIndex(row:TableRowElement):int + { + for(var i:int=0;i<rows.length;i++) + { + if(rows[i] == row) + return i; + } + return -1; + } + + /** + * Returns a vector of the cells for the row specified. + **/ + public function getCellsForRow(row:TableRowElement):Vector.<TableCellElement>{ + + return getCellsForRowAt(row.rowIndex); + } + + /** + * Returns a vector of the cells for the row specified. + **/ + public function getCellsForRowArray(row:TableRowElement):Array { + + return getCellsForRowAtArray(row.rowIndex); + } + + /** + * Returns a vector of the cells for the row at the specified index. + **/ + public function getCellsForRowAt(index:int):Vector.<TableCellElement>{ + var cells:Vector.<TableCellElement> = new Vector.<TableCellElement>(); + + if (index < 0) { + return cells; + } + + for each(var cell:TableCellElement in mxmlChildren){ + if (cell.rowIndex == index) { + cells.push(cell); + } + } + + return cells; + } + + /** + * Returns an array of the cells for the row specified. + **/ + public function getCellsForRowAtArray(index:int):Array { + var cells:Array = []; + + if (index < 0) { + return cells; + } + + for each(var cell:TableCellElement in mxmlChildren){ + if (cell.rowIndex == index) { + cells.push(cell); + } + } + + return cells; + } + + /** + * Returns a Vector of the TableCellElements for the column specified. + **/ + public function getCellsForColumn(column:TableColElement):Vector.<TableCellElement> { + if(columns.indexOf(column) < 0) + return null; + + return getCellsForColumnAt(column.colIndex); + } + + /** + * Returns a Vector of the TableCellElements for the column at the specified index. + **/ + public function getCellsForColumnAt(index:int):Vector.<TableCellElement> { + var cells:Vector.<TableCellElement> = new Vector.<TableCellElement>(); + + if (index < 0) { + return cells; + } + + for each(var cell:TableCellElement in mxmlChildren){ + if (cell.colIndex == index) { + cells.push(cell); + } + } + + return cells; + } + + /** + * Inserts a column at the end of the table. If a column is not provided one is created. + * + * @see addColumn + * @see addColumnAt + * @see insertColumnAt + **/ + public function insertColumn(column:TableColElement=null,cells:Array = null):Boolean{ + return insertColumnAt(numColumns,column,cells); + } + + /** + * Inserts a column at the column specified. If the column is not provided it + * creates a new column containing the cells supplied or creates the cells + * based on the number of rows in the table. + * @see addColumn + * @see addColumnAt + * @see insertColumn + **/ + public function insertColumnAt(idx:int,column:TableColElement=null,cells:Array = null):Boolean{ + + if (idx < 0 || idx > columns.length) { + throw RangeError(GlobalSettings.resourceStringFunction("badPropertyValue")); + } + + if (!column) { + column = createColumnElement(idx, defaultColumnFormat); + } + + columns.splice(idx,0,column); + + var blockedCoords:Vector.<CellCoords> = getBlockedCoords(-1,idx); + var cellIdx:int = getCellIndex(0,idx); + if(cellIdx < 0) + cellIdx = numChildren; + var rowIdx:int = 0; + + if (cells==null) cells = []; + + while(cells.length < numRows){ + cells.push(new TableCellElement()); + } + + for each(var cell:TableCellElement in cells){ + while(blockedCoords.length && blockedCoords[0].row == rowIdx){ + rowIdx++; + blockedCoords.shift(); + } + cellIdx = getCellIndex(rowIdx,idx); + if(cellIdx < 0) + cellIdx = numChildren; + + if(rowIdx < numRows){ + addChildAt(cellIdx,cell); + } + } + + + return true; + } + + /** + * Inserts a row at the end of the table. If a row is not provided one is created. + * @see insertRowAt + **/ + public function insertRow(row:TableRowElement=null,cells:Array = null):Boolean{ + return insertRowAt(numRows,row,cells); + } + + /** + * Inserts a row at the index specified. If the row is not provided it + * creates a new row containing the cells supplied or creates the cells + * based on the number of columns in the table. + **/ + public function insertRowAt(idx:int,row:TableRowElement=null,cells:Array = null):Boolean{ + if (idx < 0 || idx > rows.length) { + throw RangeError(GlobalSettings.resourceStringFunction("badPropertyValue")); + } + + if (!row) { + row = createRowElement(idx, defaultRowFormat); + } + + rows.splice(idx,0,row); + row.composedHeight = row.computedFormat.minCellHeight; + row.isMaxHeight = row.computedFormat.minCellHeight == row.computedFormat.maxCellHeight; + + var blockedCoords:Vector.<CellCoords> = getBlockedCoords(idx); + var cellIdx:int = getCellIndex(idx,0); + if(cellIdx < 0) + cellIdx = numChildren; + + var colIdx:int = 0; + + if (cells==null) cells = []; + + // create more cells + while(cells.length < numColumns){ + cells.push(new TableCellElement()); + } + + for each(var cell:TableCellElement in cells){ + while(blockedCoords.length && blockedCoords[0].column == colIdx){ + colIdx++; + blockedCoords.shift(); + } + if(colIdx < numColumns){ + addChildAt(cellIdx++,cell); + } + } + return true; + } + + /** + * Removes the row + **/ + public function removeRow(row:TableRowElement):TableRowElement { + var i:int = rows.indexOf(row); + if(i < 0) + return null; + return removeRowAt(i); + } + + /** + * Removes the row and the cells it contains. + **/ + public function removeRowWithContent(row:TableRowElement):Array + { + var i:int = rows.indexOf(row); + if(i < 0) return null; - return arColumn[columnIndex]; + return removeRowWithContentAt(i); + } + + /** + * Removes the row at the index specified. + * @see removeRowWithContentAt + **/ + public function removeRowAt(idx:int):TableRowElement { + if(idx < 0 || idx > rows.length - 1) + return null; + + var row:TableRowElement = TableRowElement(rows.splice(idx,1)[0]); + normalizeCells(); + hasCellDamage = true; + return row; + + } + + /** + * Removes the row at the index specified and the cells it contains. + **/ + public function removeRowWithContentAt(idx:int):Array + { + + var removedCells:Array = []; + + if(mxmlChildren){ + for (var i:int = mxmlChildren.length-1;i>=0;i--){ + var child:* = mxmlChildren[i]; + if(!(child is TableCellElement)) + continue; + var cell:TableCellElement = child as TableCellElement; + if(cell.rowIndex == idx){ + removedCells.unshift(removeChild(cell)); + } + } + } + + removeRowAt(idx); + return removedCells; + } + + /** + * Removes all the rows and the cells. + **/ + public function removeAllRowsWithContent():void + { + var rowCount:int; + var cellCount:int; + + if (numRows>-1) { + rowCount = numRows-1; + + for (;rowCount>-1;) { + removeRowWithContentAt(rowCount--); + } + + } + } + + /** + * Removes all the rows. Does not remove the cells. + * @see removeAllRowsWithContent + **/ + public function removeAllRows():void + { + var rowCount:int; + var cellCount:int; + + if (numRows>-1) { + rowCount = numRows; + + for (var i:int; i < rowCount; i++) { + removeRowAt(i); + } + + } + } + + /** + * Removes the column + **/ + public function removeColumn(column:TableColElement):TableColElement { + var i:int = columns.indexOf(column); + if(i < 0) + return null; + return removeColumnAt(i); + } + + /** + * Removes the column and the cells it contains. + **/ + public function removeColumnWithContent(column:TableColElement):Array + { + var i:int = columns.indexOf(column); + if(i < 0) + return null; + return removeColumnWithContentAt(i); + } + + /** + * Removes the column at the index specified + **/ + public function removeColumnAt(idx:int):TableColElement { + if(idx < 0 || idx > columns.length - 1) + return null; + + var col:TableColElement = columns.splice(idx,1)[0]; + normalizeCells(); + hasCellDamage = true; + return col; + } + + /** + * Removes the column at the index specified and the cells it contains. + **/ + public function removeColumnWithContentAt(idx:int):Array + { + + var removedCells:Array = []; + if(mxmlChildren){ + for (var i:int = mxmlChildren.length-1;i>=0;i--){ + var child:* = mxmlChildren[i]; + if(!(child is TableCellElement)) + continue; + var cell:TableCellElement = child as TableCellElement; + if(cell.colIndex == idx){ + removedCells.unshift(removeChild(cell)); + } + } + } + removeColumnAt(idx); + + return removedCells; + } + + /** + * Remove all cells + * @inheritDoc + **/ + override tlf_internal function removed():void + { + hasCellDamage = true; + //removeAllRowsWithContent(); + } + + /** + * @private + * Gets table coordinates which represents the space occupied by cells spanning rows or columns + **/ + private function getBlockedCoords(inRow:int = -1, inColumn:int = -1):Vector.<CellCoords>{ + var coords:Vector.<CellCoords> = new Vector.<CellCoords>(); + + if(mxmlChildren) { + for each(var child:* in mxmlChildren){ + var cell:TableCellElement = child as TableCellElement; + if (cell==null) continue; + if(cell.columnSpan == 1 && cell.rowSpan == 1) + continue; + var curRow:int = cell.rowIndex; + if(inRow >= 0 && curRow != inRow) + continue; + if(inColumn >= 0 && inColumn != curColumn) + continue; + var curColumn:int = cell.colIndex; + var endRow:int = curRow + cell.rowSpan - 1; + var endColumn:int = curColumn + cell.columnSpan -1; + for(var rowIdx:int = curRow;rowIdx <= endRow;rowIdx++){ + for(var colIdx:int = curColumn;colIdx <=endColumn;colIdx++){ + if(rowIdx == curRow && colIdx == curColumn){ + continue; + } + coords.push( new CellCoords(colIdx, rowIdx) ); + } + } + + } + } + return coords; } + /** + * Sets the row and column indices of the cells in the table to match their logical position as described by the table columns and rows + **/ + public function normalizeCells():void + { + this.numColumns;this.numRows; + var i:int; + var blockedCoords:Vector.<CellCoords> = new Vector.<CellCoords>(); + + if (!mxmlChildren) { + return; + } + + var curRow:int = 0; + var curColumn:int = 0; + + for each(var child:* in mxmlChildren) { + + if (!(child is TableCellElement)) { + continue; + } + + var cell:TableCellElement = child as TableCellElement; + + if (cell.rowIndex != curRow || cell.colIndex != curColumn) { + cell.rowIndex = curRow; + cell.colIndex = curColumn; + cell.damage(); + } + + // add blocked coords if the cell spans rows or columns + var endRow:int = curRow + cell.rowSpan - 1; + var endColumn:int = curColumn + cell.columnSpan -1; + + for(var rowIdx:int = curRow;rowIdx <= endRow;rowIdx++){ + for(var colIdx:int = curColumn;colIdx <=endColumn;colIdx++){ + if(rowIdx == curRow && colIdx == curColumn){ + continue; + } + blockedCoords.push(new CellCoords(colIdx,rowIdx) ); + } + } + + // advance coordinates while checking blocked ones from spans + do { + curColumn++; + + if (curColumn >= numColumns){ + curColumn = 0; + curRow++; + } + + var advanced:Boolean = true; + + for (i=0;i<blockedCoords.length;i++){ + if(blockedCoords[i].column == curColumn && blockedCoords[i].row == curRow){ + advanced = false; + blockedCoords.splice(i,1); + } + } + + if (advanced) { + break; + } + + } while(1); + + } + + } + + /** + * Set the width of the specified column. The value can be a number or percent. + **/ public function setColumnWidth(columnIndex:int, value:*):Boolean { - var tableColElement:TableColElement = getColumnAt(columnIndex) as TableColElement; + //TODO: changing the column width probably requires a recompose of all cells in that column. Mark the cells in that row damaged. + var tableColElement:TableColElement = getColumnAt(columnIndex); if ( ! tableColElement ) return false; @@ -131,6 +804,22 @@ package flashx.textLayout.elements return true; } + /** + * Set the height of the specified row. The value can be a number or percent. + **/ + public function setRowHeight(rowIdx:int, value:*):Boolean{ + //TODO: setting the row height might change the composition height of the cells. We'll need to do some housekeeping here. + // I'm not sure this function makes sense. We need to handle both min and max values to allow for expanding cells. + var row:TableRowElement = getRowAt(rowIdx); + if(!row) + return false; + + return true; + } + + /** + * Get the width of the column. + **/ public function getColumnWidth(columnIndex:int):* { var tableColElement:TableColElement = getColumnAt(columnIndex) as TableColElement; @@ -138,26 +827,712 @@ package flashx.textLayout.elements return tableColElement.tableColumnWidth; return 0; } - - public function get height():Number - { - return _height[numAcrossParcels]; - } - - public function set height(val:*):void - { - _height[numAcrossParcels] = val; - } - - public function get heightArray():Array - { - return _height; - } - - public function set heightArray(newArray:Array):void - { - _height = newArray; - } + /** + * Sizes and positions the cells in the table. + **/ + public function composeCells():void{ + normalizeCells(); + _composedRowIndex = 0; + + // make sure the height that defines the row height did not change. If it did we might need to change the row height. + if(!hasCellDamage) + return; + var damagedCells:Vector.<TableCellElement> = getDamagedCells(); + var cell:TableCellElement; + + for each(cell in damagedCells){ + // recompose the cells while tracking row height if necessary + cell.compose(); + } + + // set row heights to minimum + for each (var row:TableRowElement in rows){ + var minH:Number = row.computedFormat.minCellHeight; + var maxH:Number = row.computedFormat.maxCellHeight; + row.totalHeight = row.composedHeight = minH; + if(maxH > minH) + row.isMaxHeight = false; + else + row.isMaxHeight = true; + + } + + // set column positions... + var xPos:Number = 0; + for each (var col:TableColElement in columns){ + col.x = xPos; + xPos += col.columnWidth; + } + + if (mxmlChildren) { + for(var i:int=0;i<mxmlChildren.length;i++){ + if( !(mxmlChildren[i] is TableCellElement) ) + continue; + cell = mxmlChildren[i] as TableCellElement; + while(rows.length < cell.rowIndex+1){ + addRow(defaultRowFormat); + } + row = getRowAt(cell.rowIndex); + if(!row) + throw new Error("this should not happen..."); + if(row.isMaxHeight) { + continue; + } + + var cellHeight:Number = cell.getComposedHeight(); + if(cell.rowSpan > 1) + { + // figure out the total height taking into account fixed height rows and the total span. + + // for now, we're taking the easy way out assuming the rows are not fixed... + row.totalHeight = Math.max(row.totalHeight, cellHeight); + + } + else + { + row.composedHeight = Math.max(row.composedHeight, cellHeight); + row.composedHeight = Math.min(row.composedHeight, row.computedFormat.maxCellHeight); + row.totalHeight = Math.max(row.composedHeight, row.totalHeight); + } + if(row.composedHeight == row.computedFormat.maxCellHeight) + row.isMaxHeight = true; + } + } + + + if(!_tableRowsComputed) + { + // create arrays or rows to make table composition simpler + // For now we're assuming all cells have the correct row and column indices. + // For this assumption to remain valid, the interaction manager will have to update all indices when inserting rows and columns. + // actually, it probably makes sense for TableElement to handle that when adding rows and columns. + // we need to think this through. + _bodyRows = new Vector.< Vector.<TableCellElement> >(); + + if (mxmlChildren) { + for(i=0;i<mxmlChildren.length;i++){ + + if ( !(mxmlChildren[i] is TableCellElement) ) { + continue; + } + + cell = mxmlChildren[i] as TableCellElement; + + while(cell.rowIndex >= _bodyRows.length) + _bodyRows.push(new Vector.<TableCellElement>()); + + var rowVec:Vector.<TableCellElement> = _bodyRows[cell.rowIndex] as Vector.<TableCellElement>; + + if(!rowVec){ + rowVec = new Vector.<TableCellElement>(); + _bodyRows[cell.rowIndex] = rowVec; + } + + if(rowVec.length > cell.colIndex && rowVec[cell.colIndex]) { + throw new Error("Two cells cannot have the same coordinates"); + } + + rowVec.push(cell); + } + } + + if(headerRowCount > 0){ + _headerRows = _bodyRows.splice(0,headerRowCount); + } else { + _headerRows = null; + } + + if(footerRowCount > 0){ + _footerRows = _bodyRows.splice(-footerRowCount,Number.MAX_VALUE); + } else { + _footerRows = null; + } + } + } + + /** + * returns the header rows for composition + **/ + public function getHeaderRows():Vector.< Vector.<TableCellElement> >{ + return _headerRows; + } + + /** + * returns the footer rows for composition + **/ + public function getFooterRows():Vector.< Vector.<TableCellElement> >{ + return _footerRows; + } + + /** + * returns the body rows (sans header and footer cells) for composition + **/ + public function getBodyRows():Vector.< Vector.<TableCellElement> >{ + return _bodyRows; + } + + /** + * returns a vector of table cells in the next row during composition + **/ + public function getNextRow():Vector.<TableCellElement>{ + if(_composedRowIndex >= _bodyRows.length) + return null; + return _bodyRows[_composedRowIndex++]; + } + + /** + * Returns the next table cell after the supplied table cell + **/ + public function getNextCell(tableCell:TableCellElement):TableCellElement { + var cell:TableCellElement; + + for each (var element:FlowElement in mxmlChildren) { + cell = element as TableCellElement; + + if (cell) { + + // get next cell in same row + if (cell.rowIndex==tableCell.rowIndex && cell.colIndex-1==tableCell.colIndex) { + return cell; + } + + // get first cell in next row + if (cell.rowIndex-1==tableCell.rowIndex && cell.colIndex==0) { + return cell; + } + + } + } + + return null; + } + + /** + * Returns the previous table cell after the supplied table cell + **/ + public function getPreviousCell(tableCell:TableCellElement):TableCellElement { + var cell:TableCellElement; + var highestCellIndex:int = -1; + var rowIndex:int = -1; + + for each (var element:FlowElement in mxmlChildren) { + cell = element as TableCellElement; + + if (cell) { + + // get previous cell in same row + if (cell.rowIndex==tableCell.rowIndex && cell.colIndex+1==tableCell.colIndex) { + return cell; + } + + // get last cell in previous row + if (cell.rowIndex+1==tableCell.rowIndex) { + rowIndex = cell.rowIndex; + + if (highestCellIndex<cell.colIndex) { + highestCellIndex = cell.colIndex; + } + } + + } + } + + if (rowIndex>-1 && highestCellIndex>-1) { + return getCellAt(rowIndex, highestCellIndex); + } + + return null; + } + + /** + * Returns the table cell at the row and column specified. + **/ + public function getCellAt(rowIndex:int, columnIndex:int):TableCellElement { + var cell:TableCellElement; + + for each (var element:FlowElement in mxmlChildren) { + cell = element as TableCellElement; + + if (cell && cell.rowIndex==rowIndex && cell.colIndex==columnIndex) { + return cell; + } + } + + return null; + } + + /** + * Computed height of the header cells + **/ + public function getHeaderHeight():Number{ + //TODO: compute the header height from the header cells + return 0; + } + + /** + * Computed height of the footer cells + **/ + public function getFooterHeight():Number{ + //TODO: compute the footer height from the footer cells + return 0; + + } + + /** + * Accepts a suggested table width and calculates the column widths. + **/ + public function normalizeColumnWidths(suggestedWidth:Number = 600):void{ + //TODO: before composition make sure all column widths are rational numbers + // We feed in a width to use if there's no width otherwise specified. + + // quick and dirty... + var setCount:* = computedFormat.columnCount; + if(!setCount){ + // we need to figure this out... + } else if(setCount == FormatValue.AUTO){ + // figure out... + } else { + var cCount:Number = computedFormat.columnCount; + } + + while (cCount > columns.length){ + addColumn(); + } + + var w:Number; + switch(typeof(computedFormat.tableWidth)){ + case "number": + w = suggestedWidth; + break; + case "string": + if(computedFormat.tableWidth.indexOf("%") > 0){ + w = suggestedWidth / (parseFloat(computedFormat.tableWidth)/100); + break; + } + default: + w = suggestedWidth; + break; + } + if(isNaN(w)) + w = 600; + if(w > 20000) + w = 600; + + _computedWidth = w; + + var numNonsetColumns:int = numColumns; + var col:TableColElement; + for each(col in columns){ + // simply stomp on the settings. (need to finesse this...) + if(typeof(col.columnWidth) == "number") + { + w-= col.columnWidth; + numNonsetColumns--; + } + } + + for each(col in columns) + { + // simply stomp on the settings. (need to finesse this...) + if(typeof(col.columnWidth) == "number") + continue; + col.columnWidth = w / numNonsetColumns; + } + } + + /** + * Returns a vector of all the damaged cells in the table. + **/ + private function getDamagedCells():Vector.<TableCellElement>{ + var cells:Vector.<TableCellElement> = new Vector.<TableCellElement>(); + for each (var cell:* in this.mxmlChildren){ + if((cell is TableCellElement) && cell.isDamaged()) + cells.push(cell as TableCellElement); + } + return cells; + } + + /** + * Marks all of the cells in the table as damaged. + **/ + private function markCellsDamaged():void { + if (!mxmlChildren) return; + + for each (var cell:* in this.mxmlChildren){ + if (cell is TableCellElement) { + cell.damage(); + } + } + } + + /** + * Returns a vector of all the table cell elements in the table. + **/ + public function getCells():Vector.<TableCellElement> { + var cells:Vector.<TableCellElement> = new Vector.<TableCellElement>(); + + for each (var cell:* in mxmlChildren){ + if (cell is TableCellElement) { + cells.push(cell as TableCellElement); + } + } + + return cells; + } + + /** + * Returns an array of all the table cells. + **/ + public function getCellsArray():Array { + var cells:Array = []; + + for each (var cell:* in mxmlChildren){ + if (cell is TableCellElement) { + cells.push(cell as TableCellElement); + } + } + + return cells; + } + + /** + * Returns the table width + **/ + public function get width():Number + { + return _computedWidth; + } + + /** + * Sets the table width + **/ + public function set width(value:*):void + { + normalizeColumnWidths(value); + } + + + /** + * Indicates elements in the table have been modified and the table must be recomposed. + **/ + public function get hasCellDamage():Boolean + { + return _hasCellDamage; + } + + public function set hasCellDamage(value:Boolean):void + { + _hasCellDamage = value; + } + + /** + * Returns the number of header rows in the table + **/ + public function get headerRowCount():uint + { + return _headerRowCount; + } + + /** + * Sets the number of header rows in the table + **/ + public function set headerRowCount(value:uint):void + { + if(value != _headerRowCount) + _tableRowsComputed = false; + _headerRowCount = value; + } + + /** + * Returns the number of footer rows in the table + **/ + public function get footerRowCount():uint + { + return _footerRowCount; + } + + /** + * Sets the number of footer rows in the table + **/ + public function set footerRowCount(value:uint):void + { + if(value != _footerRowCount) + _tableRowsComputed = false; + _footerRowCount = value; + } + + /** + * Gets the first TextFlowTableBlock in the table. + **/ + public function getFirstBlock():TextFlowTableBlock{ + if(_tableBlocks == null) + _tableBlocks = new Vector.<TextFlowTableBlock>(); + if(_tableBlocks.length == 0) + _tableBlocks.push(new TextFlowTableBlock(0)); + _tableBlockIndex = 0; + _tableBlocks[0].parentTable = this; + + return _tableBlocks[0]; + } + + /** + * Gets the next TextFlowTableBlock. + **/ + public function getNextBlock():TextFlowTableBlock{ + if(_tableBlocks == null) + _tableBlocks = new Vector.<TextFlowTableBlock>(); + _tableBlockIndex++; + while(_tableBlocks.length <= _tableBlockIndex){ + _tableBlocks.push( new TextFlowTableBlock(_tableBlocks.length) ); + } + _tableBlocks[_tableBlockIndex].parentTable = this; + + return _tableBlocks[_tableBlockIndex]; + } + + /** + * Gets the total atom length of this flow element in the text flow. + * + * @inheritDoc + **/ + override public function get textLength():int{ + return 1; + } + + /** + * Returns the cell at the specified row and column. + **/ + private function getCellIndex(rowIdx:int,columnIdx:int):int{ + if(rowIdx == 0 && columnIdx == 0) + return 0; + for (var i:int=0;i<mxmlChildren.length;i++){ + var item:* = mxmlChildren[i]; + if(!(item is TableCellElement)) + continue; + var cell:TableCellElement = item as TableCellElement; + if(cell.rowIndex == rowIdx && cell.colIndex == columnIdx) + return i; + } + return -1; + + } + + /** + * Returns a vector of table cell elements in the given cell range. + **/ + public function getCellsInRange(anchorCoords:CellCoordinates, activeCoords:CellCoordinates, block:TextFlowTableBlock=null):Vector.<TableCellElement> + { + var firstCoords:CellCoordinates = anchorCoords.clone(); + var lastCoords:CellCoordinates = activeCoords.clone(); + if( + lastCoords.row < firstCoords.row || + (lastCoords.row == firstCoords.row && lastCoords.column < firstCoords.column) + ) + { + firstCoords = activeCoords.clone(); + lastCoords = anchorCoords.clone(); + } + + // make sure the rectangle is not inversed + if(lastCoords.column < firstCoords.column) + { + var col:int = firstCoords.column; + firstCoords.column = lastCoords.column; + lastCoords.column = col; + } + var firstCell:TableCellElement = findCell(firstCoords); + var cells:Vector.<TableCellElement> = new Vector.<TableCellElement>(); + if(!block || getCellBlock(firstCell) == block) + cells.push(firstCell); + var idx:int = mxmlChildren.indexOf(firstCell); + while(++idx < mxmlChildren.length) + { + var nextCell:TableCellElement = mxmlChildren[idx]; + if(nextCell.rowIndex > lastCoords.row || (nextCell.rowIndex == lastCoords.row && nextCell.colIndex > lastCoords.column)) + break; + // skip cells outside rectangle + if(nextCell.colIndex > lastCoords.column || nextCell.colIndex < firstCoords.column) + continue; + if(!block || getCellBlock(nextCell) == block) + cells.push(nextCell); + } + return cells; + } + + /** + * Finds the cell at the specified cell coordinates or null if no cell is found. + **/ + public function findCell(coords:CellCoordinates):TableCellElement + { + // get a guess of the cell location. If there's no holes (such as spans), it should theoretically pinpoint the index. + var idx:int = (coords.row+1) * (coords.column+1) -1; + if(idx >= numChildren) + idx = numChildren-1; + + var cell:TableCellElement = mxmlChildren[idx]; + // look ahead to see if we're short (not sure if this is needed). + do + { + if(idx == numChildren-1) + break; + var nextCell:TableCellElement = mxmlChildren[idx+1]; + if(nextCell.rowIndex > coords.row || (nextCell.rowIndex == coords.row && nextCell.colIndex > coords.column)) + break; + + cell = nextCell; + idx++; + + }while(true); + // look behind accounting for spans + do + { + //check if the coords fall within the row and column span + if( + cell.colIndex <= coords.column && cell.colIndex + cell.columnSpan - 1 >= coords.column && + cell.rowIndex <= coords.row && cell.rowIndex + cell.rowSpan - 1 >= coords.row + ) + break; + //oops we hit the first cell without finding anything. At least return that... + if(cell.colIndex == 0 && cell.rowIndex == 0) + break; + if(idx == 0) + break; + var prevCell:TableCellElement = mxmlChildren[idx-1]; + cell = prevCell; + idx--; + }while(true); + + return cell; + } + + /** + * Adds the table cell container to the table block specified. + **/ + public function addCellToBlock(cell:TableCellElement, block:TextFlowTableBlock):void + { + block.addCell(cell.container); + tableBlockDict[cell] = block; + } + + /** + * Returns the table block for the given table cell. + **/ + public function getCellBlock(cell:TableCellElement):TextFlowTableBlock + { + return tableBlockDict[cell]; + } + + /** + * Keeps a reference to all the table blocks belonging to this table. + **/ + private function get tableBlockDict():Dictionary + { + if(_tableBlockDict == null) + _tableBlockDict = new Dictionary(); + return _tableBlockDict; + } + + /** + * Returns a vector of the table blocks. + **/ + public function get tableBlocks():Vector.<TextFlowTableBlock> + { + return _tableBlocks; + } + + public function getTableBlocksInRange(start:CellCoordinates,end:CellCoordinates):Vector.<TextFlowTableBlock> + { + var coords:CellCoordinates = start.clone(); + if(end.column < start.column) + { + coords = end.clone(); + end = start.clone(); + } + var blocks:Vector.<TextFlowTableBlock> = new Vector.<TextFlowTableBlock>(); + var block:TextFlowTableBlock = getCellBlock(findCell(coords)); + if(block) + blocks.push(block); + while(block) + { + coords.row++; + if(coords.row > end.row) + break; + if(getCellBlock(findCell(coords)) == block) + continue; + block = getCellBlock(findCell(coords)); + if(block) + blocks.push(block); + } + return blocks; + } + + /** @private */ + tlf_internal override function getNextLeafHelper(limitElement:FlowGroupElement,child:FlowElement):FlowLeafElement + { + return parent.getNextLeafHelper(limitElement,this); + } + + /** @private */ + tlf_internal override function getPreviousLeafHelper(limitElement:FlowGroupElement,child:FlowElement):FlowLeafElement + { + return parent.getPreviousLeafHelper(limitElement,this); + } + + private function getLeaf():TableLeafElement + { + if(_leaf == null) + _leaf = new TableLeafElement(this); + return _leaf; + } + + public override function findLeaf(relativePosition:int):FlowLeafElement + { + return getLeaf(); + } + public override function getLastLeaf(): FlowLeafElement + { + return getLeaf(); + } + public override function getFirstLeaf():FlowLeafElement + { + return getLeaf(); + } + + tlf_internal override function createContentElement():void{} + /** @private + * Release the FTE data structure that corresponds to the FlowElement, so it can be gc'ed + */ + tlf_internal override function releaseContentElement():void{} + + /** + * Creates and returns a default row + **/ + tlf_internal function createRowElement(index:int, defaultRowFormat:ITextLayoutFormat):TableRowElement { + var row:TableRowElement = new TableRowElement(defaultRowFormat); + row.rowIndex = index; + row.table = this; + return row; + } + + /** + * Creates and returns a default column + **/ + tlf_internal function createColumnElement(index:int, defaultColumnFormat:ITextLayoutFormat):TableColElement { + var column:TableColElement = new TableColElement(defaultColumnFormat); + column.colIndex = index; + column.table = this; + return column; + } + } +} +class CellCoords +{ + public var column:int; + public var row:int; + public function CellCoords(colIdx:int,rowIdx:int) + { + column = colIdx; + row = rowIdx; } } http://git-wip-us.apache.org/repos/asf/flex-tlf/blob/33df98ab/textLayout/src/flashx/textLayout/elements/TableFormattedElement.as ---------------------------------------------------------------------- diff --git a/textLayout/src/flashx/textLayout/elements/TableFormattedElement.as b/textLayout/src/flashx/textLayout/elements/TableFormattedElement.as index 4b5bd0f..edad5e3 100644 --- a/textLayout/src/flashx/textLayout/elements/TableFormattedElement.as +++ b/textLayout/src/flashx/textLayout/elements/TableFormattedElement.as @@ -25,6 +25,9 @@ package flashx.textLayout.elements super(); } + /** + * Get a reference to the table. We could refactor this since it's accessed multiple times. + **/ public function getTable():TableElement { // find the root element entry and either return null or the containing textFlow @@ -32,6 +35,37 @@ package flashx.textLayout.elements while ((elem.parent) != null && !( elem.parent is TableElement)) elem = elem.parent; return elem.parent as TableElement; - } + } + + + private var _table:TableElement; + + /** + * Returns a reference to the table. For this to work we need to set the + * table to null when it's removed. + **/ + public function get table():TableElement { + + if (_table) return _table; + + // find the root element entry and either return null or the containing textFlow + var elem:FlowGroupElement = this; + + while ((elem.parent) != null && !(elem.parent is TableElement)) { + elem = elem.parent; + } + + _table = elem.parent as TableElement; + + return _table; + } + + /** + * @private + **/ + public function set table(element:TableElement):void { + _table = element; + } } -} \ No newline at end of file +} +