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::PAN_EVENT:
179 case Event::GRAB_HANDLE_EVENT:
180 case Event::LEFT_SELECTION_HANDLE_EVENT:
181 case Event::RIGHT_SELECTION_HANDLE_EVENT: // Fall through
183 OnHandleEvent( *iter );
188 OnSelectEvent( *iter );
191 case Event::SELECT_ALL:
200 // The cursor must also be repositioned after inserts into the model
201 if( mEventData->mUpdateCursorPosition )
203 // Updates the cursor position and scrolls the text to make it visible.
205 UpdateCursorPosition();
207 if( mEventData->mScrollAfterUpdatePosition )
209 const Vector2& primaryCursorPosition = mEventData->mDecorator->GetPosition( PRIMARY_CURSOR );
211 ScrollToMakePositionVisible( primaryCursorPosition );
212 mEventData->mScrollAfterUpdatePosition = false;
215 mEventData->mDecoratorUpdated = true;
216 mEventData->mUpdateCursorPosition = false;
218 else if( mEventData->mScrollAfterDelete )
220 ScrollTextToMatchCursor();
221 mEventData->mDecoratorUpdated = true;
222 mEventData->mScrollAfterDelete = false;
226 bool leftScroll = false;
227 bool rightScroll = false;
229 if( mEventData->mUpdateLeftSelectionPosition )
231 UpdateSelectionHandle( LEFT_SELECTION_HANDLE );
233 if( mEventData->mScrollAfterUpdatePosition )
235 const Vector2& leftHandlePosition = mEventData->mDecorator->GetPosition( LEFT_SELECTION_HANDLE );
237 ScrollToMakePositionVisible( leftHandlePosition );
242 mEventData->mDecoratorUpdated = true;
243 mEventData->mUpdateLeftSelectionPosition = false;
246 if( mEventData->mUpdateRightSelectionPosition )
248 UpdateSelectionHandle( RIGHT_SELECTION_HANDLE );
250 if( mEventData->mScrollAfterUpdatePosition )
252 const Vector2& rightHandlePosition = mEventData->mDecorator->GetPosition( RIGHT_SELECTION_HANDLE );
254 ScrollToMakePositionVisible( rightHandlePosition );
259 mEventData->mDecoratorUpdated = true;
260 mEventData->mUpdateRightSelectionPosition = false;
263 if( leftScroll || rightScroll )
265 mEventData->mScrollAfterUpdatePosition = false;
269 mEventData->mEventQueue.clear();
271 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::ProcessInputEvents\n" );
273 const bool decoratorUpdated = mEventData->mDecoratorUpdated;
274 mEventData->mDecoratorUpdated = false;
276 return decoratorUpdated;
279 void Controller::Impl::UpdateModel( OperationsMask operationsRequired )
281 // Calculate the operations to be done.
282 const OperationsMask operations = static_cast<OperationsMask>( mOperationsPending & operationsRequired );
284 Vector<Character>& utf32Characters = mLogicalModel->mText;
286 const Length numberOfCharacters = utf32Characters.Count();
288 Vector<LineBreakInfo>& lineBreakInfo = mLogicalModel->mLineBreakInfo;
289 if( GET_LINE_BREAKS & operations )
291 // Retrieves the line break info. The line break info is used to split the text in 'paragraphs' to
292 // calculate the bidirectional info for each 'paragraph'.
293 // It's also used to layout the text (where it should be a new line) or to shape the text (text in different lines
294 // is not shaped together).
295 lineBreakInfo.Resize( numberOfCharacters, TextAbstraction::LINE_NO_BREAK );
297 SetLineBreakInfo( utf32Characters,
301 Vector<WordBreakInfo>& wordBreakInfo = mLogicalModel->mWordBreakInfo;
302 if( GET_WORD_BREAKS & operations )
304 // Retrieves the word break info. The word break info is used to layout the text (where to wrap the text in lines).
305 wordBreakInfo.Resize( numberOfCharacters, TextAbstraction::WORD_NO_BREAK );
307 SetWordBreakInfo( utf32Characters,
311 const bool getScripts = GET_SCRIPTS & operations;
312 const bool validateFonts = VALIDATE_FONTS & operations;
314 Vector<ScriptRun>& scripts = mLogicalModel->mScriptRuns;
315 Vector<FontRun>& validFonts = mLogicalModel->mFontRuns;
317 if( getScripts || validateFonts )
319 // Validates the fonts assigned by the application or assigns default ones.
320 // It makes sure all the characters are going to be rendered by the correct font.
321 MultilanguageSupport multilanguageSupport = MultilanguageSupport::Get();
325 // Retrieves the scripts used in the text.
326 multilanguageSupport.SetScripts( utf32Characters,
333 if( 0u == validFonts.Count() )
335 // Copy the requested font defaults received via the property system.
336 // These may not be valid i.e. may not contain glyphs for the necessary scripts.
337 GetDefaultFonts( validFonts, numberOfCharacters );
340 // Validates the fonts. If there is a character with no assigned font it sets a default one.
341 // After this call, fonts are validated.
342 multilanguageSupport.ValidateFonts( utf32Characters,
348 Vector<Character> mirroredUtf32Characters;
349 bool textMirrored = false;
350 if( BIDI_INFO & operations )
352 // Count the number of LINE_NO_BREAK to reserve some space for the vector of paragraph's
353 // bidirectional info.
355 Length numberOfParagraphs = 0u;
357 const TextAbstraction::LineBreakInfo* lineBreakInfoBuffer = lineBreakInfo.Begin();
358 for( Length index = 0u; index < numberOfCharacters; ++index )
360 if( TextAbstraction::LINE_NO_BREAK == *( lineBreakInfoBuffer + index ) )
362 ++numberOfParagraphs;
366 Vector<BidirectionalParagraphInfoRun>& bidirectionalInfo = mLogicalModel->mBidirectionalParagraphInfo;
367 bidirectionalInfo.Reserve( numberOfParagraphs );
369 // Calculates the bidirectional info for the whole paragraph if it contains right to left scripts.
370 SetBidirectionalInfo( utf32Characters,
375 if( 0u != bidirectionalInfo.Count() )
377 // This paragraph has right to left text. Some characters may need to be mirrored.
378 // TODO: consider if the mirrored string can be stored as well.
380 textMirrored = GetMirroredText( utf32Characters, mirroredUtf32Characters );
382 // Only set the character directions if there is right to left characters.
383 Vector<CharacterDirection>& directions = mLogicalModel->mCharacterDirections;
384 directions.Resize( numberOfCharacters );
386 GetCharactersDirection( bidirectionalInfo,
391 // There is no right to left characters. Clear the directions vector.
392 mLogicalModel->mCharacterDirections.Clear();
397 Vector<GlyphInfo>& glyphs = mVisualModel->mGlyphs;
398 Vector<CharacterIndex>& glyphsToCharactersMap = mVisualModel->mGlyphsToCharacters;
399 Vector<Length>& charactersPerGlyph = mVisualModel->mCharactersPerGlyph;
400 if( SHAPE_TEXT & operations )
402 const Vector<Character>& textToShape = textMirrored ? mirroredUtf32Characters : utf32Characters;
404 ShapeText( textToShape,
409 glyphsToCharactersMap,
410 charactersPerGlyph );
412 // Create the 'number of glyphs' per character and the glyph to character conversion tables.
413 mVisualModel->CreateGlyphsPerCharacterTable( numberOfCharacters );
414 mVisualModel->CreateCharacterToGlyphTable( numberOfCharacters );
417 const Length numberOfGlyphs = glyphs.Count();
419 if( GET_GLYPH_METRICS & operations )
421 mFontClient.GetGlyphMetrics( glyphs.Begin(), numberOfGlyphs );
425 void Controller::Impl::GetDefaultFonts( Vector<FontRun>& fonts, Length numberOfCharacters )
430 fontRun.characterRun.characterIndex = 0;
431 fontRun.characterRun.numberOfCharacters = numberOfCharacters;
432 fontRun.fontId = mFontDefaults->GetFontId( mFontClient );
433 fontRun.isDefault = true;
435 fonts.PushBack( fontRun );
439 void Controller::Impl::OnCursorKeyEvent( const Event& event )
441 if( NULL == mEventData )
443 // Nothing to do if there is no text input.
447 int keyCode = event.p1.mInt;
449 if( Dali::DALI_KEY_CURSOR_LEFT == keyCode )
451 if( mEventData->mPrimaryCursorPosition > 0u )
453 mEventData->mPrimaryCursorPosition = CalculateNewCursorIndex( mEventData->mPrimaryCursorPosition - 1u );
456 else if( Dali::DALI_KEY_CURSOR_RIGHT == keyCode )
458 if( mLogicalModel->mText.Count() > mEventData->mPrimaryCursorPosition )
460 mEventData->mPrimaryCursorPosition = CalculateNewCursorIndex( mEventData->mPrimaryCursorPosition );
463 else if( Dali::DALI_KEY_CURSOR_UP == keyCode )
467 else if( Dali::DALI_KEY_CURSOR_DOWN == keyCode )
472 mEventData->mUpdateCursorPosition = true;
473 mEventData->mScrollAfterUpdatePosition = true;
476 void Controller::Impl::OnTapEvent( const Event& event )
478 if( NULL != mEventData )
480 const unsigned int tapCount = event.p1.mUint;
484 if( ! IsShowingPlaceholderText() )
486 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
487 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
489 mEventData->mPrimaryCursorPosition = GetClosestCursorIndex( xPosition,
494 mEventData->mPrimaryCursorPosition = 0u;
497 mEventData->mUpdateCursorPosition = true;
498 mEventData->mScrollAfterUpdatePosition = true;
503 void Controller::Impl::OnPanEvent( const Event& event )
505 if( NULL == mEventData )
507 // Nothing to do if there is no text input.
511 int state = event.p1.mInt;
513 if( Gesture::Started == state ||
514 Gesture::Continuing == state )
516 const Vector2& actualSize = mVisualModel->GetActualSize();
517 const Vector2 currentScroll = mEventData->mScrollPosition;
519 if( mEventData->mHorizontalScrollingEnabled )
521 const float displacementX = event.p2.mFloat;
522 mEventData->mScrollPosition.x += displacementX;
524 ClampHorizontalScroll( actualSize );
527 if( mEventData->mVerticalScrollingEnabled )
529 const float displacementY = event.p3.mFloat;
530 mEventData->mScrollPosition.y += displacementY;
532 ClampVerticalScroll( actualSize );
535 if( mEventData->mDecorator )
537 mEventData->mDecorator->UpdatePositions( mEventData->mScrollPosition - currentScroll );
542 void Controller::Impl::OnHandleEvent( const Event& event )
544 if( NULL == mEventData )
546 // Nothing to do if there is no text input.
550 const unsigned int state = event.p1.mUint;
551 const bool handleStopScrolling = ( HANDLE_STOP_SCROLLING == state );
553 if( HANDLE_PRESSED == state )
555 // The event.p2 and event.p3 are in decorator coords. Need to transforms to text coords.
556 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
557 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
559 const CharacterIndex handleNewPosition = GetClosestCursorIndex( xPosition, yPosition );
561 if( Event::GRAB_HANDLE_EVENT == event.type )
563 ChangeState ( EventData::GRAB_HANDLE_PANNING );
565 if( handleNewPosition != mEventData->mPrimaryCursorPosition )
567 mEventData->mPrimaryCursorPosition = handleNewPosition;
568 mEventData->mUpdateCursorPosition = true;
571 else if( Event::LEFT_SELECTION_HANDLE_EVENT == event.type )
573 ChangeState ( EventData::SELECTION_HANDLE_PANNING );
575 if( ( handleNewPosition != mEventData->mLeftSelectionPosition ) &&
576 ( handleNewPosition != mEventData->mRightSelectionPosition ) )
578 mEventData->mLeftSelectionPosition = handleNewPosition;
580 RepositionSelectionHandles( mEventData->mLeftSelectionPosition,
581 mEventData->mRightSelectionPosition );
583 mEventData->mUpdateLeftSelectionPosition = true;
586 else if( Event::RIGHT_SELECTION_HANDLE_EVENT == event.type )
588 ChangeState ( EventData::SELECTION_HANDLE_PANNING );
590 if( ( handleNewPosition != mEventData->mRightSelectionPosition ) &&
591 ( handleNewPosition != mEventData->mLeftSelectionPosition ) )
593 mEventData->mRightSelectionPosition = handleNewPosition;
595 RepositionSelectionHandles( mEventData->mLeftSelectionPosition,
596 mEventData->mRightSelectionPosition );
598 mEventData->mUpdateRightSelectionPosition = true;
601 } // end ( HANDLE_PRESSED == state )
602 else if( ( HANDLE_RELEASED == state ) ||
603 handleStopScrolling )
605 CharacterIndex handlePosition = 0u;
606 if( handleStopScrolling )
608 // The event.p2 and event.p3 are in decorator coords. Need to transforms to text coords.
609 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
610 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
612 handlePosition = GetClosestCursorIndex( xPosition, yPosition );
615 if( Event::GRAB_HANDLE_EVENT == event.type )
617 mEventData->mUpdateCursorPosition = true;
619 ChangeState( EventData::EDITING_WITH_POPUP );
621 if( handleStopScrolling )
623 mEventData->mScrollAfterUpdatePosition = mEventData->mPrimaryCursorPosition != handlePosition;
624 mEventData->mPrimaryCursorPosition = handlePosition;
627 else if( Event::LEFT_SELECTION_HANDLE_EVENT == event.type )
629 ChangeState( EventData::SELECTING );
631 if( handleStopScrolling )
633 mEventData->mUpdateLeftSelectionPosition = ( mEventData->mLeftSelectionPosition != handlePosition ) && ( mEventData->mRightSelectionPosition != handlePosition);
634 mEventData->mScrollAfterUpdatePosition = mEventData->mUpdateLeftSelectionPosition;
636 if( mEventData->mUpdateLeftSelectionPosition )
638 mEventData->mLeftSelectionPosition = handlePosition;
640 RepositionSelectionHandles( mEventData->mLeftSelectionPosition,
641 mEventData->mRightSelectionPosition );
645 else if( Event::RIGHT_SELECTION_HANDLE_EVENT == event.type )
647 ChangeState( EventData::SELECTING );
649 if( handleStopScrolling )
651 mEventData->mUpdateRightSelectionPosition = ( mEventData->mRightSelectionPosition != handlePosition ) && ( mEventData->mLeftSelectionPosition != handlePosition );
652 mEventData->mScrollAfterUpdatePosition = mEventData->mUpdateRightSelectionPosition;
654 if( mEventData->mUpdateRightSelectionPosition )
656 mEventData->mRightSelectionPosition = handlePosition;
657 RepositionSelectionHandles( mEventData->mLeftSelectionPosition,
658 mEventData->mRightSelectionPosition );
663 mEventData->mDecoratorUpdated = true;
664 } // end ( ( HANDLE_RELEASED == state ) || ( HANDLE_STOP_SCROLLING == state ) )
665 else if( HANDLE_SCROLLING == state )
667 const float xSpeed = event.p2.mFloat;
668 const Vector2& actualSize = mVisualModel->GetActualSize();
669 const Vector2 currentScrollPosition = mEventData->mScrollPosition;
671 mEventData->mScrollPosition.x += xSpeed;
673 ClampHorizontalScroll( actualSize );
675 if( Vector2::ZERO == ( currentScrollPosition - mEventData->mScrollPosition ) )
677 // Notify the decorator there is no more text to scroll.
678 // The decorator won't send more scroll events.
679 mEventData->mDecorator->NotifyEndOfScroll();
683 const bool scrollRightDirection = xSpeed > 0.f;
684 const bool leftSelectionHandleEvent = Event::LEFT_SELECTION_HANDLE_EVENT == event.type;
685 const bool rightSelectionHandleEvent = Event::RIGHT_SELECTION_HANDLE_EVENT == event.type;
687 if( Event::GRAB_HANDLE_EVENT == event.type )
689 ChangeState( EventData::GRAB_HANDLE_PANNING );
691 Vector2 position = mEventData->mDecorator->GetPosition( GRAB_HANDLE );
693 // Position the grag handle close to either the left or right edge.
694 position.x = scrollRightDirection ? 0.f : mControlSize.width;
696 // Get the new handle position.
697 // The grab handle's position is in decorator coords. Need to transforms to text coords.
698 const CharacterIndex handlePosition = GetClosestCursorIndex( position.x - mEventData->mScrollPosition.x - mAlignmentOffset.x,
699 position.y - mEventData->mScrollPosition.y - mAlignmentOffset.y );
701 mEventData->mUpdateCursorPosition = mEventData->mPrimaryCursorPosition != handlePosition;
702 mEventData->mScrollAfterUpdatePosition = mEventData->mUpdateCursorPosition;
703 mEventData->mPrimaryCursorPosition = handlePosition;
705 else if( leftSelectionHandleEvent || rightSelectionHandleEvent )
707 // TODO: This is recalculating the selection box every time the text is scrolled with the selection handles.
708 // Think if something can be done to save power.
710 ChangeState( EventData::SELECTION_HANDLE_PANNING );
712 Vector2 position = mEventData->mDecorator->GetPosition( leftSelectionHandleEvent ? Text::LEFT_SELECTION_HANDLE : Text::RIGHT_SELECTION_HANDLE );
714 // Position the selection handle close to either the left or right edge.
715 position.x = scrollRightDirection ? 0.f : mControlSize.width;
717 // Get the new handle position.
718 // The selection handle's position is in decorator coords. Need to transforms to text coords.
719 const CharacterIndex handlePosition = GetClosestCursorIndex( position.x - mEventData->mScrollPosition.x - mAlignmentOffset.x,
720 position.y - mEventData->mScrollPosition.y - mAlignmentOffset.y );
722 if( leftSelectionHandleEvent )
724 mEventData->mUpdateLeftSelectionPosition = handlePosition != mEventData->mLeftSelectionPosition;
725 mEventData->mLeftSelectionPosition = handlePosition;
729 mEventData->mUpdateRightSelectionPosition = handlePosition != mEventData->mRightSelectionPosition;
730 mEventData->mRightSelectionPosition = handlePosition;
733 if( mEventData->mUpdateLeftSelectionPosition || mEventData->mUpdateRightSelectionPosition )
735 RepositionSelectionHandles( mEventData->mLeftSelectionPosition,
736 mEventData->mRightSelectionPosition );
738 mEventData->mScrollAfterUpdatePosition = true;
741 mEventData->mDecoratorUpdated = true;
743 } // end ( HANDLE_SCROLLING == state )
746 void Controller::Impl::OnSelectEvent( const Event& event )
748 if( NULL == mEventData )
750 // Nothing to do if there is no text.
754 if( mEventData->mSelectionEnabled )
756 // The event.p2 and event.p3 are in decorator coords. Need to transforms to text coords.
757 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
758 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
760 const CharacterIndex leftPosition = mEventData->mLeftSelectionPosition;
761 const CharacterIndex rightPosition = mEventData->mRightSelectionPosition;
763 RepositionSelectionHandles( xPosition,
766 mEventData->mUpdateLeftSelectionPosition = leftPosition != mEventData->mLeftSelectionPosition;
767 mEventData->mUpdateRightSelectionPosition = rightPosition != mEventData->mRightSelectionPosition;
769 mEventData->mScrollAfterUpdatePosition = ( ( mEventData->mUpdateLeftSelectionPosition || mEventData->mUpdateRightSelectionPosition ) &&
770 ( mEventData->mLeftSelectionPosition != mEventData->mRightSelectionPosition ) );
774 void Controller::Impl::OnSelectAllEvent()
776 if( NULL == mEventData )
778 // Nothing to do if there is no text.
782 if( mEventData->mSelectionEnabled )
784 RepositionSelectionHandles( 0u,
785 mLogicalModel->mText.Count() );
787 mEventData->mScrollAfterUpdatePosition = true;
788 mEventData->mUpdateLeftSelectionPosition = true;
789 mEventData->mUpdateRightSelectionPosition = true;
793 void Controller::Impl::RetrieveSelection( std::string& selectedText, bool deleteAfterRetreival )
795 if( mEventData->mLeftSelectionPosition == mEventData->mRightSelectionPosition )
797 // Nothing to select if handles are in the same place.
802 //Get start and end position of selection
803 uint32_t startOfSelectedText = mEventData->mLeftSelectionPosition;
804 uint32_t lengthOfSelectedText = mEventData->mRightSelectionPosition - startOfSelectedText;
806 // Validate the start and end selection points
807 if( ( startOfSelectedText >= 0 ) && ( ( startOfSelectedText + lengthOfSelectedText ) <= mLogicalModel->mText.Count() ) )
809 //Get text as a UTF8 string
810 Vector<Character>& utf32Characters = mLogicalModel->mText;
812 Utf32ToUtf8( &utf32Characters[startOfSelectedText], lengthOfSelectedText, selectedText );
814 if ( deleteAfterRetreival ) // Only delete text if copied successfully
816 // Delete text between handles
817 Vector<Character>& currentText = mLogicalModel->mText;
819 Vector<Character>::Iterator first = currentText.Begin() + startOfSelectedText;
820 Vector<Character>::Iterator last = first + lengthOfSelectedText;
821 currentText.Erase( first, last );
823 mEventData->mPrimaryCursorPosition = mEventData->mLeftSelectionPosition;
824 mEventData->mScrollAfterDelete = true;
825 mEventData->mDecoratorUpdated = true;
829 void Controller::Impl::ShowClipboard()
833 mClipboard.ShowClipboard();
837 void Controller::Impl::HideClipboard()
841 mClipboard.HideClipboard();
845 bool Controller::Impl::CopyStringToClipboard( std::string& source )
847 //Send string to clipboard
848 return ( mClipboard && mClipboard.SetItem( source ) );
851 void Controller::Impl::SendSelectionToClipboard( bool deleteAfterSending )
853 std::string selectedText;
854 RetrieveSelection( selectedText, deleteAfterSending );
855 CopyStringToClipboard( selectedText );
856 ChangeState( EventData::EDITING );
859 void Controller::Impl::GetTextFromClipboard( unsigned int itemIndex, std::string& retreivedString )
863 retreivedString = mClipboard.GetItem( itemIndex );
867 void Controller::Impl::RepositionSelectionHandles( CharacterIndex selectionStart, CharacterIndex selectionEnd )
869 if( selectionStart == selectionEnd )
871 // Nothing to select if handles are in the same place.
875 mEventData->mDecorator->ClearHighlights();
877 mEventData->mLeftSelectionPosition = selectionStart;
878 mEventData->mRightSelectionPosition = selectionEnd;
880 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
881 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
882 const GlyphInfo* const glyphsBuffer = mVisualModel->mGlyphs.Begin();
883 const Vector2* const positionsBuffer = mVisualModel->mGlyphPositions.Begin();
885 // TODO: Better algorithm to create the highlight box.
888 const Vector<LineRun>& lines = mVisualModel->mLines;
889 const LineRun& firstLine = *lines.Begin();
890 const float height = firstLine.ascender + -firstLine.descender;
892 const bool indicesSwapped = ( selectionStart > selectionEnd );
895 std::swap( selectionStart, selectionEnd );
898 GlyphIndex glyphStart = *( charactersToGlyphBuffer + selectionStart );
899 GlyphIndex glyphEnd = *( charactersToGlyphBuffer + ( selectionEnd - 1u ) ) + *( glyphsPerCharacterBuffer + ( selectionEnd - 1u ) ) - 1u;
901 mEventData->mDecorator->SwapSelectionHandlesEnabled( firstLine.direction != indicesSwapped );
903 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
905 for( GlyphIndex index = glyphStart; index <= glyphEnd; ++index )
907 const GlyphInfo& glyph = *( glyphsBuffer + index );
908 const Vector2& position = *( positionsBuffer + index );
910 const float xPosition = position.x - glyph.xBearing + offset.x;
911 mEventData->mDecorator->AddHighlight( xPosition, offset.y, xPosition + glyph.advance, height );
914 CursorInfo primaryCursorInfo;
915 GetCursorPosition( mEventData->mLeftSelectionPosition,
918 CursorInfo secondaryCursorInfo;
919 GetCursorPosition( mEventData->mRightSelectionPosition,
920 secondaryCursorInfo );
922 const Vector2 primaryPosition = primaryCursorInfo.primaryPosition + offset;
923 const Vector2 secondaryPosition = secondaryCursorInfo.primaryPosition + offset;
925 mEventData->mDecorator->SetPosition( LEFT_SELECTION_HANDLE, primaryPosition.x, primaryPosition.y, primaryCursorInfo.lineHeight );
927 mEventData->mDecorator->SetPosition( RIGHT_SELECTION_HANDLE, secondaryPosition.x, secondaryPosition.y, secondaryCursorInfo.lineHeight );
929 // Set the flag to update the decorator.
930 mEventData->mDecoratorUpdated = true;
933 void Controller::Impl::RepositionSelectionHandles( float visualX, float visualY )
935 if( NULL == mEventData )
937 // Nothing to do if there is no text input.
941 if( IsShowingPlaceholderText() )
943 // Nothing to do if there is the place-holder text.
947 const Length numberOfGlyphs = mVisualModel->mGlyphs.Count();
948 const Length numberOfLines = mVisualModel->mLines.Count();
949 if( 0 == numberOfGlyphs ||
952 // Nothing to do if there is no text.
956 // Find which word was selected
957 CharacterIndex selectionStart( 0 );
958 CharacterIndex selectionEnd( 0 );
959 FindSelectionIndices( visualX, visualY, selectionStart, selectionEnd );
960 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "%p selectionStart %d selectionEnd %d\n", this, selectionStart, selectionEnd );
962 if( selectionStart == selectionEnd )
964 ChangeState( EventData::EDITING );
965 // Nothing to select. i.e. a white space, out of bounds
969 RepositionSelectionHandles( selectionStart, selectionEnd );
972 void Controller::Impl::SetPopupButtons()
975 * Sets the Popup buttons to be shown depending on State.
977 * If SELECTING : CUT & COPY + ( PASTE & CLIPBOARD if content available to paste )
979 * If EDITING_WITH_POPUP : SELECT & SELECT_ALL
982 TextSelectionPopup::Buttons buttonsToShow = TextSelectionPopup::NONE;
984 if ( ( EventData::SELECTING == mEventData->mState ) || ( EventData::SELECTION_CHANGED == mEventData->mState ) )
986 buttonsToShow = TextSelectionPopup::Buttons( TextSelectionPopup::CUT | TextSelectionPopup::COPY );
988 if ( !IsClipboardEmpty() )
990 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
991 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::CLIPBOARD ) );
994 if ( !mEventData->mAllTextSelected )
996 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::SELECT_ALL ) );
999 else if ( EventData::EDITING_WITH_POPUP == mEventData->mState )
1001 buttonsToShow = TextSelectionPopup::Buttons( TextSelectionPopup::SELECT | TextSelectionPopup::SELECT_ALL );
1003 if ( !IsClipboardEmpty() )
1005 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
1006 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::CLIPBOARD ) );
1010 mEventData->mDecorator->SetEnabledPopupButtons( buttonsToShow );
1013 void Controller::Impl::ChangeState( EventData::State newState )
1015 if( NULL == mEventData )
1017 // Nothing to do if there is no text input.
1021 if( mEventData->mState != newState )
1023 mEventData->mState = newState;
1025 if( EventData::INACTIVE == mEventData->mState )
1027 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
1028 mEventData->mDecorator->StopCursorBlink();
1029 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1030 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1031 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1032 mEventData->mDecorator->SetPopupActive( false );
1033 mEventData->mDecoratorUpdated = true;
1036 else if ( EventData::SELECTING == mEventData->mState )
1038 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
1039 mEventData->mDecorator->StopCursorBlink();
1040 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1041 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true );
1042 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true );
1043 if( mEventData->mGrabHandlePopupEnabled )
1046 mEventData->mDecorator->SetPopupActive( true );
1048 mEventData->mDecoratorUpdated = true;
1050 else if ( EventData::SELECTION_CHANGED == mEventData->mState )
1052 if( mEventData->mGrabHandlePopupEnabled )
1055 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
1056 mEventData->mDecorator->SetPopupActive( true );
1058 mEventData->mDecoratorUpdated = true;
1060 else if( EventData::EDITING == mEventData->mState )
1062 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1063 if( mEventData->mCursorBlinkEnabled )
1065 mEventData->mDecorator->StartCursorBlink();
1067 // Grab handle is not shown until a tap is received whilst EDITING
1068 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1069 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1070 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1071 if( mEventData->mGrabHandlePopupEnabled )
1073 mEventData->mDecorator->SetPopupActive( false );
1075 mEventData->mDecoratorUpdated = true;
1078 else if( EventData::EDITING_WITH_POPUP == mEventData->mState )
1080 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1081 if( mEventData->mCursorBlinkEnabled )
1083 mEventData->mDecorator->StartCursorBlink();
1085 if( mEventData->mSelectionEnabled )
1087 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1088 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1092 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
1094 if( mEventData->mGrabHandlePopupEnabled )
1097 mEventData->mDecorator->SetPopupActive( true );
1100 mEventData->mDecoratorUpdated = true;
1102 else if ( EventData::SELECTION_HANDLE_PANNING == mEventData->mState )
1104 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
1105 mEventData->mDecorator->StopCursorBlink();
1106 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1107 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true );
1108 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true );
1109 if( mEventData->mGrabHandlePopupEnabled )
1111 mEventData->mDecorator->SetPopupActive( false );
1113 mEventData->mDecoratorUpdated = true;
1115 else if ( EventData::GRAB_HANDLE_PANNING == mEventData->mState )
1117 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1118 if( mEventData->mCursorBlinkEnabled )
1120 mEventData->mDecorator->StartCursorBlink();
1122 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
1123 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1124 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1125 if( mEventData->mGrabHandlePopupEnabled )
1127 mEventData->mDecorator->SetPopupActive( false );
1129 mEventData->mDecoratorUpdated = true;
1134 LineIndex Controller::Impl::GetClosestLine( float y ) const
1136 float totalHeight = 0.f;
1137 LineIndex lineIndex = 0u;
1139 const Vector<LineRun>& lines = mVisualModel->mLines;
1140 for( LineIndex endLine = lines.Count();
1141 lineIndex < endLine;
1144 const LineRun& lineRun = lines[lineIndex];
1145 totalHeight += lineRun.ascender + -lineRun.descender;
1146 if( y < totalHeight )
1152 if( lineIndex == 0 )
1160 void Controller::Impl::FindSelectionIndices( float visualX, float visualY, CharacterIndex& startIndex, CharacterIndex& endIndex )
1162 CharacterIndex hitCharacter = GetClosestCursorIndex( visualX, visualY );
1163 if( hitCharacter >= mLogicalModel->mText.Count() )
1165 // Selection out of bounds.
1169 startIndex = hitCharacter;
1170 endIndex = hitCharacter;
1172 if( !TextAbstraction::IsWhiteSpace( mLogicalModel->mText[hitCharacter] ) )
1174 // Find the start and end of the text
1175 for( startIndex = hitCharacter; startIndex > 0; --startIndex )
1177 Character charCode = mLogicalModel->mText[ startIndex-1 ];
1178 if( TextAbstraction::IsWhiteSpace( charCode ) )
1183 const CharacterIndex pastTheEnd = mLogicalModel->mText.Count();
1184 for( endIndex = hitCharacter + 1u; endIndex < pastTheEnd; ++endIndex )
1186 Character charCode = mLogicalModel->mText[ endIndex ];
1187 if( TextAbstraction::IsWhiteSpace( charCode ) )
1195 CharacterIndex Controller::Impl::GetClosestCursorIndex( float visualX,
1198 if( NULL == mEventData )
1200 // Nothing to do if there is no text input.
1204 CharacterIndex logicalIndex = 0u;
1206 const Length numberOfGlyphs = mVisualModel->mGlyphs.Count();
1207 const Length numberOfLines = mVisualModel->mLines.Count();
1208 if( 0 == numberOfGlyphs ||
1209 0 == numberOfLines )
1211 return logicalIndex;
1214 // Find which line is closest
1215 const LineIndex lineIndex = GetClosestLine( visualY );
1216 const LineRun& line = mVisualModel->mLines[lineIndex];
1218 // Get the positions of the glyphs.
1219 const Vector<Vector2>& positions = mVisualModel->mGlyphPositions;
1220 const Vector2* const positionsBuffer = positions.Begin();
1222 // Get the visual to logical conversion tables.
1223 const CharacterIndex* const visualToLogicalBuffer = ( 0u != mLogicalModel->mVisualToLogicalMap.Count() ) ? mLogicalModel->mVisualToLogicalMap.Begin() : NULL;
1224 const CharacterIndex* const visualToLogicalCursorBuffer = mLogicalModel->mVisualToLogicalCursorMap.Begin();
1226 // Get the character to glyph conversion table.
1227 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
1229 // Get the glyphs per character table.
1230 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
1231 const Length* const charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
1233 // If the vector is void, there is no right to left characters.
1234 const bool hasRightToLeftCharacters = NULL != visualToLogicalBuffer;
1236 const CharacterIndex startCharacter = line.characterRun.characterIndex;
1237 const CharacterIndex endCharacter = line.characterRun.characterIndex + line.characterRun.numberOfCharacters;
1238 DALI_ASSERT_DEBUG( endCharacter <= mLogicalModel->mText.Count() && "Invalid line info" );
1240 // Whether there is a hit on a glyph.
1241 bool matched = false;
1243 // Traverses glyphs in visual order. To do that use the visual to logical conversion table.
1244 CharacterIndex visualIndex = startCharacter;
1245 for( ; !matched && ( visualIndex < endCharacter ); ++visualIndex )
1247 // The character in logical order.
1248 const CharacterIndex characterLogicalOrderIndex = hasRightToLeftCharacters ? *( visualToLogicalBuffer + visualIndex ) : visualIndex;
1250 // Get the script of the character.
1251 const Script script = mLogicalModel->GetScript( characterLogicalOrderIndex );
1253 // The first glyph for that character in logical order.
1254 const GlyphIndex glyphLogicalOrderIndex = *( charactersToGlyphBuffer + characterLogicalOrderIndex );
1255 // The number of glyphs for that character
1256 const Length numberOfGlyphs = *( glyphsPerCharacterBuffer + characterLogicalOrderIndex );
1258 // Get the metrics for the group of glyphs.
1259 GlyphMetrics glyphMetrics;
1260 GetGlyphsMetrics( glyphLogicalOrderIndex,
1266 const Vector2& position = *( positionsBuffer + glyphLogicalOrderIndex );
1268 // Prevents to jump the whole Latin ligatures like fi, ff, ...
1269 const Length numberOfCharactersInLigature = ( TextAbstraction::LATIN == script ) ? *( charactersPerGlyphBuffer + glyphLogicalOrderIndex ) : 1u;
1270 const float glyphAdvance = glyphMetrics.advance / static_cast<float>( numberOfCharactersInLigature );
1272 for( GlyphIndex index = 0u; !matched && ( index < numberOfCharactersInLigature ); ++index )
1274 // Find the mid-point of the area containing the glyph
1275 const float glyphCenter = -glyphMetrics.xBearing + position.x + ( static_cast<float>( index ) + 0.5f ) * glyphAdvance;
1277 if( visualX < glyphCenter )
1279 visualIndex += index;
1291 // Return the logical position of the cursor in characters.
1295 visualIndex = endCharacter;
1298 logicalIndex = hasRightToLeftCharacters ? *( visualToLogicalCursorBuffer + visualIndex ) : visualIndex;
1299 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "%p closest visualIndex %d logicalIndex %d\n", this, visualIndex, logicalIndex );
1301 return logicalIndex;
1304 void Controller::Impl::GetCursorPosition( CharacterIndex logical,
1305 CursorInfo& cursorInfo )
1307 // TODO: Check for multiline with \n, etc...
1309 // Check if the logical position is the first or the last one of the text.
1310 const bool isFirstPosition = 0u == logical;
1311 const bool isLastPosition = mLogicalModel->mText.Count() == logical;
1313 if( isFirstPosition && isLastPosition )
1315 // There is zero characters. Get the default font.
1317 FontId defaultFontId = 0u;
1318 if( NULL == mFontDefaults )
1320 defaultFontId = mFontClient.GetFontId( EMPTY_STRING,
1325 defaultFontId = mFontDefaults->GetFontId( mFontClient );
1328 Text::FontMetrics fontMetrics;
1329 mFontClient.GetFontMetrics( defaultFontId, fontMetrics );
1331 cursorInfo.lineHeight = fontMetrics.ascender - fontMetrics.descender;
1332 cursorInfo.primaryCursorHeight = cursorInfo.lineHeight;
1334 cursorInfo.primaryPosition.x = 1.f;
1335 cursorInfo.primaryPosition.y = 0.f;
1337 // Nothing else to do.
1341 // Get the previous logical index.
1342 const CharacterIndex previousLogical = isFirstPosition ? 0u : logical - 1u;
1344 // Decrease the logical index if it's the last one.
1345 if( isLastPosition )
1350 // Get the direction of the character and the previous one.
1351 const CharacterDirection* const modelCharacterDirectionsBuffer = ( 0u != mLogicalModel->mCharacterDirections.Count() ) ? mLogicalModel->mCharacterDirections.Begin() : NULL;
1353 CharacterDirection isCurrentRightToLeft = false;
1354 CharacterDirection isPreviousRightToLeft = false;
1355 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
1357 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + logical );
1358 isPreviousRightToLeft = *( modelCharacterDirectionsBuffer + previousLogical );
1361 // Get the line where the character is laid-out.
1362 const LineRun* modelLines = mVisualModel->mLines.Begin();
1364 const LineIndex lineIndex = mVisualModel->GetLineOfCharacter( logical );
1365 const LineRun& line = *( modelLines + lineIndex );
1367 // Get the paragraph's direction.
1368 const CharacterDirection isRightToLeftParagraph = line.direction;
1370 // Check whether there is an alternative position:
1372 cursorInfo.isSecondaryCursor = ( isCurrentRightToLeft != isPreviousRightToLeft ) ||
1373 ( isLastPosition && ( isRightToLeftParagraph != isCurrentRightToLeft ) );
1375 // Set the line height.
1376 cursorInfo.lineHeight = line.ascender + -line.descender;
1378 // Convert the cursor position into the glyph position.
1379 CharacterIndex characterIndex = logical;
1380 if( cursorInfo.isSecondaryCursor &&
1381 ( isRightToLeftParagraph != isCurrentRightToLeft ) )
1383 characterIndex = previousLogical;
1386 const GlyphIndex currentGlyphIndex = *( mVisualModel->mCharactersToGlyph.Begin() + characterIndex );
1387 const Length numberOfGlyphs = *( mVisualModel->mGlyphsPerCharacter.Begin() + characterIndex );
1388 const Length numberOfCharacters = *( mVisualModel->mCharactersPerGlyph.Begin() +currentGlyphIndex );
1390 // Get the metrics for the group of glyphs.
1391 GlyphMetrics glyphMetrics;
1392 GetGlyphsMetrics( currentGlyphIndex,
1398 float interGlyphAdvance = 0.f;
1399 if( !isLastPosition &&
1400 ( numberOfCharacters > 1u ) )
1402 const CharacterIndex firstIndex = *( mVisualModel->mGlyphsToCharacters.Begin() + currentGlyphIndex );
1403 interGlyphAdvance = static_cast<float>( characterIndex - firstIndex ) * glyphMetrics.advance / static_cast<float>( numberOfCharacters );
1406 // Get the glyph position and x bearing.
1407 const Vector2& currentPosition = *( mVisualModel->mGlyphPositions.Begin() + currentGlyphIndex );
1409 // Set the cursor's height.
1410 cursorInfo.primaryCursorHeight = glyphMetrics.fontHeight;
1412 // Set the position.
1413 cursorInfo.primaryPosition.x = -glyphMetrics.xBearing + currentPosition.x + ( isCurrentRightToLeft ? glyphMetrics.advance : interGlyphAdvance );
1414 cursorInfo.primaryPosition.y = line.ascender - glyphMetrics.ascender;
1416 if( isLastPosition )
1418 // The position of the cursor after the last character needs special
1419 // care depending on its direction and the direction of the paragraph.
1421 if( cursorInfo.isSecondaryCursor )
1423 // Need to find the first character after the last character with the paragraph's direction.
1424 // i.e l0 l1 l2 r0 r1 should find r0.
1426 // TODO: check for more than one line!
1427 characterIndex = isRightToLeftParagraph ? line.characterRun.characterIndex : line.characterRun.characterIndex + line.characterRun.numberOfCharacters - 1u;
1428 characterIndex = mLogicalModel->GetLogicalCharacterIndex( characterIndex );
1430 const GlyphIndex glyphIndex = *( mVisualModel->mCharactersToGlyph.Begin() + characterIndex );
1431 const Length numberOfGlyphs = *( mVisualModel->mGlyphsPerCharacter.Begin() + characterIndex );
1433 const Vector2& position = *( mVisualModel->mGlyphPositions.Begin() + glyphIndex );
1435 // Get the metrics for the group of glyphs.
1436 GlyphMetrics glyphMetrics;
1437 GetGlyphsMetrics( glyphIndex,
1443 cursorInfo.primaryPosition.x = -glyphMetrics.xBearing + position.x + ( isRightToLeftParagraph ? 0.f : glyphMetrics.advance );
1445 cursorInfo.primaryPosition.y = line.ascender - glyphMetrics.ascender;
1449 if( !isCurrentRightToLeft )
1451 cursorInfo.primaryPosition.x += glyphMetrics.advance;
1455 cursorInfo.primaryPosition.x -= glyphMetrics.advance;
1460 // Set the alternative cursor position.
1461 if( cursorInfo.isSecondaryCursor )
1463 // Convert the cursor position into the glyph position.
1464 const CharacterIndex previousCharacterIndex = ( ( isRightToLeftParagraph != isCurrentRightToLeft ) ? logical : previousLogical );
1465 const GlyphIndex previousGlyphIndex = *( mVisualModel->mCharactersToGlyph.Begin() + previousCharacterIndex );
1466 const Length numberOfGlyphs = *( mVisualModel->mGlyphsPerCharacter.Begin() + previousCharacterIndex );
1468 // Get the glyph position.
1469 const Vector2& previousPosition = *( mVisualModel->mGlyphPositions.Begin() + previousGlyphIndex );
1471 // Get the metrics for the group of glyphs.
1472 GlyphMetrics glyphMetrics;
1473 GetGlyphsMetrics( previousGlyphIndex,
1479 // Set the cursor position and height.
1480 cursorInfo.secondaryPosition.x = -glyphMetrics.xBearing + previousPosition.x + ( ( ( isLastPosition && !isCurrentRightToLeft ) ||
1481 ( !isLastPosition && isCurrentRightToLeft ) ) ? glyphMetrics.advance : 0.f );
1483 cursorInfo.secondaryCursorHeight = 0.5f * glyphMetrics.fontHeight;
1485 cursorInfo.secondaryPosition.y = cursorInfo.lineHeight - cursorInfo.secondaryCursorHeight - line.descender - ( glyphMetrics.fontHeight - glyphMetrics.ascender );
1487 // Update the primary cursor height as well.
1488 cursorInfo.primaryCursorHeight *= 0.5f;
1492 CharacterIndex Controller::Impl::CalculateNewCursorIndex( CharacterIndex index ) const
1494 if( NULL == mEventData )
1496 // Nothing to do if there is no text input.
1500 CharacterIndex cursorIndex = mEventData->mPrimaryCursorPosition;
1502 const Script script = mLogicalModel->GetScript( index );
1503 const GlyphIndex* charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
1504 const Length* charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
1506 Length numberOfCharacters = 0u;
1507 if( TextAbstraction::LATIN == script )
1509 // Prevents to jump the whole Latin ligatures like fi, ff, ...
1510 numberOfCharacters = 1u;
1514 GlyphIndex glyphIndex = *( charactersToGlyphBuffer + index );
1515 numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
1517 while( 0u == numberOfCharacters )
1519 numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
1524 if( index < mEventData->mPrimaryCursorPosition )
1526 cursorIndex -= numberOfCharacters;
1530 cursorIndex += numberOfCharacters;
1536 void Controller::Impl::UpdateCursorPosition()
1538 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "-->Controller::UpdateCursorPosition %p\n", this );
1539 if( NULL == mEventData )
1541 // Nothing to do if there is no text input.
1542 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition no event data\n" );
1546 if( IsShowingPlaceholderText() )
1548 // Do not want to use the place-holder text to set the cursor position.
1550 // Use the line's height of the font's family set to set the cursor's size.
1551 // If there is no font's family set, use the default font.
1552 // Use the current alignment to place the cursor at the beginning, center or end of the box.
1554 float lineHeight = 0.f;
1556 FontId defaultFontId = 0u;
1557 if( NULL == mFontDefaults )
1559 defaultFontId = mFontClient.GetFontId( EMPTY_STRING,
1564 defaultFontId = mFontDefaults->GetFontId( mFontClient );
1567 Text::FontMetrics fontMetrics;
1568 mFontClient.GetFontMetrics( defaultFontId, fontMetrics );
1570 lineHeight = fontMetrics.ascender - fontMetrics.descender;
1573 Vector2 cursorPosition;
1575 switch( mLayoutEngine.GetHorizontalAlignment() )
1577 case LayoutEngine::HORIZONTAL_ALIGN_BEGIN:
1579 cursorPosition.x = 1.f;
1582 case LayoutEngine::HORIZONTAL_ALIGN_CENTER:
1584 cursorPosition.x = floor( 0.5f * mControlSize.width );
1587 case LayoutEngine::HORIZONTAL_ALIGN_END:
1589 cursorPosition.x = mControlSize.width;
1594 switch( mLayoutEngine.GetVerticalAlignment() )
1596 case LayoutEngine::VERTICAL_ALIGN_TOP:
1598 cursorPosition.y = 0.f;
1601 case LayoutEngine::VERTICAL_ALIGN_CENTER:
1603 cursorPosition.y = floorf( 0.5f * ( mControlSize.height - lineHeight ) );
1606 case LayoutEngine::VERTICAL_ALIGN_BOTTOM:
1608 cursorPosition.y = mControlSize.height - lineHeight;
1613 mEventData->mDecorator->SetPosition( PRIMARY_CURSOR,
1621 CursorInfo cursorInfo;
1622 GetCursorPosition( mEventData->mPrimaryCursorPosition,
1625 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
1626 const Vector2 cursorPosition = cursorInfo.primaryPosition + offset;
1628 // Sets the cursor position.
1629 mEventData->mDecorator->SetPosition( PRIMARY_CURSOR,
1632 cursorInfo.primaryCursorHeight,
1633 cursorInfo.lineHeight );
1634 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Primary cursor position: %f,%f\n", cursorPosition.x, cursorPosition.y );
1636 // Sets the grab handle position.
1637 mEventData->mDecorator->SetPosition( GRAB_HANDLE,
1640 cursorInfo.lineHeight );
1642 if( cursorInfo.isSecondaryCursor )
1644 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_BOTH );
1645 mEventData->mDecorator->SetPosition( SECONDARY_CURSOR,
1646 cursorInfo.secondaryPosition.x + offset.x,
1647 cursorInfo.secondaryPosition.y + offset.y,
1648 cursorInfo.secondaryCursorHeight,
1649 cursorInfo.lineHeight );
1650 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Secondary cursor position: %f,%f\n", cursorInfo.secondaryPosition.x + offset.x, cursorInfo.secondaryPosition.y + offset.y );
1654 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1657 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition\n" );
1660 void Controller::Impl::UpdateSelectionHandle( HandleType handleType )
1662 if( ( LEFT_SELECTION_HANDLE != handleType ) &&
1663 ( RIGHT_SELECTION_HANDLE != handleType ) )
1668 const bool leftSelectionHandle = LEFT_SELECTION_HANDLE == handleType;
1669 const CharacterIndex index = leftSelectionHandle ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition;
1671 CursorInfo cursorInfo;
1672 GetCursorPosition( index,
1675 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
1676 const Vector2 cursorPosition = cursorInfo.primaryPosition + offset;
1678 // Sets the grab handle position.
1679 mEventData->mDecorator->SetPosition( handleType,
1682 cursorInfo.lineHeight );
1684 // If selection handle at start of the text and other at end of the text then all text is selected.
1685 const CharacterIndex startOfSelection = std::min( mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition );
1686 const CharacterIndex endOfSelection = std::max ( mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition );
1687 mEventData->mAllTextSelected = ( startOfSelection == 0 ) && ( endOfSelection == mLogicalModel->mText.Count() );
1690 void Controller::Impl::ClampHorizontalScroll( const Vector2& actualSize )
1692 // Clamp between -space & 0 (and the text alignment).
1693 if( actualSize.width > mControlSize.width )
1695 const float space = ( actualSize.width - mControlSize.width ) + mAlignmentOffset.x;
1696 mEventData->mScrollPosition.x = ( mEventData->mScrollPosition.x < -space ) ? -space : mEventData->mScrollPosition.x;
1697 mEventData->mScrollPosition.x = ( mEventData->mScrollPosition.x > -mAlignmentOffset.x ) ? -mAlignmentOffset.x : mEventData->mScrollPosition.x;
1699 mEventData->mDecoratorUpdated = true;
1703 mEventData->mScrollPosition.x = 0.f;
1707 void Controller::Impl::ClampVerticalScroll( const Vector2& actualSize )
1709 // Clamp between -space & 0 (and the text alignment).
1710 if( actualSize.height > mControlSize.height )
1712 const float space = ( actualSize.height - mControlSize.height ) + mAlignmentOffset.y;
1713 mEventData->mScrollPosition.y = ( mEventData->mScrollPosition.y < -space ) ? -space : mEventData->mScrollPosition.y;
1714 mEventData->mScrollPosition.y = ( mEventData->mScrollPosition.y > -mAlignmentOffset.y ) ? -mAlignmentOffset.y : mEventData->mScrollPosition.y;
1716 mEventData->mDecoratorUpdated = true;
1720 mEventData->mScrollPosition.y = 0.f;
1724 void Controller::Impl::ScrollToMakePositionVisible( const Vector2& position )
1727 bool updateDecorator = false;
1728 if( position.x < 0.f )
1730 offset.x = -position.x;
1731 mEventData->mScrollPosition.x += offset.x;
1732 updateDecorator = true;
1734 else if( position.x > mControlSize.width )
1736 offset.x = mControlSize.width - position.x;
1737 mEventData->mScrollPosition.x += offset.x;
1738 updateDecorator = true;
1741 if( updateDecorator && mEventData->mDecorator )
1743 mEventData->mDecorator->UpdatePositions( offset );
1746 // TODO : calculate the vertical scroll.
1749 void Controller::Impl::ScrollTextToMatchCursor()
1751 // Get the current cursor position in decorator coords.
1752 const Vector2& currentCursorPosition = mEventData->mDecorator->GetPosition( PRIMARY_CURSOR );
1754 // Calculate the new cursor position.
1755 CursorInfo cursorInfo;
1756 GetCursorPosition( mEventData->mPrimaryCursorPosition,
1759 // Calculate the offset to match the cursor position before the character was deleted.
1760 mEventData->mScrollPosition.x = currentCursorPosition.x - cursorInfo.primaryPosition.x - mAlignmentOffset.x;
1762 ClampHorizontalScroll( mVisualModel->GetActualSize() );
1764 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
1765 const Vector2 cursorPosition = cursorInfo.primaryPosition + offset;
1767 // Sets the cursor position.
1768 mEventData->mDecorator->SetPosition( PRIMARY_CURSOR,
1771 cursorInfo.primaryCursorHeight,
1772 cursorInfo.lineHeight );
1774 // Sets the grab handle position.
1775 mEventData->mDecorator->SetPosition( GRAB_HANDLE,
1778 cursorInfo.lineHeight );
1780 if( cursorInfo.isSecondaryCursor )
1782 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_BOTH );
1783 mEventData->mDecorator->SetPosition( SECONDARY_CURSOR,
1784 cursorInfo.secondaryPosition.x + offset.x,
1785 cursorInfo.secondaryPosition.y + offset.y,
1786 cursorInfo.secondaryCursorHeight,
1787 cursorInfo.lineHeight );
1788 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Secondary cursor position: %f,%f\n", cursorInfo.secondaryPosition.x + offset.x, cursorInfo.secondaryPosition.y + offset.y );
1792 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1796 void Controller::Impl::RequestRelayout()
1798 mControlInterface.RequestTextRelayout();
1803 } // namespace Toolkit