2 * Copyright (c) 2015 Samsung Electronics Co., Ltd.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
19 #include <dali-toolkit/internal/text/text-controller-impl.h>
22 #include <dali/public-api/adaptor-framework/key.h>
23 #include <dali/integration-api/debug.h>
27 #include <dali-toolkit/internal/text/bidirectional-support.h>
28 #include <dali-toolkit/internal/text/character-set-conversion.h>
29 #include <dali-toolkit/internal/text/color-segmentation.h>
30 #include <dali-toolkit/internal/text/cursor-helper-functions.h>
31 #include <dali-toolkit/internal/text/multi-language-support.h>
32 #include <dali-toolkit/internal/text/segmentation.h>
33 #include <dali-toolkit/internal/text/shaper.h>
34 #include <dali-toolkit/internal/text/text-run-container.h>
40 * @brief Struct used to calculate the selection box.
42 struct SelectionBoxInfo
50 #if defined(DEBUG_ENABLED)
51 Debug::Filter* gLogFilter = Debug::Filter::New(Debug::NoLogging, true, "LOG_TEXT_CONTROLS");
54 const float MAX_FLOAT = std::numeric_limits<float>::max();
55 const float MIN_FLOAT = std::numeric_limits<float>::min();
56 const Dali::Toolkit::Text::CharacterDirection LTR = false; ///< Left To Right direction
69 EventData::EventData( DecoratorPtr decorator )
70 : mDecorator( decorator ),
72 mPlaceholderTextActive(),
73 mPlaceholderTextInactive(),
74 mPlaceholderTextColor( 0.8f, 0.8f, 0.8f, 0.8f ),
77 mPrimaryCursorPosition( 0u ),
78 mLeftSelectionPosition( 0u ),
79 mRightSelectionPosition( 0u ),
80 mPreEditStartPosition( 0u ),
82 mCursorHookPositionX( 0.f ),
83 mIsShowingPlaceholderText( false ),
84 mPreEditFlag( false ),
85 mDecoratorUpdated( false ),
86 mCursorBlinkEnabled( true ),
87 mGrabHandleEnabled( true ),
88 mGrabHandlePopupEnabled( true ),
89 mSelectionEnabled( true ),
90 mUpdateCursorPosition( false ),
91 mUpdateGrabHandlePosition( false ),
92 mUpdateLeftSelectionPosition( false ),
93 mUpdateRightSelectionPosition( false ),
94 mIsLeftHandleSelected( false ),
95 mUpdateHighlightBox( false ),
96 mScrollAfterUpdatePosition( false ),
97 mScrollAfterDelete( false ),
98 mAllTextSelected( false ),
99 mUpdateInputStyle( false )
101 mImfManager = ImfManager::Get();
104 EventData::~EventData()
107 bool Controller::Impl::ProcessInputEvents()
109 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "-->Controller::ProcessInputEvents\n" );
110 if( NULL == mEventData )
112 // Nothing to do if there is no text input.
113 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::ProcessInputEvents no event data\n" );
117 if( mEventData->mDecorator )
119 for( std::vector<Event>::iterator iter = mEventData->mEventQueue.begin();
120 iter != mEventData->mEventQueue.end();
125 case Event::CURSOR_KEY_EVENT:
127 OnCursorKeyEvent( *iter );
130 case Event::TAP_EVENT:
135 case Event::LONG_PRESS_EVENT:
137 OnLongPressEvent( *iter );
140 case Event::PAN_EVENT:
145 case Event::GRAB_HANDLE_EVENT:
146 case Event::LEFT_SELECTION_HANDLE_EVENT:
147 case Event::RIGHT_SELECTION_HANDLE_EVENT: // Fall through
149 OnHandleEvent( *iter );
154 OnSelectEvent( *iter );
157 case Event::SELECT_ALL:
166 if( mEventData->mUpdateCursorPosition ||
167 mEventData->mUpdateHighlightBox )
172 // The cursor must also be repositioned after inserts into the model
173 if( mEventData->mUpdateCursorPosition )
175 // Updates the cursor position and scrolls the text to make it visible.
176 CursorInfo cursorInfo;
177 // Calculate the cursor position from the new cursor index.
178 GetCursorPosition( mEventData->mPrimaryCursorPosition,
181 if( mEventData->mUpdateCursorHookPosition )
183 // Update the cursor hook position. Used to move the cursor with the keys 'up' and 'down'.
184 mEventData->mCursorHookPositionX = cursorInfo.primaryPosition.x;
185 mEventData->mUpdateCursorHookPosition = false;
188 // Scroll first the text after delete ...
189 if( mEventData->mScrollAfterDelete )
191 ScrollTextToMatchCursor( cursorInfo );
194 // ... then, text can be scrolled to make the cursor visible.
195 if( mEventData->mScrollAfterUpdatePosition )
197 const Vector2 currentCursorPosition( cursorInfo.primaryPosition.x, cursorInfo.lineOffset );
198 ScrollToMakePositionVisible( currentCursorPosition, cursorInfo.lineHeight );
200 mEventData->mScrollAfterUpdatePosition = false;
201 mEventData->mScrollAfterDelete = false;
203 UpdateCursorPosition( cursorInfo );
205 mEventData->mDecoratorUpdated = true;
206 mEventData->mUpdateCursorPosition = false;
207 mEventData->mUpdateGrabHandlePosition = false;
211 CursorInfo leftHandleInfo;
212 CursorInfo rightHandleInfo;
214 if( mEventData->mUpdateHighlightBox )
216 GetCursorPosition( mEventData->mLeftSelectionPosition,
219 GetCursorPosition( mEventData->mRightSelectionPosition,
222 if( mEventData->mScrollAfterUpdatePosition && ( mEventData->mIsLeftHandleSelected ? mEventData->mUpdateLeftSelectionPosition : mEventData->mUpdateRightSelectionPosition ) )
224 CursorInfo& info = mEventData->mIsLeftHandleSelected ? leftHandleInfo : rightHandleInfo;
226 const Vector2 currentCursorPosition( info.primaryPosition.x, info.lineOffset );
227 ScrollToMakePositionVisible( currentCursorPosition, info.lineHeight );
231 if( mEventData->mUpdateLeftSelectionPosition )
233 UpdateSelectionHandle( LEFT_SELECTION_HANDLE,
237 mEventData->mDecoratorUpdated = true;
238 mEventData->mUpdateLeftSelectionPosition = false;
241 if( mEventData->mUpdateRightSelectionPosition )
243 UpdateSelectionHandle( RIGHT_SELECTION_HANDLE,
247 mEventData->mDecoratorUpdated = true;
248 mEventData->mUpdateRightSelectionPosition = false;
251 if( mEventData->mUpdateHighlightBox )
253 RepositionSelectionHandles();
255 mEventData->mUpdateLeftSelectionPosition = false;
256 mEventData->mUpdateRightSelectionPosition = false;
257 mEventData->mUpdateHighlightBox = false;
260 mEventData->mScrollAfterUpdatePosition = false;
263 if( mEventData->mUpdateInputStyle )
265 // Set the default style first.
266 RetrieveDefaultInputStyle( mEventData->mInputStyle );
268 // Get the character index from the cursor index.
269 const CharacterIndex styleIndex = ( mEventData->mPrimaryCursorPosition > 0u ) ? mEventData->mPrimaryCursorPosition - 1u : 0u;
271 // Retrieve the style from the style runs stored in the logical model.
272 mLogicalModel->RetrieveStyle( styleIndex, mEventData->mInputStyle );
274 mEventData->mUpdateInputStyle = false;
277 mEventData->mEventQueue.clear();
279 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::ProcessInputEvents\n" );
281 const bool decoratorUpdated = mEventData->mDecoratorUpdated;
282 mEventData->mDecoratorUpdated = false;
284 return decoratorUpdated;
287 void Controller::Impl::NotifyImfManager()
289 if( mEventData && mEventData->mImfManager )
291 CharacterIndex cursorPosition = GetLogicalCursorPosition();
293 const Length numberOfWhiteSpaces = GetNumberOfWhiteSpaces( 0u );
295 // Update the cursor position by removing the initial white spaces.
296 if( cursorPosition < numberOfWhiteSpaces )
302 cursorPosition -= numberOfWhiteSpaces;
305 mEventData->mImfManager.SetCursorPosition( cursorPosition );
306 mEventData->mImfManager.NotifyCursorPosition();
310 CharacterIndex Controller::Impl::GetLogicalCursorPosition() const
312 CharacterIndex cursorPosition = 0u;
316 if( ( EventData::SELECTING == mEventData->mState ) ||
317 ( EventData::SELECTION_HANDLE_PANNING == mEventData->mState ) )
319 cursorPosition = std::min( mEventData->mRightSelectionPosition, mEventData->mLeftSelectionPosition );
323 cursorPosition = mEventData->mPrimaryCursorPosition;
327 return cursorPosition;
330 Length Controller::Impl::GetNumberOfWhiteSpaces( CharacterIndex index ) const
332 Length numberOfWhiteSpaces = 0u;
334 // Get the buffer to the text.
335 Character* utf32CharacterBuffer = mLogicalModel->mText.Begin();
337 const Length totalNumberOfCharacters = mLogicalModel->mText.Count();
338 for( ; index < totalNumberOfCharacters; ++index, ++numberOfWhiteSpaces )
340 if( !TextAbstraction::IsWhiteSpace( *( utf32CharacterBuffer + index ) ) )
346 return numberOfWhiteSpaces;
349 void Controller::Impl::GetText( CharacterIndex index, std::string& text ) const
351 // Get the total number of characters.
352 Length numberOfCharacters = mLogicalModel->mText.Count();
354 // Retrieve the text.
355 if( 0u != numberOfCharacters )
357 Utf32ToUtf8( mLogicalModel->mText.Begin() + index, numberOfCharacters - index, text );
361 void Controller::Impl::CalculateTextUpdateIndices( Length& numberOfCharacters )
363 mTextUpdateInfo.mParagraphCharacterIndex = 0u;
364 mTextUpdateInfo.mStartGlyphIndex = 0u;
365 mTextUpdateInfo.mStartLineIndex = 0u;
366 numberOfCharacters = 0u;
368 const Length numberOfParagraphs = mLogicalModel->mParagraphInfo.Count();
369 if( 0u == numberOfParagraphs )
371 mTextUpdateInfo.mParagraphCharacterIndex = 0u;
372 numberOfCharacters = 0u;
374 mTextUpdateInfo.mRequestedNumberOfCharacters = mTextUpdateInfo.mNumberOfCharactersToAdd - mTextUpdateInfo.mNumberOfCharactersToRemove;
376 // Nothing else to do if there are no paragraphs.
380 // Find the paragraphs to be updated.
381 Vector<ParagraphRunIndex> paragraphsToBeUpdated;
382 if( mTextUpdateInfo.mCharacterIndex >= mTextUpdateInfo.mPreviousNumberOfCharacters )
384 // Text is being added at the end of the current text.
385 if( mTextUpdateInfo.mIsLastCharacterNewParagraph )
387 // Text is being added in a new paragraph after the last character of the text.
388 mTextUpdateInfo.mParagraphCharacterIndex = mTextUpdateInfo.mPreviousNumberOfCharacters;
389 numberOfCharacters = 0u;
390 mTextUpdateInfo.mRequestedNumberOfCharacters = mTextUpdateInfo.mNumberOfCharactersToAdd - mTextUpdateInfo.mNumberOfCharactersToRemove;
392 mTextUpdateInfo.mStartGlyphIndex = mVisualModel->mGlyphs.Count();
393 mTextUpdateInfo.mStartLineIndex = mVisualModel->mLines.Count() - 1u;
395 // Nothing else to do;
399 paragraphsToBeUpdated.PushBack( numberOfParagraphs - 1u );
403 Length numberOfCharactersToUpdate = 0u;
404 if( mTextUpdateInfo.mFullRelayoutNeeded )
406 numberOfCharactersToUpdate = mTextUpdateInfo.mPreviousNumberOfCharacters;
410 numberOfCharactersToUpdate = ( mTextUpdateInfo.mNumberOfCharactersToRemove > 0u ) ? mTextUpdateInfo.mNumberOfCharactersToRemove : 1u;
412 mLogicalModel->FindParagraphs( mTextUpdateInfo.mCharacterIndex,
413 numberOfCharactersToUpdate,
414 paragraphsToBeUpdated );
417 if( 0u != paragraphsToBeUpdated.Count() )
419 const ParagraphRunIndex firstParagraphIndex = *( paragraphsToBeUpdated.Begin() );
420 const ParagraphRun& firstParagraph = *( mLogicalModel->mParagraphInfo.Begin() + firstParagraphIndex );
421 mTextUpdateInfo.mParagraphCharacterIndex = firstParagraph.characterRun.characterIndex;
423 ParagraphRunIndex lastParagraphIndex = *( paragraphsToBeUpdated.End() - 1u );
424 const ParagraphRun& lastParagraph = *( mLogicalModel->mParagraphInfo.Begin() + lastParagraphIndex );
426 if( ( mTextUpdateInfo.mNumberOfCharactersToRemove > 0u ) && // Some character are removed.
427 ( lastParagraphIndex < numberOfParagraphs - 1u ) && // There is a next paragraph.
428 ( ( lastParagraph.characterRun.characterIndex + lastParagraph.characterRun.numberOfCharacters ) == // The last removed character is the new paragraph character.
429 ( mTextUpdateInfo.mCharacterIndex + mTextUpdateInfo.mNumberOfCharactersToRemove ) ) )
431 // The new paragraph character of the last updated paragraph has been removed so is going to be merged with the next one.
432 const ParagraphRun& lastParagraph = *( mLogicalModel->mParagraphInfo.Begin() + lastParagraphIndex + 1u );
434 numberOfCharacters = lastParagraph.characterRun.characterIndex + lastParagraph.characterRun.numberOfCharacters - mTextUpdateInfo.mParagraphCharacterIndex;
438 numberOfCharacters = lastParagraph.characterRun.characterIndex + lastParagraph.characterRun.numberOfCharacters - mTextUpdateInfo.mParagraphCharacterIndex;
442 mTextUpdateInfo.mRequestedNumberOfCharacters = numberOfCharacters + mTextUpdateInfo.mNumberOfCharactersToAdd - mTextUpdateInfo.mNumberOfCharactersToRemove;
443 mTextUpdateInfo.mStartGlyphIndex = *( mVisualModel->mCharactersToGlyph.Begin() + mTextUpdateInfo.mParagraphCharacterIndex );
446 void Controller::Impl::ClearFullModelData( OperationsMask operations )
448 if( NO_OPERATION != ( GET_LINE_BREAKS & operations ) )
450 mLogicalModel->mLineBreakInfo.Clear();
451 mLogicalModel->mParagraphInfo.Clear();
454 if( NO_OPERATION != ( GET_WORD_BREAKS & operations ) )
456 mLogicalModel->mLineBreakInfo.Clear();
459 if( NO_OPERATION != ( GET_SCRIPTS & operations ) )
461 mLogicalModel->mScriptRuns.Clear();
464 if( NO_OPERATION != ( VALIDATE_FONTS & operations ) )
466 mLogicalModel->mFontRuns.Clear();
469 if( 0u != mLogicalModel->mBidirectionalParagraphInfo.Count() )
471 if( NO_OPERATION != ( BIDI_INFO & operations ) )
473 mLogicalModel->mBidirectionalParagraphInfo.Clear();
474 mLogicalModel->mCharacterDirections.Clear();
477 if( NO_OPERATION != ( REORDER & operations ) )
479 // Free the allocated memory used to store the conversion table in the bidirectional line info run.
480 for( Vector<BidirectionalLineInfoRun>::Iterator it = mLogicalModel->mBidirectionalLineInfo.Begin(),
481 endIt = mLogicalModel->mBidirectionalLineInfo.End();
485 BidirectionalLineInfoRun& bidiLineInfo = *it;
487 free( bidiLineInfo.visualToLogicalMap );
488 bidiLineInfo.visualToLogicalMap = NULL;
490 mLogicalModel->mBidirectionalLineInfo.Clear();
494 if( NO_OPERATION != ( SHAPE_TEXT & operations ) )
496 mVisualModel->mGlyphs.Clear();
497 mVisualModel->mGlyphsToCharacters.Clear();
498 mVisualModel->mCharactersToGlyph.Clear();
499 mVisualModel->mCharactersPerGlyph.Clear();
500 mVisualModel->mGlyphsPerCharacter.Clear();
501 mVisualModel->mGlyphPositions.Clear();
504 if( NO_OPERATION != ( LAYOUT & operations ) )
506 mVisualModel->mLines.Clear();
509 if( NO_OPERATION != ( COLOR & operations ) )
511 mVisualModel->mColorIndices.Clear();
515 void Controller::Impl::ClearCharacterModelData( CharacterIndex startIndex, CharacterIndex endIndex, OperationsMask operations )
517 const CharacterIndex endIndexPlusOne = endIndex + 1u;
519 if( NO_OPERATION != ( GET_LINE_BREAKS & operations ) )
521 // Clear the line break info.
522 LineBreakInfo* lineBreakInfoBuffer = mLogicalModel->mLineBreakInfo.Begin();
524 mLogicalModel->mLineBreakInfo.Erase( lineBreakInfoBuffer + startIndex,
525 lineBreakInfoBuffer + endIndexPlusOne );
527 // Clear the paragraphs.
528 ClearCharacterRuns( startIndex,
530 mLogicalModel->mParagraphInfo );
533 if( NO_OPERATION != ( GET_WORD_BREAKS & operations ) )
535 // Clear the word break info.
536 WordBreakInfo* wordBreakInfoBuffer = mLogicalModel->mWordBreakInfo.Begin();
538 mLogicalModel->mWordBreakInfo.Erase( wordBreakInfoBuffer + startIndex,
539 wordBreakInfoBuffer + endIndexPlusOne );
542 if( NO_OPERATION != ( GET_SCRIPTS & operations ) )
544 // Clear the scripts.
545 ClearCharacterRuns( startIndex,
547 mLogicalModel->mScriptRuns );
550 if( NO_OPERATION != ( VALIDATE_FONTS & operations ) )
553 ClearCharacterRuns( startIndex,
555 mLogicalModel->mFontRuns );
558 if( 0u != mLogicalModel->mBidirectionalParagraphInfo.Count() )
560 if( NO_OPERATION != ( BIDI_INFO & operations ) )
562 // Clear the bidirectional paragraph info.
563 ClearCharacterRuns( startIndex,
565 mLogicalModel->mBidirectionalParagraphInfo );
567 // Clear the character's directions.
568 CharacterDirection* characterDirectionsBuffer = mLogicalModel->mCharacterDirections.Begin();
570 mLogicalModel->mCharacterDirections.Erase( characterDirectionsBuffer + startIndex,
571 characterDirectionsBuffer + endIndexPlusOne );
574 if( NO_OPERATION != ( REORDER & operations ) )
576 uint32_t startRemoveIndex = mLogicalModel->mBidirectionalLineInfo.Count();
577 uint32_t endRemoveIndex = startRemoveIndex;
578 ClearCharacterRuns( startIndex,
580 mLogicalModel->mBidirectionalLineInfo,
584 BidirectionalLineInfoRun* bidirectionalLineInfoBuffer = mLogicalModel->mBidirectionalLineInfo.Begin();
586 // Free the allocated memory used to store the conversion table in the bidirectional line info run.
587 for( Vector<BidirectionalLineInfoRun>::Iterator it = bidirectionalLineInfoBuffer + startRemoveIndex,
588 endIt = bidirectionalLineInfoBuffer + endRemoveIndex;
592 BidirectionalLineInfoRun& bidiLineInfo = *it;
594 free( bidiLineInfo.visualToLogicalMap );
595 bidiLineInfo.visualToLogicalMap = NULL;
598 mLogicalModel->mBidirectionalLineInfo.Erase( bidirectionalLineInfoBuffer + startRemoveIndex,
599 bidirectionalLineInfoBuffer + endRemoveIndex );
604 void Controller::Impl::ClearGlyphModelData( CharacterIndex startIndex, CharacterIndex endIndex, OperationsMask operations )
606 const CharacterIndex endIndexPlusOne = endIndex + 1u;
607 const Length numberOfCharactersRemoved = endIndexPlusOne - startIndex;
609 // Convert the character index to glyph index before deleting the character to glyph and the glyphs per character buffers.
610 GlyphIndex* charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
611 Length* glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
613 const GlyphIndex endGlyphIndexPlusOne = *( charactersToGlyphBuffer + endIndex ) + *( glyphsPerCharacterBuffer + endIndex );
614 const Length numberOfGlyphsRemoved = endGlyphIndexPlusOne - mTextUpdateInfo.mStartGlyphIndex;
616 if( NO_OPERATION != ( SHAPE_TEXT & operations ) )
618 // Update the character to glyph indices.
619 for( Vector<GlyphIndex>::Iterator it = charactersToGlyphBuffer + endIndexPlusOne,
620 endIt = charactersToGlyphBuffer + mVisualModel->mCharactersToGlyph.Count();
624 CharacterIndex& index = *it;
625 index -= numberOfGlyphsRemoved;
628 // Clear the character to glyph conversion table.
629 mVisualModel->mCharactersToGlyph.Erase( charactersToGlyphBuffer + startIndex,
630 charactersToGlyphBuffer + endIndexPlusOne );
632 // Clear the glyphs per character table.
633 mVisualModel->mGlyphsPerCharacter.Erase( glyphsPerCharacterBuffer + startIndex,
634 glyphsPerCharacterBuffer + endIndexPlusOne );
636 // Clear the glyphs buffer.
637 GlyphInfo* glyphsBuffer = mVisualModel->mGlyphs.Begin();
638 mVisualModel->mGlyphs.Erase( glyphsBuffer + mTextUpdateInfo.mStartGlyphIndex,
639 glyphsBuffer + endGlyphIndexPlusOne );
641 CharacterIndex* glyphsToCharactersBuffer = mVisualModel->mGlyphsToCharacters.Begin();
643 // Update the glyph to character indices.
644 for( Vector<CharacterIndex>::Iterator it = glyphsToCharactersBuffer + endGlyphIndexPlusOne,
645 endIt = glyphsToCharactersBuffer + mVisualModel->mGlyphsToCharacters.Count();
649 CharacterIndex& index = *it;
650 index -= numberOfCharactersRemoved;
653 // Clear the glyphs to characters buffer.
654 mVisualModel->mGlyphsToCharacters.Erase( glyphsToCharactersBuffer + mTextUpdateInfo.mStartGlyphIndex,
655 glyphsToCharactersBuffer + endGlyphIndexPlusOne );
657 // Clear the characters per glyph buffer.
658 Length* charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
659 mVisualModel->mCharactersPerGlyph.Erase( charactersPerGlyphBuffer + mTextUpdateInfo.mStartGlyphIndex,
660 charactersPerGlyphBuffer + endGlyphIndexPlusOne );
662 // Clear the positions buffer.
663 Vector2* positionsBuffer = mVisualModel->mGlyphPositions.Begin();
664 mVisualModel->mGlyphPositions.Erase( positionsBuffer + mTextUpdateInfo.mStartGlyphIndex,
665 positionsBuffer + endGlyphIndexPlusOne );
668 if( NO_OPERATION != ( LAYOUT & operations ) )
671 uint32_t startRemoveIndex = mVisualModel->mLines.Count();
672 uint32_t endRemoveIndex = startRemoveIndex;
673 ClearCharacterRuns( startIndex,
675 mVisualModel->mLines,
679 // Will update the glyph runs.
680 startRemoveIndex = mVisualModel->mLines.Count();
681 endRemoveIndex = startRemoveIndex;
682 ClearGlyphRuns( mTextUpdateInfo.mStartGlyphIndex,
683 endGlyphIndexPlusOne - 1u,
684 mVisualModel->mLines,
688 // Set the line index from where to insert the new laid-out lines.
689 mTextUpdateInfo.mStartLineIndex = startRemoveIndex;
691 LineRun* linesBuffer = mVisualModel->mLines.Begin();
692 mVisualModel->mLines.Erase( linesBuffer + startRemoveIndex,
693 linesBuffer + endRemoveIndex );
696 if( NO_OPERATION != ( COLOR & operations ) )
698 if( 0u != mVisualModel->mColorIndices.Count() )
700 ColorIndex* colorIndexBuffer = mVisualModel->mColorIndices.Begin();
701 mVisualModel->mColorIndices.Erase( colorIndexBuffer + mTextUpdateInfo.mStartGlyphIndex,
702 colorIndexBuffer + endGlyphIndexPlusOne );
707 void Controller::Impl::ClearModelData( CharacterIndex startIndex, CharacterIndex endIndex, OperationsMask operations )
709 if( mTextUpdateInfo.mClearAll ||
710 ( ( 0u == startIndex ) &&
711 ( mTextUpdateInfo.mPreviousNumberOfCharacters == endIndex + 1u ) ) )
713 ClearFullModelData( operations );
717 // Clear the model data related with characters.
718 ClearCharacterModelData( startIndex, endIndex, operations );
720 // Clear the model data related with glyphs.
721 ClearGlyphModelData( startIndex, endIndex, operations );
724 // The estimated number of lines. Used to avoid reallocations when layouting.
725 mTextUpdateInfo.mEstimatedNumberOfLines = std::max( mVisualModel->mLines.Count(), mLogicalModel->mParagraphInfo.Count() );
727 mVisualModel->ClearCaches();
730 bool Controller::Impl::UpdateModel( OperationsMask operationsRequired )
732 DALI_LOG_INFO( gLogFilter, Debug::General, "Controller::UpdateModel\n" );
734 // Calculate the operations to be done.
735 const OperationsMask operations = static_cast<OperationsMask>( mOperationsPending & operationsRequired );
737 if( NO_OPERATION == operations )
739 // Nothing to do if no operations are pending and required.
743 Vector<Character>& utf32Characters = mLogicalModel->mText;
745 const Length numberOfCharacters = utf32Characters.Count();
747 // Index to the first character of the first paragraph to be updated.
748 CharacterIndex startIndex = 0u;
749 // Number of characters of the paragraphs to be removed.
750 Length paragraphCharacters = 0u;
752 CalculateTextUpdateIndices( paragraphCharacters );
753 startIndex = mTextUpdateInfo.mParagraphCharacterIndex;
755 if( mTextUpdateInfo.mClearAll ||
756 ( 0u != paragraphCharacters ) )
758 ClearModelData( startIndex, startIndex + ( ( paragraphCharacters > 0u ) ? paragraphCharacters - 1u : 0u ), operations );
761 mTextUpdateInfo.mClearAll = false;
763 // Whether the model is updated.
764 bool updated = false;
766 Vector<LineBreakInfo>& lineBreakInfo = mLogicalModel->mLineBreakInfo;
767 const Length requestedNumberOfCharacters = mTextUpdateInfo.mRequestedNumberOfCharacters;
769 if( NO_OPERATION != ( GET_LINE_BREAKS & operations ) )
771 // Retrieves the line break info. The line break info is used to split the text in 'paragraphs' to
772 // calculate the bidirectional info for each 'paragraph'.
773 // It's also used to layout the text (where it should be a new line) or to shape the text (text in different lines
774 // is not shaped together).
775 lineBreakInfo.Resize( numberOfCharacters, TextAbstraction::LINE_NO_BREAK );
777 SetLineBreakInfo( utf32Characters,
779 requestedNumberOfCharacters,
782 // Create the paragraph info.
783 mLogicalModel->CreateParagraphInfo( startIndex,
784 requestedNumberOfCharacters );
788 Vector<WordBreakInfo>& wordBreakInfo = mLogicalModel->mWordBreakInfo;
789 if( NO_OPERATION != ( GET_WORD_BREAKS & operations ) )
791 // Retrieves the word break info. The word break info is used to layout the text (where to wrap the text in lines).
792 wordBreakInfo.Resize( numberOfCharacters, TextAbstraction::WORD_NO_BREAK );
794 SetWordBreakInfo( utf32Characters,
796 requestedNumberOfCharacters,
801 const bool getScripts = NO_OPERATION != ( GET_SCRIPTS & operations );
802 const bool validateFonts = NO_OPERATION != ( VALIDATE_FONTS & operations );
804 Vector<ScriptRun>& scripts = mLogicalModel->mScriptRuns;
805 Vector<FontRun>& validFonts = mLogicalModel->mFontRuns;
807 if( getScripts || validateFonts )
809 // Validates the fonts assigned by the application or assigns default ones.
810 // It makes sure all the characters are going to be rendered by the correct font.
811 MultilanguageSupport multilanguageSupport = MultilanguageSupport::Get();
815 // Retrieves the scripts used in the text.
816 multilanguageSupport.SetScripts( utf32Characters,
818 requestedNumberOfCharacters,
824 // Validate the fonts set through the mark-up string.
825 Vector<FontDescriptionRun>& fontDescriptionRuns = mLogicalModel->mFontDescriptionRuns;
827 // Get the default font id.
828 const FontId defaultFontId = ( NULL == mFontDefaults ) ? 0u : mFontDefaults->GetFontId( mFontClient );
830 // Validates the fonts. If there is a character with no assigned font it sets a default one.
831 // After this call, fonts are validated.
832 multilanguageSupport.ValidateFonts( utf32Characters,
837 requestedNumberOfCharacters,
843 Vector<Character> mirroredUtf32Characters;
844 bool textMirrored = false;
845 const Length numberOfParagraphs = mLogicalModel->mParagraphInfo.Count();
846 if( NO_OPERATION != ( BIDI_INFO & operations ) )
848 Vector<BidirectionalParagraphInfoRun>& bidirectionalInfo = mLogicalModel->mBidirectionalParagraphInfo;
849 bidirectionalInfo.Reserve( numberOfParagraphs );
851 // Calculates the bidirectional info for the whole paragraph if it contains right to left scripts.
852 SetBidirectionalInfo( utf32Characters,
856 requestedNumberOfCharacters,
859 if( 0u != bidirectionalInfo.Count() )
861 // Only set the character directions if there is right to left characters.
862 Vector<CharacterDirection>& directions = mLogicalModel->mCharacterDirections;
863 GetCharactersDirection( bidirectionalInfo,
866 requestedNumberOfCharacters,
869 // This paragraph has right to left text. Some characters may need to be mirrored.
870 // TODO: consider if the mirrored string can be stored as well.
872 textMirrored = GetMirroredText( utf32Characters,
876 requestedNumberOfCharacters,
877 mirroredUtf32Characters );
881 // There is no right to left characters. Clear the directions vector.
882 mLogicalModel->mCharacterDirections.Clear();
887 Vector<GlyphInfo>& glyphs = mVisualModel->mGlyphs;
888 Vector<CharacterIndex>& glyphsToCharactersMap = mVisualModel->mGlyphsToCharacters;
889 Vector<Length>& charactersPerGlyph = mVisualModel->mCharactersPerGlyph;
890 Vector<GlyphIndex> newParagraphGlyphs;
891 newParagraphGlyphs.Reserve( numberOfParagraphs );
893 const Length currentNumberOfGlyphs = glyphs.Count();
894 if( NO_OPERATION != ( SHAPE_TEXT & operations ) )
896 const Vector<Character>& textToShape = textMirrored ? mirroredUtf32Characters : utf32Characters;
898 ShapeText( textToShape,
903 mTextUpdateInfo.mStartGlyphIndex,
904 requestedNumberOfCharacters,
906 glyphsToCharactersMap,
908 newParagraphGlyphs );
910 // Create the 'number of glyphs' per character and the glyph to character conversion tables.
911 mVisualModel->CreateGlyphsPerCharacterTable( startIndex, mTextUpdateInfo.mStartGlyphIndex, requestedNumberOfCharacters );
912 mVisualModel->CreateCharacterToGlyphTable( startIndex, mTextUpdateInfo.mStartGlyphIndex, requestedNumberOfCharacters );
916 const Length numberOfGlyphs = glyphs.Count() - currentNumberOfGlyphs;
918 if( NO_OPERATION != ( GET_GLYPH_METRICS & operations ) )
920 GlyphInfo* glyphsBuffer = glyphs.Begin();
921 mMetrics->GetGlyphMetrics( glyphsBuffer + mTextUpdateInfo.mStartGlyphIndex, numberOfGlyphs );
923 // Update the width and advance of all new paragraph characters.
924 for( Vector<GlyphIndex>::ConstIterator it = newParagraphGlyphs.Begin(), endIt = newParagraphGlyphs.End(); it != endIt; ++it )
926 const GlyphIndex index = *it;
927 GlyphInfo& glyph = *( glyphsBuffer + index );
929 glyph.xBearing = 0.f;
936 if( NO_OPERATION != ( COLOR & operations ) )
938 // Set the color runs in glyphs.
939 SetColorSegmentationInfo( mLogicalModel->mColorRuns,
940 mVisualModel->mCharactersToGlyph,
941 mVisualModel->mGlyphsPerCharacter,
943 mTextUpdateInfo.mStartGlyphIndex,
944 requestedNumberOfCharacters,
945 mVisualModel->mColors,
946 mVisualModel->mColorIndices );
951 if( ( NULL != mEventData ) &&
952 mEventData->mPreEditFlag &&
953 ( 0u != mVisualModel->mCharactersToGlyph.Count() ) )
955 // Add the underline for the pre-edit text.
956 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
957 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
959 const GlyphIndex glyphStart = *( charactersToGlyphBuffer + mEventData->mPreEditStartPosition );
960 const CharacterIndex lastPreEditCharacter = mEventData->mPreEditStartPosition + ( ( mEventData->mPreEditLength > 0u ) ? mEventData->mPreEditLength - 1u : 0u );
961 const Length numberOfGlyphsLastCharacter = *( glyphsPerCharacterBuffer + lastPreEditCharacter );
962 const GlyphIndex glyphEnd = *( charactersToGlyphBuffer + lastPreEditCharacter ) + ( numberOfGlyphsLastCharacter > 1u ? numberOfGlyphsLastCharacter - 1u : 0u );
964 GlyphRun underlineRun;
965 underlineRun.glyphIndex = glyphStart;
966 underlineRun.numberOfGlyphs = 1u + glyphEnd - glyphStart;
968 // TODO: At the moment the underline runs are only for pre-edit.
969 mVisualModel->mUnderlineRuns.PushBack( underlineRun );
972 // The estimated number of lines. Used to avoid reallocations when layouting.
973 mTextUpdateInfo.mEstimatedNumberOfLines = std::max( mVisualModel->mLines.Count(), mLogicalModel->mParagraphInfo.Count() );
975 // Set the previous number of characters for the next time the text is updated.
976 mTextUpdateInfo.mPreviousNumberOfCharacters = numberOfCharacters;
981 void Controller::Impl::RetrieveDefaultInputStyle( InputStyle& inputStyle )
983 // Sets the default text's color.
984 inputStyle.textColor = mTextColor;
985 inputStyle.isDefaultColor = true;
987 inputStyle.familyName.clear();
988 inputStyle.weight = TextAbstraction::FontWeight::NORMAL;
989 inputStyle.width = TextAbstraction::FontWidth::NORMAL;
990 inputStyle.slant = TextAbstraction::FontSlant::NORMAL;
991 inputStyle.size = 0.f;
993 inputStyle.familyDefined = false;
994 inputStyle.weightDefined = false;
995 inputStyle.widthDefined = false;
996 inputStyle.slantDefined = false;
997 inputStyle.sizeDefined = false;
999 // Sets the default font's family name, weight, width, slant and size.
1002 if( mFontDefaults->familyDefined )
1004 inputStyle.familyName = mFontDefaults->mFontDescription.family;
1005 inputStyle.familyDefined = true;
1008 if( mFontDefaults->weightDefined )
1010 inputStyle.weight = mFontDefaults->mFontDescription.weight;
1011 inputStyle.weightDefined = true;
1014 if( mFontDefaults->widthDefined )
1016 inputStyle.width = mFontDefaults->mFontDescription.width;
1017 inputStyle.widthDefined = true;
1020 if( mFontDefaults->slantDefined )
1022 inputStyle.slant = mFontDefaults->mFontDescription.slant;
1023 inputStyle.slantDefined = true;
1026 if( mFontDefaults->sizeDefined )
1028 inputStyle.size = mFontDefaults->mDefaultPointSize;
1029 inputStyle.sizeDefined = true;
1034 float Controller::Impl::GetDefaultFontLineHeight()
1036 FontId defaultFontId = 0u;
1037 if( NULL == mFontDefaults )
1039 TextAbstraction::FontDescription fontDescription;
1040 defaultFontId = mFontClient.GetFontId( fontDescription );
1044 defaultFontId = mFontDefaults->GetFontId( mFontClient );
1047 Text::FontMetrics fontMetrics;
1048 mMetrics->GetFontMetrics( defaultFontId, fontMetrics );
1050 return( fontMetrics.ascender - fontMetrics.descender );
1053 void Controller::Impl::OnCursorKeyEvent( const Event& event )
1055 if( NULL == mEventData )
1057 // Nothing to do if there is no text input.
1061 int keyCode = event.p1.mInt;
1063 if( Dali::DALI_KEY_CURSOR_LEFT == keyCode )
1065 if( mEventData->mPrimaryCursorPosition > 0u )
1067 mEventData->mPrimaryCursorPosition = CalculateNewCursorIndex( mEventData->mPrimaryCursorPosition - 1u );
1070 else if( Dali::DALI_KEY_CURSOR_RIGHT == keyCode )
1072 if( mLogicalModel->mText.Count() > mEventData->mPrimaryCursorPosition )
1074 mEventData->mPrimaryCursorPosition = CalculateNewCursorIndex( mEventData->mPrimaryCursorPosition );
1077 else if( Dali::DALI_KEY_CURSOR_UP == keyCode )
1079 // Get first the line index of the current cursor position index.
1080 CharacterIndex characterIndex = 0u;
1082 if( mEventData->mPrimaryCursorPosition > 0u )
1084 characterIndex = mEventData->mPrimaryCursorPosition - 1u;
1087 const LineIndex lineIndex = mVisualModel->GetLineOfCharacter( characterIndex );
1089 if( lineIndex > 0u )
1091 // Retrieve the cursor position info.
1092 CursorInfo cursorInfo;
1093 GetCursorPosition( mEventData->mPrimaryCursorPosition,
1096 // Get the line above.
1097 const LineRun& line = *( mVisualModel->mLines.Begin() + ( lineIndex - 1u ) );
1099 // Get the next hit 'y' point.
1100 const float hitPointY = cursorInfo.lineOffset - 0.5f * ( line.ascender - line.descender );
1102 // Use the cursor hook position 'x' and the next hit 'y' position to calculate the new cursor index.
1103 mEventData->mPrimaryCursorPosition = Text::GetClosestCursorIndex( mVisualModel,
1106 mEventData->mCursorHookPositionX,
1110 else if( Dali::DALI_KEY_CURSOR_DOWN == keyCode )
1112 // Get first the line index of the current cursor position index.
1113 CharacterIndex characterIndex = 0u;
1115 if( mEventData->mPrimaryCursorPosition > 0u )
1117 characterIndex = mEventData->mPrimaryCursorPosition - 1u;
1120 const LineIndex lineIndex = mVisualModel->GetLineOfCharacter( characterIndex );
1122 if( lineIndex + 1u < mVisualModel->mLines.Count() )
1124 // Retrieve the cursor position info.
1125 CursorInfo cursorInfo;
1126 GetCursorPosition( mEventData->mPrimaryCursorPosition,
1129 // Get the line below.
1130 const LineRun& line = *( mVisualModel->mLines.Begin() + lineIndex + 1u );
1132 // Get the next hit 'y' point.
1133 const float hitPointY = cursorInfo.lineOffset + cursorInfo.lineHeight + 0.5f * ( line.ascender - line.descender );
1135 // Use the cursor hook position 'x' and the next hit 'y' position to calculate the new cursor index.
1136 mEventData->mPrimaryCursorPosition = Text::GetClosestCursorIndex( mVisualModel,
1139 mEventData->mCursorHookPositionX,
1144 mEventData->mUpdateCursorPosition = true;
1145 mEventData->mUpdateInputStyle = true;
1146 mEventData->mScrollAfterUpdatePosition = true;
1149 void Controller::Impl::OnTapEvent( const Event& event )
1151 if( NULL != mEventData )
1153 const unsigned int tapCount = event.p1.mUint;
1155 if( 1u == tapCount )
1157 if( IsShowingRealText() )
1159 // Convert from control's coords to text's coords.
1160 const float xPosition = event.p2.mFloat - mScrollPosition.x;
1161 const float yPosition = event.p3.mFloat - mScrollPosition.y;
1163 // Keep the tap 'x' position. Used to move the cursor.
1164 mEventData->mCursorHookPositionX = xPosition;
1166 mEventData->mPrimaryCursorPosition = Text::GetClosestCursorIndex( mVisualModel,
1172 // When the cursor position is changing, delay cursor blinking
1173 mEventData->mDecorator->DelayCursorBlink();
1177 mEventData->mPrimaryCursorPosition = 0u;
1180 mEventData->mUpdateCursorPosition = true;
1181 mEventData->mUpdateGrabHandlePosition = true;
1182 mEventData->mScrollAfterUpdatePosition = true;
1183 mEventData->mUpdateInputStyle = true;
1185 // Notify the cursor position to the imf manager.
1186 if( mEventData->mImfManager )
1188 mEventData->mImfManager.SetCursorPosition( mEventData->mPrimaryCursorPosition );
1189 mEventData->mImfManager.NotifyCursorPosition();
1195 void Controller::Impl::OnPanEvent( const Event& event )
1197 if( NULL == mEventData )
1199 // Nothing to do if there is no text input.
1203 const bool isHorizontalScrollEnabled = mEventData->mDecorator->IsHorizontalScrollEnabled();
1204 const bool isVerticalScrollEnabled = mEventData->mDecorator->IsVerticalScrollEnabled();
1206 if( !isHorizontalScrollEnabled && !isVerticalScrollEnabled )
1208 // Nothing to do if scrolling is not enabled.
1212 const int state = event.p1.mInt;
1216 case Gesture::Started:
1218 // Will remove the cursor, handles or text's popup, ...
1219 ChangeState( EventData::TEXT_PANNING );
1222 case Gesture::Continuing:
1224 const Vector2& layoutSize = mVisualModel->GetLayoutSize();
1225 const Vector2 currentScroll = mScrollPosition;
1227 if( isHorizontalScrollEnabled )
1229 const float displacementX = event.p2.mFloat;
1230 mScrollPosition.x += displacementX;
1232 ClampHorizontalScroll( layoutSize );
1235 if( isVerticalScrollEnabled )
1237 const float displacementY = event.p3.mFloat;
1238 mScrollPosition.y += displacementY;
1240 ClampVerticalScroll( layoutSize );
1243 mEventData->mDecorator->UpdatePositions( mScrollPosition - currentScroll );
1246 case Gesture::Finished:
1247 case Gesture::Cancelled: // FALLTHROUGH
1249 // Will go back to the previous state to show the cursor, handles, the text's popup, ...
1250 ChangeState( mEventData->mPreviousState );
1258 void Controller::Impl::OnLongPressEvent( const Event& event )
1260 DALI_LOG_INFO( gLogFilter, Debug::General, "Controller::OnLongPressEvent\n" );
1262 if( EventData::EDITING == mEventData->mState )
1264 ChangeState ( EventData::EDITING_WITH_POPUP );
1265 mEventData->mDecoratorUpdated = true;
1269 void Controller::Impl::OnHandleEvent( const Event& event )
1271 if( NULL == mEventData )
1273 // Nothing to do if there is no text input.
1277 const unsigned int state = event.p1.mUint;
1278 const bool handleStopScrolling = ( HANDLE_STOP_SCROLLING == state );
1279 const bool isSmoothHandlePanEnabled = mEventData->mDecorator->IsSmoothHandlePanEnabled();
1281 if( HANDLE_PRESSED == state )
1283 // Convert from decorator's coords to text's coords.
1284 const float xPosition = event.p2.mFloat - mScrollPosition.x;
1285 const float yPosition = event.p3.mFloat - mScrollPosition.y;
1287 // Need to calculate the handle's new position.
1288 const CharacterIndex handleNewPosition = Text::GetClosestCursorIndex( mVisualModel,
1294 if( Event::GRAB_HANDLE_EVENT == event.type )
1296 ChangeState ( EventData::GRAB_HANDLE_PANNING );
1298 if( handleNewPosition != mEventData->mPrimaryCursorPosition )
1300 // Updates the cursor position if the handle's new position is different than the current one.
1301 mEventData->mUpdateCursorPosition = true;
1302 // Does not update the grab handle position if the smooth panning is enabled. (The decorator does it smooth).
1303 mEventData->mUpdateGrabHandlePosition = !isSmoothHandlePanEnabled;
1304 mEventData->mPrimaryCursorPosition = handleNewPosition;
1307 // Updates the decorator if the soft handle panning is enabled. It triggers a relayout in the decorator and the new position of the handle is set.
1308 mEventData->mDecoratorUpdated = isSmoothHandlePanEnabled;
1310 else if( Event::LEFT_SELECTION_HANDLE_EVENT == event.type )
1312 ChangeState ( EventData::SELECTION_HANDLE_PANNING );
1314 if( ( handleNewPosition != mEventData->mLeftSelectionPosition ) &&
1315 ( handleNewPosition != mEventData->mRightSelectionPosition ) )
1317 // Updates the highlight box if the handle's new position is different than the current one.
1318 mEventData->mUpdateHighlightBox = true;
1319 // Does not update the selection handle position if the smooth panning is enabled. (The decorator does it smooth).
1320 mEventData->mUpdateLeftSelectionPosition = !isSmoothHandlePanEnabled;
1321 mEventData->mLeftSelectionPosition = handleNewPosition;
1324 // Updates the decorator if the soft handle panning is enabled. It triggers a relayout in the decorator and the new position of the handle is set.
1325 mEventData->mDecoratorUpdated = isSmoothHandlePanEnabled;
1327 // Will define the order to scroll the text to match the handle position.
1328 mEventData->mIsLeftHandleSelected = true;
1330 else if( Event::RIGHT_SELECTION_HANDLE_EVENT == event.type )
1332 ChangeState ( EventData::SELECTION_HANDLE_PANNING );
1334 if( ( handleNewPosition != mEventData->mRightSelectionPosition ) &&
1335 ( handleNewPosition != mEventData->mLeftSelectionPosition ) )
1337 // Updates the highlight box if the handle's new position is different than the current one.
1338 mEventData->mUpdateHighlightBox = true;
1339 // Does not update the selection handle position if the smooth panning is enabled. (The decorator does it smooth).
1340 mEventData->mUpdateRightSelectionPosition = !isSmoothHandlePanEnabled;
1341 mEventData->mRightSelectionPosition = handleNewPosition;
1344 // Updates the decorator if the soft handle panning is enabled. It triggers a relayout in the decorator and the new position of the handle is set.
1345 mEventData->mDecoratorUpdated = isSmoothHandlePanEnabled;
1347 // Will define the order to scroll the text to match the handle position.
1348 mEventData->mIsLeftHandleSelected = false;
1350 } // end ( HANDLE_PRESSED == state )
1351 else if( ( HANDLE_RELEASED == state ) ||
1352 handleStopScrolling )
1354 CharacterIndex handlePosition = 0u;
1355 if( handleStopScrolling || isSmoothHandlePanEnabled )
1357 // Convert from decorator's coords to text's coords.
1358 const float xPosition = event.p2.mFloat - mScrollPosition.x;
1359 const float yPosition = event.p3.mFloat - mScrollPosition.y;
1361 handlePosition = Text::GetClosestCursorIndex( mVisualModel,
1368 if( Event::GRAB_HANDLE_EVENT == event.type )
1370 mEventData->mUpdateCursorPosition = true;
1371 mEventData->mUpdateGrabHandlePosition = true;
1372 mEventData->mUpdateInputStyle = true;
1374 if( !IsClipboardEmpty() )
1376 ChangeState( EventData::EDITING_WITH_PASTE_POPUP ); // Moving grabhandle will show Paste Popup
1379 if( handleStopScrolling || isSmoothHandlePanEnabled )
1381 mEventData->mScrollAfterUpdatePosition = true;
1382 mEventData->mPrimaryCursorPosition = handlePosition;
1385 else if( Event::LEFT_SELECTION_HANDLE_EVENT == event.type )
1387 ChangeState( EventData::SELECTING );
1389 mEventData->mUpdateHighlightBox = true;
1390 mEventData->mUpdateLeftSelectionPosition = true;
1391 mEventData->mUpdateRightSelectionPosition = true;
1393 if( handleStopScrolling || isSmoothHandlePanEnabled )
1395 mEventData->mScrollAfterUpdatePosition = true;
1397 if( ( handlePosition != mEventData->mRightSelectionPosition ) &&
1398 ( handlePosition != mEventData->mLeftSelectionPosition ) )
1400 mEventData->mLeftSelectionPosition = handlePosition;
1404 else if( Event::RIGHT_SELECTION_HANDLE_EVENT == event.type )
1406 ChangeState( EventData::SELECTING );
1408 mEventData->mUpdateHighlightBox = true;
1409 mEventData->mUpdateRightSelectionPosition = true;
1410 mEventData->mUpdateLeftSelectionPosition = true;
1412 if( handleStopScrolling || isSmoothHandlePanEnabled )
1414 mEventData->mScrollAfterUpdatePosition = true;
1415 if( ( handlePosition != mEventData->mRightSelectionPosition ) &&
1416 ( handlePosition != mEventData->mLeftSelectionPosition ) )
1418 mEventData->mRightSelectionPosition = handlePosition;
1423 mEventData->mDecoratorUpdated = true;
1424 } // end ( ( HANDLE_RELEASED == state ) || ( HANDLE_STOP_SCROLLING == state ) )
1425 else if( HANDLE_SCROLLING == state )
1427 const float xSpeed = event.p2.mFloat;
1428 const float ySpeed = event.p3.mFloat;
1429 const Vector2& layoutSize = mVisualModel->GetLayoutSize();
1430 const Vector2 currentScrollPosition = mScrollPosition;
1432 mScrollPosition.x += xSpeed;
1433 mScrollPosition.y += ySpeed;
1435 ClampHorizontalScroll( layoutSize );
1436 ClampVerticalScroll( layoutSize );
1438 bool endOfScroll = false;
1439 if( Vector2::ZERO == ( currentScrollPosition - mScrollPosition ) )
1441 // Notify the decorator there is no more text to scroll.
1442 // The decorator won't send more scroll events.
1443 mEventData->mDecorator->NotifyEndOfScroll();
1444 // Still need to set the position of the handle.
1448 // Set the position of the handle.
1449 const bool scrollRightDirection = xSpeed > 0.f;
1450 const bool scrollBottomDirection = ySpeed > 0.f;
1451 const bool leftSelectionHandleEvent = Event::LEFT_SELECTION_HANDLE_EVENT == event.type;
1452 const bool rightSelectionHandleEvent = Event::RIGHT_SELECTION_HANDLE_EVENT == event.type;
1454 if( Event::GRAB_HANDLE_EVENT == event.type )
1456 ChangeState( EventData::GRAB_HANDLE_PANNING );
1458 // Get the grab handle position in decorator coords.
1459 Vector2 position = mEventData->mDecorator->GetPosition( GRAB_HANDLE );
1461 if( mEventData->mDecorator->IsHorizontalScrollEnabled() )
1463 // Position the grag handle close to either the left or right edge.
1464 position.x = scrollRightDirection ? 0.f : mVisualModel->mControlSize.width;
1467 if( mEventData->mDecorator->IsVerticalScrollEnabled() )
1469 position.x = mEventData->mCursorHookPositionX;
1471 // Position the grag handle close to either the top or bottom edge.
1472 position.y = scrollBottomDirection ? 0.f : mVisualModel->mControlSize.height;
1475 // Get the new handle position.
1476 // The grab handle's position is in decorator's coords. Need to transforms to text's coords.
1477 const CharacterIndex handlePosition = Text::GetClosestCursorIndex( mVisualModel,
1480 position.x - mScrollPosition.x,
1481 position.y - mScrollPosition.y );
1483 if( mEventData->mPrimaryCursorPosition != handlePosition )
1485 mEventData->mUpdateCursorPosition = true;
1486 mEventData->mUpdateGrabHandlePosition = !isSmoothHandlePanEnabled;
1487 mEventData->mScrollAfterUpdatePosition = true;
1488 mEventData->mPrimaryCursorPosition = handlePosition;
1490 mEventData->mUpdateInputStyle = mEventData->mUpdateCursorPosition;
1492 // Updates the decorator if the soft handle panning is enabled.
1493 mEventData->mDecoratorUpdated = isSmoothHandlePanEnabled;
1495 else if( leftSelectionHandleEvent || rightSelectionHandleEvent )
1497 ChangeState( EventData::SELECTION_HANDLE_PANNING );
1499 // Get the selection handle position in decorator coords.
1500 Vector2 position = mEventData->mDecorator->GetPosition( leftSelectionHandleEvent ? Text::LEFT_SELECTION_HANDLE : Text::RIGHT_SELECTION_HANDLE );
1502 if( mEventData->mDecorator->IsHorizontalScrollEnabled() )
1504 // Position the selection handle close to either the left or right edge.
1505 position.x = scrollRightDirection ? 0.f : mVisualModel->mControlSize.width;
1508 if( mEventData->mDecorator->IsVerticalScrollEnabled() )
1510 position.x = mEventData->mCursorHookPositionX;
1512 // Position the grag handle close to either the top or bottom edge.
1513 position.y = scrollBottomDirection ? 0.f : mVisualModel->mControlSize.height;
1516 // Get the new handle position.
1517 // The selection handle's position is in decorator's coords. Need to transform to text's coords.
1518 const CharacterIndex handlePosition = Text::GetClosestCursorIndex( mVisualModel,
1521 position.x - mScrollPosition.x,
1522 position.y - mScrollPosition.y );
1524 if( leftSelectionHandleEvent )
1526 const bool differentHandles = ( mEventData->mLeftSelectionPosition != handlePosition ) && ( mEventData->mRightSelectionPosition != handlePosition );
1528 if( differentHandles || endOfScroll )
1530 mEventData->mUpdateHighlightBox = true;
1531 mEventData->mUpdateLeftSelectionPosition = !isSmoothHandlePanEnabled;
1532 mEventData->mUpdateRightSelectionPosition = isSmoothHandlePanEnabled;
1533 mEventData->mLeftSelectionPosition = handlePosition;
1538 const bool differentHandles = ( mEventData->mRightSelectionPosition != handlePosition ) && ( mEventData->mLeftSelectionPosition != handlePosition );
1539 if( differentHandles || endOfScroll )
1541 mEventData->mUpdateHighlightBox = true;
1542 mEventData->mUpdateRightSelectionPosition = !isSmoothHandlePanEnabled;
1543 mEventData->mUpdateLeftSelectionPosition = isSmoothHandlePanEnabled;
1544 mEventData->mRightSelectionPosition = handlePosition;
1548 if( mEventData->mUpdateLeftSelectionPosition || mEventData->mUpdateRightSelectionPosition )
1550 RepositionSelectionHandles();
1552 mEventData->mScrollAfterUpdatePosition = !isSmoothHandlePanEnabled;
1555 mEventData->mDecoratorUpdated = true;
1556 } // end ( HANDLE_SCROLLING == state )
1559 void Controller::Impl::OnSelectEvent( const Event& event )
1561 if( NULL == mEventData )
1563 // Nothing to do if there is no text.
1567 if( mEventData->mSelectionEnabled )
1569 // Convert from control's coords to text's coords.
1570 const float xPosition = event.p2.mFloat - mScrollPosition.x;
1571 const float yPosition = event.p3.mFloat - mScrollPosition.y;
1573 // Calculates the logical position from the x,y coords.
1574 RepositionSelectionHandles( xPosition,
1579 void Controller::Impl::OnSelectAllEvent()
1581 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "OnSelectAllEvent mEventData->mSelectionEnabled%s \n", mEventData->mSelectionEnabled?"true":"false");
1583 if( NULL == mEventData )
1585 // Nothing to do if there is no text.
1589 if( mEventData->mSelectionEnabled )
1591 ChangeState( EventData::SELECTING );
1593 mEventData->mLeftSelectionPosition = 0u;
1594 mEventData->mRightSelectionPosition = mLogicalModel->mText.Count();
1596 mEventData->mScrollAfterUpdatePosition = true;
1597 mEventData->mUpdateLeftSelectionPosition = true;
1598 mEventData->mUpdateRightSelectionPosition = true;
1599 mEventData->mUpdateHighlightBox = true;
1603 void Controller::Impl::RetrieveSelection( std::string& selectedText, bool deleteAfterRetrieval )
1605 if( mEventData->mLeftSelectionPosition == mEventData->mRightSelectionPosition )
1607 // Nothing to select if handles are in the same place.
1608 selectedText.clear();
1612 const bool handlesCrossed = mEventData->mLeftSelectionPosition > mEventData->mRightSelectionPosition;
1614 //Get start and end position of selection
1615 const CharacterIndex startOfSelectedText = handlesCrossed ? mEventData->mRightSelectionPosition : mEventData->mLeftSelectionPosition;
1616 const Length lengthOfSelectedText = ( handlesCrossed ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition ) - startOfSelectedText;
1618 Vector<Character>& utf32Characters = mLogicalModel->mText;
1619 const Length numberOfCharacters = utf32Characters.Count();
1621 // Validate the start and end selection points
1622 if( ( startOfSelectedText + lengthOfSelectedText ) <= numberOfCharacters )
1624 //Get text as a UTF8 string
1625 Utf32ToUtf8( &utf32Characters[startOfSelectedText], lengthOfSelectedText, selectedText );
1627 if( deleteAfterRetrieval ) // Only delete text if copied successfully
1629 // Set as input style the style of the first deleted character.
1630 mLogicalModel->RetrieveStyle( startOfSelectedText, mEventData->mInputStyle );
1632 mLogicalModel->UpdateTextStyleRuns( startOfSelectedText, -static_cast<int>( lengthOfSelectedText ) );
1634 // Mark the paragraphs to be updated.
1635 mTextUpdateInfo.mCharacterIndex = startOfSelectedText;
1636 mTextUpdateInfo.mNumberOfCharactersToRemove = lengthOfSelectedText;
1638 // Delete text between handles
1639 Vector<Character>::Iterator first = utf32Characters.Begin() + startOfSelectedText;
1640 Vector<Character>::Iterator last = first + lengthOfSelectedText;
1641 utf32Characters.Erase( first, last );
1643 // Will show the cursor at the first character of the selection.
1644 mEventData->mPrimaryCursorPosition = handlesCrossed ? mEventData->mRightSelectionPosition : mEventData->mLeftSelectionPosition;
1648 // Will show the cursor at the last character of the selection.
1649 mEventData->mPrimaryCursorPosition = handlesCrossed ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition;
1652 mEventData->mDecoratorUpdated = true;
1656 void Controller::Impl::ShowClipboard()
1660 mClipboard.ShowClipboard();
1664 void Controller::Impl::HideClipboard()
1666 if( mClipboard && mClipboardHideEnabled )
1668 mClipboard.HideClipboard();
1672 void Controller::Impl::SetClipboardHideEnable(bool enable)
1674 mClipboardHideEnabled = enable;
1677 bool Controller::Impl::CopyStringToClipboard( std::string& source )
1679 //Send string to clipboard
1680 return ( mClipboard && mClipboard.SetItem( source ) );
1683 void Controller::Impl::SendSelectionToClipboard( bool deleteAfterSending )
1685 std::string selectedText;
1686 RetrieveSelection( selectedText, deleteAfterSending );
1687 CopyStringToClipboard( selectedText );
1688 ChangeState( EventData::EDITING );
1691 void Controller::Impl::GetTextFromClipboard( unsigned int itemIndex, std::string& retrievedString )
1695 retrievedString = mClipboard.GetItem( itemIndex );
1699 void Controller::Impl::RepositionSelectionHandles()
1701 CharacterIndex selectionStart = mEventData->mLeftSelectionPosition;
1702 CharacterIndex selectionEnd = mEventData->mRightSelectionPosition;
1704 if( selectionStart == selectionEnd )
1706 // Nothing to select if handles are in the same place.
1710 mEventData->mDecorator->ClearHighlights();
1712 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
1713 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
1714 const GlyphInfo* const glyphsBuffer = mVisualModel->mGlyphs.Begin();
1715 const Vector2* const positionsBuffer = mVisualModel->mGlyphPositions.Begin();
1716 const Length* const charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
1717 const CharacterIndex* const glyphToCharacterBuffer = mVisualModel->mGlyphsToCharacters.Begin();
1718 const CharacterDirection* const modelCharacterDirectionsBuffer = ( 0u != mLogicalModel->mCharacterDirections.Count() ) ? mLogicalModel->mCharacterDirections.Begin() : NULL;
1720 const bool isLastCharacter = selectionEnd >= mLogicalModel->mText.Count();
1721 const CharacterDirection startDirection = ( ( NULL == modelCharacterDirectionsBuffer ) ? false : *( modelCharacterDirectionsBuffer + selectionStart ) );
1722 const CharacterDirection endDirection = ( ( NULL == modelCharacterDirectionsBuffer ) ? false : *( modelCharacterDirectionsBuffer + ( selectionEnd - ( isLastCharacter ? 1u : 0u ) ) ) );
1724 // Swap the indices if the start is greater than the end.
1725 const bool indicesSwapped = selectionStart > selectionEnd;
1727 // Tell the decorator to flip the selection handles if needed.
1728 mEventData->mDecorator->SetSelectionHandleFlipState( indicesSwapped, startDirection, endDirection );
1730 if( indicesSwapped )
1732 std::swap( selectionStart, selectionEnd );
1735 // Get the indices to the first and last selected glyphs.
1736 const CharacterIndex selectionEndMinusOne = selectionEnd - 1u;
1737 const GlyphIndex glyphStart = *( charactersToGlyphBuffer + selectionStart );
1738 const Length numberOfGlyphs = *( glyphsPerCharacterBuffer + selectionEndMinusOne );
1739 const GlyphIndex glyphEnd = *( charactersToGlyphBuffer + selectionEndMinusOne ) + ( ( numberOfGlyphs > 0 ) ? numberOfGlyphs - 1u : 0u );
1741 // Get the lines where the glyphs are laid-out.
1742 const LineRun* lineRun = mVisualModel->mLines.Begin();
1744 LineIndex lineIndex = 0u;
1745 Length numberOfLines = 0u;
1746 mVisualModel->GetNumberOfLines( glyphStart,
1747 1u + glyphEnd - glyphStart,
1750 const LineIndex firstLineIndex = lineIndex;
1752 // Create the structure to store some selection box info.
1753 Vector<SelectionBoxInfo> selectionBoxLinesInfo;
1754 selectionBoxLinesInfo.Resize( numberOfLines );
1756 SelectionBoxInfo* selectionBoxInfo = selectionBoxLinesInfo.Begin();
1757 selectionBoxInfo->minX = MAX_FLOAT;
1758 selectionBoxInfo->maxX = MIN_FLOAT;
1760 // Keep the min and max 'x' position to calculate the size and position of the highlighed text.
1761 float minHighlightX = std::numeric_limits<float>::max();
1762 float maxHighlightX = std::numeric_limits<float>::min();
1764 Vector2 highLightPosition; // The highlight position in decorator's coords.
1766 // Retrieve the first line and get the line's vertical offset, the line's height and the index to the last glyph.
1768 // The line's vertical offset of all the lines before the line where the first glyph is laid-out.
1769 selectionBoxInfo->lineOffset = CalculateLineOffset( mVisualModel->mLines,
1772 // Transform to decorator's (control) coords.
1773 selectionBoxInfo->lineOffset += mScrollPosition.y;
1775 lineRun += firstLineIndex;
1777 // The line height is the addition of the line ascender and the line descender.
1778 // However, the line descender has a negative value, hence the subtraction.
1779 selectionBoxInfo->lineHeight = lineRun->ascender - lineRun->descender;
1781 GlyphIndex lastGlyphOfLine = lineRun->glyphRun.glyphIndex + lineRun->glyphRun.numberOfGlyphs - 1u;
1783 // Check if the first glyph is a ligature that must be broken like Latin ff, fi, or Arabic ﻻ, etc which needs special code.
1784 const Length numberOfCharactersStart = *( charactersPerGlyphBuffer + glyphStart );
1785 bool splitStartGlyph = ( numberOfCharactersStart > 1u ) && HasLigatureMustBreak( mLogicalModel->GetScript( selectionStart ) );
1787 // Check if the last glyph is a ligature that must be broken like Latin ff, fi, or Arabic ﻻ, etc which needs special code.
1788 const Length numberOfCharactersEnd = *( charactersPerGlyphBuffer + glyphEnd );
1789 bool splitEndGlyph = ( glyphStart != glyphEnd ) && ( numberOfCharactersEnd > 1u ) && HasLigatureMustBreak( mLogicalModel->GetScript( selectionEndMinusOne ) );
1791 // Traverse the glyphs.
1792 for( GlyphIndex index = glyphStart; index <= glyphEnd; ++index )
1794 const GlyphInfo& glyph = *( glyphsBuffer + index );
1795 const Vector2& position = *( positionsBuffer + index );
1797 if( splitStartGlyph )
1799 // If the first glyph is a ligature that must be broken it may be needed to add only part of the glyph to the highlight box.
1801 const float glyphAdvance = glyph.advance / static_cast<float>( numberOfCharactersStart );
1802 const CharacterIndex interGlyphIndex = selectionStart - *( glyphToCharacterBuffer + glyphStart );
1803 // Get the direction of the character.
1804 CharacterDirection isCurrentRightToLeft = false;
1805 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
1807 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + selectionStart );
1810 // The end point could be in the middle of the ligature.
1811 // Calculate the number of characters selected.
1812 const Length numberOfCharacters = ( glyphStart == glyphEnd ) ? ( selectionEnd - selectionStart ) : ( numberOfCharactersStart - interGlyphIndex );
1814 const float xPosition = lineRun->alignmentOffset + position.x - glyph.xBearing + mScrollPosition.x + glyphAdvance * static_cast<float>( isCurrentRightToLeft ? ( numberOfCharactersStart - interGlyphIndex - numberOfCharacters ) : interGlyphIndex );
1815 const float xPositionAdvance = xPosition + static_cast<float>( numberOfCharacters ) * glyphAdvance;
1816 const float yPosition = selectionBoxInfo->lineOffset;
1818 // Store the min and max 'x' for each line.
1819 selectionBoxInfo->minX = std::min( selectionBoxInfo->minX, xPosition );
1820 selectionBoxInfo->maxX = std::max( selectionBoxInfo->maxX, xPositionAdvance );
1822 mEventData->mDecorator->AddHighlight( xPosition,
1825 yPosition + selectionBoxInfo->lineHeight );
1827 splitStartGlyph = false;
1831 if( splitEndGlyph && ( index == glyphEnd ) )
1833 // Equally, if the last glyph is a ligature that must be broken it may be needed to add only part of the glyph to the highlight box.
1835 const float glyphAdvance = glyph.advance / static_cast<float>( numberOfCharactersEnd );
1836 const CharacterIndex interGlyphIndex = selectionEnd - *( glyphToCharacterBuffer + glyphEnd );
1837 // Get the direction of the character.
1838 CharacterDirection isCurrentRightToLeft = false;
1839 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
1841 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + selectionEnd );
1844 const Length numberOfCharacters = numberOfCharactersEnd - interGlyphIndex;
1846 const float xPosition = lineRun->alignmentOffset + position.x - glyph.xBearing + mScrollPosition.x + ( isCurrentRightToLeft ? ( glyphAdvance * static_cast<float>( numberOfCharacters ) ) : 0.f );
1847 const float xPositionAdvance = xPosition + static_cast<float>( interGlyphIndex ) * glyphAdvance;
1848 const float yPosition = selectionBoxInfo->lineOffset;
1850 // Store the min and max 'x' for each line.
1851 selectionBoxInfo->minX = std::min( selectionBoxInfo->minX, xPosition );
1852 selectionBoxInfo->maxX = std::max( selectionBoxInfo->maxX, xPositionAdvance );
1854 mEventData->mDecorator->AddHighlight( xPosition,
1857 yPosition + selectionBoxInfo->lineHeight );
1859 splitEndGlyph = false;
1863 const float xPosition = lineRun->alignmentOffset + position.x - glyph.xBearing + mScrollPosition.x;
1864 const float xPositionAdvance = xPosition + glyph.advance;
1865 const float yPosition = selectionBoxInfo->lineOffset;
1867 // Store the min and max 'x' for each line.
1868 selectionBoxInfo->minX = std::min( selectionBoxInfo->minX, xPosition );
1869 selectionBoxInfo->maxX = std::max( selectionBoxInfo->maxX, xPositionAdvance );
1871 mEventData->mDecorator->AddHighlight( xPosition,
1874 yPosition + selectionBoxInfo->lineHeight );
1876 // Whether to retrieve the next line.
1877 if( index == lastGlyphOfLine )
1879 // Retrieve the next line.
1882 // Get the last glyph of the new line.
1883 lastGlyphOfLine = lineRun->glyphRun.glyphIndex + lineRun->glyphRun.numberOfGlyphs - 1u;
1886 if( lineIndex < firstLineIndex + numberOfLines )
1888 // Keep the offset and height of the current selection box.
1889 const float currentLineOffset = selectionBoxInfo->lineOffset;
1890 const float currentLineHeight = selectionBoxInfo->lineHeight;
1892 // Get the selection box info for the next line.
1895 selectionBoxInfo->minX = MAX_FLOAT;
1896 selectionBoxInfo->maxX = MIN_FLOAT;
1898 // Update the line's vertical offset.
1899 selectionBoxInfo->lineOffset = currentLineOffset + currentLineHeight;
1901 // The line height is the addition of the line ascender and the line descender.
1902 // However, the line descender has a negative value, hence the subtraction.
1903 selectionBoxInfo->lineHeight = lineRun->ascender - lineRun->descender;
1908 // Traverses all the lines and updates the min and max 'x' positions and the total height.
1909 // The final width is calculated after 'boxifying' the selection.
1910 for( Vector<SelectionBoxInfo>::ConstIterator it = selectionBoxLinesInfo.Begin(),
1911 endIt = selectionBoxLinesInfo.End();
1915 const SelectionBoxInfo& info = *it;
1917 // Update the size of the highlighted text.
1918 highLightSize.height += selectionBoxInfo->lineHeight;
1919 minHighlightX = std::min( minHighlightX, info.minX );
1920 maxHighlightX = std::max( maxHighlightX, info.maxX );
1923 // Add extra geometry to 'boxify' the selection.
1925 if( 1u < numberOfLines )
1927 // Boxify the first line.
1928 lineRun = mVisualModel->mLines.Begin() + firstLineIndex;
1929 const SelectionBoxInfo& firstSelectionBoxLineInfo = *( selectionBoxLinesInfo.Begin() );
1931 bool boxifyBegin = ( LTR != lineRun->direction ) && ( LTR != startDirection );
1932 bool boxifyEnd = ( LTR == lineRun->direction ) && ( LTR == startDirection );
1936 // Boxify at the beginning of the line.
1937 mEventData->mDecorator->AddHighlight( 0.f,
1938 firstSelectionBoxLineInfo.lineOffset,
1939 firstSelectionBoxLineInfo.minX,
1940 firstSelectionBoxLineInfo.lineOffset + firstSelectionBoxLineInfo.lineHeight );
1942 // Update the size of the highlighted text.
1943 minHighlightX = 0.f;
1948 // Boxify at the end of the line.
1949 mEventData->mDecorator->AddHighlight( firstSelectionBoxLineInfo.maxX,
1950 firstSelectionBoxLineInfo.lineOffset,
1951 mVisualModel->mControlSize.width,
1952 firstSelectionBoxLineInfo.lineOffset + firstSelectionBoxLineInfo.lineHeight );
1954 // Update the size of the highlighted text.
1955 maxHighlightX = mVisualModel->mControlSize.width;
1958 // Boxify the central lines.
1959 if( 2u < numberOfLines )
1961 for( Vector<SelectionBoxInfo>::ConstIterator it = selectionBoxLinesInfo.Begin() + 1u,
1962 endIt = selectionBoxLinesInfo.End() - 1u;
1966 const SelectionBoxInfo& info = *it;
1968 mEventData->mDecorator->AddHighlight( 0.f,
1971 info.lineOffset + info.lineHeight );
1973 mEventData->mDecorator->AddHighlight( info.maxX,
1975 mVisualModel->mControlSize.width,
1976 info.lineOffset + info.lineHeight );
1979 // Update the size of the highlighted text.
1980 minHighlightX = 0.f;
1981 maxHighlightX = mVisualModel->mControlSize.width;
1984 // Boxify the last line.
1985 lineRun = mVisualModel->mLines.Begin() + firstLineIndex + numberOfLines - 1u;
1986 const SelectionBoxInfo& lastSelectionBoxLineInfo = *( selectionBoxLinesInfo.End() - 1u );
1988 boxifyBegin = ( LTR == lineRun->direction ) && ( LTR == endDirection );
1989 boxifyEnd = ( LTR != lineRun->direction ) && ( LTR != endDirection );
1993 // Boxify at the beginning of the line.
1994 mEventData->mDecorator->AddHighlight( 0.f,
1995 lastSelectionBoxLineInfo.lineOffset,
1996 lastSelectionBoxLineInfo.minX,
1997 lastSelectionBoxLineInfo.lineOffset + lastSelectionBoxLineInfo.lineHeight );
1999 // Update the size of the highlighted text.
2000 minHighlightX = 0.f;
2005 // Boxify at the end of the line.
2006 mEventData->mDecorator->AddHighlight( lastSelectionBoxLineInfo.maxX,
2007 lastSelectionBoxLineInfo.lineOffset,
2008 mVisualModel->mControlSize.width,
2009 lastSelectionBoxLineInfo.lineOffset + lastSelectionBoxLineInfo.lineHeight );
2011 // Update the size of the highlighted text.
2012 maxHighlightX = mVisualModel->mControlSize.width;
2016 // Sets the highlight's size and position. In decorator's coords.
2017 // The highlight's height has been calculated above (before 'boxifying' the highlight).
2018 highLightSize.width = maxHighlightX - minHighlightX;
2020 highLightPosition.x = minHighlightX;
2021 const SelectionBoxInfo& firstSelectionBoxLineInfo = *( selectionBoxLinesInfo.Begin() );
2022 highLightPosition.y = firstSelectionBoxLineInfo.lineOffset;
2024 mEventData->mDecorator->SetHighLightBox( highLightPosition, highLightSize );
2026 if( !mEventData->mDecorator->IsSmoothHandlePanEnabled() )
2028 CursorInfo primaryCursorInfo;
2029 GetCursorPosition( mEventData->mLeftSelectionPosition,
2030 primaryCursorInfo );
2032 const Vector2 primaryPosition = primaryCursorInfo.primaryPosition + mScrollPosition;
2034 mEventData->mDecorator->SetPosition( LEFT_SELECTION_HANDLE,
2036 primaryCursorInfo.lineOffset + mScrollPosition.y,
2037 primaryCursorInfo.lineHeight );
2039 CursorInfo secondaryCursorInfo;
2040 GetCursorPosition( mEventData->mRightSelectionPosition,
2041 secondaryCursorInfo );
2043 const Vector2 secondaryPosition = secondaryCursorInfo.primaryPosition + mScrollPosition;
2045 mEventData->mDecorator->SetPosition( RIGHT_SELECTION_HANDLE,
2046 secondaryPosition.x,
2047 secondaryCursorInfo.lineOffset + mScrollPosition.y,
2048 secondaryCursorInfo.lineHeight );
2051 // Cursor to be positioned at end of selection so if selection interrupted and edit mode restarted the cursor will be at end of selection
2052 mEventData->mPrimaryCursorPosition = ( indicesSwapped ) ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition;
2054 // Set the flag to update the decorator.
2055 mEventData->mDecoratorUpdated = true;
2058 void Controller::Impl::RepositionSelectionHandles( float visualX, float visualY )
2060 if( NULL == mEventData )
2062 // Nothing to do if there is no text input.
2066 if( IsShowingPlaceholderText() )
2068 // Nothing to do if there is the place-holder text.
2072 const Length numberOfGlyphs = mVisualModel->mGlyphs.Count();
2073 const Length numberOfLines = mVisualModel->mLines.Count();
2074 if( ( 0 == numberOfGlyphs ) ||
2075 ( 0 == numberOfLines ) )
2077 // Nothing to do if there is no text.
2081 // Find which word was selected
2082 CharacterIndex selectionStart( 0 );
2083 CharacterIndex selectionEnd( 0 );
2084 const bool indicesFound = FindSelectionIndices( mVisualModel,
2091 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "%p selectionStart %d selectionEnd %d\n", this, selectionStart, selectionEnd );
2095 ChangeState( EventData::SELECTING );
2097 mEventData->mLeftSelectionPosition = selectionStart;
2098 mEventData->mRightSelectionPosition = selectionEnd;
2100 mEventData->mUpdateLeftSelectionPosition = true;
2101 mEventData->mUpdateRightSelectionPosition = true;
2102 mEventData->mUpdateHighlightBox = true;
2104 mEventData->mScrollAfterUpdatePosition = ( mEventData->mLeftSelectionPosition != mEventData->mRightSelectionPosition );
2108 // Nothing to select. i.e. a white space, out of bounds
2109 ChangeState( EventData::EDITING );
2111 mEventData->mPrimaryCursorPosition = selectionEnd;
2113 mEventData->mUpdateCursorPosition = true;
2114 mEventData->mUpdateGrabHandlePosition = true;
2115 mEventData->mScrollAfterUpdatePosition = true;
2116 mEventData->mUpdateInputStyle = true;
2120 void Controller::Impl::SetPopupButtons()
2123 * Sets the Popup buttons to be shown depending on State.
2125 * If SELECTING : CUT & COPY + ( PASTE & CLIPBOARD if content available to paste )
2127 * If EDITING_WITH_POPUP : SELECT & SELECT_ALL
2130 TextSelectionPopup::Buttons buttonsToShow = TextSelectionPopup::NONE;
2132 if( EventData::SELECTING == mEventData->mState )
2134 buttonsToShow = TextSelectionPopup::Buttons( TextSelectionPopup::CUT | TextSelectionPopup::COPY );
2136 if( !IsClipboardEmpty() )
2138 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
2139 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::CLIPBOARD ) );
2142 if( !mEventData->mAllTextSelected )
2144 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::SELECT_ALL ) );
2147 else if( EventData::EDITING_WITH_POPUP == mEventData->mState )
2149 if( mLogicalModel->mText.Count() && !IsShowingPlaceholderText() )
2151 buttonsToShow = TextSelectionPopup::Buttons( TextSelectionPopup::SELECT | TextSelectionPopup::SELECT_ALL );
2154 if( !IsClipboardEmpty() )
2156 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
2157 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::CLIPBOARD ) );
2160 else if( EventData::EDITING_WITH_PASTE_POPUP == mEventData->mState )
2162 if ( !IsClipboardEmpty() )
2164 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
2165 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::CLIPBOARD ) );
2169 mEventData->mDecorator->SetEnabledPopupButtons( buttonsToShow );
2172 void Controller::Impl::ChangeState( EventData::State newState )
2174 if( NULL == mEventData )
2176 // Nothing to do if there is no text input.
2180 DALI_LOG_INFO( gLogFilter, Debug::General, "ChangeState state:%d newstate:%d\n", mEventData->mState, newState );
2182 if( mEventData->mState != newState )
2184 mEventData->mPreviousState = mEventData->mState;
2185 mEventData->mState = newState;
2187 switch( mEventData->mState )
2189 case EventData::INACTIVE:
2191 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
2192 mEventData->mDecorator->StopCursorBlink();
2193 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
2194 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
2195 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
2196 mEventData->mDecorator->SetHighlightActive( false );
2197 mEventData->mDecorator->SetPopupActive( false );
2198 mEventData->mDecoratorUpdated = true;
2202 case EventData::INTERRUPTED:
2204 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
2205 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
2206 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
2207 mEventData->mDecorator->SetHighlightActive( false );
2208 mEventData->mDecorator->SetPopupActive( false );
2209 mEventData->mDecoratorUpdated = true;
2213 case EventData::SELECTING:
2215 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
2216 mEventData->mDecorator->StopCursorBlink();
2217 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
2218 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true );
2219 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true );
2220 mEventData->mDecorator->SetHighlightActive( true );
2221 if( mEventData->mGrabHandlePopupEnabled )
2224 mEventData->mDecorator->SetPopupActive( true );
2226 mEventData->mDecoratorUpdated = true;
2229 case EventData::EDITING:
2231 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
2232 if( mEventData->mCursorBlinkEnabled )
2234 mEventData->mDecorator->StartCursorBlink();
2236 // Grab handle is not shown until a tap is received whilst EDITING
2237 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
2238 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
2239 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
2240 mEventData->mDecorator->SetHighlightActive( false );
2241 if( mEventData->mGrabHandlePopupEnabled )
2243 mEventData->mDecorator->SetPopupActive( false );
2245 mEventData->mDecoratorUpdated = true;
2249 case EventData::EDITING_WITH_POPUP:
2251 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "EDITING_WITH_POPUP \n", newState );
2253 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
2254 if( mEventData->mCursorBlinkEnabled )
2256 mEventData->mDecorator->StartCursorBlink();
2258 if( mEventData->mSelectionEnabled )
2260 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
2261 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
2262 mEventData->mDecorator->SetHighlightActive( false );
2266 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
2268 if( mEventData->mGrabHandlePopupEnabled )
2271 mEventData->mDecorator->SetPopupActive( true );
2274 mEventData->mDecoratorUpdated = true;
2277 case EventData::EDITING_WITH_GRAB_HANDLE:
2279 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "EDITING_WITH_GRAB_HANDLE \n", newState );
2281 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
2282 if( mEventData->mCursorBlinkEnabled )
2284 mEventData->mDecorator->StartCursorBlink();
2286 // Grab handle is not shown until a tap is received whilst EDITING
2287 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
2288 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
2289 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
2290 mEventData->mDecorator->SetHighlightActive( false );
2291 if( mEventData->mGrabHandlePopupEnabled )
2293 mEventData->mDecorator->SetPopupActive( false );
2295 mEventData->mDecoratorUpdated = true;
2299 case EventData::SELECTION_HANDLE_PANNING:
2301 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
2302 mEventData->mDecorator->StopCursorBlink();
2303 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
2304 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true );
2305 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true );
2306 mEventData->mDecorator->SetHighlightActive( true );
2307 if( mEventData->mGrabHandlePopupEnabled )
2309 mEventData->mDecorator->SetPopupActive( false );
2311 mEventData->mDecoratorUpdated = true;
2314 case EventData::GRAB_HANDLE_PANNING:
2316 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "GRAB_HANDLE_PANNING \n", newState );
2318 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
2319 if( mEventData->mCursorBlinkEnabled )
2321 mEventData->mDecorator->StartCursorBlink();
2323 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
2324 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
2325 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
2326 mEventData->mDecorator->SetHighlightActive( false );
2327 if( mEventData->mGrabHandlePopupEnabled )
2329 mEventData->mDecorator->SetPopupActive( false );
2331 mEventData->mDecoratorUpdated = true;
2334 case EventData::EDITING_WITH_PASTE_POPUP:
2336 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "EDITING_WITH_PASTE_POPUP \n", newState );
2338 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
2339 if( mEventData->mCursorBlinkEnabled )
2341 mEventData->mDecorator->StartCursorBlink();
2344 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
2345 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
2346 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
2347 mEventData->mDecorator->SetHighlightActive( false );
2349 if( mEventData->mGrabHandlePopupEnabled )
2352 mEventData->mDecorator->SetPopupActive( true );
2355 mEventData->mDecoratorUpdated = true;
2358 case EventData::TEXT_PANNING:
2360 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
2361 mEventData->mDecorator->StopCursorBlink();
2362 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
2363 if( mEventData->mDecorator->IsHandleActive( LEFT_SELECTION_HANDLE ) ||
2364 mEventData->mDecorator->IsHandleActive( RIGHT_SELECTION_HANDLE ) )
2366 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
2367 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
2368 mEventData->mDecorator->SetHighlightActive( true );
2371 if( mEventData->mGrabHandlePopupEnabled )
2373 mEventData->mDecorator->SetPopupActive( false );
2376 mEventData->mDecoratorUpdated = true;
2383 void Controller::Impl::GetCursorPosition( CharacterIndex logical,
2384 CursorInfo& cursorInfo )
2386 if( !IsShowingRealText() )
2388 // Do not want to use the place-holder text to set the cursor position.
2390 // Use the line's height of the font's family set to set the cursor's size.
2391 // If there is no font's family set, use the default font.
2392 // Use the current alignment to place the cursor at the beginning, center or end of the box.
2394 cursorInfo.lineOffset = 0.f;
2395 cursorInfo.lineHeight = GetDefaultFontLineHeight();
2396 cursorInfo.primaryCursorHeight = cursorInfo.lineHeight;
2398 switch( mLayoutEngine.GetHorizontalAlignment() )
2400 case LayoutEngine::HORIZONTAL_ALIGN_BEGIN:
2402 cursorInfo.primaryPosition.x = 0.f;
2405 case LayoutEngine::HORIZONTAL_ALIGN_CENTER:
2407 cursorInfo.primaryPosition.x = floorf( 0.5f * mVisualModel->mControlSize.width );
2410 case LayoutEngine::HORIZONTAL_ALIGN_END:
2412 cursorInfo.primaryPosition.x = mVisualModel->mControlSize.width - mEventData->mDecorator->GetCursorWidth();
2417 // Nothing else to do.
2421 Text::GetCursorPosition( mVisualModel,
2427 if( LayoutEngine::MULTI_LINE_BOX == mLayoutEngine.GetLayout() )
2429 // If the text is editable and multi-line, the cursor position after a white space shouldn't exceed the boundaries of the text control.
2431 // Note the white spaces laid-out at the end of the line might exceed the boundaries of the control.
2432 // The reason is a wrapped line must not start with a white space so they are laid-out at the end of the line.
2434 if( 0.f > cursorInfo.primaryPosition.x )
2436 cursorInfo.primaryPosition.x = 0.f;
2439 const float edgeWidth = mVisualModel->mControlSize.width - static_cast<float>( mEventData->mDecorator->GetCursorWidth() );
2440 if( cursorInfo.primaryPosition.x > edgeWidth )
2442 cursorInfo.primaryPosition.x = edgeWidth;
2447 CharacterIndex Controller::Impl::CalculateNewCursorIndex( CharacterIndex index ) const
2449 if( NULL == mEventData )
2451 // Nothing to do if there is no text input.
2455 CharacterIndex cursorIndex = mEventData->mPrimaryCursorPosition;
2457 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
2458 const Length* const charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
2460 GlyphIndex glyphIndex = *( charactersToGlyphBuffer + index );
2461 Length numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
2463 if( numberOfCharacters > 1u )
2465 const Script script = mLogicalModel->GetScript( index );
2466 if( HasLigatureMustBreak( script ) )
2468 // Prevents to jump the whole Latin ligatures like fi, ff, or Arabic ﻻ, ...
2469 numberOfCharacters = 1u;
2474 while( 0u == numberOfCharacters )
2477 numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
2481 if( index < mEventData->mPrimaryCursorPosition )
2483 cursorIndex -= numberOfCharacters;
2487 cursorIndex += numberOfCharacters;
2490 // Will update the cursor hook position.
2491 mEventData->mUpdateCursorHookPosition = true;
2496 void Controller::Impl::UpdateCursorPosition( const CursorInfo& cursorInfo )
2498 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "-->Controller::UpdateCursorPosition %p\n", this );
2499 if( NULL == mEventData )
2501 // Nothing to do if there is no text input.
2502 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition no event data\n" );
2506 const Vector2 cursorPosition = cursorInfo.primaryPosition + mScrollPosition;
2508 // Sets the cursor position.
2509 mEventData->mDecorator->SetPosition( PRIMARY_CURSOR,
2512 cursorInfo.primaryCursorHeight,
2513 cursorInfo.lineHeight );
2514 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Primary cursor position: %f,%f\n", cursorPosition.x, cursorPosition.y );
2516 if( mEventData->mUpdateGrabHandlePosition )
2518 // Sets the grab handle position.
2519 mEventData->mDecorator->SetPosition( GRAB_HANDLE,
2521 cursorInfo.lineOffset + mScrollPosition.y,
2522 cursorInfo.lineHeight );
2525 if( cursorInfo.isSecondaryCursor )
2527 mEventData->mDecorator->SetPosition( SECONDARY_CURSOR,
2528 cursorInfo.secondaryPosition.x + mScrollPosition.x,
2529 cursorInfo.secondaryPosition.y + mScrollPosition.y,
2530 cursorInfo.secondaryCursorHeight,
2531 cursorInfo.lineHeight );
2532 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Secondary cursor position: %f,%f\n", cursorInfo.secondaryPosition.x + mScrollPosition.x, cursorInfo.secondaryPosition.y + mScrollPosition.y );
2535 // Set which cursors are active according the state.
2536 if( EventData::IsEditingState( mEventData->mState ) || ( EventData::GRAB_HANDLE_PANNING == mEventData->mState ) )
2538 if( cursorInfo.isSecondaryCursor )
2540 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_BOTH );
2544 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
2549 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
2552 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition\n" );
2555 void Controller::Impl::UpdateSelectionHandle( HandleType handleType,
2556 const CursorInfo& cursorInfo )
2558 if( ( LEFT_SELECTION_HANDLE != handleType ) &&
2559 ( RIGHT_SELECTION_HANDLE != handleType ) )
2564 const Vector2 cursorPosition = cursorInfo.primaryPosition + mScrollPosition;
2566 // Sets the handle's position.
2567 mEventData->mDecorator->SetPosition( handleType,
2569 cursorInfo.lineOffset + mScrollPosition.y,
2570 cursorInfo.lineHeight );
2572 // If selection handle at start of the text and other at end of the text then all text is selected.
2573 const CharacterIndex startOfSelection = std::min( mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition );
2574 const CharacterIndex endOfSelection = std::max ( mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition );
2575 mEventData->mAllTextSelected = ( startOfSelection == 0 ) && ( endOfSelection == mLogicalModel->mText.Count() );
2578 void Controller::Impl::ClampHorizontalScroll( const Vector2& layoutSize )
2580 // Clamp between -space & 0.
2582 if( layoutSize.width > mVisualModel->mControlSize.width )
2584 const float space = ( layoutSize.width - mVisualModel->mControlSize.width );
2585 mScrollPosition.x = ( mScrollPosition.x < -space ) ? -space : mScrollPosition.x;
2586 mScrollPosition.x = ( mScrollPosition.x > 0.f ) ? 0.f : mScrollPosition.x;
2588 mEventData->mDecoratorUpdated = true;
2592 mScrollPosition.x = 0.f;
2596 void Controller::Impl::ClampVerticalScroll( const Vector2& layoutSize )
2598 // Clamp between -space & 0.
2599 if( layoutSize.height > mVisualModel->mControlSize.height )
2601 const float space = ( layoutSize.height - mVisualModel->mControlSize.height );
2602 mScrollPosition.y = ( mScrollPosition.y < -space ) ? -space : mScrollPosition.y;
2603 mScrollPosition.y = ( mScrollPosition.y > 0.f ) ? 0.f : mScrollPosition.y;
2605 mEventData->mDecoratorUpdated = true;
2609 mScrollPosition.y = 0.f;
2613 void Controller::Impl::ScrollToMakePositionVisible( const Vector2& position, float lineHeight )
2615 const float cursorWidth = mEventData->mDecorator ? static_cast<float>( mEventData->mDecorator->GetCursorWidth() ) : 0.f;
2617 // position is in actor's coords.
2618 const float positionEndX = position.x + cursorWidth;
2619 const float positionEndY = position.y + lineHeight;
2621 // Transform the position to decorator coords.
2622 const float decoratorPositionBeginX = position.x + mScrollPosition.x;
2623 const float decoratorPositionEndX = positionEndX + mScrollPosition.x;
2625 const float decoratorPositionBeginY = position.y + mScrollPosition.y;
2626 const float decoratorPositionEndY = positionEndY + mScrollPosition.y;
2628 if( decoratorPositionBeginX < 0.f )
2630 mScrollPosition.x = -position.x;
2632 else if( decoratorPositionEndX > mVisualModel->mControlSize.width )
2634 mScrollPosition.x = mVisualModel->mControlSize.width - positionEndX;
2637 if( decoratorPositionBeginY < 0.f )
2639 mScrollPosition.y = -position.y;
2641 else if( decoratorPositionEndY > mVisualModel->mControlSize.height )
2643 mScrollPosition.y = mVisualModel->mControlSize.height - positionEndY;
2647 void Controller::Impl::ScrollTextToMatchCursor( const CursorInfo& cursorInfo )
2649 // Get the current cursor position in decorator coords.
2650 const Vector2& currentCursorPosition = mEventData->mDecorator->GetPosition( PRIMARY_CURSOR );
2652 // Calculate the offset to match the cursor position before the character was deleted.
2653 mScrollPosition.x = currentCursorPosition.x - cursorInfo.primaryPosition.x;
2654 mScrollPosition.y = currentCursorPosition.y - cursorInfo.lineOffset;
2656 ClampHorizontalScroll( mVisualModel->GetLayoutSize() );
2657 ClampVerticalScroll( mVisualModel->GetLayoutSize() );
2659 // Makes the new cursor position visible if needed.
2660 ScrollToMakePositionVisible( cursorInfo.primaryPosition, cursorInfo.lineHeight );
2663 void Controller::Impl::RequestRelayout()
2665 mControlInterface.RequestTextRelayout();
2670 } // namespace Toolkit