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 ) &&
573 ( handleNewPosition != mEventData->mRightSelectionPosition ) )
575 mEventData->mLeftSelectionPosition = handleNewPosition;
577 RepositionSelectionHandles( mEventData->mLeftSelectionPosition,
578 mEventData->mRightSelectionPosition );
580 mEventData->mUpdateLeftSelectionPosition = true;
583 else if( Event::RIGHT_SELECTION_HANDLE_EVENT == event.type )
585 ChangeState ( EventData::SELECTION_HANDLE_PANNING );
587 if( ( handleNewPosition != mEventData->mRightSelectionPosition ) &&
588 ( handleNewPosition != mEventData->mLeftSelectionPosition ) )
590 mEventData->mRightSelectionPosition = handleNewPosition;
592 RepositionSelectionHandles( mEventData->mLeftSelectionPosition,
593 mEventData->mRightSelectionPosition );
595 mEventData->mUpdateRightSelectionPosition = true;
598 } // end ( HANDLE_PRESSED == state )
599 else if( ( HANDLE_RELEASED == state ) ||
600 handleStopScrolling )
602 CharacterIndex handlePosition = 0u;
603 if( handleStopScrolling )
605 // The event.p2 and event.p3 are in decorator coords. Need to transforms to text coords.
606 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
607 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
609 handlePosition = GetClosestCursorIndex( xPosition, yPosition );
612 if( Event::GRAB_HANDLE_EVENT == event.type )
614 mEventData->mUpdateCursorPosition = true;
616 ChangeState( EventData::EDITING_WITH_POPUP );
618 if( handleStopScrolling )
620 mEventData->mScrollAfterUpdatePosition = mEventData->mPrimaryCursorPosition != handlePosition;
621 mEventData->mPrimaryCursorPosition = handlePosition;
624 else if( Event::LEFT_SELECTION_HANDLE_EVENT == event.type )
626 ChangeState( EventData::SELECTING );
628 if( handleStopScrolling )
630 mEventData->mUpdateLeftSelectionPosition = ( mEventData->mLeftSelectionPosition != handlePosition ) && ( mEventData->mRightSelectionPosition != handlePosition);
631 mEventData->mScrollAfterUpdatePosition = mEventData->mUpdateLeftSelectionPosition;
633 if( mEventData->mUpdateLeftSelectionPosition )
635 mEventData->mLeftSelectionPosition = handlePosition;
637 RepositionSelectionHandles( mEventData->mLeftSelectionPosition,
638 mEventData->mRightSelectionPosition );
642 else if( Event::RIGHT_SELECTION_HANDLE_EVENT == event.type )
644 ChangeState( EventData::SELECTING );
646 if( handleStopScrolling )
648 mEventData->mUpdateRightSelectionPosition = ( mEventData->mRightSelectionPosition != handlePosition ) && ( mEventData->mLeftSelectionPosition != handlePosition );
649 mEventData->mScrollAfterUpdatePosition = mEventData->mUpdateRightSelectionPosition;
651 if( mEventData->mUpdateRightSelectionPosition )
653 mEventData->mRightSelectionPosition = handlePosition;
654 RepositionSelectionHandles( mEventData->mLeftSelectionPosition,
655 mEventData->mRightSelectionPosition );
660 mEventData->mDecoratorUpdated = true;
661 } // end ( ( HANDLE_RELEASED == state ) || ( HANDLE_STOP_SCROLLING == state ) )
662 else if( HANDLE_SCROLLING == state )
664 const float xSpeed = event.p2.mFloat;
665 const Vector2& actualSize = mVisualModel->GetActualSize();
666 const Vector2 currentScrollPosition = mEventData->mScrollPosition;
668 mEventData->mScrollPosition.x += xSpeed;
670 ClampHorizontalScroll( actualSize );
672 if( Vector2::ZERO == ( currentScrollPosition - mEventData->mScrollPosition ) )
674 // Notify the decorator there is no more text to scroll.
675 // The decorator won't send more scroll events.
676 mEventData->mDecorator->NotifyEndOfScroll();
680 const bool scrollRightDirection = xSpeed > 0.f;
681 const bool leftSelectionHandleEvent = Event::LEFT_SELECTION_HANDLE_EVENT == event.type;
682 const bool rightSelectionHandleEvent = Event::RIGHT_SELECTION_HANDLE_EVENT == event.type;
684 if( Event::GRAB_HANDLE_EVENT == event.type )
686 ChangeState( EventData::GRAB_HANDLE_PANNING );
688 Vector2 position = mEventData->mDecorator->GetPosition( GRAB_HANDLE );
690 // Position the grag handle close to either the left or right edge.
691 position.x = scrollRightDirection ? 0.f : mControlSize.width;
693 // Get the new handle position.
694 // The grab handle's position is in decorator coords. Need to transforms to text coords.
695 const CharacterIndex handlePosition = GetClosestCursorIndex( position.x - mEventData->mScrollPosition.x - mAlignmentOffset.x,
696 position.y - mEventData->mScrollPosition.y - mAlignmentOffset.y );
698 mEventData->mUpdateCursorPosition = mEventData->mPrimaryCursorPosition != handlePosition;
699 mEventData->mScrollAfterUpdatePosition = mEventData->mUpdateCursorPosition;
700 mEventData->mPrimaryCursorPosition = handlePosition;
702 else if( leftSelectionHandleEvent || rightSelectionHandleEvent )
704 // TODO: This is recalculating the selection box every time the text is scrolled with the selection handles.
705 // Think if something can be done to save power.
707 ChangeState( EventData::SELECTION_HANDLE_PANNING );
709 Vector2 position = mEventData->mDecorator->GetPosition( leftSelectionHandleEvent ? Text::LEFT_SELECTION_HANDLE : Text::RIGHT_SELECTION_HANDLE );
711 // Position the selection handle close to either the left or right edge.
712 position.x = scrollRightDirection ? 0.f : mControlSize.width;
714 // Get the new handle position.
715 // The selection handle's position is in decorator coords. Need to transforms to text coords.
716 const CharacterIndex handlePosition = GetClosestCursorIndex( position.x - mEventData->mScrollPosition.x - mAlignmentOffset.x,
717 position.y - mEventData->mScrollPosition.y - mAlignmentOffset.y );
719 if( leftSelectionHandleEvent )
721 mEventData->mUpdateLeftSelectionPosition = handlePosition != mEventData->mLeftSelectionPosition;
722 mEventData->mLeftSelectionPosition = handlePosition;
726 mEventData->mUpdateRightSelectionPosition = handlePosition != mEventData->mRightSelectionPosition;
727 mEventData->mRightSelectionPosition = handlePosition;
730 if( mEventData->mUpdateLeftSelectionPosition || mEventData->mUpdateRightSelectionPosition )
732 RepositionSelectionHandles( mEventData->mLeftSelectionPosition,
733 mEventData->mRightSelectionPosition );
735 mEventData->mScrollAfterUpdatePosition = true;
738 mEventData->mDecoratorUpdated = true;
740 } // end ( HANDLE_SCROLLING == state )
743 void Controller::Impl::OnSelectEvent( const Event& event )
745 if( NULL == mEventData )
747 // Nothing to do if there is no text.
751 if( mEventData->mSelectionEnabled )
753 // The event.p2 and event.p3 are in decorator coords. Need to transforms to text coords.
754 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
755 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
757 const CharacterIndex leftPosition = mEventData->mLeftSelectionPosition;
758 const CharacterIndex rightPosition = mEventData->mRightSelectionPosition;
760 RepositionSelectionHandles( xPosition,
763 mEventData->mUpdateLeftSelectionPosition = leftPosition != mEventData->mLeftSelectionPosition;
764 mEventData->mUpdateRightSelectionPosition = rightPosition != mEventData->mRightSelectionPosition;
766 mEventData->mScrollAfterUpdatePosition = ( ( mEventData->mUpdateLeftSelectionPosition || mEventData->mUpdateRightSelectionPosition ) &&
767 ( mEventData->mLeftSelectionPosition != mEventData->mRightSelectionPosition ) );
771 void Controller::Impl::OnSelectAllEvent()
773 if( NULL == mEventData )
775 // Nothing to do if there is no text.
779 if( mEventData->mSelectionEnabled )
781 RepositionSelectionHandles( 0u,
782 mLogicalModel->mText.Count() );
784 mEventData->mScrollAfterUpdatePosition = true;
785 mEventData->mUpdateLeftSelectionPosition = true;
786 mEventData->mUpdateRightSelectionPosition = true;
790 void Controller::Impl::RetrieveSelection( std::string& selectedText, bool deleteAfterRetreival )
792 if( mEventData->mLeftSelectionPosition == mEventData->mRightSelectionPosition )
794 // Nothing to select if handles are in the same place.
799 //Get start and end position of selection
800 uint32_t startOfSelectedText = mEventData->mLeftSelectionPosition;
801 uint32_t lengthOfSelectedText = mEventData->mRightSelectionPosition - startOfSelectedText;
803 // Validate the start and end selection points
804 if( ( startOfSelectedText >= 0 ) && ( ( startOfSelectedText + lengthOfSelectedText ) <= mLogicalModel->mText.Count() ) )
806 //Get text as a UTF8 string
807 Vector<Character>& utf32Characters = mLogicalModel->mText;
809 Utf32ToUtf8( &utf32Characters[startOfSelectedText], lengthOfSelectedText, selectedText );
811 if ( deleteAfterRetreival ) // Only delete text if copied successfully
813 // Delete text between handles
814 Vector<Character>& currentText = mLogicalModel->mText;
816 Vector<Character>::Iterator first = currentText.Begin() + startOfSelectedText;
817 Vector<Character>::Iterator last = first + lengthOfSelectedText;
818 currentText.Erase( first, last );
820 mEventData->mPrimaryCursorPosition = mEventData->mLeftSelectionPosition;
821 mEventData->mScrollAfterDelete = true;
822 mEventData->mDecoratorUpdated = true;
826 void Controller::Impl::ShowClipboard()
830 mClipboard.ShowClipboard();
834 void Controller::Impl::HideClipboard()
838 mClipboard.HideClipboard();
842 bool Controller::Impl::CopyStringToClipboard( std::string& source )
844 //Send string to clipboard
845 return ( mClipboard && mClipboard.SetItem( source ) );
848 void Controller::Impl::SendSelectionToClipboard( bool deleteAfterSending )
850 std::string selectedText;
851 RetrieveSelection( selectedText, deleteAfterSending );
852 CopyStringToClipboard( selectedText );
853 ChangeState( EventData::EDITING );
856 void Controller::Impl::GetTextFromClipboard( unsigned int itemIndex, std::string& retreivedString )
860 retreivedString = mClipboard.GetItem( itemIndex );
864 void Controller::Impl::RepositionSelectionHandles( CharacterIndex selectionStart, CharacterIndex selectionEnd )
866 if( selectionStart == selectionEnd )
868 // Nothing to select if handles are in the same place.
872 mEventData->mDecorator->ClearHighlights();
874 mEventData->mLeftSelectionPosition = selectionStart;
875 mEventData->mRightSelectionPosition = selectionEnd;
877 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
878 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
879 const GlyphInfo* const glyphsBuffer = mVisualModel->mGlyphs.Begin();
880 const Vector2* const positionsBuffer = mVisualModel->mGlyphPositions.Begin();
882 // TODO: Better algorithm to create the highlight box.
885 const Vector<LineRun>& lines = mVisualModel->mLines;
886 const LineRun& firstLine = *lines.Begin();
887 const float height = firstLine.ascender + -firstLine.descender;
889 const bool indicesSwapped = ( selectionStart > selectionEnd );
892 std::swap( selectionStart, selectionEnd );
895 GlyphIndex glyphStart = *( charactersToGlyphBuffer + selectionStart );
896 GlyphIndex glyphEnd = *( charactersToGlyphBuffer + ( selectionEnd - 1u ) ) + *( glyphsPerCharacterBuffer + ( selectionEnd - 1u ) ) - 1u;
898 mEventData->mDecorator->SwapSelectionHandlesEnabled( firstLine.direction != indicesSwapped );
900 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
902 for( GlyphIndex index = glyphStart; index <= glyphEnd; ++index )
904 const GlyphInfo& glyph = *( glyphsBuffer + index );
905 const Vector2& position = *( positionsBuffer + index );
907 const float xPosition = position.x - glyph.xBearing + offset.x;
908 mEventData->mDecorator->AddHighlight( xPosition, offset.y, xPosition + glyph.advance, height );
911 CursorInfo primaryCursorInfo;
912 GetCursorPosition( mEventData->mLeftSelectionPosition,
915 CursorInfo secondaryCursorInfo;
916 GetCursorPosition( mEventData->mRightSelectionPosition,
917 secondaryCursorInfo );
919 const Vector2 primaryPosition = primaryCursorInfo.primaryPosition + offset;
920 const Vector2 secondaryPosition = secondaryCursorInfo.primaryPosition + offset;
922 mEventData->mDecorator->SetPosition( LEFT_SELECTION_HANDLE, primaryPosition.x, primaryPosition.y, primaryCursorInfo.lineHeight );
924 mEventData->mDecorator->SetPosition( RIGHT_SELECTION_HANDLE, secondaryPosition.x, secondaryPosition.y, secondaryCursorInfo.lineHeight );
926 // Set the flag to update the decorator.
927 mEventData->mDecoratorUpdated = true;
930 void Controller::Impl::RepositionSelectionHandles( float visualX, float visualY )
932 if( NULL == mEventData )
934 // Nothing to do if there is no text input.
938 if( IsShowingPlaceholderText() )
940 // Nothing to do if there is the place-holder text.
944 const Length numberOfGlyphs = mVisualModel->mGlyphs.Count();
945 const Length numberOfLines = mVisualModel->mLines.Count();
946 if( 0 == numberOfGlyphs ||
949 // Nothing to do if there is no text.
953 // Find which word was selected
954 CharacterIndex selectionStart( 0 );
955 CharacterIndex selectionEnd( 0 );
956 FindSelectionIndices( visualX, visualY, selectionStart, selectionEnd );
957 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "%p selectionStart %d selectionEnd %d\n", this, selectionStart, selectionEnd );
959 if( selectionStart == selectionEnd )
961 ChangeState( EventData::EDITING );
962 // Nothing to select. i.e. a white space, out of bounds
966 RepositionSelectionHandles( selectionStart, selectionEnd );
969 void Controller::Impl::ChangeState( EventData::State newState )
971 if( NULL == mEventData )
973 // Nothing to do if there is no text input.
977 if( mEventData->mState != newState )
979 mEventData->mState = newState;
981 if( EventData::INACTIVE == mEventData->mState )
983 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
984 mEventData->mDecorator->StopCursorBlink();
985 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
986 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
987 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
988 mEventData->mDecorator->SetPopupActive( false );
989 mEventData->mDecoratorUpdated = true;
992 else if ( EventData::SELECTING == mEventData->mState )
994 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
995 mEventData->mDecorator->StopCursorBlink();
996 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
997 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true );
998 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true );
999 if( mEventData->mGrabHandlePopupEnabled )
1001 TextSelectionPopup::Buttons buttonsToShow = TextSelectionPopup::Buttons( TextSelectionPopup::CUT | TextSelectionPopup::COPY );
1002 if ( !IsClipboardEmpty() )
1004 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
1005 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::CLIPBOARD ) );
1008 mEventData->mDecorator->SetEnabledPopupButtons( buttonsToShow );
1009 mEventData->mDecorator->SetPopupActive( true );
1011 mEventData->mDecoratorUpdated = true;
1013 else if ( EventData::SELECTION_CHANGED == mEventData->mState )
1015 if( mEventData->mGrabHandlePopupEnabled )
1017 TextSelectionPopup::Buttons buttonsToShow = TextSelectionPopup::Buttons( TextSelectionPopup::CUT | TextSelectionPopup::COPY );
1018 if ( !IsClipboardEmpty() )
1020 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
1021 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::CLIPBOARD ) );
1023 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
1024 mEventData->mDecorator->SetEnabledPopupButtons( buttonsToShow );
1025 mEventData->mDecorator->SetPopupActive( true );
1027 mEventData->mDecoratorUpdated = true;
1029 else if( EventData::EDITING == mEventData->mState )
1031 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1032 if( mEventData->mCursorBlinkEnabled )
1034 mEventData->mDecorator->StartCursorBlink();
1036 // Grab handle is not shown until a tap is received whilst EDITING
1037 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1038 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1039 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1040 if( mEventData->mGrabHandlePopupEnabled )
1042 mEventData->mDecorator->SetPopupActive( false );
1044 mEventData->mDecoratorUpdated = true;
1047 else if( EventData::EDITING_WITH_POPUP == mEventData->mState )
1049 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1050 if( mEventData->mCursorBlinkEnabled )
1052 mEventData->mDecorator->StartCursorBlink();
1054 if( mEventData->mSelectionEnabled )
1056 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1057 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1061 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
1063 if( mEventData->mGrabHandlePopupEnabled )
1065 TextSelectionPopup::Buttons buttonsToShow = TextSelectionPopup::Buttons( TextSelectionPopup::SELECT | TextSelectionPopup::SELECT_ALL );
1067 if ( !IsClipboardEmpty() )
1069 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
1070 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::CLIPBOARD ) );
1073 mEventData->mDecorator->SetEnabledPopupButtons( buttonsToShow );
1074 mEventData->mDecorator->SetPopupActive( true );
1077 mEventData->mDecoratorUpdated = true;
1079 else if ( EventData::SELECTION_HANDLE_PANNING == mEventData->mState )
1081 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
1082 mEventData->mDecorator->StopCursorBlink();
1083 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1084 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true );
1085 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true );
1086 if( mEventData->mGrabHandlePopupEnabled )
1088 mEventData->mDecorator->SetPopupActive( false );
1090 mEventData->mDecoratorUpdated = true;
1092 else if ( EventData::GRAB_HANDLE_PANNING == mEventData->mState )
1094 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1095 if( mEventData->mCursorBlinkEnabled )
1097 mEventData->mDecorator->StartCursorBlink();
1099 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
1100 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1101 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1102 if( mEventData->mGrabHandlePopupEnabled )
1104 mEventData->mDecorator->SetPopupActive( false );
1106 mEventData->mDecoratorUpdated = true;
1111 LineIndex Controller::Impl::GetClosestLine( float y ) const
1113 float totalHeight = 0.f;
1114 LineIndex lineIndex = 0u;
1116 const Vector<LineRun>& lines = mVisualModel->mLines;
1117 for( LineIndex endLine = lines.Count();
1118 lineIndex < endLine;
1121 const LineRun& lineRun = lines[lineIndex];
1122 totalHeight += lineRun.ascender + -lineRun.descender;
1123 if( y < totalHeight )
1129 if( lineIndex == 0 )
1137 void Controller::Impl::FindSelectionIndices( float visualX, float visualY, CharacterIndex& startIndex, CharacterIndex& endIndex )
1139 CharacterIndex hitCharacter = GetClosestCursorIndex( visualX, visualY );
1140 if( hitCharacter >= mLogicalModel->mText.Count() )
1142 // Selection out of bounds.
1146 startIndex = hitCharacter;
1147 endIndex = hitCharacter;
1149 if( !TextAbstraction::IsWhiteSpace( mLogicalModel->mText[hitCharacter] ) )
1151 // Find the start and end of the text
1152 for( startIndex = hitCharacter; startIndex > 0; --startIndex )
1154 Character charCode = mLogicalModel->mText[ startIndex-1 ];
1155 if( TextAbstraction::IsWhiteSpace( charCode ) )
1160 const CharacterIndex pastTheEnd = mLogicalModel->mText.Count();
1161 for( endIndex = hitCharacter + 1u; endIndex < pastTheEnd; ++endIndex )
1163 Character charCode = mLogicalModel->mText[ endIndex ];
1164 if( TextAbstraction::IsWhiteSpace( charCode ) )
1172 CharacterIndex Controller::Impl::GetClosestCursorIndex( float visualX,
1175 if( NULL == mEventData )
1177 // Nothing to do if there is no text input.
1181 CharacterIndex logicalIndex = 0u;
1183 const Length numberOfGlyphs = mVisualModel->mGlyphs.Count();
1184 const Length numberOfLines = mVisualModel->mLines.Count();
1185 if( 0 == numberOfGlyphs ||
1186 0 == numberOfLines )
1188 return logicalIndex;
1191 // Find which line is closest
1192 const LineIndex lineIndex = GetClosestLine( visualY );
1193 const LineRun& line = mVisualModel->mLines[lineIndex];
1195 // Get the positions of the glyphs.
1196 const Vector<Vector2>& positions = mVisualModel->mGlyphPositions;
1197 const Vector2* const positionsBuffer = positions.Begin();
1199 // Get the visual to logical conversion tables.
1200 const CharacterIndex* const visualToLogicalBuffer = ( 0u != mLogicalModel->mVisualToLogicalMap.Count() ) ? mLogicalModel->mVisualToLogicalMap.Begin() : NULL;
1201 const CharacterIndex* const visualToLogicalCursorBuffer = mLogicalModel->mVisualToLogicalCursorMap.Begin();
1203 // Get the character to glyph conversion table.
1204 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
1206 // Get the glyphs per character table.
1207 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
1209 // If the vector is void, there is no right to left characters.
1210 const bool hasRightToLeftCharacters = NULL != visualToLogicalBuffer;
1212 const CharacterIndex startCharacter = line.characterRun.characterIndex;
1213 const CharacterIndex endCharacter = line.characterRun.characterIndex + line.characterRun.numberOfCharacters;
1214 DALI_ASSERT_DEBUG( endCharacter <= mLogicalModel->mText.Count() && "Invalid line info" );
1216 // Whether there is a hit on a glyph.
1217 bool matched = false;
1219 // Traverses glyphs in visual order. To do that use the visual to logical conversion table.
1220 CharacterIndex visualIndex = startCharacter;
1221 for( ; !matched && ( visualIndex < endCharacter ); ++visualIndex )
1223 // The character in logical order.
1224 const CharacterIndex characterLogicalOrderIndex = hasRightToLeftCharacters ? *( visualToLogicalBuffer + visualIndex ) : visualIndex;
1226 // The first glyph for that character in logical order.
1227 const GlyphIndex glyphLogicalOrderIndex = *( charactersToGlyphBuffer + characterLogicalOrderIndex );
1229 // The number of glyphs for that character
1230 const Length numberOfGlyphs = *( glyphsPerCharacterBuffer + characterLogicalOrderIndex );
1232 // Get the metrics for the group of glyphs.
1233 GlyphMetrics glyphMetrics;
1234 GetGlyphsMetrics( glyphLogicalOrderIndex,
1240 const Vector2& position = *( positionsBuffer + glyphLogicalOrderIndex );
1242 // Find the mid-point of the area containing the glyph
1243 const float glyphCenter = -glyphMetrics.xBearing + position.x + 0.5f * glyphMetrics.advance;
1245 if( visualX < glyphCenter )
1252 // Return the logical position of the cursor in characters.
1256 visualIndex = endCharacter;
1259 logicalIndex = hasRightToLeftCharacters ? *( visualToLogicalCursorBuffer + visualIndex ) : visualIndex;
1260 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "%p closest visualIndex %d logicalIndex %d\n", this, visualIndex, logicalIndex );
1261 return logicalIndex;
1264 void Controller::Impl::GetCursorPosition( CharacterIndex logical,
1265 CursorInfo& cursorInfo )
1267 // TODO: Check for multiline with \n, etc...
1269 // Check if the logical position is the first or the last one of the text.
1270 const bool isFirstPosition = 0u == logical;
1271 const bool isLastPosition = mLogicalModel->mText.Count() == logical;
1273 if( isFirstPosition && isLastPosition )
1275 // There is zero characters. Get the default font.
1277 FontId defaultFontId = 0u;
1278 if( NULL == mFontDefaults )
1280 defaultFontId = mFontClient.GetFontId( EMPTY_STRING,
1285 defaultFontId = mFontDefaults->GetFontId( mFontClient );
1288 Text::FontMetrics fontMetrics;
1289 mFontClient.GetFontMetrics( defaultFontId, fontMetrics );
1291 cursorInfo.lineHeight = fontMetrics.ascender - fontMetrics.descender;
1292 cursorInfo.primaryCursorHeight = cursorInfo.lineHeight;
1294 cursorInfo.primaryPosition.x = 1.f;
1295 cursorInfo.primaryPosition.y = 0.f;
1297 // Nothing else to do.
1301 // Get the previous logical index.
1302 const CharacterIndex previousLogical = isFirstPosition ? 0u : logical - 1u;
1304 // Decrease the logical index if it's the last one.
1305 if( isLastPosition )
1310 // Get the direction of the character and the previous one.
1311 const CharacterDirection* const modelCharacterDirectionsBuffer = ( 0u != mLogicalModel->mCharacterDirections.Count() ) ? mLogicalModel->mCharacterDirections.Begin() : NULL;
1313 CharacterDirection isCurrentRightToLeft = false;
1314 CharacterDirection isPreviousRightToLeft = false;
1315 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
1317 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + logical );
1318 isPreviousRightToLeft = *( modelCharacterDirectionsBuffer + previousLogical );
1321 // Get the line where the character is laid-out.
1322 const LineRun* modelLines = mVisualModel->mLines.Begin();
1324 const LineIndex lineIndex = mVisualModel->GetLineOfCharacter( logical );
1325 const LineRun& line = *( modelLines + lineIndex );
1327 // Get the paragraph's direction.
1328 const CharacterDirection isRightToLeftParagraph = line.direction;
1330 // Check whether there is an alternative position:
1332 cursorInfo.isSecondaryCursor = ( isCurrentRightToLeft != isPreviousRightToLeft ) ||
1333 ( isLastPosition && ( isRightToLeftParagraph != isCurrentRightToLeft ) );
1335 // Set the line height.
1336 cursorInfo.lineHeight = line.ascender + -line.descender;
1338 // Convert the cursor position into the glyph position.
1339 CharacterIndex characterIndex = logical;
1340 if( cursorInfo.isSecondaryCursor &&
1341 ( isRightToLeftParagraph != isCurrentRightToLeft ) )
1343 characterIndex = previousLogical;
1346 const GlyphIndex currentGlyphIndex = *( mVisualModel->mCharactersToGlyph.Begin() + characterIndex );
1347 const Length numberOfGlyphs = *( mVisualModel->mGlyphsPerCharacter.Begin() + characterIndex );
1348 const Length numberOfCharacters = *( mVisualModel->mCharactersPerGlyph.Begin() +currentGlyphIndex );
1350 // Get the metrics for the group of glyphs.
1351 GlyphMetrics glyphMetrics;
1352 GetGlyphsMetrics( currentGlyphIndex,
1358 float interGlyphAdvance = 0.f;
1359 if( !isLastPosition &&
1360 ( numberOfCharacters > 1u ) )
1362 const CharacterIndex firstIndex = *( mVisualModel->mGlyphsToCharacters.Begin() + currentGlyphIndex );
1363 interGlyphAdvance = static_cast<float>( characterIndex - firstIndex ) * glyphMetrics.advance / static_cast<float>( numberOfCharacters );
1366 // Get the glyph position and x bearing.
1367 const Vector2& currentPosition = *( mVisualModel->mGlyphPositions.Begin() + currentGlyphIndex );
1369 // Set the cursor's height.
1370 cursorInfo.primaryCursorHeight = glyphMetrics.fontHeight;
1372 // Set the position.
1373 cursorInfo.primaryPosition.x = -glyphMetrics.xBearing + currentPosition.x + ( isCurrentRightToLeft ? glyphMetrics.advance : interGlyphAdvance );
1374 cursorInfo.primaryPosition.y = line.ascender - glyphMetrics.ascender;
1376 if( isLastPosition )
1378 // The position of the cursor after the last character needs special
1379 // care depending on its direction and the direction of the paragraph.
1381 if( cursorInfo.isSecondaryCursor )
1383 // Need to find the first character after the last character with the paragraph's direction.
1384 // i.e l0 l1 l2 r0 r1 should find r0.
1386 // TODO: check for more than one line!
1387 characterIndex = isRightToLeftParagraph ? line.characterRun.characterIndex : line.characterRun.characterIndex + line.characterRun.numberOfCharacters - 1u;
1388 characterIndex = mLogicalModel->GetLogicalCharacterIndex( characterIndex );
1390 const GlyphIndex glyphIndex = *( mVisualModel->mCharactersToGlyph.Begin() + characterIndex );
1391 const Length numberOfGlyphs = *( mVisualModel->mGlyphsPerCharacter.Begin() + characterIndex );
1393 const Vector2& position = *( mVisualModel->mGlyphPositions.Begin() + glyphIndex );
1395 // Get the metrics for the group of glyphs.
1396 GlyphMetrics glyphMetrics;
1397 GetGlyphsMetrics( glyphIndex,
1403 cursorInfo.primaryPosition.x = -glyphMetrics.xBearing + position.x + ( isRightToLeftParagraph ? 0.f : glyphMetrics.advance );
1405 cursorInfo.primaryPosition.y = line.ascender - glyphMetrics.ascender;
1409 if( !isCurrentRightToLeft )
1411 cursorInfo.primaryPosition.x += glyphMetrics.advance;
1415 cursorInfo.primaryPosition.x -= glyphMetrics.advance;
1420 // Set the alternative cursor position.
1421 if( cursorInfo.isSecondaryCursor )
1423 // Convert the cursor position into the glyph position.
1424 const CharacterIndex previousCharacterIndex = ( ( isRightToLeftParagraph != isCurrentRightToLeft ) ? logical : previousLogical );
1425 const GlyphIndex previousGlyphIndex = *( mVisualModel->mCharactersToGlyph.Begin() + previousCharacterIndex );
1426 const Length numberOfGlyphs = *( mVisualModel->mGlyphsPerCharacter.Begin() + previousCharacterIndex );
1428 // Get the glyph position.
1429 const Vector2& previousPosition = *( mVisualModel->mGlyphPositions.Begin() + previousGlyphIndex );
1431 // Get the metrics for the group of glyphs.
1432 GlyphMetrics glyphMetrics;
1433 GetGlyphsMetrics( previousGlyphIndex,
1439 // Set the cursor position and height.
1440 cursorInfo.secondaryPosition.x = -glyphMetrics.xBearing + previousPosition.x + ( ( ( isLastPosition && !isCurrentRightToLeft ) ||
1441 ( !isLastPosition && isCurrentRightToLeft ) ) ? glyphMetrics.advance : 0.f );
1443 cursorInfo.secondaryCursorHeight = 0.5f * glyphMetrics.fontHeight;
1445 cursorInfo.secondaryPosition.y = cursorInfo.lineHeight - cursorInfo.secondaryCursorHeight - line.descender - ( glyphMetrics.fontHeight - glyphMetrics.ascender );
1447 // Update the primary cursor height as well.
1448 cursorInfo.primaryCursorHeight *= 0.5f;
1452 CharacterIndex Controller::Impl::CalculateNewCursorIndex( CharacterIndex index ) const
1454 if( NULL == mEventData )
1456 // Nothing to do if there is no text input.
1460 CharacterIndex cursorIndex = mEventData->mPrimaryCursorPosition;
1462 const Script script = mLogicalModel->GetScript( index );
1463 const GlyphIndex* charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
1464 const Length* charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
1466 Length numberOfCharacters = 0u;
1467 if( TextAbstraction::LATIN == script )
1469 // Prevents to jump the whole Latin ligatures like fi, ff, ...
1470 numberOfCharacters = 1u;
1474 GlyphIndex glyphIndex = *( charactersToGlyphBuffer + index );
1475 numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
1477 while( 0u == numberOfCharacters )
1479 numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
1484 if( index < mEventData->mPrimaryCursorPosition )
1486 cursorIndex -= numberOfCharacters;
1490 cursorIndex += numberOfCharacters;
1496 void Controller::Impl::UpdateCursorPosition()
1498 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "-->Controller::UpdateCursorPosition %p\n", this );
1499 if( NULL == mEventData )
1501 // Nothing to do if there is no text input.
1502 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition no event data\n" );
1506 if( IsShowingPlaceholderText() )
1508 // Do not want to use the place-holder text to set the cursor position.
1510 // Use the line's height of the font's family set to set the cursor's size.
1511 // If there is no font's family set, use the default font.
1512 // Use the current alignment to place the cursor at the beginning, center or end of the box.
1514 float lineHeight = 0.f;
1516 FontId defaultFontId = 0u;
1517 if( NULL == mFontDefaults )
1519 defaultFontId = mFontClient.GetFontId( EMPTY_STRING,
1524 defaultFontId = mFontDefaults->GetFontId( mFontClient );
1527 Text::FontMetrics fontMetrics;
1528 mFontClient.GetFontMetrics( defaultFontId, fontMetrics );
1530 lineHeight = fontMetrics.ascender - fontMetrics.descender;
1533 Vector2 cursorPosition;
1535 switch( mLayoutEngine.GetHorizontalAlignment() )
1537 case LayoutEngine::HORIZONTAL_ALIGN_BEGIN:
1539 cursorPosition.x = 1.f;
1542 case LayoutEngine::HORIZONTAL_ALIGN_CENTER:
1544 cursorPosition.x = floor( 0.5f * mControlSize.width );
1547 case LayoutEngine::HORIZONTAL_ALIGN_END:
1549 cursorPosition.x = mControlSize.width;
1554 switch( mLayoutEngine.GetVerticalAlignment() )
1556 case LayoutEngine::VERTICAL_ALIGN_TOP:
1558 cursorPosition.y = 0.f;
1561 case LayoutEngine::VERTICAL_ALIGN_CENTER:
1563 cursorPosition.y = floorf( 0.5f * ( mControlSize.height - lineHeight ) );
1566 case LayoutEngine::VERTICAL_ALIGN_BOTTOM:
1568 cursorPosition.y = mControlSize.height - lineHeight;
1573 mEventData->mDecorator->SetPosition( PRIMARY_CURSOR,
1581 CursorInfo cursorInfo;
1582 GetCursorPosition( mEventData->mPrimaryCursorPosition,
1585 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
1586 const Vector2 cursorPosition = cursorInfo.primaryPosition + offset;
1588 // Sets the cursor position.
1589 mEventData->mDecorator->SetPosition( PRIMARY_CURSOR,
1592 cursorInfo.primaryCursorHeight,
1593 cursorInfo.lineHeight );
1594 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Primary cursor position: %f,%f\n", cursorPosition.x, cursorPosition.y );
1596 // Sets the grab handle position.
1597 mEventData->mDecorator->SetPosition( GRAB_HANDLE,
1600 cursorInfo.lineHeight );
1602 if( cursorInfo.isSecondaryCursor )
1604 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_BOTH );
1605 mEventData->mDecorator->SetPosition( SECONDARY_CURSOR,
1606 cursorInfo.secondaryPosition.x + offset.x,
1607 cursorInfo.secondaryPosition.y + offset.y,
1608 cursorInfo.secondaryCursorHeight,
1609 cursorInfo.lineHeight );
1610 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Secondary cursor position: %f,%f\n", cursorInfo.secondaryPosition.x + offset.x, cursorInfo.secondaryPosition.y + offset.y );
1614 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1617 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition\n" );
1620 void Controller::Impl::UpdateSelectionHandle( HandleType handleType )
1622 if( ( LEFT_SELECTION_HANDLE != handleType ) &&
1623 ( RIGHT_SELECTION_HANDLE != handleType ) )
1628 const bool leftSelectionHandle = LEFT_SELECTION_HANDLE == handleType;
1629 const CharacterIndex index = leftSelectionHandle ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition;
1631 CursorInfo cursorInfo;
1632 GetCursorPosition( index,
1635 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
1636 const Vector2 cursorPosition = cursorInfo.primaryPosition + offset;
1638 // Sets the grab handle position.
1639 mEventData->mDecorator->SetPosition( handleType,
1642 cursorInfo.lineHeight );
1645 void Controller::Impl::ClampHorizontalScroll( const Vector2& actualSize )
1647 // Clamp between -space & 0 (and the text alignment).
1648 if( actualSize.width > mControlSize.width )
1650 const float space = ( actualSize.width - mControlSize.width ) + mAlignmentOffset.x;
1651 mEventData->mScrollPosition.x = ( mEventData->mScrollPosition.x < -space ) ? -space : mEventData->mScrollPosition.x;
1652 mEventData->mScrollPosition.x = ( mEventData->mScrollPosition.x > -mAlignmentOffset.x ) ? -mAlignmentOffset.x : mEventData->mScrollPosition.x;
1654 mEventData->mDecoratorUpdated = true;
1658 mEventData->mScrollPosition.x = 0.f;
1662 void Controller::Impl::ClampVerticalScroll( const Vector2& actualSize )
1664 // Clamp between -space & 0 (and the text alignment).
1665 if( actualSize.height > mControlSize.height )
1667 const float space = ( actualSize.height - mControlSize.height ) + mAlignmentOffset.y;
1668 mEventData->mScrollPosition.y = ( mEventData->mScrollPosition.y < -space ) ? -space : mEventData->mScrollPosition.y;
1669 mEventData->mScrollPosition.y = ( mEventData->mScrollPosition.y > -mAlignmentOffset.y ) ? -mAlignmentOffset.y : mEventData->mScrollPosition.y;
1671 mEventData->mDecoratorUpdated = true;
1675 mEventData->mScrollPosition.y = 0.f;
1679 void Controller::Impl::ScrollToMakePositionVisible( const Vector2& position )
1682 bool updateDecorator = false;
1683 if( position.x < 0.f )
1685 offset.x = -position.x;
1686 mEventData->mScrollPosition.x += offset.x;
1687 updateDecorator = true;
1689 else if( position.x > mControlSize.width )
1691 offset.x = mControlSize.width - position.x;
1692 mEventData->mScrollPosition.x += offset.x;
1693 updateDecorator = true;
1696 if( updateDecorator && mEventData->mDecorator )
1698 mEventData->mDecorator->UpdatePositions( offset );
1701 // TODO : calculate the vertical scroll.
1704 void Controller::Impl::ScrollTextToMatchCursor()
1706 // Get the current cursor position in decorator coords.
1707 const Vector2& currentCursorPosition = mEventData->mDecorator->GetPosition( PRIMARY_CURSOR );
1709 // Calculate the new cursor position.
1710 CursorInfo cursorInfo;
1711 GetCursorPosition( mEventData->mPrimaryCursorPosition,
1714 // Calculate the offset to match the cursor position before the character was deleted.
1715 mEventData->mScrollPosition.x = currentCursorPosition.x - cursorInfo.primaryPosition.x - mAlignmentOffset.x;
1717 ClampHorizontalScroll( mVisualModel->GetActualSize() );
1719 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
1720 const Vector2 cursorPosition = cursorInfo.primaryPosition + offset;
1722 // Sets the cursor position.
1723 mEventData->mDecorator->SetPosition( PRIMARY_CURSOR,
1726 cursorInfo.primaryCursorHeight,
1727 cursorInfo.lineHeight );
1729 // Sets the grab handle position.
1730 mEventData->mDecorator->SetPosition( GRAB_HANDLE,
1733 cursorInfo.lineHeight );
1735 if( cursorInfo.isSecondaryCursor )
1737 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_BOTH );
1738 mEventData->mDecorator->SetPosition( SECONDARY_CURSOR,
1739 cursorInfo.secondaryPosition.x + offset.x,
1740 cursorInfo.secondaryPosition.y + offset.y,
1741 cursorInfo.secondaryCursorHeight,
1742 cursorInfo.lineHeight );
1743 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Secondary cursor position: %f,%f\n", cursorInfo.secondaryPosition.x + offset.x, cursorInfo.secondaryPosition.y + offset.y );
1747 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1751 void Controller::Impl::RequestRelayout()
1753 mControlInterface.RequestTextRelayout();
1758 } // namespace Toolkit