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 void Controller::Impl::NotifyImfMultiLineStatus()
314 LayoutEngine::Layout layout = mLayoutEngine.GetLayout();
315 mEventData->mImfManager.NotifyTextInputMultiLine( layout == LayoutEngine::MULTI_LINE_BOX );
319 CharacterIndex Controller::Impl::GetLogicalCursorPosition() const
321 CharacterIndex cursorPosition = 0u;
325 if( ( EventData::SELECTING == mEventData->mState ) ||
326 ( EventData::SELECTION_HANDLE_PANNING == mEventData->mState ) )
328 cursorPosition = std::min( mEventData->mRightSelectionPosition, mEventData->mLeftSelectionPosition );
332 cursorPosition = mEventData->mPrimaryCursorPosition;
336 return cursorPosition;
339 Length Controller::Impl::GetNumberOfWhiteSpaces( CharacterIndex index ) const
341 Length numberOfWhiteSpaces = 0u;
343 // Get the buffer to the text.
344 Character* utf32CharacterBuffer = mLogicalModel->mText.Begin();
346 const Length totalNumberOfCharacters = mLogicalModel->mText.Count();
347 for( ; index < totalNumberOfCharacters; ++index, ++numberOfWhiteSpaces )
349 if( !TextAbstraction::IsWhiteSpace( *( utf32CharacterBuffer + index ) ) )
355 return numberOfWhiteSpaces;
358 void Controller::Impl::GetText( CharacterIndex index, std::string& text ) const
360 // Get the total number of characters.
361 Length numberOfCharacters = mLogicalModel->mText.Count();
363 // Retrieve the text.
364 if( 0u != numberOfCharacters )
366 Utf32ToUtf8( mLogicalModel->mText.Begin() + index, numberOfCharacters - index, text );
370 void Controller::Impl::CalculateTextUpdateIndices( Length& numberOfCharacters )
372 mTextUpdateInfo.mParagraphCharacterIndex = 0u;
373 mTextUpdateInfo.mStartGlyphIndex = 0u;
374 mTextUpdateInfo.mStartLineIndex = 0u;
375 numberOfCharacters = 0u;
377 const Length numberOfParagraphs = mLogicalModel->mParagraphInfo.Count();
378 if( 0u == numberOfParagraphs )
380 mTextUpdateInfo.mParagraphCharacterIndex = 0u;
381 numberOfCharacters = 0u;
383 mTextUpdateInfo.mRequestedNumberOfCharacters = mTextUpdateInfo.mNumberOfCharactersToAdd - mTextUpdateInfo.mNumberOfCharactersToRemove;
385 // Nothing else to do if there are no paragraphs.
389 // Find the paragraphs to be updated.
390 Vector<ParagraphRunIndex> paragraphsToBeUpdated;
391 if( mTextUpdateInfo.mCharacterIndex >= mTextUpdateInfo.mPreviousNumberOfCharacters )
393 // Text is being added at the end of the current text.
394 if( mTextUpdateInfo.mIsLastCharacterNewParagraph )
396 // Text is being added in a new paragraph after the last character of the text.
397 mTextUpdateInfo.mParagraphCharacterIndex = mTextUpdateInfo.mPreviousNumberOfCharacters;
398 numberOfCharacters = 0u;
399 mTextUpdateInfo.mRequestedNumberOfCharacters = mTextUpdateInfo.mNumberOfCharactersToAdd - mTextUpdateInfo.mNumberOfCharactersToRemove;
401 mTextUpdateInfo.mStartGlyphIndex = mVisualModel->mGlyphs.Count();
402 mTextUpdateInfo.mStartLineIndex = mVisualModel->mLines.Count() - 1u;
404 // Nothing else to do;
408 paragraphsToBeUpdated.PushBack( numberOfParagraphs - 1u );
412 Length numberOfCharactersToUpdate = 0u;
413 if( mTextUpdateInfo.mFullRelayoutNeeded )
415 numberOfCharactersToUpdate = mTextUpdateInfo.mPreviousNumberOfCharacters;
419 numberOfCharactersToUpdate = ( mTextUpdateInfo.mNumberOfCharactersToRemove > 0u ) ? mTextUpdateInfo.mNumberOfCharactersToRemove : 1u;
421 mLogicalModel->FindParagraphs( mTextUpdateInfo.mCharacterIndex,
422 numberOfCharactersToUpdate,
423 paragraphsToBeUpdated );
426 if( 0u != paragraphsToBeUpdated.Count() )
428 const ParagraphRunIndex firstParagraphIndex = *( paragraphsToBeUpdated.Begin() );
429 const ParagraphRun& firstParagraph = *( mLogicalModel->mParagraphInfo.Begin() + firstParagraphIndex );
430 mTextUpdateInfo.mParagraphCharacterIndex = firstParagraph.characterRun.characterIndex;
432 ParagraphRunIndex lastParagraphIndex = *( paragraphsToBeUpdated.End() - 1u );
433 const ParagraphRun& lastParagraph = *( mLogicalModel->mParagraphInfo.Begin() + lastParagraphIndex );
435 if( ( mTextUpdateInfo.mNumberOfCharactersToRemove > 0u ) && // Some character are removed.
436 ( lastParagraphIndex < numberOfParagraphs - 1u ) && // There is a next paragraph.
437 ( ( lastParagraph.characterRun.characterIndex + lastParagraph.characterRun.numberOfCharacters ) == // The last removed character is the new paragraph character.
438 ( mTextUpdateInfo.mCharacterIndex + mTextUpdateInfo.mNumberOfCharactersToRemove ) ) )
440 // The new paragraph character of the last updated paragraph has been removed so is going to be merged with the next one.
441 const ParagraphRun& lastParagraph = *( mLogicalModel->mParagraphInfo.Begin() + lastParagraphIndex + 1u );
443 numberOfCharacters = lastParagraph.characterRun.characterIndex + lastParagraph.characterRun.numberOfCharacters - mTextUpdateInfo.mParagraphCharacterIndex;
447 numberOfCharacters = lastParagraph.characterRun.characterIndex + lastParagraph.characterRun.numberOfCharacters - mTextUpdateInfo.mParagraphCharacterIndex;
451 mTextUpdateInfo.mRequestedNumberOfCharacters = numberOfCharacters + mTextUpdateInfo.mNumberOfCharactersToAdd - mTextUpdateInfo.mNumberOfCharactersToRemove;
452 mTextUpdateInfo.mStartGlyphIndex = *( mVisualModel->mCharactersToGlyph.Begin() + mTextUpdateInfo.mParagraphCharacterIndex );
455 void Controller::Impl::ClearFullModelData( OperationsMask operations )
457 if( NO_OPERATION != ( GET_LINE_BREAKS & operations ) )
459 mLogicalModel->mLineBreakInfo.Clear();
460 mLogicalModel->mParagraphInfo.Clear();
463 if( NO_OPERATION != ( GET_WORD_BREAKS & operations ) )
465 mLogicalModel->mLineBreakInfo.Clear();
468 if( NO_OPERATION != ( GET_SCRIPTS & operations ) )
470 mLogicalModel->mScriptRuns.Clear();
473 if( NO_OPERATION != ( VALIDATE_FONTS & operations ) )
475 mLogicalModel->mFontRuns.Clear();
478 if( 0u != mLogicalModel->mBidirectionalParagraphInfo.Count() )
480 if( NO_OPERATION != ( BIDI_INFO & operations ) )
482 mLogicalModel->mBidirectionalParagraphInfo.Clear();
483 mLogicalModel->mCharacterDirections.Clear();
486 if( NO_OPERATION != ( REORDER & operations ) )
488 // Free the allocated memory used to store the conversion table in the bidirectional line info run.
489 for( Vector<BidirectionalLineInfoRun>::Iterator it = mLogicalModel->mBidirectionalLineInfo.Begin(),
490 endIt = mLogicalModel->mBidirectionalLineInfo.End();
494 BidirectionalLineInfoRun& bidiLineInfo = *it;
496 free( bidiLineInfo.visualToLogicalMap );
497 bidiLineInfo.visualToLogicalMap = NULL;
499 mLogicalModel->mBidirectionalLineInfo.Clear();
503 if( NO_OPERATION != ( SHAPE_TEXT & operations ) )
505 mVisualModel->mGlyphs.Clear();
506 mVisualModel->mGlyphsToCharacters.Clear();
507 mVisualModel->mCharactersToGlyph.Clear();
508 mVisualModel->mCharactersPerGlyph.Clear();
509 mVisualModel->mGlyphsPerCharacter.Clear();
510 mVisualModel->mGlyphPositions.Clear();
513 if( NO_OPERATION != ( LAYOUT & operations ) )
515 mVisualModel->mLines.Clear();
518 if( NO_OPERATION != ( COLOR & operations ) )
520 mVisualModel->mColorIndices.Clear();
524 void Controller::Impl::ClearCharacterModelData( CharacterIndex startIndex, CharacterIndex endIndex, OperationsMask operations )
526 const CharacterIndex endIndexPlusOne = endIndex + 1u;
528 if( NO_OPERATION != ( GET_LINE_BREAKS & operations ) )
530 // Clear the line break info.
531 LineBreakInfo* lineBreakInfoBuffer = mLogicalModel->mLineBreakInfo.Begin();
533 mLogicalModel->mLineBreakInfo.Erase( lineBreakInfoBuffer + startIndex,
534 lineBreakInfoBuffer + endIndexPlusOne );
536 // Clear the paragraphs.
537 ClearCharacterRuns( startIndex,
539 mLogicalModel->mParagraphInfo );
542 if( NO_OPERATION != ( GET_WORD_BREAKS & operations ) )
544 // Clear the word break info.
545 WordBreakInfo* wordBreakInfoBuffer = mLogicalModel->mWordBreakInfo.Begin();
547 mLogicalModel->mWordBreakInfo.Erase( wordBreakInfoBuffer + startIndex,
548 wordBreakInfoBuffer + endIndexPlusOne );
551 if( NO_OPERATION != ( GET_SCRIPTS & operations ) )
553 // Clear the scripts.
554 ClearCharacterRuns( startIndex,
556 mLogicalModel->mScriptRuns );
559 if( NO_OPERATION != ( VALIDATE_FONTS & operations ) )
562 ClearCharacterRuns( startIndex,
564 mLogicalModel->mFontRuns );
567 if( 0u != mLogicalModel->mBidirectionalParagraphInfo.Count() )
569 if( NO_OPERATION != ( BIDI_INFO & operations ) )
571 // Clear the bidirectional paragraph info.
572 ClearCharacterRuns( startIndex,
574 mLogicalModel->mBidirectionalParagraphInfo );
576 // Clear the character's directions.
577 CharacterDirection* characterDirectionsBuffer = mLogicalModel->mCharacterDirections.Begin();
579 mLogicalModel->mCharacterDirections.Erase( characterDirectionsBuffer + startIndex,
580 characterDirectionsBuffer + endIndexPlusOne );
583 if( NO_OPERATION != ( REORDER & operations ) )
585 uint32_t startRemoveIndex = mLogicalModel->mBidirectionalLineInfo.Count();
586 uint32_t endRemoveIndex = startRemoveIndex;
587 ClearCharacterRuns( startIndex,
589 mLogicalModel->mBidirectionalLineInfo,
593 BidirectionalLineInfoRun* bidirectionalLineInfoBuffer = mLogicalModel->mBidirectionalLineInfo.Begin();
595 // Free the allocated memory used to store the conversion table in the bidirectional line info run.
596 for( Vector<BidirectionalLineInfoRun>::Iterator it = bidirectionalLineInfoBuffer + startRemoveIndex,
597 endIt = bidirectionalLineInfoBuffer + endRemoveIndex;
601 BidirectionalLineInfoRun& bidiLineInfo = *it;
603 free( bidiLineInfo.visualToLogicalMap );
604 bidiLineInfo.visualToLogicalMap = NULL;
607 mLogicalModel->mBidirectionalLineInfo.Erase( bidirectionalLineInfoBuffer + startRemoveIndex,
608 bidirectionalLineInfoBuffer + endRemoveIndex );
613 void Controller::Impl::ClearGlyphModelData( CharacterIndex startIndex, CharacterIndex endIndex, OperationsMask operations )
615 const CharacterIndex endIndexPlusOne = endIndex + 1u;
616 const Length numberOfCharactersRemoved = endIndexPlusOne - startIndex;
618 // Convert the character index to glyph index before deleting the character to glyph and the glyphs per character buffers.
619 GlyphIndex* charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
620 Length* glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
622 const GlyphIndex endGlyphIndexPlusOne = *( charactersToGlyphBuffer + endIndex ) + *( glyphsPerCharacterBuffer + endIndex );
623 const Length numberOfGlyphsRemoved = endGlyphIndexPlusOne - mTextUpdateInfo.mStartGlyphIndex;
625 if( NO_OPERATION != ( SHAPE_TEXT & operations ) )
627 // Update the character to glyph indices.
628 for( Vector<GlyphIndex>::Iterator it = charactersToGlyphBuffer + endIndexPlusOne,
629 endIt = charactersToGlyphBuffer + mVisualModel->mCharactersToGlyph.Count();
633 CharacterIndex& index = *it;
634 index -= numberOfGlyphsRemoved;
637 // Clear the character to glyph conversion table.
638 mVisualModel->mCharactersToGlyph.Erase( charactersToGlyphBuffer + startIndex,
639 charactersToGlyphBuffer + endIndexPlusOne );
641 // Clear the glyphs per character table.
642 mVisualModel->mGlyphsPerCharacter.Erase( glyphsPerCharacterBuffer + startIndex,
643 glyphsPerCharacterBuffer + endIndexPlusOne );
645 // Clear the glyphs buffer.
646 GlyphInfo* glyphsBuffer = mVisualModel->mGlyphs.Begin();
647 mVisualModel->mGlyphs.Erase( glyphsBuffer + mTextUpdateInfo.mStartGlyphIndex,
648 glyphsBuffer + endGlyphIndexPlusOne );
650 CharacterIndex* glyphsToCharactersBuffer = mVisualModel->mGlyphsToCharacters.Begin();
652 // Update the glyph to character indices.
653 for( Vector<CharacterIndex>::Iterator it = glyphsToCharactersBuffer + endGlyphIndexPlusOne,
654 endIt = glyphsToCharactersBuffer + mVisualModel->mGlyphsToCharacters.Count();
658 CharacterIndex& index = *it;
659 index -= numberOfCharactersRemoved;
662 // Clear the glyphs to characters buffer.
663 mVisualModel->mGlyphsToCharacters.Erase( glyphsToCharactersBuffer + mTextUpdateInfo.mStartGlyphIndex,
664 glyphsToCharactersBuffer + endGlyphIndexPlusOne );
666 // Clear the characters per glyph buffer.
667 Length* charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
668 mVisualModel->mCharactersPerGlyph.Erase( charactersPerGlyphBuffer + mTextUpdateInfo.mStartGlyphIndex,
669 charactersPerGlyphBuffer + endGlyphIndexPlusOne );
671 // Clear the positions buffer.
672 Vector2* positionsBuffer = mVisualModel->mGlyphPositions.Begin();
673 mVisualModel->mGlyphPositions.Erase( positionsBuffer + mTextUpdateInfo.mStartGlyphIndex,
674 positionsBuffer + endGlyphIndexPlusOne );
677 if( NO_OPERATION != ( LAYOUT & operations ) )
680 uint32_t startRemoveIndex = mVisualModel->mLines.Count();
681 uint32_t endRemoveIndex = startRemoveIndex;
682 ClearCharacterRuns( startIndex,
684 mVisualModel->mLines,
688 // Will update the glyph runs.
689 startRemoveIndex = mVisualModel->mLines.Count();
690 endRemoveIndex = startRemoveIndex;
691 ClearGlyphRuns( mTextUpdateInfo.mStartGlyphIndex,
692 endGlyphIndexPlusOne - 1u,
693 mVisualModel->mLines,
697 // Set the line index from where to insert the new laid-out lines.
698 mTextUpdateInfo.mStartLineIndex = startRemoveIndex;
700 LineRun* linesBuffer = mVisualModel->mLines.Begin();
701 mVisualModel->mLines.Erase( linesBuffer + startRemoveIndex,
702 linesBuffer + endRemoveIndex );
705 if( NO_OPERATION != ( COLOR & operations ) )
707 if( 0u != mVisualModel->mColorIndices.Count() )
709 ColorIndex* colorIndexBuffer = mVisualModel->mColorIndices.Begin();
710 mVisualModel->mColorIndices.Erase( colorIndexBuffer + mTextUpdateInfo.mStartGlyphIndex,
711 colorIndexBuffer + endGlyphIndexPlusOne );
716 void Controller::Impl::ClearModelData( CharacterIndex startIndex, CharacterIndex endIndex, OperationsMask operations )
718 if( mTextUpdateInfo.mClearAll ||
719 ( ( 0u == startIndex ) &&
720 ( mTextUpdateInfo.mPreviousNumberOfCharacters == endIndex + 1u ) ) )
722 ClearFullModelData( operations );
726 // Clear the model data related with characters.
727 ClearCharacterModelData( startIndex, endIndex, operations );
729 // Clear the model data related with glyphs.
730 ClearGlyphModelData( startIndex, endIndex, operations );
733 // The estimated number of lines. Used to avoid reallocations when layouting.
734 mTextUpdateInfo.mEstimatedNumberOfLines = std::max( mVisualModel->mLines.Count(), mLogicalModel->mParagraphInfo.Count() );
736 mVisualModel->ClearCaches();
739 bool Controller::Impl::UpdateModel( OperationsMask operationsRequired )
741 DALI_LOG_INFO( gLogFilter, Debug::General, "Controller::UpdateModel\n" );
743 // Calculate the operations to be done.
744 const OperationsMask operations = static_cast<OperationsMask>( mOperationsPending & operationsRequired );
746 if( NO_OPERATION == operations )
748 // Nothing to do if no operations are pending and required.
752 Vector<Character>& utf32Characters = mLogicalModel->mText;
754 const Length numberOfCharacters = utf32Characters.Count();
756 // Index to the first character of the first paragraph to be updated.
757 CharacterIndex startIndex = 0u;
758 // Number of characters of the paragraphs to be removed.
759 Length paragraphCharacters = 0u;
761 CalculateTextUpdateIndices( paragraphCharacters );
762 startIndex = mTextUpdateInfo.mParagraphCharacterIndex;
764 if( mTextUpdateInfo.mClearAll ||
765 ( 0u != paragraphCharacters ) )
767 ClearModelData( startIndex, startIndex + ( ( paragraphCharacters > 0u ) ? paragraphCharacters - 1u : 0u ), operations );
770 mTextUpdateInfo.mClearAll = false;
772 // Whether the model is updated.
773 bool updated = false;
775 Vector<LineBreakInfo>& lineBreakInfo = mLogicalModel->mLineBreakInfo;
776 const Length requestedNumberOfCharacters = mTextUpdateInfo.mRequestedNumberOfCharacters;
778 if( NO_OPERATION != ( GET_LINE_BREAKS & operations ) )
780 // Retrieves the line break info. The line break info is used to split the text in 'paragraphs' to
781 // calculate the bidirectional info for each 'paragraph'.
782 // It's also used to layout the text (where it should be a new line) or to shape the text (text in different lines
783 // is not shaped together).
784 lineBreakInfo.Resize( numberOfCharacters, TextAbstraction::LINE_NO_BREAK );
786 SetLineBreakInfo( utf32Characters,
788 requestedNumberOfCharacters,
791 // Create the paragraph info.
792 mLogicalModel->CreateParagraphInfo( startIndex,
793 requestedNumberOfCharacters );
797 Vector<WordBreakInfo>& wordBreakInfo = mLogicalModel->mWordBreakInfo;
798 if( NO_OPERATION != ( GET_WORD_BREAKS & operations ) )
800 // Retrieves the word break info. The word break info is used to layout the text (where to wrap the text in lines).
801 wordBreakInfo.Resize( numberOfCharacters, TextAbstraction::WORD_NO_BREAK );
803 SetWordBreakInfo( utf32Characters,
805 requestedNumberOfCharacters,
810 const bool getScripts = NO_OPERATION != ( GET_SCRIPTS & operations );
811 const bool validateFonts = NO_OPERATION != ( VALIDATE_FONTS & operations );
813 Vector<ScriptRun>& scripts = mLogicalModel->mScriptRuns;
814 Vector<FontRun>& validFonts = mLogicalModel->mFontRuns;
816 if( getScripts || validateFonts )
818 // Validates the fonts assigned by the application or assigns default ones.
819 // It makes sure all the characters are going to be rendered by the correct font.
820 MultilanguageSupport multilanguageSupport = MultilanguageSupport::Get();
824 // Retrieves the scripts used in the text.
825 multilanguageSupport.SetScripts( utf32Characters,
827 requestedNumberOfCharacters,
833 // Validate the fonts set through the mark-up string.
834 Vector<FontDescriptionRun>& fontDescriptionRuns = mLogicalModel->mFontDescriptionRuns;
836 // Get the default font id.
837 const FontId defaultFontId = ( NULL == mFontDefaults ) ? 0u : mFontDefaults->GetFontId( mFontClient );
839 // Validates the fonts. If there is a character with no assigned font it sets a default one.
840 // After this call, fonts are validated.
841 multilanguageSupport.ValidateFonts( utf32Characters,
846 requestedNumberOfCharacters,
852 Vector<Character> mirroredUtf32Characters;
853 bool textMirrored = false;
854 const Length numberOfParagraphs = mLogicalModel->mParagraphInfo.Count();
855 if( NO_OPERATION != ( BIDI_INFO & operations ) )
857 Vector<BidirectionalParagraphInfoRun>& bidirectionalInfo = mLogicalModel->mBidirectionalParagraphInfo;
858 bidirectionalInfo.Reserve( numberOfParagraphs );
860 // Calculates the bidirectional info for the whole paragraph if it contains right to left scripts.
861 SetBidirectionalInfo( utf32Characters,
865 requestedNumberOfCharacters,
868 if( 0u != bidirectionalInfo.Count() )
870 // Only set the character directions if there is right to left characters.
871 Vector<CharacterDirection>& directions = mLogicalModel->mCharacterDirections;
872 GetCharactersDirection( bidirectionalInfo,
875 requestedNumberOfCharacters,
878 // This paragraph has right to left text. Some characters may need to be mirrored.
879 // TODO: consider if the mirrored string can be stored as well.
881 textMirrored = GetMirroredText( utf32Characters,
885 requestedNumberOfCharacters,
886 mirroredUtf32Characters );
890 // There is no right to left characters. Clear the directions vector.
891 mLogicalModel->mCharacterDirections.Clear();
896 Vector<GlyphInfo>& glyphs = mVisualModel->mGlyphs;
897 Vector<CharacterIndex>& glyphsToCharactersMap = mVisualModel->mGlyphsToCharacters;
898 Vector<Length>& charactersPerGlyph = mVisualModel->mCharactersPerGlyph;
899 Vector<GlyphIndex> newParagraphGlyphs;
900 newParagraphGlyphs.Reserve( numberOfParagraphs );
902 const Length currentNumberOfGlyphs = glyphs.Count();
903 if( NO_OPERATION != ( SHAPE_TEXT & operations ) )
905 const Vector<Character>& textToShape = textMirrored ? mirroredUtf32Characters : utf32Characters;
907 ShapeText( textToShape,
912 mTextUpdateInfo.mStartGlyphIndex,
913 requestedNumberOfCharacters,
915 glyphsToCharactersMap,
917 newParagraphGlyphs );
919 // Create the 'number of glyphs' per character and the glyph to character conversion tables.
920 mVisualModel->CreateGlyphsPerCharacterTable( startIndex, mTextUpdateInfo.mStartGlyphIndex, requestedNumberOfCharacters );
921 mVisualModel->CreateCharacterToGlyphTable( startIndex, mTextUpdateInfo.mStartGlyphIndex, requestedNumberOfCharacters );
925 const Length numberOfGlyphs = glyphs.Count() - currentNumberOfGlyphs;
927 if( NO_OPERATION != ( GET_GLYPH_METRICS & operations ) )
929 GlyphInfo* glyphsBuffer = glyphs.Begin();
930 mMetrics->GetGlyphMetrics( glyphsBuffer + mTextUpdateInfo.mStartGlyphIndex, numberOfGlyphs );
932 // Update the width and advance of all new paragraph characters.
933 for( Vector<GlyphIndex>::ConstIterator it = newParagraphGlyphs.Begin(), endIt = newParagraphGlyphs.End(); it != endIt; ++it )
935 const GlyphIndex index = *it;
936 GlyphInfo& glyph = *( glyphsBuffer + index );
938 glyph.xBearing = 0.f;
945 if( NO_OPERATION != ( COLOR & operations ) )
947 // Set the color runs in glyphs.
948 SetColorSegmentationInfo( mLogicalModel->mColorRuns,
949 mVisualModel->mCharactersToGlyph,
950 mVisualModel->mGlyphsPerCharacter,
952 mTextUpdateInfo.mStartGlyphIndex,
953 requestedNumberOfCharacters,
954 mVisualModel->mColors,
955 mVisualModel->mColorIndices );
960 if( ( NULL != mEventData ) &&
961 mEventData->mPreEditFlag &&
962 ( 0u != mVisualModel->mCharactersToGlyph.Count() ) )
964 // Add the underline for the pre-edit text.
965 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
966 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
968 const GlyphIndex glyphStart = *( charactersToGlyphBuffer + mEventData->mPreEditStartPosition );
969 const CharacterIndex lastPreEditCharacter = mEventData->mPreEditStartPosition + ( ( mEventData->mPreEditLength > 0u ) ? mEventData->mPreEditLength - 1u : 0u );
970 const Length numberOfGlyphsLastCharacter = *( glyphsPerCharacterBuffer + lastPreEditCharacter );
971 const GlyphIndex glyphEnd = *( charactersToGlyphBuffer + lastPreEditCharacter ) + ( numberOfGlyphsLastCharacter > 1u ? numberOfGlyphsLastCharacter - 1u : 0u );
973 GlyphRun underlineRun;
974 underlineRun.glyphIndex = glyphStart;
975 underlineRun.numberOfGlyphs = 1u + glyphEnd - glyphStart;
977 // TODO: At the moment the underline runs are only for pre-edit.
978 mVisualModel->mUnderlineRuns.PushBack( underlineRun );
981 // The estimated number of lines. Used to avoid reallocations when layouting.
982 mTextUpdateInfo.mEstimatedNumberOfLines = std::max( mVisualModel->mLines.Count(), mLogicalModel->mParagraphInfo.Count() );
984 // Set the previous number of characters for the next time the text is updated.
985 mTextUpdateInfo.mPreviousNumberOfCharacters = numberOfCharacters;
990 void Controller::Impl::RetrieveDefaultInputStyle( InputStyle& inputStyle )
992 // Sets the default text's color.
993 inputStyle.textColor = mTextColor;
994 inputStyle.isDefaultColor = true;
996 inputStyle.familyName.clear();
997 inputStyle.weight = TextAbstraction::FontWeight::NORMAL;
998 inputStyle.width = TextAbstraction::FontWidth::NORMAL;
999 inputStyle.slant = TextAbstraction::FontSlant::NORMAL;
1000 inputStyle.size = 0.f;
1002 inputStyle.familyDefined = false;
1003 inputStyle.weightDefined = false;
1004 inputStyle.widthDefined = false;
1005 inputStyle.slantDefined = false;
1006 inputStyle.sizeDefined = false;
1008 // Sets the default font's family name, weight, width, slant and size.
1011 if( mFontDefaults->familyDefined )
1013 inputStyle.familyName = mFontDefaults->mFontDescription.family;
1014 inputStyle.familyDefined = true;
1017 if( mFontDefaults->weightDefined )
1019 inputStyle.weight = mFontDefaults->mFontDescription.weight;
1020 inputStyle.weightDefined = true;
1023 if( mFontDefaults->widthDefined )
1025 inputStyle.width = mFontDefaults->mFontDescription.width;
1026 inputStyle.widthDefined = true;
1029 if( mFontDefaults->slantDefined )
1031 inputStyle.slant = mFontDefaults->mFontDescription.slant;
1032 inputStyle.slantDefined = true;
1035 if( mFontDefaults->sizeDefined )
1037 inputStyle.size = mFontDefaults->mDefaultPointSize;
1038 inputStyle.sizeDefined = true;
1043 float Controller::Impl::GetDefaultFontLineHeight()
1045 FontId defaultFontId = 0u;
1046 if( NULL == mFontDefaults )
1048 TextAbstraction::FontDescription fontDescription;
1049 defaultFontId = mFontClient.GetFontId( fontDescription );
1053 defaultFontId = mFontDefaults->GetFontId( mFontClient );
1056 Text::FontMetrics fontMetrics;
1057 mMetrics->GetFontMetrics( defaultFontId, fontMetrics );
1059 return( fontMetrics.ascender - fontMetrics.descender );
1062 void Controller::Impl::OnCursorKeyEvent( const Event& event )
1064 if( NULL == mEventData )
1066 // Nothing to do if there is no text input.
1070 int keyCode = event.p1.mInt;
1072 if( Dali::DALI_KEY_CURSOR_LEFT == keyCode )
1074 if( mEventData->mPrimaryCursorPosition > 0u )
1076 mEventData->mPrimaryCursorPosition = CalculateNewCursorIndex( mEventData->mPrimaryCursorPosition - 1u );
1079 else if( Dali::DALI_KEY_CURSOR_RIGHT == keyCode )
1081 if( mLogicalModel->mText.Count() > mEventData->mPrimaryCursorPosition )
1083 mEventData->mPrimaryCursorPosition = CalculateNewCursorIndex( mEventData->mPrimaryCursorPosition );
1086 else if( Dali::DALI_KEY_CURSOR_UP == keyCode )
1088 // Get first the line index of the current cursor position index.
1089 CharacterIndex characterIndex = 0u;
1091 if( mEventData->mPrimaryCursorPosition > 0u )
1093 characterIndex = mEventData->mPrimaryCursorPosition - 1u;
1096 const LineIndex lineIndex = mVisualModel->GetLineOfCharacter( characterIndex );
1098 if( lineIndex > 0u )
1100 // Retrieve the cursor position info.
1101 CursorInfo cursorInfo;
1102 GetCursorPosition( mEventData->mPrimaryCursorPosition,
1105 // Get the line above.
1106 const LineRun& line = *( mVisualModel->mLines.Begin() + ( lineIndex - 1u ) );
1108 // Get the next hit 'y' point.
1109 const float hitPointY = cursorInfo.lineOffset - 0.5f * ( line.ascender - line.descender );
1111 // Use the cursor hook position 'x' and the next hit 'y' position to calculate the new cursor index.
1112 mEventData->mPrimaryCursorPosition = Text::GetClosestCursorIndex( mVisualModel,
1115 mEventData->mCursorHookPositionX,
1119 else if( Dali::DALI_KEY_CURSOR_DOWN == keyCode )
1121 // Get first the line index of the current cursor position index.
1122 CharacterIndex characterIndex = 0u;
1124 if( mEventData->mPrimaryCursorPosition > 0u )
1126 characterIndex = mEventData->mPrimaryCursorPosition - 1u;
1129 const LineIndex lineIndex = mVisualModel->GetLineOfCharacter( characterIndex );
1131 if( lineIndex + 1u < mVisualModel->mLines.Count() )
1133 // Retrieve the cursor position info.
1134 CursorInfo cursorInfo;
1135 GetCursorPosition( mEventData->mPrimaryCursorPosition,
1138 // Get the line below.
1139 const LineRun& line = *( mVisualModel->mLines.Begin() + lineIndex + 1u );
1141 // Get the next hit 'y' point.
1142 const float hitPointY = cursorInfo.lineOffset + cursorInfo.lineHeight + 0.5f * ( line.ascender - line.descender );
1144 // Use the cursor hook position 'x' and the next hit 'y' position to calculate the new cursor index.
1145 mEventData->mPrimaryCursorPosition = Text::GetClosestCursorIndex( mVisualModel,
1148 mEventData->mCursorHookPositionX,
1153 mEventData->mUpdateCursorPosition = true;
1154 mEventData->mUpdateInputStyle = true;
1155 mEventData->mScrollAfterUpdatePosition = true;
1158 void Controller::Impl::OnTapEvent( const Event& event )
1160 if( NULL != mEventData )
1162 const unsigned int tapCount = event.p1.mUint;
1164 if( 1u == tapCount )
1166 if( IsShowingRealText() )
1168 // Convert from control's coords to text's coords.
1169 const float xPosition = event.p2.mFloat - mScrollPosition.x;
1170 const float yPosition = event.p3.mFloat - mScrollPosition.y;
1172 // Keep the tap 'x' position. Used to move the cursor.
1173 mEventData->mCursorHookPositionX = xPosition;
1175 mEventData->mPrimaryCursorPosition = Text::GetClosestCursorIndex( mVisualModel,
1181 // When the cursor position is changing, delay cursor blinking
1182 mEventData->mDecorator->DelayCursorBlink();
1186 mEventData->mPrimaryCursorPosition = 0u;
1189 mEventData->mUpdateCursorPosition = true;
1190 mEventData->mUpdateGrabHandlePosition = true;
1191 mEventData->mScrollAfterUpdatePosition = true;
1192 mEventData->mUpdateInputStyle = true;
1194 // Notify the cursor position to the imf manager.
1195 if( mEventData->mImfManager )
1197 mEventData->mImfManager.SetCursorPosition( mEventData->mPrimaryCursorPosition );
1198 mEventData->mImfManager.NotifyCursorPosition();
1204 void Controller::Impl::OnPanEvent( const Event& event )
1206 if( NULL == mEventData )
1208 // Nothing to do if there is no text input.
1212 const bool isHorizontalScrollEnabled = mEventData->mDecorator->IsHorizontalScrollEnabled();
1213 const bool isVerticalScrollEnabled = mEventData->mDecorator->IsVerticalScrollEnabled();
1215 if( !isHorizontalScrollEnabled && !isVerticalScrollEnabled )
1217 // Nothing to do if scrolling is not enabled.
1221 const int state = event.p1.mInt;
1225 case Gesture::Started:
1227 // Will remove the cursor, handles or text's popup, ...
1228 ChangeState( EventData::TEXT_PANNING );
1231 case Gesture::Continuing:
1233 const Vector2& layoutSize = mVisualModel->GetLayoutSize();
1234 const Vector2 currentScroll = mScrollPosition;
1236 if( isHorizontalScrollEnabled )
1238 const float displacementX = event.p2.mFloat;
1239 mScrollPosition.x += displacementX;
1241 ClampHorizontalScroll( layoutSize );
1244 if( isVerticalScrollEnabled )
1246 const float displacementY = event.p3.mFloat;
1247 mScrollPosition.y += displacementY;
1249 ClampVerticalScroll( layoutSize );
1252 mEventData->mDecorator->UpdatePositions( mScrollPosition - currentScroll );
1255 case Gesture::Finished:
1256 case Gesture::Cancelled: // FALLTHROUGH
1258 // Will go back to the previous state to show the cursor, handles, the text's popup, ...
1259 ChangeState( mEventData->mPreviousState );
1267 void Controller::Impl::OnLongPressEvent( const Event& event )
1269 DALI_LOG_INFO( gLogFilter, Debug::General, "Controller::OnLongPressEvent\n" );
1271 if( EventData::EDITING == mEventData->mState )
1273 ChangeState ( EventData::EDITING_WITH_POPUP );
1274 mEventData->mDecoratorUpdated = true;
1278 void Controller::Impl::OnHandleEvent( const Event& event )
1280 if( NULL == mEventData )
1282 // Nothing to do if there is no text input.
1286 const unsigned int state = event.p1.mUint;
1287 const bool handleStopScrolling = ( HANDLE_STOP_SCROLLING == state );
1288 const bool isSmoothHandlePanEnabled = mEventData->mDecorator->IsSmoothHandlePanEnabled();
1290 if( HANDLE_PRESSED == state )
1292 // Convert from decorator's coords to text's coords.
1293 const float xPosition = event.p2.mFloat - mScrollPosition.x;
1294 const float yPosition = event.p3.mFloat - mScrollPosition.y;
1296 // Need to calculate the handle's new position.
1297 const CharacterIndex handleNewPosition = Text::GetClosestCursorIndex( mVisualModel,
1303 if( Event::GRAB_HANDLE_EVENT == event.type )
1305 ChangeState ( EventData::GRAB_HANDLE_PANNING );
1307 if( handleNewPosition != mEventData->mPrimaryCursorPosition )
1309 // Updates the cursor position if the handle's new position is different than the current one.
1310 mEventData->mUpdateCursorPosition = true;
1311 // Does not update the grab handle position if the smooth panning is enabled. (The decorator does it smooth).
1312 mEventData->mUpdateGrabHandlePosition = !isSmoothHandlePanEnabled;
1313 mEventData->mPrimaryCursorPosition = handleNewPosition;
1316 // 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.
1317 mEventData->mDecoratorUpdated = isSmoothHandlePanEnabled;
1319 else if( Event::LEFT_SELECTION_HANDLE_EVENT == event.type )
1321 ChangeState ( EventData::SELECTION_HANDLE_PANNING );
1323 if( ( handleNewPosition != mEventData->mLeftSelectionPosition ) &&
1324 ( handleNewPosition != mEventData->mRightSelectionPosition ) )
1326 // Updates the highlight box if the handle's new position is different than the current one.
1327 mEventData->mUpdateHighlightBox = true;
1328 // Does not update the selection handle position if the smooth panning is enabled. (The decorator does it smooth).
1329 mEventData->mUpdateLeftSelectionPosition = !isSmoothHandlePanEnabled;
1330 mEventData->mLeftSelectionPosition = handleNewPosition;
1333 // 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.
1334 mEventData->mDecoratorUpdated = isSmoothHandlePanEnabled;
1336 // Will define the order to scroll the text to match the handle position.
1337 mEventData->mIsLeftHandleSelected = true;
1339 else if( Event::RIGHT_SELECTION_HANDLE_EVENT == event.type )
1341 ChangeState ( EventData::SELECTION_HANDLE_PANNING );
1343 if( ( handleNewPosition != mEventData->mRightSelectionPosition ) &&
1344 ( handleNewPosition != mEventData->mLeftSelectionPosition ) )
1346 // Updates the highlight box if the handle's new position is different than the current one.
1347 mEventData->mUpdateHighlightBox = true;
1348 // Does not update the selection handle position if the smooth panning is enabled. (The decorator does it smooth).
1349 mEventData->mUpdateRightSelectionPosition = !isSmoothHandlePanEnabled;
1350 mEventData->mRightSelectionPosition = handleNewPosition;
1353 // 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.
1354 mEventData->mDecoratorUpdated = isSmoothHandlePanEnabled;
1356 // Will define the order to scroll the text to match the handle position.
1357 mEventData->mIsLeftHandleSelected = false;
1359 } // end ( HANDLE_PRESSED == state )
1360 else if( ( HANDLE_RELEASED == state ) ||
1361 handleStopScrolling )
1363 CharacterIndex handlePosition = 0u;
1364 if( handleStopScrolling || isSmoothHandlePanEnabled )
1366 // Convert from decorator's coords to text's coords.
1367 const float xPosition = event.p2.mFloat - mScrollPosition.x;
1368 const float yPosition = event.p3.mFloat - mScrollPosition.y;
1370 handlePosition = Text::GetClosestCursorIndex( mVisualModel,
1377 if( Event::GRAB_HANDLE_EVENT == event.type )
1379 mEventData->mUpdateCursorPosition = true;
1380 mEventData->mUpdateGrabHandlePosition = true;
1381 mEventData->mUpdateInputStyle = true;
1383 if( !IsClipboardEmpty() )
1385 ChangeState( EventData::EDITING_WITH_PASTE_POPUP ); // Moving grabhandle will show Paste Popup
1388 if( handleStopScrolling || isSmoothHandlePanEnabled )
1390 mEventData->mScrollAfterUpdatePosition = true;
1391 mEventData->mPrimaryCursorPosition = handlePosition;
1394 else if( Event::LEFT_SELECTION_HANDLE_EVENT == event.type )
1396 ChangeState( EventData::SELECTING );
1398 mEventData->mUpdateHighlightBox = true;
1399 mEventData->mUpdateLeftSelectionPosition = true;
1400 mEventData->mUpdateRightSelectionPosition = true;
1402 if( handleStopScrolling || isSmoothHandlePanEnabled )
1404 mEventData->mScrollAfterUpdatePosition = true;
1406 if( ( handlePosition != mEventData->mRightSelectionPosition ) &&
1407 ( handlePosition != mEventData->mLeftSelectionPosition ) )
1409 mEventData->mLeftSelectionPosition = handlePosition;
1413 else if( Event::RIGHT_SELECTION_HANDLE_EVENT == event.type )
1415 ChangeState( EventData::SELECTING );
1417 mEventData->mUpdateHighlightBox = true;
1418 mEventData->mUpdateRightSelectionPosition = true;
1419 mEventData->mUpdateLeftSelectionPosition = true;
1421 if( handleStopScrolling || isSmoothHandlePanEnabled )
1423 mEventData->mScrollAfterUpdatePosition = true;
1424 if( ( handlePosition != mEventData->mRightSelectionPosition ) &&
1425 ( handlePosition != mEventData->mLeftSelectionPosition ) )
1427 mEventData->mRightSelectionPosition = handlePosition;
1432 mEventData->mDecoratorUpdated = true;
1433 } // end ( ( HANDLE_RELEASED == state ) || ( HANDLE_STOP_SCROLLING == state ) )
1434 else if( HANDLE_SCROLLING == state )
1436 const float xSpeed = event.p2.mFloat;
1437 const float ySpeed = event.p3.mFloat;
1438 const Vector2& layoutSize = mVisualModel->GetLayoutSize();
1439 const Vector2 currentScrollPosition = mScrollPosition;
1441 mScrollPosition.x += xSpeed;
1442 mScrollPosition.y += ySpeed;
1444 ClampHorizontalScroll( layoutSize );
1445 ClampVerticalScroll( layoutSize );
1447 bool endOfScroll = false;
1448 if( Vector2::ZERO == ( currentScrollPosition - mScrollPosition ) )
1450 // Notify the decorator there is no more text to scroll.
1451 // The decorator won't send more scroll events.
1452 mEventData->mDecorator->NotifyEndOfScroll();
1453 // Still need to set the position of the handle.
1457 // Set the position of the handle.
1458 const bool scrollRightDirection = xSpeed > 0.f;
1459 const bool scrollBottomDirection = ySpeed > 0.f;
1460 const bool leftSelectionHandleEvent = Event::LEFT_SELECTION_HANDLE_EVENT == event.type;
1461 const bool rightSelectionHandleEvent = Event::RIGHT_SELECTION_HANDLE_EVENT == event.type;
1463 if( Event::GRAB_HANDLE_EVENT == event.type )
1465 ChangeState( EventData::GRAB_HANDLE_PANNING );
1467 // Get the grab handle position in decorator coords.
1468 Vector2 position = mEventData->mDecorator->GetPosition( GRAB_HANDLE );
1470 if( mEventData->mDecorator->IsHorizontalScrollEnabled() )
1472 // Position the grag handle close to either the left or right edge.
1473 position.x = scrollRightDirection ? 0.f : mVisualModel->mControlSize.width;
1476 if( mEventData->mDecorator->IsVerticalScrollEnabled() )
1478 position.x = mEventData->mCursorHookPositionX;
1480 // Position the grag handle close to either the top or bottom edge.
1481 position.y = scrollBottomDirection ? 0.f : mVisualModel->mControlSize.height;
1484 // Get the new handle position.
1485 // The grab handle's position is in decorator's coords. Need to transforms to text's coords.
1486 const CharacterIndex handlePosition = Text::GetClosestCursorIndex( mVisualModel,
1489 position.x - mScrollPosition.x,
1490 position.y - mScrollPosition.y );
1492 if( mEventData->mPrimaryCursorPosition != handlePosition )
1494 mEventData->mUpdateCursorPosition = true;
1495 mEventData->mUpdateGrabHandlePosition = !isSmoothHandlePanEnabled;
1496 mEventData->mScrollAfterUpdatePosition = true;
1497 mEventData->mPrimaryCursorPosition = handlePosition;
1499 mEventData->mUpdateInputStyle = mEventData->mUpdateCursorPosition;
1501 // Updates the decorator if the soft handle panning is enabled.
1502 mEventData->mDecoratorUpdated = isSmoothHandlePanEnabled;
1504 else if( leftSelectionHandleEvent || rightSelectionHandleEvent )
1506 ChangeState( EventData::SELECTION_HANDLE_PANNING );
1508 // Get the selection handle position in decorator coords.
1509 Vector2 position = mEventData->mDecorator->GetPosition( leftSelectionHandleEvent ? Text::LEFT_SELECTION_HANDLE : Text::RIGHT_SELECTION_HANDLE );
1511 if( mEventData->mDecorator->IsHorizontalScrollEnabled() )
1513 // Position the selection handle close to either the left or right edge.
1514 position.x = scrollRightDirection ? 0.f : mVisualModel->mControlSize.width;
1517 if( mEventData->mDecorator->IsVerticalScrollEnabled() )
1519 position.x = mEventData->mCursorHookPositionX;
1521 // Position the grag handle close to either the top or bottom edge.
1522 position.y = scrollBottomDirection ? 0.f : mVisualModel->mControlSize.height;
1525 // Get the new handle position.
1526 // The selection handle's position is in decorator's coords. Need to transform to text's coords.
1527 const CharacterIndex handlePosition = Text::GetClosestCursorIndex( mVisualModel,
1530 position.x - mScrollPosition.x,
1531 position.y - mScrollPosition.y );
1533 if( leftSelectionHandleEvent )
1535 const bool differentHandles = ( mEventData->mLeftSelectionPosition != handlePosition ) && ( mEventData->mRightSelectionPosition != handlePosition );
1537 if( differentHandles || endOfScroll )
1539 mEventData->mUpdateHighlightBox = true;
1540 mEventData->mUpdateLeftSelectionPosition = !isSmoothHandlePanEnabled;
1541 mEventData->mUpdateRightSelectionPosition = isSmoothHandlePanEnabled;
1542 mEventData->mLeftSelectionPosition = handlePosition;
1547 const bool differentHandles = ( mEventData->mRightSelectionPosition != handlePosition ) && ( mEventData->mLeftSelectionPosition != handlePosition );
1548 if( differentHandles || endOfScroll )
1550 mEventData->mUpdateHighlightBox = true;
1551 mEventData->mUpdateRightSelectionPosition = !isSmoothHandlePanEnabled;
1552 mEventData->mUpdateLeftSelectionPosition = isSmoothHandlePanEnabled;
1553 mEventData->mRightSelectionPosition = handlePosition;
1557 if( mEventData->mUpdateLeftSelectionPosition || mEventData->mUpdateRightSelectionPosition )
1559 RepositionSelectionHandles();
1561 mEventData->mScrollAfterUpdatePosition = !isSmoothHandlePanEnabled;
1564 mEventData->mDecoratorUpdated = true;
1565 } // end ( HANDLE_SCROLLING == state )
1568 void Controller::Impl::OnSelectEvent( const Event& event )
1570 if( NULL == mEventData )
1572 // Nothing to do if there is no text.
1576 if( mEventData->mSelectionEnabled )
1578 // Convert from control's coords to text's coords.
1579 const float xPosition = event.p2.mFloat - mScrollPosition.x;
1580 const float yPosition = event.p3.mFloat - mScrollPosition.y;
1582 // Calculates the logical position from the x,y coords.
1583 RepositionSelectionHandles( xPosition,
1588 void Controller::Impl::OnSelectAllEvent()
1590 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "OnSelectAllEvent mEventData->mSelectionEnabled%s \n", mEventData->mSelectionEnabled?"true":"false");
1592 if( NULL == mEventData )
1594 // Nothing to do if there is no text.
1598 if( mEventData->mSelectionEnabled )
1600 ChangeState( EventData::SELECTING );
1602 mEventData->mLeftSelectionPosition = 0u;
1603 mEventData->mRightSelectionPosition = mLogicalModel->mText.Count();
1605 mEventData->mScrollAfterUpdatePosition = true;
1606 mEventData->mUpdateLeftSelectionPosition = true;
1607 mEventData->mUpdateRightSelectionPosition = true;
1608 mEventData->mUpdateHighlightBox = true;
1612 void Controller::Impl::RetrieveSelection( std::string& selectedText, bool deleteAfterRetrieval )
1614 if( mEventData->mLeftSelectionPosition == mEventData->mRightSelectionPosition )
1616 // Nothing to select if handles are in the same place.
1617 selectedText.clear();
1621 const bool handlesCrossed = mEventData->mLeftSelectionPosition > mEventData->mRightSelectionPosition;
1623 //Get start and end position of selection
1624 const CharacterIndex startOfSelectedText = handlesCrossed ? mEventData->mRightSelectionPosition : mEventData->mLeftSelectionPosition;
1625 const Length lengthOfSelectedText = ( handlesCrossed ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition ) - startOfSelectedText;
1627 Vector<Character>& utf32Characters = mLogicalModel->mText;
1628 const Length numberOfCharacters = utf32Characters.Count();
1630 // Validate the start and end selection points
1631 if( ( startOfSelectedText + lengthOfSelectedText ) <= numberOfCharacters )
1633 //Get text as a UTF8 string
1634 Utf32ToUtf8( &utf32Characters[startOfSelectedText], lengthOfSelectedText, selectedText );
1636 if( deleteAfterRetrieval ) // Only delete text if copied successfully
1638 // Set as input style the style of the first deleted character.
1639 mLogicalModel->RetrieveStyle( startOfSelectedText, mEventData->mInputStyle );
1641 mLogicalModel->UpdateTextStyleRuns( startOfSelectedText, -static_cast<int>( lengthOfSelectedText ) );
1643 // Mark the paragraphs to be updated.
1644 mTextUpdateInfo.mCharacterIndex = startOfSelectedText;
1645 mTextUpdateInfo.mNumberOfCharactersToRemove = lengthOfSelectedText;
1647 // Delete text between handles
1648 Vector<Character>::Iterator first = utf32Characters.Begin() + startOfSelectedText;
1649 Vector<Character>::Iterator last = first + lengthOfSelectedText;
1650 utf32Characters.Erase( first, last );
1652 // Will show the cursor at the first character of the selection.
1653 mEventData->mPrimaryCursorPosition = handlesCrossed ? mEventData->mRightSelectionPosition : mEventData->mLeftSelectionPosition;
1657 // Will show the cursor at the last character of the selection.
1658 mEventData->mPrimaryCursorPosition = handlesCrossed ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition;
1661 mEventData->mDecoratorUpdated = true;
1665 void Controller::Impl::ShowClipboard()
1669 mClipboard.ShowClipboard();
1673 void Controller::Impl::HideClipboard()
1675 if( mClipboard && mClipboardHideEnabled )
1677 mClipboard.HideClipboard();
1681 void Controller::Impl::SetClipboardHideEnable(bool enable)
1683 mClipboardHideEnabled = enable;
1686 bool Controller::Impl::CopyStringToClipboard( std::string& source )
1688 //Send string to clipboard
1689 return ( mClipboard && mClipboard.SetItem( source ) );
1692 void Controller::Impl::SendSelectionToClipboard( bool deleteAfterSending )
1694 std::string selectedText;
1695 RetrieveSelection( selectedText, deleteAfterSending );
1696 CopyStringToClipboard( selectedText );
1697 ChangeState( EventData::EDITING );
1700 void Controller::Impl::GetTextFromClipboard( unsigned int itemIndex, std::string& retrievedString )
1704 retrievedString = mClipboard.GetItem( itemIndex );
1708 void Controller::Impl::RepositionSelectionHandles()
1710 CharacterIndex selectionStart = mEventData->mLeftSelectionPosition;
1711 CharacterIndex selectionEnd = mEventData->mRightSelectionPosition;
1713 if( selectionStart == selectionEnd )
1715 // Nothing to select if handles are in the same place.
1719 mEventData->mDecorator->ClearHighlights();
1721 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
1722 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
1723 const GlyphInfo* const glyphsBuffer = mVisualModel->mGlyphs.Begin();
1724 const Vector2* const positionsBuffer = mVisualModel->mGlyphPositions.Begin();
1725 const Length* const charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
1726 const CharacterIndex* const glyphToCharacterBuffer = mVisualModel->mGlyphsToCharacters.Begin();
1727 const CharacterDirection* const modelCharacterDirectionsBuffer = ( 0u != mLogicalModel->mCharacterDirections.Count() ) ? mLogicalModel->mCharacterDirections.Begin() : NULL;
1729 const bool isLastCharacter = selectionEnd >= mLogicalModel->mText.Count();
1730 const CharacterDirection startDirection = ( ( NULL == modelCharacterDirectionsBuffer ) ? false : *( modelCharacterDirectionsBuffer + selectionStart ) );
1731 const CharacterDirection endDirection = ( ( NULL == modelCharacterDirectionsBuffer ) ? false : *( modelCharacterDirectionsBuffer + ( selectionEnd - ( isLastCharacter ? 1u : 0u ) ) ) );
1733 // Swap the indices if the start is greater than the end.
1734 const bool indicesSwapped = selectionStart > selectionEnd;
1736 // Tell the decorator to flip the selection handles if needed.
1737 mEventData->mDecorator->SetSelectionHandleFlipState( indicesSwapped, startDirection, endDirection );
1739 if( indicesSwapped )
1741 std::swap( selectionStart, selectionEnd );
1744 // Get the indices to the first and last selected glyphs.
1745 const CharacterIndex selectionEndMinusOne = selectionEnd - 1u;
1746 const GlyphIndex glyphStart = *( charactersToGlyphBuffer + selectionStart );
1747 const Length numberOfGlyphs = *( glyphsPerCharacterBuffer + selectionEndMinusOne );
1748 const GlyphIndex glyphEnd = *( charactersToGlyphBuffer + selectionEndMinusOne ) + ( ( numberOfGlyphs > 0 ) ? numberOfGlyphs - 1u : 0u );
1750 // Get the lines where the glyphs are laid-out.
1751 const LineRun* lineRun = mVisualModel->mLines.Begin();
1753 LineIndex lineIndex = 0u;
1754 Length numberOfLines = 0u;
1755 mVisualModel->GetNumberOfLines( glyphStart,
1756 1u + glyphEnd - glyphStart,
1759 const LineIndex firstLineIndex = lineIndex;
1761 // Create the structure to store some selection box info.
1762 Vector<SelectionBoxInfo> selectionBoxLinesInfo;
1763 selectionBoxLinesInfo.Resize( numberOfLines );
1765 SelectionBoxInfo* selectionBoxInfo = selectionBoxLinesInfo.Begin();
1766 selectionBoxInfo->minX = MAX_FLOAT;
1767 selectionBoxInfo->maxX = MIN_FLOAT;
1769 // Keep the min and max 'x' position to calculate the size and position of the highlighed text.
1770 float minHighlightX = std::numeric_limits<float>::max();
1771 float maxHighlightX = std::numeric_limits<float>::min();
1773 Vector2 highLightPosition; // The highlight position in decorator's coords.
1775 // Retrieve the first line and get the line's vertical offset, the line's height and the index to the last glyph.
1777 // The line's vertical offset of all the lines before the line where the first glyph is laid-out.
1778 selectionBoxInfo->lineOffset = CalculateLineOffset( mVisualModel->mLines,
1781 // Transform to decorator's (control) coords.
1782 selectionBoxInfo->lineOffset += mScrollPosition.y;
1784 lineRun += firstLineIndex;
1786 // The line height is the addition of the line ascender and the line descender.
1787 // However, the line descender has a negative value, hence the subtraction.
1788 selectionBoxInfo->lineHeight = lineRun->ascender - lineRun->descender;
1790 GlyphIndex lastGlyphOfLine = lineRun->glyphRun.glyphIndex + lineRun->glyphRun.numberOfGlyphs - 1u;
1792 // Check if the first glyph is a ligature that must be broken like Latin ff, fi, or Arabic ï»», etc which needs special code.
1793 const Length numberOfCharactersStart = *( charactersPerGlyphBuffer + glyphStart );
1794 bool splitStartGlyph = ( numberOfCharactersStart > 1u ) && HasLigatureMustBreak( mLogicalModel->GetScript( selectionStart ) );
1796 // Check if the last glyph is a ligature that must be broken like Latin ff, fi, or Arabic ï»», etc which needs special code.
1797 const Length numberOfCharactersEnd = *( charactersPerGlyphBuffer + glyphEnd );
1798 bool splitEndGlyph = ( glyphStart != glyphEnd ) && ( numberOfCharactersEnd > 1u ) && HasLigatureMustBreak( mLogicalModel->GetScript( selectionEndMinusOne ) );
1800 // The number of quads of the selection box.
1801 const unsigned int numberOfQuads = 1u + ( glyphEnd - glyphStart ) + ( ( numberOfLines > 1u ) ? 2u * numberOfLines : 0u );
1802 mEventData->mDecorator->ResizeHighlightQuads( numberOfQuads );
1804 // Count the actual number of quads.
1805 unsigned int actualNumberOfQuads = 0u;
1808 // Traverse the glyphs.
1809 for( GlyphIndex index = glyphStart; index <= glyphEnd; ++index )
1811 const GlyphInfo& glyph = *( glyphsBuffer + index );
1812 const Vector2& position = *( positionsBuffer + index );
1814 if( splitStartGlyph )
1816 // 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.
1818 const float glyphAdvance = glyph.advance / static_cast<float>( numberOfCharactersStart );
1819 const CharacterIndex interGlyphIndex = selectionStart - *( glyphToCharacterBuffer + glyphStart );
1820 // Get the direction of the character.
1821 CharacterDirection isCurrentRightToLeft = false;
1822 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
1824 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + selectionStart );
1827 // The end point could be in the middle of the ligature.
1828 // Calculate the number of characters selected.
1829 const Length numberOfCharacters = ( glyphStart == glyphEnd ) ? ( selectionEnd - selectionStart ) : ( numberOfCharactersStart - interGlyphIndex );
1831 quad.x = lineRun->alignmentOffset + position.x - glyph.xBearing + mScrollPosition.x + glyphAdvance * static_cast<float>( isCurrentRightToLeft ? ( numberOfCharactersStart - interGlyphIndex - numberOfCharacters ) : interGlyphIndex );
1832 quad.y = selectionBoxInfo->lineOffset;
1833 quad.z = quad.x + static_cast<float>( numberOfCharacters ) * glyphAdvance;
1834 quad.w = selectionBoxInfo->lineOffset + selectionBoxInfo->lineHeight;
1836 // Store the min and max 'x' for each line.
1837 selectionBoxInfo->minX = std::min( selectionBoxInfo->minX, quad.x );
1838 selectionBoxInfo->maxX = std::max( selectionBoxInfo->maxX, quad.z );
1840 mEventData->mDecorator->AddHighlight( actualNumberOfQuads, quad );
1841 ++actualNumberOfQuads;
1843 splitStartGlyph = false;
1847 if( splitEndGlyph && ( index == glyphEnd ) )
1849 // 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.
1851 const float glyphAdvance = glyph.advance / static_cast<float>( numberOfCharactersEnd );
1852 const CharacterIndex interGlyphIndex = selectionEnd - *( glyphToCharacterBuffer + glyphEnd );
1853 // Get the direction of the character.
1854 CharacterDirection isCurrentRightToLeft = false;
1855 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
1857 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + selectionEnd );
1860 const Length numberOfCharacters = numberOfCharactersEnd - interGlyphIndex;
1862 quad.x = lineRun->alignmentOffset + position.x - glyph.xBearing + mScrollPosition.x + ( isCurrentRightToLeft ? ( glyphAdvance * static_cast<float>( numberOfCharacters ) ) : 0.f );
1863 quad.y = selectionBoxInfo->lineOffset;
1864 quad.z = quad.x + static_cast<float>( interGlyphIndex ) * glyphAdvance;
1865 quad.w = quad.y + selectionBoxInfo->lineHeight;
1867 // Store the min and max 'x' for each line.
1868 selectionBoxInfo->minX = std::min( selectionBoxInfo->minX, quad.x );
1869 selectionBoxInfo->maxX = std::max( selectionBoxInfo->maxX, quad.z );
1871 mEventData->mDecorator->AddHighlight( actualNumberOfQuads,
1873 ++actualNumberOfQuads;
1875 splitEndGlyph = false;
1879 quad.x = lineRun->alignmentOffset + position.x - glyph.xBearing + mScrollPosition.x;
1880 quad.y = selectionBoxInfo->lineOffset;
1881 quad.z = quad.x + glyph.advance;
1882 quad.w = quad.y + selectionBoxInfo->lineHeight;
1884 // Store the min and max 'x' for each line.
1885 selectionBoxInfo->minX = std::min( selectionBoxInfo->minX, quad.x );
1886 selectionBoxInfo->maxX = std::max( selectionBoxInfo->maxX, quad.z );
1888 mEventData->mDecorator->AddHighlight( actualNumberOfQuads,
1890 ++actualNumberOfQuads;
1892 // Whether to retrieve the next line.
1893 if( index == lastGlyphOfLine )
1895 // Retrieve the next line.
1898 // Get the last glyph of the new line.
1899 lastGlyphOfLine = lineRun->glyphRun.glyphIndex + lineRun->glyphRun.numberOfGlyphs - 1u;
1902 if( lineIndex < firstLineIndex + numberOfLines )
1904 // Keep the offset and height of the current selection box.
1905 const float currentLineOffset = selectionBoxInfo->lineOffset;
1906 const float currentLineHeight = selectionBoxInfo->lineHeight;
1908 // Get the selection box info for the next line.
1911 selectionBoxInfo->minX = MAX_FLOAT;
1912 selectionBoxInfo->maxX = MIN_FLOAT;
1914 // Update the line's vertical offset.
1915 selectionBoxInfo->lineOffset = currentLineOffset + currentLineHeight;
1917 // The line height is the addition of the line ascender and the line descender.
1918 // However, the line descender has a negative value, hence the subtraction.
1919 selectionBoxInfo->lineHeight = lineRun->ascender - lineRun->descender;
1924 // Traverses all the lines and updates the min and max 'x' positions and the total height.
1925 // The final width is calculated after 'boxifying' the selection.
1926 for( Vector<SelectionBoxInfo>::ConstIterator it = selectionBoxLinesInfo.Begin(),
1927 endIt = selectionBoxLinesInfo.End();
1931 const SelectionBoxInfo& info = *it;
1933 // Update the size of the highlighted text.
1934 highLightSize.height += info.lineHeight;
1935 minHighlightX = std::min( minHighlightX, info.minX );
1936 maxHighlightX = std::max( maxHighlightX, info.maxX );
1939 // Add extra geometry to 'boxify' the selection.
1941 if( 1u < numberOfLines )
1943 // Boxify the first line.
1944 lineRun = mVisualModel->mLines.Begin() + firstLineIndex;
1945 const SelectionBoxInfo& firstSelectionBoxLineInfo = *( selectionBoxLinesInfo.Begin() );
1947 bool boxifyBegin = ( LTR != lineRun->direction ) && ( LTR != startDirection );
1948 bool boxifyEnd = ( LTR == lineRun->direction ) && ( LTR == startDirection );
1953 quad.y = firstSelectionBoxLineInfo.lineOffset;
1954 quad.z = firstSelectionBoxLineInfo.minX;
1955 quad.w = firstSelectionBoxLineInfo.lineOffset + firstSelectionBoxLineInfo.lineHeight;
1957 // Boxify at the beginning of the line.
1958 mEventData->mDecorator->AddHighlight( actualNumberOfQuads,
1960 ++actualNumberOfQuads;
1962 // Update the size of the highlighted text.
1963 minHighlightX = 0.f;
1968 quad.x = firstSelectionBoxLineInfo.maxX;
1969 quad.y = firstSelectionBoxLineInfo.lineOffset;
1970 quad.z = mVisualModel->mControlSize.width;
1971 quad.w = firstSelectionBoxLineInfo.lineOffset + firstSelectionBoxLineInfo.lineHeight;
1973 // Boxify at the end of the line.
1974 mEventData->mDecorator->AddHighlight( actualNumberOfQuads,
1976 ++actualNumberOfQuads;
1978 // Update the size of the highlighted text.
1979 maxHighlightX = mVisualModel->mControlSize.width;
1982 // Boxify the central lines.
1983 if( 2u < numberOfLines )
1985 for( Vector<SelectionBoxInfo>::ConstIterator it = selectionBoxLinesInfo.Begin() + 1u,
1986 endIt = selectionBoxLinesInfo.End() - 1u;
1990 const SelectionBoxInfo& info = *it;
1993 quad.y = info.lineOffset;
1995 quad.w = info.lineOffset + info.lineHeight;
1997 mEventData->mDecorator->AddHighlight( actualNumberOfQuads,
1999 ++actualNumberOfQuads;
2002 quad.y = info.lineOffset;
2003 quad.z = mVisualModel->mControlSize.width;
2004 quad.w = info.lineOffset + info.lineHeight;
2006 mEventData->mDecorator->AddHighlight( actualNumberOfQuads,
2008 ++actualNumberOfQuads;
2011 // Update the size of the highlighted text.
2012 minHighlightX = 0.f;
2013 maxHighlightX = mVisualModel->mControlSize.width;
2016 // Boxify the last line.
2017 lineRun = mVisualModel->mLines.Begin() + firstLineIndex + numberOfLines - 1u;
2018 const SelectionBoxInfo& lastSelectionBoxLineInfo = *( selectionBoxLinesInfo.End() - 1u );
2020 boxifyBegin = ( LTR == lineRun->direction ) && ( LTR == endDirection );
2021 boxifyEnd = ( LTR != lineRun->direction ) && ( LTR != endDirection );
2026 quad.y = lastSelectionBoxLineInfo.lineOffset;
2027 quad.z = lastSelectionBoxLineInfo.minX;
2028 quad.w = lastSelectionBoxLineInfo.lineOffset + lastSelectionBoxLineInfo.lineHeight;
2030 // Boxify at the beginning of the line.
2031 mEventData->mDecorator->AddHighlight( actualNumberOfQuads,
2033 ++actualNumberOfQuads;
2035 // Update the size of the highlighted text.
2036 minHighlightX = 0.f;
2041 quad.x = lastSelectionBoxLineInfo.maxX;
2042 quad.y = lastSelectionBoxLineInfo.lineOffset;
2043 quad.z = mVisualModel->mControlSize.width;
2044 quad.w = lastSelectionBoxLineInfo.lineOffset + lastSelectionBoxLineInfo.lineHeight;
2046 // Boxify at the end of the line.
2047 mEventData->mDecorator->AddHighlight( actualNumberOfQuads,
2049 ++actualNumberOfQuads;
2051 // Update the size of the highlighted text.
2052 maxHighlightX = mVisualModel->mControlSize.width;
2056 // Set the actual number of quads.
2057 mEventData->mDecorator->ResizeHighlightQuads( actualNumberOfQuads );
2059 // Sets the highlight's size and position. In decorator's coords.
2060 // The highlight's height has been calculated above (before 'boxifying' the highlight).
2061 highLightSize.width = maxHighlightX - minHighlightX;
2063 highLightPosition.x = minHighlightX;
2064 const SelectionBoxInfo& firstSelectionBoxLineInfo = *( selectionBoxLinesInfo.Begin() );
2065 highLightPosition.y = firstSelectionBoxLineInfo.lineOffset;
2067 mEventData->mDecorator->SetHighLightBox( highLightPosition, highLightSize );
2069 if( !mEventData->mDecorator->IsSmoothHandlePanEnabled() )
2071 CursorInfo primaryCursorInfo;
2072 GetCursorPosition( mEventData->mLeftSelectionPosition,
2073 primaryCursorInfo );
2075 const Vector2 primaryPosition = primaryCursorInfo.primaryPosition + mScrollPosition;
2077 mEventData->mDecorator->SetPosition( LEFT_SELECTION_HANDLE,
2079 primaryCursorInfo.lineOffset + mScrollPosition.y,
2080 primaryCursorInfo.lineHeight );
2082 CursorInfo secondaryCursorInfo;
2083 GetCursorPosition( mEventData->mRightSelectionPosition,
2084 secondaryCursorInfo );
2086 const Vector2 secondaryPosition = secondaryCursorInfo.primaryPosition + mScrollPosition;
2088 mEventData->mDecorator->SetPosition( RIGHT_SELECTION_HANDLE,
2089 secondaryPosition.x,
2090 secondaryCursorInfo.lineOffset + mScrollPosition.y,
2091 secondaryCursorInfo.lineHeight );
2094 // Cursor to be positioned at end of selection so if selection interrupted and edit mode restarted the cursor will be at end of selection
2095 mEventData->mPrimaryCursorPosition = ( indicesSwapped ) ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition;
2097 // Set the flag to update the decorator.
2098 mEventData->mDecoratorUpdated = true;
2101 void Controller::Impl::RepositionSelectionHandles( float visualX, float visualY )
2103 if( NULL == mEventData )
2105 // Nothing to do if there is no text input.
2109 if( IsShowingPlaceholderText() )
2111 // Nothing to do if there is the place-holder text.
2115 const Length numberOfGlyphs = mVisualModel->mGlyphs.Count();
2116 const Length numberOfLines = mVisualModel->mLines.Count();
2117 if( ( 0 == numberOfGlyphs ) ||
2118 ( 0 == numberOfLines ) )
2120 // Nothing to do if there is no text.
2124 // Find which word was selected
2125 CharacterIndex selectionStart( 0 );
2126 CharacterIndex selectionEnd( 0 );
2127 const bool indicesFound = FindSelectionIndices( mVisualModel,
2134 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "%p selectionStart %d selectionEnd %d\n", this, selectionStart, selectionEnd );
2138 ChangeState( EventData::SELECTING );
2140 mEventData->mLeftSelectionPosition = selectionStart;
2141 mEventData->mRightSelectionPosition = selectionEnd;
2143 mEventData->mUpdateLeftSelectionPosition = true;
2144 mEventData->mUpdateRightSelectionPosition = true;
2145 mEventData->mUpdateHighlightBox = true;
2147 mEventData->mScrollAfterUpdatePosition = ( mEventData->mLeftSelectionPosition != mEventData->mRightSelectionPosition );
2151 // Nothing to select. i.e. a white space, out of bounds
2152 ChangeState( EventData::EDITING );
2154 mEventData->mPrimaryCursorPosition = selectionEnd;
2156 mEventData->mUpdateCursorPosition = true;
2157 mEventData->mUpdateGrabHandlePosition = true;
2158 mEventData->mScrollAfterUpdatePosition = true;
2159 mEventData->mUpdateInputStyle = true;
2163 void Controller::Impl::SetPopupButtons()
2166 * Sets the Popup buttons to be shown depending on State.
2168 * If SELECTING : CUT & COPY + ( PASTE & CLIPBOARD if content available to paste )
2170 * If EDITING_WITH_POPUP : SELECT & SELECT_ALL
2173 TextSelectionPopup::Buttons buttonsToShow = TextSelectionPopup::NONE;
2175 if( EventData::SELECTING == mEventData->mState )
2177 buttonsToShow = TextSelectionPopup::Buttons( TextSelectionPopup::CUT | TextSelectionPopup::COPY );
2179 if( !IsClipboardEmpty() )
2181 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
2182 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::CLIPBOARD ) );
2185 if( !mEventData->mAllTextSelected )
2187 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::SELECT_ALL ) );
2190 else if( EventData::EDITING_WITH_POPUP == mEventData->mState )
2192 if( mLogicalModel->mText.Count() && !IsShowingPlaceholderText() )
2194 buttonsToShow = TextSelectionPopup::Buttons( TextSelectionPopup::SELECT | TextSelectionPopup::SELECT_ALL );
2197 if( !IsClipboardEmpty() )
2199 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
2200 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::CLIPBOARD ) );
2203 else if( EventData::EDITING_WITH_PASTE_POPUP == mEventData->mState )
2205 if ( !IsClipboardEmpty() )
2207 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
2208 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::CLIPBOARD ) );
2212 mEventData->mDecorator->SetEnabledPopupButtons( buttonsToShow );
2215 void Controller::Impl::ChangeState( EventData::State newState )
2217 if( NULL == mEventData )
2219 // Nothing to do if there is no text input.
2223 DALI_LOG_INFO( gLogFilter, Debug::General, "ChangeState state:%d newstate:%d\n", mEventData->mState, newState );
2225 if( mEventData->mState != newState )
2227 mEventData->mPreviousState = mEventData->mState;
2228 mEventData->mState = newState;
2230 switch( mEventData->mState )
2232 case EventData::INACTIVE:
2234 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
2235 mEventData->mDecorator->StopCursorBlink();
2236 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
2237 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
2238 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
2239 mEventData->mDecorator->SetHighlightActive( false );
2240 mEventData->mDecorator->SetPopupActive( false );
2241 mEventData->mDecoratorUpdated = true;
2245 case EventData::INTERRUPTED:
2247 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
2248 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
2249 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
2250 mEventData->mDecorator->SetHighlightActive( false );
2251 mEventData->mDecorator->SetPopupActive( false );
2252 mEventData->mDecoratorUpdated = true;
2256 case EventData::SELECTING:
2258 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
2259 mEventData->mDecorator->StopCursorBlink();
2260 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
2261 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true );
2262 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true );
2263 mEventData->mDecorator->SetHighlightActive( true );
2264 if( mEventData->mGrabHandlePopupEnabled )
2267 mEventData->mDecorator->SetPopupActive( true );
2269 mEventData->mDecoratorUpdated = true;
2272 case EventData::EDITING:
2274 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
2275 if( mEventData->mCursorBlinkEnabled )
2277 mEventData->mDecorator->StartCursorBlink();
2279 // Grab handle is not shown until a tap is received whilst EDITING
2280 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
2281 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
2282 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
2283 mEventData->mDecorator->SetHighlightActive( false );
2284 if( mEventData->mGrabHandlePopupEnabled )
2286 mEventData->mDecorator->SetPopupActive( false );
2288 mEventData->mDecoratorUpdated = true;
2292 case EventData::EDITING_WITH_POPUP:
2294 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "EDITING_WITH_POPUP \n", newState );
2296 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
2297 if( mEventData->mCursorBlinkEnabled )
2299 mEventData->mDecorator->StartCursorBlink();
2301 if( mEventData->mSelectionEnabled )
2303 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
2304 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
2305 mEventData->mDecorator->SetHighlightActive( false );
2309 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
2311 if( mEventData->mGrabHandlePopupEnabled )
2314 mEventData->mDecorator->SetPopupActive( true );
2317 mEventData->mDecoratorUpdated = true;
2320 case EventData::EDITING_WITH_GRAB_HANDLE:
2322 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "EDITING_WITH_GRAB_HANDLE \n", newState );
2324 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
2325 if( mEventData->mCursorBlinkEnabled )
2327 mEventData->mDecorator->StartCursorBlink();
2329 // Grab handle is not shown until a tap is received whilst EDITING
2330 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
2331 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
2332 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
2333 mEventData->mDecorator->SetHighlightActive( false );
2334 if( mEventData->mGrabHandlePopupEnabled )
2336 mEventData->mDecorator->SetPopupActive( false );
2338 mEventData->mDecoratorUpdated = true;
2342 case EventData::SELECTION_HANDLE_PANNING:
2344 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
2345 mEventData->mDecorator->StopCursorBlink();
2346 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
2347 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true );
2348 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true );
2349 mEventData->mDecorator->SetHighlightActive( true );
2350 if( mEventData->mGrabHandlePopupEnabled )
2352 mEventData->mDecorator->SetPopupActive( false );
2354 mEventData->mDecoratorUpdated = true;
2357 case EventData::GRAB_HANDLE_PANNING:
2359 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "GRAB_HANDLE_PANNING \n", newState );
2361 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
2362 if( mEventData->mCursorBlinkEnabled )
2364 mEventData->mDecorator->StartCursorBlink();
2366 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
2367 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
2368 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
2369 mEventData->mDecorator->SetHighlightActive( false );
2370 if( mEventData->mGrabHandlePopupEnabled )
2372 mEventData->mDecorator->SetPopupActive( false );
2374 mEventData->mDecoratorUpdated = true;
2377 case EventData::EDITING_WITH_PASTE_POPUP:
2379 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "EDITING_WITH_PASTE_POPUP \n", newState );
2381 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
2382 if( mEventData->mCursorBlinkEnabled )
2384 mEventData->mDecorator->StartCursorBlink();
2387 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
2388 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
2389 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
2390 mEventData->mDecorator->SetHighlightActive( false );
2392 if( mEventData->mGrabHandlePopupEnabled )
2395 mEventData->mDecorator->SetPopupActive( true );
2398 mEventData->mDecoratorUpdated = true;
2401 case EventData::TEXT_PANNING:
2403 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
2404 mEventData->mDecorator->StopCursorBlink();
2405 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
2406 if( mEventData->mDecorator->IsHandleActive( LEFT_SELECTION_HANDLE ) ||
2407 mEventData->mDecorator->IsHandleActive( RIGHT_SELECTION_HANDLE ) )
2409 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
2410 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
2411 mEventData->mDecorator->SetHighlightActive( true );
2414 if( mEventData->mGrabHandlePopupEnabled )
2416 mEventData->mDecorator->SetPopupActive( false );
2419 mEventData->mDecoratorUpdated = true;
2426 void Controller::Impl::GetCursorPosition( CharacterIndex logical,
2427 CursorInfo& cursorInfo )
2429 if( !IsShowingRealText() )
2431 // Do not want to use the place-holder text to set the cursor position.
2433 // Use the line's height of the font's family set to set the cursor's size.
2434 // If there is no font's family set, use the default font.
2435 // Use the current alignment to place the cursor at the beginning, center or end of the box.
2437 cursorInfo.lineOffset = 0.f;
2438 cursorInfo.lineHeight = GetDefaultFontLineHeight();
2439 cursorInfo.primaryCursorHeight = cursorInfo.lineHeight;
2441 switch( mLayoutEngine.GetHorizontalAlignment() )
2443 case LayoutEngine::HORIZONTAL_ALIGN_BEGIN:
2445 cursorInfo.primaryPosition.x = 0.f;
2448 case LayoutEngine::HORIZONTAL_ALIGN_CENTER:
2450 cursorInfo.primaryPosition.x = floorf( 0.5f * mVisualModel->mControlSize.width );
2453 case LayoutEngine::HORIZONTAL_ALIGN_END:
2455 cursorInfo.primaryPosition.x = mVisualModel->mControlSize.width - mEventData->mDecorator->GetCursorWidth();
2460 // Nothing else to do.
2464 Text::GetCursorPosition( mVisualModel,
2470 if( LayoutEngine::MULTI_LINE_BOX == mLayoutEngine.GetLayout() )
2472 // If the text is editable and multi-line, the cursor position after a white space shouldn't exceed the boundaries of the text control.
2474 // Note the white spaces laid-out at the end of the line might exceed the boundaries of the control.
2475 // The reason is a wrapped line must not start with a white space so they are laid-out at the end of the line.
2477 if( 0.f > cursorInfo.primaryPosition.x )
2479 cursorInfo.primaryPosition.x = 0.f;
2482 const float edgeWidth = mVisualModel->mControlSize.width - static_cast<float>( mEventData->mDecorator->GetCursorWidth() );
2483 if( cursorInfo.primaryPosition.x > edgeWidth )
2485 cursorInfo.primaryPosition.x = edgeWidth;
2490 CharacterIndex Controller::Impl::CalculateNewCursorIndex( CharacterIndex index ) const
2492 if( NULL == mEventData )
2494 // Nothing to do if there is no text input.
2498 CharacterIndex cursorIndex = mEventData->mPrimaryCursorPosition;
2500 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
2501 const Length* const charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
2503 GlyphIndex glyphIndex = *( charactersToGlyphBuffer + index );
2504 Length numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
2506 if( numberOfCharacters > 1u )
2508 const Script script = mLogicalModel->GetScript( index );
2509 if( HasLigatureMustBreak( script ) )
2511 // Prevents to jump the whole Latin ligatures like fi, ff, or Arabic ï»», ...
2512 numberOfCharacters = 1u;
2517 while( 0u == numberOfCharacters )
2520 numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
2524 if( index < mEventData->mPrimaryCursorPosition )
2526 cursorIndex -= numberOfCharacters;
2530 cursorIndex += numberOfCharacters;
2533 // Will update the cursor hook position.
2534 mEventData->mUpdateCursorHookPosition = true;
2539 void Controller::Impl::UpdateCursorPosition( const CursorInfo& cursorInfo )
2541 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "-->Controller::UpdateCursorPosition %p\n", this );
2542 if( NULL == mEventData )
2544 // Nothing to do if there is no text input.
2545 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition no event data\n" );
2549 const Vector2 cursorPosition = cursorInfo.primaryPosition + mScrollPosition;
2551 // Sets the cursor position.
2552 mEventData->mDecorator->SetPosition( PRIMARY_CURSOR,
2555 cursorInfo.primaryCursorHeight,
2556 cursorInfo.lineHeight );
2557 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Primary cursor position: %f,%f\n", cursorPosition.x, cursorPosition.y );
2559 if( mEventData->mUpdateGrabHandlePosition )
2561 // Sets the grab handle position.
2562 mEventData->mDecorator->SetPosition( GRAB_HANDLE,
2564 cursorInfo.lineOffset + mScrollPosition.y,
2565 cursorInfo.lineHeight );
2568 if( cursorInfo.isSecondaryCursor )
2570 mEventData->mDecorator->SetPosition( SECONDARY_CURSOR,
2571 cursorInfo.secondaryPosition.x + mScrollPosition.x,
2572 cursorInfo.secondaryPosition.y + mScrollPosition.y,
2573 cursorInfo.secondaryCursorHeight,
2574 cursorInfo.lineHeight );
2575 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Secondary cursor position: %f,%f\n", cursorInfo.secondaryPosition.x + mScrollPosition.x, cursorInfo.secondaryPosition.y + mScrollPosition.y );
2578 // Set which cursors are active according the state.
2579 if( EventData::IsEditingState( mEventData->mState ) || ( EventData::GRAB_HANDLE_PANNING == mEventData->mState ) )
2581 if( cursorInfo.isSecondaryCursor )
2583 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_BOTH );
2587 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
2592 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
2595 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition\n" );
2598 void Controller::Impl::UpdateSelectionHandle( HandleType handleType,
2599 const CursorInfo& cursorInfo )
2601 if( ( LEFT_SELECTION_HANDLE != handleType ) &&
2602 ( RIGHT_SELECTION_HANDLE != handleType ) )
2607 const Vector2 cursorPosition = cursorInfo.primaryPosition + mScrollPosition;
2609 // Sets the handle's position.
2610 mEventData->mDecorator->SetPosition( handleType,
2612 cursorInfo.lineOffset + mScrollPosition.y,
2613 cursorInfo.lineHeight );
2615 // If selection handle at start of the text and other at end of the text then all text is selected.
2616 const CharacterIndex startOfSelection = std::min( mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition );
2617 const CharacterIndex endOfSelection = std::max ( mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition );
2618 mEventData->mAllTextSelected = ( startOfSelection == 0 ) && ( endOfSelection == mLogicalModel->mText.Count() );
2621 void Controller::Impl::ClampHorizontalScroll( const Vector2& layoutSize )
2623 // Clamp between -space & 0.
2625 if( layoutSize.width > mVisualModel->mControlSize.width )
2627 const float space = ( layoutSize.width - mVisualModel->mControlSize.width );
2628 mScrollPosition.x = ( mScrollPosition.x < -space ) ? -space : mScrollPosition.x;
2629 mScrollPosition.x = ( mScrollPosition.x > 0.f ) ? 0.f : mScrollPosition.x;
2631 mEventData->mDecoratorUpdated = true;
2635 mScrollPosition.x = 0.f;
2639 void Controller::Impl::ClampVerticalScroll( const Vector2& layoutSize )
2641 // Clamp between -space & 0.
2642 if( layoutSize.height > mVisualModel->mControlSize.height )
2644 const float space = ( layoutSize.height - mVisualModel->mControlSize.height );
2645 mScrollPosition.y = ( mScrollPosition.y < -space ) ? -space : mScrollPosition.y;
2646 mScrollPosition.y = ( mScrollPosition.y > 0.f ) ? 0.f : mScrollPosition.y;
2648 mEventData->mDecoratorUpdated = true;
2652 mScrollPosition.y = 0.f;
2656 void Controller::Impl::ScrollToMakePositionVisible( const Vector2& position, float lineHeight )
2658 const float cursorWidth = mEventData->mDecorator ? static_cast<float>( mEventData->mDecorator->GetCursorWidth() ) : 0.f;
2660 // position is in actor's coords.
2661 const float positionEndX = position.x + cursorWidth;
2662 const float positionEndY = position.y + lineHeight;
2664 // Transform the position to decorator coords.
2665 const float decoratorPositionBeginX = position.x + mScrollPosition.x;
2666 const float decoratorPositionEndX = positionEndX + mScrollPosition.x;
2668 const float decoratorPositionBeginY = position.y + mScrollPosition.y;
2669 const float decoratorPositionEndY = positionEndY + mScrollPosition.y;
2671 if( decoratorPositionBeginX < 0.f )
2673 mScrollPosition.x = -position.x;
2675 else if( decoratorPositionEndX > mVisualModel->mControlSize.width )
2677 mScrollPosition.x = mVisualModel->mControlSize.width - positionEndX;
2680 if( decoratorPositionBeginY < 0.f )
2682 mScrollPosition.y = -position.y;
2684 else if( decoratorPositionEndY > mVisualModel->mControlSize.height )
2686 mScrollPosition.y = mVisualModel->mControlSize.height - positionEndY;
2690 void Controller::Impl::ScrollTextToMatchCursor( const CursorInfo& cursorInfo )
2692 // Get the current cursor position in decorator coords.
2693 const Vector2& currentCursorPosition = mEventData->mDecorator->GetPosition( PRIMARY_CURSOR );
2695 // Calculate the offset to match the cursor position before the character was deleted.
2696 mScrollPosition.x = currentCursorPosition.x - cursorInfo.primaryPosition.x;
2697 mScrollPosition.y = currentCursorPosition.y - cursorInfo.lineOffset;
2699 ClampHorizontalScroll( mVisualModel->GetLayoutSize() );
2700 ClampVerticalScroll( mVisualModel->GetLayoutSize() );
2702 // Makes the new cursor position visible if needed.
2703 ScrollToMakePositionVisible( cursorInfo.primaryPosition, cursorInfo.lineHeight );
2706 void Controller::Impl::RequestRelayout()
2708 mControlInterface.RequestTextRelayout();
2713 } // namespace Toolkit