2 * Copyright (c) 2015 Samsung Electronics Co., Ltd.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
19 #include <dali-toolkit/internal/text/text-controller-impl.h>
22 #include <dali/public-api/adaptor-framework/key.h>
23 #include <dali/integration-api/debug.h>
26 #include <dali-toolkit/internal/text/bidirectional-support.h>
27 #include <dali-toolkit/internal/text/character-set-conversion.h>
28 #include <dali-toolkit/internal/text/layouts/layout-parameters.h>
29 #include <dali-toolkit/internal/text/multi-language-support.h>
30 #include <dali-toolkit/internal/text/script-run.h>
31 #include <dali-toolkit/internal/text/segmentation.h>
32 #include <dali-toolkit/internal/text/shaper.h>
33 #include <dali-toolkit/internal/text/text-io.h>
34 #include <dali-toolkit/internal/text/text-view.h>
39 #if defined(DEBUG_ENABLED)
40 Debug::Filter* gLogFilter = Debug::Filter::New(Debug::Concise, true, "LOG_TEXT_CONTROLS");
44 * @brief Some characters can be shaped in more than one glyph.
45 * This struct is used to retrieve metrics from these group of glyphs.
59 float fontHeight; ///< The font's height of that glyphs.
60 float advance; ///< The sum of all the advances of all the glyphs.
61 float ascender; ///< The font's ascender.
62 float xBearing; ///< The x bearing of the first glyph.
65 const std::string EMPTY_STRING("");
79 * @brief Get some glyph's metrics of a group of glyphs formed as a result of shaping one character.
81 * @param[in] glyphIndex The index to the first glyph.
82 * @param[in] numberOfGlyphs The number of glyphs.
83 * @param[out] glyphMetrics Some glyph metrics (font height, advance, ascender and x bearing).
87 void GetGlyphsMetrics( GlyphIndex glyphIndex,
88 Length numberOfGlyphs,
89 GlyphMetrics& glyphMetrics,
90 VisualModelPtr visualModel,
91 TextAbstraction::FontClient& fontClient )
93 const GlyphInfo* glyphsBuffer = visualModel->mGlyphs.Begin();
95 const GlyphInfo& firstGlyph = *( glyphsBuffer + glyphIndex );
97 Text::FontMetrics fontMetrics;
98 fontClient.GetFontMetrics( firstGlyph.fontId, fontMetrics );
100 glyphMetrics.fontHeight = fontMetrics.height;
101 glyphMetrics.advance = firstGlyph.advance;
102 glyphMetrics.ascender = fontMetrics.ascender;
103 glyphMetrics.xBearing = firstGlyph.xBearing;
105 for( unsigned int i = 1u; i < numberOfGlyphs; ++i )
107 const GlyphInfo& glyphInfo = *( glyphsBuffer + glyphIndex + i );
109 glyphMetrics.advance += glyphInfo.advance;
113 EventData::EventData( DecoratorPtr decorator )
114 : mDecorator( decorator ),
115 mPlaceholderTextActive(),
116 mPlaceholderTextInactive(),
117 mPlaceholderTextColor( 0.8f, 0.8f, 0.8f, 0.8f ),
121 mPrimaryCursorPosition( 0u ),
122 mLeftSelectionPosition( 0u ),
123 mRightSelectionPosition( 0u ),
124 mPreEditStartPosition( 0u ),
125 mPreEditLength( 0u ),
126 mIsShowingPlaceholderText( false ),
127 mPreEditFlag( false ),
128 mDecoratorUpdated( false ),
129 mCursorBlinkEnabled( true ),
130 mGrabHandleEnabled( true ),
131 mGrabHandlePopupEnabled( true ),
132 mSelectionEnabled( true ),
133 mHorizontalScrollingEnabled( true ),
134 mVerticalScrollingEnabled( false ),
135 mUpdateCursorPosition( false ),
136 mUpdateLeftSelectionPosition( false ),
137 mUpdateRightSelectionPosition( false ),
138 mScrollAfterUpdatePosition( false ),
139 mScrollAfterDelete( false )
142 EventData::~EventData()
145 bool Controller::Impl::ProcessInputEvents()
147 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "-->Controller::ProcessInputEvents\n" );
148 if( NULL == mEventData )
150 // Nothing to do if there is no text input.
151 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::ProcessInputEvents no event data\n" );
155 if( mEventData->mDecorator )
157 for( std::vector<Event>::iterator iter = mEventData->mEventQueue.begin();
158 iter != mEventData->mEventQueue.end();
163 case Event::CURSOR_KEY_EVENT:
165 OnCursorKeyEvent( *iter );
168 case Event::TAP_EVENT:
173 case Event::PAN_EVENT:
178 case Event::GRAB_HANDLE_EVENT:
179 case Event::LEFT_SELECTION_HANDLE_EVENT:
180 case Event::RIGHT_SELECTION_HANDLE_EVENT: // Fall through
182 OnHandleEvent( *iter );
187 OnSelectEvent( *iter );
190 case Event::SELECT_ALL:
199 // The cursor must also be repositioned after inserts into the model
200 if( mEventData->mUpdateCursorPosition )
202 // Updates the cursor position and scrolls the text to make it visible.
204 UpdateCursorPosition();
206 if( mEventData->mScrollAfterUpdatePosition )
208 const Vector2& primaryCursorPosition = mEventData->mDecorator->GetPosition( PRIMARY_CURSOR );
210 ScrollToMakePositionVisible( primaryCursorPosition );
211 mEventData->mScrollAfterUpdatePosition = false;
214 mEventData->mDecoratorUpdated = true;
215 mEventData->mUpdateCursorPosition = false;
217 else if( mEventData->mScrollAfterDelete )
219 ScrollTextToMatchCursor();
220 mEventData->mDecoratorUpdated = true;
221 mEventData->mScrollAfterDelete = false;
225 bool leftScroll = false;
226 bool rightScroll = false;
228 if( mEventData->mUpdateLeftSelectionPosition )
230 UpdateSelectionHandle( LEFT_SELECTION_HANDLE );
232 if( mEventData->mScrollAfterUpdatePosition )
234 const Vector2& leftHandlePosition = mEventData->mDecorator->GetPosition( LEFT_SELECTION_HANDLE );
236 ScrollToMakePositionVisible( leftHandlePosition );
240 mEventData->mDecoratorUpdated = true;
241 mEventData->mUpdateLeftSelectionPosition = false;
244 if( mEventData->mUpdateRightSelectionPosition )
246 UpdateSelectionHandle( RIGHT_SELECTION_HANDLE );
248 if( mEventData->mScrollAfterUpdatePosition )
250 const Vector2& rightHandlePosition = mEventData->mDecorator->GetPosition( RIGHT_SELECTION_HANDLE );
252 ScrollToMakePositionVisible( rightHandlePosition );
256 mEventData->mDecoratorUpdated = true;
257 mEventData->mUpdateRightSelectionPosition = false;
260 if( leftScroll || rightScroll )
262 mEventData->mScrollAfterUpdatePosition = false;
266 mEventData->mEventQueue.clear();
268 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::ProcessInputEvents\n" );
270 const bool decoratorUpdated = mEventData->mDecoratorUpdated;
271 mEventData->mDecoratorUpdated = false;
273 return decoratorUpdated;
276 void Controller::Impl::UpdateModel( OperationsMask operationsRequired )
278 // Calculate the operations to be done.
279 const OperationsMask operations = static_cast<OperationsMask>( mOperationsPending & operationsRequired );
281 Vector<Character>& utf32Characters = mLogicalModel->mText;
283 const Length numberOfCharacters = utf32Characters.Count();
285 Vector<LineBreakInfo>& lineBreakInfo = mLogicalModel->mLineBreakInfo;
286 if( GET_LINE_BREAKS & operations )
288 // Retrieves the line break info. The line break info is used to split the text in 'paragraphs' to
289 // calculate the bidirectional info for each 'paragraph'.
290 // It's also used to layout the text (where it should be a new line) or to shape the text (text in different lines
291 // is not shaped together).
292 lineBreakInfo.Resize( numberOfCharacters, TextAbstraction::LINE_NO_BREAK );
294 SetLineBreakInfo( utf32Characters,
298 Vector<WordBreakInfo>& wordBreakInfo = mLogicalModel->mWordBreakInfo;
299 if( GET_WORD_BREAKS & operations )
301 // Retrieves the word break info. The word break info is used to layout the text (where to wrap the text in lines).
302 wordBreakInfo.Resize( numberOfCharacters, TextAbstraction::WORD_NO_BREAK );
304 SetWordBreakInfo( utf32Characters,
308 const bool getScripts = GET_SCRIPTS & operations;
309 const bool validateFonts = VALIDATE_FONTS & operations;
311 Vector<ScriptRun>& scripts = mLogicalModel->mScriptRuns;
312 Vector<FontRun>& validFonts = mLogicalModel->mFontRuns;
314 if( getScripts || validateFonts )
316 // Validates the fonts assigned by the application or assigns default ones.
317 // It makes sure all the characters are going to be rendered by the correct font.
318 MultilanguageSupport multilanguageSupport = MultilanguageSupport::Get();
322 // Retrieves the scripts used in the text.
323 multilanguageSupport.SetScripts( utf32Characters,
330 if( 0u == validFonts.Count() )
332 // Copy the requested font defaults received via the property system.
333 // These may not be valid i.e. may not contain glyphs for the necessary scripts.
334 GetDefaultFonts( validFonts, numberOfCharacters );
337 // Validates the fonts. If there is a character with no assigned font it sets a default one.
338 // After this call, fonts are validated.
339 multilanguageSupport.ValidateFonts( utf32Characters,
345 Vector<Character> mirroredUtf32Characters;
346 bool textMirrored = false;
347 if( BIDI_INFO & operations )
349 // Count the number of LINE_NO_BREAK to reserve some space for the vector of paragraph's
350 // bidirectional info.
352 Length numberOfParagraphs = 0u;
354 const TextAbstraction::LineBreakInfo* lineBreakInfoBuffer = lineBreakInfo.Begin();
355 for( Length index = 0u; index < numberOfCharacters; ++index )
357 if( TextAbstraction::LINE_NO_BREAK == *( lineBreakInfoBuffer + index ) )
359 ++numberOfParagraphs;
363 Vector<BidirectionalParagraphInfoRun>& bidirectionalInfo = mLogicalModel->mBidirectionalParagraphInfo;
364 bidirectionalInfo.Reserve( numberOfParagraphs );
366 // Calculates the bidirectional info for the whole paragraph if it contains right to left scripts.
367 SetBidirectionalInfo( utf32Characters,
372 if( 0u != bidirectionalInfo.Count() )
374 // This paragraph has right to left text. Some characters may need to be mirrored.
375 // TODO: consider if the mirrored string can be stored as well.
377 textMirrored = GetMirroredText( utf32Characters, mirroredUtf32Characters );
379 // Only set the character directions if there is right to left characters.
380 Vector<CharacterDirection>& directions = mLogicalModel->mCharacterDirections;
381 directions.Resize( numberOfCharacters );
383 GetCharactersDirection( bidirectionalInfo,
388 // There is no right to left characters. Clear the directions vector.
389 mLogicalModel->mCharacterDirections.Clear();
394 Vector<GlyphInfo>& glyphs = mVisualModel->mGlyphs;
395 Vector<CharacterIndex>& glyphsToCharactersMap = mVisualModel->mGlyphsToCharacters;
396 Vector<Length>& charactersPerGlyph = mVisualModel->mCharactersPerGlyph;
397 if( SHAPE_TEXT & operations )
399 const Vector<Character>& textToShape = textMirrored ? mirroredUtf32Characters : utf32Characters;
401 ShapeText( textToShape,
406 glyphsToCharactersMap,
407 charactersPerGlyph );
409 // Create the 'number of glyphs' per character and the glyph to character conversion tables.
410 mVisualModel->CreateGlyphsPerCharacterTable( numberOfCharacters );
411 mVisualModel->CreateCharacterToGlyphTable( numberOfCharacters );
414 const Length numberOfGlyphs = glyphs.Count();
416 if( GET_GLYPH_METRICS & operations )
418 mFontClient.GetGlyphMetrics( glyphs.Begin(), numberOfGlyphs );
422 void Controller::Impl::GetDefaultFonts( Vector<FontRun>& fonts, Length numberOfCharacters )
427 fontRun.characterRun.characterIndex = 0;
428 fontRun.characterRun.numberOfCharacters = numberOfCharacters;
429 fontRun.fontId = mFontDefaults->GetFontId( mFontClient );
430 fontRun.isDefault = true;
432 fonts.PushBack( fontRun );
436 void Controller::Impl::OnCursorKeyEvent( const Event& event )
438 if( NULL == mEventData )
440 // Nothing to do if there is no text input.
444 int keyCode = event.p1.mInt;
446 if( Dali::DALI_KEY_CURSOR_LEFT == keyCode )
448 if( mEventData->mPrimaryCursorPosition > 0u )
450 mEventData->mPrimaryCursorPosition = CalculateNewCursorIndex( mEventData->mPrimaryCursorPosition - 1u );
453 else if( Dali::DALI_KEY_CURSOR_RIGHT == keyCode )
455 if( mLogicalModel->mText.Count() > mEventData->mPrimaryCursorPosition )
457 mEventData->mPrimaryCursorPosition = CalculateNewCursorIndex( mEventData->mPrimaryCursorPosition );
460 else if( Dali::DALI_KEY_CURSOR_UP == keyCode )
464 else if( Dali::DALI_KEY_CURSOR_DOWN == keyCode )
469 mEventData->mUpdateCursorPosition = true;
470 mEventData->mScrollAfterUpdatePosition = true;
473 void Controller::Impl::OnTapEvent( const Event& event )
475 if( NULL != mEventData )
477 const unsigned int tapCount = event.p1.mUint;
481 if( ! IsShowingPlaceholderText() )
483 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
484 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
486 mEventData->mPrimaryCursorPosition = GetClosestCursorIndex( xPosition,
491 mEventData->mPrimaryCursorPosition = 0u;
494 mEventData->mUpdateCursorPosition = true;
495 mEventData->mScrollAfterUpdatePosition = true;
500 void Controller::Impl::OnPanEvent( const Event& event )
502 if( NULL == mEventData )
504 // Nothing to do if there is no text input.
508 int state = event.p1.mInt;
510 if( Gesture::Started == state ||
511 Gesture::Continuing == state )
513 const Vector2& actualSize = mVisualModel->GetActualSize();
514 const Vector2 currentScroll = mEventData->mScrollPosition;
516 if( mEventData->mHorizontalScrollingEnabled )
518 const float displacementX = event.p2.mFloat;
519 mEventData->mScrollPosition.x += displacementX;
521 ClampHorizontalScroll( actualSize );
524 if( mEventData->mVerticalScrollingEnabled )
526 const float displacementY = event.p3.mFloat;
527 mEventData->mScrollPosition.y += displacementY;
529 ClampVerticalScroll( actualSize );
532 if( mEventData->mDecorator )
534 mEventData->mDecorator->UpdatePositions( mEventData->mScrollPosition - currentScroll );
539 void Controller::Impl::OnHandleEvent( const Event& event )
541 if( NULL == mEventData )
543 // Nothing to do if there is no text input.
547 const unsigned int state = event.p1.mUint;
548 const bool handleStopScrolling = ( HANDLE_STOP_SCROLLING == state );
550 if( HANDLE_PRESSED == state )
552 // The event.p2 and event.p3 are in decorator coords. Need to transforms to text coords.
553 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
554 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
556 const CharacterIndex handleNewPosition = GetClosestCursorIndex( xPosition, yPosition );
558 if( Event::GRAB_HANDLE_EVENT == event.type )
560 ChangeState ( EventData::GRAB_HANDLE_PANNING );
562 if( handleNewPosition != mEventData->mPrimaryCursorPosition )
564 mEventData->mPrimaryCursorPosition = handleNewPosition;
565 mEventData->mUpdateCursorPosition = true;
568 else if( Event::LEFT_SELECTION_HANDLE_EVENT == event.type )
570 ChangeState ( EventData::SELECTION_HANDLE_PANNING );
572 if( handleNewPosition != mEventData->mLeftSelectionPosition )
574 mEventData->mLeftSelectionPosition = handleNewPosition;
576 RepositionSelectionHandles( mEventData->mLeftSelectionPosition,
577 mEventData->mRightSelectionPosition );
579 mEventData->mUpdateLeftSelectionPosition = true;
582 else if( Event::RIGHT_SELECTION_HANDLE_EVENT == event.type )
584 ChangeState ( EventData::SELECTION_HANDLE_PANNING );
586 if( handleNewPosition != mEventData->mRightSelectionPosition )
588 mEventData->mRightSelectionPosition = handleNewPosition;
590 RepositionSelectionHandles( mEventData->mLeftSelectionPosition,
591 mEventData->mRightSelectionPosition );
593 mEventData->mUpdateRightSelectionPosition = true;
596 } // end ( HANDLE_PRESSED == state )
597 else if( ( HANDLE_RELEASED == state ) ||
598 handleStopScrolling )
600 CharacterIndex handlePosition = 0u;
601 if( handleStopScrolling )
603 // The event.p2 and event.p3 are in decorator coords. Need to transforms to text coords.
604 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
605 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
607 handlePosition = GetClosestCursorIndex( xPosition, yPosition );
610 if( Event::GRAB_HANDLE_EVENT == event.type )
612 mEventData->mUpdateCursorPosition = true;
614 ChangeState( EventData::EDITING_WITH_POPUP );
616 if( handleStopScrolling )
618 mEventData->mScrollAfterUpdatePosition = mEventData->mPrimaryCursorPosition != handlePosition;
619 mEventData->mPrimaryCursorPosition = handlePosition;
622 else if( Event::LEFT_SELECTION_HANDLE_EVENT == event.type )
624 ChangeState( EventData::SELECTING );
626 if( handleStopScrolling )
628 mEventData->mUpdateLeftSelectionPosition = mEventData->mLeftSelectionPosition != handlePosition;
629 mEventData->mScrollAfterUpdatePosition = mEventData->mUpdateLeftSelectionPosition;
630 mEventData->mLeftSelectionPosition = handlePosition;
632 if( mEventData->mUpdateLeftSelectionPosition )
634 RepositionSelectionHandles( mEventData->mLeftSelectionPosition,
635 mEventData->mRightSelectionPosition );
639 else if( Event::RIGHT_SELECTION_HANDLE_EVENT == event.type )
641 ChangeState( EventData::SELECTING );
643 if( handleStopScrolling )
645 mEventData->mUpdateRightSelectionPosition = mEventData->mRightSelectionPosition != handlePosition;
646 mEventData->mScrollAfterUpdatePosition = mEventData->mUpdateRightSelectionPosition;
647 mEventData->mRightSelectionPosition = handlePosition;
649 if( mEventData->mUpdateRightSelectionPosition )
651 RepositionSelectionHandles( mEventData->mLeftSelectionPosition,
652 mEventData->mRightSelectionPosition );
657 mEventData->mDecoratorUpdated = true;
658 } // end ( ( HANDLE_RELEASED == state ) || ( HANDLE_STOP_SCROLLING == state ) )
659 else if( HANDLE_SCROLLING == state )
661 const float xSpeed = event.p2.mFloat;
662 const Vector2& actualSize = mVisualModel->GetActualSize();
664 mEventData->mScrollPosition.x += xSpeed;
666 ClampHorizontalScroll( actualSize );
668 const bool leftSelectionHandleEvent = Event::LEFT_SELECTION_HANDLE_EVENT == event.type;
669 const bool rightSelectionHandleEvent = Event::RIGHT_SELECTION_HANDLE_EVENT == event.type;
671 if( Event::GRAB_HANDLE_EVENT == event.type )
673 ChangeState( EventData::GRAB_HANDLE_PANNING );
675 else if( leftSelectionHandleEvent || rightSelectionHandleEvent )
677 // TODO: This is recalculating the selection box every time the text is scrolled with the selection handles.
678 // Think if something can be done to save power.
680 ChangeState( EventData::SELECTION_HANDLE_PANNING );
682 const Vector2& position = mEventData->mDecorator->GetPosition( leftSelectionHandleEvent ? Text::LEFT_SELECTION_HANDLE : Text::RIGHT_SELECTION_HANDLE );
684 // Get the new handle position.
685 // The selection handle's position is in decorator coords. Need to transforms to text coords.
686 const CharacterIndex handlePosition = GetClosestCursorIndex( position.x - mEventData->mScrollPosition.x - mAlignmentOffset.x,
687 position.y - mEventData->mScrollPosition.y - mAlignmentOffset.y );
689 if( leftSelectionHandleEvent )
691 mEventData->mUpdateLeftSelectionPosition = handlePosition != mEventData->mLeftSelectionPosition;
692 mEventData->mLeftSelectionPosition = handlePosition;
696 mEventData->mUpdateRightSelectionPosition = handlePosition != mEventData->mRightSelectionPosition;
697 mEventData->mRightSelectionPosition = handlePosition;
700 if( mEventData->mUpdateLeftSelectionPosition || mEventData->mUpdateRightSelectionPosition )
702 RepositionSelectionHandles( mEventData->mLeftSelectionPosition,
703 mEventData->mRightSelectionPosition );
706 mEventData->mDecoratorUpdated = true;
707 } // end ( HANDLE_SCROLLING == state )
710 void Controller::Impl::OnSelectEvent( const Event& event )
712 if( NULL == mEventData )
714 // Nothing to do if there is no text.
718 if( mEventData->mSelectionEnabled )
720 // The event.p2 and event.p3 are in decorator coords. Need to transforms to text coords.
721 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
722 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
724 RepositionSelectionHandles( xPosition,
727 mEventData->mScrollAfterUpdatePosition = true;
728 mEventData->mUpdateLeftSelectionPosition = true;
729 mEventData->mUpdateRightSelectionPosition = true;
733 void Controller::Impl::OnSelectAllEvent()
735 if( NULL == mEventData )
737 // Nothing to do if there is no text.
741 if( mEventData->mSelectionEnabled )
743 RepositionSelectionHandles( 0u,
744 mLogicalModel->mText.Count() );
746 mEventData->mScrollAfterUpdatePosition = true;
747 mEventData->mUpdateLeftSelectionPosition = true;
748 mEventData->mUpdateRightSelectionPosition = true;
752 void Controller::Impl::RetreiveSelection( std::string& selectedText, bool deleteAfterRetreival )
754 if( mEventData->mLeftSelectionPosition == mEventData->mRightSelectionPosition )
756 // Nothing to select if handles are in the same place.
761 //Get start and end position of selection
762 uint32_t startOfSelectedText = mEventData->mLeftSelectionPosition;
763 uint32_t lengthOfSelectedText = mEventData->mRightSelectionPosition - startOfSelectedText;
765 // Validate the start and end selection points
766 if( ( startOfSelectedText >= 0 ) && ( ( startOfSelectedText + lengthOfSelectedText ) <= mLogicalModel->mText.Count() ) )
768 //Get text as a UTF8 string
769 Vector<Character>& utf32Characters = mLogicalModel->mText;
771 Utf32ToUtf8( &utf32Characters[startOfSelectedText], lengthOfSelectedText, selectedText );
773 if ( deleteAfterRetreival ) // Only delete text if copied successfully
775 // Delete text between handles
776 Vector<Character>& currentText = mLogicalModel->mText;
778 Vector<Character>::Iterator first = currentText.Begin() + startOfSelectedText;
779 Vector<Character>::Iterator last = first + lengthOfSelectedText;
780 currentText.Erase( first, last );
782 mEventData->mPrimaryCursorPosition = mEventData->mLeftSelectionPosition;
783 mEventData->mScrollAfterDelete = true;
784 mEventData->mDecoratorUpdated = true;
788 bool Controller::Impl::CopyStringToClipboard( std::string& source )
790 //Send string to clipboard
791 return ( mClipboard && mClipboard.SetItem( source ) );
794 void Controller::Impl::SendSelectionToClipboard( bool deleteAfterSending )
796 std::string selectedText;
797 RetreiveSelection( selectedText, deleteAfterSending );
798 CopyStringToClipboard( selectedText );
799 ChangeState( EventData::EDITING );
802 void Controller::Impl::GetTextFromClipboard( unsigned int itemIndex, std::string& retreivedString )
806 retreivedString = mClipboard.GetItem( itemIndex );
810 void Controller::Impl::RepositionSelectionHandles( CharacterIndex selectionStart, CharacterIndex selectionEnd )
812 if( selectionStart == selectionEnd )
814 // Nothing to select if handles are in the same place.
818 mEventData->mDecorator->ClearHighlights();
820 mEventData->mLeftSelectionPosition = selectionStart;
821 mEventData->mRightSelectionPosition = selectionEnd;
823 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
824 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
825 const GlyphInfo* const glyphsBuffer = mVisualModel->mGlyphs.Begin();
826 const Vector2* const positionsBuffer = mVisualModel->mGlyphPositions.Begin();
828 // TODO: Better algorithm to create the highlight box.
831 const Vector<LineRun>& lines = mVisualModel->mLines;
832 const LineRun& firstLine = *lines.Begin();
833 const float height = firstLine.ascender + -firstLine.descender;
835 const bool indicesSwapped = ( selectionStart > selectionEnd );
838 std::swap( selectionStart, selectionEnd );
841 GlyphIndex glyphStart = *( charactersToGlyphBuffer + selectionStart );
842 GlyphIndex glyphEnd = *( charactersToGlyphBuffer + ( selectionEnd - 1u ) ) + *( glyphsPerCharacterBuffer + ( selectionEnd - 1u ) ) - 1u;
844 mEventData->mDecorator->SwapSelectionHandlesEnabled( firstLine.direction != indicesSwapped );
846 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
848 for( GlyphIndex index = glyphStart; index <= glyphEnd; ++index )
850 const GlyphInfo& glyph = *( glyphsBuffer + index );
851 const Vector2& position = *( positionsBuffer + index );
853 const float xPosition = position.x - glyph.xBearing + offset.x;
854 mEventData->mDecorator->AddHighlight( xPosition, offset.y, xPosition + glyph.advance, height );
857 CursorInfo primaryCursorInfo;
858 GetCursorPosition( mEventData->mLeftSelectionPosition,
861 CursorInfo secondaryCursorInfo;
862 GetCursorPosition( mEventData->mRightSelectionPosition,
863 secondaryCursorInfo );
865 const Vector2 primaryPosition = primaryCursorInfo.primaryPosition + offset;
866 const Vector2 secondaryPosition = secondaryCursorInfo.primaryPosition + offset;
868 mEventData->mDecorator->SetPosition( LEFT_SELECTION_HANDLE, primaryPosition.x, primaryPosition.y, primaryCursorInfo.lineHeight );
870 mEventData->mDecorator->SetPosition( RIGHT_SELECTION_HANDLE, secondaryPosition.x, secondaryPosition.y, secondaryCursorInfo.lineHeight );
872 // Set the flag to update the decorator.
873 mEventData->mDecoratorUpdated = true;
876 void Controller::Impl::RepositionSelectionHandles( float visualX, float visualY )
878 if( NULL == mEventData )
880 // Nothing to do if there is no text input.
884 if( IsShowingPlaceholderText() )
886 // Nothing to do if there is the place-holder text.
890 const Length numberOfGlyphs = mVisualModel->mGlyphs.Count();
891 const Length numberOfLines = mVisualModel->mLines.Count();
892 if( 0 == numberOfGlyphs ||
895 // Nothing to do if there is no text.
899 // Find which word was selected
900 CharacterIndex selectionStart( 0 );
901 CharacterIndex selectionEnd( 0 );
902 FindSelectionIndices( visualX, visualY, selectionStart, selectionEnd );
903 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "%p selectionStart %d selectionEnd %d\n", this, selectionStart, selectionEnd );
905 if( selectionStart == selectionEnd )
907 ChangeState( EventData::EDITING );
908 // Nothing to select. i.e. a white space, out of bounds
912 RepositionSelectionHandles( selectionStart, selectionEnd );
915 void Controller::Impl::ChangeState( EventData::State newState )
917 if( NULL == mEventData )
919 // Nothing to do if there is no text input.
923 if( mEventData->mState != newState )
925 mEventData->mState = newState;
927 if( EventData::INACTIVE == mEventData->mState )
929 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
930 mEventData->mDecorator->StopCursorBlink();
931 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
932 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
933 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
934 mEventData->mDecorator->SetPopupActive( false );
935 mEventData->mDecoratorUpdated = true;
937 else if ( EventData::SELECTING == mEventData->mState )
939 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
940 mEventData->mDecorator->StopCursorBlink();
941 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
942 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true );
943 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true );
944 if( mEventData->mGrabHandlePopupEnabled )
946 TextSelectionPopup::Buttons buttonsToShow = TextSelectionPopup::Buttons( TextSelectionPopup::CUT | TextSelectionPopup::COPY );
947 if ( !IsClipboardEmpty() )
949 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
952 mEventData->mDecorator->SetEnabledPopupButtons( buttonsToShow );
953 mEventData->mDecorator->SetPopupActive( true );
955 mEventData->mDecoratorUpdated = true;
957 else if ( EventData::SELECTION_CHANGED == mEventData->mState )
959 if( mEventData->mGrabHandlePopupEnabled )
961 TextSelectionPopup::Buttons buttonsToShow = TextSelectionPopup::Buttons( TextSelectionPopup::CUT | TextSelectionPopup::COPY );
962 if ( !IsClipboardEmpty() )
964 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
966 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
967 mEventData->mDecorator->SetEnabledPopupButtons( buttonsToShow );
968 mEventData->mDecorator->SetPopupActive( true );
970 mEventData->mDecoratorUpdated = true;
972 else if( EventData::EDITING == mEventData->mState )
974 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
975 if( mEventData->mCursorBlinkEnabled )
977 mEventData->mDecorator->StartCursorBlink();
979 // Grab handle is not shown until a tap is received whilst EDITING
980 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
981 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
982 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
983 if( mEventData->mGrabHandlePopupEnabled )
985 mEventData->mDecorator->SetPopupActive( false );
987 mEventData->mDecoratorUpdated = true;
989 else if( EventData::EDITING_WITH_POPUP == mEventData->mState )
991 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
992 if( mEventData->mCursorBlinkEnabled )
994 mEventData->mDecorator->StartCursorBlink();
996 if( mEventData->mSelectionEnabled )
998 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
999 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1003 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
1005 if( mEventData->mGrabHandlePopupEnabled )
1007 TextSelectionPopup::Buttons buttonsToShow = TextSelectionPopup::Buttons( TextSelectionPopup::SELECT | TextSelectionPopup::SELECT_ALL );
1009 if ( !IsClipboardEmpty() )
1011 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
1014 mEventData->mDecorator->SetEnabledPopupButtons( buttonsToShow );
1015 mEventData->mDecorator->SetPopupActive( true );
1017 mEventData->mDecoratorUpdated = true;
1019 else if ( EventData::SELECTION_HANDLE_PANNING == mEventData->mState )
1021 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
1022 mEventData->mDecorator->StopCursorBlink();
1023 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1024 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true );
1025 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true );
1026 if( mEventData->mGrabHandlePopupEnabled )
1028 mEventData->mDecorator->SetPopupActive( false );
1030 mEventData->mDecoratorUpdated = true;
1032 else if ( EventData::GRAB_HANDLE_PANNING == mEventData->mState )
1034 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1035 if( mEventData->mCursorBlinkEnabled )
1037 mEventData->mDecorator->StartCursorBlink();
1039 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
1040 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1041 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1042 if( mEventData->mGrabHandlePopupEnabled )
1044 mEventData->mDecorator->SetPopupActive( false );
1046 mEventData->mDecoratorUpdated = true;
1051 LineIndex Controller::Impl::GetClosestLine( float y ) const
1053 float totalHeight = 0.f;
1054 LineIndex lineIndex = 0u;
1056 const Vector<LineRun>& lines = mVisualModel->mLines;
1057 for( LineIndex endLine = lines.Count();
1058 lineIndex < endLine;
1061 const LineRun& lineRun = lines[lineIndex];
1062 totalHeight += lineRun.ascender + -lineRun.descender;
1063 if( y < totalHeight )
1069 if( lineIndex == 0 )
1077 void Controller::Impl::FindSelectionIndices( float visualX, float visualY, CharacterIndex& startIndex, CharacterIndex& endIndex )
1079 CharacterIndex hitCharacter = GetClosestCursorIndex( visualX, visualY );
1080 if( hitCharacter >= mLogicalModel->mText.Count() )
1082 // Selection out of bounds.
1086 startIndex = hitCharacter;
1087 endIndex = hitCharacter;
1089 if( !TextAbstraction::IsWhiteSpace( mLogicalModel->mText[hitCharacter] ) )
1091 // Find the start and end of the text
1092 for( startIndex = hitCharacter; startIndex > 0; --startIndex )
1094 Character charCode = mLogicalModel->mText[ startIndex-1 ];
1095 if( TextAbstraction::IsWhiteSpace( charCode ) )
1100 const CharacterIndex pastTheEnd = mLogicalModel->mText.Count();
1101 for( endIndex = hitCharacter + 1u; endIndex < pastTheEnd; ++endIndex )
1103 Character charCode = mLogicalModel->mText[ endIndex ];
1104 if( TextAbstraction::IsWhiteSpace( charCode ) )
1112 CharacterIndex Controller::Impl::GetClosestCursorIndex( float visualX,
1115 if( NULL == mEventData )
1117 // Nothing to do if there is no text input.
1121 CharacterIndex logicalIndex = 0u;
1123 const Length numberOfGlyphs = mVisualModel->mGlyphs.Count();
1124 const Length numberOfLines = mVisualModel->mLines.Count();
1125 if( 0 == numberOfGlyphs ||
1126 0 == numberOfLines )
1128 return logicalIndex;
1131 // Find which line is closest
1132 const LineIndex lineIndex = GetClosestLine( visualY );
1133 const LineRun& line = mVisualModel->mLines[lineIndex];
1135 // Get the positions of the glyphs.
1136 const Vector<Vector2>& positions = mVisualModel->mGlyphPositions;
1137 const Vector2* const positionsBuffer = positions.Begin();
1139 // Get the visual to logical conversion tables.
1140 const CharacterIndex* const visualToLogicalBuffer = ( 0u != mLogicalModel->mVisualToLogicalMap.Count() ) ? mLogicalModel->mVisualToLogicalMap.Begin() : NULL;
1141 const CharacterIndex* const visualToLogicalCursorBuffer = mLogicalModel->mVisualToLogicalCursorMap.Begin();
1143 // Get the character to glyph conversion table.
1144 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
1146 // Get the glyphs per character table.
1147 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
1149 // If the vector is void, there is no right to left characters.
1150 const bool hasRightToLeftCharacters = NULL != visualToLogicalBuffer;
1152 const CharacterIndex startCharacter = line.characterRun.characterIndex;
1153 const CharacterIndex endCharacter = line.characterRun.characterIndex + line.characterRun.numberOfCharacters;
1154 DALI_ASSERT_DEBUG( endCharacter <= mLogicalModel->mText.Count() && "Invalid line info" );
1156 // Whether there is a hit on a glyph.
1157 bool matched = false;
1159 // Traverses glyphs in visual order. To do that use the visual to logical conversion table.
1160 CharacterIndex visualIndex = startCharacter;
1161 for( ; !matched && ( visualIndex < endCharacter ); ++visualIndex )
1163 // The character in logical order.
1164 const CharacterIndex characterLogicalOrderIndex = hasRightToLeftCharacters ? *( visualToLogicalBuffer + visualIndex ) : visualIndex;
1166 // The first glyph for that character in logical order.
1167 const GlyphIndex glyphLogicalOrderIndex = *( charactersToGlyphBuffer + characterLogicalOrderIndex );
1169 // The number of glyphs for that character
1170 const Length numberOfGlyphs = *( glyphsPerCharacterBuffer + characterLogicalOrderIndex );
1172 // Get the metrics for the group of glyphs.
1173 GlyphMetrics glyphMetrics;
1174 GetGlyphsMetrics( glyphLogicalOrderIndex,
1180 const Vector2& position = *( positionsBuffer + glyphLogicalOrderIndex );
1182 // Find the mid-point of the area containing the glyph
1183 const float glyphCenter = -glyphMetrics.xBearing + position.x + 0.5f * glyphMetrics.advance;
1185 if( visualX < glyphCenter )
1192 // Return the logical position of the cursor in characters.
1196 visualIndex = endCharacter;
1199 logicalIndex = hasRightToLeftCharacters ? *( visualToLogicalCursorBuffer + visualIndex ) : visualIndex;
1200 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "%p closest visualIndex %d logicalIndex %d\n", this, visualIndex, logicalIndex );
1201 return logicalIndex;
1204 void Controller::Impl::GetCursorPosition( CharacterIndex logical,
1205 CursorInfo& cursorInfo )
1207 // TODO: Check for multiline with \n, etc...
1209 // Check if the logical position is the first or the last one of the text.
1210 const bool isFirstPosition = 0u == logical;
1211 const bool isLastPosition = mLogicalModel->mText.Count() == logical;
1213 if( isFirstPosition && isLastPosition )
1215 // There is zero characters. Get the default font.
1217 FontId defaultFontId = 0u;
1218 if( NULL == mFontDefaults )
1220 defaultFontId = mFontClient.GetFontId( EMPTY_STRING,
1225 defaultFontId = mFontDefaults->GetFontId( mFontClient );
1228 Text::FontMetrics fontMetrics;
1229 mFontClient.GetFontMetrics( defaultFontId, fontMetrics );
1231 cursorInfo.lineHeight = fontMetrics.ascender - fontMetrics.descender;
1232 cursorInfo.primaryCursorHeight = cursorInfo.lineHeight;
1234 cursorInfo.primaryPosition.x = 0.f;
1235 cursorInfo.primaryPosition.y = 0.f;
1237 // Nothing else to do.
1241 // Get the previous logical index.
1242 const CharacterIndex previousLogical = isFirstPosition ? 0u : logical - 1u;
1244 // Decrease the logical index if it's the last one.
1245 if( isLastPosition )
1250 // Get the direction of the character and the previous one.
1251 const CharacterDirection* const modelCharacterDirectionsBuffer = ( 0u != mLogicalModel->mCharacterDirections.Count() ) ? mLogicalModel->mCharacterDirections.Begin() : NULL;
1253 CharacterDirection isCurrentRightToLeft = false;
1254 CharacterDirection isPreviousRightToLeft = false;
1255 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
1257 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + logical );
1258 isPreviousRightToLeft = *( modelCharacterDirectionsBuffer + previousLogical );
1261 // Get the line where the character is laid-out.
1262 const LineRun* modelLines = mVisualModel->mLines.Begin();
1264 const LineIndex lineIndex = mVisualModel->GetLineOfCharacter( logical );
1265 const LineRun& line = *( modelLines + lineIndex );
1267 // Get the paragraph's direction.
1268 const CharacterDirection isRightToLeftParagraph = line.direction;
1270 // Check whether there is an alternative position:
1272 cursorInfo.isSecondaryCursor = ( isCurrentRightToLeft != isPreviousRightToLeft ) ||
1273 ( isLastPosition && ( isRightToLeftParagraph != isCurrentRightToLeft ) );
1275 // Set the line height.
1276 cursorInfo.lineHeight = line.ascender + -line.descender;
1278 // Convert the cursor position into the glyph position.
1279 CharacterIndex characterIndex = logical;
1280 if( cursorInfo.isSecondaryCursor &&
1281 ( isRightToLeftParagraph != isCurrentRightToLeft ) )
1283 characterIndex = previousLogical;
1286 const GlyphIndex currentGlyphIndex = *( mVisualModel->mCharactersToGlyph.Begin() + characterIndex );
1287 const Length numberOfGlyphs = *( mVisualModel->mGlyphsPerCharacter.Begin() + characterIndex );
1288 const Length numberOfCharacters = *( mVisualModel->mCharactersPerGlyph.Begin() +currentGlyphIndex );
1290 // Get the metrics for the group of glyphs.
1291 GlyphMetrics glyphMetrics;
1292 GetGlyphsMetrics( currentGlyphIndex,
1298 float interGlyphAdvance = 0.f;
1299 if( !isLastPosition &&
1300 ( numberOfCharacters > 1u ) )
1302 const CharacterIndex firstIndex = *( mVisualModel->mGlyphsToCharacters.Begin() + currentGlyphIndex );
1303 interGlyphAdvance = static_cast<float>( characterIndex - firstIndex ) * glyphMetrics.advance / static_cast<float>( numberOfCharacters );
1306 // Get the glyph position and x bearing.
1307 const Vector2& currentPosition = *( mVisualModel->mGlyphPositions.Begin() + currentGlyphIndex );
1309 // Set the cursor's height.
1310 cursorInfo.primaryCursorHeight = glyphMetrics.fontHeight;
1312 // Set the position.
1313 cursorInfo.primaryPosition.x = -glyphMetrics.xBearing + currentPosition.x + ( isCurrentRightToLeft ? glyphMetrics.advance : interGlyphAdvance );
1314 cursorInfo.primaryPosition.y = line.ascender - glyphMetrics.ascender;
1316 if( isLastPosition )
1318 // The position of the cursor after the last character needs special
1319 // care depending on its direction and the direction of the paragraph.
1321 if( cursorInfo.isSecondaryCursor )
1323 // Need to find the first character after the last character with the paragraph's direction.
1324 // i.e l0 l1 l2 r0 r1 should find r0.
1326 // TODO: check for more than one line!
1327 characterIndex = isRightToLeftParagraph ? line.characterRun.characterIndex : line.characterRun.characterIndex + line.characterRun.numberOfCharacters - 1u;
1328 characterIndex = mLogicalModel->GetLogicalCharacterIndex( characterIndex );
1330 const GlyphIndex glyphIndex = *( mVisualModel->mCharactersToGlyph.Begin() + characterIndex );
1331 const Length numberOfGlyphs = *( mVisualModel->mGlyphsPerCharacter.Begin() + characterIndex );
1333 const Vector2& position = *( mVisualModel->mGlyphPositions.Begin() + glyphIndex );
1335 // Get the metrics for the group of glyphs.
1336 GlyphMetrics glyphMetrics;
1337 GetGlyphsMetrics( glyphIndex,
1343 cursorInfo.primaryPosition.x = -glyphMetrics.xBearing + position.x + ( isRightToLeftParagraph ? 0.f : glyphMetrics.advance );
1345 cursorInfo.primaryPosition.y = line.ascender - glyphMetrics.ascender;
1349 if( !isCurrentRightToLeft )
1351 cursorInfo.primaryPosition.x += glyphMetrics.advance;
1355 cursorInfo.primaryPosition.x -= glyphMetrics.advance;
1360 // Set the alternative cursor position.
1361 if( cursorInfo.isSecondaryCursor )
1363 // Convert the cursor position into the glyph position.
1364 const CharacterIndex previousCharacterIndex = ( ( isRightToLeftParagraph != isCurrentRightToLeft ) ? logical : previousLogical );
1365 const GlyphIndex previousGlyphIndex = *( mVisualModel->mCharactersToGlyph.Begin() + previousCharacterIndex );
1366 const Length numberOfGlyphs = *( mVisualModel->mGlyphsPerCharacter.Begin() + previousCharacterIndex );
1368 // Get the glyph position.
1369 const Vector2& previousPosition = *( mVisualModel->mGlyphPositions.Begin() + previousGlyphIndex );
1371 // Get the metrics for the group of glyphs.
1372 GlyphMetrics glyphMetrics;
1373 GetGlyphsMetrics( previousGlyphIndex,
1379 // Set the cursor position and height.
1380 cursorInfo.secondaryPosition.x = -glyphMetrics.xBearing + previousPosition.x + ( ( ( isLastPosition && !isCurrentRightToLeft ) ||
1381 ( !isLastPosition && isCurrentRightToLeft ) ) ? glyphMetrics.advance : 0.f );
1383 cursorInfo.secondaryCursorHeight = 0.5f * glyphMetrics.fontHeight;
1385 cursorInfo.secondaryPosition.y = cursorInfo.lineHeight - cursorInfo.secondaryCursorHeight - line.descender - ( glyphMetrics.fontHeight - glyphMetrics.ascender );
1387 // Update the primary cursor height as well.
1388 cursorInfo.primaryCursorHeight *= 0.5f;
1392 CharacterIndex Controller::Impl::CalculateNewCursorIndex( CharacterIndex index ) const
1394 if( NULL == mEventData )
1396 // Nothing to do if there is no text input.
1400 CharacterIndex cursorIndex = mEventData->mPrimaryCursorPosition;
1402 const Script script = mLogicalModel->GetScript( index );
1403 const GlyphIndex* charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
1404 const Length* charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
1406 Length numberOfCharacters = 0u;
1407 if( TextAbstraction::LATIN == script )
1409 // Prevents to jump the whole Latin ligatures like fi, ff, ...
1410 numberOfCharacters = 1u;
1414 GlyphIndex glyphIndex = *( charactersToGlyphBuffer + index );
1415 numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
1417 while( 0u == numberOfCharacters )
1419 numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
1424 if( index < mEventData->mPrimaryCursorPosition )
1426 cursorIndex -= numberOfCharacters;
1430 cursorIndex += numberOfCharacters;
1436 void Controller::Impl::UpdateCursorPosition()
1438 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "-->Controller::UpdateCursorPosition %p\n", this );
1439 if( NULL == mEventData )
1441 // Nothing to do if there is no text input.
1442 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition no event data\n" );
1446 if( IsShowingPlaceholderText() )
1448 // Do not want to use the place-holder text to set the cursor position.
1450 // Use the line's height of the font's family set to set the cursor's size.
1451 // If there is no font's family set, use the default font.
1452 // Use the current alignment to place the cursor at the beginning, center or end of the box.
1454 float lineHeight = 0.f;
1456 FontId defaultFontId = 0u;
1457 if( NULL == mFontDefaults )
1459 defaultFontId = mFontClient.GetFontId( EMPTY_STRING,
1464 defaultFontId = mFontDefaults->GetFontId( mFontClient );
1467 Text::FontMetrics fontMetrics;
1468 mFontClient.GetFontMetrics( defaultFontId, fontMetrics );
1470 lineHeight = fontMetrics.ascender - fontMetrics.descender;
1473 Vector2 cursorPosition;
1475 switch( mLayoutEngine.GetHorizontalAlignment() )
1477 case LayoutEngine::HORIZONTAL_ALIGN_BEGIN:
1479 cursorPosition.x = 1.f;
1482 case LayoutEngine::HORIZONTAL_ALIGN_CENTER:
1484 cursorPosition.x = floor( 0.5f * mControlSize.width );
1487 case LayoutEngine::HORIZONTAL_ALIGN_END:
1489 cursorPosition.x = mControlSize.width;
1494 switch( mLayoutEngine.GetVerticalAlignment() )
1496 case LayoutEngine::VERTICAL_ALIGN_TOP:
1498 cursorPosition.y = 0.f;
1501 case LayoutEngine::VERTICAL_ALIGN_CENTER:
1503 cursorPosition.y = floorf( 0.5f * ( mControlSize.height - lineHeight ) );
1506 case LayoutEngine::VERTICAL_ALIGN_BOTTOM:
1508 cursorPosition.y = mControlSize.height - lineHeight;
1513 mEventData->mDecorator->SetPosition( PRIMARY_CURSOR,
1521 CursorInfo cursorInfo;
1522 GetCursorPosition( mEventData->mPrimaryCursorPosition,
1525 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
1526 const Vector2 cursorPosition = cursorInfo.primaryPosition + offset;
1528 // Sets the cursor position.
1529 mEventData->mDecorator->SetPosition( PRIMARY_CURSOR,
1532 cursorInfo.primaryCursorHeight,
1533 cursorInfo.lineHeight );
1534 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Primary cursor position: %f,%f\n", cursorPosition.x, cursorPosition.y );
1536 // Sets the grab handle position.
1537 mEventData->mDecorator->SetPosition( GRAB_HANDLE,
1540 cursorInfo.lineHeight );
1542 if( cursorInfo.isSecondaryCursor )
1544 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_BOTH );
1545 mEventData->mDecorator->SetPosition( SECONDARY_CURSOR,
1546 cursorInfo.secondaryPosition.x + offset.x,
1547 cursorInfo.secondaryPosition.y + offset.y,
1548 cursorInfo.secondaryCursorHeight,
1549 cursorInfo.lineHeight );
1550 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Secondary cursor position: %f,%f\n", cursorInfo.secondaryPosition.x + offset.x, cursorInfo.secondaryPosition.y + offset.y );
1554 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1557 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition\n" );
1560 void Controller::Impl::UpdateSelectionHandle( HandleType handleType )
1562 if( ( LEFT_SELECTION_HANDLE != handleType ) &&
1563 ( RIGHT_SELECTION_HANDLE != handleType ) )
1568 const bool leftSelectionHandle = LEFT_SELECTION_HANDLE == handleType;
1569 const CharacterIndex index = leftSelectionHandle ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition;
1571 CursorInfo cursorInfo;
1572 GetCursorPosition( index,
1575 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
1576 const Vector2 cursorPosition = cursorInfo.primaryPosition + offset;
1578 // Sets the grab handle position.
1579 mEventData->mDecorator->SetPosition( handleType,
1582 cursorInfo.lineHeight );
1585 void Controller::Impl::ClampHorizontalScroll( const Vector2& actualSize )
1587 // Clamp between -space & 0 (and the text alignment).
1588 if( actualSize.width > mControlSize.width )
1590 const float space = ( actualSize.width - mControlSize.width ) + mAlignmentOffset.x;
1591 mEventData->mScrollPosition.x = ( mEventData->mScrollPosition.x < -space ) ? -space : mEventData->mScrollPosition.x;
1592 mEventData->mScrollPosition.x = ( mEventData->mScrollPosition.x > -mAlignmentOffset.x ) ? -mAlignmentOffset.x : mEventData->mScrollPosition.x;
1594 mEventData->mDecoratorUpdated = true;
1598 mEventData->mScrollPosition.x = 0.f;
1602 void Controller::Impl::ClampVerticalScroll( const Vector2& actualSize )
1604 // Clamp between -space & 0 (and the text alignment).
1605 if( actualSize.height > mControlSize.height )
1607 const float space = ( actualSize.height - mControlSize.height ) + mAlignmentOffset.y;
1608 mEventData->mScrollPosition.y = ( mEventData->mScrollPosition.y < -space ) ? -space : mEventData->mScrollPosition.y;
1609 mEventData->mScrollPosition.y = ( mEventData->mScrollPosition.y > -mAlignmentOffset.y ) ? -mAlignmentOffset.y : mEventData->mScrollPosition.y;
1611 mEventData->mDecoratorUpdated = true;
1615 mEventData->mScrollPosition.y = 0.f;
1619 void Controller::Impl::ScrollToMakePositionVisible( const Vector2& position )
1622 bool updateDecorator = false;
1623 if( position.x < 0.f )
1625 offset.x = -position.x;
1626 mEventData->mScrollPosition.x += offset.x;
1627 updateDecorator = true;
1629 else if( position.x > mControlSize.width )
1631 offset.x = mControlSize.width - position.x;
1632 mEventData->mScrollPosition.x += offset.x;
1633 updateDecorator = true;
1636 if( updateDecorator && mEventData->mDecorator )
1638 mEventData->mDecorator->UpdatePositions( offset );
1641 // TODO : calculate the vertical scroll.
1644 void Controller::Impl::ScrollTextToMatchCursor()
1646 // Get the current cursor position in decorator coords.
1647 const Vector2& currentCursorPosition = mEventData->mDecorator->GetPosition( PRIMARY_CURSOR );
1649 // Calculate the new cursor position.
1650 CursorInfo cursorInfo;
1651 GetCursorPosition( mEventData->mPrimaryCursorPosition,
1654 // Calculate the offset to match the cursor position before the character was deleted.
1655 mEventData->mScrollPosition.x = currentCursorPosition.x - cursorInfo.primaryPosition.x - mAlignmentOffset.x;
1657 ClampHorizontalScroll( mVisualModel->GetActualSize() );
1658 bool updateCursorPosition = true;
1660 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
1661 const Vector2 cursorPosition = cursorInfo.primaryPosition + offset;
1663 if( updateCursorPosition )
1665 // Sets the cursor position.
1666 mEventData->mDecorator->SetPosition( PRIMARY_CURSOR,
1669 cursorInfo.primaryCursorHeight,
1670 cursorInfo.lineHeight );
1672 // Sets the grab handle position.
1673 mEventData->mDecorator->SetPosition( GRAB_HANDLE,
1676 cursorInfo.lineHeight );
1678 if( cursorInfo.isSecondaryCursor )
1680 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_BOTH );
1681 mEventData->mDecorator->SetPosition( SECONDARY_CURSOR,
1682 cursorInfo.secondaryPosition.x + offset.x,
1683 cursorInfo.secondaryPosition.y + offset.y,
1684 cursorInfo.secondaryCursorHeight,
1685 cursorInfo.lineHeight );
1686 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Secondary cursor position: %f,%f\n", cursorInfo.secondaryPosition.x + offset.x, cursorInfo.secondaryPosition.y + offset.y );
1690 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1695 void Controller::Impl::RequestRelayout()
1697 mControlInterface.RequestTextRelayout();
1702 } // namespace Toolkit