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 // The number of quads of the selection box.
1792 const unsigned int numberOfQuads = 1u + ( glyphEnd - glyphStart ) + ( ( numberOfLines > 1u ) ? 2u * numberOfLines : 0u );
1793 mEventData->mDecorator->ResizeHighlightQuads( numberOfQuads );
1795 // Count the actual number of quads.
1796 unsigned int actualNumberOfQuads = 0u;
1799 // Traverse the glyphs.
1800 for( GlyphIndex index = glyphStart; index <= glyphEnd; ++index )
1802 const GlyphInfo& glyph = *( glyphsBuffer + index );
1803 const Vector2& position = *( positionsBuffer + index );
1805 if( splitStartGlyph )
1807 // 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.
1809 const float glyphAdvance = glyph.advance / static_cast<float>( numberOfCharactersStart );
1810 const CharacterIndex interGlyphIndex = selectionStart - *( glyphToCharacterBuffer + glyphStart );
1811 // Get the direction of the character.
1812 CharacterDirection isCurrentRightToLeft = false;
1813 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
1815 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + selectionStart );
1818 // The end point could be in the middle of the ligature.
1819 // Calculate the number of characters selected.
1820 const Length numberOfCharacters = ( glyphStart == glyphEnd ) ? ( selectionEnd - selectionStart ) : ( numberOfCharactersStart - interGlyphIndex );
1822 quad.x = lineRun->alignmentOffset + position.x - glyph.xBearing + mScrollPosition.x + glyphAdvance * static_cast<float>( isCurrentRightToLeft ? ( numberOfCharactersStart - interGlyphIndex - numberOfCharacters ) : interGlyphIndex );
1823 quad.y = selectionBoxInfo->lineOffset;
1824 quad.z = quad.x + static_cast<float>( numberOfCharacters ) * glyphAdvance;
1825 quad.w = selectionBoxInfo->lineOffset + selectionBoxInfo->lineHeight;
1827 // Store the min and max 'x' for each line.
1828 selectionBoxInfo->minX = std::min( selectionBoxInfo->minX, quad.x );
1829 selectionBoxInfo->maxX = std::max( selectionBoxInfo->maxX, quad.z );
1831 mEventData->mDecorator->AddHighlight( actualNumberOfQuads, quad );
1832 ++actualNumberOfQuads;
1834 splitStartGlyph = false;
1838 if( splitEndGlyph && ( index == glyphEnd ) )
1840 // 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.
1842 const float glyphAdvance = glyph.advance / static_cast<float>( numberOfCharactersEnd );
1843 const CharacterIndex interGlyphIndex = selectionEnd - *( glyphToCharacterBuffer + glyphEnd );
1844 // Get the direction of the character.
1845 CharacterDirection isCurrentRightToLeft = false;
1846 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
1848 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + selectionEnd );
1851 const Length numberOfCharacters = numberOfCharactersEnd - interGlyphIndex;
1853 quad.x = lineRun->alignmentOffset + position.x - glyph.xBearing + mScrollPosition.x + ( isCurrentRightToLeft ? ( glyphAdvance * static_cast<float>( numberOfCharacters ) ) : 0.f );
1854 quad.y = selectionBoxInfo->lineOffset;
1855 quad.z = quad.x + static_cast<float>( interGlyphIndex ) * glyphAdvance;
1856 quad.w = quad.y + selectionBoxInfo->lineHeight;
1858 // Store the min and max 'x' for each line.
1859 selectionBoxInfo->minX = std::min( selectionBoxInfo->minX, quad.x );
1860 selectionBoxInfo->maxX = std::max( selectionBoxInfo->maxX, quad.z );
1862 mEventData->mDecorator->AddHighlight( actualNumberOfQuads,
1864 ++actualNumberOfQuads;
1866 splitEndGlyph = false;
1870 quad.x = lineRun->alignmentOffset + position.x - glyph.xBearing + mScrollPosition.x;
1871 quad.y = selectionBoxInfo->lineOffset;
1872 quad.z = quad.x + glyph.advance;
1873 quad.w = quad.y + selectionBoxInfo->lineHeight;
1875 // Store the min and max 'x' for each line.
1876 selectionBoxInfo->minX = std::min( selectionBoxInfo->minX, quad.x );
1877 selectionBoxInfo->maxX = std::max( selectionBoxInfo->maxX, quad.z );
1879 mEventData->mDecorator->AddHighlight( actualNumberOfQuads,
1881 ++actualNumberOfQuads;
1883 // Whether to retrieve the next line.
1884 if( index == lastGlyphOfLine )
1886 // Retrieve the next line.
1889 // Get the last glyph of the new line.
1890 lastGlyphOfLine = lineRun->glyphRun.glyphIndex + lineRun->glyphRun.numberOfGlyphs - 1u;
1893 if( lineIndex < firstLineIndex + numberOfLines )
1895 // Keep the offset and height of the current selection box.
1896 const float currentLineOffset = selectionBoxInfo->lineOffset;
1897 const float currentLineHeight = selectionBoxInfo->lineHeight;
1899 // Get the selection box info for the next line.
1902 selectionBoxInfo->minX = MAX_FLOAT;
1903 selectionBoxInfo->maxX = MIN_FLOAT;
1905 // Update the line's vertical offset.
1906 selectionBoxInfo->lineOffset = currentLineOffset + currentLineHeight;
1908 // The line height is the addition of the line ascender and the line descender.
1909 // However, the line descender has a negative value, hence the subtraction.
1910 selectionBoxInfo->lineHeight = lineRun->ascender - lineRun->descender;
1915 // Traverses all the lines and updates the min and max 'x' positions and the total height.
1916 // The final width is calculated after 'boxifying' the selection.
1917 for( Vector<SelectionBoxInfo>::ConstIterator it = selectionBoxLinesInfo.Begin(),
1918 endIt = selectionBoxLinesInfo.End();
1922 const SelectionBoxInfo& info = *it;
1924 // Update the size of the highlighted text.
1925 highLightSize.height += info.lineHeight;
1926 minHighlightX = std::min( minHighlightX, info.minX );
1927 maxHighlightX = std::max( maxHighlightX, info.maxX );
1930 // Add extra geometry to 'boxify' the selection.
1932 if( 1u < numberOfLines )
1934 // Boxify the first line.
1935 lineRun = mVisualModel->mLines.Begin() + firstLineIndex;
1936 const SelectionBoxInfo& firstSelectionBoxLineInfo = *( selectionBoxLinesInfo.Begin() );
1938 bool boxifyBegin = ( LTR != lineRun->direction ) && ( LTR != startDirection );
1939 bool boxifyEnd = ( LTR == lineRun->direction ) && ( LTR == startDirection );
1944 quad.y = firstSelectionBoxLineInfo.lineOffset;
1945 quad.z = firstSelectionBoxLineInfo.minX;
1946 quad.w = firstSelectionBoxLineInfo.lineOffset + firstSelectionBoxLineInfo.lineHeight;
1948 // Boxify at the beginning of the line.
1949 mEventData->mDecorator->AddHighlight( actualNumberOfQuads,
1951 ++actualNumberOfQuads;
1953 // Update the size of the highlighted text.
1954 minHighlightX = 0.f;
1959 quad.x = firstSelectionBoxLineInfo.maxX;
1960 quad.y = firstSelectionBoxLineInfo.lineOffset;
1961 quad.z = mVisualModel->mControlSize.width;
1962 quad.w = firstSelectionBoxLineInfo.lineOffset + firstSelectionBoxLineInfo.lineHeight;
1964 // Boxify at the end of the line.
1965 mEventData->mDecorator->AddHighlight( actualNumberOfQuads,
1967 ++actualNumberOfQuads;
1969 // Update the size of the highlighted text.
1970 maxHighlightX = mVisualModel->mControlSize.width;
1973 // Boxify the central lines.
1974 if( 2u < numberOfLines )
1976 for( Vector<SelectionBoxInfo>::ConstIterator it = selectionBoxLinesInfo.Begin() + 1u,
1977 endIt = selectionBoxLinesInfo.End() - 1u;
1981 const SelectionBoxInfo& info = *it;
1984 quad.y = info.lineOffset;
1986 quad.w = info.lineOffset + info.lineHeight;
1988 mEventData->mDecorator->AddHighlight( actualNumberOfQuads,
1990 ++actualNumberOfQuads;
1993 quad.y = info.lineOffset;
1994 quad.z = mVisualModel->mControlSize.width;
1995 quad.w = info.lineOffset + info.lineHeight;
1997 mEventData->mDecorator->AddHighlight( actualNumberOfQuads,
1999 ++actualNumberOfQuads;
2002 // Update the size of the highlighted text.
2003 minHighlightX = 0.f;
2004 maxHighlightX = mVisualModel->mControlSize.width;
2007 // Boxify the last line.
2008 lineRun = mVisualModel->mLines.Begin() + firstLineIndex + numberOfLines - 1u;
2009 const SelectionBoxInfo& lastSelectionBoxLineInfo = *( selectionBoxLinesInfo.End() - 1u );
2011 boxifyBegin = ( LTR == lineRun->direction ) && ( LTR == endDirection );
2012 boxifyEnd = ( LTR != lineRun->direction ) && ( LTR != endDirection );
2017 quad.y = lastSelectionBoxLineInfo.lineOffset;
2018 quad.z = lastSelectionBoxLineInfo.minX;
2019 quad.w = lastSelectionBoxLineInfo.lineOffset + lastSelectionBoxLineInfo.lineHeight;
2021 // Boxify at the beginning of the line.
2022 mEventData->mDecorator->AddHighlight( actualNumberOfQuads,
2024 ++actualNumberOfQuads;
2026 // Update the size of the highlighted text.
2027 minHighlightX = 0.f;
2032 quad.x = lastSelectionBoxLineInfo.maxX;
2033 quad.y = lastSelectionBoxLineInfo.lineOffset;
2034 quad.z = mVisualModel->mControlSize.width;
2035 quad.w = lastSelectionBoxLineInfo.lineOffset + lastSelectionBoxLineInfo.lineHeight;
2037 // Boxify at the end of the line.
2038 mEventData->mDecorator->AddHighlight( actualNumberOfQuads,
2040 ++actualNumberOfQuads;
2042 // Update the size of the highlighted text.
2043 maxHighlightX = mVisualModel->mControlSize.width;
2047 // Set the actual number of quads.
2048 mEventData->mDecorator->ResizeHighlightQuads( actualNumberOfQuads );
2050 // Sets the highlight's size and position. In decorator's coords.
2051 // The highlight's height has been calculated above (before 'boxifying' the highlight).
2052 highLightSize.width = maxHighlightX - minHighlightX;
2054 highLightPosition.x = minHighlightX;
2055 const SelectionBoxInfo& firstSelectionBoxLineInfo = *( selectionBoxLinesInfo.Begin() );
2056 highLightPosition.y = firstSelectionBoxLineInfo.lineOffset;
2058 mEventData->mDecorator->SetHighLightBox( highLightPosition, highLightSize );
2060 if( !mEventData->mDecorator->IsSmoothHandlePanEnabled() )
2062 CursorInfo primaryCursorInfo;
2063 GetCursorPosition( mEventData->mLeftSelectionPosition,
2064 primaryCursorInfo );
2066 const Vector2 primaryPosition = primaryCursorInfo.primaryPosition + mScrollPosition;
2068 mEventData->mDecorator->SetPosition( LEFT_SELECTION_HANDLE,
2070 primaryCursorInfo.lineOffset + mScrollPosition.y,
2071 primaryCursorInfo.lineHeight );
2073 CursorInfo secondaryCursorInfo;
2074 GetCursorPosition( mEventData->mRightSelectionPosition,
2075 secondaryCursorInfo );
2077 const Vector2 secondaryPosition = secondaryCursorInfo.primaryPosition + mScrollPosition;
2079 mEventData->mDecorator->SetPosition( RIGHT_SELECTION_HANDLE,
2080 secondaryPosition.x,
2081 secondaryCursorInfo.lineOffset + mScrollPosition.y,
2082 secondaryCursorInfo.lineHeight );
2085 // Cursor to be positioned at end of selection so if selection interrupted and edit mode restarted the cursor will be at end of selection
2086 mEventData->mPrimaryCursorPosition = ( indicesSwapped ) ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition;
2088 // Set the flag to update the decorator.
2089 mEventData->mDecoratorUpdated = true;
2092 void Controller::Impl::RepositionSelectionHandles( float visualX, float visualY )
2094 if( NULL == mEventData )
2096 // Nothing to do if there is no text input.
2100 if( IsShowingPlaceholderText() )
2102 // Nothing to do if there is the place-holder text.
2106 const Length numberOfGlyphs = mVisualModel->mGlyphs.Count();
2107 const Length numberOfLines = mVisualModel->mLines.Count();
2108 if( ( 0 == numberOfGlyphs ) ||
2109 ( 0 == numberOfLines ) )
2111 // Nothing to do if there is no text.
2115 // Find which word was selected
2116 CharacterIndex selectionStart( 0 );
2117 CharacterIndex selectionEnd( 0 );
2118 const bool indicesFound = FindSelectionIndices( mVisualModel,
2125 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "%p selectionStart %d selectionEnd %d\n", this, selectionStart, selectionEnd );
2129 ChangeState( EventData::SELECTING );
2131 mEventData->mLeftSelectionPosition = selectionStart;
2132 mEventData->mRightSelectionPosition = selectionEnd;
2134 mEventData->mUpdateLeftSelectionPosition = true;
2135 mEventData->mUpdateRightSelectionPosition = true;
2136 mEventData->mUpdateHighlightBox = true;
2138 mEventData->mScrollAfterUpdatePosition = ( mEventData->mLeftSelectionPosition != mEventData->mRightSelectionPosition );
2142 // Nothing to select. i.e. a white space, out of bounds
2143 ChangeState( EventData::EDITING );
2145 mEventData->mPrimaryCursorPosition = selectionEnd;
2147 mEventData->mUpdateCursorPosition = true;
2148 mEventData->mUpdateGrabHandlePosition = true;
2149 mEventData->mScrollAfterUpdatePosition = true;
2150 mEventData->mUpdateInputStyle = true;
2154 void Controller::Impl::SetPopupButtons()
2157 * Sets the Popup buttons to be shown depending on State.
2159 * If SELECTING : CUT & COPY + ( PASTE & CLIPBOARD if content available to paste )
2161 * If EDITING_WITH_POPUP : SELECT & SELECT_ALL
2164 TextSelectionPopup::Buttons buttonsToShow = TextSelectionPopup::NONE;
2166 if( EventData::SELECTING == mEventData->mState )
2168 buttonsToShow = TextSelectionPopup::Buttons( TextSelectionPopup::CUT | TextSelectionPopup::COPY );
2170 if( !IsClipboardEmpty() )
2172 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
2173 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::CLIPBOARD ) );
2176 if( !mEventData->mAllTextSelected )
2178 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::SELECT_ALL ) );
2181 else if( EventData::EDITING_WITH_POPUP == mEventData->mState )
2183 if( mLogicalModel->mText.Count() && !IsShowingPlaceholderText() )
2185 buttonsToShow = TextSelectionPopup::Buttons( TextSelectionPopup::SELECT | TextSelectionPopup::SELECT_ALL );
2188 if( !IsClipboardEmpty() )
2190 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
2191 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::CLIPBOARD ) );
2194 else if( EventData::EDITING_WITH_PASTE_POPUP == mEventData->mState )
2196 if ( !IsClipboardEmpty() )
2198 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
2199 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::CLIPBOARD ) );
2203 mEventData->mDecorator->SetEnabledPopupButtons( buttonsToShow );
2206 void Controller::Impl::ChangeState( EventData::State newState )
2208 if( NULL == mEventData )
2210 // Nothing to do if there is no text input.
2214 DALI_LOG_INFO( gLogFilter, Debug::General, "ChangeState state:%d newstate:%d\n", mEventData->mState, newState );
2216 if( mEventData->mState != newState )
2218 mEventData->mPreviousState = mEventData->mState;
2219 mEventData->mState = newState;
2221 switch( mEventData->mState )
2223 case EventData::INACTIVE:
2225 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
2226 mEventData->mDecorator->StopCursorBlink();
2227 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
2228 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
2229 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
2230 mEventData->mDecorator->SetHighlightActive( false );
2231 mEventData->mDecorator->SetPopupActive( false );
2232 mEventData->mDecoratorUpdated = true;
2236 case EventData::INTERRUPTED:
2238 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
2239 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
2240 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
2241 mEventData->mDecorator->SetHighlightActive( false );
2242 mEventData->mDecorator->SetPopupActive( false );
2243 mEventData->mDecoratorUpdated = true;
2247 case EventData::SELECTING:
2249 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
2250 mEventData->mDecorator->StopCursorBlink();
2251 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
2252 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true );
2253 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true );
2254 mEventData->mDecorator->SetHighlightActive( true );
2255 if( mEventData->mGrabHandlePopupEnabled )
2258 mEventData->mDecorator->SetPopupActive( true );
2260 mEventData->mDecoratorUpdated = true;
2263 case EventData::EDITING:
2265 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
2266 if( mEventData->mCursorBlinkEnabled )
2268 mEventData->mDecorator->StartCursorBlink();
2270 // Grab handle is not shown until a tap is received whilst EDITING
2271 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
2272 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
2273 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
2274 mEventData->mDecorator->SetHighlightActive( false );
2275 if( mEventData->mGrabHandlePopupEnabled )
2277 mEventData->mDecorator->SetPopupActive( false );
2279 mEventData->mDecoratorUpdated = true;
2283 case EventData::EDITING_WITH_POPUP:
2285 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "EDITING_WITH_POPUP \n", newState );
2287 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
2288 if( mEventData->mCursorBlinkEnabled )
2290 mEventData->mDecorator->StartCursorBlink();
2292 if( mEventData->mSelectionEnabled )
2294 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
2295 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
2296 mEventData->mDecorator->SetHighlightActive( false );
2300 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
2302 if( mEventData->mGrabHandlePopupEnabled )
2305 mEventData->mDecorator->SetPopupActive( true );
2308 mEventData->mDecoratorUpdated = true;
2311 case EventData::EDITING_WITH_GRAB_HANDLE:
2313 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "EDITING_WITH_GRAB_HANDLE \n", newState );
2315 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
2316 if( mEventData->mCursorBlinkEnabled )
2318 mEventData->mDecorator->StartCursorBlink();
2320 // Grab handle is not shown until a tap is received whilst EDITING
2321 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
2322 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
2323 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
2324 mEventData->mDecorator->SetHighlightActive( false );
2325 if( mEventData->mGrabHandlePopupEnabled )
2327 mEventData->mDecorator->SetPopupActive( false );
2329 mEventData->mDecoratorUpdated = true;
2333 case EventData::SELECTION_HANDLE_PANNING:
2335 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
2336 mEventData->mDecorator->StopCursorBlink();
2337 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
2338 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true );
2339 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true );
2340 mEventData->mDecorator->SetHighlightActive( true );
2341 if( mEventData->mGrabHandlePopupEnabled )
2343 mEventData->mDecorator->SetPopupActive( false );
2345 mEventData->mDecoratorUpdated = true;
2348 case EventData::GRAB_HANDLE_PANNING:
2350 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "GRAB_HANDLE_PANNING \n", newState );
2352 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
2353 if( mEventData->mCursorBlinkEnabled )
2355 mEventData->mDecorator->StartCursorBlink();
2357 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
2358 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
2359 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
2360 mEventData->mDecorator->SetHighlightActive( false );
2361 if( mEventData->mGrabHandlePopupEnabled )
2363 mEventData->mDecorator->SetPopupActive( false );
2365 mEventData->mDecoratorUpdated = true;
2368 case EventData::EDITING_WITH_PASTE_POPUP:
2370 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "EDITING_WITH_PASTE_POPUP \n", newState );
2372 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
2373 if( mEventData->mCursorBlinkEnabled )
2375 mEventData->mDecorator->StartCursorBlink();
2378 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
2379 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
2380 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
2381 mEventData->mDecorator->SetHighlightActive( false );
2383 if( mEventData->mGrabHandlePopupEnabled )
2386 mEventData->mDecorator->SetPopupActive( true );
2389 mEventData->mDecoratorUpdated = true;
2392 case EventData::TEXT_PANNING:
2394 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
2395 mEventData->mDecorator->StopCursorBlink();
2396 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
2397 if( mEventData->mDecorator->IsHandleActive( LEFT_SELECTION_HANDLE ) ||
2398 mEventData->mDecorator->IsHandleActive( RIGHT_SELECTION_HANDLE ) )
2400 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
2401 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
2402 mEventData->mDecorator->SetHighlightActive( true );
2405 if( mEventData->mGrabHandlePopupEnabled )
2407 mEventData->mDecorator->SetPopupActive( false );
2410 mEventData->mDecoratorUpdated = true;
2417 void Controller::Impl::GetCursorPosition( CharacterIndex logical,
2418 CursorInfo& cursorInfo )
2420 if( !IsShowingRealText() )
2422 // Do not want to use the place-holder text to set the cursor position.
2424 // Use the line's height of the font's family set to set the cursor's size.
2425 // If there is no font's family set, use the default font.
2426 // Use the current alignment to place the cursor at the beginning, center or end of the box.
2428 cursorInfo.lineOffset = 0.f;
2429 cursorInfo.lineHeight = GetDefaultFontLineHeight();
2430 cursorInfo.primaryCursorHeight = cursorInfo.lineHeight;
2432 switch( mLayoutEngine.GetHorizontalAlignment() )
2434 case LayoutEngine::HORIZONTAL_ALIGN_BEGIN:
2436 cursorInfo.primaryPosition.x = 0.f;
2439 case LayoutEngine::HORIZONTAL_ALIGN_CENTER:
2441 cursorInfo.primaryPosition.x = floorf( 0.5f * mVisualModel->mControlSize.width );
2444 case LayoutEngine::HORIZONTAL_ALIGN_END:
2446 cursorInfo.primaryPosition.x = mVisualModel->mControlSize.width - mEventData->mDecorator->GetCursorWidth();
2451 // Nothing else to do.
2455 Text::GetCursorPosition( mVisualModel,
2461 if( LayoutEngine::MULTI_LINE_BOX == mLayoutEngine.GetLayout() )
2463 // If the text is editable and multi-line, the cursor position after a white space shouldn't exceed the boundaries of the text control.
2465 // Note the white spaces laid-out at the end of the line might exceed the boundaries of the control.
2466 // The reason is a wrapped line must not start with a white space so they are laid-out at the end of the line.
2468 if( 0.f > cursorInfo.primaryPosition.x )
2470 cursorInfo.primaryPosition.x = 0.f;
2473 const float edgeWidth = mVisualModel->mControlSize.width - static_cast<float>( mEventData->mDecorator->GetCursorWidth() );
2474 if( cursorInfo.primaryPosition.x > edgeWidth )
2476 cursorInfo.primaryPosition.x = edgeWidth;
2481 CharacterIndex Controller::Impl::CalculateNewCursorIndex( CharacterIndex index ) const
2483 if( NULL == mEventData )
2485 // Nothing to do if there is no text input.
2489 CharacterIndex cursorIndex = mEventData->mPrimaryCursorPosition;
2491 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
2492 const Length* const charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
2494 GlyphIndex glyphIndex = *( charactersToGlyphBuffer + index );
2495 Length numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
2497 if( numberOfCharacters > 1u )
2499 const Script script = mLogicalModel->GetScript( index );
2500 if( HasLigatureMustBreak( script ) )
2502 // Prevents to jump the whole Latin ligatures like fi, ff, or Arabic ï»», ...
2503 numberOfCharacters = 1u;
2508 while( 0u == numberOfCharacters )
2511 numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
2515 if( index < mEventData->mPrimaryCursorPosition )
2517 cursorIndex -= numberOfCharacters;
2521 cursorIndex += numberOfCharacters;
2524 // Will update the cursor hook position.
2525 mEventData->mUpdateCursorHookPosition = true;
2530 void Controller::Impl::UpdateCursorPosition( const CursorInfo& cursorInfo )
2532 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "-->Controller::UpdateCursorPosition %p\n", this );
2533 if( NULL == mEventData )
2535 // Nothing to do if there is no text input.
2536 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition no event data\n" );
2540 const Vector2 cursorPosition = cursorInfo.primaryPosition + mScrollPosition;
2542 // Sets the cursor position.
2543 mEventData->mDecorator->SetPosition( PRIMARY_CURSOR,
2546 cursorInfo.primaryCursorHeight,
2547 cursorInfo.lineHeight );
2548 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Primary cursor position: %f,%f\n", cursorPosition.x, cursorPosition.y );
2550 if( mEventData->mUpdateGrabHandlePosition )
2552 // Sets the grab handle position.
2553 mEventData->mDecorator->SetPosition( GRAB_HANDLE,
2555 cursorInfo.lineOffset + mScrollPosition.y,
2556 cursorInfo.lineHeight );
2559 if( cursorInfo.isSecondaryCursor )
2561 mEventData->mDecorator->SetPosition( SECONDARY_CURSOR,
2562 cursorInfo.secondaryPosition.x + mScrollPosition.x,
2563 cursorInfo.secondaryPosition.y + mScrollPosition.y,
2564 cursorInfo.secondaryCursorHeight,
2565 cursorInfo.lineHeight );
2566 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Secondary cursor position: %f,%f\n", cursorInfo.secondaryPosition.x + mScrollPosition.x, cursorInfo.secondaryPosition.y + mScrollPosition.y );
2569 // Set which cursors are active according the state.
2570 if( EventData::IsEditingState( mEventData->mState ) || ( EventData::GRAB_HANDLE_PANNING == mEventData->mState ) )
2572 if( cursorInfo.isSecondaryCursor )
2574 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_BOTH );
2578 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
2583 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
2586 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition\n" );
2589 void Controller::Impl::UpdateSelectionHandle( HandleType handleType,
2590 const CursorInfo& cursorInfo )
2592 if( ( LEFT_SELECTION_HANDLE != handleType ) &&
2593 ( RIGHT_SELECTION_HANDLE != handleType ) )
2598 const Vector2 cursorPosition = cursorInfo.primaryPosition + mScrollPosition;
2600 // Sets the handle's position.
2601 mEventData->mDecorator->SetPosition( handleType,
2603 cursorInfo.lineOffset + mScrollPosition.y,
2604 cursorInfo.lineHeight );
2606 // If selection handle at start of the text and other at end of the text then all text is selected.
2607 const CharacterIndex startOfSelection = std::min( mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition );
2608 const CharacterIndex endOfSelection = std::max ( mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition );
2609 mEventData->mAllTextSelected = ( startOfSelection == 0 ) && ( endOfSelection == mLogicalModel->mText.Count() );
2612 void Controller::Impl::ClampHorizontalScroll( const Vector2& layoutSize )
2614 // Clamp between -space & 0.
2616 if( layoutSize.width > mVisualModel->mControlSize.width )
2618 const float space = ( layoutSize.width - mVisualModel->mControlSize.width );
2619 mScrollPosition.x = ( mScrollPosition.x < -space ) ? -space : mScrollPosition.x;
2620 mScrollPosition.x = ( mScrollPosition.x > 0.f ) ? 0.f : mScrollPosition.x;
2622 mEventData->mDecoratorUpdated = true;
2626 mScrollPosition.x = 0.f;
2630 void Controller::Impl::ClampVerticalScroll( const Vector2& layoutSize )
2632 // Clamp between -space & 0.
2633 if( layoutSize.height > mVisualModel->mControlSize.height )
2635 const float space = ( layoutSize.height - mVisualModel->mControlSize.height );
2636 mScrollPosition.y = ( mScrollPosition.y < -space ) ? -space : mScrollPosition.y;
2637 mScrollPosition.y = ( mScrollPosition.y > 0.f ) ? 0.f : mScrollPosition.y;
2639 mEventData->mDecoratorUpdated = true;
2643 mScrollPosition.y = 0.f;
2647 void Controller::Impl::ScrollToMakePositionVisible( const Vector2& position, float lineHeight )
2649 const float cursorWidth = mEventData->mDecorator ? static_cast<float>( mEventData->mDecorator->GetCursorWidth() ) : 0.f;
2651 // position is in actor's coords.
2652 const float positionEndX = position.x + cursorWidth;
2653 const float positionEndY = position.y + lineHeight;
2655 // Transform the position to decorator coords.
2656 const float decoratorPositionBeginX = position.x + mScrollPosition.x;
2657 const float decoratorPositionEndX = positionEndX + mScrollPosition.x;
2659 const float decoratorPositionBeginY = position.y + mScrollPosition.y;
2660 const float decoratorPositionEndY = positionEndY + mScrollPosition.y;
2662 if( decoratorPositionBeginX < 0.f )
2664 mScrollPosition.x = -position.x;
2666 else if( decoratorPositionEndX > mVisualModel->mControlSize.width )
2668 mScrollPosition.x = mVisualModel->mControlSize.width - positionEndX;
2671 if( decoratorPositionBeginY < 0.f )
2673 mScrollPosition.y = -position.y;
2675 else if( decoratorPositionEndY > mVisualModel->mControlSize.height )
2677 mScrollPosition.y = mVisualModel->mControlSize.height - positionEndY;
2681 void Controller::Impl::ScrollTextToMatchCursor( const CursorInfo& cursorInfo )
2683 // Get the current cursor position in decorator coords.
2684 const Vector2& currentCursorPosition = mEventData->mDecorator->GetPosition( PRIMARY_CURSOR );
2686 // Calculate the offset to match the cursor position before the character was deleted.
2687 mScrollPosition.x = currentCursorPosition.x - cursorInfo.primaryPosition.x;
2688 mScrollPosition.y = currentCursorPosition.y - cursorInfo.lineOffset;
2690 ClampHorizontalScroll( mVisualModel->GetLayoutSize() );
2691 ClampVerticalScroll( mVisualModel->GetLayoutSize() );
2693 // Makes the new cursor position visible if needed.
2694 ScrollToMakePositionVisible( cursorInfo.primaryPosition, cursorInfo.lineHeight );
2697 void Controller::Impl::RequestRelayout()
2699 mControlInterface.RequestTextRelayout();
2704 } // namespace Toolkit