2 * Copyright (c) 2017 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-control-interface.h>
35 #include <dali-toolkit/internal/text/text-run-container.h>
41 * @brief Struct used to calculate the selection box.
43 struct SelectionBoxInfo
51 #if defined(DEBUG_ENABLED)
52 Debug::Filter* gLogFilter = Debug::Filter::New(Debug::NoLogging, true, "LOG_TEXT_CONTROLS");
55 const float MAX_FLOAT = std::numeric_limits<float>::max();
56 const float MIN_FLOAT = std::numeric_limits<float>::min();
57 const Dali::Toolkit::Text::CharacterDirection LTR = false; ///< Left To Right direction
58 const uint32_t STAR = 0x2A;
71 EventData::EventData( DecoratorPtr decorator )
72 : mDecorator( decorator ),
74 mPlaceholderTextActive(),
75 mPlaceholderTextInactive(),
76 mPlaceholderTextColor( 0.8f, 0.8f, 0.8f, 0.8f ),
78 mInputStyleChangedQueue(),
79 mPreviousState( INACTIVE ),
81 mPrimaryCursorPosition( 0u ),
82 mLeftSelectionPosition( 0u ),
83 mRightSelectionPosition( 0u ),
84 mPreEditStartPosition( 0u ),
86 mCursorHookPositionX( 0.f ),
87 mIsShowingPlaceholderText( false ),
88 mPreEditFlag( false ),
89 mDecoratorUpdated( false ),
90 mCursorBlinkEnabled( true ),
91 mGrabHandleEnabled( true ),
92 mGrabHandlePopupEnabled( true ),
93 mSelectionEnabled( true ),
94 mUpdateCursorHookPosition( false ),
95 mUpdateCursorPosition( false ),
96 mUpdateGrabHandlePosition( false ),
97 mUpdateLeftSelectionPosition( false ),
98 mUpdateRightSelectionPosition( false ),
99 mIsLeftHandleSelected( false ),
100 mUpdateHighlightBox( false ),
101 mScrollAfterUpdatePosition( false ),
102 mScrollAfterDelete( false ),
103 mAllTextSelected( false ),
104 mUpdateInputStyle( false ),
105 mPasswordInput( false )
107 mImfManager = ImfManager::Get();
110 EventData::~EventData()
113 bool Controller::Impl::ProcessInputEvents()
115 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "-->Controller::ProcessInputEvents\n" );
116 if( NULL == mEventData )
118 // Nothing to do if there is no text input.
119 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::ProcessInputEvents no event data\n" );
123 if( mEventData->mDecorator )
125 for( std::vector<Event>::iterator iter = mEventData->mEventQueue.begin();
126 iter != mEventData->mEventQueue.end();
131 case Event::CURSOR_KEY_EVENT:
133 OnCursorKeyEvent( *iter );
136 case Event::TAP_EVENT:
141 case Event::LONG_PRESS_EVENT:
143 OnLongPressEvent( *iter );
146 case Event::PAN_EVENT:
151 case Event::GRAB_HANDLE_EVENT:
152 case Event::LEFT_SELECTION_HANDLE_EVENT:
153 case Event::RIGHT_SELECTION_HANDLE_EVENT: // Fall through
155 OnHandleEvent( *iter );
160 OnSelectEvent( *iter );
163 case Event::SELECT_ALL:
172 if( mEventData->mUpdateCursorPosition ||
173 mEventData->mUpdateHighlightBox )
178 // The cursor must also be repositioned after inserts into the model
179 if( mEventData->mUpdateCursorPosition )
181 // Updates the cursor position and scrolls the text to make it visible.
182 CursorInfo cursorInfo;
183 // Calculate the cursor position from the new cursor index.
184 GetCursorPosition( mEventData->mPrimaryCursorPosition,
187 if( mEventData->mUpdateCursorHookPosition )
189 // Update the cursor hook position. Used to move the cursor with the keys 'up' and 'down'.
190 mEventData->mCursorHookPositionX = cursorInfo.primaryPosition.x;
191 mEventData->mUpdateCursorHookPosition = false;
194 // Scroll first the text after delete ...
195 if( mEventData->mScrollAfterDelete )
197 ScrollTextToMatchCursor( cursorInfo );
200 // ... then, text can be scrolled to make the cursor visible.
201 if( mEventData->mScrollAfterUpdatePosition )
203 const Vector2 currentCursorPosition( cursorInfo.primaryPosition.x, cursorInfo.lineOffset );
204 ScrollToMakePositionVisible( currentCursorPosition, cursorInfo.lineHeight );
206 mEventData->mScrollAfterUpdatePosition = false;
207 mEventData->mScrollAfterDelete = false;
209 UpdateCursorPosition( cursorInfo );
211 mEventData->mDecoratorUpdated = true;
212 mEventData->mUpdateCursorPosition = false;
213 mEventData->mUpdateGrabHandlePosition = false;
217 CursorInfo leftHandleInfo;
218 CursorInfo rightHandleInfo;
220 if( mEventData->mUpdateHighlightBox )
222 GetCursorPosition( mEventData->mLeftSelectionPosition,
225 GetCursorPosition( mEventData->mRightSelectionPosition,
228 if( mEventData->mScrollAfterUpdatePosition && ( mEventData->mIsLeftHandleSelected ? mEventData->mUpdateLeftSelectionPosition : mEventData->mUpdateRightSelectionPosition ) )
230 CursorInfo& info = mEventData->mIsLeftHandleSelected ? leftHandleInfo : rightHandleInfo;
232 const Vector2 currentCursorPosition( info.primaryPosition.x, info.lineOffset );
233 ScrollToMakePositionVisible( currentCursorPosition, info.lineHeight );
237 if( mEventData->mUpdateLeftSelectionPosition )
239 UpdateSelectionHandle( LEFT_SELECTION_HANDLE,
243 mEventData->mDecoratorUpdated = true;
244 mEventData->mUpdateLeftSelectionPosition = false;
247 if( mEventData->mUpdateRightSelectionPosition )
249 UpdateSelectionHandle( RIGHT_SELECTION_HANDLE,
253 mEventData->mDecoratorUpdated = true;
254 mEventData->mUpdateRightSelectionPosition = false;
257 if( mEventData->mUpdateHighlightBox )
259 RepositionSelectionHandles();
261 mEventData->mUpdateLeftSelectionPosition = false;
262 mEventData->mUpdateRightSelectionPosition = false;
263 mEventData->mUpdateHighlightBox = false;
266 mEventData->mScrollAfterUpdatePosition = false;
269 if( mEventData->mUpdateInputStyle )
271 // Keep a copy of the current input style.
272 InputStyle currentInputStyle;
273 currentInputStyle.Copy( mEventData->mInputStyle );
275 // Set the default style first.
276 RetrieveDefaultInputStyle( mEventData->mInputStyle );
278 // Get the character index from the cursor index.
279 const CharacterIndex styleIndex = ( mEventData->mPrimaryCursorPosition > 0u ) ? mEventData->mPrimaryCursorPosition - 1u : 0u;
281 // Retrieve the style from the style runs stored in the logical model.
282 mModel->mLogicalModel->RetrieveStyle( styleIndex, mEventData->mInputStyle );
284 // Compare if the input style has changed.
285 const bool hasInputStyleChanged = !currentInputStyle.Equal( mEventData->mInputStyle );
287 if( hasInputStyleChanged )
289 const InputStyle::Mask styleChangedMask = currentInputStyle.GetInputStyleChangeMask( mEventData->mInputStyle );
290 // Queue the input style changed signal.
291 mEventData->mInputStyleChangedQueue.PushBack( styleChangedMask );
294 mEventData->mUpdateInputStyle = false;
297 mEventData->mEventQueue.clear();
299 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::ProcessInputEvents\n" );
301 const bool decoratorUpdated = mEventData->mDecoratorUpdated;
302 mEventData->mDecoratorUpdated = false;
304 return decoratorUpdated;
307 void Controller::Impl::NotifyImfManager()
309 if( mEventData && mEventData->mImfManager )
311 CharacterIndex cursorPosition = GetLogicalCursorPosition();
313 const Length numberOfWhiteSpaces = GetNumberOfWhiteSpaces( 0u );
315 // Update the cursor position by removing the initial white spaces.
316 if( cursorPosition < numberOfWhiteSpaces )
322 cursorPosition -= numberOfWhiteSpaces;
325 mEventData->mImfManager.SetCursorPosition( cursorPosition );
326 mEventData->mImfManager.NotifyCursorPosition();
330 void Controller::Impl::NotifyImfMultiLineStatus()
334 Text::Layout::Engine::Type layout = mLayoutEngine.GetLayout();
335 mEventData->mImfManager.NotifyTextInputMultiLine( layout == Text::Layout::Engine::MULTI_LINE_BOX );
339 CharacterIndex Controller::Impl::GetLogicalCursorPosition() const
341 CharacterIndex cursorPosition = 0u;
345 if( ( EventData::SELECTING == mEventData->mState ) ||
346 ( EventData::SELECTION_HANDLE_PANNING == mEventData->mState ) )
348 cursorPosition = std::min( mEventData->mRightSelectionPosition, mEventData->mLeftSelectionPosition );
352 cursorPosition = mEventData->mPrimaryCursorPosition;
356 return cursorPosition;
359 Length Controller::Impl::GetNumberOfWhiteSpaces( CharacterIndex index ) const
361 Length numberOfWhiteSpaces = 0u;
363 // Get the buffer to the text.
364 Character* utf32CharacterBuffer = mModel->mLogicalModel->mText.Begin();
366 const Length totalNumberOfCharacters = mModel->mLogicalModel->mText.Count();
367 for( ; index < totalNumberOfCharacters; ++index, ++numberOfWhiteSpaces )
369 if( !TextAbstraction::IsWhiteSpace( *( utf32CharacterBuffer + index ) ) )
375 return numberOfWhiteSpaces;
378 void Controller::Impl::GetText( CharacterIndex index, std::string& text ) const
380 // Get the total number of characters.
381 Length numberOfCharacters = mModel->mLogicalModel->mText.Count();
383 // Retrieve the text.
384 if( 0u != numberOfCharacters )
386 Utf32ToUtf8( mModel->mLogicalModel->mText.Begin() + index, numberOfCharacters - index, text );
390 void Controller::Impl::CalculateTextUpdateIndices( Length& numberOfCharacters )
392 mTextUpdateInfo.mParagraphCharacterIndex = 0u;
393 mTextUpdateInfo.mStartGlyphIndex = 0u;
394 mTextUpdateInfo.mStartLineIndex = 0u;
395 numberOfCharacters = 0u;
397 const Length numberOfParagraphs = mModel->mLogicalModel->mParagraphInfo.Count();
398 if( 0u == numberOfParagraphs )
400 mTextUpdateInfo.mParagraphCharacterIndex = 0u;
401 numberOfCharacters = 0u;
403 mTextUpdateInfo.mRequestedNumberOfCharacters = mTextUpdateInfo.mNumberOfCharactersToAdd - mTextUpdateInfo.mNumberOfCharactersToRemove;
405 // Nothing else to do if there are no paragraphs.
409 // Find the paragraphs to be updated.
410 Vector<ParagraphRunIndex> paragraphsToBeUpdated;
411 if( mTextUpdateInfo.mCharacterIndex >= mTextUpdateInfo.mPreviousNumberOfCharacters )
413 // Text is being added at the end of the current text.
414 if( mTextUpdateInfo.mIsLastCharacterNewParagraph )
416 // Text is being added in a new paragraph after the last character of the text.
417 mTextUpdateInfo.mParagraphCharacterIndex = mTextUpdateInfo.mPreviousNumberOfCharacters;
418 numberOfCharacters = 0u;
419 mTextUpdateInfo.mRequestedNumberOfCharacters = mTextUpdateInfo.mNumberOfCharactersToAdd - mTextUpdateInfo.mNumberOfCharactersToRemove;
421 mTextUpdateInfo.mStartGlyphIndex = mModel->mVisualModel->mGlyphs.Count();
422 mTextUpdateInfo.mStartLineIndex = mModel->mVisualModel->mLines.Count() - 1u;
424 // Nothing else to do;
428 paragraphsToBeUpdated.PushBack( numberOfParagraphs - 1u );
432 Length numberOfCharactersToUpdate = 0u;
433 if( mTextUpdateInfo.mFullRelayoutNeeded )
435 numberOfCharactersToUpdate = mTextUpdateInfo.mPreviousNumberOfCharacters;
439 numberOfCharactersToUpdate = ( mTextUpdateInfo.mNumberOfCharactersToRemove > 0u ) ? mTextUpdateInfo.mNumberOfCharactersToRemove : 1u;
441 mModel->mLogicalModel->FindParagraphs( mTextUpdateInfo.mCharacterIndex,
442 numberOfCharactersToUpdate,
443 paragraphsToBeUpdated );
446 if( 0u != paragraphsToBeUpdated.Count() )
448 const ParagraphRunIndex firstParagraphIndex = *( paragraphsToBeUpdated.Begin() );
449 const ParagraphRun& firstParagraph = *( mModel->mLogicalModel->mParagraphInfo.Begin() + firstParagraphIndex );
450 mTextUpdateInfo.mParagraphCharacterIndex = firstParagraph.characterRun.characterIndex;
452 ParagraphRunIndex lastParagraphIndex = *( paragraphsToBeUpdated.End() - 1u );
453 const ParagraphRun& lastParagraph = *( mModel->mLogicalModel->mParagraphInfo.Begin() + lastParagraphIndex );
455 if( ( mTextUpdateInfo.mNumberOfCharactersToRemove > 0u ) && // Some character are removed.
456 ( lastParagraphIndex < numberOfParagraphs - 1u ) && // There is a next paragraph.
457 ( ( lastParagraph.characterRun.characterIndex + lastParagraph.characterRun.numberOfCharacters ) == // The last removed character is the new paragraph character.
458 ( mTextUpdateInfo.mCharacterIndex + mTextUpdateInfo.mNumberOfCharactersToRemove ) ) )
460 // The new paragraph character of the last updated paragraph has been removed so is going to be merged with the next one.
461 const ParagraphRun& lastParagraph = *( mModel->mLogicalModel->mParagraphInfo.Begin() + lastParagraphIndex + 1u );
463 numberOfCharacters = lastParagraph.characterRun.characterIndex + lastParagraph.characterRun.numberOfCharacters - mTextUpdateInfo.mParagraphCharacterIndex;
467 numberOfCharacters = lastParagraph.characterRun.characterIndex + lastParagraph.characterRun.numberOfCharacters - mTextUpdateInfo.mParagraphCharacterIndex;
471 mTextUpdateInfo.mRequestedNumberOfCharacters = numberOfCharacters + mTextUpdateInfo.mNumberOfCharactersToAdd - mTextUpdateInfo.mNumberOfCharactersToRemove;
472 mTextUpdateInfo.mStartGlyphIndex = *( mModel->mVisualModel->mCharactersToGlyph.Begin() + mTextUpdateInfo.mParagraphCharacterIndex );
475 void Controller::Impl::ClearFullModelData( OperationsMask operations )
477 if( NO_OPERATION != ( GET_LINE_BREAKS & operations ) )
479 mModel->mLogicalModel->mLineBreakInfo.Clear();
480 mModel->mLogicalModel->mParagraphInfo.Clear();
483 if( NO_OPERATION != ( GET_WORD_BREAKS & operations ) )
485 mModel->mLogicalModel->mLineBreakInfo.Clear();
488 if( NO_OPERATION != ( GET_SCRIPTS & operations ) )
490 mModel->mLogicalModel->mScriptRuns.Clear();
493 if( NO_OPERATION != ( VALIDATE_FONTS & operations ) )
495 mModel->mLogicalModel->mFontRuns.Clear();
498 if( 0u != mModel->mLogicalModel->mBidirectionalParagraphInfo.Count() )
500 if( NO_OPERATION != ( BIDI_INFO & operations ) )
502 mModel->mLogicalModel->mBidirectionalParagraphInfo.Clear();
503 mModel->mLogicalModel->mCharacterDirections.Clear();
506 if( NO_OPERATION != ( REORDER & operations ) )
508 // Free the allocated memory used to store the conversion table in the bidirectional line info run.
509 for( Vector<BidirectionalLineInfoRun>::Iterator it = mModel->mLogicalModel->mBidirectionalLineInfo.Begin(),
510 endIt = mModel->mLogicalModel->mBidirectionalLineInfo.End();
514 BidirectionalLineInfoRun& bidiLineInfo = *it;
516 free( bidiLineInfo.visualToLogicalMap );
517 bidiLineInfo.visualToLogicalMap = NULL;
519 mModel->mLogicalModel->mBidirectionalLineInfo.Clear();
523 if( NO_OPERATION != ( SHAPE_TEXT & operations ) )
525 mModel->mVisualModel->mGlyphs.Clear();
526 mModel->mVisualModel->mGlyphsToCharacters.Clear();
527 mModel->mVisualModel->mCharactersToGlyph.Clear();
528 mModel->mVisualModel->mCharactersPerGlyph.Clear();
529 mModel->mVisualModel->mGlyphsPerCharacter.Clear();
530 mModel->mVisualModel->mGlyphPositions.Clear();
533 if( NO_OPERATION != ( LAYOUT & operations ) )
535 mModel->mVisualModel->mLines.Clear();
538 if( NO_OPERATION != ( COLOR & operations ) )
540 mModel->mVisualModel->mColorIndices.Clear();
544 void Controller::Impl::ClearCharacterModelData( CharacterIndex startIndex, CharacterIndex endIndex, OperationsMask operations )
546 const CharacterIndex endIndexPlusOne = endIndex + 1u;
548 if( NO_OPERATION != ( GET_LINE_BREAKS & operations ) )
550 // Clear the line break info.
551 LineBreakInfo* lineBreakInfoBuffer = mModel->mLogicalModel->mLineBreakInfo.Begin();
553 mModel->mLogicalModel->mLineBreakInfo.Erase( lineBreakInfoBuffer + startIndex,
554 lineBreakInfoBuffer + endIndexPlusOne );
556 // Clear the paragraphs.
557 ClearCharacterRuns( startIndex,
559 mModel->mLogicalModel->mParagraphInfo );
562 if( NO_OPERATION != ( GET_WORD_BREAKS & operations ) )
564 // Clear the word break info.
565 WordBreakInfo* wordBreakInfoBuffer = mModel->mLogicalModel->mWordBreakInfo.Begin();
567 mModel->mLogicalModel->mWordBreakInfo.Erase( wordBreakInfoBuffer + startIndex,
568 wordBreakInfoBuffer + endIndexPlusOne );
571 if( NO_OPERATION != ( GET_SCRIPTS & operations ) )
573 // Clear the scripts.
574 ClearCharacterRuns( startIndex,
576 mModel->mLogicalModel->mScriptRuns );
579 if( NO_OPERATION != ( VALIDATE_FONTS & operations ) )
582 ClearCharacterRuns( startIndex,
584 mModel->mLogicalModel->mFontRuns );
587 if( 0u != mModel->mLogicalModel->mBidirectionalParagraphInfo.Count() )
589 if( NO_OPERATION != ( BIDI_INFO & operations ) )
591 // Clear the bidirectional paragraph info.
592 ClearCharacterRuns( startIndex,
594 mModel->mLogicalModel->mBidirectionalParagraphInfo );
596 // Clear the character's directions.
597 CharacterDirection* characterDirectionsBuffer = mModel->mLogicalModel->mCharacterDirections.Begin();
599 mModel->mLogicalModel->mCharacterDirections.Erase( characterDirectionsBuffer + startIndex,
600 characterDirectionsBuffer + endIndexPlusOne );
603 if( NO_OPERATION != ( REORDER & operations ) )
605 uint32_t startRemoveIndex = mModel->mLogicalModel->mBidirectionalLineInfo.Count();
606 uint32_t endRemoveIndex = startRemoveIndex;
607 ClearCharacterRuns( startIndex,
609 mModel->mLogicalModel->mBidirectionalLineInfo,
613 BidirectionalLineInfoRun* bidirectionalLineInfoBuffer = mModel->mLogicalModel->mBidirectionalLineInfo.Begin();
615 // Free the allocated memory used to store the conversion table in the bidirectional line info run.
616 for( Vector<BidirectionalLineInfoRun>::Iterator it = bidirectionalLineInfoBuffer + startRemoveIndex,
617 endIt = bidirectionalLineInfoBuffer + endRemoveIndex;
621 BidirectionalLineInfoRun& bidiLineInfo = *it;
623 free( bidiLineInfo.visualToLogicalMap );
624 bidiLineInfo.visualToLogicalMap = NULL;
627 mModel->mLogicalModel->mBidirectionalLineInfo.Erase( bidirectionalLineInfoBuffer + startRemoveIndex,
628 bidirectionalLineInfoBuffer + endRemoveIndex );
633 void Controller::Impl::ClearGlyphModelData( CharacterIndex startIndex, CharacterIndex endIndex, OperationsMask operations )
635 const CharacterIndex endIndexPlusOne = endIndex + 1u;
636 const Length numberOfCharactersRemoved = endIndexPlusOne - startIndex;
638 // Convert the character index to glyph index before deleting the character to glyph and the glyphs per character buffers.
639 GlyphIndex* charactersToGlyphBuffer = mModel->mVisualModel->mCharactersToGlyph.Begin();
640 Length* glyphsPerCharacterBuffer = mModel->mVisualModel->mGlyphsPerCharacter.Begin();
642 const GlyphIndex endGlyphIndexPlusOne = *( charactersToGlyphBuffer + endIndex ) + *( glyphsPerCharacterBuffer + endIndex );
643 const Length numberOfGlyphsRemoved = endGlyphIndexPlusOne - mTextUpdateInfo.mStartGlyphIndex;
645 if( NO_OPERATION != ( SHAPE_TEXT & operations ) )
647 // Update the character to glyph indices.
648 for( Vector<GlyphIndex>::Iterator it = charactersToGlyphBuffer + endIndexPlusOne,
649 endIt = charactersToGlyphBuffer + mModel->mVisualModel->mCharactersToGlyph.Count();
653 CharacterIndex& index = *it;
654 index -= numberOfGlyphsRemoved;
657 // Clear the character to glyph conversion table.
658 mModel->mVisualModel->mCharactersToGlyph.Erase( charactersToGlyphBuffer + startIndex,
659 charactersToGlyphBuffer + endIndexPlusOne );
661 // Clear the glyphs per character table.
662 mModel->mVisualModel->mGlyphsPerCharacter.Erase( glyphsPerCharacterBuffer + startIndex,
663 glyphsPerCharacterBuffer + endIndexPlusOne );
665 // Clear the glyphs buffer.
666 GlyphInfo* glyphsBuffer = mModel->mVisualModel->mGlyphs.Begin();
667 mModel->mVisualModel->mGlyphs.Erase( glyphsBuffer + mTextUpdateInfo.mStartGlyphIndex,
668 glyphsBuffer + endGlyphIndexPlusOne );
670 CharacterIndex* glyphsToCharactersBuffer = mModel->mVisualModel->mGlyphsToCharacters.Begin();
672 // Update the glyph to character indices.
673 for( Vector<CharacterIndex>::Iterator it = glyphsToCharactersBuffer + endGlyphIndexPlusOne,
674 endIt = glyphsToCharactersBuffer + mModel->mVisualModel->mGlyphsToCharacters.Count();
678 CharacterIndex& index = *it;
679 index -= numberOfCharactersRemoved;
682 // Clear the glyphs to characters buffer.
683 mModel->mVisualModel->mGlyphsToCharacters.Erase( glyphsToCharactersBuffer + mTextUpdateInfo.mStartGlyphIndex,
684 glyphsToCharactersBuffer + endGlyphIndexPlusOne );
686 // Clear the characters per glyph buffer.
687 Length* charactersPerGlyphBuffer = mModel->mVisualModel->mCharactersPerGlyph.Begin();
688 mModel->mVisualModel->mCharactersPerGlyph.Erase( charactersPerGlyphBuffer + mTextUpdateInfo.mStartGlyphIndex,
689 charactersPerGlyphBuffer + endGlyphIndexPlusOne );
691 // Clear the positions buffer.
692 Vector2* positionsBuffer = mModel->mVisualModel->mGlyphPositions.Begin();
693 mModel->mVisualModel->mGlyphPositions.Erase( positionsBuffer + mTextUpdateInfo.mStartGlyphIndex,
694 positionsBuffer + endGlyphIndexPlusOne );
697 if( NO_OPERATION != ( LAYOUT & operations ) )
700 uint32_t startRemoveIndex = mModel->mVisualModel->mLines.Count();
701 uint32_t endRemoveIndex = startRemoveIndex;
702 ClearCharacterRuns( startIndex,
704 mModel->mVisualModel->mLines,
708 // Will update the glyph runs.
709 startRemoveIndex = mModel->mVisualModel->mLines.Count();
710 endRemoveIndex = startRemoveIndex;
711 ClearGlyphRuns( mTextUpdateInfo.mStartGlyphIndex,
712 endGlyphIndexPlusOne - 1u,
713 mModel->mVisualModel->mLines,
717 // Set the line index from where to insert the new laid-out lines.
718 mTextUpdateInfo.mStartLineIndex = startRemoveIndex;
720 LineRun* linesBuffer = mModel->mVisualModel->mLines.Begin();
721 mModel->mVisualModel->mLines.Erase( linesBuffer + startRemoveIndex,
722 linesBuffer + endRemoveIndex );
725 if( NO_OPERATION != ( COLOR & operations ) )
727 if( 0u != mModel->mVisualModel->mColorIndices.Count() )
729 ColorIndex* colorIndexBuffer = mModel->mVisualModel->mColorIndices.Begin();
730 mModel->mVisualModel->mColorIndices.Erase( colorIndexBuffer + mTextUpdateInfo.mStartGlyphIndex,
731 colorIndexBuffer + endGlyphIndexPlusOne );
736 void Controller::Impl::ClearModelData( CharacterIndex startIndex, CharacterIndex endIndex, OperationsMask operations )
738 if( mTextUpdateInfo.mClearAll ||
739 ( ( 0u == startIndex ) &&
740 ( mTextUpdateInfo.mPreviousNumberOfCharacters == endIndex + 1u ) ) )
742 ClearFullModelData( operations );
746 // Clear the model data related with characters.
747 ClearCharacterModelData( startIndex, endIndex, operations );
749 // Clear the model data related with glyphs.
750 ClearGlyphModelData( startIndex, endIndex, operations );
753 // The estimated number of lines. Used to avoid reallocations when layouting.
754 mTextUpdateInfo.mEstimatedNumberOfLines = std::max( mModel->mVisualModel->mLines.Count(), mModel->mLogicalModel->mParagraphInfo.Count() );
756 mModel->mVisualModel->ClearCaches();
759 bool Controller::Impl::UpdateModel( OperationsMask operationsRequired )
761 DALI_LOG_INFO( gLogFilter, Debug::General, "Controller::UpdateModel\n" );
763 // Calculate the operations to be done.
764 const OperationsMask operations = static_cast<OperationsMask>( mOperationsPending & operationsRequired );
766 if( NO_OPERATION == operations )
768 // Nothing to do if no operations are pending and required.
772 Vector<Character> utf32CharactersStar;
773 const Length characterCount = mModel->mLogicalModel->mText.Count();
774 const bool isPasswordInput = ( mEventData != NULL && mEventData->mPasswordInput &&
775 !mEventData->mIsShowingPlaceholderText && characterCount > 0 );
779 utf32CharactersStar.Resize( characterCount );
781 uint32_t* begin = utf32CharactersStar.Begin();
782 uint32_t* end = begin + characterCount;
783 while ( begin < end )
789 Vector<Character>& utf32Characters = isPasswordInput ? utf32CharactersStar : mModel->mLogicalModel->mText;
790 const Length numberOfCharacters = utf32Characters.Count();
792 // Index to the first character of the first paragraph to be updated.
793 CharacterIndex startIndex = 0u;
794 // Number of characters of the paragraphs to be removed.
795 Length paragraphCharacters = 0u;
797 CalculateTextUpdateIndices( paragraphCharacters );
798 startIndex = mTextUpdateInfo.mParagraphCharacterIndex;
800 if( mTextUpdateInfo.mClearAll ||
801 ( 0u != paragraphCharacters ) )
803 ClearModelData( startIndex, startIndex + ( ( paragraphCharacters > 0u ) ? paragraphCharacters - 1u : 0u ), operations );
806 mTextUpdateInfo.mClearAll = false;
808 // Whether the model is updated.
809 bool updated = false;
811 Vector<LineBreakInfo>& lineBreakInfo = mModel->mLogicalModel->mLineBreakInfo;
812 const Length requestedNumberOfCharacters = mTextUpdateInfo.mRequestedNumberOfCharacters;
814 if( NO_OPERATION != ( GET_LINE_BREAKS & operations ) )
816 // Retrieves the line break info. The line break info is used to split the text in 'paragraphs' to
817 // calculate the bidirectional info for each 'paragraph'.
818 // It's also used to layout the text (where it should be a new line) or to shape the text (text in different lines
819 // is not shaped together).
820 lineBreakInfo.Resize( numberOfCharacters, TextAbstraction::LINE_NO_BREAK );
822 SetLineBreakInfo( utf32Characters,
824 requestedNumberOfCharacters,
827 // Create the paragraph info.
828 mModel->mLogicalModel->CreateParagraphInfo( startIndex,
829 requestedNumberOfCharacters );
833 Vector<WordBreakInfo>& wordBreakInfo = mModel->mLogicalModel->mWordBreakInfo;
834 if( NO_OPERATION != ( GET_WORD_BREAKS & operations ) )
836 // Retrieves the word break info. The word break info is used to layout the text (where to wrap the text in lines).
837 wordBreakInfo.Resize( numberOfCharacters, TextAbstraction::WORD_NO_BREAK );
839 SetWordBreakInfo( utf32Characters,
841 requestedNumberOfCharacters,
846 const bool getScripts = NO_OPERATION != ( GET_SCRIPTS & operations );
847 const bool validateFonts = NO_OPERATION != ( VALIDATE_FONTS & operations );
849 Vector<ScriptRun>& scripts = mModel->mLogicalModel->mScriptRuns;
850 Vector<FontRun>& validFonts = mModel->mLogicalModel->mFontRuns;
852 if( getScripts || validateFonts )
854 // Validates the fonts assigned by the application or assigns default ones.
855 // It makes sure all the characters are going to be rendered by the correct font.
856 MultilanguageSupport multilanguageSupport = MultilanguageSupport::Get();
860 // Retrieves the scripts used in the text.
861 multilanguageSupport.SetScripts( utf32Characters,
863 requestedNumberOfCharacters,
869 // Validate the fonts set through the mark-up string.
870 Vector<FontDescriptionRun>& fontDescriptionRuns = mModel->mLogicalModel->mFontDescriptionRuns;
872 // Get the default font's description.
873 TextAbstraction::FontDescription defaultFontDescription;
874 TextAbstraction::PointSize26Dot6 defaultPointSize = TextAbstraction::FontClient::DEFAULT_POINT_SIZE;
875 if( NULL != mFontDefaults )
877 defaultFontDescription = mFontDefaults->mFontDescription;
878 defaultPointSize = mFontDefaults->mDefaultPointSize * 64u;
881 // Validates the fonts. If there is a character with no assigned font it sets a default one.
882 // After this call, fonts are validated.
883 multilanguageSupport.ValidateFonts( utf32Characters,
886 defaultFontDescription,
889 requestedNumberOfCharacters,
895 Vector<Character> mirroredUtf32Characters;
896 bool textMirrored = false;
897 const Length numberOfParagraphs = mModel->mLogicalModel->mParagraphInfo.Count();
898 if( NO_OPERATION != ( BIDI_INFO & operations ) )
900 Vector<BidirectionalParagraphInfoRun>& bidirectionalInfo = mModel->mLogicalModel->mBidirectionalParagraphInfo;
901 bidirectionalInfo.Reserve( numberOfParagraphs );
903 // Calculates the bidirectional info for the whole paragraph if it contains right to left scripts.
904 SetBidirectionalInfo( utf32Characters,
908 requestedNumberOfCharacters,
911 if( 0u != bidirectionalInfo.Count() )
913 // Only set the character directions if there is right to left characters.
914 Vector<CharacterDirection>& directions = mModel->mLogicalModel->mCharacterDirections;
915 GetCharactersDirection( bidirectionalInfo,
918 requestedNumberOfCharacters,
921 // This paragraph has right to left text. Some characters may need to be mirrored.
922 // TODO: consider if the mirrored string can be stored as well.
924 textMirrored = GetMirroredText( utf32Characters,
928 requestedNumberOfCharacters,
929 mirroredUtf32Characters );
933 // There is no right to left characters. Clear the directions vector.
934 mModel->mLogicalModel->mCharacterDirections.Clear();
939 Vector<GlyphInfo>& glyphs = mModel->mVisualModel->mGlyphs;
940 Vector<CharacterIndex>& glyphsToCharactersMap = mModel->mVisualModel->mGlyphsToCharacters;
941 Vector<Length>& charactersPerGlyph = mModel->mVisualModel->mCharactersPerGlyph;
942 Vector<GlyphIndex> newParagraphGlyphs;
943 newParagraphGlyphs.Reserve( numberOfParagraphs );
945 const Length currentNumberOfGlyphs = glyphs.Count();
946 if( NO_OPERATION != ( SHAPE_TEXT & operations ) )
948 const Vector<Character>& textToShape = textMirrored ? mirroredUtf32Characters : utf32Characters;
950 ShapeText( textToShape,
955 mTextUpdateInfo.mStartGlyphIndex,
956 requestedNumberOfCharacters,
958 glyphsToCharactersMap,
960 newParagraphGlyphs );
962 // Create the 'number of glyphs' per character and the glyph to character conversion tables.
963 mModel->mVisualModel->CreateGlyphsPerCharacterTable( startIndex, mTextUpdateInfo.mStartGlyphIndex, requestedNumberOfCharacters );
964 mModel->mVisualModel->CreateCharacterToGlyphTable( startIndex, mTextUpdateInfo.mStartGlyphIndex, requestedNumberOfCharacters );
968 const Length numberOfGlyphs = glyphs.Count() - currentNumberOfGlyphs;
970 if( NO_OPERATION != ( GET_GLYPH_METRICS & operations ) )
972 GlyphInfo* glyphsBuffer = glyphs.Begin();
973 mMetrics->GetGlyphMetrics( glyphsBuffer + mTextUpdateInfo.mStartGlyphIndex, numberOfGlyphs );
975 // Update the width and advance of all new paragraph characters.
976 for( Vector<GlyphIndex>::ConstIterator it = newParagraphGlyphs.Begin(), endIt = newParagraphGlyphs.End(); it != endIt; ++it )
978 const GlyphIndex index = *it;
979 GlyphInfo& glyph = *( glyphsBuffer + index );
981 glyph.xBearing = 0.f;
988 if( NO_OPERATION != ( COLOR & operations ) )
990 // Set the color runs in glyphs.
991 SetColorSegmentationInfo( mModel->mLogicalModel->mColorRuns,
992 mModel->mVisualModel->mCharactersToGlyph,
993 mModel->mVisualModel->mGlyphsPerCharacter,
995 mTextUpdateInfo.mStartGlyphIndex,
996 requestedNumberOfCharacters,
997 mModel->mVisualModel->mColors,
998 mModel->mVisualModel->mColorIndices );
1003 if( ( NULL != mEventData ) &&
1004 mEventData->mPreEditFlag &&
1005 ( 0u != mModel->mVisualModel->mCharactersToGlyph.Count() ) )
1007 // Add the underline for the pre-edit text.
1008 const GlyphIndex* const charactersToGlyphBuffer = mModel->mVisualModel->mCharactersToGlyph.Begin();
1009 const Length* const glyphsPerCharacterBuffer = mModel->mVisualModel->mGlyphsPerCharacter.Begin();
1011 const GlyphIndex glyphStart = *( charactersToGlyphBuffer + mEventData->mPreEditStartPosition );
1012 const CharacterIndex lastPreEditCharacter = mEventData->mPreEditStartPosition + ( ( mEventData->mPreEditLength > 0u ) ? mEventData->mPreEditLength - 1u : 0u );
1013 const Length numberOfGlyphsLastCharacter = *( glyphsPerCharacterBuffer + lastPreEditCharacter );
1014 const GlyphIndex glyphEnd = *( charactersToGlyphBuffer + lastPreEditCharacter ) + ( numberOfGlyphsLastCharacter > 1u ? numberOfGlyphsLastCharacter - 1u : 0u );
1016 GlyphRun underlineRun;
1017 underlineRun.glyphIndex = glyphStart;
1018 underlineRun.numberOfGlyphs = 1u + glyphEnd - glyphStart;
1020 // TODO: At the moment the underline runs are only for pre-edit.
1021 mModel->mVisualModel->mUnderlineRuns.PushBack( underlineRun );
1024 // The estimated number of lines. Used to avoid reallocations when layouting.
1025 mTextUpdateInfo.mEstimatedNumberOfLines = std::max( mModel->mVisualModel->mLines.Count(), mModel->mLogicalModel->mParagraphInfo.Count() );
1027 // Set the previous number of characters for the next time the text is updated.
1028 mTextUpdateInfo.mPreviousNumberOfCharacters = numberOfCharacters;
1033 void Controller::Impl::RetrieveDefaultInputStyle( InputStyle& inputStyle )
1035 // Sets the default text's color.
1036 inputStyle.textColor = mTextColor;
1037 inputStyle.isDefaultColor = true;
1039 inputStyle.familyName.clear();
1040 inputStyle.weight = TextAbstraction::FontWeight::NORMAL;
1041 inputStyle.width = TextAbstraction::FontWidth::NORMAL;
1042 inputStyle.slant = TextAbstraction::FontSlant::NORMAL;
1043 inputStyle.size = 0.f;
1045 inputStyle.lineSpacing = 0.f;
1047 inputStyle.underlineProperties.clear();
1048 inputStyle.shadowProperties.clear();
1049 inputStyle.embossProperties.clear();
1050 inputStyle.outlineProperties.clear();
1052 inputStyle.isFamilyDefined = false;
1053 inputStyle.isWeightDefined = false;
1054 inputStyle.isWidthDefined = false;
1055 inputStyle.isSlantDefined = false;
1056 inputStyle.isSizeDefined = false;
1058 inputStyle.isLineSpacingDefined = false;
1060 inputStyle.isUnderlineDefined = false;
1061 inputStyle.isShadowDefined = false;
1062 inputStyle.isEmbossDefined = false;
1063 inputStyle.isOutlineDefined = false;
1065 // Sets the default font's family name, weight, width, slant and size.
1068 if( mFontDefaults->familyDefined )
1070 inputStyle.familyName = mFontDefaults->mFontDescription.family;
1071 inputStyle.isFamilyDefined = true;
1074 if( mFontDefaults->weightDefined )
1076 inputStyle.weight = mFontDefaults->mFontDescription.weight;
1077 inputStyle.isWeightDefined = true;
1080 if( mFontDefaults->widthDefined )
1082 inputStyle.width = mFontDefaults->mFontDescription.width;
1083 inputStyle.isWidthDefined = true;
1086 if( mFontDefaults->slantDefined )
1088 inputStyle.slant = mFontDefaults->mFontDescription.slant;
1089 inputStyle.isSlantDefined = true;
1092 if( mFontDefaults->sizeDefined )
1094 inputStyle.size = mFontDefaults->mDefaultPointSize;
1095 inputStyle.isSizeDefined = true;
1100 float Controller::Impl::GetDefaultFontLineHeight()
1102 FontId defaultFontId = 0u;
1103 if( NULL == mFontDefaults )
1105 TextAbstraction::FontDescription fontDescription;
1106 defaultFontId = mFontClient.GetFontId( fontDescription );
1110 defaultFontId = mFontDefaults->GetFontId( mFontClient );
1113 Text::FontMetrics fontMetrics;
1114 mMetrics->GetFontMetrics( defaultFontId, fontMetrics );
1116 return( fontMetrics.ascender - fontMetrics.descender );
1119 void Controller::Impl::OnCursorKeyEvent( const Event& event )
1121 if( NULL == mEventData )
1123 // Nothing to do if there is no text input.
1127 int keyCode = event.p1.mInt;
1129 if( Dali::DALI_KEY_CURSOR_LEFT == keyCode )
1131 if( mEventData->mPrimaryCursorPosition > 0u )
1133 mEventData->mPrimaryCursorPosition = CalculateNewCursorIndex( mEventData->mPrimaryCursorPosition - 1u );
1136 else if( Dali::DALI_KEY_CURSOR_RIGHT == keyCode )
1138 if( mModel->mLogicalModel->mText.Count() > mEventData->mPrimaryCursorPosition )
1140 mEventData->mPrimaryCursorPosition = CalculateNewCursorIndex( mEventData->mPrimaryCursorPosition );
1143 else if( Dali::DALI_KEY_CURSOR_UP == keyCode )
1145 // Get first the line index of the current cursor position index.
1146 CharacterIndex characterIndex = 0u;
1148 if( mEventData->mPrimaryCursorPosition > 0u )
1150 characterIndex = mEventData->mPrimaryCursorPosition - 1u;
1153 const LineIndex lineIndex = mModel->mVisualModel->GetLineOfCharacter( characterIndex );
1154 const LineIndex previousLineIndex = ( lineIndex > 0 ? lineIndex - 1u : lineIndex );
1156 if( previousLineIndex >= 0u )
1158 // Retrieve the cursor position info.
1159 CursorInfo cursorInfo;
1160 GetCursorPosition( mEventData->mPrimaryCursorPosition,
1163 // Get the line above.
1164 const LineRun& line = *( mModel->mVisualModel->mLines.Begin() + previousLineIndex );
1166 // Get the next hit 'y' point.
1167 const float hitPointY = cursorInfo.lineOffset - 0.5f * ( line.ascender - line.descender );
1169 // Use the cursor hook position 'x' and the next hit 'y' position to calculate the new cursor index.
1170 mEventData->mPrimaryCursorPosition = Text::GetClosestCursorIndex( mModel->mVisualModel,
1171 mModel->mLogicalModel,
1173 mEventData->mCursorHookPositionX,
1177 else if( Dali::DALI_KEY_CURSOR_DOWN == keyCode )
1179 // Get first the line index of the current cursor position index.
1180 CharacterIndex characterIndex = 0u;
1182 if( mEventData->mPrimaryCursorPosition > 0u )
1184 characterIndex = mEventData->mPrimaryCursorPosition - 1u;
1187 const LineIndex lineIndex = mModel->mVisualModel->GetLineOfCharacter( characterIndex );
1189 if( lineIndex + 1u < mModel->mVisualModel->mLines.Count() )
1191 // Retrieve the cursor position info.
1192 CursorInfo cursorInfo;
1193 GetCursorPosition( mEventData->mPrimaryCursorPosition,
1196 // Get the line below.
1197 const LineRun& line = *( mModel->mVisualModel->mLines.Begin() + lineIndex + 1u );
1199 // Get the next hit 'y' point.
1200 const float hitPointY = cursorInfo.lineOffset + cursorInfo.lineHeight + 0.5f * ( line.ascender - line.descender );
1202 // Use the cursor hook position 'x' and the next hit 'y' position to calculate the new cursor index.
1203 mEventData->mPrimaryCursorPosition = Text::GetClosestCursorIndex( mModel->mVisualModel,
1204 mModel->mLogicalModel,
1206 mEventData->mCursorHookPositionX,
1211 mEventData->mUpdateCursorPosition = true;
1212 mEventData->mUpdateInputStyle = true;
1213 mEventData->mScrollAfterUpdatePosition = true;
1216 void Controller::Impl::OnTapEvent( const Event& event )
1218 if( NULL != mEventData )
1220 const unsigned int tapCount = event.p1.mUint;
1222 if( 1u == tapCount )
1224 if( IsShowingRealText() )
1226 // Convert from control's coords to text's coords.
1227 const float xPosition = event.p2.mFloat - mModel->mScrollPosition.x;
1228 const float yPosition = event.p3.mFloat - mModel->mScrollPosition.y;
1230 // Keep the tap 'x' position. Used to move the cursor.
1231 mEventData->mCursorHookPositionX = xPosition;
1233 mEventData->mPrimaryCursorPosition = Text::GetClosestCursorIndex( mModel->mVisualModel,
1234 mModel->mLogicalModel,
1239 // When the cursor position is changing, delay cursor blinking
1240 mEventData->mDecorator->DelayCursorBlink();
1244 mEventData->mPrimaryCursorPosition = 0u;
1247 mEventData->mUpdateCursorPosition = true;
1248 mEventData->mUpdateGrabHandlePosition = true;
1249 mEventData->mScrollAfterUpdatePosition = true;
1250 mEventData->mUpdateInputStyle = true;
1252 // Notify the cursor position to the imf manager.
1253 if( mEventData->mImfManager )
1255 mEventData->mImfManager.SetCursorPosition( mEventData->mPrimaryCursorPosition );
1256 mEventData->mImfManager.NotifyCursorPosition();
1262 void Controller::Impl::OnPanEvent( const Event& event )
1264 if( NULL == mEventData )
1266 // Nothing to do if there is no text input.
1270 const bool isHorizontalScrollEnabled = mEventData->mDecorator->IsHorizontalScrollEnabled();
1271 const bool isVerticalScrollEnabled = mEventData->mDecorator->IsVerticalScrollEnabled();
1273 if( !isHorizontalScrollEnabled && !isVerticalScrollEnabled )
1275 // Nothing to do if scrolling is not enabled.
1279 const int state = event.p1.mInt;
1283 case Gesture::Started:
1285 // Will remove the cursor, handles or text's popup, ...
1286 ChangeState( EventData::TEXT_PANNING );
1289 case Gesture::Continuing:
1291 const Vector2& layoutSize = mModel->mVisualModel->GetLayoutSize();
1292 const Vector2 currentScroll = mModel->mScrollPosition;
1294 if( isHorizontalScrollEnabled )
1296 const float displacementX = event.p2.mFloat;
1297 mModel->mScrollPosition.x += displacementX;
1299 ClampHorizontalScroll( layoutSize );
1302 if( isVerticalScrollEnabled )
1304 const float displacementY = event.p3.mFloat;
1305 mModel->mScrollPosition.y += displacementY;
1307 ClampVerticalScroll( layoutSize );
1310 mEventData->mDecorator->UpdatePositions( mModel->mScrollPosition - currentScroll );
1313 case Gesture::Finished:
1314 case Gesture::Cancelled: // FALLTHROUGH
1316 // Will go back to the previous state to show the cursor, handles, the text's popup, ...
1317 ChangeState( mEventData->mPreviousState );
1325 void Controller::Impl::OnLongPressEvent( const Event& event )
1327 DALI_LOG_INFO( gLogFilter, Debug::General, "Controller::OnLongPressEvent\n" );
1329 if( EventData::EDITING == mEventData->mState )
1331 ChangeState ( EventData::EDITING_WITH_POPUP );
1332 mEventData->mDecoratorUpdated = true;
1333 mEventData->mUpdateInputStyle = true;
1337 void Controller::Impl::OnHandleEvent( const Event& event )
1339 if( NULL == mEventData )
1341 // Nothing to do if there is no text input.
1345 const unsigned int state = event.p1.mUint;
1346 const bool handleStopScrolling = ( HANDLE_STOP_SCROLLING == state );
1347 const bool isSmoothHandlePanEnabled = mEventData->mDecorator->IsSmoothHandlePanEnabled();
1349 if( HANDLE_PRESSED == state )
1351 // Convert from decorator's coords to text's coords.
1352 const float xPosition = event.p2.mFloat - mModel->mScrollPosition.x;
1353 const float yPosition = event.p3.mFloat - mModel->mScrollPosition.y;
1355 // Need to calculate the handle's new position.
1356 const CharacterIndex handleNewPosition = Text::GetClosestCursorIndex( mModel->mVisualModel,
1357 mModel->mLogicalModel,
1362 if( Event::GRAB_HANDLE_EVENT == event.type )
1364 ChangeState ( EventData::GRAB_HANDLE_PANNING );
1366 if( handleNewPosition != mEventData->mPrimaryCursorPosition )
1368 // Updates the cursor position if the handle's new position is different than the current one.
1369 mEventData->mUpdateCursorPosition = true;
1370 // Does not update the grab handle position if the smooth panning is enabled. (The decorator does it smooth).
1371 mEventData->mUpdateGrabHandlePosition = !isSmoothHandlePanEnabled;
1372 mEventData->mPrimaryCursorPosition = handleNewPosition;
1375 // 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.
1376 mEventData->mDecoratorUpdated = isSmoothHandlePanEnabled;
1378 else if( Event::LEFT_SELECTION_HANDLE_EVENT == event.type )
1380 ChangeState ( EventData::SELECTION_HANDLE_PANNING );
1382 if( ( handleNewPosition != mEventData->mLeftSelectionPosition ) &&
1383 ( handleNewPosition != mEventData->mRightSelectionPosition ) )
1385 // Updates the highlight box if the handle's new position is different than the current one.
1386 mEventData->mUpdateHighlightBox = true;
1387 // Does not update the selection handle position if the smooth panning is enabled. (The decorator does it smooth).
1388 mEventData->mUpdateLeftSelectionPosition = !isSmoothHandlePanEnabled;
1389 mEventData->mLeftSelectionPosition = handleNewPosition;
1392 // 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.
1393 mEventData->mDecoratorUpdated = isSmoothHandlePanEnabled;
1395 // Will define the order to scroll the text to match the handle position.
1396 mEventData->mIsLeftHandleSelected = true;
1398 else if( Event::RIGHT_SELECTION_HANDLE_EVENT == event.type )
1400 ChangeState ( EventData::SELECTION_HANDLE_PANNING );
1402 if( ( handleNewPosition != mEventData->mRightSelectionPosition ) &&
1403 ( handleNewPosition != mEventData->mLeftSelectionPosition ) )
1405 // Updates the highlight box if the handle's new position is different than the current one.
1406 mEventData->mUpdateHighlightBox = true;
1407 // Does not update the selection handle position if the smooth panning is enabled. (The decorator does it smooth).
1408 mEventData->mUpdateRightSelectionPosition = !isSmoothHandlePanEnabled;
1409 mEventData->mRightSelectionPosition = handleNewPosition;
1412 // 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.
1413 mEventData->mDecoratorUpdated = isSmoothHandlePanEnabled;
1415 // Will define the order to scroll the text to match the handle position.
1416 mEventData->mIsLeftHandleSelected = false;
1418 } // end ( HANDLE_PRESSED == state )
1419 else if( ( HANDLE_RELEASED == state ) ||
1420 handleStopScrolling )
1422 CharacterIndex handlePosition = 0u;
1423 if( handleStopScrolling || isSmoothHandlePanEnabled )
1425 // Convert from decorator's coords to text's coords.
1426 const float xPosition = event.p2.mFloat - mModel->mScrollPosition.x;
1427 const float yPosition = event.p3.mFloat - mModel->mScrollPosition.y;
1429 handlePosition = Text::GetClosestCursorIndex( mModel->mVisualModel,
1430 mModel->mLogicalModel,
1436 if( Event::GRAB_HANDLE_EVENT == event.type )
1438 mEventData->mUpdateCursorPosition = true;
1439 mEventData->mUpdateGrabHandlePosition = true;
1440 mEventData->mUpdateInputStyle = true;
1442 if( !IsClipboardEmpty() )
1444 ChangeState( EventData::EDITING_WITH_PASTE_POPUP ); // Moving grabhandle will show Paste Popup
1447 if( handleStopScrolling || isSmoothHandlePanEnabled )
1449 mEventData->mScrollAfterUpdatePosition = true;
1450 mEventData->mPrimaryCursorPosition = handlePosition;
1453 else if( Event::LEFT_SELECTION_HANDLE_EVENT == event.type )
1455 ChangeState( EventData::SELECTING );
1457 mEventData->mUpdateHighlightBox = true;
1458 mEventData->mUpdateLeftSelectionPosition = true;
1459 mEventData->mUpdateRightSelectionPosition = true;
1461 if( handleStopScrolling || isSmoothHandlePanEnabled )
1463 mEventData->mScrollAfterUpdatePosition = true;
1465 if( ( handlePosition != mEventData->mRightSelectionPosition ) &&
1466 ( handlePosition != mEventData->mLeftSelectionPosition ) )
1468 mEventData->mLeftSelectionPosition = handlePosition;
1472 else if( Event::RIGHT_SELECTION_HANDLE_EVENT == event.type )
1474 ChangeState( EventData::SELECTING );
1476 mEventData->mUpdateHighlightBox = true;
1477 mEventData->mUpdateRightSelectionPosition = true;
1478 mEventData->mUpdateLeftSelectionPosition = true;
1480 if( handleStopScrolling || isSmoothHandlePanEnabled )
1482 mEventData->mScrollAfterUpdatePosition = true;
1483 if( ( handlePosition != mEventData->mRightSelectionPosition ) &&
1484 ( handlePosition != mEventData->mLeftSelectionPosition ) )
1486 mEventData->mRightSelectionPosition = handlePosition;
1491 mEventData->mDecoratorUpdated = true;
1492 } // end ( ( HANDLE_RELEASED == state ) || ( HANDLE_STOP_SCROLLING == state ) )
1493 else if( HANDLE_SCROLLING == state )
1495 const float xSpeed = event.p2.mFloat;
1496 const float ySpeed = event.p3.mFloat;
1497 const Vector2& layoutSize = mModel->mVisualModel->GetLayoutSize();
1498 const Vector2 currentScrollPosition = mModel->mScrollPosition;
1500 mModel->mScrollPosition.x += xSpeed;
1501 mModel->mScrollPosition.y += ySpeed;
1503 ClampHorizontalScroll( layoutSize );
1504 ClampVerticalScroll( layoutSize );
1506 bool endOfScroll = false;
1507 if( Vector2::ZERO == ( currentScrollPosition - mModel->mScrollPosition ) )
1509 // Notify the decorator there is no more text to scroll.
1510 // The decorator won't send more scroll events.
1511 mEventData->mDecorator->NotifyEndOfScroll();
1512 // Still need to set the position of the handle.
1516 // Set the position of the handle.
1517 const bool scrollRightDirection = xSpeed > 0.f;
1518 const bool scrollBottomDirection = ySpeed > 0.f;
1519 const bool leftSelectionHandleEvent = Event::LEFT_SELECTION_HANDLE_EVENT == event.type;
1520 const bool rightSelectionHandleEvent = Event::RIGHT_SELECTION_HANDLE_EVENT == event.type;
1522 if( Event::GRAB_HANDLE_EVENT == event.type )
1524 ChangeState( EventData::GRAB_HANDLE_PANNING );
1526 // Get the grab handle position in decorator coords.
1527 Vector2 position = mEventData->mDecorator->GetPosition( GRAB_HANDLE );
1529 if( mEventData->mDecorator->IsHorizontalScrollEnabled() )
1531 // Position the grag handle close to either the left or right edge.
1532 position.x = scrollRightDirection ? 0.f : mModel->mVisualModel->mControlSize.width;
1535 if( mEventData->mDecorator->IsVerticalScrollEnabled() )
1537 position.x = mEventData->mCursorHookPositionX;
1539 // Position the grag handle close to either the top or bottom edge.
1540 position.y = scrollBottomDirection ? 0.f : mModel->mVisualModel->mControlSize.height;
1543 // Get the new handle position.
1544 // The grab handle's position is in decorator's coords. Need to transforms to text's coords.
1545 const CharacterIndex handlePosition = Text::GetClosestCursorIndex( mModel->mVisualModel,
1546 mModel->mLogicalModel,
1548 position.x - mModel->mScrollPosition.x,
1549 position.y - mModel->mScrollPosition.y );
1551 if( mEventData->mPrimaryCursorPosition != handlePosition )
1553 mEventData->mUpdateCursorPosition = true;
1554 mEventData->mUpdateGrabHandlePosition = !isSmoothHandlePanEnabled;
1555 mEventData->mScrollAfterUpdatePosition = true;
1556 mEventData->mPrimaryCursorPosition = handlePosition;
1558 mEventData->mUpdateInputStyle = mEventData->mUpdateCursorPosition;
1560 // Updates the decorator if the soft handle panning is enabled.
1561 mEventData->mDecoratorUpdated = isSmoothHandlePanEnabled;
1563 else if( leftSelectionHandleEvent || rightSelectionHandleEvent )
1565 ChangeState( EventData::SELECTION_HANDLE_PANNING );
1567 // Get the selection handle position in decorator coords.
1568 Vector2 position = mEventData->mDecorator->GetPosition( leftSelectionHandleEvent ? Text::LEFT_SELECTION_HANDLE : Text::RIGHT_SELECTION_HANDLE );
1570 if( mEventData->mDecorator->IsHorizontalScrollEnabled() )
1572 // Position the selection handle close to either the left or right edge.
1573 position.x = scrollRightDirection ? 0.f : mModel->mVisualModel->mControlSize.width;
1576 if( mEventData->mDecorator->IsVerticalScrollEnabled() )
1578 position.x = mEventData->mCursorHookPositionX;
1580 // Position the grag handle close to either the top or bottom edge.
1581 position.y = scrollBottomDirection ? 0.f : mModel->mVisualModel->mControlSize.height;
1584 // Get the new handle position.
1585 // The selection handle's position is in decorator's coords. Need to transform to text's coords.
1586 const CharacterIndex handlePosition = Text::GetClosestCursorIndex( mModel->mVisualModel,
1587 mModel->mLogicalModel,
1589 position.x - mModel->mScrollPosition.x,
1590 position.y - mModel->mScrollPosition.y );
1592 if( leftSelectionHandleEvent )
1594 const bool differentHandles = ( mEventData->mLeftSelectionPosition != handlePosition ) && ( mEventData->mRightSelectionPosition != handlePosition );
1596 if( differentHandles || endOfScroll )
1598 mEventData->mUpdateHighlightBox = true;
1599 mEventData->mUpdateLeftSelectionPosition = !isSmoothHandlePanEnabled;
1600 mEventData->mUpdateRightSelectionPosition = isSmoothHandlePanEnabled;
1601 mEventData->mLeftSelectionPosition = handlePosition;
1606 const bool differentHandles = ( mEventData->mRightSelectionPosition != handlePosition ) && ( mEventData->mLeftSelectionPosition != handlePosition );
1607 if( differentHandles || endOfScroll )
1609 mEventData->mUpdateHighlightBox = true;
1610 mEventData->mUpdateRightSelectionPosition = !isSmoothHandlePanEnabled;
1611 mEventData->mUpdateLeftSelectionPosition = isSmoothHandlePanEnabled;
1612 mEventData->mRightSelectionPosition = handlePosition;
1616 if( mEventData->mUpdateLeftSelectionPosition || mEventData->mUpdateRightSelectionPosition )
1618 RepositionSelectionHandles();
1620 mEventData->mScrollAfterUpdatePosition = !isSmoothHandlePanEnabled;
1623 mEventData->mDecoratorUpdated = true;
1624 } // end ( HANDLE_SCROLLING == state )
1627 void Controller::Impl::OnSelectEvent( const Event& event )
1629 if( NULL == mEventData )
1631 // Nothing to do if there is no text.
1635 if( mEventData->mSelectionEnabled )
1637 // Convert from control's coords to text's coords.
1638 const float xPosition = event.p2.mFloat - mModel->mScrollPosition.x;
1639 const float yPosition = event.p3.mFloat - mModel->mScrollPosition.y;
1641 // Calculates the logical position from the x,y coords.
1642 RepositionSelectionHandles( xPosition,
1647 void Controller::Impl::OnSelectAllEvent()
1649 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "OnSelectAllEvent mEventData->mSelectionEnabled%s \n", mEventData->mSelectionEnabled?"true":"false");
1651 if( NULL == mEventData )
1653 // Nothing to do if there is no text.
1657 if( mEventData->mSelectionEnabled )
1659 ChangeState( EventData::SELECTING );
1661 mEventData->mLeftSelectionPosition = 0u;
1662 mEventData->mRightSelectionPosition = mModel->mLogicalModel->mText.Count();
1664 mEventData->mScrollAfterUpdatePosition = true;
1665 mEventData->mUpdateLeftSelectionPosition = true;
1666 mEventData->mUpdateRightSelectionPosition = true;
1667 mEventData->mUpdateHighlightBox = true;
1671 void Controller::Impl::RetrieveSelection( std::string& selectedText, bool deleteAfterRetrieval )
1673 if( mEventData->mLeftSelectionPosition == mEventData->mRightSelectionPosition )
1675 // Nothing to select if handles are in the same place.
1676 selectedText.clear();
1680 const bool handlesCrossed = mEventData->mLeftSelectionPosition > mEventData->mRightSelectionPosition;
1682 //Get start and end position of selection
1683 const CharacterIndex startOfSelectedText = handlesCrossed ? mEventData->mRightSelectionPosition : mEventData->mLeftSelectionPosition;
1684 const Length lengthOfSelectedText = ( handlesCrossed ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition ) - startOfSelectedText;
1686 Vector<Character>& utf32Characters = mModel->mLogicalModel->mText;
1687 const Length numberOfCharacters = utf32Characters.Count();
1689 // Validate the start and end selection points
1690 if( ( startOfSelectedText + lengthOfSelectedText ) <= numberOfCharacters )
1692 //Get text as a UTF8 string
1693 Utf32ToUtf8( &utf32Characters[startOfSelectedText], lengthOfSelectedText, selectedText );
1695 if( deleteAfterRetrieval ) // Only delete text if copied successfully
1697 // Keep a copy of the current input style.
1698 InputStyle currentInputStyle;
1699 currentInputStyle.Copy( mEventData->mInputStyle );
1701 // Set as input style the style of the first deleted character.
1702 mModel->mLogicalModel->RetrieveStyle( startOfSelectedText, mEventData->mInputStyle );
1704 // Compare if the input style has changed.
1705 const bool hasInputStyleChanged = !currentInputStyle.Equal( mEventData->mInputStyle );
1707 if( hasInputStyleChanged )
1709 const InputStyle::Mask styleChangedMask = currentInputStyle.GetInputStyleChangeMask( mEventData->mInputStyle );
1710 // Queue the input style changed signal.
1711 mEventData->mInputStyleChangedQueue.PushBack( styleChangedMask );
1714 mModel->mLogicalModel->UpdateTextStyleRuns( startOfSelectedText, -static_cast<int>( lengthOfSelectedText ) );
1716 // Mark the paragraphs to be updated.
1717 mTextUpdateInfo.mCharacterIndex = startOfSelectedText;
1718 mTextUpdateInfo.mNumberOfCharactersToRemove = lengthOfSelectedText;
1720 // Delete text between handles
1721 Vector<Character>::Iterator first = utf32Characters.Begin() + startOfSelectedText;
1722 Vector<Character>::Iterator last = first + lengthOfSelectedText;
1723 utf32Characters.Erase( first, last );
1725 // Will show the cursor at the first character of the selection.
1726 mEventData->mPrimaryCursorPosition = handlesCrossed ? mEventData->mRightSelectionPosition : mEventData->mLeftSelectionPosition;
1730 // Will show the cursor at the last character of the selection.
1731 mEventData->mPrimaryCursorPosition = handlesCrossed ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition;
1734 mEventData->mDecoratorUpdated = true;
1738 void Controller::Impl::ShowClipboard()
1742 mClipboard.ShowClipboard();
1746 void Controller::Impl::HideClipboard()
1748 if( mClipboard && mClipboardHideEnabled )
1750 mClipboard.HideClipboard();
1754 void Controller::Impl::SetClipboardHideEnable(bool enable)
1756 mClipboardHideEnabled = enable;
1759 bool Controller::Impl::CopyStringToClipboard( std::string& source )
1761 //Send string to clipboard
1762 return ( mClipboard && mClipboard.SetItem( source ) );
1765 void Controller::Impl::SendSelectionToClipboard( bool deleteAfterSending )
1767 std::string selectedText;
1768 RetrieveSelection( selectedText, deleteAfterSending );
1769 CopyStringToClipboard( selectedText );
1770 ChangeState( EventData::EDITING );
1773 void Controller::Impl::RequestGetTextFromClipboard()
1777 mClipboard.RequestItem();
1781 void Controller::Impl::RepositionSelectionHandles()
1783 CharacterIndex selectionStart = mEventData->mLeftSelectionPosition;
1784 CharacterIndex selectionEnd = mEventData->mRightSelectionPosition;
1786 if( selectionStart == selectionEnd )
1788 // Nothing to select if handles are in the same place.
1792 mEventData->mDecorator->ClearHighlights();
1794 const GlyphIndex* const charactersToGlyphBuffer = mModel->mVisualModel->mCharactersToGlyph.Begin();
1795 const Length* const glyphsPerCharacterBuffer = mModel->mVisualModel->mGlyphsPerCharacter.Begin();
1796 const GlyphInfo* const glyphsBuffer = mModel->mVisualModel->mGlyphs.Begin();
1797 const Vector2* const positionsBuffer = mModel->mVisualModel->mGlyphPositions.Begin();
1798 const Length* const charactersPerGlyphBuffer = mModel->mVisualModel->mCharactersPerGlyph.Begin();
1799 const CharacterIndex* const glyphToCharacterBuffer = mModel->mVisualModel->mGlyphsToCharacters.Begin();
1800 const CharacterDirection* const modelCharacterDirectionsBuffer = ( 0u != mModel->mLogicalModel->mCharacterDirections.Count() ) ? mModel->mLogicalModel->mCharacterDirections.Begin() : NULL;
1802 const bool isLastCharacter = selectionEnd >= mModel->mLogicalModel->mText.Count();
1803 const CharacterDirection startDirection = ( ( NULL == modelCharacterDirectionsBuffer ) ? false : *( modelCharacterDirectionsBuffer + selectionStart ) );
1804 const CharacterDirection endDirection = ( ( NULL == modelCharacterDirectionsBuffer ) ? false : *( modelCharacterDirectionsBuffer + ( selectionEnd - ( isLastCharacter ? 1u : 0u ) ) ) );
1806 // Swap the indices if the start is greater than the end.
1807 const bool indicesSwapped = selectionStart > selectionEnd;
1809 // Tell the decorator to flip the selection handles if needed.
1810 mEventData->mDecorator->SetSelectionHandleFlipState( indicesSwapped, startDirection, endDirection );
1812 if( indicesSwapped )
1814 std::swap( selectionStart, selectionEnd );
1817 // Get the indices to the first and last selected glyphs.
1818 const CharacterIndex selectionEndMinusOne = selectionEnd - 1u;
1819 const GlyphIndex glyphStart = *( charactersToGlyphBuffer + selectionStart );
1820 const Length numberOfGlyphs = *( glyphsPerCharacterBuffer + selectionEndMinusOne );
1821 const GlyphIndex glyphEnd = *( charactersToGlyphBuffer + selectionEndMinusOne ) + ( ( numberOfGlyphs > 0 ) ? numberOfGlyphs - 1u : 0u );
1823 // Get the lines where the glyphs are laid-out.
1824 const LineRun* lineRun = mModel->mVisualModel->mLines.Begin();
1826 LineIndex lineIndex = 0u;
1827 Length numberOfLines = 0u;
1828 mModel->mVisualModel->GetNumberOfLines( glyphStart,
1829 1u + glyphEnd - glyphStart,
1832 const LineIndex firstLineIndex = lineIndex;
1834 // Create the structure to store some selection box info.
1835 Vector<SelectionBoxInfo> selectionBoxLinesInfo;
1836 selectionBoxLinesInfo.Resize( numberOfLines );
1838 SelectionBoxInfo* selectionBoxInfo = selectionBoxLinesInfo.Begin();
1839 selectionBoxInfo->minX = MAX_FLOAT;
1840 selectionBoxInfo->maxX = MIN_FLOAT;
1842 // Keep the min and max 'x' position to calculate the size and position of the highlighed text.
1843 float minHighlightX = std::numeric_limits<float>::max();
1844 float maxHighlightX = std::numeric_limits<float>::min();
1846 Vector2 highLightPosition; // The highlight position in decorator's coords.
1848 // Retrieve the first line and get the line's vertical offset, the line's height and the index to the last glyph.
1850 // The line's vertical offset of all the lines before the line where the first glyph is laid-out.
1851 selectionBoxInfo->lineOffset = CalculateLineOffset( mModel->mVisualModel->mLines,
1854 // Transform to decorator's (control) coords.
1855 selectionBoxInfo->lineOffset += mModel->mScrollPosition.y;
1857 lineRun += firstLineIndex;
1859 // The line height is the addition of the line ascender and the line descender.
1860 // However, the line descender has a negative value, hence the subtraction.
1861 selectionBoxInfo->lineHeight = lineRun->ascender - lineRun->descender;
1863 GlyphIndex lastGlyphOfLine = lineRun->glyphRun.glyphIndex + lineRun->glyphRun.numberOfGlyphs - 1u;
1865 // Check if the first glyph is a ligature that must be broken like Latin ff, fi, or Arabic ï»», etc which needs special code.
1866 const Length numberOfCharactersStart = *( charactersPerGlyphBuffer + glyphStart );
1867 bool splitStartGlyph = ( numberOfCharactersStart > 1u ) && HasLigatureMustBreak( mModel->mLogicalModel->GetScript( selectionStart ) );
1869 // Check if the last glyph is a ligature that must be broken like Latin ff, fi, or Arabic ï»», etc which needs special code.
1870 const Length numberOfCharactersEnd = *( charactersPerGlyphBuffer + glyphEnd );
1871 bool splitEndGlyph = ( glyphStart != glyphEnd ) && ( numberOfCharactersEnd > 1u ) && HasLigatureMustBreak( mModel->mLogicalModel->GetScript( selectionEndMinusOne ) );
1873 // The number of quads of the selection box.
1874 const unsigned int numberOfQuads = 1u + ( glyphEnd - glyphStart ) + ( ( numberOfLines > 1u ) ? 2u * numberOfLines : 0u );
1875 mEventData->mDecorator->ResizeHighlightQuads( numberOfQuads );
1877 // Count the actual number of quads.
1878 unsigned int actualNumberOfQuads = 0u;
1881 // Traverse the glyphs.
1882 for( GlyphIndex index = glyphStart; index <= glyphEnd; ++index )
1884 const GlyphInfo& glyph = *( glyphsBuffer + index );
1885 const Vector2& position = *( positionsBuffer + index );
1887 if( splitStartGlyph )
1889 // 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.
1891 const float glyphAdvance = glyph.advance / static_cast<float>( numberOfCharactersStart );
1892 const CharacterIndex interGlyphIndex = selectionStart - *( glyphToCharacterBuffer + glyphStart );
1893 // Get the direction of the character.
1894 CharacterDirection isCurrentRightToLeft = false;
1895 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
1897 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + selectionStart );
1900 // The end point could be in the middle of the ligature.
1901 // Calculate the number of characters selected.
1902 const Length numberOfCharacters = ( glyphStart == glyphEnd ) ? ( selectionEnd - selectionStart ) : ( numberOfCharactersStart - interGlyphIndex );
1904 quad.x = lineRun->alignmentOffset + position.x - glyph.xBearing + mModel->mScrollPosition.x + glyphAdvance * static_cast<float>( isCurrentRightToLeft ? ( numberOfCharactersStart - interGlyphIndex - numberOfCharacters ) : interGlyphIndex );
1905 quad.y = selectionBoxInfo->lineOffset;
1906 quad.z = quad.x + static_cast<float>( numberOfCharacters ) * glyphAdvance;
1907 quad.w = selectionBoxInfo->lineOffset + selectionBoxInfo->lineHeight;
1909 // Store the min and max 'x' for each line.
1910 selectionBoxInfo->minX = std::min( selectionBoxInfo->minX, quad.x );
1911 selectionBoxInfo->maxX = std::max( selectionBoxInfo->maxX, quad.z );
1913 mEventData->mDecorator->AddHighlight( actualNumberOfQuads, quad );
1914 ++actualNumberOfQuads;
1916 splitStartGlyph = false;
1920 if( splitEndGlyph && ( index == glyphEnd ) )
1922 // 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.
1924 const float glyphAdvance = glyph.advance / static_cast<float>( numberOfCharactersEnd );
1925 const CharacterIndex interGlyphIndex = selectionEnd - *( glyphToCharacterBuffer + glyphEnd );
1926 // Get the direction of the character.
1927 CharacterDirection isCurrentRightToLeft = false;
1928 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
1930 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + selectionEnd );
1933 const Length numberOfCharacters = numberOfCharactersEnd - interGlyphIndex;
1935 quad.x = lineRun->alignmentOffset + position.x - glyph.xBearing + mModel->mScrollPosition.x + ( isCurrentRightToLeft ? ( glyphAdvance * static_cast<float>( numberOfCharacters ) ) : 0.f );
1936 quad.y = selectionBoxInfo->lineOffset;
1937 quad.z = quad.x + static_cast<float>( interGlyphIndex ) * glyphAdvance;
1938 quad.w = quad.y + selectionBoxInfo->lineHeight;
1940 // Store the min and max 'x' for each line.
1941 selectionBoxInfo->minX = std::min( selectionBoxInfo->minX, quad.x );
1942 selectionBoxInfo->maxX = std::max( selectionBoxInfo->maxX, quad.z );
1944 mEventData->mDecorator->AddHighlight( actualNumberOfQuads,
1946 ++actualNumberOfQuads;
1948 splitEndGlyph = false;
1952 quad.x = lineRun->alignmentOffset + position.x - glyph.xBearing + mModel->mScrollPosition.x;
1953 quad.y = selectionBoxInfo->lineOffset;
1954 quad.z = quad.x + glyph.advance;
1955 quad.w = quad.y + selectionBoxInfo->lineHeight;
1957 // Store the min and max 'x' for each line.
1958 selectionBoxInfo->minX = std::min( selectionBoxInfo->minX, quad.x );
1959 selectionBoxInfo->maxX = std::max( selectionBoxInfo->maxX, quad.z );
1961 mEventData->mDecorator->AddHighlight( actualNumberOfQuads,
1963 ++actualNumberOfQuads;
1965 // Whether to retrieve the next line.
1966 if( index == lastGlyphOfLine )
1969 if( lineIndex < firstLineIndex + numberOfLines )
1971 // Retrieve the next line.
1974 // Get the last glyph of the new line.
1975 lastGlyphOfLine = lineRun->glyphRun.glyphIndex + lineRun->glyphRun.numberOfGlyphs - 1u;
1977 // Keep the offset and height of the current selection box.
1978 const float currentLineOffset = selectionBoxInfo->lineOffset;
1979 const float currentLineHeight = selectionBoxInfo->lineHeight;
1981 // Get the selection box info for the next line.
1984 selectionBoxInfo->minX = MAX_FLOAT;
1985 selectionBoxInfo->maxX = MIN_FLOAT;
1987 // Update the line's vertical offset.
1988 selectionBoxInfo->lineOffset = currentLineOffset + currentLineHeight;
1990 // The line height is the addition of the line ascender and the line descender.
1991 // However, the line descender has a negative value, hence the subtraction.
1992 selectionBoxInfo->lineHeight = lineRun->ascender - lineRun->descender;
1997 // Traverses all the lines and updates the min and max 'x' positions and the total height.
1998 // The final width is calculated after 'boxifying' the selection.
1999 for( Vector<SelectionBoxInfo>::ConstIterator it = selectionBoxLinesInfo.Begin(),
2000 endIt = selectionBoxLinesInfo.End();
2004 const SelectionBoxInfo& info = *it;
2006 // Update the size of the highlighted text.
2007 highLightSize.height += info.lineHeight;
2008 minHighlightX = std::min( minHighlightX, info.minX );
2009 maxHighlightX = std::max( maxHighlightX, info.maxX );
2012 // Add extra geometry to 'boxify' the selection.
2014 if( 1u < numberOfLines )
2016 // Boxify the first line.
2017 lineRun = mModel->mVisualModel->mLines.Begin() + firstLineIndex;
2018 const SelectionBoxInfo& firstSelectionBoxLineInfo = *( selectionBoxLinesInfo.Begin() );
2020 bool boxifyBegin = ( LTR != lineRun->direction ) && ( LTR != startDirection );
2021 bool boxifyEnd = ( LTR == lineRun->direction ) && ( LTR == startDirection );
2026 quad.y = firstSelectionBoxLineInfo.lineOffset;
2027 quad.z = firstSelectionBoxLineInfo.minX;
2028 quad.w = firstSelectionBoxLineInfo.lineOffset + firstSelectionBoxLineInfo.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 = firstSelectionBoxLineInfo.maxX;
2042 quad.y = firstSelectionBoxLineInfo.lineOffset;
2043 quad.z = mModel->mVisualModel->mControlSize.width;
2044 quad.w = firstSelectionBoxLineInfo.lineOffset + firstSelectionBoxLineInfo.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 = mModel->mVisualModel->mControlSize.width;
2055 // Boxify the central lines.
2056 if( 2u < numberOfLines )
2058 for( Vector<SelectionBoxInfo>::ConstIterator it = selectionBoxLinesInfo.Begin() + 1u,
2059 endIt = selectionBoxLinesInfo.End() - 1u;
2063 const SelectionBoxInfo& info = *it;
2066 quad.y = info.lineOffset;
2068 quad.w = info.lineOffset + info.lineHeight;
2070 mEventData->mDecorator->AddHighlight( actualNumberOfQuads,
2072 ++actualNumberOfQuads;
2075 quad.y = info.lineOffset;
2076 quad.z = mModel->mVisualModel->mControlSize.width;
2077 quad.w = info.lineOffset + info.lineHeight;
2079 mEventData->mDecorator->AddHighlight( actualNumberOfQuads,
2081 ++actualNumberOfQuads;
2084 // Update the size of the highlighted text.
2085 minHighlightX = 0.f;
2086 maxHighlightX = mModel->mVisualModel->mControlSize.width;
2089 // Boxify the last line.
2090 lineRun = mModel->mVisualModel->mLines.Begin() + firstLineIndex + numberOfLines - 1u;
2091 const SelectionBoxInfo& lastSelectionBoxLineInfo = *( selectionBoxLinesInfo.End() - 1u );
2093 boxifyBegin = ( LTR == lineRun->direction ) && ( LTR == endDirection );
2094 boxifyEnd = ( LTR != lineRun->direction ) && ( LTR != endDirection );
2099 quad.y = lastSelectionBoxLineInfo.lineOffset;
2100 quad.z = lastSelectionBoxLineInfo.minX;
2101 quad.w = lastSelectionBoxLineInfo.lineOffset + lastSelectionBoxLineInfo.lineHeight;
2103 // Boxify at the beginning of the line.
2104 mEventData->mDecorator->AddHighlight( actualNumberOfQuads,
2106 ++actualNumberOfQuads;
2108 // Update the size of the highlighted text.
2109 minHighlightX = 0.f;
2114 quad.x = lastSelectionBoxLineInfo.maxX;
2115 quad.y = lastSelectionBoxLineInfo.lineOffset;
2116 quad.z = mModel->mVisualModel->mControlSize.width;
2117 quad.w = lastSelectionBoxLineInfo.lineOffset + lastSelectionBoxLineInfo.lineHeight;
2119 // Boxify at the end of the line.
2120 mEventData->mDecorator->AddHighlight( actualNumberOfQuads,
2122 ++actualNumberOfQuads;
2124 // Update the size of the highlighted text.
2125 maxHighlightX = mModel->mVisualModel->mControlSize.width;
2129 // Set the actual number of quads.
2130 mEventData->mDecorator->ResizeHighlightQuads( actualNumberOfQuads );
2132 // Sets the highlight's size and position. In decorator's coords.
2133 // The highlight's height has been calculated above (before 'boxifying' the highlight).
2134 highLightSize.width = maxHighlightX - minHighlightX;
2136 highLightPosition.x = minHighlightX;
2137 const SelectionBoxInfo& firstSelectionBoxLineInfo = *( selectionBoxLinesInfo.Begin() );
2138 highLightPosition.y = firstSelectionBoxLineInfo.lineOffset;
2140 mEventData->mDecorator->SetHighLightBox( highLightPosition, highLightSize );
2142 if( !mEventData->mDecorator->IsSmoothHandlePanEnabled() )
2144 CursorInfo primaryCursorInfo;
2145 GetCursorPosition( mEventData->mLeftSelectionPosition,
2146 primaryCursorInfo );
2148 const Vector2 primaryPosition = primaryCursorInfo.primaryPosition + mModel->mScrollPosition;
2150 mEventData->mDecorator->SetPosition( LEFT_SELECTION_HANDLE,
2152 primaryCursorInfo.lineOffset + mModel->mScrollPosition.y,
2153 primaryCursorInfo.lineHeight );
2155 CursorInfo secondaryCursorInfo;
2156 GetCursorPosition( mEventData->mRightSelectionPosition,
2157 secondaryCursorInfo );
2159 const Vector2 secondaryPosition = secondaryCursorInfo.primaryPosition + mModel->mScrollPosition;
2161 mEventData->mDecorator->SetPosition( RIGHT_SELECTION_HANDLE,
2162 secondaryPosition.x,
2163 secondaryCursorInfo.lineOffset + mModel->mScrollPosition.y,
2164 secondaryCursorInfo.lineHeight );
2167 // Cursor to be positioned at end of selection so if selection interrupted and edit mode restarted the cursor will be at end of selection
2168 mEventData->mPrimaryCursorPosition = ( indicesSwapped ) ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition;
2170 // Set the flag to update the decorator.
2171 mEventData->mDecoratorUpdated = true;
2174 void Controller::Impl::RepositionSelectionHandles( float visualX, float visualY )
2176 if( NULL == mEventData )
2178 // Nothing to do if there is no text input.
2182 if( IsShowingPlaceholderText() )
2184 // Nothing to do if there is the place-holder text.
2188 const Length numberOfGlyphs = mModel->mVisualModel->mGlyphs.Count();
2189 const Length numberOfLines = mModel->mVisualModel->mLines.Count();
2190 if( ( 0 == numberOfGlyphs ) ||
2191 ( 0 == numberOfLines ) )
2193 // Nothing to do if there is no text.
2197 // Find which word was selected
2198 CharacterIndex selectionStart( 0 );
2199 CharacterIndex selectionEnd( 0 );
2200 const bool indicesFound = FindSelectionIndices( mModel->mVisualModel,
2201 mModel->mLogicalModel,
2207 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "%p selectionStart %d selectionEnd %d\n", this, selectionStart, selectionEnd );
2211 ChangeState( EventData::SELECTING );
2213 mEventData->mLeftSelectionPosition = selectionStart;
2214 mEventData->mRightSelectionPosition = selectionEnd;
2216 mEventData->mUpdateLeftSelectionPosition = true;
2217 mEventData->mUpdateRightSelectionPosition = true;
2218 mEventData->mUpdateHighlightBox = true;
2220 // It may happen an IMF commit event arrives before the selection event
2221 // if the IMF manager is in pre-edit state. The commit event will set the
2222 // mEventData->mUpdateCursorPosition flag to true. If it's not set back
2223 // to false, the highlight box won't be updated.
2224 mEventData->mUpdateCursorPosition = false;
2226 mEventData->mScrollAfterUpdatePosition = ( mEventData->mLeftSelectionPosition != mEventData->mRightSelectionPosition );
2230 // Nothing to select. i.e. a white space, out of bounds
2231 ChangeState( EventData::EDITING );
2233 mEventData->mPrimaryCursorPosition = selectionEnd;
2235 mEventData->mUpdateCursorPosition = true;
2236 mEventData->mUpdateGrabHandlePosition = true;
2237 mEventData->mScrollAfterUpdatePosition = true;
2238 mEventData->mUpdateInputStyle = true;
2242 void Controller::Impl::SetPopupButtons()
2245 * Sets the Popup buttons to be shown depending on State.
2247 * If SELECTING : CUT & COPY + ( PASTE & CLIPBOARD if content available to paste )
2249 * If EDITING_WITH_POPUP : SELECT & SELECT_ALL
2252 TextSelectionPopup::Buttons buttonsToShow = TextSelectionPopup::NONE;
2254 if( EventData::SELECTING == mEventData->mState )
2256 buttonsToShow = TextSelectionPopup::Buttons( TextSelectionPopup::CUT | TextSelectionPopup::COPY );
2258 if( !IsClipboardEmpty() )
2260 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
2261 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::CLIPBOARD ) );
2264 if( !mEventData->mAllTextSelected )
2266 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::SELECT_ALL ) );
2269 else if( EventData::EDITING_WITH_POPUP == mEventData->mState )
2271 if( mModel->mLogicalModel->mText.Count() && !IsShowingPlaceholderText() )
2273 buttonsToShow = TextSelectionPopup::Buttons( TextSelectionPopup::SELECT | TextSelectionPopup::SELECT_ALL );
2276 if( !IsClipboardEmpty() )
2278 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
2279 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::CLIPBOARD ) );
2282 else if( EventData::EDITING_WITH_PASTE_POPUP == mEventData->mState )
2284 if ( !IsClipboardEmpty() )
2286 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
2287 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::CLIPBOARD ) );
2291 mEventData->mDecorator->SetEnabledPopupButtons( buttonsToShow );
2294 void Controller::Impl::ChangeState( EventData::State newState )
2296 if( NULL == mEventData )
2298 // Nothing to do if there is no text input.
2302 DALI_LOG_INFO( gLogFilter, Debug::General, "ChangeState state:%d newstate:%d\n", mEventData->mState, newState );
2304 if( mEventData->mState != newState )
2306 mEventData->mPreviousState = mEventData->mState;
2307 mEventData->mState = newState;
2309 switch( mEventData->mState )
2311 case EventData::INACTIVE:
2313 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
2314 mEventData->mDecorator->StopCursorBlink();
2315 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
2316 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
2317 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
2318 mEventData->mDecorator->SetHighlightActive( false );
2319 mEventData->mDecorator->SetPopupActive( false );
2320 mEventData->mDecoratorUpdated = true;
2323 case EventData::INTERRUPTED:
2325 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
2326 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
2327 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
2328 mEventData->mDecorator->SetHighlightActive( false );
2329 mEventData->mDecorator->SetPopupActive( false );
2330 mEventData->mDecoratorUpdated = true;
2333 case EventData::SELECTING:
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 )
2344 mEventData->mDecorator->SetPopupActive( true );
2346 mEventData->mDecoratorUpdated = true;
2349 case EventData::EDITING:
2351 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
2352 if( mEventData->mCursorBlinkEnabled )
2354 mEventData->mDecorator->StartCursorBlink();
2356 // Grab handle is not shown until a tap is received whilst EDITING
2357 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
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_POPUP:
2370 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "EDITING_WITH_POPUP \n", newState );
2372 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
2373 if( mEventData->mCursorBlinkEnabled )
2375 mEventData->mDecorator->StartCursorBlink();
2377 if( mEventData->mSelectionEnabled )
2379 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
2380 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
2381 mEventData->mDecorator->SetHighlightActive( false );
2385 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
2387 if( mEventData->mGrabHandlePopupEnabled )
2390 mEventData->mDecorator->SetPopupActive( true );
2392 mEventData->mDecoratorUpdated = true;
2395 case EventData::EDITING_WITH_GRAB_HANDLE:
2397 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "EDITING_WITH_GRAB_HANDLE \n", newState );
2399 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
2400 if( mEventData->mCursorBlinkEnabled )
2402 mEventData->mDecorator->StartCursorBlink();
2404 // Grab handle is not shown until a tap is received whilst EDITING
2405 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
2406 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
2407 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
2408 mEventData->mDecorator->SetHighlightActive( false );
2409 if( mEventData->mGrabHandlePopupEnabled )
2411 mEventData->mDecorator->SetPopupActive( false );
2413 mEventData->mDecoratorUpdated = true;
2416 case EventData::SELECTION_HANDLE_PANNING:
2418 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
2419 mEventData->mDecorator->StopCursorBlink();
2420 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
2421 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true );
2422 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true );
2423 mEventData->mDecorator->SetHighlightActive( true );
2424 if( mEventData->mGrabHandlePopupEnabled )
2426 mEventData->mDecorator->SetPopupActive( false );
2428 mEventData->mDecoratorUpdated = true;
2431 case EventData::GRAB_HANDLE_PANNING:
2433 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "GRAB_HANDLE_PANNING \n", newState );
2435 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
2436 if( mEventData->mCursorBlinkEnabled )
2438 mEventData->mDecorator->StartCursorBlink();
2440 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
2441 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
2442 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
2443 mEventData->mDecorator->SetHighlightActive( false );
2444 if( mEventData->mGrabHandlePopupEnabled )
2446 mEventData->mDecorator->SetPopupActive( false );
2448 mEventData->mDecoratorUpdated = true;
2451 case EventData::EDITING_WITH_PASTE_POPUP:
2453 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "EDITING_WITH_PASTE_POPUP \n", newState );
2455 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
2456 if( mEventData->mCursorBlinkEnabled )
2458 mEventData->mDecorator->StartCursorBlink();
2461 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
2462 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
2463 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
2464 mEventData->mDecorator->SetHighlightActive( false );
2466 if( mEventData->mGrabHandlePopupEnabled )
2469 mEventData->mDecorator->SetPopupActive( true );
2471 mEventData->mDecoratorUpdated = true;
2474 case EventData::TEXT_PANNING:
2476 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
2477 mEventData->mDecorator->StopCursorBlink();
2478 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
2479 if( mEventData->mDecorator->IsHandleActive( LEFT_SELECTION_HANDLE ) ||
2480 mEventData->mDecorator->IsHandleActive( RIGHT_SELECTION_HANDLE ) )
2482 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
2483 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
2484 mEventData->mDecorator->SetHighlightActive( true );
2487 if( mEventData->mGrabHandlePopupEnabled )
2489 mEventData->mDecorator->SetPopupActive( false );
2492 mEventData->mDecoratorUpdated = true;
2499 void Controller::Impl::GetCursorPosition( CharacterIndex logical,
2500 CursorInfo& cursorInfo )
2502 if( !IsShowingRealText() )
2504 // Do not want to use the place-holder text to set the cursor position.
2506 // Use the line's height of the font's family set to set the cursor's size.
2507 // If there is no font's family set, use the default font.
2508 // Use the current alignment to place the cursor at the beginning, center or end of the box.
2510 cursorInfo.lineOffset = 0.f;
2511 cursorInfo.lineHeight = GetDefaultFontLineHeight();
2512 cursorInfo.primaryCursorHeight = cursorInfo.lineHeight;
2514 switch( mModel->mHorizontalAlignment )
2516 case Layout::HORIZONTAL_ALIGN_BEGIN:
2518 cursorInfo.primaryPosition.x = 0.f;
2521 case Layout::HORIZONTAL_ALIGN_CENTER:
2523 cursorInfo.primaryPosition.x = floorf( 0.5f * mModel->mVisualModel->mControlSize.width );
2526 case Layout::HORIZONTAL_ALIGN_END:
2528 cursorInfo.primaryPosition.x = mModel->mVisualModel->mControlSize.width - mEventData->mDecorator->GetCursorWidth();
2533 // Nothing else to do.
2537 Text::GetCursorPosition( mModel->mVisualModel,
2538 mModel->mLogicalModel,
2543 if( Layout::Engine::MULTI_LINE_BOX == mLayoutEngine.GetLayout() )
2545 // If the text is editable and multi-line, the cursor position after a white space shouldn't exceed the boundaries of the text control.
2547 // Note the white spaces laid-out at the end of the line might exceed the boundaries of the control.
2548 // The reason is a wrapped line must not start with a white space so they are laid-out at the end of the line.
2550 if( 0.f > cursorInfo.primaryPosition.x )
2552 cursorInfo.primaryPosition.x = 0.f;
2555 const float edgeWidth = mModel->mVisualModel->mControlSize.width - static_cast<float>( mEventData->mDecorator->GetCursorWidth() );
2556 if( cursorInfo.primaryPosition.x > edgeWidth )
2558 cursorInfo.primaryPosition.x = edgeWidth;
2563 CharacterIndex Controller::Impl::CalculateNewCursorIndex( CharacterIndex index ) const
2565 if( NULL == mEventData )
2567 // Nothing to do if there is no text input.
2571 CharacterIndex cursorIndex = mEventData->mPrimaryCursorPosition;
2573 const GlyphIndex* const charactersToGlyphBuffer = mModel->mVisualModel->mCharactersToGlyph.Begin();
2574 const Length* const charactersPerGlyphBuffer = mModel->mVisualModel->mCharactersPerGlyph.Begin();
2576 GlyphIndex glyphIndex = *( charactersToGlyphBuffer + index );
2577 Length numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
2579 if( numberOfCharacters > 1u )
2581 const Script script = mModel->mLogicalModel->GetScript( index );
2582 if( HasLigatureMustBreak( script ) )
2584 // Prevents to jump the whole Latin ligatures like fi, ff, or Arabic ï»», ...
2585 numberOfCharacters = 1u;
2590 while( 0u == numberOfCharacters )
2593 numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
2597 if( index < mEventData->mPrimaryCursorPosition )
2599 cursorIndex -= numberOfCharacters;
2603 cursorIndex += numberOfCharacters;
2606 // Will update the cursor hook position.
2607 mEventData->mUpdateCursorHookPosition = true;
2612 void Controller::Impl::UpdateCursorPosition( const CursorInfo& cursorInfo )
2614 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "-->Controller::UpdateCursorPosition %p\n", this );
2615 if( NULL == mEventData )
2617 // Nothing to do if there is no text input.
2618 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition no event data\n" );
2622 const Vector2 cursorPosition = cursorInfo.primaryPosition + mModel->mScrollPosition;
2624 // Sets the cursor position.
2625 mEventData->mDecorator->SetPosition( PRIMARY_CURSOR,
2628 cursorInfo.primaryCursorHeight,
2629 cursorInfo.lineHeight );
2630 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Primary cursor position: %f,%f\n", cursorPosition.x, cursorPosition.y );
2632 if( mEventData->mUpdateGrabHandlePosition )
2634 // Sets the grab handle position.
2635 mEventData->mDecorator->SetPosition( GRAB_HANDLE,
2637 cursorInfo.lineOffset + mModel->mScrollPosition.y,
2638 cursorInfo.lineHeight );
2641 if( cursorInfo.isSecondaryCursor )
2643 mEventData->mDecorator->SetPosition( SECONDARY_CURSOR,
2644 cursorInfo.secondaryPosition.x + mModel->mScrollPosition.x,
2645 cursorInfo.secondaryPosition.y + mModel->mScrollPosition.y,
2646 cursorInfo.secondaryCursorHeight,
2647 cursorInfo.lineHeight );
2648 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Secondary cursor position: %f,%f\n", cursorInfo.secondaryPosition.x + mModel->mScrollPosition.x, cursorInfo.secondaryPosition.y + mModel->mScrollPosition.y );
2651 // Set which cursors are active according the state.
2652 if( EventData::IsEditingState( mEventData->mState ) || ( EventData::GRAB_HANDLE_PANNING == mEventData->mState ) )
2654 if( cursorInfo.isSecondaryCursor )
2656 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_BOTH );
2660 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
2665 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
2668 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition\n" );
2671 void Controller::Impl::UpdateSelectionHandle( HandleType handleType,
2672 const CursorInfo& cursorInfo )
2674 if( ( LEFT_SELECTION_HANDLE != handleType ) &&
2675 ( RIGHT_SELECTION_HANDLE != handleType ) )
2680 const Vector2 cursorPosition = cursorInfo.primaryPosition + mModel->mScrollPosition;
2682 // Sets the handle's position.
2683 mEventData->mDecorator->SetPosition( handleType,
2685 cursorInfo.lineOffset + mModel->mScrollPosition.y,
2686 cursorInfo.lineHeight );
2688 // If selection handle at start of the text and other at end of the text then all text is selected.
2689 const CharacterIndex startOfSelection = std::min( mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition );
2690 const CharacterIndex endOfSelection = std::max ( mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition );
2691 mEventData->mAllTextSelected = ( startOfSelection == 0 ) && ( endOfSelection == mModel->mLogicalModel->mText.Count() );
2694 void Controller::Impl::ClampHorizontalScroll( const Vector2& layoutSize )
2696 // Clamp between -space & -alignment offset.
2698 if( layoutSize.width > mModel->mVisualModel->mControlSize.width )
2700 const float space = ( layoutSize.width - mModel->mVisualModel->mControlSize.width ) + mModel->mAlignmentOffset;
2701 mModel->mScrollPosition.x = ( mModel->mScrollPosition.x < -space ) ? -space : mModel->mScrollPosition.x;
2702 mModel->mScrollPosition.x = ( mModel->mScrollPosition.x > -mModel->mAlignmentOffset ) ? -mModel->mAlignmentOffset : mModel->mScrollPosition.x;
2704 mEventData->mDecoratorUpdated = true;
2708 mModel->mScrollPosition.x = 0.f;
2712 void Controller::Impl::ClampVerticalScroll( const Vector2& layoutSize )
2714 if( Layout::Engine::SINGLE_LINE_BOX == mLayoutEngine.GetLayout() )
2716 // Nothing to do if the text is single line.
2720 // Clamp between -space & 0.
2721 if( layoutSize.height > mModel->mVisualModel->mControlSize.height )
2723 const float space = ( layoutSize.height - mModel->mVisualModel->mControlSize.height );
2724 mModel->mScrollPosition.y = ( mModel->mScrollPosition.y < -space ) ? -space : mModel->mScrollPosition.y;
2725 mModel->mScrollPosition.y = ( mModel->mScrollPosition.y > 0.f ) ? 0.f : mModel->mScrollPosition.y;
2727 mEventData->mDecoratorUpdated = true;
2731 mModel->mScrollPosition.y = 0.f;
2735 void Controller::Impl::ScrollToMakePositionVisible( const Vector2& position, float lineHeight )
2737 const float cursorWidth = mEventData->mDecorator ? static_cast<float>( mEventData->mDecorator->GetCursorWidth() ) : 0.f;
2739 // position is in actor's coords.
2740 const float positionEndX = position.x + cursorWidth;
2741 const float positionEndY = position.y + lineHeight;
2743 // Transform the position to decorator coords.
2744 const float decoratorPositionBeginX = position.x + mModel->mScrollPosition.x;
2745 const float decoratorPositionEndX = positionEndX + mModel->mScrollPosition.x;
2747 const float decoratorPositionBeginY = position.y + mModel->mScrollPosition.y;
2748 const float decoratorPositionEndY = positionEndY + mModel->mScrollPosition.y;
2750 if( decoratorPositionBeginX < 0.f )
2752 mModel->mScrollPosition.x = -position.x;
2754 else if( decoratorPositionEndX > mModel->mVisualModel->mControlSize.width )
2756 mModel->mScrollPosition.x = mModel->mVisualModel->mControlSize.width - positionEndX;
2759 if( decoratorPositionBeginY < 0.f )
2761 mModel->mScrollPosition.y = -position.y;
2763 else if( decoratorPositionEndY > mModel->mVisualModel->mControlSize.height )
2765 mModel->mScrollPosition.y = mModel->mVisualModel->mControlSize.height - positionEndY;
2769 void Controller::Impl::ScrollTextToMatchCursor( const CursorInfo& cursorInfo )
2771 // Get the current cursor position in decorator coords.
2772 const Vector2& currentCursorPosition = mEventData->mDecorator->GetPosition( PRIMARY_CURSOR );
2774 // Calculate the offset to match the cursor position before the character was deleted.
2775 mModel->mScrollPosition.x = currentCursorPosition.x - cursorInfo.primaryPosition.x;
2776 mModel->mScrollPosition.y = currentCursorPosition.y - cursorInfo.lineOffset;
2778 ClampHorizontalScroll( mModel->mVisualModel->GetLayoutSize() );
2779 ClampVerticalScroll( mModel->mVisualModel->GetLayoutSize() );
2781 // Makes the new cursor position visible if needed.
2782 ScrollToMakePositionVisible( cursorInfo.primaryPosition, cursorInfo.lineHeight );
2785 void Controller::Impl::RequestRelayout()
2787 if( NULL != mControlInterface )
2789 mControlInterface->RequestTextRelayout();
2795 } // namespace Toolkit