1//////////////////////////////////////////////////////////////////////////////// 2// 3// ADOBE SYSTEMS INCORPORATED 4// Copyright 2008 Adobe Systems Incorporated 5// All Rights Reserved. 6// 7// NOTICE: Adobe permits you to use, modify, and distribute this file 8// in accordance with the terms of the license agreement accompanying it. 9// 10//////////////////////////////////////////////////////////////////////////////// 11 12package spark.components.supportClasses 13{ 14 15import flash.display.BlendMode; 16import flash.display.Graphics; 17import flash.events.Event; 18import flash.events.FocusEvent; 19import flash.events.KeyboardEvent; 20import flash.events.MouseEvent; 21import flash.geom.Rectangle; 22import flash.ui.ContextMenu; 23import flash.ui.Keyboard; 24 25import flashx.textLayout.container.ContainerController; 26import flashx.textLayout.container.TextContainerManager; 27import flashx.textLayout.edit.EditManager; 28import flashx.textLayout.edit.EditingMode; 29import flashx.textLayout.edit.ElementRange; 30import flashx.textLayout.edit.IEditManager; 31import flashx.textLayout.edit.ISelectionManager; 32import flashx.textLayout.edit.SelectionFormat; 33import flashx.textLayout.edit.SelectionManager; 34import flashx.textLayout.edit.SelectionState; 35import flashx.textLayout.elements.FlowLeafElement; 36import flashx.textLayout.elements.IConfiguration; 37import flashx.textLayout.elements.ParagraphElement; 38import flashx.textLayout.elements.TextFlow; 39import flashx.textLayout.elements.TextRange; 40import flashx.textLayout.events.SelectionEvent; 41import flashx.textLayout.formats.Category; 42import flashx.textLayout.formats.ITextLayoutFormat; 43import flashx.textLayout.formats.TextLayoutFormat; 44import flashx.textLayout.operations.ApplyFormatOperation; 45import flashx.textLayout.operations.InsertTextOperation; 46import flashx.textLayout.property.Property; 47import flashx.textLayout.tlf_internal; 48import flashx.undo.IUndoManager; 49import flashx.undo.UndoManager; 50 51import mx.core.mx_internal; 52import mx.events.SandboxMouseEvent; 53import mx.styles.IStyleClient; 54 55import spark.components.RichEditableText; 56import spark.components.TextSelectionHighlighting; 57 58use namespace mx_internal; 59use namespace tlf_internal; 60 61[ExcludeClass] 62 63/** 64 * @private 65 * A subclass of TextContainerManager that manages the text in 66 * a RichEditableText component. 67 * 68 * @langversion 3.0 69 * @playerversion Flash 10 70 * @playerversion AIR 1.5 71 * @productversion Flex 4 72 */ 73public class RichEditableTextContainerManager extends TextContainerManager 74{ 75 /** 76 * Constructor. 77 * 78 * @langversion 3.0 79 * @playerversion Flash 10 80 * @playerversion AIR 1.5 81 * @productversion Flex 4 82 */ 83 public function RichEditableTextContainerManager( 84 container:RichEditableText, 85 configuration:IConfiguration=null) 86 { 87 super(container, configuration); 88 89 textDisplay = container; 90 } 91 92 //-------------------------------------------------------------------------- 93 // 94 // Variables 95 // 96 //-------------------------------------------------------------------------- 97 98 /** 99 * @private 100 */ 101 private var hasScrollRect:Boolean = false; 102 103 /** 104 * @private 105 */ 106 private var textDisplay:RichEditableText; 107 108 //-------------------------------------------------------------------------- 109 // 110 // Overridden methods 111 // 112 //-------------------------------------------------------------------------- 113 114 /** 115 * @private 116 */ 117 override public function drawBackgroundAndSetScrollRect( 118 scrollX:Number, scrollY:Number):Boolean 119 { 120 // If not auto-sizing these are the same as the compositionWidth/Height. 121 // If auto-sizing, the compositionWidth/Height may be NaN. If no 122 // constraints this will reflect the actual size of the text. 123 var width:Number = textDisplay.width; 124 var height:Number = textDisplay.height; 125 126 var contentBounds:Rectangle = getContentBounds(); 127 128 // If measuring width, use the content width. 129 if (isNaN(width)) 130 width = contentBounds.right; 131 132 // If measuring height, use the content height. 133 if (isNaN(height)) 134 height = contentBounds.bottom; 135 136 // TODO:(cframpto) Adjust for RL text. 137 // See ContainerController.updateVisibleRectangle(). 138 // (effectiveBlockProgression == BlockProgression.RL) ? -width : 0; 139 var xOrigin:Number = 0; 140 141 // If autoSize, and lineBreak="toFit" there should never be 142 // a scroll rect but if lineBreak="explicit" the text may need 143 // to be clipped. 144 if (scrollX == 0 && scrollY == 0 && 145 contentBounds.left >= xOrigin && 146 contentBounds.right <= width && 147 contentBounds.top >= 0 && 148 contentBounds.bottom <= height) 149 { 150 // skip the scrollRect 151 if (hasScrollRect) 152 { 153 container.scrollRect = null; 154 hasScrollRect = false; 155 } 156 } 157 else 158 { 159 container.scrollRect = new Rectangle(scrollX, scrollY, width, height); 160 hasScrollRect = true; 161 } 162 163 // Client must draw a background to get mouse events, 164 // even it if is 100% transparent. 165 // If backgroundColor is defined, fill the bounds of the component 166 // with backgroundColor drawn with alpha level backgroundAlpha. 167 // Otherwise, fill with transparent black. 168 // (The color in this case is irrelevant.) 169 var color:uint = 0x000000; 170 var alpha:Number = 0.0; 171 var styleableContainer:IStyleClient = container as IStyleClient; 172 if (styleableContainer) 173 { 174 var backgroundColor:* = 175 styleableContainer.getStyle("backgroundColor"); 176 if (backgroundColor !== undefined) 177 { 178 color = uint(backgroundColor); 179 alpha = styleableContainer.getStyle("backgroundAlpha"); 180 } 181 } 182 // TODO (cframpto): Adjust for RL text. See 183 // ContainerController.attachTransparentBackgroundForHit(). 184 var g:Graphics = container.graphics; 185 g.clear(); 186 g.lineStyle(); 187 g.beginFill(color, alpha); 188 g.drawRect(scrollX, scrollY, width, height); 189 g.endFill(); 190 191 return hasScrollRect; 192 } 193 194 /** 195 * @private 196 * 197 * If the user specified a custom context menu then use 198 * it rather than the default context menu. It must be set before the 199 * first mouse over/mouse hover or foucsIn event to be used. 200 * 201 * TLF will remove the context menu when it switches from the factory 202 * to the composer and the controller will then request it again. 203 */ 204 override tlf_internal function getContextMenu():ContextMenu 205 { 206 // ToDo(cframpto): Ideally could specify the context 207 // menu on the TextArea or the TextInput and it wouldn't be obscured 208 // by TLF's context menu. 209 210 // Return null to use the existing contextMenu on the container. 211 // Otherwise the TCM will overwrite this contextMenu. 212 return textDisplay.contextMenu != null ? null : super.getContextMenu(); 213 } 214 215 /** 216 * @private 217 */ 218 override protected function getUndoManager():IUndoManager 219 { 220 if (!textDisplay.undoManager) 221 { 222 textDisplay.undoManager = new UndoManager(); 223 textDisplay.undoManager.undoAndRedoItemLimit = int.MAX_VALUE; 224 } 225 226 return textDisplay.undoManager; 227 } 228 229 /** 230 * @private 231 */ 232 override protected function getFocusedSelectionFormat():SelectionFormat 233 { 234 var selectionColor:* = textDisplay.getStyle("focusedTextSelectionColor"); 235 236 var focusedPointAlpha:Number = 237 editingMode == EditingMode.READ_WRITE ? 238 1.0 : 239 0.0; 240 241 // If editable, the insertion point is black, inverted, which makes it 242 // the inverse color of the background, for maximum readability. 243 // If not editable, then no insertion point. 244 return new SelectionFormat( 245 selectionColor, 1.0, BlendMode.NORMAL, 246 0x000000, focusedPointAlpha, BlendMode.INVERT); 247 } 248 249 /** 250 * @private 251 */ 252 override protected function getUnfocusedSelectionFormat():SelectionFormat 253 { 254 var unfocusedSelectionColor:* = textDisplay.getStyle( 255 "unfocusedTextSelectionColor"); 256 257 var unfocusedAlpha:Number = 258 textDisplay.selectionHighlighting != 259 TextSelectionHighlighting.WHEN_FOCUSED ? 260 1.0 : 261 0.0; 262 263 // No insertion point when no focus. 264 return new SelectionFormat( 265 unfocusedSelectionColor, unfocusedAlpha, BlendMode.NORMAL, 266 unfocusedSelectionColor, 0.0); 267 } 268 269 /** 270 * @private 271 */ 272 override protected function getInactiveSelectionFormat():SelectionFormat 273 { 274 var inactiveSelectionColor:* = textDisplay.getStyle( 275 "inactiveTextSelectionColor"); 276 277 var inactiveAlpha:Number = 278 textDisplay.selectionHighlighting == 279 TextSelectionHighlighting.ALWAYS ? 280 1.0 : 281 0.0; 282 283 // No insertion point when not active. 284 return new SelectionFormat( 285 inactiveSelectionColor, inactiveAlpha, BlendMode.NORMAL, 286 inactiveSelectionColor, 0.0); 287 } 288 289 /** 290 * @private 291 */ 292 override protected function createEditManager( 293 undoManager:flashx.undo.IUndoManager):IEditManager 294 { 295 var editManager:IEditManager = super.createEditManager(undoManager); 296 297 // Default is to batch text input. If the component, like ComboBox 298 // wants to act on each keystroke then set this to false. 299 editManager.allowDelayedOperations = textDisplay.batchTextInput; 300 301 // Do not delayUpdates until further work is done to ensure our public API methods to 302 // format and insert text work correctly. TLF does not dispatch the selectionChange 303 // event until the display is updated which means our selection properties may not 304 // be in sync with the TLF values. This could matter for our API methods that 305 // take the selection as parameters or default to the current selection. In the 306 // former case, the user could query for the selection or rely on the selectionChange 307 // event and get incorrect values if there is a pending update and in the later case 308 // we fill in the default selection which might not be current if there is a pending 309 // update. 310 editManager.delayUpdates = false; 311 312 return editManager; 313 } 314 315 /** 316 * @private 317 */ 318 override public function setText(text:String):void 319 { 320 super.setText(text); 321 322 // If we have focus, need to make sure we can still input text. 323 initForInputIfHaveFocus(); 324 } 325 326 /** 327 * @private 328 */ 329 override public function setTextFlow(textFlow:TextFlow):void 330 { 331 super.setTextFlow(textFlow); 332 333 // If we have focus, need to make sure we can still input text. 334 initForInputIfHaveFocus(); 335 } 336 337 /** 338 * @private 339 */ 340 private function initForInputIfHaveFocus():void 341 { 342 // If we have focus, need to make sure there is a composer in place, 343 // the new controller knows it has focus, and there is an insertion 344 // point so input works without a mouse over or mouse click. Normally 345 // this is done in our focusIn handler by making sure there is a 346 // selection. Test this by clicking an arrow in the NumericStepper 347 // and then entering a number without clicking on the input field first. 348 if (editingMode != EditingMode.READ_ONLY && 349 textDisplay.getFocus() == textDisplay) 350 { 351 // this will ensure a text flow with a comopser 352 var im:ISelectionManager = beginInteraction(); 353 354 var controller:ContainerController = 355 getTextFlow().flowComposer.getControllerAt(0); 356 357 controller.requiredFocusInHandler(null); 358 359 if (!textDisplay.preserveSelectionOnSetText) 360 im.selectRange(0, 0); 361 362 endInteraction(); 363 } 364 } 365 366 /** 367 * @private 368 * To apply a format to a selection in a textFlow without using the 369 * selection manager. 370 */ 371 mx_internal function applyFormatOperation( 372 leafFormat:ITextLayoutFormat, 373 paragraphFormat:ITextLayoutFormat, 374 containerFormat:ITextLayoutFormat, 375 anchorPosition:int, 376 activePosition:int):Boolean 377 { 378 // Nothing to do. 379 if (anchorPosition == -1 || activePosition == -1) 380 return true; 381 382 var textFlow:TextFlow = getTextFlowWithComposer(); 383 384 var operationState:SelectionState = 385 new SelectionState(textFlow, anchorPosition, activePosition); 386 387 // If using the edit manager and the selection is the current selection, 388 // need to set the flag so point selection is set with pending formats for next 389 // char typed. 390 const editManager:IEditManager = textFlow.interactionManager as IEditManager; 391 if (editManager) 392 { 393 const absoluteStart:int = getAbsoluteStart(anchorPosition, activePosition); 394 const absoluteEnd:int = getAbsoluteEnd(anchorPosition, activePosition); 395 396 if (editManager.absoluteStart == absoluteStart && editManager.absoluteEnd == absoluteEnd) 397 operationState.selectionManagerOperationState = true; 398 } 399 400 // For the case when interactive editing is not allowed. 401 var op:ApplyFormatOperation = 402 new ApplyFormatOperation( 403 operationState, leafFormat, paragraphFormat, containerFormat); 404 405 var success:Boolean = op.doOperation(); 406 if (success) 407 { 408 textFlow.normalize(); 409 textFlow.flowComposer.updateAllControllers(); 410 } 411 412 return success; 413 } 414 415 /** 416 * @private 417 * To get the format of a character. Our API allows this operation even 418 * when the editingMode does not permit either interactive selection or 419 * editing. 420 */ 421 mx_internal function getCommonCharacterFormat( 422 anchorPosition:int, 423 activePosition:int):ITextLayoutFormat 424 { 425 if (anchorPosition == -1 || activePosition == -1) 426 return null; 427 428 var textFlow:TextFlow = getTextFlowWithComposer(); 429 430 if (textFlow.interactionManager) 431 { 432 // If there is a selection manager use it so that the format, 433 // depending on the range, may include any attributes set on a point 434 // selection but not yet applied. 435 const range:TextRange = 436 new TextRange(textFlow, anchorPosition, activePosition); 437 438 return textFlow.interactionManager.getCommonCharacterFormat(range); 439 } 440 else 441 { 442 // ElementRange will order the selection points. Since there isn't 443 // an interactionManager there is not a point selection to worry 444 // about. 445 var selRange:ElementRange = 446 ElementRange.createElementRange(textFlow, anchorPosition, activePosition); 447 448 return selRange.getCommonCharacterFormat(); 449 } 450 } 451 452 /** 453 * @private 454 * To get the format of the container without using a SelectionManager. 455 * The method should be kept in sync with the version in the 456 * SelectionManager. 457 */ 458 mx_internal function getCommonContainerFormat():ITextLayoutFormat 459 { 460 var textFlow:TextFlow = getTextFlowWithComposer(); 461 462 // absoluteStart and absoluteEnd values not used. 463 var selRange:ElementRange = 464 ElementRange.createElementRange(textFlow, 0, textFlow.textLength - 1); 465 466 return selRange.getCommonContainerFormat(); 467 } 468 469 /** 470 * @private 471 * To get the format of a paragraph without using a SelectionManager. 472 * The method should be kept in sync with the version in the 473 * SelectionManager. 474 */ 475 mx_internal function getCommonParagraphFormat( 476 anchorPosition:int, 477 activePosition:int):ITextLayoutFormat 478 { 479 if (anchorPosition == -1 || activePosition == -1) 480 return null; 481 482 var textFlow:TextFlow = getTextFlowWithComposer(); 483 484 // ElementRange will order the selection points. 485 var selRange:ElementRange = 486 ElementRange.createElementRange(textFlow, anchorPosition, activePosition); 487 488 return selRange.getCommonParagraphFormat(); 489 } 490 491 /** 492 * @private 493 * Insert or append text to the textFlow without using an EditManager. 494 * If there is a SelectionManager or EditManager its selection will be 495 * updated at the end of the operation to keep it in sync. 496 */ 497 mx_internal function insertTextOperation(insertText:String, 498 anchorPosition:int, 499 activePosition:int):Boolean 500 { 501 // No insertion point. 502 if (anchorPosition == -1 || activePosition == -1) 503 return false; 504 505 var textFlow:TextFlow = getTextFlowWithComposer(); 506 507 var absoluteStart:int = getAbsoluteStart(anchorPosition, activePosition); 508 var absoluteEnd:int = getAbsoluteEnd(anchorPosition, activePosition); 509 510 // Need to get the format of the insertion point so that the inserted 511 // text will have this format. 512 var pointFormat:ITextLayoutFormat = 513 getCommonCharacterFormat(absoluteStart, absoluteStart); 514 515 var operationState:SelectionState = 516 new SelectionState(textFlow, absoluteStart, absoluteEnd, pointFormat); 517 518 // If there is an interaction manager, this keeps it in sync with 519 // the results of this operation. 520 operationState.selectionManagerOperationState = true; 521 522 var op:InsertTextOperation = 523 new InsertTextOperation(operationState, insertText); 524 525 // Generations don't seem to be used in this code path since we 526 // aren't doing composite, merge or undo operations so they were 527 // optimized out. 528 529 var success:Boolean = op.doOperation(); 530 if (success) 531 { 532 textFlow.normalize(); 533 534 textFlow.flowComposer.updateAllControllers(); 535 536 var insertPt:int = absoluteEnd - (absoluteEnd - absoluteStart) + 537 + insertText.length; 538 539 // No point format. 540 var selectionState:SelectionState = 541 new SelectionState(textFlow, insertPt, insertPt); 542 543 var selectionEvent:SelectionEvent = 544 new SelectionEvent(SelectionEvent.SELECTION_CHANGE, 545 false, false, selectionState); 546 547 textFlow.dispatchEvent(selectionEvent); 548 549 scrollToRange(insertPt, insertPt); 550 } 551 552 return success; 553 } 554 555 /** 556 * Note: It is probably a TLF bug that, if delayedUpdates is true, we have to call 557 * updateAllControllers before doing a format operation to guarantee the correct 558 * results. 559 */ 560 mx_internal function getTextFlowWithComposer():TextFlow 561 { 562 var textFlow:TextFlow = getTextFlow(); 563 564 // Make sure there is a text flow with a flow composer. There will 565 // not be an interaction manager if editingMode is read-only. If 566 // there is an interaction manager flush any pending inserts into the 567 // text flow unless we are delaying updates in which case we may have to finish 568 // composition. 569 if (composeState != TextContainerManager.COMPOSE_COMPOSER) 570 { 571 convertToTextFlowWithComposer(); 572 } 573 else if (textFlow.interactionManager) 574 { 575 const editManager:IEditManager = textFlow.interactionManager as IEditManager; 576 if (editManager && editManager.delayUpdates) 577 editManager.updateAllControllers(); 578 else 579 textFlow.interactionManager.flushPendingOperations(); 580 } 581 582 return textFlow; 583 } 584 585 /** 586 * @private 587 */ 588 private function getAbsoluteStart(anchorPosition:int, activePosition:int):int 589 { 590 return (anchorPosition < activePosition) ? 591 anchorPosition : activePosition; 592 } 593 594 /** 595 * @private 596 */ 597 private function getAbsoluteEnd(anchorPosition:int, activePosition:int):int 598 { 599 return (anchorPosition > activePosition) ? 600 anchorPosition : activePosition; 601 } 602 603 //-------------------------------------------------------------------------- 604 // 605 // Overridden event handlers 606 // 607 //-------------------------------------------------------------------------- 608 609 /** 610 * @private 611 */ 612 override public function focusInHandler(event:FocusEvent):void 613 { 614 textDisplay.focusInHandler(event); 615 616 super.focusInHandler(event); 617 } 618 619 /** 620 * @private 621 */ 622 override public function focusOutHandler(event:FocusEvent):void 623 { 624 textDisplay.focusOutHandler(event); 625 626 super.focusOutHandler(event); 627 } 628 629 /** 630 * @private 631 */ 632 override public function keyDownHandler(event:KeyboardEvent):void 633 { 634 textDisplay.keyDownHandler(event); 635 636 if (!event.isDefaultPrevented()) 637 { 638 var clone:KeyboardEvent = KeyboardEvent(event.clone()); 639 super.keyDownHandler(clone); 640 if (clone.isDefaultPrevented()) 641 event.preventDefault(); 642 } 643 } 644 645 /** 646 * @private 647 */ 648 override public function keyUpHandler(event:KeyboardEvent):void 649 { 650 if (!event.isDefaultPrevented()) 651 { 652 var clone:KeyboardEvent = KeyboardEvent(event.clone()); 653 super.keyUpHandler(clone); 654 if (clone.isDefaultPrevented()) 655 event.preventDefault(); 656 } 657 } 658 659 /** 660 * @private 661 */ 662 override public function mouseDownHandler(event:MouseEvent):void 663 { 664 textDisplay.mouseDownHandler(event); 665 666 super.mouseDownHandler(event); 667 } 668 669 /** 670 * @private 671 * This handler gets called for ACTIVATE events from the player 672 * and FLEX_WINDOW_ACTIVATE events from Flex. Because of the 673 * way AIR handles activation of AIR Windows, and because Flex 674 * has its own concept of popups or pseudo-windows, we 675 * ignore ACTIVATE and respond to FLEX_WINDOW_ACTIVATE instead 676 */ 677 override public function activateHandler(event:Event):void 678 { 679 // block ACTIVATE events 680 if (event.type == Event.ACTIVATE) 681 return; 682 683 super.activateHandler(event); 684 685 // TLF ties activation and focus together. If a Flex PopUp is created 686 // it is possible to get deactivate/activate events without any 687 // focus events. If we have focus when we are activated, the selection 688 // state should be SelectionFormatState.FOCUSED not 689 // SelectionFormatState.UNFOCUSED since there might not be a follow on 690 // focusIn event. 691 if (editingMode != EditingMode.READ_ONLY && 692 textDisplay.getFocus() == textDisplay) 693 { 694 var im:SelectionManager = SelectionManager(beginInteraction()); 695 im.setFocus(); 696 endInteraction(); 697 } 698 } 699 700 /** 701 * @private 702 * This handler gets called for DEACTIVATE events from the player 703 * and FLEX_WINDOW_DEACTIVATE events from Flex. Because of the 704 * way AIR handles activation of AIR Windows, and because Flex 705 * has its own concept of popups or pseudo-windows, we 706 * ignore DEACTIVATE and respond to FLEX_WINDOW_DEACTIVATE instead 707 */ 708 override public function deactivateHandler(event:Event):void 709 { 710 // block DEACTIVATE events 711 if (event.type == Event.DEACTIVATE) 712 return; 713 714 super.deactivateHandler(event); 715 } 716 717 /** 718 * @private 719 * sandbox support 720 */ 721 override public function beginMouseCapture():void 722 { 723 super.beginMouseCapture(); 724 textDisplay.systemManager.getSandboxRoot().addEventListener(SandboxMouseEvent.MOUSE_UP_SOMEWHERE, mouseUpSomewhereHandler); 725 textDisplay.systemManager.getSandboxRoot().addEventListener(SandboxMouseEvent.MOUSE_MOVE_SOMEWHERE, mouseMoveSomewhereHandler); 726 } 727 728 /** 729 * @private 730 * sandbox support 731 */ 732 override public function endMouseCapture():void 733 { 734 super.endMouseCapture(); 735 textDisplay.systemManager.getSandboxRoot().removeEventListener(SandboxMouseEvent.MOUSE_UP_SOMEWHERE, mouseUpSomewhereHandler); 736 textDisplay.systemManager.getSandboxRoot().removeEventListener(SandboxMouseEvent.MOUSE_MOVE_SOMEWHERE, mouseMoveSomewhereHandler); 737 } 738 739 //-------------------------------------------------------------------------- 740 // 741 // Event handlers 742 // 743 //-------------------------------------------------------------------------- 744 745 private function mouseUpSomewhereHandler(event:Event):void 746 { 747 mouseUpSomewhere(event); 748 } 749 750 private function mouseMoveSomewhereHandler(event:Event):void 751 { 752 mouseMoveSomewhere(event); 753 } 754} 755 756} 757