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::PasteTextFromClipboard()
807 void Controller::Impl::RepositionSelectionHandles( CharacterIndex selectionStart, CharacterIndex selectionEnd )
809 if( selectionStart == selectionEnd )
811 // Nothing to select if handles are in the same place.
815 mEventData->mDecorator->ClearHighlights();
817 mEventData->mLeftSelectionPosition = selectionStart;
818 mEventData->mRightSelectionPosition = selectionEnd;
820 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
821 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
822 const GlyphInfo* const glyphsBuffer = mVisualModel->mGlyphs.Begin();
823 const Vector2* const positionsBuffer = mVisualModel->mGlyphPositions.Begin();
825 // TODO: Better algorithm to create the highlight box.
828 const Vector<LineRun>& lines = mVisualModel->mLines;
829 const LineRun& firstLine = *lines.Begin();
830 const float height = firstLine.ascender + -firstLine.descender;
832 const bool indicesSwapped = ( selectionStart > selectionEnd );
835 std::swap( selectionStart, selectionEnd );
838 GlyphIndex glyphStart = *( charactersToGlyphBuffer + selectionStart );
839 GlyphIndex glyphEnd = *( charactersToGlyphBuffer + ( selectionEnd - 1u ) ) + *( glyphsPerCharacterBuffer + ( selectionEnd - 1u ) ) - 1u;
841 mEventData->mDecorator->SwapSelectionHandlesEnabled( firstLine.direction != indicesSwapped );
843 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
845 for( GlyphIndex index = glyphStart; index <= glyphEnd; ++index )
847 const GlyphInfo& glyph = *( glyphsBuffer + index );
848 const Vector2& position = *( positionsBuffer + index );
850 const float xPosition = position.x - glyph.xBearing + offset.x;
851 mEventData->mDecorator->AddHighlight( xPosition, offset.y, xPosition + glyph.advance, height );
854 CursorInfo primaryCursorInfo;
855 GetCursorPosition( mEventData->mLeftSelectionPosition,
858 CursorInfo secondaryCursorInfo;
859 GetCursorPosition( mEventData->mRightSelectionPosition,
860 secondaryCursorInfo );
862 const Vector2 primaryPosition = primaryCursorInfo.primaryPosition + offset;
863 const Vector2 secondaryPosition = secondaryCursorInfo.primaryPosition + offset;
865 mEventData->mDecorator->SetPosition( LEFT_SELECTION_HANDLE, primaryPosition.x, primaryPosition.y, primaryCursorInfo.lineHeight );
867 mEventData->mDecorator->SetPosition( RIGHT_SELECTION_HANDLE, secondaryPosition.x, secondaryPosition.y, secondaryCursorInfo.lineHeight );
869 // Set the flag to update the decorator.
870 mEventData->mDecoratorUpdated = true;
873 void Controller::Impl::RepositionSelectionHandles( float visualX, float visualY )
875 if( NULL == mEventData )
877 // Nothing to do if there is no text input.
881 if( IsShowingPlaceholderText() )
883 // Nothing to do if there is the place-holder text.
887 const Length numberOfGlyphs = mVisualModel->mGlyphs.Count();
888 const Length numberOfLines = mVisualModel->mLines.Count();
889 if( 0 == numberOfGlyphs ||
892 // Nothing to do if there is no text.
896 // Find which word was selected
897 CharacterIndex selectionStart( 0 );
898 CharacterIndex selectionEnd( 0 );
899 FindSelectionIndices( visualX, visualY, selectionStart, selectionEnd );
900 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "%p selectionStart %d selectionEnd %d\n", this, selectionStart, selectionEnd );
902 if( selectionStart == selectionEnd )
904 ChangeState( EventData::EDITING );
905 // Nothing to select. i.e. a white space, out of bounds
909 RepositionSelectionHandles( selectionStart, selectionEnd );
912 void Controller::Impl::ChangeState( EventData::State newState )
914 if( NULL == mEventData )
916 // Nothing to do if there is no text input.
920 if( mEventData->mState != newState )
922 mEventData->mState = newState;
924 if( EventData::INACTIVE == mEventData->mState )
926 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
927 mEventData->mDecorator->StopCursorBlink();
928 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
929 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
930 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
931 mEventData->mDecorator->SetPopupActive( false );
932 mEventData->mDecoratorUpdated = true;
934 else if ( EventData::SELECTING == mEventData->mState )
936 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
937 mEventData->mDecorator->StopCursorBlink();
938 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
939 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true );
940 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true );
941 if( mEventData->mGrabHandlePopupEnabled )
943 TextSelectionPopup::Buttons buttonsToShow = TextSelectionPopup::Buttons( TextSelectionPopup::CUT | TextSelectionPopup::COPY );
944 if ( !IsClipboardEmpty() )
946 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
949 mEventData->mDecorator->SetEnabledPopupButtons( buttonsToShow );
950 mEventData->mDecorator->SetPopupActive( true );
952 mEventData->mDecoratorUpdated = true;
954 else if ( EventData::SELECTION_CHANGED == mEventData->mState )
956 if( mEventData->mGrabHandlePopupEnabled )
958 TextSelectionPopup::Buttons buttonsToShow = TextSelectionPopup::Buttons( TextSelectionPopup::CUT | TextSelectionPopup::COPY );
959 if ( !IsClipboardEmpty() )
961 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
963 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
964 mEventData->mDecorator->SetEnabledPopupButtons( buttonsToShow );
965 mEventData->mDecorator->SetPopupActive( true );
967 mEventData->mDecoratorUpdated = true;
969 else if( EventData::EDITING == mEventData->mState )
971 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
972 if( mEventData->mCursorBlinkEnabled )
974 mEventData->mDecorator->StartCursorBlink();
976 // Grab handle is not shown until a tap is received whilst EDITING
977 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
978 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
979 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
980 if( mEventData->mGrabHandlePopupEnabled )
982 mEventData->mDecorator->SetPopupActive( false );
984 mEventData->mDecoratorUpdated = true;
986 else if( EventData::EDITING_WITH_POPUP == mEventData->mState )
988 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
989 if( mEventData->mCursorBlinkEnabled )
991 mEventData->mDecorator->StartCursorBlink();
993 if( mEventData->mSelectionEnabled )
995 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
996 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1000 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
1002 if( mEventData->mGrabHandlePopupEnabled )
1004 TextSelectionPopup::Buttons buttonsToShow = TextSelectionPopup::Buttons( TextSelectionPopup::SELECT | TextSelectionPopup::SELECT_ALL );
1006 if ( !IsClipboardEmpty() )
1008 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
1011 mEventData->mDecorator->SetEnabledPopupButtons( buttonsToShow );
1012 mEventData->mDecorator->SetPopupActive( true );
1014 mEventData->mDecoratorUpdated = true;
1016 else if ( EventData::SELECTION_HANDLE_PANNING == mEventData->mState )
1018 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
1019 mEventData->mDecorator->StopCursorBlink();
1020 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1021 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true );
1022 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true );
1023 if( mEventData->mGrabHandlePopupEnabled )
1025 mEventData->mDecorator->SetPopupActive( false );
1027 mEventData->mDecoratorUpdated = true;
1029 else if ( EventData::GRAB_HANDLE_PANNING == mEventData->mState )
1031 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1032 if( mEventData->mCursorBlinkEnabled )
1034 mEventData->mDecorator->StartCursorBlink();
1036 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
1037 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1038 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1039 if( mEventData->mGrabHandlePopupEnabled )
1041 mEventData->mDecorator->SetPopupActive( false );
1043 mEventData->mDecoratorUpdated = true;
1048 LineIndex Controller::Impl::GetClosestLine( float y ) const
1050 float totalHeight = 0.f;
1051 LineIndex lineIndex = 0u;
1053 const Vector<LineRun>& lines = mVisualModel->mLines;
1054 for( LineIndex endLine = lines.Count();
1055 lineIndex < endLine;
1058 const LineRun& lineRun = lines[lineIndex];
1059 totalHeight += lineRun.ascender + -lineRun.descender;
1060 if( y < totalHeight )
1066 if( lineIndex == 0 )
1074 void Controller::Impl::FindSelectionIndices( float visualX, float visualY, CharacterIndex& startIndex, CharacterIndex& endIndex )
1076 CharacterIndex hitCharacter = GetClosestCursorIndex( visualX, visualY );
1077 if( hitCharacter >= mLogicalModel->mText.Count() )
1079 // Selection out of bounds.
1083 startIndex = hitCharacter;
1084 endIndex = hitCharacter;
1086 if( !TextAbstraction::IsWhiteSpace( mLogicalModel->mText[hitCharacter] ) )
1088 // Find the start and end of the text
1089 for( startIndex = hitCharacter; startIndex > 0; --startIndex )
1091 Character charCode = mLogicalModel->mText[ startIndex-1 ];
1092 if( TextAbstraction::IsWhiteSpace( charCode ) )
1097 const CharacterIndex pastTheEnd = mLogicalModel->mText.Count();
1098 for( endIndex = hitCharacter + 1u; endIndex < pastTheEnd; ++endIndex )
1100 Character charCode = mLogicalModel->mText[ endIndex ];
1101 if( TextAbstraction::IsWhiteSpace( charCode ) )
1109 CharacterIndex Controller::Impl::GetClosestCursorIndex( float visualX,
1112 if( NULL == mEventData )
1114 // Nothing to do if there is no text input.
1118 CharacterIndex logicalIndex = 0u;
1120 const Length numberOfGlyphs = mVisualModel->mGlyphs.Count();
1121 const Length numberOfLines = mVisualModel->mLines.Count();
1122 if( 0 == numberOfGlyphs ||
1123 0 == numberOfLines )
1125 return logicalIndex;
1128 // Find which line is closest
1129 const LineIndex lineIndex = GetClosestLine( visualY );
1130 const LineRun& line = mVisualModel->mLines[lineIndex];
1132 // Get the positions of the glyphs.
1133 const Vector<Vector2>& positions = mVisualModel->mGlyphPositions;
1134 const Vector2* const positionsBuffer = positions.Begin();
1136 // Get the visual to logical conversion tables.
1137 const CharacterIndex* const visualToLogicalBuffer = ( 0u != mLogicalModel->mVisualToLogicalMap.Count() ) ? mLogicalModel->mVisualToLogicalMap.Begin() : NULL;
1138 const CharacterIndex* const visualToLogicalCursorBuffer = mLogicalModel->mVisualToLogicalCursorMap.Begin();
1140 // Get the character to glyph conversion table.
1141 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
1143 // Get the glyphs per character table.
1144 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
1146 // If the vector is void, there is no right to left characters.
1147 const bool hasRightToLeftCharacters = NULL != visualToLogicalBuffer;
1149 const CharacterIndex startCharacter = line.characterRun.characterIndex;
1150 const CharacterIndex endCharacter = line.characterRun.characterIndex + line.characterRun.numberOfCharacters;
1151 DALI_ASSERT_DEBUG( endCharacter <= mLogicalModel->mText.Count() && "Invalid line info" );
1153 // Whether there is a hit on a glyph.
1154 bool matched = false;
1156 // Traverses glyphs in visual order. To do that use the visual to logical conversion table.
1157 CharacterIndex visualIndex = startCharacter;
1158 for( ; !matched && ( visualIndex < endCharacter ); ++visualIndex )
1160 // The character in logical order.
1161 const CharacterIndex characterLogicalOrderIndex = hasRightToLeftCharacters ? *( visualToLogicalBuffer + visualIndex ) : visualIndex;
1163 // The first glyph for that character in logical order.
1164 const GlyphIndex glyphLogicalOrderIndex = *( charactersToGlyphBuffer + characterLogicalOrderIndex );
1166 // The number of glyphs for that character
1167 const Length numberOfGlyphs = *( glyphsPerCharacterBuffer + characterLogicalOrderIndex );
1169 // Get the metrics for the group of glyphs.
1170 GlyphMetrics glyphMetrics;
1171 GetGlyphsMetrics( glyphLogicalOrderIndex,
1177 const Vector2& position = *( positionsBuffer + glyphLogicalOrderIndex );
1179 // Find the mid-point of the area containing the glyph
1180 const float glyphCenter = -glyphMetrics.xBearing + position.x + 0.5f * glyphMetrics.advance;
1182 if( visualX < glyphCenter )
1189 // Return the logical position of the cursor in characters.
1193 visualIndex = endCharacter;
1196 logicalIndex = hasRightToLeftCharacters ? *( visualToLogicalCursorBuffer + visualIndex ) : visualIndex;
1197 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "%p closest visualIndex %d logicalIndex %d\n", this, visualIndex, logicalIndex );
1198 return logicalIndex;
1201 void Controller::Impl::GetCursorPosition( CharacterIndex logical,
1202 CursorInfo& cursorInfo )
1204 // TODO: Check for multiline with \n, etc...
1206 // Check if the logical position is the first or the last one of the text.
1207 const bool isFirstPosition = 0u == logical;
1208 const bool isLastPosition = mLogicalModel->mText.Count() == logical;
1210 if( isFirstPosition && isLastPosition )
1212 // There is zero characters. Get the default font.
1214 FontId defaultFontId = 0u;
1215 if( NULL == mFontDefaults )
1217 defaultFontId = mFontClient.GetFontId( EMPTY_STRING,
1222 defaultFontId = mFontDefaults->GetFontId( mFontClient );
1225 Text::FontMetrics fontMetrics;
1226 mFontClient.GetFontMetrics( defaultFontId, fontMetrics );
1228 cursorInfo.lineHeight = fontMetrics.ascender - fontMetrics.descender;
1229 cursorInfo.primaryCursorHeight = cursorInfo.lineHeight;
1231 cursorInfo.primaryPosition.x = 0.f;
1232 cursorInfo.primaryPosition.y = 0.f;
1234 // Nothing else to do.
1238 // Get the previous logical index.
1239 const CharacterIndex previousLogical = isFirstPosition ? 0u : logical - 1u;
1241 // Decrease the logical index if it's the last one.
1242 if( isLastPosition )
1247 // Get the direction of the character and the previous one.
1248 const CharacterDirection* const modelCharacterDirectionsBuffer = ( 0u != mLogicalModel->mCharacterDirections.Count() ) ? mLogicalModel->mCharacterDirections.Begin() : NULL;
1250 CharacterDirection isCurrentRightToLeft = false;
1251 CharacterDirection isPreviousRightToLeft = false;
1252 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
1254 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + logical );
1255 isPreviousRightToLeft = *( modelCharacterDirectionsBuffer + previousLogical );
1258 // Get the line where the character is laid-out.
1259 const LineRun* modelLines = mVisualModel->mLines.Begin();
1261 const LineIndex lineIndex = mVisualModel->GetLineOfCharacter( logical );
1262 const LineRun& line = *( modelLines + lineIndex );
1264 // Get the paragraph's direction.
1265 const CharacterDirection isRightToLeftParagraph = line.direction;
1267 // Check whether there is an alternative position:
1269 cursorInfo.isSecondaryCursor = ( isCurrentRightToLeft != isPreviousRightToLeft ) ||
1270 ( isLastPosition && ( isRightToLeftParagraph != isCurrentRightToLeft ) );
1272 // Set the line height.
1273 cursorInfo.lineHeight = line.ascender + -line.descender;
1275 // Convert the cursor position into the glyph position.
1276 CharacterIndex characterIndex = logical;
1277 if( cursorInfo.isSecondaryCursor &&
1278 ( isRightToLeftParagraph != isCurrentRightToLeft ) )
1280 characterIndex = previousLogical;
1283 const GlyphIndex currentGlyphIndex = *( mVisualModel->mCharactersToGlyph.Begin() + characterIndex );
1284 const Length numberOfGlyphs = *( mVisualModel->mGlyphsPerCharacter.Begin() + characterIndex );
1285 const Length numberOfCharacters = *( mVisualModel->mCharactersPerGlyph.Begin() +currentGlyphIndex );
1287 // Get the metrics for the group of glyphs.
1288 GlyphMetrics glyphMetrics;
1289 GetGlyphsMetrics( currentGlyphIndex,
1295 float interGlyphAdvance = 0.f;
1296 if( !isLastPosition &&
1297 ( numberOfCharacters > 1u ) )
1299 const CharacterIndex firstIndex = *( mVisualModel->mGlyphsToCharacters.Begin() + currentGlyphIndex );
1300 interGlyphAdvance = static_cast<float>( characterIndex - firstIndex ) * glyphMetrics.advance / static_cast<float>( numberOfCharacters );
1303 // Get the glyph position and x bearing.
1304 const Vector2& currentPosition = *( mVisualModel->mGlyphPositions.Begin() + currentGlyphIndex );
1306 // Set the cursor's height.
1307 cursorInfo.primaryCursorHeight = glyphMetrics.fontHeight;
1309 // Set the position.
1310 cursorInfo.primaryPosition.x = -glyphMetrics.xBearing + currentPosition.x + ( isCurrentRightToLeft ? glyphMetrics.advance : interGlyphAdvance );
1311 cursorInfo.primaryPosition.y = line.ascender - glyphMetrics.ascender;
1313 if( isLastPosition )
1315 // The position of the cursor after the last character needs special
1316 // care depending on its direction and the direction of the paragraph.
1318 if( cursorInfo.isSecondaryCursor )
1320 // Need to find the first character after the last character with the paragraph's direction.
1321 // i.e l0 l1 l2 r0 r1 should find r0.
1323 // TODO: check for more than one line!
1324 characterIndex = isRightToLeftParagraph ? line.characterRun.characterIndex : line.characterRun.characterIndex + line.characterRun.numberOfCharacters - 1u;
1325 characterIndex = mLogicalModel->GetLogicalCharacterIndex( characterIndex );
1327 const GlyphIndex glyphIndex = *( mVisualModel->mCharactersToGlyph.Begin() + characterIndex );
1328 const Length numberOfGlyphs = *( mVisualModel->mGlyphsPerCharacter.Begin() + characterIndex );
1330 const Vector2& position = *( mVisualModel->mGlyphPositions.Begin() + glyphIndex );
1332 // Get the metrics for the group of glyphs.
1333 GlyphMetrics glyphMetrics;
1334 GetGlyphsMetrics( glyphIndex,
1340 cursorInfo.primaryPosition.x = -glyphMetrics.xBearing + position.x + ( isRightToLeftParagraph ? 0.f : glyphMetrics.advance );
1342 cursorInfo.primaryPosition.y = line.ascender - glyphMetrics.ascender;
1346 if( !isCurrentRightToLeft )
1348 cursorInfo.primaryPosition.x += glyphMetrics.advance;
1352 cursorInfo.primaryPosition.x -= glyphMetrics.advance;
1357 // Set the alternative cursor position.
1358 if( cursorInfo.isSecondaryCursor )
1360 // Convert the cursor position into the glyph position.
1361 const CharacterIndex previousCharacterIndex = ( ( isRightToLeftParagraph != isCurrentRightToLeft ) ? logical : previousLogical );
1362 const GlyphIndex previousGlyphIndex = *( mVisualModel->mCharactersToGlyph.Begin() + previousCharacterIndex );
1363 const Length numberOfGlyphs = *( mVisualModel->mGlyphsPerCharacter.Begin() + previousCharacterIndex );
1365 // Get the glyph position.
1366 const Vector2& previousPosition = *( mVisualModel->mGlyphPositions.Begin() + previousGlyphIndex );
1368 // Get the metrics for the group of glyphs.
1369 GlyphMetrics glyphMetrics;
1370 GetGlyphsMetrics( previousGlyphIndex,
1376 // Set the cursor position and height.
1377 cursorInfo.secondaryPosition.x = -glyphMetrics.xBearing + previousPosition.x + ( ( ( isLastPosition && !isCurrentRightToLeft ) ||
1378 ( !isLastPosition && isCurrentRightToLeft ) ) ? glyphMetrics.advance : 0.f );
1380 cursorInfo.secondaryCursorHeight = 0.5f * glyphMetrics.fontHeight;
1382 cursorInfo.secondaryPosition.y = cursorInfo.lineHeight - cursorInfo.secondaryCursorHeight - line.descender - ( glyphMetrics.fontHeight - glyphMetrics.ascender );
1384 // Update the primary cursor height as well.
1385 cursorInfo.primaryCursorHeight *= 0.5f;
1389 CharacterIndex Controller::Impl::CalculateNewCursorIndex( CharacterIndex index ) const
1391 if( NULL == mEventData )
1393 // Nothing to do if there is no text input.
1397 CharacterIndex cursorIndex = mEventData->mPrimaryCursorPosition;
1399 const Script script = mLogicalModel->GetScript( index );
1400 const GlyphIndex* charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
1401 const Length* charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
1403 Length numberOfCharacters = 0u;
1404 if( TextAbstraction::LATIN == script )
1406 // Prevents to jump the whole Latin ligatures like fi, ff, ...
1407 numberOfCharacters = 1u;
1411 GlyphIndex glyphIndex = *( charactersToGlyphBuffer + index );
1412 numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
1414 while( 0u == numberOfCharacters )
1416 numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
1421 if( index < mEventData->mPrimaryCursorPosition )
1423 cursorIndex -= numberOfCharacters;
1427 cursorIndex += numberOfCharacters;
1433 void Controller::Impl::UpdateCursorPosition()
1435 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "-->Controller::UpdateCursorPosition %p\n", this );
1436 if( NULL == mEventData )
1438 // Nothing to do if there is no text input.
1439 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition no event data\n" );
1443 if( IsShowingPlaceholderText() )
1445 // Do not want to use the place-holder text to set the cursor position.
1447 // Use the line's height of the font's family set to set the cursor's size.
1448 // If there is no font's family set, use the default font.
1449 // Use the current alignment to place the cursor at the beginning, center or end of the box.
1451 float lineHeight = 0.f;
1453 FontId defaultFontId = 0u;
1454 if( NULL == mFontDefaults )
1456 defaultFontId = mFontClient.GetFontId( EMPTY_STRING,
1461 defaultFontId = mFontDefaults->GetFontId( mFontClient );
1464 Text::FontMetrics fontMetrics;
1465 mFontClient.GetFontMetrics( defaultFontId, fontMetrics );
1467 lineHeight = fontMetrics.ascender - fontMetrics.descender;
1470 Vector2 cursorPosition;
1472 switch( mLayoutEngine.GetHorizontalAlignment() )
1474 case LayoutEngine::HORIZONTAL_ALIGN_BEGIN:
1476 cursorPosition.x = 1.f;
1479 case LayoutEngine::HORIZONTAL_ALIGN_CENTER:
1481 cursorPosition.x = floor( 0.5f * mControlSize.width );
1484 case LayoutEngine::HORIZONTAL_ALIGN_END:
1486 cursorPosition.x = mControlSize.width;
1491 switch( mLayoutEngine.GetVerticalAlignment() )
1493 case LayoutEngine::VERTICAL_ALIGN_TOP:
1495 cursorPosition.y = 0.f;
1498 case LayoutEngine::VERTICAL_ALIGN_CENTER:
1500 cursorPosition.y = floorf( 0.5f * ( mControlSize.height - lineHeight ) );
1503 case LayoutEngine::VERTICAL_ALIGN_BOTTOM:
1505 cursorPosition.y = mControlSize.height - lineHeight;
1510 mEventData->mDecorator->SetPosition( PRIMARY_CURSOR,
1518 CursorInfo cursorInfo;
1519 GetCursorPosition( mEventData->mPrimaryCursorPosition,
1522 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
1523 const Vector2 cursorPosition = cursorInfo.primaryPosition + offset;
1525 // Sets the cursor position.
1526 mEventData->mDecorator->SetPosition( PRIMARY_CURSOR,
1529 cursorInfo.primaryCursorHeight,
1530 cursorInfo.lineHeight );
1531 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Primary cursor position: %f,%f\n", cursorPosition.x, cursorPosition.y );
1533 // Sets the grab handle position.
1534 mEventData->mDecorator->SetPosition( GRAB_HANDLE,
1537 cursorInfo.lineHeight );
1539 if( cursorInfo.isSecondaryCursor )
1541 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_BOTH );
1542 mEventData->mDecorator->SetPosition( SECONDARY_CURSOR,
1543 cursorInfo.secondaryPosition.x + offset.x,
1544 cursorInfo.secondaryPosition.y + offset.y,
1545 cursorInfo.secondaryCursorHeight,
1546 cursorInfo.lineHeight );
1547 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Secondary cursor position: %f,%f\n", cursorInfo.secondaryPosition.x + offset.x, cursorInfo.secondaryPosition.y + offset.y );
1551 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1554 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition\n" );
1557 void Controller::Impl::UpdateSelectionHandle( HandleType handleType )
1559 if( ( LEFT_SELECTION_HANDLE != handleType ) &&
1560 ( RIGHT_SELECTION_HANDLE != handleType ) )
1565 const bool leftSelectionHandle = LEFT_SELECTION_HANDLE == handleType;
1566 const CharacterIndex index = leftSelectionHandle ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition;
1568 CursorInfo cursorInfo;
1569 GetCursorPosition( index,
1572 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
1573 const Vector2 cursorPosition = cursorInfo.primaryPosition + offset;
1575 // Sets the grab handle position.
1576 mEventData->mDecorator->SetPosition( handleType,
1579 cursorInfo.lineHeight );
1582 void Controller::Impl::ClampHorizontalScroll( const Vector2& actualSize )
1584 // Clamp between -space & 0 (and the text alignment).
1585 if( actualSize.width > mControlSize.width )
1587 const float space = ( actualSize.width - mControlSize.width ) + mAlignmentOffset.x;
1588 mEventData->mScrollPosition.x = ( mEventData->mScrollPosition.x < -space ) ? -space : mEventData->mScrollPosition.x;
1589 mEventData->mScrollPosition.x = ( mEventData->mScrollPosition.x > -mAlignmentOffset.x ) ? -mAlignmentOffset.x : mEventData->mScrollPosition.x;
1591 mEventData->mDecoratorUpdated = true;
1595 mEventData->mScrollPosition.x = 0.f;
1599 void Controller::Impl::ClampVerticalScroll( const Vector2& actualSize )
1601 // Clamp between -space & 0 (and the text alignment).
1602 if( actualSize.height > mControlSize.height )
1604 const float space = ( actualSize.height - mControlSize.height ) + mAlignmentOffset.y;
1605 mEventData->mScrollPosition.y = ( mEventData->mScrollPosition.y < -space ) ? -space : mEventData->mScrollPosition.y;
1606 mEventData->mScrollPosition.y = ( mEventData->mScrollPosition.y > -mAlignmentOffset.y ) ? -mAlignmentOffset.y : mEventData->mScrollPosition.y;
1608 mEventData->mDecoratorUpdated = true;
1612 mEventData->mScrollPosition.y = 0.f;
1616 void Controller::Impl::ScrollToMakePositionVisible( const Vector2& position )
1619 bool updateDecorator = false;
1620 if( position.x < 0.f )
1622 offset.x = -position.x;
1623 mEventData->mScrollPosition.x += offset.x;
1624 updateDecorator = true;
1626 else if( position.x > mControlSize.width )
1628 offset.x = mControlSize.width - position.x;
1629 mEventData->mScrollPosition.x += offset.x;
1630 updateDecorator = true;
1633 if( updateDecorator && mEventData->mDecorator )
1635 mEventData->mDecorator->UpdatePositions( offset );
1638 // TODO : calculate the vertical scroll.
1641 void Controller::Impl::ScrollTextToMatchCursor()
1643 // Get the current cursor position in decorator coords.
1644 const Vector2& currentCursorPosition = mEventData->mDecorator->GetPosition( PRIMARY_CURSOR );
1646 // Calculate the new cursor position.
1647 CursorInfo cursorInfo;
1648 GetCursorPosition( mEventData->mPrimaryCursorPosition,
1651 // Calculate the offset to match the cursor position before the character was deleted.
1652 mEventData->mScrollPosition.x = currentCursorPosition.x - cursorInfo.primaryPosition.x - mAlignmentOffset.x;
1654 ClampHorizontalScroll( mVisualModel->GetActualSize() );
1655 bool updateCursorPosition = true;
1657 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
1658 const Vector2 cursorPosition = cursorInfo.primaryPosition + offset;
1660 if( updateCursorPosition )
1662 // Sets the cursor position.
1663 mEventData->mDecorator->SetPosition( PRIMARY_CURSOR,
1666 cursorInfo.primaryCursorHeight,
1667 cursorInfo.lineHeight );
1669 // Sets the grab handle position.
1670 mEventData->mDecorator->SetPosition( GRAB_HANDLE,
1673 cursorInfo.lineHeight );
1675 if( cursorInfo.isSecondaryCursor )
1677 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_BOTH );
1678 mEventData->mDecorator->SetPosition( SECONDARY_CURSOR,
1679 cursorInfo.secondaryPosition.x + offset.x,
1680 cursorInfo.secondaryPosition.y + offset.y,
1681 cursorInfo.secondaryCursorHeight,
1682 cursorInfo.lineHeight );
1683 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Secondary cursor position: %f,%f\n", cursorInfo.secondaryPosition.x + offset.x, cursorInfo.secondaryPosition.y + offset.y );
1687 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1692 void Controller::Impl::RequestRelayout()
1694 mControlInterface.RequestTextRelayout();
1699 } // namespace Toolkit