http://git-wip-us.apache.org/repos/asf/flex-tlf/blob/33df98ab/textLayout/src/flashx/textLayout/edit/SelectionManager.as ---------------------------------------------------------------------- diff --git a/textLayout/src/flashx/textLayout/edit/SelectionManager.as b/textLayout/src/flashx/textLayout/edit/SelectionManager.as index dd6f117..6de1c2b 100644 --- a/textLayout/src/flashx/textLayout/edit/SelectionManager.as +++ b/textLayout/src/flashx/textLayout/edit/SelectionManager.as @@ -20,8 +20,10 @@ package flashx.textLayout.edit { import flash.desktop.Clipboard; import flash.desktop.ClipboardFormats; + import flash.display.BitmapData; import flash.display.DisplayObject; import flash.display.InteractiveObject; + import flash.display.Shape; import flash.display.Stage; import flash.errors.IllegalOperationError; import flash.events.ContextMenuEvent; @@ -31,8 +33,10 @@ package flashx.textLayout.edit import flash.events.KeyboardEvent; import flash.events.MouseEvent; import flash.events.TextEvent; + import flash.geom.Matrix; import flash.geom.Point; import flash.geom.Rectangle; + import flash.text.engine.TextBlock; import flash.text.engine.TextLine; import flash.text.engine.TextLineValidity; import flash.text.engine.TextRotation; @@ -40,14 +44,20 @@ package flashx.textLayout.edit import flash.ui.Keyboard; import flash.ui.Mouse; import flash.ui.MouseCursor; + import flash.ui.MouseCursorData; + import flash.utils.Dictionary; import flash.utils.getQualifiedClassName; import flashx.textLayout.compose.IFlowComposer; import flashx.textLayout.compose.TextFlowLine; + import flashx.textLayout.compose.TextFlowTableBlock; import flashx.textLayout.container.ColumnState; import flashx.textLayout.container.ContainerController; import flashx.textLayout.debug.Debugging; import flashx.textLayout.debug.assert; + import flashx.textLayout.elements.CellContainer; + import flashx.textLayout.elements.CellCoordinates; + import flashx.textLayout.elements.CellRange; import flashx.textLayout.elements.Configuration; import flashx.textLayout.elements.FlowElement; import flashx.textLayout.elements.FlowLeafElement; @@ -55,7 +65,9 @@ package flashx.textLayout.edit import flashx.textLayout.elements.IConfiguration; import flashx.textLayout.elements.InlineGraphicElement; import flashx.textLayout.elements.ParagraphElement; - import flashx.textLayout.elements.TableDataCellElement; + import flashx.textLayout.elements.TableBlockContainer; + import flashx.textLayout.elements.TableCellElement; + import flashx.textLayout.elements.TableColElement; import flashx.textLayout.elements.TableElement; import flashx.textLayout.elements.TableRowElement; import flashx.textLayout.elements.TextFlow; @@ -122,23 +134,325 @@ package flashx.textLayout.edit * @langversion 3.0 */ public class SelectionManager implements ISelectionManager - { + { + static tlf_internal var useTableSelectionCursors:Boolean = false; + /** + * Cursor for selection of table + **/ + public static var SelectTable:String = "selectTable"; + + /** + * Cursor for selection of table row + **/ + public static var SelectTableRow:String = "selectTableRow"; + + /** + * Cursor for selection of table column + **/ + public static var SelectTableColumn:String = "selectTableColumn"; + private var _focusedSelectionFormat:SelectionFormat; private var _unfocusedSelectionFormat:SelectionFormat; private var _inactiveSelectionFormat:SelectionFormat; + private var _focusedCellSelectionFormat:SelectionFormat; + private var _unfocusedCellSelectionFormat:SelectionFormat; + private var _inactiveCellSelectionFormat:SelectionFormat; private var _selFormatState:String = SelectionFormatState.UNFOCUSED; private var _isActive:Boolean; /** The TextFlow of the selection. */ private var _textFlow:TextFlow; + + protected var _subManager:ISelectionManager; + protected var _superManager:ISelectionManager; + + private var _currentTable:TableElement; + + // this should probably be produced dynamically rather than keep a reference. + private var _cellRange:CellRange; + + //TODO the following functions need proper comments and should be moved to a logical location within the class. + + public function get currentTable():TableElement + { + return _currentTable; + } + public function set currentTable(table:TableElement):void + { + _currentTable = table; + } + + public function hasCellRangeSelection():Boolean + { + if (!_currentTable) { + return false; + } + + //we should really check the anchorCellPosition and activeCellPosition instead + if (!_cellRange) { + return false; + } + + return true; + } + + /** + * Select a table cell text flow + **/ + public function selectCellTextFlow(cell:TableCellElement):void { + + if (cell && cell.table) { + var selectionManager:SelectionManager = cell.textFlow.interactionManager as SelectionManager; + + clear(); + + if (selectionManager) { + selectionManager.currentTable = cell.table; + selectionManager.selectAll(); + + // this seems to be required to work but it should not be + selectionManager.setFocus(); + } + } + } + + /** + * Select a table cell. + **/ + public function selectCell(cell:TableCellElement):void { + var beginCoordinates:CellCoordinates; + var endCoordinates:CellCoordinates; + + if (cell) { + beginCoordinates = new CellCoordinates(cell.rowIndex, cell.colIndex); + endCoordinates = new CellCoordinates(cell.rowIndex, cell.colIndex); + + if (beginCoordinates.isValid()) { + selectCellRange(beginCoordinates, endCoordinates); + } + } + } + + /** + * Select table cells at the specified index. + **/ + public function selectCellAt(table:TableElement, rowIndex:int, colIndex:int):void { + var cell:TableCellElement = table.getCellAt(rowIndex, colIndex); + + if (cell) { + selectCell(cell); + } + } + + /** + * Select table cells at the specified index + **/ + public function selectCells(cells:Vector.<TableCellElement>):void { + var startX:int = int.MAX_VALUE; + var startY:int = int.MAX_VALUE; + var endX:int = int.MIN_VALUE; + var endY:int = int.MIN_VALUE; + var cell:TableCellElement; + var table:TableElement; + for each(cell in cells) + { + if(cell) + { + if(table == null) + table = cell.getTable(); + + var col:int = cell.colIndex; + var row:int = cell.rowIndex; + if(col < startX) + startX = col; + if(col > endX) + endX = col; + if(row < startY) + startY = row; + if(row > endY) + endY = row; + } + } + if(startX <= endX && startY <= endY) + selectCellRange( + new CellCoordinates(startY,startX,table), + new CellCoordinates(endY,endX,table) + ); + } + + /** + * Select the specified table row. + **/ + public function selectRow(row:TableRowElement):void { + var beginCoordinates:CellCoordinates; + var endCoordinates:CellCoordinates; + + if (row) { + beginCoordinates = new CellCoordinates(row.rowIndex, 0); + endCoordinates = new CellCoordinates(row.rowIndex, row.numCells); + + if (beginCoordinates.isValid() && endCoordinates.isValid()) { + selectCellRange(beginCoordinates, endCoordinates); + } + } + } + + /** + * Select a table row at the specified index + **/ + public function selectRowAt(table:TableElement, index:int):void { + var row:TableRowElement = table ? table.getRowAt(index) : null; + + if (row) { + selectRow(row); + } + } + + /** + * Selects the table rows provided + **/ + public function selectRows(rows:Array):void { + var cells:Vector.<TableCellElement> = new Vector.<TableCellElement>(); + var table:TableElement; + var cell:TableCellElement; + + if (rows && rows.length) { + + for (var i:int;i<rows.length;i++) + { + var row:TableRowElement = rows[i] as TableRowElement; + + if (row) + { + for each(cell in row.cells) + cells.push(cell); + } + } + + selectCells(cells); + } + } + + /** + * Select a table column. + **/ + public function selectColumn(column:TableColElement):void { + var table:TableElement = column.table; + + if (column && table) { + selectCells(table.getCellsForColumn(column)); + } + } + + /** + * Select a table column at the specified index + **/ + public function selectColumnAt(table:TableElement, index:int):void { + var column:TableColElement = table.getColumnAt(index); + + if (column && table) { + return selectColumn(column); + } + } + + /** + * Selects the table columns provided + **/ + public function selectColumns(columns:Array):void { + var cells:Vector.<TableCellElement> = new Vector.<TableCellElement>(); + var cell:TableCellElement; + + if (columns && columns.length) { + + for (var i:int;i<columns.length;i++) + { + var column:TableColElement = columns[i] as TableColElement; + + if (column) + { + for each(cell in column.cells) + cells.push(cell); + } + + } + + selectCells(cells); + } + } + + /** + * Select all cells in a table. + **/ + public function selectTable(table:TableElement):void { + + if (table) + { + var startCoords:CellCoordinates = new CellCoordinates(0,0,table); + var endCoords:CellCoordinates = new CellCoordinates(table.numRows-1,table.numColumns-1,table); + selectCellRange(startCoords,endCoords); + } + + } + + /** + * Select a range of table cells. + **/ + public function selectCellRange(anchorCoords:CellCoordinates, activeCoords:CellCoordinates):void + { + var blocks:Vector.<TextFlowTableBlock>; + var block:TextFlowTableBlock; + var controller:ContainerController; + + if (selectionType == SelectionType.TEXT) { + clear(); + } + clearCellSelections(); + + if (anchorCoords && activeCoords) { + _cellRange = new CellRange(_currentTable, anchorCoords, activeCoords); + activeCellPosition = activeCoords; + blocks = _currentTable.getTableBlocksInRange(anchorCoords, activeCoords); + + for each(block in blocks) { + block.controller.clearSelectionShapes(); + block.controller.addCellSelectionShapes(currentCellSelectionFormat.rangeColor, block, anchorCoords, activeCoords); + } + if(subManager) + { + subManager.selectRange(-1,-1); + subManager = null; + } + } + else + { + _cellRange = null; + activeCellPosition.column = -1; + activeCellPosition.row = -1; + } + selectionChanged(); + } + + public function getCellRange():CellRange + { + // not really a good implementation. We'll fix this later + return _cellRange; + } + public function setCellRange(range:CellRange):void + { + selectCellRange(range.anchorCoordinates,range.activeCoordinates); + //_cellRange = range; + // do something about actually drawing the selection. + } // current range of selection /** Anchor point of the current selection, as an index into the TextFlow. */ private var anchorMark:Mark; /** Active end of the current selection, as an index into the TextFlow. */ private var activeMark:Mark; - - // used to save pending attributes at a point selection + private var _anchorCellPosition:CellCoordinates; + private var _activeCellPosition:CellCoordinates; + + // used to save pending attributes at a point selection private var _pointFormat:ITextLayoutFormat; /** * The format that will be applied to inserted text. @@ -190,6 +504,8 @@ package flashx.textLayout.edit _textFlow = null; anchorMark = createMark(); activeMark = createMark(); + anchorCellPosition = createCellMark(); + activeCellPosition = createCellMark(); _pointFormat = null; _isActive = false; CONFIG::debug @@ -197,7 +513,12 @@ package flashx.textLayout.edit this.id = smCount.toString(); smCount++; } + + Mouse.registerCursor(SelectTable, createSelectTableCursor()); + Mouse.registerCursor(SelectTableRow, createSelectTableRowCursor()); + Mouse.registerCursor(SelectTableColumn, createSelectTableColumnCursor()); } + /** * @copy ISelectionManager#getSelectionState() * @@ -211,7 +532,10 @@ package flashx.textLayout.edit */ public function getSelectionState():SelectionState { - return new SelectionState(_textFlow, anchorMark.position, activeMark.position, pointFormat); + if(subManager) + return subManager.getSelectionState(); + + return new SelectionState(_textFlow, anchorMark.position, activeMark.position, pointFormat, _cellRange); } /** @@ -238,8 +562,45 @@ package flashx.textLayout.edit * @langversion 3.0 */ public function hasSelection():Boolean - { return anchorMark.position != -1; } + { + return selectionType == SelectionType.TEXT; + } + + /** + * @copy ISelectionManager#hasAnySelection() + * + * @includeExample examples\SelectionManager_hasSelection.as -noswf + * + * @playerversion Flash 10 + * @playerversion AIR 1.5 + * @langversion 3.0 + */ + public function hasAnySelection():Boolean + { + return selectionType != SelectionType.NONE; + } + /** + * Indicates the type of selection. + * + * <p>The <code>selectionType</code> describes the kind of selection. + * It can either be <code>SelectionType.TEXT</code> or <code>SelectionType.CELLS</code> + * + * @see flashx.textLayout.edit.SelectionType + * + * @playerversion Flash 10 + * @playerversion AIR 1.5 + * @langversion 3.0 + */ + public function get selectionType() : String + { + if(anchorMark.position != -1) + return SelectionType.TEXT; + else if(anchorCellPosition.isValid()) + return SelectionType.CELLS; + + return SelectionType.NONE; + } /** * @copy ISelectionManager#isRangeSelection() * @@ -277,6 +638,8 @@ package flashx.textLayout.edit flushPendingOperations(); clear(); + clearCellSelections(); + _cellRange = null; // If we switch into read-only mode, make sure the cursor isn't showing a text selection IBeam if (!value) // see Watson 2637162 @@ -348,7 +711,29 @@ package flashx.textLayout.edit } return focusedSelectionFormat; } - + + /** + * @copy ISelectionManager#currentCellSelectionFormat + * + * @playerversion Flash 10 + * @playerversion AIR 1.5 + * @langversion 3.0 + * + * @see flashx.textLayout.edit.SelectionFormat + */ + public function get currentCellSelectionFormat():SelectionFormat + { + if (_selFormatState == SelectionFormatState.UNFOCUSED) + { + return unfocusedCellSelectionFormat; + } + else if (_selFormatState == SelectionFormatState.INACTIVE) + { + return inactiveCellSelectionFormat; + } + return focusedCellSelectionFormat; + } + /** * @copy ISelectionManager#focusedSelectionFormat * @@ -420,7 +805,79 @@ package flashx.textLayout.edit { return _inactiveSelectionFormat ? _inactiveSelectionFormat : (_textFlow ? _textFlow.configuration.inactiveSelectionFormat : null); } - + + /** + * @copy ISelectionManager#focusedCellSelectionFormat + * + * @playerversion Flash 10 + * @playerversion AIR 1.5 + * @langversion 3.0 + * + * @see flashx.textLayout.edit.SelectionFormat + */ + public function set focusedCellSelectionFormat(val:SelectionFormat):void + { + _focusedCellSelectionFormat = val; + if (this._selFormatState == SelectionFormatState.FOCUSED) + refreshSelection(); + } + + /** + * @private - docs on setter + */ + public function get focusedCellSelectionFormat():SelectionFormat + { + return _focusedCellSelectionFormat ? _focusedCellSelectionFormat : (_textFlow ? _textFlow.configuration.focusedSelectionFormat : null); + } + + /** + * @copy ISelectionManager#unfocusedCellSelectionFormat + * + * @playerversion Flash 10 + * @playerversion AIR 1.5 + * @langversion 3.0 + * + * @see flashx.textLayout.edit.SelectionFormat + */ + public function set unfocusedCellSelectionFormat(val:SelectionFormat):void + { + _unfocusedCellSelectionFormat = val; + if (this._selFormatState == SelectionFormatState.UNFOCUSED) + refreshSelection(); + } + + /** + * @private - docs on setter + */ + public function get unfocusedCellSelectionFormat():SelectionFormat + { + return _unfocusedCellSelectionFormat ? _unfocusedCellSelectionFormat : (_textFlow ? _textFlow.configuration.unfocusedSelectionFormat : null); + } + + /** + * @copy ISelectionManager#inactiveCellSelectionFormat + * + * @playerversion Flash 10 + * @playerversion AIR 1.5 + * @langversion 3.0 + * + * @see flashx.textLayout.edit.SelectionFormat + */ + public function set inactiveCellSelectionFormat(val:SelectionFormat):void + { + _inactiveCellSelectionFormat = val; + if (this._selFormatState == SelectionFormatState.INACTIVE) + refreshSelection(); + } + + /** + * @private - docs on setter + */ + public function get inactiveCellSelectionFormat():SelectionFormat + { + return _inactiveCellSelectionFormat ? _inactiveCellSelectionFormat : (_textFlow ? _textFlow.configuration.inactiveSelectionFormat : null); + } + /** @private - returns the selectionFormatState. @see flashx.textLayout.edit.SelectionFormatState */ tlf_internal function get selectionFormatState():String { return _selFormatState; } @@ -584,11 +1041,17 @@ package flashx.textLayout.edit */ public function selectAll() : void { - selectRange(0, int.MAX_VALUE); + if(subManager) + subManager.selectAll(); + else + { + var lastSelectablePos:int = (_textFlow.textLength > 0) ? _textFlow.textLength - 1 : 0; + selectRange(0, lastSelectablePos); + } } /** - * @copy ISelectionManager#selectRange + * @copy ISelectionManager#selectRange * * @includeExample examples\SelectionManager_selectRange.as -noswf * @@ -601,22 +1064,76 @@ package flashx.textLayout.edit public function selectRange(anchorPosition:int, activePosition:int) : void { flushPendingOperations(); + + if(subManager) + subManager.selectRange(-1,-1); // anchor and active can be in any order // TODO: range check and clamp anchor,active if (anchorPosition != anchorMark.position || activePosition != activeMark.position) { clearSelectionShapes(); + clearCellSelections(); + _cellRange = null; - internalSetSelection(_textFlow, anchorPosition, activePosition); + internalSetSelection(_textFlow, anchorPosition, activePosition, _pointFormat); // selection changed - selectionChanged(); + selectionChanged(); allowOperationMerge = false; } } - + + /** + * @copy ISelectionManager#selectFirstPosition + * + * @playerversion Flash 10 + * @playerversion AIR 1.5 + * @langversion 3.0 + * + * @see flashx.textLayout.compose.IFlowComposer + */ + public function selectFirstPosition():void + { + selectRange(0, 0); + } + + /** + * @copy ISelectionManager#selectLastPosition + * + * @playerversion Flash 10 + * @playerversion AIR 1.5 + * @langversion 3.0 + * + * @see flashx.textLayout.compose.IFlowComposer + */ + public function selectLastPosition():void + { + selectRange(int.MAX_VALUE, int.MAX_VALUE); + } + + /** + * @copy ISelectionManager#deselect + * + * @playerversion Flash 10 + * @playerversion AIR 1.5 + * @langversion 3.0 + * + * @see flashx.textLayout.compose.IFlowComposer + */ + public function deselect():void + { + if (hasAnySelection()) + { + clearSelectionShapes(); + clearCellSelections(); + addSelectionShapes(); + } + selectRange(-1,-1); + _cellRange = null; + } + private function internalSetSelection(root:TextFlow,anchorPosition:int,activePosition:int,format:ITextLayoutFormat = null) : void { _textFlow = root; @@ -627,6 +1144,8 @@ package flashx.textLayout.edit anchorPosition = -1; activePosition = -1; } + else if(subManager) + subManager = null; var lastSelectablePos:int = (_textFlow.textLength > 0) ? _textFlow.textLength - 1 : 0; @@ -645,7 +1164,8 @@ package flashx.textLayout.edit // trace("Selection ", anchorMark, "to", activeMark.position); } - /** Clear any active selections. + /** + * Clear any active selections. */ private function clear(): void { @@ -659,7 +1179,32 @@ package flashx.textLayout.edit allowOperationMerge = false; } } - + /** + * Clear any cell selections + * */ + private function clearCellSelections():void + { + var blocks:Vector.<TextFlowTableBlock>; + var block:TextFlowTableBlock; + var controller:ContainerController; + + if (_cellRange) { + blocks = _cellRange.table.getTableBlocksInRange(_cellRange.anchorCoordinates, _cellRange.activeCoordinates); + + for each (block in blocks) { + if (controller != block.controller) { + block.controller.clearSelectionShapes(); + } + + controller = block.controller; + } + + } + if(block) + block.controller.clearSelectionShapes(); + + //_cellRange = null; + } private function addSelectionShapes():void { if (_textFlow.flowComposer) @@ -677,7 +1222,7 @@ package flashx.textLayout.edit { _textFlow.flowComposer.getControllerAt(containerIter++).addSelectionShapes(currentSelectionFormat, absoluteStart, absoluteEnd); } - } + } } } @@ -693,22 +1238,39 @@ package flashx.textLayout.edit } } } - - /** - * @copy ISelectionManager#refreshSelection() - * - * @playerversion Flash 10 - * @playerversion AIR 1.5 - * @langversion 3.0 - */ - public function refreshSelection(): void - { - if (hasSelection()) - { - clearSelectionShapes(); - addSelectionShapes(); - } - } + + /** + * @copy ISelectionManager#refreshSelection() + * + * @playerversion Flash 10 + * @playerversion AIR 1.5 + * @langversion 3.0 + */ + public function refreshSelection(): void + { + if (hasAnySelection()) + { + clearSelectionShapes(); + clearCellSelections(); + addSelectionShapes(); + } + } + + /** + * @copy ISelectionManager#clearSelection() + * + * @playerversion Flash 10 + * @playerversion AIR 1.5 + * @langversion 3.0 + */ + public function clearSelection(): void + { + if (hasAnySelection()) + { + clearSelectionShapes(); + clearCellSelections(); + } + } /** Verifies that the selection is in a legal state. @private */ CONFIG::debug public function debugCheckSelectionManager():int @@ -745,7 +1307,12 @@ package flashx.textLayout.edit _pointFormat = null; if (doDispatchEvent && _textFlow) - textFlow.dispatchEvent(new SelectionEvent(SelectionEvent.SELECTION_CHANGE, false, false, hasSelection() ? getSelectionState() : null)); + { + if(textFlow.parentElement && textFlow.parentElement.getTextFlow()) + textFlow.parentElement.getTextFlow().dispatchEvent(new SelectionEvent(SelectionEvent.SELECTION_CHANGE, false, false, hasSelection() ? getSelectionState() : null)); + else + textFlow.dispatchEvent(new SelectionEvent(SelectionEvent.SELECTION_CHANGE, false, false, hasSelection() ? getSelectionState() : null)); + } } // TODO: this routine could be much more efficient - instead of iterating over all lines in the TextFlow it should iterate over @@ -771,11 +1338,14 @@ package flashx.textLayout.edit //get the nearest column so we can ignore lines which aren't in the column we're looking for. //if we don't do this, we won't be able to select across column boundaries. var nearestColIdx:int = locateNearestColumn(controller, localX, localY, textFlow.computedFormat.blockProgression,textFlow.computedFormat.direction); + + var prevLineBounds:Rectangle = null; + var previousLineIndex:int = -1; + + /* //For the table feature, we are trying to make sure if the current point is in the table and which cell it is in - var nearestCell:TableDataCellElement = locateNearestCell(controller, localX, localY, textFlow.computedFormat.blockProgression,textFlow.computedFormat.direction); + var nearestCell:TableCellElement = locateNearestCell(controller, localX, localY, textFlow.computedFormat.blockProgression,textFlow.computedFormat.direction); - var prevLineBounds:Rectangle = null; - var previousLineIndex:int = -1; if(nearestCell) { @@ -790,7 +1360,7 @@ package flashx.textLayout.edit return cellPara.getAbsoluteStart() + cellPara.textLength - 1; } } - + */ var lastLineIndexInColumn:int = -1; // Matching TextFlowLine and TextLine - they are not necessarily valid @@ -845,6 +1415,7 @@ package flashx.textLayout.edit //current line,. Otherwise, if the click's perpendicular coordinate is below the mid point between the current //line or below it, then we want to use the line below (ie the previous line, but logically the one after the current) var inPrevLine:Boolean = midPerpCoor != -1 && (isTTB ? perpCoor < midPerpCoor : perpCoor > midPerpCoor); + /* if(rtline.paragraph.isInTable()) { //if rtline is the last line of the cell and the isPrevLine is true, find the cell of the column in next row @@ -852,10 +1423,10 @@ package flashx.textLayout.edit if ( inPrevLine && testIndex != lastLineIndexInColumn ) { var rtPara:ParagraphElement = rtline.paragraph; - var rtCell:TableDataCellElement = rtPara.getTableDataCellElement(); + var rtCell:TableCellElement = rtPara.getParentCellElement(); //get the last element of the cell var lastElement:FlowElement = rtCell.getLastLeaf(); - var rtLastTbLine:TextFlowLine = lastElement.getParagraph().getTextBlock().lastLine.userData; + var rtLastTbLine:TextFlowLine = lastElement ? lastElement.getParagraph().getTextBlock().lastLine.userData : null; if( rtline == rtLastTbLine ) { //temproray codes, need to be updated when the column apis are ready @@ -864,7 +1435,7 @@ package flashx.textLayout.edit var nextRow:TableRowElement = rtRow.getNextSibling() as TableRowElement; if ( nextRow && rtCell ) { - var nextCell:TableDataCellElement = nextRow.getChildAt(rtCell.colIndex) as TableDataCellElement; + var nextCell:TableCellElement = nextRow.getChildAt(rtCell.colIndex) as TableCellElement; lineIndex = textFlow.flowComposer.findLineIndexAtPosition(nextCell.getFirstLeaf().getParagraph().getAbsoluteStart()); } } @@ -875,6 +1446,7 @@ package flashx.textLayout.edit lineIndex = testIndex; } else + */ lineIndex = inPrevLine && testIndex != lastLineIndexInColumn ? testIndex+1 : testIndex; break; } @@ -895,79 +1467,87 @@ package flashx.textLayout.edit //Get a valid textLine -- check to make sure line is valid, regenerate if necessary, make sure it has correct container relative coordinates var textFlowLine:TextFlowLine = textFlow.flowComposer.getLineAt(lineIndex); - var textLine:TextLine = textFlowLine.getTextLine(true); - - // adjust localX,localY to be relative to the textLine. - // Can't use localToGlobal/globalToLocal because textLine may not be on the display list due to virtualization - // we may need to bring this back if textline's can be rotated or placed by any mechanism other than a translation - // but then we'll need to provisionally place a virtualized TextLine in its parent container - localX -= textLine.x; - localY -= textLine.y; - /* var localPoint:Point = DisplayObject(controller.container).localToGlobal(new Point(localX,localY)); - localPoint = textLine.globalToLocal(localPoint); - localX = localPoint.x; - localY = localPoint.y; */ - - - var startOnNextLineIfNecessary:Boolean = false; - - var lastAtom:int = -1; - if (isDirectionRTL) { - lastAtom = textLine.atomCount - 1; - } else { - if ((textFlowLine.absoluteStart + textFlowLine.textLength) >= textFlowLine.paragraph.getAbsoluteStart() + textFlowLine.paragraph.textLength) { - if (textLine.atomCount > 1) lastAtom = textLine.atomCount - 2; - } else { - var lastLinePosInPar:int = textFlowLine.absoluteStart + textFlowLine.textLength - 1; - var lastChar:String = textLine.textBlock.content.rawText.charAt(lastLinePosInPar); - if (lastChar == " ") { - if (textLine.atomCount > 1) lastAtom = textLine.atomCount - 2; - } else { - startOnNextLineIfNecessary = true; - if (textLine.atomCount > 0) lastAtom = textLine.atomCount - 1; - } - } - } - var lastAtomRect:Rectangle = (lastAtom > 0) ? textLine.getAtomBounds(lastAtom) : new Rectangle(0, 0, 0, 0); - - if (!isTTB) - { - if (localX < 0) - localX = 0; - else if (localX > (lastAtomRect.x + lastAtomRect.width)) - { - if (startOnNextLineIfNecessary) - return textFlowLine.absoluteStart + textFlowLine.textLength - 1; - if (lastAtomRect.x + lastAtomRect.width > 0) - localX = lastAtomRect.x + lastAtomRect.width; - } - } - else - { - if (localY < 0) - localY = 0; - else if (localY > (lastAtomRect.y + lastAtomRect.height)) - { - if (startOnNextLineIfNecessary) - return textFlowLine.absoluteStart + textFlowLine.textLength - 1; - if (lastAtomRect.y + lastAtomRect.height > 0) - localY = lastAtomRect.y + lastAtomRect.height; - } - } - - result = computeSelectionIndexInLine(textFlow, textLine, localX, localY); + if(textFlowLine is TextFlowTableBlock) + { + result = TextFlowTableBlock(textFlowLine).absoluteStart; + } + else + { + var textLine:TextLine = textFlowLine.getTextLine(true); + + // adjust localX,localY to be relative to the textLine. + // Can't use localToGlobal/globalToLocal because textLine may not be on the display list due to virtualization + // we may need to bring this back if textline's can be rotated or placed by any mechanism other than a translation + // but then we'll need to provisionally place a virtualized TextLine in its parent container + localX -= textLine.x; + localY -= textLine.y; + /* var localPoint:Point = DisplayObject(controller.container).localToGlobal(new Point(localX,localY)); + localPoint = textLine.globalToLocal(localPoint); + localX = localPoint.x; + localY = localPoint.y; */ + + + var startOnNextLineIfNecessary:Boolean = false; + + var lastAtom:int = -1; + if (isDirectionRTL) { + lastAtom = textLine.atomCount - 1; + } else { + if ((textFlowLine.absoluteStart + textFlowLine.textLength) >= textFlowLine.paragraph.getAbsoluteStart() + textFlowLine.paragraph.textLength) { + if (textLine.atomCount > 1) lastAtom = textLine.atomCount - 2; + } else { + var lastLinePosInPar:int = textFlowLine.absoluteStart + textFlowLine.textLength - 1; + var lastChar:String = textLine.textBlock.content.rawText.charAt(lastLinePosInPar); + if (lastChar == " ") { + if (textLine.atomCount > 1) lastAtom = textLine.atomCount - 2; + } else { + startOnNextLineIfNecessary = true; + if (textLine.atomCount > 0) lastAtom = textLine.atomCount - 1; + } + } + } + var lastAtomRect:Rectangle = (lastAtom > 0) ? textLine.getAtomBounds(lastAtom) : new Rectangle(0, 0, 0, 0); + + if (!isTTB) + { + if (localX < 0) + localX = 0; + else if (localX > (lastAtomRect.x + lastAtomRect.width)) + { + if (startOnNextLineIfNecessary) + return textFlowLine.absoluteStart + textFlowLine.textLength - 1; + if (lastAtomRect.x + lastAtomRect.width > 0) + localX = lastAtomRect.x + lastAtomRect.width; + } + } + else + { + if (localY < 0) + localY = 0; + else if (localY > (lastAtomRect.y + lastAtomRect.height)) + { + if (startOnNextLineIfNecessary) + return textFlowLine.absoluteStart + textFlowLine.textLength - 1; + if (lastAtomRect.y + lastAtomRect.height > 0) + localY = lastAtomRect.y + lastAtomRect.height; + } + } + + result = computeSelectionIndexInLine(textFlow, textLine, localX, localY); + } + // trace("computeSelectionIndexInContainer:(",origX,origY,")",textFlow.flowComposer.getControllerIndex(controller).toString(),lineIndex.toString(),result.toString()); return result != -1 ? result : firstCharVisible + length; } - - static private function locateNearestCell(container:ContainerController, localX:Number, localY:Number, wm:String, direction:String):TableDataCellElement + /* + static private function locateNearestCell(container:ContainerController, localX:Number, localY:Number, wm:String, direction:String):TableCellElement { var cellIdx:int = 0; //if we only have 1 column, no need to perform calculation... var columnState:ColumnState = container.columnState; var isFound:Boolean = false; - var curCell:TableDataCellElement = null; + var curCell:TableCellElement = null; //we need to compare the current column to the nextColmn while(cellIdx < columnState.cellCount - 1) @@ -984,7 +1564,7 @@ package flashx.textLayout.edit } return isFound? curCell : null; } - + */ static private function locateNearestColumn(container:ContainerController, localX:Number, localY:Number, wm:String, direction:String):int { var colIdx:int = 0; @@ -1141,9 +1721,8 @@ package flashx.textLayout.edit else // Left to right case, right is "end" unicode paraSelectionIdx = leanRight ? textLine.getAtomTextBlockEndIndex(elemIdx) : textLine.getAtomTextBlockBeginIndex(elemIdx); - //we again need to do some fixup here. Unfortunately, we don't have the index into the paragraph until - - return rtline.paragraph.getAbsoluteStart() + paraSelectionIdx; + //we again need to do some fixup here. Unfortunately, we don't have the index into the paragraph until + return rtline.paragraph.getTextBlockAbsoluteStart(textLine.textBlock) + paraSelectionIdx; } static private function checkForDisplayed(container:DisplayObject):Boolean @@ -1164,6 +1743,142 @@ package flashx.textLayout.edit return false; // not on the stage } + /** @private - find a controller and adjusts the x and y values of localPoint if necessary */ + private static function findController(textFlow:TextFlow, target:Object, currentTarget:Object, localPoint:Point):ContainerController + { + var localX:Number = localPoint.x; + var localY:Number = localPoint.y; + var controller:ContainerController; + var containerPoint:Point; // scratch + + var globalPoint:Point = DisplayObject(target).localToGlobal(new Point(localX, localY)); + + for (var idx:int = 0; idx < textFlow.flowComposer.numControllers; idx++) + { + var testController:ContainerController = textFlow.flowComposer.getControllerAt(idx); + if (testController.container == target || testController.container == currentTarget) + { + controller = testController; + break; + } + } + if (controller) + { + if (target != controller.container) + { + containerPoint = DisplayObject(controller.container).globalToLocal(globalPoint); + localPoint.x = containerPoint.x; + localPoint.y = containerPoint.y; + } + return controller; + } + + //the point is someplace else on stage. Map the target + //to the textFlow.container. + CONFIG::debug { assert(textFlow.flowComposer && textFlow.flowComposer.numControllers,"findController: invalid textFlow"); } + + + + // result of the search + var controllerCandidate:ContainerController = null; + var candidateLocalX:Number; + var candidateLocalY:Number; + var relDistance:Number = Number.MAX_VALUE; + + for (var containerIndex:int = 0; containerIndex < textFlow.flowComposer.numControllers; containerIndex++) + { + var curContainerController:ContainerController = textFlow.flowComposer.getControllerAt(containerIndex); + + // displayed?? + if (!checkForDisplayed(curContainerController.container as DisplayObject)) + continue; + + // handle measured containers?? + var bounds:Rectangle = curContainerController.getContentBounds(); + var containerWidth:Number = isNaN(curContainerController.compositionWidth) ? curContainerController.getTotalPaddingLeft()+bounds.width : curContainerController.compositionWidth; + var containerHeight:Number = isNaN(curContainerController.compositionHeight) ? curContainerController.getTotalPaddingTop()+bounds.height : curContainerController.compositionHeight; + + containerPoint = DisplayObject(curContainerController.container).globalToLocal(globalPoint); + + // remove scrollRect effects for the distance test but add it back in for the result + var adjustX:Number = 0; + var adjustY:Number = 0; + + if (curContainerController.hasScrollRect) + { + containerPoint.x -= (adjustX = curContainerController.container.scrollRect.x); + containerPoint.y -= (adjustY = curContainerController.container.scrollRect.y); + } + + if ((containerPoint.x >= 0) && (containerPoint.x <= containerWidth) && + (containerPoint.y >= 0) && (containerPoint.y <= containerHeight)) + { + controllerCandidate = curContainerController; + candidateLocalX = containerPoint.x+adjustX; + candidateLocalY = containerPoint.y+adjustY; + break; + } + + // figure minimum distance of containerPoint to curContainerController - 8 cases + var relDistanceX:Number = 0; + var relDistanceY:Number = 0; + + if (containerPoint.x < 0) + { + relDistanceX = containerPoint.x; + if (containerPoint.y < 0) + relDistanceY = containerPoint.y; + else if (containerPoint.y > containerHeight) + relDistanceY = containerPoint.y-containerHeight; + } + else if (containerPoint.x > containerWidth) + { + relDistanceX = containerPoint.x-containerWidth; + if (containerPoint.y < 0) + relDistanceY = containerPoint.y; + else if (containerPoint.y > containerHeight) + relDistanceY = containerPoint.y-containerHeight; + } + else if (containerPoint.y < 0) + relDistanceY = -containerPoint.y; + else + relDistanceY = containerPoint.y-containerHeight; + var tempDist:Number = relDistanceX*relDistanceX + relDistanceY*relDistanceY; // could do sqrt but why bother - there is no Math.hypot function + if (tempDist <= relDistance) + { + relDistance = tempDist; + controllerCandidate = curContainerController; + candidateLocalX = containerPoint.x+adjustX; + candidateLocalY = containerPoint.y+adjustY; + } + } + localPoint.x = candidateLocalX; + localPoint.y = candidateLocalY; + return controllerCandidate; + + } + /** @private - given a target and location compute the CellCoordinates */ + static tlf_internal function computeCellCoordinates(textFlow:TextFlow, target:Object, currentTarget:Object, localX:Number, localY:Number):CellCoordinates + { + var rslt:CellCoordinates; + var containerPoint:Point; // scratch + + + if (target is TextLine) + return null; + if(target is CellContainer) + { + var cell:TableCellElement = (target as CellContainer).element; + return new CellCoordinates(cell.rowIndex, cell.colIndex, cell.getTable()); + } + var localPoint:Point = new Point(localX, localY); + var controller:ContainerController = findController(textFlow, target, currentTarget, localPoint); + if(!controller) + return null; + + return controller.findCellAtPosition(localPoint); + } + /** @private - given a target and location compute the selectionIndex */ static tlf_internal function computeSelectionIndex(textFlow:TextFlow, target:Object, currentTarget:Object, localX:Number,localY:Number):int { @@ -1196,112 +1911,9 @@ package flashx.textLayout.edit rslt = computeSelectionIndexInLine(textFlow, TextLine(target), localX, localY); else { - var controller:ContainerController; - for (var idx:int = 0; idx < textFlow.flowComposer.numControllers; idx++) - { - var testController:ContainerController = textFlow.flowComposer.getControllerAt(idx); - if (testController.container == target || testController.container == currentTarget) - { - controller = testController; - break; - } - } - if (controller) - { - if (target != controller.container) - { - containerPoint = DisplayObject(target).localToGlobal(new Point(localX, localY)); - containerPoint = DisplayObject(controller.container).globalToLocal(containerPoint); - localX = containerPoint.x; - localY = containerPoint.y; - } - rslt = computeSelectionIndexInContainer(textFlow, controller, localX, localY); - } - else - { - //the point is someplace else on stage. Map the target - //to the textFlow.container. - CONFIG::debug { assert(textFlow.flowComposer && textFlow.flowComposer.numControllers,"computeSelectionIndex: invalid textFlow"); } - - - // result of the search - var controllerCandidate:ContainerController = null; - var candidateLocalX:Number; - var candidateLocalY:Number; - var relDistance:Number = Number.MAX_VALUE; - - for (var containerIndex:int = 0; containerIndex < textFlow.flowComposer.numControllers; containerIndex++) - { - var curContainerController:ContainerController = textFlow.flowComposer.getControllerAt(containerIndex); - - // displayed?? - if (!checkForDisplayed(curContainerController.container as DisplayObject)) - continue; - - // handle measured containers?? - var bounds:Rectangle = curContainerController.getContentBounds(); - var containerWidth:Number = isNaN(curContainerController.compositionWidth) ? curContainerController.getTotalPaddingLeft()+bounds.width : curContainerController.compositionWidth; - var containerHeight:Number = isNaN(curContainerController.compositionHeight) ? curContainerController.getTotalPaddingTop()+bounds.height : curContainerController.compositionHeight; - - containerPoint = DisplayObject(target).localToGlobal(new Point(localX, localY)); - containerPoint = DisplayObject(curContainerController.container).globalToLocal(containerPoint); - - // remove scrollRect effects for the distance test but add it back in for the result - var adjustX:Number = 0; - var adjustY:Number = 0; - - if (curContainerController.hasScrollRect) - { - containerPoint.x -= (adjustX = curContainerController.container.scrollRect.x); - containerPoint.y -= (adjustY = curContainerController.container.scrollRect.y); - } - - if ((containerPoint.x >= 0) && (containerPoint.x <= containerWidth) && - (containerPoint.y >= 0) && (containerPoint.y <= containerHeight)) - { - controllerCandidate = curContainerController; - candidateLocalX = containerPoint.x+adjustX; - candidateLocalY = containerPoint.y+adjustY; - break; - } - - // figure minimum distance of containerPoint to curContainerController - 8 cases - var relDistanceX:Number = 0; - var relDistanceY:Number = 0; - - if (containerPoint.x < 0) - { - relDistanceX = containerPoint.x; - if (containerPoint.y < 0) - relDistanceY = containerPoint.y; - else if (containerPoint.y > containerHeight) - relDistanceY = containerPoint.y-containerHeight; - } - else if (containerPoint.x > containerWidth) - { - relDistanceX = containerPoint.x-containerWidth; - if (containerPoint.y < 0) - relDistanceY = containerPoint.y; - else if (containerPoint.y > containerHeight) - relDistanceY = containerPoint.y-containerHeight; - } - else if (containerPoint.y < 0) - relDistanceY = -containerPoint.y; - else - relDistanceY = containerPoint.y-containerHeight; - var tempDist:Number = relDistanceX*relDistanceX + relDistanceY*relDistanceY; // could do sqrt but why bother - there is no Math.hypot function - if (tempDist <= relDistance) - { - relDistance = tempDist; - controllerCandidate = curContainerController; - candidateLocalX = containerPoint.x+adjustX; - candidateLocalY = containerPoint.y+adjustY; - } - } - - - rslt = controllerCandidate ? computeSelectionIndexInContainer(textFlow, controllerCandidate, candidateLocalX, candidateLocalY) : -1; - } + var localPoint:Point = new Point(localX,localY); + var controller:ContainerController = findController(textFlow, target, currentTarget, localPoint); + rslt = controller ? computeSelectionIndexInContainer(textFlow, controller, localPoint.x, localPoint.y) : -1; } if (rslt >= textFlow.textLength) @@ -1339,7 +1951,50 @@ package flashx.textLayout.edit */ public function mouseDownHandler(event:MouseEvent):void { - handleMouseEventForSelection(event, event.shiftKey); + if(subManager) + subManager.selectRange(-1,-1); + + var cell:TableCellElement = _textFlow.parentElement as TableCellElement; + var coords:CellCoordinates; + if(!cell) + coords = computeCellCoordinates(textFlow,event.target,event.currentTarget,event.localX, event.localY); + if(cell || coords) + { + if(coords) + cell = currentTable.findCell(coords); + + superManager = cell.getTextFlow().interactionManager; + if(event.shiftKey && cell.getTable() == superManager.currentTable) + { + // expand cell selection if applicable + coords = new CellCoordinates(cell.rowIndex,cell.colIndex); + if( + !CellCoordinates.areEqual(coords,superManager.anchorCellPosition) || + superManager.activeCellPosition.isValid() + ){ + superManager.selectCellRange(superManager.anchorCellPosition,coords); + superManager.subManager = null; + allowOperationMerge = false; + event.stopPropagation(); + return; + } + } + if(superManager == this) + { + if(cell.textFlow.interactionManager) + { + cell.textFlow.interactionManager.mouseDownHandler(event); + } + return; + } + superManager.currentTable = cell.getTable(); + superManager.deselect(); + //superManager.setSelectionState(new SelectionState(superManager.textFlow,-1,-1) ); + superManager.anchorCellPosition.column = cell.colIndex; + superManager.anchorCellPosition.row = cell.rowIndex; + superManager.subManager = this; + } + handleMouseEventForSelection(event, event.shiftKey, cell != null); } /** @@ -1348,17 +2003,56 @@ package flashx.textLayout.edit * @playerversion AIR 1.5 * @langversion 3.0 */ - public function mouseMoveHandler(event:MouseEvent):void - { + public function mouseMoveHandler(event:MouseEvent):void { var wmode:String = textFlow.computedFormat.blockProgression; - if (wmode != BlockProgression.RL) - setMouseCursor(MouseCursor.IBEAM); + + if (wmode != BlockProgression.RL) { + setMouseCursor(MouseCursor.IBEAM); + } + + if (event.buttonDown) - handleMouseEventForSelection(event, true); + { + var cell:TableCellElement = _textFlow.parentElement as TableCellElement; + + // if the event is owned by a cell, we need to check if the mouse is now above another cell to select a cell range. + if (cell) { + + do { + var cellCoords:CellCoordinates = new CellCoordinates(cell.rowIndex, cell.colIndex, cell.getTable()); + var coords:CellCoordinates = computeCellCoordinates(cell.getTextFlow(), event.target, event.currentTarget, event.localX, event.localY); + if(!coords) + break; + if(CellCoordinates.areEqual(cellCoords, coords) && + (!superManager.activeCellPosition.isValid() || CellCoordinates.areEqual(coords, superManager.activeCellPosition)) + ) + break; + if(coords.table != cellCoords.table) + break; + + superManager = cell.getTextFlow().interactionManager; + if( + !CellCoordinates.areEqual(coords, superManager.activeCellPosition) + ){ + allowOperationMerge = false; + superManager.selectCellRange(superManager.anchorCellPosition, coords); + event.stopPropagation(); + return; + } + + + }while(0); + } + if(superManager && superManager.getCellRange()) + return; + + handleMouseEventForSelection(event, true, _textFlow.parentElement != null); + + } } /** @private */ - tlf_internal function handleMouseEventForSelection(event:MouseEvent, allowExtend:Boolean):void + tlf_internal function handleMouseEventForSelection(event:MouseEvent, allowExtend:Boolean,stopPropogate:Boolean=false):void { var startSelectionActive:Boolean = hasSelection(); @@ -1371,6 +2065,8 @@ package flashx.textLayout.edit addSelectionShapes(); } allowOperationMerge = false; + if(stopPropogate) + event.stopPropagation(); } /** @@ -1479,10 +2175,60 @@ package flashx.textLayout.edit { _mouseOverSelectionArea = true; var wmode:String = textFlow.computedFormat.blockProgression; - if (wmode != BlockProgression.RL) - setMouseCursor(MouseCursor.IBEAM); - else - setMouseCursor(MouseCursor.AUTO); + + if (wmode != BlockProgression.RL) { + var cell:TableCellElement = _textFlow.parentElement as TableCellElement; + + // set the cursor if around the edge of the table + if (cell) { + var leftEdge:int = 5; + var topEdge:int = 5; + var globalPoint:Point = new Point(event.stageX, event.stageY); + var cellContainer:CellContainer = event.currentTarget as CellContainer; + var point:Point; + + if (cellContainer) { + var cellContainerPoint:Point = cellContainer.localToGlobal(new Point); + point = globalPoint.subtract(cellContainerPoint); + } + if(useTableSelectionCursors) + { + // set cursor for row, table or column + if (cell.colIndex==0 && point.x<leftEdge && point.y>topEdge) + { + event.stopPropagation(); + event.stopImmediatePropagation(); + setMouseCursor(SelectTableRow); + } + else if (cell.rowIndex==0 && cell.colIndex==0 && + point.x<leftEdge && point.y<topEdge) + { + event.stopPropagation(); + event.stopImmediatePropagation(); + setMouseCursor(SelectTable); + } + else if (cell.rowIndex==0 && point.x>leftEdge && point.y<topEdge) + { + event.stopPropagation(); + event.stopImmediatePropagation(); + setMouseCursor(SelectTableColumn); + } + else { + setMouseCursor(MouseCursor.IBEAM); + } + + } + else { + setMouseCursor(MouseCursor.IBEAM); + } + } + else { + setMouseCursor(MouseCursor.IBEAM); + } + } + else { + setMouseCursor(MouseCursor.AUTO); + } } /** @@ -1873,6 +2619,9 @@ package flashx.textLayout.edit } else if (event.keyCode == Keyboard.ESCAPE) handleKeyEvent(event); + if(_textFlow.parentElement) + event.stopPropagation(); + } /** @@ -2098,9 +2847,26 @@ package flashx.textLayout.edit { var idx:int = marks.indexOf(mark); if (idx != -1) - marks.splice(idx,idx+1); + marks.splice(idx,1); } - + + private var cellMarks:Array = []; + + /** @private */ + tlf_internal function createCellMark():CellCoordinates + { + var mark:CellCoordinates = new CellCoordinates(-1,-1); + cellMarks.push(mark); + return mark; + } + /** @private */ + tlf_internal function removeCellMark(mark:CellCoordinates):void + { + var idx:int = cellMarks.indexOf(mark); + if (idx != -1) + marks.splice(idx,1); + } + /** * @copy ISelectionManager#notifyInsertOrDelete() * @@ -2126,5 +2892,138 @@ package flashx.textLayout.edit } } } + + /** + * The ISelectionManager object used to for cell selections nested within the TextFlow managed by this ISelectionManager. + * + * @playerversion Flash 10 + * @playerversion AIR 1.5 + * @langversion 3.0 + */ + public function get subManager():ISelectionManager + { + return _subManager; + } + public function set subManager(value:ISelectionManager):void + { + if(_subManager) + _subManager.selectRange(-1,-1); + _subManager = value; + } + /** + * The ISelectionManager object used to manage the parent TextFlow of this ISelectionManager (i.e. for cell ISelectionManagers). + * + * @playerversion Flash 10 + * @playerversion AIR 1.5 + * @langversion 3.0 + */ + + public function get superManager():ISelectionManager + { + return _superManager; + } + + public function set superManager(value:ISelectionManager):void + { + _superManager = value; + } + + /** Anchor point of the current cell selection, as coordinates within the table. */ + public function get anchorCellPosition():CellCoordinates + { + return _anchorCellPosition; + } + public function set anchorCellPosition(value:CellCoordinates):void + { + _anchorCellPosition = value; + } + + /** Active end of the current cell selection, as coordinates within the table. */ + public function get activeCellPosition():CellCoordinates + { + return _activeCellPosition; + } + public function set activeCellPosition(value:CellCoordinates):void + { + _activeCellPosition = value; + } + + public var selectTableCursorPoints:Vector.<Number> = new <Number>[1,3, 11,3, 11,0, 12,0, 16,4, 12,8, 11,8, 11,5, 1,5, 1,3]; + public var selectTableCursorDrawCommands:Vector.<int> = new <int>[1,2,2,2,2,2,2,2,2,2]; + + + /** + * Create a select table cursor + */ + public function createSelectTableCursor():MouseCursorData { + var cursorData:Vector.<BitmapData> = new Vector.<BitmapData>(); + var cursorShape:Shape = new Shape(); + cursorShape.graphics.beginFill(0x0, 1); + cursorShape.graphics.lineStyle(0, 0xFFFFFF, 1, true); + cursorShape.graphics.drawPath( selectTableCursorDrawCommands, selectTableCursorPoints); + cursorShape.graphics.endFill(); + var transformer:Matrix = new Matrix(); + var cursorFrame:BitmapData = new BitmapData(32, 32, true, 0); + var angle:int = 8; + var rotation:Number = 0.785398163; + transformer.translate(-angle,-angle); + transformer.rotate(rotation); + transformer.translate(angle, angle); + cursorFrame.draw(cursorShape, transformer); + cursorData.push(cursorFrame); + var mouseCursorData:MouseCursorData = new MouseCursorData(); + mouseCursorData.data = cursorData; + mouseCursorData.hotSpot = new Point(16, 10); + mouseCursorData.frameRate = 1; + return mouseCursorData; + } + + /** + * Create a select row cursor + */ + public function createSelectTableRowCursor():MouseCursorData { + var cursorData:Vector.<BitmapData> = new Vector.<BitmapData>(); + var cursorShape:Shape = new Shape(); + cursorShape.graphics.beginFill(0x0, 1); + cursorShape.graphics.lineStyle(0, 0xFFFFFF, 1, true); + cursorShape.graphics.drawPath(selectTableCursorDrawCommands, selectTableCursorPoints); + cursorShape.graphics.endFill(); + var transformer:Matrix = new Matrix(); + var cursorFrame:BitmapData = new BitmapData(32, 32, true, 0); + cursorFrame.draw(cursorShape, transformer); + cursorData.push(cursorFrame); + var mouseCursorData:MouseCursorData = new MouseCursorData(); + mouseCursorData.data = cursorData; + mouseCursorData.hotSpot = new Point(16, 4); + mouseCursorData.frameRate = 1; + return mouseCursorData; + } + + /** + * Create a select table column cursor + */ + public function createSelectTableColumnCursor():MouseCursorData { + var cursorData:Vector.<BitmapData> = new Vector.<BitmapData>(); + var cursorShape:Shape = new Shape(); + cursorShape.graphics.beginFill(0x0, 1 ); + cursorShape.graphics.lineStyle(0, 0xFFFFFF, 1, true ); + cursorShape.graphics.drawPath(selectTableCursorDrawCommands, selectTableCursorPoints); + cursorShape.graphics.endFill(); + var transformer:Matrix = new Matrix(); + var cursorFrame:BitmapData = new BitmapData(32, 32, true, 0); + var angle:int = 16; + var rotation:Number = 0.785398163; + transformer.translate(-angle,-angle); + transformer.rotate(rotation * 2); + transformer.translate(angle, angle); + cursorFrame.draw(cursorShape, transformer); + cursorData.push(cursorFrame); + var mouseCursorData:MouseCursorData = new MouseCursorData(); + mouseCursorData.data = cursorData; + mouseCursorData.hotSpot = new Point(28, 16); + mouseCursorData.frameRate = 1; + return mouseCursorData; + } + } }
http://git-wip-us.apache.org/repos/asf/flex-tlf/blob/33df98ab/textLayout/src/flashx/textLayout/edit/SelectionState.as ---------------------------------------------------------------------- diff --git a/textLayout/src/flashx/textLayout/edit/SelectionState.as b/textLayout/src/flashx/textLayout/edit/SelectionState.as index 4d500e4..379f023 100644 --- a/textLayout/src/flashx/textLayout/edit/SelectionState.as +++ b/textLayout/src/flashx/textLayout/edit/SelectionState.as @@ -27,6 +27,8 @@ package flashx.textLayout.edit use namespace tlf_internal; import flashx.textLayout.tlf_internal; + import flashx.textLayout.elements.CellRange; + use namespace tlf_internal; /** * The SelectionState class represents a selection in a text flow. @@ -49,6 +51,8 @@ package flashx.textLayout.edit /** Format that are associated with the caret position & will be applied to inserted text */ private var _pointFormat:ITextLayoutFormat; + private var _cellRange:CellRange; + private var _selectionManagerOperationState:Boolean; /** @@ -72,11 +76,12 @@ package flashx.textLayout.edit * @playerversion AIR 1.5 * @langversion 3.0 */ - public function SelectionState(root:TextFlow,anchorPosition:int,activePosition:int,format:ITextLayoutFormat = null) + public function SelectionState(root:TextFlow,anchorPosition:int,activePosition:int,format:ITextLayoutFormat = null,cellRange:CellRange = null) { super(root, anchorPosition, activePosition); if (format) _pointFormat = format; + _cellRange = cellRange; } /** @@ -126,5 +131,20 @@ package flashx.textLayout.edit /** @private */ tlf_internal function clone():SelectionState { return new SelectionState(textFlow,anchorPosition,activePosition,pointFormat); } + + /** Range of table cells in selection (null if no cells selected)*/ + public function get cellRange():CellRange + { + return _cellRange; + } + + /** + * @private + */ + public function set cellRange(value:CellRange):void + { + _cellRange = value; + } + } } \ No newline at end of file http://git-wip-us.apache.org/repos/asf/flex-tlf/blob/33df98ab/textLayout/src/flashx/textLayout/edit/TextFlowEdit.as ---------------------------------------------------------------------- diff --git a/textLayout/src/flashx/textLayout/edit/TextFlowEdit.as b/textLayout/src/flashx/textLayout/edit/TextFlowEdit.as index 347aa53..235cd08 100644 --- a/textLayout/src/flashx/textLayout/edit/TextFlowEdit.as +++ b/textLayout/src/flashx/textLayout/edit/TextFlowEdit.as @@ -1114,10 +1114,14 @@ package flashx.textLayout.edit /** if parent is a singleton element, deletes it, then repeats deletion of singletons up the parent chain. Used after paragraph merge. */ tlf_internal static function removeEmptyParentChain(parent:FlowGroupElement):IMemento { + if(parent is ParagraphElement) + ParagraphElement(parent).removeEmptyTerminator(); var mementoList:MementoList = new MementoList(parent.getTextFlow()); while(parent && (parent.numChildren == 0)) { var grandParent:FlowGroupElement = parent.parent; + if(grandParent is ParagraphElement) + ParagraphElement(grandParent).removeEmptyTerminator(); if(grandParent) { var parentIdx:int = grandParent.getChildIndex(parent); http://git-wip-us.apache.org/repos/asf/flex-tlf/blob/33df98ab/textLayout/src/flashx/textLayout/elements/BackgroundManager.as ---------------------------------------------------------------------- diff --git a/textLayout/src/flashx/textLayout/elements/BackgroundManager.as b/textLayout/src/flashx/textLayout/elements/BackgroundManager.as index adee363..b29accf 100644 --- a/textLayout/src/flashx/textLayout/elements/BackgroundManager.as +++ b/textLayout/src/flashx/textLayout/elements/BackgroundManager.as @@ -32,6 +32,7 @@ package flashx.textLayout.elements import flashx.textLayout.compose.ParcelList; import flashx.textLayout.compose.StandardFlowComposer; import flashx.textLayout.compose.TextFlowLine; + import flashx.textLayout.compose.TextFlowTableBlock; import flashx.textLayout.container.ContainerController; import flashx.textLayout.container.TextContainerManager; import flashx.textLayout.debug.assert; @@ -45,7 +46,6 @@ package flashx.textLayout.elements use namespace tlf_internal; - [ExcludeClass] /** @private Manages bounds calculation and rendering of backgroundColor character format. */ public class BackgroundManager { @@ -109,6 +109,34 @@ package flashx.textLayout.elements } } + public static function collectTableBlock(_textFlow:TextFlow,block:TextFlowTableBlock,controller:ContainerController):void + { + // add block rect for each cell in table block + + var bb:BackgroundManager; + var r:Rectangle; + var composer:IFlowComposer; + + var cells:Vector.<TableCellElement> = block.getTableCells(); + for each(var cell:TableCellElement in cells){ + if(BackgroundManager.hasBorderOrBackground(cell)) + { + if(!_textFlow.backgroundManager) + _textFlow.getBackgroundManager(); + bb = _textFlow.backgroundManager; + + bb.addBlockElement(cell); + + var row:TableRowElement = cell.getRow(); + r = new Rectangle(cell.x, cell.y + block.y, cell.width, row.composedHeight); + bb.addBlockRect(cell, r, controller); + + } + } + block.y; + + } + public static function collectBlock(_textFlow:TextFlow, elem:FlowGroupElement, _parcelList:ParcelList = null, tableComposeNotFromBeginning:Boolean = false, tableOutOfView:Boolean = false):void { var bb:BackgroundManager; @@ -118,61 +146,8 @@ package flashx.textLayout.elements if(elem) { - //The height of TableDataCellElement can only be identified after all the cells in the row are composed. - //So, pick it out of the common process - if(elem is TableRowElement) - { - var tabRow:TableRowElement = elem as TableRowElement; - //for table cells - var cell:TableDataCellElement; - var cellParcel:Parcel; - for(var cIdx:Number = 0; cIdx < elem.numChildren; cIdx++) - { - cell = elem.getChildAt(cIdx) as TableDataCellElement; - if(BackgroundManager.hasBorderOrBackground(cell) || BackgroundManager.hasBorderOrBackground(elem)) - { - //mark the paragraph that has border or background - if(!_textFlow.backgroundManager) - _textFlow.getBackgroundManager(); - bb = _textFlow.backgroundManager; - - //BackgroundManager should not be null here - CONFIG::debug { assert(_textFlow.backgroundManager != null ,"BackgroundManager should not be null"); } - - bb.addBlockElement(cell); - - cellParcel = _parcelList.getParcelAt(cell.parcelIndex); - if(cellParcel) - { - r = new Rectangle(cell.x, cell.y, cell.width, tabRow.height); - bb.addBlockRect(cell, r, cellParcel.controller); - } - } - } - - //for table rows - /*if(BackgroundManager.hasBorderOrBackground(elem)) - { - //mark the paragraph that has border or background - if(!_textFlow.backgroundManager) - _textFlow.getBackgroundManager(); - bb = _textFlow.backgroundManager; - - //BackgroundManager should not be null here - CONFIG::debug { assert(_textFlow.backgroundManager != null ,"BackgroundManager should not be null"); } - - bb.addBlockElement(elem); - - var parentTable:TableElement = elem.parent as TableElement; - var rowParcel:Parcel = _parcelList.getParcelAt(tabRow.parcelIndex); - if(parentTable && rowParcel){ - r = new Rectangle(parentTable.x + rowParcel.x, tabRow.y + rowParcel.y, parentTable.computedWidth, tabRow.height); - bb.addBlockRect(elem, r, rowParcel.controller); - } - }*/ - } - //for the other elements - else if(BackgroundManager.hasBorderOrBackground(elem)) + + if(BackgroundManager.hasBorderOrBackground(elem)) { //mark the paragraph that has border or background if(!_textFlow.backgroundManager) @@ -189,61 +164,9 @@ package flashx.textLayout.elements { if(elem is TableElement) { + // Do we need to do anything for table elements? Not sure... var tab:TableElement = elem as TableElement; - var parcel:Parcel; - if(tab.numAcrossParcels == 0) - { - r = new Rectangle(); - parcel = _parcelList.getParcelAt(tab.originParcelIndex); - if(parcel) - { - if(tableComposeNotFromBeginning) - { - r.x = parcel.x; - r.y = parcel.y; - } - else - { - r.x = tab.x; - r.y = tab.y; - } - r.width = tab.computedWidth; - r.height = tab.height; - bb.addBlockRect(elem, r, parcel.controller); - } - }else - { - for(var tIdx:Number = 0; tIdx <= tab.numAcrossParcels; tIdx++) - { - r = new Rectangle(); - parcel = _parcelList.getParcelAt(tab.originParcelIndex + tIdx); - if(parcel) - { - if(tIdx == 0 && !tableComposeNotFromBeginning) - { - r.x = tab.x; - r.y = tab.y; - r.width = tab.computedWidth; - r.height = tab.heightArray[tIdx]; - bb.addBlockRect(elem, r, parcel.controller, BackgroundManager.BOTTOM_EXCLUDED); - }else if (tIdx == tab.numAcrossParcels && !tableOutOfView) - { - r.x = parcel.x + tab.computedFormat.marginLeft; - r.y = parcel.y; - r.width = tab.computedWidth; - r.height = tab.totalRowDepth; - bb.addBlockRect(elem, r, parcel.controller, BackgroundManager.TOP_EXCLUDED); - }else - { - r.x = parcel.x + tab.computedFormat.marginLeft; - r.y = parcel.y; - r.width = tab.computedWidth; - r.height = tab.heightArray[tIdx]; - bb.addBlockRect(elem, r, parcel.controller, BackgroundManager.TOP_AND_BOTTOM_EXCLUDED); - } - } - } - } + } else //for elements like ParagraphElement, DivElement, ListItemElement, ListElement, TextFlow { @@ -461,8 +384,9 @@ package flashx.textLayout.elements //draw background if(style.backgroundColor != BackgroundColor.TRANSPARENT) { - g.lineStyle(0, style.backgroundColor, style.backgroundAlpha, true); - g.beginFill(style.backgroundColor); + // The value 0 indicates hairline thickness; + g.lineStyle(NaN, style.backgroundColor, style.backgroundAlpha, true); + g.beginFill(style.backgroundColor, style.backgroundAlpha); g.drawRect(rec.x, rec.y, rec.width, rec.height); g.endFill(); } @@ -577,8 +501,9 @@ package flashx.textLayout.elements //draw background if(style.backgroundColor != BackgroundColor.TRANSPARENT) { - g.lineStyle(0, style.backgroundColor, style.backgroundAlpha, true); - g.beginFill(style.backgroundColor); + // The value 0 indicates hairline thickness; NaN removes line + g.lineStyle(NaN, style.backgroundColor, style.backgroundAlpha, true); + g.beginFill(style.backgroundColor, style.backgroundAlpha); g.drawRect(rec.x, rec.y, rec.width, rec.height); g.endFill(); } @@ -618,7 +543,11 @@ package flashx.textLayout.elements //draw background for span for(var childIdx:int = 0; childIdx<controller.textLines.length; ++childIdx) { - var tl:TextLine = controller.textLines[childIdx]; + var line:* = controller.textLines[childIdx]; + // skip TextFlowTableBlocks + if(!(line is TextLine)) + continue; + var tl:TextLine = line; var entry:Array = _lineDict[tl]; if (entry) http://git-wip-us.apache.org/repos/asf/flex-tlf/blob/33df98ab/textLayout/src/flashx/textLayout/elements/CellContainer.as ---------------------------------------------------------------------- diff --git a/textLayout/src/flashx/textLayout/elements/CellContainer.as b/textLayout/src/flashx/textLayout/elements/CellContainer.as index 336da5a..256c495 100644 --- a/textLayout/src/flashx/textLayout/elements/CellContainer.as +++ b/textLayout/src/flashx/textLayout/elements/CellContainer.as @@ -20,13 +20,37 @@ package flashx.textLayout.elements { import flash.display.Sprite; - public class CellContainer extends Sprite + //import mx.core.IIMESupport; + + public class CellContainer extends Sprite// implements IIMESupport { - public var userData:Object=null; + private var _imeMode:String; + private var _enableIME:Boolean; + public var element:TableCellElement; + + public function CellContainer(imeEnabled:Boolean = true) + { + _enableIME = imeEnabled; + } + + public function get enableIME():Boolean + { + return false; + } + + public function set enableIME(value:Boolean):void + { + _enableIME = value; + } + + public function get imeMode():String + { + return _imeMode; + } - public function CellContainer() + public function set imeMode(value:String):void { - super(); + _imeMode = value; } } -} \ No newline at end of file +} http://git-wip-us.apache.org/repos/asf/flex-tlf/blob/33df98ab/textLayout/src/flashx/textLayout/elements/FlowElement.as ---------------------------------------------------------------------- diff --git a/textLayout/src/flashx/textLayout/elements/FlowElement.as b/textLayout/src/flashx/textLayout/elements/FlowElement.as index 6ef5053..12ba288 100644 --- a/textLayout/src/flashx/textLayout/elements/FlowElement.as +++ b/textLayout/src/flashx/textLayout/elements/FlowElement.as @@ -805,6 +805,14 @@ package flashx.textLayout.elements { setStyle(styleProp,undefined); } /** + * Called when an element is removed. Used for container elements to run any clean up code. + **/ + tlf_internal function removed():void + { + // override in sub classes + } + + /** * Called whenever the model is modified. Updates the TextFlow and notifies the selection manager - if it is set. * This method has to be called while the element is still in the flow * @param changeType - type of change @@ -1024,6 +1032,24 @@ package flashx.textLayout.elements } + public function isInTable():Boolean + { + var tf:TextFlow = getTextFlow(); + return tf && tf.parentElement && tf.parentElement is TableCellElement; + } + + public function getParentCellElement():TableCellElement + { + var tf:TextFlow = getTextFlow(); + + if(!tf) + return null; + if(tf.parentElement && tf.parentElement is TableCellElement) + return tf.parentElement as TableCellElement; + return null; + } + + /** * Returns the FlowElement object that contains this FlowElement object, if this element is contained within * an element of a particular type. http://git-wip-us.apache.org/repos/asf/flex-tlf/blob/33df98ab/textLayout/src/flashx/textLayout/elements/FlowGroupElement.as ---------------------------------------------------------------------- diff --git a/textLayout/src/flashx/textLayout/elements/FlowGroupElement.as b/textLayout/src/flashx/textLayout/elements/FlowGroupElement.as index b02a22d..62567dd 100644 --- a/textLayout/src/flashx/textLayout/elements/FlowGroupElement.as +++ b/textLayout/src/flashx/textLayout/elements/FlowGroupElement.as @@ -582,7 +582,7 @@ package flashx.textLayout.elements } /** @private */ - tlf_internal function createContentAsGroup():GroupElement + tlf_internal function createContentAsGroup(pos:int=0):GroupElement { CONFIG::debug { assert(false,"invalid call to createContentAsGroup"); } return null; @@ -626,7 +626,7 @@ package flashx.textLayout.elements */ tlf_internal function canOwnFlowElement(elem:FlowElement):Boolean { - return !(elem is TextFlow) && !(elem is FlowLeafElement) && !(elem is SubParagraphGroupElementBase) && !(elem is ListItemElement); + return !(elem is TextFlow) && !(elem is FlowLeafElement) && !(elem is SubParagraphGroupElementBase) && !(elem is ListItemElement) && !(elem is TableElement); } /** @private */ @@ -687,6 +687,7 @@ package flashx.textLayout.elements { child = this.getChildAt(beginChildIndex); this.modelChanged(ModelChange.ELEMENT_REMOVAL, child, child.parentRelativeStart, child.textLength); + child.removed(); len += child.textLength; child.setParentAndRelativeStart(null,0); @@ -770,8 +771,10 @@ package flashx.textLayout.elements relStartIdx = beginChildIndex == _numChildren ? textLength : getChildAt(beginChildIndex).parentRelativeStart; } } - if (!canOwnFlowElement(newChild)) - throw ArgumentError(GlobalSettings.resourceStringFunction("invalidChildType")); + + if (!canOwnFlowElement(newChild)) { + throw ArgumentError(GlobalSettings.resourceStringFunction("invalidChildType") + ". " + defaultTypeName + " cannot own " + newChild.defaultTypeName); + } // manage as an array or a single child if (childrenToAdd == 0) @@ -988,6 +991,7 @@ package flashx.textLayout.elements parent.replaceChildren(myidx+1,myidx+1,newSibling); } + newSibling.normalizeRange(0,newSibling.textLength); return newSibling; } http://git-wip-us.apache.org/repos/asf/flex-tlf/blob/33df98ab/textLayout/src/flashx/textLayout/elements/FlowLeafElement.as ---------------------------------------------------------------------- diff --git a/textLayout/src/flashx/textLayout/elements/FlowLeafElement.as b/textLayout/src/flashx/textLayout/elements/FlowLeafElement.as index 22bfe40..894a362 100644 --- a/textLayout/src/flashx/textLayout/elements/FlowLeafElement.as +++ b/textLayout/src/flashx/textLayout/elements/FlowLeafElement.as @@ -337,6 +337,8 @@ package flashx.textLayout.elements { if (!_blockElement) createContentElement(); + if(!_blockElement) + return null; var ef:ElementFormat = _blockElement.elementFormat; if (!ef) return null;