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 ),
140 mAllTextSelected( false )
143 EventData::~EventData()
146 bool Controller::Impl::ProcessInputEvents()
148 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "-->Controller::ProcessInputEvents\n" );
149 if( NULL == mEventData )
151 // Nothing to do if there is no text input.
152 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::ProcessInputEvents no event data\n" );
156 if( mEventData->mDecorator )
158 for( std::vector<Event>::iterator iter = mEventData->mEventQueue.begin();
159 iter != mEventData->mEventQueue.end();
164 case Event::CURSOR_KEY_EVENT:
166 OnCursorKeyEvent( *iter );
169 case Event::TAP_EVENT:
174 case Event::LONG_PRESS_EVENT:
176 OnLongPressEvent( *iter );
179 case Event::PAN_EVENT:
184 case Event::GRAB_HANDLE_EVENT:
185 case Event::LEFT_SELECTION_HANDLE_EVENT:
186 case Event::RIGHT_SELECTION_HANDLE_EVENT: // Fall through
188 OnHandleEvent( *iter );
193 OnSelectEvent( *iter );
196 case Event::SELECT_ALL:
205 // The cursor must also be repositioned after inserts into the model
206 if( mEventData->mUpdateCursorPosition )
208 // Updates the cursor position and scrolls the text to make it visible.
210 UpdateCursorPosition();
212 if( mEventData->mScrollAfterUpdatePosition )
214 const Vector2& primaryCursorPosition = mEventData->mDecorator->GetPosition( PRIMARY_CURSOR );
216 ScrollToMakePositionVisible( primaryCursorPosition );
217 mEventData->mScrollAfterUpdatePosition = false;
220 mEventData->mDecoratorUpdated = true;
221 mEventData->mUpdateCursorPosition = false;
223 else if( mEventData->mScrollAfterDelete )
225 ScrollTextToMatchCursor();
226 mEventData->mDecoratorUpdated = true;
227 mEventData->mScrollAfterDelete = false;
231 bool leftScroll = false;
232 bool rightScroll = false;
234 if( mEventData->mUpdateLeftSelectionPosition )
236 UpdateSelectionHandle( LEFT_SELECTION_HANDLE );
238 if( mEventData->mScrollAfterUpdatePosition )
240 const Vector2& leftHandlePosition = mEventData->mDecorator->GetPosition( LEFT_SELECTION_HANDLE );
242 ScrollToMakePositionVisible( leftHandlePosition );
247 mEventData->mDecoratorUpdated = true;
248 mEventData->mUpdateLeftSelectionPosition = false;
251 if( mEventData->mUpdateRightSelectionPosition )
253 UpdateSelectionHandle( RIGHT_SELECTION_HANDLE );
255 if( mEventData->mScrollAfterUpdatePosition )
257 const Vector2& rightHandlePosition = mEventData->mDecorator->GetPosition( RIGHT_SELECTION_HANDLE );
259 ScrollToMakePositionVisible( rightHandlePosition );
264 mEventData->mDecoratorUpdated = true;
265 mEventData->mUpdateRightSelectionPosition = false;
268 if( leftScroll || rightScroll )
270 mEventData->mScrollAfterUpdatePosition = false;
274 mEventData->mEventQueue.clear();
276 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::ProcessInputEvents\n" );
278 const bool decoratorUpdated = mEventData->mDecoratorUpdated;
279 mEventData->mDecoratorUpdated = false;
281 return decoratorUpdated;
284 void Controller::Impl::UpdateModel( OperationsMask operationsRequired )
286 // Calculate the operations to be done.
287 const OperationsMask operations = static_cast<OperationsMask>( mOperationsPending & operationsRequired );
289 Vector<Character>& utf32Characters = mLogicalModel->mText;
291 const Length numberOfCharacters = utf32Characters.Count();
293 Vector<LineBreakInfo>& lineBreakInfo = mLogicalModel->mLineBreakInfo;
294 if( GET_LINE_BREAKS & operations )
296 // Retrieves the line break info. The line break info is used to split the text in 'paragraphs' to
297 // calculate the bidirectional info for each 'paragraph'.
298 // It's also used to layout the text (where it should be a new line) or to shape the text (text in different lines
299 // is not shaped together).
300 lineBreakInfo.Resize( numberOfCharacters, TextAbstraction::LINE_NO_BREAK );
302 SetLineBreakInfo( utf32Characters,
306 Vector<WordBreakInfo>& wordBreakInfo = mLogicalModel->mWordBreakInfo;
307 if( GET_WORD_BREAKS & operations )
309 // Retrieves the word break info. The word break info is used to layout the text (where to wrap the text in lines).
310 wordBreakInfo.Resize( numberOfCharacters, TextAbstraction::WORD_NO_BREAK );
312 SetWordBreakInfo( utf32Characters,
316 const bool getScripts = GET_SCRIPTS & operations;
317 const bool validateFonts = VALIDATE_FONTS & operations;
319 Vector<ScriptRun>& scripts = mLogicalModel->mScriptRuns;
320 Vector<FontRun>& validFonts = mLogicalModel->mFontRuns;
322 if( getScripts || validateFonts )
324 // Validates the fonts assigned by the application or assigns default ones.
325 // It makes sure all the characters are going to be rendered by the correct font.
326 MultilanguageSupport multilanguageSupport = MultilanguageSupport::Get();
330 // Retrieves the scripts used in the text.
331 multilanguageSupport.SetScripts( utf32Characters,
338 if( 0u == validFonts.Count() )
340 // Copy the requested font defaults received via the property system.
341 // These may not be valid i.e. may not contain glyphs for the necessary scripts.
342 GetDefaultFonts( validFonts, numberOfCharacters );
345 // Validates the fonts. If there is a character with no assigned font it sets a default one.
346 // After this call, fonts are validated.
347 multilanguageSupport.ValidateFonts( utf32Characters,
353 Vector<Character> mirroredUtf32Characters;
354 bool textMirrored = false;
355 if( BIDI_INFO & operations )
357 // Count the number of LINE_NO_BREAK to reserve some space for the vector of paragraph's
358 // bidirectional info.
360 Length numberOfParagraphs = 0u;
362 const TextAbstraction::LineBreakInfo* lineBreakInfoBuffer = lineBreakInfo.Begin();
363 for( Length index = 0u; index < numberOfCharacters; ++index )
365 if( TextAbstraction::LINE_NO_BREAK == *( lineBreakInfoBuffer + index ) )
367 ++numberOfParagraphs;
371 Vector<BidirectionalParagraphInfoRun>& bidirectionalInfo = mLogicalModel->mBidirectionalParagraphInfo;
372 bidirectionalInfo.Reserve( numberOfParagraphs );
374 // Calculates the bidirectional info for the whole paragraph if it contains right to left scripts.
375 SetBidirectionalInfo( utf32Characters,
380 if( 0u != bidirectionalInfo.Count() )
382 // This paragraph has right to left text. Some characters may need to be mirrored.
383 // TODO: consider if the mirrored string can be stored as well.
385 textMirrored = GetMirroredText( utf32Characters, mirroredUtf32Characters );
387 // Only set the character directions if there is right to left characters.
388 Vector<CharacterDirection>& directions = mLogicalModel->mCharacterDirections;
389 directions.Resize( numberOfCharacters );
391 GetCharactersDirection( bidirectionalInfo,
396 // There is no right to left characters. Clear the directions vector.
397 mLogicalModel->mCharacterDirections.Clear();
402 Vector<GlyphInfo>& glyphs = mVisualModel->mGlyphs;
403 Vector<CharacterIndex>& glyphsToCharactersMap = mVisualModel->mGlyphsToCharacters;
404 Vector<Length>& charactersPerGlyph = mVisualModel->mCharactersPerGlyph;
405 if( SHAPE_TEXT & operations )
407 const Vector<Character>& textToShape = textMirrored ? mirroredUtf32Characters : utf32Characters;
409 ShapeText( textToShape,
414 glyphsToCharactersMap,
415 charactersPerGlyph );
417 // Create the 'number of glyphs' per character and the glyph to character conversion tables.
418 mVisualModel->CreateGlyphsPerCharacterTable( numberOfCharacters );
419 mVisualModel->CreateCharacterToGlyphTable( numberOfCharacters );
422 const Length numberOfGlyphs = glyphs.Count();
424 if( GET_GLYPH_METRICS & operations )
426 mFontClient.GetGlyphMetrics( glyphs.Begin(), numberOfGlyphs );
430 void Controller::Impl::GetDefaultFonts( Vector<FontRun>& fonts, Length numberOfCharacters )
435 fontRun.characterRun.characterIndex = 0;
436 fontRun.characterRun.numberOfCharacters = numberOfCharacters;
437 fontRun.fontId = mFontDefaults->GetFontId( mFontClient );
438 fontRun.isDefault = true;
440 fonts.PushBack( fontRun );
444 void Controller::Impl::OnCursorKeyEvent( const Event& event )
446 if( NULL == mEventData )
448 // Nothing to do if there is no text input.
452 int keyCode = event.p1.mInt;
454 if( Dali::DALI_KEY_CURSOR_LEFT == keyCode )
456 if( mEventData->mPrimaryCursorPosition > 0u )
458 mEventData->mPrimaryCursorPosition = CalculateNewCursorIndex( mEventData->mPrimaryCursorPosition - 1u );
461 else if( Dali::DALI_KEY_CURSOR_RIGHT == keyCode )
463 if( mLogicalModel->mText.Count() > mEventData->mPrimaryCursorPosition )
465 mEventData->mPrimaryCursorPosition = CalculateNewCursorIndex( mEventData->mPrimaryCursorPosition );
468 else if( Dali::DALI_KEY_CURSOR_UP == keyCode )
472 else if( Dali::DALI_KEY_CURSOR_DOWN == keyCode )
477 mEventData->mUpdateCursorPosition = true;
478 mEventData->mScrollAfterUpdatePosition = true;
481 void Controller::Impl::OnTapEvent( const Event& event )
483 if( NULL != mEventData )
485 const unsigned int tapCount = event.p1.mUint;
489 if( ! IsShowingPlaceholderText() )
491 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
492 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
494 mEventData->mPrimaryCursorPosition = GetClosestCursorIndex( xPosition,
499 mEventData->mPrimaryCursorPosition = 0u;
502 mEventData->mUpdateCursorPosition = true;
503 mEventData->mScrollAfterUpdatePosition = true;
508 void Controller::Impl::OnPanEvent( const Event& event )
510 if( NULL == mEventData )
512 // Nothing to do if there is no text input.
516 int state = event.p1.mInt;
518 if( Gesture::Started == state ||
519 Gesture::Continuing == state )
521 const Vector2& actualSize = mVisualModel->GetActualSize();
522 const Vector2 currentScroll = mEventData->mScrollPosition;
524 if( mEventData->mHorizontalScrollingEnabled )
526 const float displacementX = event.p2.mFloat;
527 mEventData->mScrollPosition.x += displacementX;
529 ClampHorizontalScroll( actualSize );
532 if( mEventData->mVerticalScrollingEnabled )
534 const float displacementY = event.p3.mFloat;
535 mEventData->mScrollPosition.y += displacementY;
537 ClampVerticalScroll( actualSize );
540 if( mEventData->mDecorator )
542 mEventData->mDecorator->UpdatePositions( mEventData->mScrollPosition - currentScroll );
547 void Controller::Impl::OnLongPressEvent( const Event& event )
549 if ( EventData::EDITING == mEventData->mState )
551 ChangeState ( EventData::EDITING_WITH_POPUP );
552 mEventData->mDecoratorUpdated = true;
556 void Controller::Impl::OnHandleEvent( const Event& event )
558 if( NULL == mEventData )
560 // Nothing to do if there is no text input.
564 const unsigned int state = event.p1.mUint;
565 const bool handleStopScrolling = ( HANDLE_STOP_SCROLLING == state );
567 if( HANDLE_PRESSED == state )
569 // The event.p2 and event.p3 are in decorator coords. Need to transforms to text coords.
570 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
571 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
573 const CharacterIndex handleNewPosition = GetClosestCursorIndex( xPosition, yPosition );
575 if( Event::GRAB_HANDLE_EVENT == event.type )
577 ChangeState ( EventData::GRAB_HANDLE_PANNING );
579 if( handleNewPosition != mEventData->mPrimaryCursorPosition )
581 mEventData->mPrimaryCursorPosition = handleNewPosition;
582 mEventData->mUpdateCursorPosition = true;
585 else if( Event::LEFT_SELECTION_HANDLE_EVENT == event.type )
587 ChangeState ( EventData::SELECTION_HANDLE_PANNING );
589 if( ( handleNewPosition != mEventData->mLeftSelectionPosition ) &&
590 ( handleNewPosition != mEventData->mRightSelectionPosition ) )
592 mEventData->mLeftSelectionPosition = handleNewPosition;
594 RepositionSelectionHandles( mEventData->mLeftSelectionPosition,
595 mEventData->mRightSelectionPosition );
597 mEventData->mUpdateLeftSelectionPosition = true;
600 else if( Event::RIGHT_SELECTION_HANDLE_EVENT == event.type )
602 ChangeState ( EventData::SELECTION_HANDLE_PANNING );
604 if( ( handleNewPosition != mEventData->mRightSelectionPosition ) &&
605 ( handleNewPosition != mEventData->mLeftSelectionPosition ) )
607 mEventData->mRightSelectionPosition = handleNewPosition;
609 RepositionSelectionHandles( mEventData->mLeftSelectionPosition,
610 mEventData->mRightSelectionPosition );
612 mEventData->mUpdateRightSelectionPosition = true;
615 } // end ( HANDLE_PRESSED == state )
616 else if( ( HANDLE_RELEASED == state ) ||
617 handleStopScrolling )
619 CharacterIndex handlePosition = 0u;
620 if( handleStopScrolling )
622 // The event.p2 and event.p3 are in decorator coords. Need to transforms to text coords.
623 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
624 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
626 handlePosition = GetClosestCursorIndex( xPosition, yPosition );
629 if( Event::GRAB_HANDLE_EVENT == event.type )
631 mEventData->mUpdateCursorPosition = true;
633 ChangeState( EventData::EDITING_WITH_POPUP );
635 if( handleStopScrolling )
637 mEventData->mScrollAfterUpdatePosition = mEventData->mPrimaryCursorPosition != handlePosition;
638 mEventData->mPrimaryCursorPosition = handlePosition;
641 else if( Event::LEFT_SELECTION_HANDLE_EVENT == event.type )
643 ChangeState( EventData::SELECTING );
645 if( handleStopScrolling )
647 mEventData->mUpdateLeftSelectionPosition = ( mEventData->mLeftSelectionPosition != handlePosition ) && ( mEventData->mRightSelectionPosition != handlePosition);
648 mEventData->mScrollAfterUpdatePosition = mEventData->mUpdateLeftSelectionPosition;
650 if( mEventData->mUpdateLeftSelectionPosition )
652 mEventData->mLeftSelectionPosition = handlePosition;
654 RepositionSelectionHandles( mEventData->mLeftSelectionPosition,
655 mEventData->mRightSelectionPosition );
659 else if( Event::RIGHT_SELECTION_HANDLE_EVENT == event.type )
661 ChangeState( EventData::SELECTING );
663 if( handleStopScrolling )
665 mEventData->mUpdateRightSelectionPosition = ( mEventData->mRightSelectionPosition != handlePosition ) && ( mEventData->mLeftSelectionPosition != handlePosition );
666 mEventData->mScrollAfterUpdatePosition = mEventData->mUpdateRightSelectionPosition;
668 if( mEventData->mUpdateRightSelectionPosition )
670 mEventData->mRightSelectionPosition = handlePosition;
671 RepositionSelectionHandles( mEventData->mLeftSelectionPosition,
672 mEventData->mRightSelectionPosition );
677 mEventData->mDecoratorUpdated = true;
678 } // end ( ( HANDLE_RELEASED == state ) || ( HANDLE_STOP_SCROLLING == state ) )
679 else if( HANDLE_SCROLLING == state )
681 const float xSpeed = event.p2.mFloat;
682 const Vector2& actualSize = mVisualModel->GetActualSize();
683 const Vector2 currentScrollPosition = mEventData->mScrollPosition;
685 mEventData->mScrollPosition.x += xSpeed;
687 ClampHorizontalScroll( actualSize );
689 if( Vector2::ZERO == ( currentScrollPosition - mEventData->mScrollPosition ) )
691 // Notify the decorator there is no more text to scroll.
692 // The decorator won't send more scroll events.
693 mEventData->mDecorator->NotifyEndOfScroll();
697 const bool scrollRightDirection = xSpeed > 0.f;
698 const bool leftSelectionHandleEvent = Event::LEFT_SELECTION_HANDLE_EVENT == event.type;
699 const bool rightSelectionHandleEvent = Event::RIGHT_SELECTION_HANDLE_EVENT == event.type;
701 if( Event::GRAB_HANDLE_EVENT == event.type )
703 ChangeState( EventData::GRAB_HANDLE_PANNING );
705 Vector2 position = mEventData->mDecorator->GetPosition( GRAB_HANDLE );
707 // Position the grag handle close to either the left or right edge.
708 position.x = scrollRightDirection ? 0.f : mControlSize.width;
710 // Get the new handle position.
711 // The grab handle's position is in decorator coords. Need to transforms to text coords.
712 const CharacterIndex handlePosition = GetClosestCursorIndex( position.x - mEventData->mScrollPosition.x - mAlignmentOffset.x,
713 position.y - mEventData->mScrollPosition.y - mAlignmentOffset.y );
715 mEventData->mUpdateCursorPosition = mEventData->mPrimaryCursorPosition != handlePosition;
716 mEventData->mScrollAfterUpdatePosition = mEventData->mUpdateCursorPosition;
717 mEventData->mPrimaryCursorPosition = handlePosition;
719 else if( leftSelectionHandleEvent || rightSelectionHandleEvent )
721 // TODO: This is recalculating the selection box every time the text is scrolled with the selection handles.
722 // Think if something can be done to save power.
724 ChangeState( EventData::SELECTION_HANDLE_PANNING );
726 Vector2 position = mEventData->mDecorator->GetPosition( leftSelectionHandleEvent ? Text::LEFT_SELECTION_HANDLE : Text::RIGHT_SELECTION_HANDLE );
728 // Position the selection handle close to either the left or right edge.
729 position.x = scrollRightDirection ? 0.f : mControlSize.width;
731 // Get the new handle position.
732 // The selection handle's position is in decorator coords. Need to transforms to text coords.
733 const CharacterIndex handlePosition = GetClosestCursorIndex( position.x - mEventData->mScrollPosition.x - mAlignmentOffset.x,
734 position.y - mEventData->mScrollPosition.y - mAlignmentOffset.y );
736 if( leftSelectionHandleEvent )
738 mEventData->mUpdateLeftSelectionPosition = handlePosition != mEventData->mLeftSelectionPosition;
739 mEventData->mLeftSelectionPosition = handlePosition;
743 mEventData->mUpdateRightSelectionPosition = handlePosition != mEventData->mRightSelectionPosition;
744 mEventData->mRightSelectionPosition = handlePosition;
747 if( mEventData->mUpdateLeftSelectionPosition || mEventData->mUpdateRightSelectionPosition )
749 RepositionSelectionHandles( mEventData->mLeftSelectionPosition,
750 mEventData->mRightSelectionPosition );
752 mEventData->mScrollAfterUpdatePosition = true;
755 mEventData->mDecoratorUpdated = true;
757 } // end ( HANDLE_SCROLLING == state )
760 void Controller::Impl::OnSelectEvent( const Event& event )
762 if( NULL == mEventData )
764 // Nothing to do if there is no text.
768 if( mEventData->mSelectionEnabled )
770 // The event.p2 and event.p3 are in decorator coords. Need to transforms to text coords.
771 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
772 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
774 const CharacterIndex leftPosition = mEventData->mLeftSelectionPosition;
775 const CharacterIndex rightPosition = mEventData->mRightSelectionPosition;
777 RepositionSelectionHandles( xPosition,
780 mEventData->mUpdateLeftSelectionPosition = leftPosition != mEventData->mLeftSelectionPosition;
781 mEventData->mUpdateRightSelectionPosition = rightPosition != mEventData->mRightSelectionPosition;
783 mEventData->mScrollAfterUpdatePosition = ( ( mEventData->mUpdateLeftSelectionPosition || mEventData->mUpdateRightSelectionPosition ) &&
784 ( mEventData->mLeftSelectionPosition != mEventData->mRightSelectionPosition ) );
788 void Controller::Impl::OnSelectAllEvent()
790 if( NULL == mEventData )
792 // Nothing to do if there is no text.
796 if( mEventData->mSelectionEnabled )
798 RepositionSelectionHandles( 0u,
799 mLogicalModel->mText.Count() );
801 mEventData->mScrollAfterUpdatePosition = true;
802 mEventData->mUpdateLeftSelectionPosition = true;
803 mEventData->mUpdateRightSelectionPosition = true;
807 void Controller::Impl::RetrieveSelection( std::string& selectedText, bool deleteAfterRetreival )
809 if( mEventData->mLeftSelectionPosition == mEventData->mRightSelectionPosition )
811 // Nothing to select if handles are in the same place.
816 //Get start and end position of selection
817 uint32_t startOfSelectedText = mEventData->mLeftSelectionPosition;
818 uint32_t lengthOfSelectedText = mEventData->mRightSelectionPosition - startOfSelectedText;
820 // Validate the start and end selection points
821 if( ( startOfSelectedText >= 0 ) && ( ( startOfSelectedText + lengthOfSelectedText ) <= mLogicalModel->mText.Count() ) )
823 //Get text as a UTF8 string
824 Vector<Character>& utf32Characters = mLogicalModel->mText;
826 Utf32ToUtf8( &utf32Characters[startOfSelectedText], lengthOfSelectedText, selectedText );
828 if ( deleteAfterRetreival ) // Only delete text if copied successfully
830 // Delete text between handles
831 Vector<Character>& currentText = mLogicalModel->mText;
833 Vector<Character>::Iterator first = currentText.Begin() + startOfSelectedText;
834 Vector<Character>::Iterator last = first + lengthOfSelectedText;
835 currentText.Erase( first, last );
837 mEventData->mPrimaryCursorPosition = mEventData->mLeftSelectionPosition;
838 mEventData->mScrollAfterDelete = true;
839 mEventData->mDecoratorUpdated = true;
843 void Controller::Impl::ShowClipboard()
847 mClipboard.ShowClipboard();
851 void Controller::Impl::HideClipboard()
855 mClipboard.HideClipboard();
859 bool Controller::Impl::CopyStringToClipboard( std::string& source )
861 //Send string to clipboard
862 return ( mClipboard && mClipboard.SetItem( source ) );
865 void Controller::Impl::SendSelectionToClipboard( bool deleteAfterSending )
867 std::string selectedText;
868 RetrieveSelection( selectedText, deleteAfterSending );
869 CopyStringToClipboard( selectedText );
870 ChangeState( EventData::EDITING );
873 void Controller::Impl::GetTextFromClipboard( unsigned int itemIndex, std::string& retreivedString )
877 retreivedString = mClipboard.GetItem( itemIndex );
881 void Controller::Impl::RepositionSelectionHandles( CharacterIndex selectionStart, CharacterIndex selectionEnd )
883 if( selectionStart == selectionEnd )
885 // Nothing to select if handles are in the same place.
889 mEventData->mDecorator->ClearHighlights();
891 mEventData->mLeftSelectionPosition = selectionStart;
892 mEventData->mRightSelectionPosition = selectionEnd;
894 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
895 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
896 const GlyphInfo* const glyphsBuffer = mVisualModel->mGlyphs.Begin();
897 const Vector2* const positionsBuffer = mVisualModel->mGlyphPositions.Begin();
899 // TODO: Better algorithm to create the highlight box.
902 const Vector<LineRun>& lines = mVisualModel->mLines;
903 const LineRun& firstLine = *lines.Begin();
904 const float height = firstLine.ascender + -firstLine.descender;
906 const bool indicesSwapped = ( selectionStart > selectionEnd );
909 std::swap( selectionStart, selectionEnd );
912 GlyphIndex glyphStart = *( charactersToGlyphBuffer + selectionStart );
913 GlyphIndex glyphEnd = *( charactersToGlyphBuffer + ( selectionEnd - 1u ) ) + *( glyphsPerCharacterBuffer + ( selectionEnd - 1u ) ) - 1u;
915 mEventData->mDecorator->SwapSelectionHandlesEnabled( firstLine.direction != indicesSwapped );
917 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
919 for( GlyphIndex index = glyphStart; index <= glyphEnd; ++index )
921 const GlyphInfo& glyph = *( glyphsBuffer + index );
922 const Vector2& position = *( positionsBuffer + index );
924 const float xPosition = position.x - glyph.xBearing + offset.x;
925 mEventData->mDecorator->AddHighlight( xPosition, offset.y, xPosition + glyph.advance, height );
928 CursorInfo primaryCursorInfo;
929 GetCursorPosition( mEventData->mLeftSelectionPosition,
932 CursorInfo secondaryCursorInfo;
933 GetCursorPosition( mEventData->mRightSelectionPosition,
934 secondaryCursorInfo );
936 const Vector2 primaryPosition = primaryCursorInfo.primaryPosition + offset;
937 const Vector2 secondaryPosition = secondaryCursorInfo.primaryPosition + offset;
939 mEventData->mDecorator->SetPosition( LEFT_SELECTION_HANDLE, primaryPosition.x, primaryPosition.y, primaryCursorInfo.lineHeight );
941 mEventData->mDecorator->SetPosition( RIGHT_SELECTION_HANDLE, secondaryPosition.x, secondaryPosition.y, secondaryCursorInfo.lineHeight );
943 // Set the flag to update the decorator.
944 mEventData->mDecoratorUpdated = true;
947 void Controller::Impl::RepositionSelectionHandles( float visualX, float visualY )
949 if( NULL == mEventData )
951 // Nothing to do if there is no text input.
955 if( IsShowingPlaceholderText() )
957 // Nothing to do if there is the place-holder text.
961 const Length numberOfGlyphs = mVisualModel->mGlyphs.Count();
962 const Length numberOfLines = mVisualModel->mLines.Count();
963 if( 0 == numberOfGlyphs ||
966 // Nothing to do if there is no text.
970 // Find which word was selected
971 CharacterIndex selectionStart( 0 );
972 CharacterIndex selectionEnd( 0 );
973 FindSelectionIndices( visualX, visualY, selectionStart, selectionEnd );
974 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "%p selectionStart %d selectionEnd %d\n", this, selectionStart, selectionEnd );
976 if( selectionStart == selectionEnd )
978 ChangeState( EventData::EDITING );
979 // Nothing to select. i.e. a white space, out of bounds
983 RepositionSelectionHandles( selectionStart, selectionEnd );
986 void Controller::Impl::SetPopupButtons()
989 * Sets the Popup buttons to be shown depending on State.
991 * If SELECTING : CUT & COPY + ( PASTE & CLIPBOARD if content available to paste )
993 * If EDITING_WITH_POPUP : SELECT & SELECT_ALL
996 TextSelectionPopup::Buttons buttonsToShow = TextSelectionPopup::NONE;
998 if ( ( EventData::SELECTING == mEventData->mState ) || ( EventData::SELECTION_CHANGED == mEventData->mState ) )
1000 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 if ( !mEventData->mAllTextSelected )
1010 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::SELECT_ALL ) );
1013 else if ( EventData::EDITING_WITH_POPUP == mEventData->mState )
1015 if ( mLogicalModel->mText.Count() && !IsShowingPlaceholderText())
1017 buttonsToShow = TextSelectionPopup::Buttons( TextSelectionPopup::SELECT | TextSelectionPopup::SELECT_ALL );
1020 if ( !IsClipboardEmpty() )
1022 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
1023 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::CLIPBOARD ) );
1027 mEventData->mDecorator->SetEnabledPopupButtons( buttonsToShow );
1030 void Controller::Impl::ChangeState( EventData::State newState )
1032 if( NULL == mEventData )
1034 // Nothing to do if there is no text input.
1038 if( mEventData->mState != newState )
1040 mEventData->mState = newState;
1042 if( EventData::INACTIVE == mEventData->mState )
1044 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
1045 mEventData->mDecorator->StopCursorBlink();
1046 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1047 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1048 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1049 mEventData->mDecorator->SetPopupActive( false );
1050 mEventData->mDecoratorUpdated = true;
1053 else if ( EventData::INTERRUPTED == mEventData->mState)
1055 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1056 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1057 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1058 mEventData->mDecorator->SetPopupActive( false );
1059 mEventData->mDecoratorUpdated = true;
1062 else if ( EventData::SELECTING == mEventData->mState )
1064 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
1065 mEventData->mDecorator->StopCursorBlink();
1066 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1067 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true );
1068 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true );
1069 if( mEventData->mGrabHandlePopupEnabled )
1072 mEventData->mDecorator->SetPopupActive( true );
1074 mEventData->mDecoratorUpdated = true;
1076 else if ( EventData::SELECTION_CHANGED == mEventData->mState )
1078 if( mEventData->mGrabHandlePopupEnabled )
1081 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
1082 mEventData->mDecorator->SetPopupActive( true );
1084 mEventData->mDecoratorUpdated = true;
1086 else if( EventData::EDITING == mEventData->mState )
1088 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1089 if( mEventData->mCursorBlinkEnabled )
1091 mEventData->mDecorator->StartCursorBlink();
1093 // Grab handle is not shown until a tap is received whilst EDITING
1094 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1095 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1096 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1097 if( mEventData->mGrabHandlePopupEnabled )
1099 mEventData->mDecorator->SetPopupActive( false );
1101 mEventData->mDecoratorUpdated = true;
1104 else if( EventData::EDITING_WITH_POPUP == mEventData->mState )
1106 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1107 if( mEventData->mCursorBlinkEnabled )
1109 mEventData->mDecorator->StartCursorBlink();
1111 if( mEventData->mSelectionEnabled )
1113 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1114 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1118 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
1120 if( mEventData->mGrabHandlePopupEnabled )
1123 mEventData->mDecorator->SetPopupActive( true );
1126 mEventData->mDecoratorUpdated = true;
1128 else if ( EventData::SELECTION_HANDLE_PANNING == mEventData->mState )
1130 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
1131 mEventData->mDecorator->StopCursorBlink();
1132 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1133 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true );
1134 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true );
1135 if( mEventData->mGrabHandlePopupEnabled )
1137 mEventData->mDecorator->SetPopupActive( false );
1139 mEventData->mDecoratorUpdated = true;
1141 else if ( EventData::GRAB_HANDLE_PANNING == mEventData->mState )
1143 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1144 if( mEventData->mCursorBlinkEnabled )
1146 mEventData->mDecorator->StartCursorBlink();
1148 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
1149 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1150 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1151 if( mEventData->mGrabHandlePopupEnabled )
1153 mEventData->mDecorator->SetPopupActive( false );
1155 mEventData->mDecoratorUpdated = true;
1160 LineIndex Controller::Impl::GetClosestLine( float y ) const
1162 float totalHeight = 0.f;
1163 LineIndex lineIndex = 0u;
1165 const Vector<LineRun>& lines = mVisualModel->mLines;
1166 for( LineIndex endLine = lines.Count();
1167 lineIndex < endLine;
1170 const LineRun& lineRun = lines[lineIndex];
1171 totalHeight += lineRun.ascender + -lineRun.descender;
1172 if( y < totalHeight )
1178 if( lineIndex == 0 )
1186 void Controller::Impl::FindSelectionIndices( float visualX, float visualY, CharacterIndex& startIndex, CharacterIndex& endIndex )
1188 CharacterIndex hitCharacter = GetClosestCursorIndex( visualX, visualY );
1189 if( hitCharacter >= mLogicalModel->mText.Count() )
1191 // Selection out of bounds.
1195 startIndex = hitCharacter;
1196 endIndex = hitCharacter;
1198 if( !TextAbstraction::IsWhiteSpace( mLogicalModel->mText[hitCharacter] ) )
1200 // Find the start and end of the text
1201 for( startIndex = hitCharacter; startIndex > 0; --startIndex )
1203 Character charCode = mLogicalModel->mText[ startIndex-1 ];
1204 if( TextAbstraction::IsWhiteSpace( charCode ) )
1209 const CharacterIndex pastTheEnd = mLogicalModel->mText.Count();
1210 for( endIndex = hitCharacter + 1u; endIndex < pastTheEnd; ++endIndex )
1212 Character charCode = mLogicalModel->mText[ endIndex ];
1213 if( TextAbstraction::IsWhiteSpace( charCode ) )
1221 CharacterIndex Controller::Impl::GetClosestCursorIndex( float visualX,
1224 if( NULL == mEventData )
1226 // Nothing to do if there is no text input.
1230 CharacterIndex logicalIndex = 0u;
1232 const Length numberOfGlyphs = mVisualModel->mGlyphs.Count();
1233 const Length numberOfLines = mVisualModel->mLines.Count();
1234 if( 0 == numberOfGlyphs ||
1235 0 == numberOfLines )
1237 return logicalIndex;
1240 // Find which line is closest
1241 const LineIndex lineIndex = GetClosestLine( visualY );
1242 const LineRun& line = mVisualModel->mLines[lineIndex];
1244 // Get the positions of the glyphs.
1245 const Vector<Vector2>& positions = mVisualModel->mGlyphPositions;
1246 const Vector2* const positionsBuffer = positions.Begin();
1248 // Get the visual to logical conversion tables.
1249 const CharacterIndex* const visualToLogicalBuffer = ( 0u != mLogicalModel->mVisualToLogicalMap.Count() ) ? mLogicalModel->mVisualToLogicalMap.Begin() : NULL;
1250 const CharacterIndex* const visualToLogicalCursorBuffer = mLogicalModel->mVisualToLogicalCursorMap.Begin();
1252 // Get the character to glyph conversion table.
1253 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
1255 // Get the glyphs per character table.
1256 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
1257 const Length* const charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
1259 // If the vector is void, there is no right to left characters.
1260 const bool hasRightToLeftCharacters = NULL != visualToLogicalBuffer;
1262 const CharacterIndex startCharacter = line.characterRun.characterIndex;
1263 const CharacterIndex endCharacter = line.characterRun.characterIndex + line.characterRun.numberOfCharacters;
1264 DALI_ASSERT_DEBUG( endCharacter <= mLogicalModel->mText.Count() && "Invalid line info" );
1266 // Whether there is a hit on a glyph.
1267 bool matched = false;
1269 // Traverses glyphs in visual order. To do that use the visual to logical conversion table.
1270 CharacterIndex visualIndex = startCharacter;
1271 for( ; !matched && ( visualIndex < endCharacter ); ++visualIndex )
1273 // The character in logical order.
1274 const CharacterIndex characterLogicalOrderIndex = hasRightToLeftCharacters ? *( visualToLogicalBuffer + visualIndex ) : visualIndex;
1276 // Get the script of the character.
1277 const Script script = mLogicalModel->GetScript( characterLogicalOrderIndex );
1279 // The first glyph for that character in logical order.
1280 const GlyphIndex glyphLogicalOrderIndex = *( charactersToGlyphBuffer + characterLogicalOrderIndex );
1281 // The number of glyphs for that character
1282 const Length numberOfGlyphs = *( glyphsPerCharacterBuffer + characterLogicalOrderIndex );
1284 // Get the metrics for the group of glyphs.
1285 GlyphMetrics glyphMetrics;
1286 GetGlyphsMetrics( glyphLogicalOrderIndex,
1292 const Vector2& position = *( positionsBuffer + glyphLogicalOrderIndex );
1294 // Prevents to jump the whole Latin ligatures like fi, ff, ...
1295 const Length numberOfCharactersInLigature = ( TextAbstraction::LATIN == script ) ? *( charactersPerGlyphBuffer + glyphLogicalOrderIndex ) : 1u;
1296 const float glyphAdvance = glyphMetrics.advance / static_cast<float>( numberOfCharactersInLigature );
1298 for( GlyphIndex index = 0u; !matched && ( index < numberOfCharactersInLigature ); ++index )
1300 // Find the mid-point of the area containing the glyph
1301 const float glyphCenter = -glyphMetrics.xBearing + position.x + ( static_cast<float>( index ) + 0.5f ) * glyphAdvance;
1303 if( visualX < glyphCenter )
1305 visualIndex += index;
1317 // Return the logical position of the cursor in characters.
1321 visualIndex = endCharacter;
1324 logicalIndex = hasRightToLeftCharacters ? *( visualToLogicalCursorBuffer + visualIndex ) : visualIndex;
1325 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "%p closest visualIndex %d logicalIndex %d\n", this, visualIndex, logicalIndex );
1327 return logicalIndex;
1330 void Controller::Impl::GetCursorPosition( CharacterIndex logical,
1331 CursorInfo& cursorInfo )
1333 // TODO: Check for multiline with \n, etc...
1335 // Check if the logical position is the first or the last one of the text.
1336 const bool isFirstPosition = 0u == logical;
1337 const bool isLastPosition = mLogicalModel->mText.Count() == logical;
1339 if( isFirstPosition && isLastPosition )
1341 // There is zero characters. Get the default font.
1343 FontId defaultFontId = 0u;
1344 if( NULL == mFontDefaults )
1346 defaultFontId = mFontClient.GetFontId( EMPTY_STRING,
1351 defaultFontId = mFontDefaults->GetFontId( mFontClient );
1354 Text::FontMetrics fontMetrics;
1355 mFontClient.GetFontMetrics( defaultFontId, fontMetrics );
1357 cursorInfo.lineHeight = fontMetrics.ascender - fontMetrics.descender;
1358 cursorInfo.primaryCursorHeight = cursorInfo.lineHeight;
1360 cursorInfo.primaryPosition.x = 1.f;
1361 cursorInfo.primaryPosition.y = 0.f;
1363 // Nothing else to do.
1367 // Get the previous logical index.
1368 const CharacterIndex previousLogical = isFirstPosition ? 0u : logical - 1u;
1370 // Decrease the logical index if it's the last one.
1371 if( isLastPosition )
1376 // Get the direction of the character and the previous one.
1377 const CharacterDirection* const modelCharacterDirectionsBuffer = ( 0u != mLogicalModel->mCharacterDirections.Count() ) ? mLogicalModel->mCharacterDirections.Begin() : NULL;
1379 CharacterDirection isCurrentRightToLeft = false;
1380 CharacterDirection isPreviousRightToLeft = false;
1381 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
1383 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + logical );
1384 isPreviousRightToLeft = *( modelCharacterDirectionsBuffer + previousLogical );
1387 // Get the line where the character is laid-out.
1388 const LineRun* modelLines = mVisualModel->mLines.Begin();
1390 const LineIndex lineIndex = mVisualModel->GetLineOfCharacter( logical );
1391 const LineRun& line = *( modelLines + lineIndex );
1393 // Get the paragraph's direction.
1394 const CharacterDirection isRightToLeftParagraph = line.direction;
1396 // Check whether there is an alternative position:
1398 cursorInfo.isSecondaryCursor = ( isCurrentRightToLeft != isPreviousRightToLeft ) ||
1399 ( isLastPosition && ( isRightToLeftParagraph != isCurrentRightToLeft ) );
1401 // Set the line height.
1402 cursorInfo.lineHeight = line.ascender + -line.descender;
1404 // Convert the cursor position into the glyph position.
1405 CharacterIndex characterIndex = logical;
1406 if( cursorInfo.isSecondaryCursor &&
1407 ( isRightToLeftParagraph != isCurrentRightToLeft ) )
1409 characterIndex = previousLogical;
1412 const GlyphIndex currentGlyphIndex = *( mVisualModel->mCharactersToGlyph.Begin() + characterIndex );
1413 const Length numberOfGlyphs = *( mVisualModel->mGlyphsPerCharacter.Begin() + characterIndex );
1414 const Length numberOfCharacters = *( mVisualModel->mCharactersPerGlyph.Begin() +currentGlyphIndex );
1416 // Get the metrics for the group of glyphs.
1417 GlyphMetrics glyphMetrics;
1418 GetGlyphsMetrics( currentGlyphIndex,
1424 float interGlyphAdvance = 0.f;
1425 if( !isLastPosition &&
1426 ( numberOfCharacters > 1u ) )
1428 const CharacterIndex firstIndex = *( mVisualModel->mGlyphsToCharacters.Begin() + currentGlyphIndex );
1429 interGlyphAdvance = static_cast<float>( characterIndex - firstIndex ) * glyphMetrics.advance / static_cast<float>( numberOfCharacters );
1432 // Get the glyph position and x bearing.
1433 const Vector2& currentPosition = *( mVisualModel->mGlyphPositions.Begin() + currentGlyphIndex );
1435 // Set the cursor's height.
1436 cursorInfo.primaryCursorHeight = glyphMetrics.fontHeight;
1438 // Set the position.
1439 cursorInfo.primaryPosition.x = -glyphMetrics.xBearing + currentPosition.x + ( isCurrentRightToLeft ? glyphMetrics.advance : interGlyphAdvance );
1440 cursorInfo.primaryPosition.y = line.ascender - glyphMetrics.ascender;
1442 if( isLastPosition )
1444 // The position of the cursor after the last character needs special
1445 // care depending on its direction and the direction of the paragraph.
1447 if( cursorInfo.isSecondaryCursor )
1449 // Need to find the first character after the last character with the paragraph's direction.
1450 // i.e l0 l1 l2 r0 r1 should find r0.
1452 // TODO: check for more than one line!
1453 characterIndex = isRightToLeftParagraph ? line.characterRun.characterIndex : line.characterRun.characterIndex + line.characterRun.numberOfCharacters - 1u;
1454 characterIndex = mLogicalModel->GetLogicalCharacterIndex( characterIndex );
1456 const GlyphIndex glyphIndex = *( mVisualModel->mCharactersToGlyph.Begin() + characterIndex );
1457 const Length numberOfGlyphs = *( mVisualModel->mGlyphsPerCharacter.Begin() + characterIndex );
1459 const Vector2& position = *( mVisualModel->mGlyphPositions.Begin() + glyphIndex );
1461 // Get the metrics for the group of glyphs.
1462 GlyphMetrics glyphMetrics;
1463 GetGlyphsMetrics( glyphIndex,
1469 cursorInfo.primaryPosition.x = -glyphMetrics.xBearing + position.x + ( isRightToLeftParagraph ? 0.f : glyphMetrics.advance );
1471 cursorInfo.primaryPosition.y = line.ascender - glyphMetrics.ascender;
1475 if( !isCurrentRightToLeft )
1477 cursorInfo.primaryPosition.x += glyphMetrics.advance;
1481 cursorInfo.primaryPosition.x -= glyphMetrics.advance;
1486 // Set the alternative cursor position.
1487 if( cursorInfo.isSecondaryCursor )
1489 // Convert the cursor position into the glyph position.
1490 const CharacterIndex previousCharacterIndex = ( ( isRightToLeftParagraph != isCurrentRightToLeft ) ? logical : previousLogical );
1491 const GlyphIndex previousGlyphIndex = *( mVisualModel->mCharactersToGlyph.Begin() + previousCharacterIndex );
1492 const Length numberOfGlyphs = *( mVisualModel->mGlyphsPerCharacter.Begin() + previousCharacterIndex );
1494 // Get the glyph position.
1495 const Vector2& previousPosition = *( mVisualModel->mGlyphPositions.Begin() + previousGlyphIndex );
1497 // Get the metrics for the group of glyphs.
1498 GlyphMetrics glyphMetrics;
1499 GetGlyphsMetrics( previousGlyphIndex,
1505 // Set the cursor position and height.
1506 cursorInfo.secondaryPosition.x = -glyphMetrics.xBearing + previousPosition.x + ( ( ( isLastPosition && !isCurrentRightToLeft ) ||
1507 ( !isLastPosition && isCurrentRightToLeft ) ) ? glyphMetrics.advance : 0.f );
1509 cursorInfo.secondaryCursorHeight = 0.5f * glyphMetrics.fontHeight;
1511 cursorInfo.secondaryPosition.y = cursorInfo.lineHeight - cursorInfo.secondaryCursorHeight - line.descender - ( glyphMetrics.fontHeight - glyphMetrics.ascender );
1513 // Update the primary cursor height as well.
1514 cursorInfo.primaryCursorHeight *= 0.5f;
1518 CharacterIndex Controller::Impl::CalculateNewCursorIndex( CharacterIndex index ) const
1520 if( NULL == mEventData )
1522 // Nothing to do if there is no text input.
1526 CharacterIndex cursorIndex = mEventData->mPrimaryCursorPosition;
1528 const Script script = mLogicalModel->GetScript( index );
1529 const GlyphIndex* charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
1530 const Length* charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
1532 Length numberOfCharacters = 0u;
1533 if( TextAbstraction::LATIN == script )
1535 // Prevents to jump the whole Latin ligatures like fi, ff, ...
1536 numberOfCharacters = 1u;
1540 GlyphIndex glyphIndex = *( charactersToGlyphBuffer + index );
1541 numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
1543 while( 0u == numberOfCharacters )
1545 numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
1550 if( index < mEventData->mPrimaryCursorPosition )
1552 cursorIndex -= numberOfCharacters;
1556 cursorIndex += numberOfCharacters;
1562 void Controller::Impl::UpdateCursorPosition()
1564 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "-->Controller::UpdateCursorPosition %p\n", this );
1565 if( NULL == mEventData )
1567 // Nothing to do if there is no text input.
1568 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition no event data\n" );
1572 if( IsShowingPlaceholderText() )
1574 // Do not want to use the place-holder text to set the cursor position.
1576 // Use the line's height of the font's family set to set the cursor's size.
1577 // If there is no font's family set, use the default font.
1578 // Use the current alignment to place the cursor at the beginning, center or end of the box.
1580 float lineHeight = 0.f;
1582 FontId defaultFontId = 0u;
1583 if( NULL == mFontDefaults )
1585 defaultFontId = mFontClient.GetFontId( EMPTY_STRING,
1590 defaultFontId = mFontDefaults->GetFontId( mFontClient );
1593 Text::FontMetrics fontMetrics;
1594 mFontClient.GetFontMetrics( defaultFontId, fontMetrics );
1596 lineHeight = fontMetrics.ascender - fontMetrics.descender;
1599 Vector2 cursorPosition;
1601 switch( mLayoutEngine.GetHorizontalAlignment() )
1603 case LayoutEngine::HORIZONTAL_ALIGN_BEGIN:
1605 cursorPosition.x = 1.f;
1608 case LayoutEngine::HORIZONTAL_ALIGN_CENTER:
1610 cursorPosition.x = floor( 0.5f * mControlSize.width );
1613 case LayoutEngine::HORIZONTAL_ALIGN_END:
1615 cursorPosition.x = mControlSize.width;
1620 switch( mLayoutEngine.GetVerticalAlignment() )
1622 case LayoutEngine::VERTICAL_ALIGN_TOP:
1624 cursorPosition.y = 0.f;
1627 case LayoutEngine::VERTICAL_ALIGN_CENTER:
1629 cursorPosition.y = floorf( 0.5f * ( mControlSize.height - lineHeight ) );
1632 case LayoutEngine::VERTICAL_ALIGN_BOTTOM:
1634 cursorPosition.y = mControlSize.height - lineHeight;
1639 mEventData->mDecorator->SetPosition( PRIMARY_CURSOR,
1647 CursorInfo cursorInfo;
1648 GetCursorPosition( mEventData->mPrimaryCursorPosition,
1651 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
1652 const Vector2 cursorPosition = cursorInfo.primaryPosition + offset;
1654 // Sets the cursor position.
1655 mEventData->mDecorator->SetPosition( PRIMARY_CURSOR,
1658 cursorInfo.primaryCursorHeight,
1659 cursorInfo.lineHeight );
1660 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Primary cursor position: %f,%f\n", cursorPosition.x, cursorPosition.y );
1662 // Sets the grab handle position.
1663 mEventData->mDecorator->SetPosition( GRAB_HANDLE,
1666 cursorInfo.lineHeight );
1668 if( cursorInfo.isSecondaryCursor )
1670 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_BOTH );
1671 mEventData->mDecorator->SetPosition( SECONDARY_CURSOR,
1672 cursorInfo.secondaryPosition.x + offset.x,
1673 cursorInfo.secondaryPosition.y + offset.y,
1674 cursorInfo.secondaryCursorHeight,
1675 cursorInfo.lineHeight );
1676 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Secondary cursor position: %f,%f\n", cursorInfo.secondaryPosition.x + offset.x, cursorInfo.secondaryPosition.y + offset.y );
1680 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1683 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition\n" );
1686 void Controller::Impl::UpdateSelectionHandle( HandleType handleType )
1688 if( ( LEFT_SELECTION_HANDLE != handleType ) &&
1689 ( RIGHT_SELECTION_HANDLE != handleType ) )
1694 const bool leftSelectionHandle = LEFT_SELECTION_HANDLE == handleType;
1695 const CharacterIndex index = leftSelectionHandle ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition;
1697 CursorInfo cursorInfo;
1698 GetCursorPosition( index,
1701 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
1702 const Vector2 cursorPosition = cursorInfo.primaryPosition + offset;
1704 // Sets the grab handle position.
1705 mEventData->mDecorator->SetPosition( handleType,
1708 cursorInfo.lineHeight );
1710 // If selection handle at start of the text and other at end of the text then all text is selected.
1711 const CharacterIndex startOfSelection = std::min( mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition );
1712 const CharacterIndex endOfSelection = std::max ( mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition );
1713 mEventData->mAllTextSelected = ( startOfSelection == 0 ) && ( endOfSelection == mLogicalModel->mText.Count() );
1716 void Controller::Impl::ClampHorizontalScroll( const Vector2& actualSize )
1718 // Clamp between -space & 0 (and the text alignment).
1719 if( actualSize.width > mControlSize.width )
1721 const float space = ( actualSize.width - mControlSize.width ) + mAlignmentOffset.x;
1722 mEventData->mScrollPosition.x = ( mEventData->mScrollPosition.x < -space ) ? -space : mEventData->mScrollPosition.x;
1723 mEventData->mScrollPosition.x = ( mEventData->mScrollPosition.x > -mAlignmentOffset.x ) ? -mAlignmentOffset.x : mEventData->mScrollPosition.x;
1725 mEventData->mDecoratorUpdated = true;
1729 mEventData->mScrollPosition.x = 0.f;
1733 void Controller::Impl::ClampVerticalScroll( const Vector2& actualSize )
1735 // Clamp between -space & 0 (and the text alignment).
1736 if( actualSize.height > mControlSize.height )
1738 const float space = ( actualSize.height - mControlSize.height ) + mAlignmentOffset.y;
1739 mEventData->mScrollPosition.y = ( mEventData->mScrollPosition.y < -space ) ? -space : mEventData->mScrollPosition.y;
1740 mEventData->mScrollPosition.y = ( mEventData->mScrollPosition.y > -mAlignmentOffset.y ) ? -mAlignmentOffset.y : mEventData->mScrollPosition.y;
1742 mEventData->mDecoratorUpdated = true;
1746 mEventData->mScrollPosition.y = 0.f;
1750 void Controller::Impl::ScrollToMakePositionVisible( const Vector2& position )
1753 bool updateDecorator = false;
1754 if( position.x < 0.f )
1756 offset.x = -position.x;
1757 mEventData->mScrollPosition.x += offset.x;
1758 updateDecorator = true;
1760 else if( position.x > mControlSize.width )
1762 offset.x = mControlSize.width - position.x;
1763 mEventData->mScrollPosition.x += offset.x;
1764 updateDecorator = true;
1767 if( updateDecorator && mEventData->mDecorator )
1769 mEventData->mDecorator->UpdatePositions( offset );
1772 // TODO : calculate the vertical scroll.
1775 void Controller::Impl::ScrollTextToMatchCursor()
1777 // Get the current cursor position in decorator coords.
1778 const Vector2& currentCursorPosition = mEventData->mDecorator->GetPosition( PRIMARY_CURSOR );
1780 // Calculate the new cursor position.
1781 CursorInfo cursorInfo;
1782 GetCursorPosition( mEventData->mPrimaryCursorPosition,
1785 // Calculate the offset to match the cursor position before the character was deleted.
1786 mEventData->mScrollPosition.x = currentCursorPosition.x - cursorInfo.primaryPosition.x - mAlignmentOffset.x;
1788 ClampHorizontalScroll( mVisualModel->GetActualSize() );
1790 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
1791 const Vector2 cursorPosition = cursorInfo.primaryPosition + offset;
1793 // Sets the cursor position.
1794 mEventData->mDecorator->SetPosition( PRIMARY_CURSOR,
1797 cursorInfo.primaryCursorHeight,
1798 cursorInfo.lineHeight );
1800 // Sets the grab handle position.
1801 mEventData->mDecorator->SetPosition( GRAB_HANDLE,
1804 cursorInfo.lineHeight );
1806 if( cursorInfo.isSecondaryCursor )
1808 mEventData->mDecorator->SetPosition( SECONDARY_CURSOR,
1809 cursorInfo.secondaryPosition.x + offset.x,
1810 cursorInfo.secondaryPosition.y + offset.y,
1811 cursorInfo.secondaryCursorHeight,
1812 cursorInfo.lineHeight );
1813 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Secondary cursor position: %f,%f\n", cursorInfo.secondaryPosition.x + offset.x, cursorInfo.secondaryPosition.y + offset.y );
1816 // Set which cursors are active according the state.
1817 if( ( EventData::EDITING == mEventData->mState ) ||
1818 ( EventData::EDITING_WITH_POPUP == mEventData->mState ) ||
1819 ( EventData::GRAB_HANDLE_PANNING == mEventData->mState ) )
1821 if( cursorInfo.isSecondaryCursor )
1823 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_BOTH );
1827 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1832 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
1836 void Controller::Impl::RequestRelayout()
1838 mControlInterface.RequestTextRelayout();
1843 } // namespace Toolkit