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/multi-language-support.h>
29 #include <dali-toolkit/internal/text/segmentation.h>
30 #include <dali-toolkit/internal/text/shaper.h>
35 #if defined(DEBUG_ENABLED)
36 Debug::Filter* gLogFilter = Debug::Filter::New(Debug::Concise, true, "LOG_TEXT_CONTROLS");
40 * @brief Some characters can be shaped in more than one glyph.
41 * This struct is used to retrieve metrics from these group of glyphs.
55 float fontHeight; ///< The font's height of that glyphs.
56 float advance; ///< The sum of all the advances of all the glyphs.
57 float ascender; ///< The font's ascender.
58 float xBearing; ///< The x bearing of the first glyph.
61 const std::string EMPTY_STRING("");
75 * @brief Get some glyph's metrics of a group of glyphs formed as a result of shaping one character.
77 * @param[in] glyphIndex The index to the first glyph.
78 * @param[in] numberOfGlyphs The number of glyphs.
79 * @param[out] glyphMetrics Some glyph metrics (font height, advance, ascender and x bearing).
80 * @param[in] visualModel The visual model.
81 * @param[in] fontClient The font client.
83 void GetGlyphsMetrics( GlyphIndex glyphIndex,
84 Length numberOfGlyphs,
85 GlyphMetrics& glyphMetrics,
86 VisualModelPtr visualModel,
87 TextAbstraction::FontClient& fontClient )
89 const GlyphInfo* glyphsBuffer = visualModel->mGlyphs.Begin();
91 const GlyphInfo& firstGlyph = *( glyphsBuffer + glyphIndex );
93 Text::FontMetrics fontMetrics;
94 fontClient.GetFontMetrics( firstGlyph.fontId, fontMetrics );
96 glyphMetrics.fontHeight = fontMetrics.height;
97 glyphMetrics.advance = firstGlyph.advance;
98 glyphMetrics.ascender = fontMetrics.ascender;
99 glyphMetrics.xBearing = firstGlyph.xBearing;
101 for( unsigned int i = 1u; i < numberOfGlyphs; ++i )
103 const GlyphInfo& glyphInfo = *( glyphsBuffer + glyphIndex + i );
105 glyphMetrics.advance += glyphInfo.advance;
109 EventData::EventData( DecoratorPtr decorator )
110 : mDecorator( decorator ),
111 mPlaceholderTextActive(),
112 mPlaceholderTextInactive(),
113 mPlaceholderTextColor( 0.8f, 0.8f, 0.8f, 0.8f ),
117 mPrimaryCursorPosition( 0u ),
118 mLeftSelectionPosition( 0u ),
119 mRightSelectionPosition( 0u ),
120 mPreEditStartPosition( 0u ),
121 mPreEditLength( 0u ),
122 mIsShowingPlaceholderText( false ),
123 mPreEditFlag( false ),
124 mDecoratorUpdated( false ),
125 mCursorBlinkEnabled( true ),
126 mGrabHandleEnabled( true ),
127 mGrabHandlePopupEnabled( true ),
128 mSelectionEnabled( true ),
129 mHorizontalScrollingEnabled( true ),
130 mVerticalScrollingEnabled( false ),
131 mUpdateCursorPosition( false ),
132 mUpdateLeftSelectionPosition( false ),
133 mUpdateRightSelectionPosition( false ),
134 mScrollAfterUpdatePosition( false ),
135 mScrollAfterDelete( false ),
136 mAllTextSelected( false )
139 EventData::~EventData()
142 bool Controller::Impl::ProcessInputEvents()
144 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "-->Controller::ProcessInputEvents\n" );
145 if( NULL == mEventData )
147 // Nothing to do if there is no text input.
148 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::ProcessInputEvents no event data\n" );
152 if( mEventData->mDecorator )
154 for( std::vector<Event>::iterator iter = mEventData->mEventQueue.begin();
155 iter != mEventData->mEventQueue.end();
160 case Event::CURSOR_KEY_EVENT:
162 OnCursorKeyEvent( *iter );
165 case Event::TAP_EVENT:
170 case Event::LONG_PRESS_EVENT:
172 OnLongPressEvent( *iter );
175 case Event::PAN_EVENT:
180 case Event::GRAB_HANDLE_EVENT:
181 case Event::LEFT_SELECTION_HANDLE_EVENT:
182 case Event::RIGHT_SELECTION_HANDLE_EVENT: // Fall through
184 OnHandleEvent( *iter );
189 OnSelectEvent( *iter );
192 case Event::SELECT_ALL:
201 // The cursor must also be repositioned after inserts into the model
202 if( mEventData->mUpdateCursorPosition )
204 // Updates the cursor position and scrolls the text to make it visible.
206 UpdateCursorPosition();
208 if( mEventData->mScrollAfterUpdatePosition )
210 const Vector2& primaryCursorPosition = mEventData->mDecorator->GetPosition( PRIMARY_CURSOR );
212 ScrollToMakePositionVisible( primaryCursorPosition );
213 mEventData->mScrollAfterUpdatePosition = false;
216 mEventData->mDecoratorUpdated = true;
217 mEventData->mUpdateCursorPosition = false;
219 else if( mEventData->mScrollAfterDelete )
221 ScrollTextToMatchCursor();
222 mEventData->mDecoratorUpdated = true;
223 mEventData->mScrollAfterDelete = false;
227 bool leftScroll = false;
228 bool rightScroll = false;
230 if( mEventData->mUpdateLeftSelectionPosition )
232 UpdateSelectionHandle( LEFT_SELECTION_HANDLE );
234 if( mEventData->mScrollAfterUpdatePosition )
236 const Vector2& leftHandlePosition = mEventData->mDecorator->GetPosition( LEFT_SELECTION_HANDLE );
238 ScrollToMakePositionVisible( leftHandlePosition );
243 mEventData->mDecoratorUpdated = true;
244 mEventData->mUpdateLeftSelectionPosition = false;
247 if( mEventData->mUpdateRightSelectionPosition )
249 UpdateSelectionHandle( RIGHT_SELECTION_HANDLE );
251 if( mEventData->mScrollAfterUpdatePosition )
253 const Vector2& rightHandlePosition = mEventData->mDecorator->GetPosition( RIGHT_SELECTION_HANDLE );
255 ScrollToMakePositionVisible( rightHandlePosition );
260 mEventData->mDecoratorUpdated = true;
261 mEventData->mUpdateRightSelectionPosition = false;
264 if( leftScroll || rightScroll )
266 mEventData->mScrollAfterUpdatePosition = false;
270 mEventData->mEventQueue.clear();
272 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::ProcessInputEvents\n" );
274 const bool decoratorUpdated = mEventData->mDecoratorUpdated;
275 mEventData->mDecoratorUpdated = false;
277 return decoratorUpdated;
280 void Controller::Impl::UpdateModel( OperationsMask operationsRequired )
282 // Calculate the operations to be done.
283 const OperationsMask operations = static_cast<OperationsMask>( mOperationsPending & operationsRequired );
285 Vector<Character>& utf32Characters = mLogicalModel->mText;
287 const Length numberOfCharacters = utf32Characters.Count();
289 Vector<LineBreakInfo>& lineBreakInfo = mLogicalModel->mLineBreakInfo;
290 if( GET_LINE_BREAKS & operations )
292 // Retrieves the line break info. The line break info is used to split the text in 'paragraphs' to
293 // calculate the bidirectional info for each 'paragraph'.
294 // It's also used to layout the text (where it should be a new line) or to shape the text (text in different lines
295 // is not shaped together).
296 lineBreakInfo.Resize( numberOfCharacters, TextAbstraction::LINE_NO_BREAK );
298 SetLineBreakInfo( utf32Characters,
302 Vector<WordBreakInfo>& wordBreakInfo = mLogicalModel->mWordBreakInfo;
303 if( GET_WORD_BREAKS & operations )
305 // Retrieves the word break info. The word break info is used to layout the text (where to wrap the text in lines).
306 wordBreakInfo.Resize( numberOfCharacters, TextAbstraction::WORD_NO_BREAK );
308 SetWordBreakInfo( utf32Characters,
312 const bool getScripts = GET_SCRIPTS & operations;
313 const bool validateFonts = VALIDATE_FONTS & operations;
315 Vector<ScriptRun>& scripts = mLogicalModel->mScriptRuns;
316 Vector<FontRun>& validFonts = mLogicalModel->mFontRuns;
318 if( getScripts || validateFonts )
320 // Validates the fonts assigned by the application or assigns default ones.
321 // It makes sure all the characters are going to be rendered by the correct font.
322 MultilanguageSupport multilanguageSupport = MultilanguageSupport::Get();
326 // Retrieves the scripts used in the text.
327 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 Length numberOfParagraphs = 0u;
351 if( BIDI_INFO & operations )
353 // Count the number of LINE_NO_BREAK to reserve some space for the vector of paragraph's
354 // bidirectional info.
356 const TextAbstraction::LineBreakInfo* lineBreakInfoBuffer = lineBreakInfo.Begin();
357 for( Length index = 0u; index < numberOfCharacters; ++index )
359 if( TextAbstraction::LINE_NO_BREAK == *( lineBreakInfoBuffer + index ) )
361 ++numberOfParagraphs;
365 Vector<BidirectionalParagraphInfoRun>& bidirectionalInfo = mLogicalModel->mBidirectionalParagraphInfo;
366 bidirectionalInfo.Reserve( numberOfParagraphs );
368 // Calculates the bidirectional info for the whole paragraph if it contains right to left scripts.
369 SetBidirectionalInfo( utf32Characters,
374 if( 0u != bidirectionalInfo.Count() )
376 // This paragraph has right to left text. Some characters may need to be mirrored.
377 // TODO: consider if the mirrored string can be stored as well.
379 textMirrored = GetMirroredText( utf32Characters,
380 mirroredUtf32Characters,
383 // Only set the character directions if there is right to left characters.
384 Vector<CharacterDirection>& directions = mLogicalModel->mCharacterDirections;
385 directions.Resize( numberOfCharacters );
387 GetCharactersDirection( bidirectionalInfo,
392 // There is no right to left characters. Clear the directions vector.
393 mLogicalModel->mCharacterDirections.Clear();
397 Vector<GlyphInfo>& glyphs = mVisualModel->mGlyphs;
398 Vector<CharacterIndex>& glyphsToCharactersMap = mVisualModel->mGlyphsToCharacters;
399 Vector<Length>& charactersPerGlyph = mVisualModel->mCharactersPerGlyph;
400 Vector<GlyphIndex> newParagraphGlyphs;
401 newParagraphGlyphs.Reserve( numberOfParagraphs );
403 if( SHAPE_TEXT & operations )
405 const Vector<Character>& textToShape = textMirrored ? mirroredUtf32Characters : utf32Characters;
407 ShapeText( textToShape,
412 glyphsToCharactersMap,
414 newParagraphGlyphs );
416 // Create the 'number of glyphs' per character and the glyph to character conversion tables.
417 mVisualModel->CreateGlyphsPerCharacterTable( numberOfCharacters );
418 mVisualModel->CreateCharacterToGlyphTable( numberOfCharacters );
421 const Length numberOfGlyphs = glyphs.Count();
423 if( GET_GLYPH_METRICS & operations )
425 GlyphInfo* glyphsBuffer = glyphs.Begin();
426 mFontClient.GetGlyphMetrics( glyphsBuffer, numberOfGlyphs );
428 // Update the width and advance of all new paragraph characters.
429 for( Vector<GlyphIndex>::ConstIterator it = newParagraphGlyphs.Begin(), endIt = newParagraphGlyphs.End(); it != endIt; ++it )
431 const GlyphIndex index = *it;
432 GlyphInfo& glyph = *( glyphsBuffer + index );
434 glyph.xBearing = 0.f;
441 void Controller::Impl::GetDefaultFonts( Vector<FontRun>& fonts, Length numberOfCharacters )
446 fontRun.characterRun.characterIndex = 0;
447 fontRun.characterRun.numberOfCharacters = numberOfCharacters;
448 fontRun.fontId = mFontDefaults->GetFontId( mFontClient );
449 fontRun.isDefault = true;
451 fonts.PushBack( fontRun );
455 float Controller::Impl::GetDefaultFontLineHeight()
457 FontId defaultFontId = 0u;
458 if( NULL == mFontDefaults )
460 defaultFontId = mFontClient.GetFontId( EMPTY_STRING,
465 defaultFontId = mFontDefaults->GetFontId( mFontClient );
468 Text::FontMetrics fontMetrics;
469 mFontClient.GetFontMetrics( defaultFontId, fontMetrics );
471 return( fontMetrics.ascender - fontMetrics.descender );
474 void Controller::Impl::OnCursorKeyEvent( const Event& event )
476 if( NULL == mEventData )
478 // Nothing to do if there is no text input.
482 int keyCode = event.p1.mInt;
484 if( Dali::DALI_KEY_CURSOR_LEFT == keyCode )
486 if( mEventData->mPrimaryCursorPosition > 0u )
488 mEventData->mPrimaryCursorPosition = CalculateNewCursorIndex( mEventData->mPrimaryCursorPosition - 1u );
491 else if( Dali::DALI_KEY_CURSOR_RIGHT == keyCode )
493 if( mLogicalModel->mText.Count() > mEventData->mPrimaryCursorPosition )
495 mEventData->mPrimaryCursorPosition = CalculateNewCursorIndex( mEventData->mPrimaryCursorPosition );
498 else if( Dali::DALI_KEY_CURSOR_UP == keyCode )
502 else if( Dali::DALI_KEY_CURSOR_DOWN == keyCode )
507 mEventData->mUpdateCursorPosition = true;
508 mEventData->mScrollAfterUpdatePosition = true;
511 void Controller::Impl::OnTapEvent( const Event& event )
513 if( NULL != mEventData )
515 const unsigned int tapCount = event.p1.mUint;
519 if( ! IsShowingPlaceholderText() )
521 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
522 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
524 mEventData->mPrimaryCursorPosition = GetClosestCursorIndex( xPosition,
529 mEventData->mPrimaryCursorPosition = 0u;
532 mEventData->mUpdateCursorPosition = true;
533 mEventData->mScrollAfterUpdatePosition = true;
538 void Controller::Impl::OnPanEvent( const Event& event )
540 if( NULL == mEventData )
542 // Nothing to do if there is no text input.
546 int state = event.p1.mInt;
548 if( Gesture::Started == state ||
549 Gesture::Continuing == state )
551 const Vector2& actualSize = mVisualModel->GetActualSize();
552 const Vector2 currentScroll = mEventData->mScrollPosition;
554 if( mEventData->mHorizontalScrollingEnabled )
556 const float displacementX = event.p2.mFloat;
557 mEventData->mScrollPosition.x += displacementX;
559 ClampHorizontalScroll( actualSize );
562 if( mEventData->mVerticalScrollingEnabled )
564 const float displacementY = event.p3.mFloat;
565 mEventData->mScrollPosition.y += displacementY;
567 ClampVerticalScroll( actualSize );
570 if( mEventData->mDecorator )
572 mEventData->mDecorator->UpdatePositions( mEventData->mScrollPosition - currentScroll );
577 void Controller::Impl::OnLongPressEvent( const Event& event )
579 if ( EventData::EDITING == mEventData->mState )
581 ChangeState ( EventData::EDITING_WITH_POPUP );
582 mEventData->mDecoratorUpdated = true;
586 void Controller::Impl::OnHandleEvent( const Event& event )
588 if( NULL == mEventData )
590 // Nothing to do if there is no text input.
594 const unsigned int state = event.p1.mUint;
595 const bool handleStopScrolling = ( HANDLE_STOP_SCROLLING == state );
597 if( HANDLE_PRESSED == state )
599 // The event.p2 and event.p3 are in decorator coords. Need to transforms to text coords.
600 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
601 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
603 const CharacterIndex handleNewPosition = GetClosestCursorIndex( xPosition, yPosition );
605 if( Event::GRAB_HANDLE_EVENT == event.type )
607 ChangeState ( EventData::GRAB_HANDLE_PANNING );
609 if( handleNewPosition != mEventData->mPrimaryCursorPosition )
611 mEventData->mPrimaryCursorPosition = handleNewPosition;
612 mEventData->mUpdateCursorPosition = true;
615 else if( Event::LEFT_SELECTION_HANDLE_EVENT == event.type )
617 ChangeState ( EventData::SELECTION_HANDLE_PANNING );
619 if( ( handleNewPosition != mEventData->mLeftSelectionPosition ) &&
620 ( handleNewPosition != mEventData->mRightSelectionPosition ) )
622 mEventData->mLeftSelectionPosition = handleNewPosition;
624 RepositionSelectionHandles( mEventData->mLeftSelectionPosition,
625 mEventData->mRightSelectionPosition );
627 mEventData->mUpdateLeftSelectionPosition = true;
630 else if( Event::RIGHT_SELECTION_HANDLE_EVENT == event.type )
632 ChangeState ( EventData::SELECTION_HANDLE_PANNING );
634 if( ( handleNewPosition != mEventData->mRightSelectionPosition ) &&
635 ( handleNewPosition != mEventData->mLeftSelectionPosition ) )
637 mEventData->mRightSelectionPosition = handleNewPosition;
639 RepositionSelectionHandles( mEventData->mLeftSelectionPosition,
640 mEventData->mRightSelectionPosition );
642 mEventData->mUpdateRightSelectionPosition = true;
645 } // end ( HANDLE_PRESSED == state )
646 else if( ( HANDLE_RELEASED == state ) ||
647 handleStopScrolling )
649 CharacterIndex handlePosition = 0u;
650 if( handleStopScrolling )
652 // The event.p2 and event.p3 are in decorator coords. Need to transforms to text coords.
653 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
654 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
656 handlePosition = GetClosestCursorIndex( xPosition, yPosition );
659 if( Event::GRAB_HANDLE_EVENT == event.type )
661 mEventData->mUpdateCursorPosition = true;
663 ChangeState( EventData::EDITING_WITH_POPUP );
665 if( handleStopScrolling )
667 mEventData->mScrollAfterUpdatePosition = mEventData->mPrimaryCursorPosition != handlePosition;
668 mEventData->mPrimaryCursorPosition = handlePosition;
671 else if( Event::LEFT_SELECTION_HANDLE_EVENT == event.type )
673 ChangeState( EventData::SELECTING );
675 if( handleStopScrolling )
677 mEventData->mUpdateLeftSelectionPosition = ( mEventData->mRightSelectionPosition != handlePosition );
678 mEventData->mScrollAfterUpdatePosition = mEventData->mUpdateLeftSelectionPosition;
680 if( mEventData->mUpdateLeftSelectionPosition )
682 mEventData->mLeftSelectionPosition = handlePosition;
684 RepositionSelectionHandles( mEventData->mLeftSelectionPosition,
685 mEventData->mRightSelectionPosition );
689 else if( Event::RIGHT_SELECTION_HANDLE_EVENT == event.type )
691 ChangeState( EventData::SELECTING );
693 if( handleStopScrolling )
695 mEventData->mUpdateRightSelectionPosition = ( mEventData->mLeftSelectionPosition != handlePosition );
696 mEventData->mScrollAfterUpdatePosition = mEventData->mUpdateRightSelectionPosition;
697 if( mEventData->mUpdateRightSelectionPosition )
699 mEventData->mRightSelectionPosition = handlePosition;
700 RepositionSelectionHandles( mEventData->mLeftSelectionPosition,
701 mEventData->mRightSelectionPosition );
706 mEventData->mDecoratorUpdated = true;
707 } // end ( ( HANDLE_RELEASED == state ) || ( HANDLE_STOP_SCROLLING == state ) )
708 else if( HANDLE_SCROLLING == state )
710 const float xSpeed = event.p2.mFloat;
711 const Vector2& actualSize = mVisualModel->GetActualSize();
712 const Vector2 currentScrollPosition = mEventData->mScrollPosition;
714 mEventData->mScrollPosition.x += xSpeed;
716 ClampHorizontalScroll( actualSize );
718 bool endOfScroll = false;
719 if( Vector2::ZERO == ( currentScrollPosition - mEventData->mScrollPosition ) )
721 // Notify the decorator there is no more text to scroll.
722 // The decorator won't send more scroll events.
723 mEventData->mDecorator->NotifyEndOfScroll();
724 // Still need to set the position of the handle.
728 // Set the position of the handle.
729 const bool scrollRightDirection = xSpeed > 0.f;
730 const bool leftSelectionHandleEvent = Event::LEFT_SELECTION_HANDLE_EVENT == event.type;
731 const bool rightSelectionHandleEvent = Event::RIGHT_SELECTION_HANDLE_EVENT == event.type;
733 if( Event::GRAB_HANDLE_EVENT == event.type )
735 ChangeState( EventData::GRAB_HANDLE_PANNING );
737 Vector2 position = mEventData->mDecorator->GetPosition( GRAB_HANDLE );
739 // Position the grag handle close to either the left or right edge.
740 position.x = scrollRightDirection ? 0.f : mVisualModel->mControlSize.width;
742 // Get the new handle position.
743 // The grab handle's position is in decorator coords. Need to transforms to text coords.
744 const CharacterIndex handlePosition = GetClosestCursorIndex( position.x - mEventData->mScrollPosition.x - mAlignmentOffset.x,
745 position.y - mEventData->mScrollPosition.y - mAlignmentOffset.y );
747 mEventData->mUpdateCursorPosition = mEventData->mPrimaryCursorPosition != handlePosition;
748 mEventData->mScrollAfterUpdatePosition = mEventData->mUpdateCursorPosition;
749 mEventData->mPrimaryCursorPosition = handlePosition;
751 else if( leftSelectionHandleEvent || rightSelectionHandleEvent )
753 // TODO: This is recalculating the selection box every time the text is scrolled with the selection handles.
754 // Think if something can be done to save power.
756 ChangeState( EventData::SELECTION_HANDLE_PANNING );
758 Vector2 position = mEventData->mDecorator->GetPosition( leftSelectionHandleEvent ? Text::LEFT_SELECTION_HANDLE : Text::RIGHT_SELECTION_HANDLE );
760 // Position the selection handle close to either the left or right edge.
761 position.x = scrollRightDirection ? 0.f : mVisualModel->mControlSize.width;
763 // Get the new handle position.
764 // The selection handle's position is in decorator coords. Need to transforms to text coords.
765 const CharacterIndex handlePosition = GetClosestCursorIndex( position.x - mEventData->mScrollPosition.x - mAlignmentOffset.x,
766 position.y - mEventData->mScrollPosition.y - mAlignmentOffset.y );
768 if( leftSelectionHandleEvent )
770 const bool differentHandles = ( mEventData->mLeftSelectionPosition != handlePosition ) && ( mEventData->mRightSelectionPosition != handlePosition );
771 mEventData->mUpdateLeftSelectionPosition = endOfScroll || differentHandles;
772 if( differentHandles )
774 mEventData->mLeftSelectionPosition = handlePosition;
779 const bool differentHandles = ( mEventData->mRightSelectionPosition != handlePosition ) && ( mEventData->mLeftSelectionPosition != handlePosition );
780 mEventData->mUpdateRightSelectionPosition = endOfScroll || differentHandles;
781 if( differentHandles )
783 mEventData->mRightSelectionPosition = handlePosition;
787 if( mEventData->mUpdateLeftSelectionPosition || mEventData->mUpdateRightSelectionPosition )
789 RepositionSelectionHandles( mEventData->mLeftSelectionPosition,
790 mEventData->mRightSelectionPosition );
792 mEventData->mScrollAfterUpdatePosition = true;
795 mEventData->mDecoratorUpdated = true;
796 } // end ( HANDLE_SCROLLING == state )
799 void Controller::Impl::OnSelectEvent( const Event& event )
801 if( NULL == mEventData )
803 // Nothing to do if there is no text.
807 if( mEventData->mSelectionEnabled )
809 // The event.p2 and event.p3 are in decorator coords. Need to transforms to text coords.
810 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
811 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
813 const CharacterIndex leftPosition = mEventData->mLeftSelectionPosition;
814 const CharacterIndex rightPosition = mEventData->mRightSelectionPosition;
816 RepositionSelectionHandles( xPosition,
819 mEventData->mUpdateLeftSelectionPosition = leftPosition != mEventData->mLeftSelectionPosition;
820 mEventData->mUpdateRightSelectionPosition = rightPosition != mEventData->mRightSelectionPosition;
822 mEventData->mScrollAfterUpdatePosition = ( ( mEventData->mUpdateLeftSelectionPosition || mEventData->mUpdateRightSelectionPosition ) &&
823 ( mEventData->mLeftSelectionPosition != mEventData->mRightSelectionPosition ) );
827 void Controller::Impl::OnSelectAllEvent()
829 if( NULL == mEventData )
831 // Nothing to do if there is no text.
835 if( mEventData->mSelectionEnabled )
837 RepositionSelectionHandles( 0u,
838 mLogicalModel->mText.Count() );
840 mEventData->mScrollAfterUpdatePosition = true;
841 mEventData->mUpdateLeftSelectionPosition = true;
842 mEventData->mUpdateRightSelectionPosition = true;
846 void Controller::Impl::RetrieveSelection( std::string& selectedText, bool deleteAfterRetreival )
848 if( mEventData->mLeftSelectionPosition == mEventData->mRightSelectionPosition )
850 // Nothing to select if handles are in the same place.
855 //Get start and end position of selection
856 uint32_t startOfSelectedText = mEventData->mLeftSelectionPosition;
857 uint32_t lengthOfSelectedText = mEventData->mRightSelectionPosition - startOfSelectedText;
859 // Validate the start and end selection points
860 if( ( startOfSelectedText >= 0 ) && ( ( startOfSelectedText + lengthOfSelectedText ) <= mLogicalModel->mText.Count() ) )
862 //Get text as a UTF8 string
863 Vector<Character>& utf32Characters = mLogicalModel->mText;
865 Utf32ToUtf8( &utf32Characters[startOfSelectedText], lengthOfSelectedText, selectedText );
867 if ( deleteAfterRetreival ) // Only delete text if copied successfully
869 // Delete text between handles
870 Vector<Character>& currentText = mLogicalModel->mText;
872 Vector<Character>::Iterator first = currentText.Begin() + startOfSelectedText;
873 Vector<Character>::Iterator last = first + lengthOfSelectedText;
874 currentText.Erase( first, last );
876 mEventData->mPrimaryCursorPosition = mEventData->mLeftSelectionPosition;
877 mEventData->mScrollAfterDelete = true;
878 mEventData->mDecoratorUpdated = true;
882 void Controller::Impl::ShowClipboard()
886 mClipboard.ShowClipboard();
890 void Controller::Impl::HideClipboard()
894 mClipboard.HideClipboard();
898 bool Controller::Impl::CopyStringToClipboard( std::string& source )
900 //Send string to clipboard
901 return ( mClipboard && mClipboard.SetItem( source ) );
904 void Controller::Impl::SendSelectionToClipboard( bool deleteAfterSending )
906 std::string selectedText;
907 RetrieveSelection( selectedText, deleteAfterSending );
908 CopyStringToClipboard( selectedText );
909 ChangeState( EventData::EDITING );
912 void Controller::Impl::GetTextFromClipboard( unsigned int itemIndex, std::string& retreivedString )
916 retreivedString = mClipboard.GetItem( itemIndex );
920 void Controller::Impl::RepositionSelectionHandles( CharacterIndex selectionStart, CharacterIndex selectionEnd )
922 if( selectionStart == selectionEnd )
924 // Nothing to select if handles are in the same place.
928 mEventData->mDecorator->ClearHighlights();
930 mEventData->mLeftSelectionPosition = selectionStart;
931 mEventData->mRightSelectionPosition = selectionEnd;
933 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
934 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
935 const GlyphInfo* const glyphsBuffer = mVisualModel->mGlyphs.Begin();
936 const Vector2* const positionsBuffer = mVisualModel->mGlyphPositions.Begin();
937 const Length* const charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
938 const CharacterIndex* const glyphToCharacterBuffer = mVisualModel->mGlyphsToCharacters.Begin();
939 const CharacterDirection* const modelCharacterDirectionsBuffer = ( 0u != mLogicalModel->mCharacterDirections.Count() ) ? mLogicalModel->mCharacterDirections.Begin() : NULL;
941 // TODO: Better algorithm to create the highlight box.
944 // Get the height of the line.
945 const Vector<LineRun>& lines = mVisualModel->mLines;
946 const LineRun& firstLine = *lines.Begin();
947 const float height = firstLine.ascender + -firstLine.descender;
949 // Swap the indices if the start is greater than the end.
950 const bool indicesSwapped = ( selectionStart > selectionEnd );
953 std::swap( selectionStart, selectionEnd );
956 // Get the indices to the first and last selected glyphs.
957 const CharacterIndex selectionEndMinusOne = selectionEnd - 1u;
958 const GlyphIndex glyphStart = *( charactersToGlyphBuffer + selectionStart );
959 const Length numberOfGlyphs = *( glyphsPerCharacterBuffer + selectionEndMinusOne );
960 const GlyphIndex glyphEnd = *( charactersToGlyphBuffer + selectionEndMinusOne ) + ( ( numberOfGlyphs > 0 ) ? numberOfGlyphs - 1u : 0u );
962 // Check if the first glyph is a ligature that must be broken like Latin ff, fi, or Arabic ï»», etc which needs special code.
963 const Length numberOfCharactersStart = *( charactersPerGlyphBuffer + glyphStart );
964 bool splitStartGlyph = ( numberOfCharactersStart > 1u ) && HasLigatureMustBreak( mLogicalModel->GetScript( selectionStart ) );
966 // Check if the last glyph is a ligature that must be broken like Latin ff, fi, or Arabic ï»», etc which needs special code.
967 const Length numberOfCharactersEnd = *( charactersPerGlyphBuffer + glyphEnd );
968 bool splitEndGlyph = ( glyphStart != glyphEnd ) && ( numberOfCharactersEnd > 1u ) && HasLigatureMustBreak( mLogicalModel->GetScript( selectionEndMinusOne ) );
970 // Tell the decorator to swap the selection handles if needed.
971 mEventData->mDecorator->SwapSelectionHandlesEnabled( firstLine.direction != indicesSwapped );
973 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
975 // Traverse the glyphs.
976 for( GlyphIndex index = glyphStart; index <= glyphEnd; ++index )
978 const GlyphInfo& glyph = *( glyphsBuffer + index );
979 const Vector2& position = *( positionsBuffer + index );
981 if( splitStartGlyph )
983 // If the first glyph is a ligature that must be broken it may be needed to add only part of the glyph to the highlight box.
985 const float glyphAdvance = glyph.advance / static_cast<float>( numberOfCharactersStart );
986 const CharacterIndex interGlyphIndex = selectionStart - *( glyphToCharacterBuffer + glyphStart );
987 // Get the direction of the character.
988 CharacterDirection isCurrentRightToLeft = false;
989 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
991 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + selectionStart );
994 // The end point could be in the middle of the ligature.
995 // Calculate the number of characters selected.
996 const Length numberOfCharacters = ( glyphStart == glyphEnd ) ? ( selectionEnd - selectionStart ) : ( numberOfCharactersStart - interGlyphIndex );
998 const float xPosition = position.x - glyph.xBearing + offset.x + glyphAdvance * static_cast<float>( isCurrentRightToLeft ? ( numberOfCharactersStart - interGlyphIndex - numberOfCharacters ) : interGlyphIndex );
1000 mEventData->mDecorator->AddHighlight( xPosition,
1002 xPosition + static_cast<float>( numberOfCharacters ) * glyphAdvance,
1003 offset.y + height );
1005 splitStartGlyph = false;
1009 if( splitEndGlyph && ( index == glyphEnd ) )
1011 // Equally, if the last glyph is a ligature that must be broken it may be needed to add only part of the glyph to the highlight box.
1013 const float glyphAdvance = glyph.advance / static_cast<float>( numberOfCharactersEnd );
1014 const CharacterIndex interGlyphIndex = selectionEnd - *( glyphToCharacterBuffer + glyphEnd );
1015 // Get the direction of the character.
1016 CharacterDirection isCurrentRightToLeft = false;
1017 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
1019 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + selectionEnd );
1022 const Length numberOfCharacters = numberOfCharactersEnd - interGlyphIndex;
1024 const float xPosition = position.x - glyph.xBearing + offset.x + ( isCurrentRightToLeft ? ( glyphAdvance * static_cast<float>( numberOfCharacters ) ) : 0.f );
1025 mEventData->mDecorator->AddHighlight( xPosition,
1027 xPosition + static_cast<float>( interGlyphIndex ) * glyphAdvance,
1028 offset.y + height );
1030 splitEndGlyph = false;
1034 const float xPosition = position.x - glyph.xBearing + offset.x;
1035 mEventData->mDecorator->AddHighlight( xPosition,
1037 xPosition + glyph.advance,
1038 offset.y + height );
1041 CursorInfo primaryCursorInfo;
1042 GetCursorPosition( mEventData->mLeftSelectionPosition,
1043 primaryCursorInfo );
1045 CursorInfo secondaryCursorInfo;
1046 GetCursorPosition( mEventData->mRightSelectionPosition,
1047 secondaryCursorInfo );
1049 const Vector2 primaryPosition = primaryCursorInfo.primaryPosition + offset;
1050 const Vector2 secondaryPosition = secondaryCursorInfo.primaryPosition + offset;
1052 mEventData->mDecorator->SetPosition( LEFT_SELECTION_HANDLE, primaryPosition.x, primaryPosition.y, primaryCursorInfo.lineHeight );
1054 mEventData->mDecorator->SetPosition( RIGHT_SELECTION_HANDLE, secondaryPosition.x, secondaryPosition.y, secondaryCursorInfo.lineHeight );
1056 // Cursor to be positioned at end of selection so if selection interrupted and edit mode restarted the cursor will be at end of selection
1057 mEventData->mPrimaryCursorPosition = (indicesSwapped)?mEventData->mLeftSelectionPosition:mEventData->mRightSelectionPosition;
1059 // Set the flag to update the decorator.
1060 mEventData->mDecoratorUpdated = true;
1063 void Controller::Impl::RepositionSelectionHandles( float visualX, float visualY )
1065 if( NULL == mEventData )
1067 // Nothing to do if there is no text input.
1071 if( IsShowingPlaceholderText() )
1073 // Nothing to do if there is the place-holder text.
1077 const Length numberOfGlyphs = mVisualModel->mGlyphs.Count();
1078 const Length numberOfLines = mVisualModel->mLines.Count();
1079 if( 0 == numberOfGlyphs ||
1080 0 == numberOfLines )
1082 // Nothing to do if there is no text.
1086 // Find which word was selected
1087 CharacterIndex selectionStart( 0 );
1088 CharacterIndex selectionEnd( 0 );
1089 FindSelectionIndices( visualX, visualY, selectionStart, selectionEnd );
1090 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "%p selectionStart %d selectionEnd %d\n", this, selectionStart, selectionEnd );
1092 if( selectionStart == selectionEnd )
1094 ChangeState( EventData::EDITING );
1095 // Nothing to select. i.e. a white space, out of bounds
1099 RepositionSelectionHandles( selectionStart, selectionEnd );
1102 void Controller::Impl::SetPopupButtons()
1105 * Sets the Popup buttons to be shown depending on State.
1107 * If SELECTING : CUT & COPY + ( PASTE & CLIPBOARD if content available to paste )
1109 * If EDITING_WITH_POPUP : SELECT & SELECT_ALL
1112 TextSelectionPopup::Buttons buttonsToShow = TextSelectionPopup::NONE;
1114 if ( ( EventData::SELECTING == mEventData->mState ) || ( EventData::SELECTION_CHANGED == mEventData->mState ) )
1116 buttonsToShow = TextSelectionPopup::Buttons( TextSelectionPopup::CUT | TextSelectionPopup::COPY );
1118 if ( !IsClipboardEmpty() )
1120 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
1121 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::CLIPBOARD ) );
1124 if ( !mEventData->mAllTextSelected )
1126 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::SELECT_ALL ) );
1129 else if ( EventData::EDITING_WITH_POPUP == mEventData->mState )
1131 if ( mLogicalModel->mText.Count() && !IsShowingPlaceholderText())
1133 buttonsToShow = TextSelectionPopup::Buttons( TextSelectionPopup::SELECT | TextSelectionPopup::SELECT_ALL );
1136 if ( !IsClipboardEmpty() )
1138 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
1139 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::CLIPBOARD ) );
1143 mEventData->mDecorator->SetEnabledPopupButtons( buttonsToShow );
1146 void Controller::Impl::ChangeState( EventData::State newState )
1148 if( NULL == mEventData )
1150 // Nothing to do if there is no text input.
1154 if( mEventData->mState != newState )
1156 mEventData->mState = newState;
1158 if( EventData::INACTIVE == mEventData->mState )
1160 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
1161 mEventData->mDecorator->StopCursorBlink();
1162 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1163 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1164 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1165 mEventData->mDecorator->SetPopupActive( false );
1166 mEventData->mDecoratorUpdated = true;
1169 else if ( EventData::INTERRUPTED == mEventData->mState)
1171 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1172 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1173 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1174 mEventData->mDecorator->SetPopupActive( false );
1175 mEventData->mDecoratorUpdated = true;
1178 else if ( EventData::SELECTING == mEventData->mState )
1180 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
1181 mEventData->mDecorator->StopCursorBlink();
1182 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1183 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true );
1184 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true );
1185 if( mEventData->mGrabHandlePopupEnabled )
1188 mEventData->mDecorator->SetPopupActive( true );
1190 mEventData->mDecoratorUpdated = true;
1192 else if ( EventData::SELECTION_CHANGED == mEventData->mState )
1194 if( mEventData->mGrabHandlePopupEnabled )
1197 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
1198 mEventData->mDecorator->SetPopupActive( true );
1200 mEventData->mDecoratorUpdated = true;
1202 else if( EventData::EDITING == mEventData->mState )
1204 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1205 if( mEventData->mCursorBlinkEnabled )
1207 mEventData->mDecorator->StartCursorBlink();
1209 // Grab handle is not shown until a tap is received whilst EDITING
1210 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1211 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1212 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1213 if( mEventData->mGrabHandlePopupEnabled )
1215 mEventData->mDecorator->SetPopupActive( false );
1217 mEventData->mDecoratorUpdated = true;
1220 else if( EventData::EDITING_WITH_POPUP == mEventData->mState )
1222 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1223 if( mEventData->mCursorBlinkEnabled )
1225 mEventData->mDecorator->StartCursorBlink();
1227 if( mEventData->mSelectionEnabled )
1229 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1230 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1234 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
1236 if( mEventData->mGrabHandlePopupEnabled )
1239 mEventData->mDecorator->SetPopupActive( true );
1242 mEventData->mDecoratorUpdated = true;
1244 else if( EventData::EDITING_WITH_GRAB_HANDLE == mEventData->mState )
1246 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1247 if( mEventData->mCursorBlinkEnabled )
1249 mEventData->mDecorator->StartCursorBlink();
1251 // Grab handle is not shown until a tap is received whilst EDITING
1252 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
1253 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1254 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1255 if( mEventData->mGrabHandlePopupEnabled )
1257 mEventData->mDecorator->SetPopupActive( false );
1259 mEventData->mDecoratorUpdated = true;
1262 else if ( EventData::SELECTION_HANDLE_PANNING == mEventData->mState )
1264 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
1265 mEventData->mDecorator->StopCursorBlink();
1266 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1267 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true );
1268 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true );
1269 if( mEventData->mGrabHandlePopupEnabled )
1271 mEventData->mDecorator->SetPopupActive( false );
1273 mEventData->mDecoratorUpdated = true;
1275 else if ( EventData::GRAB_HANDLE_PANNING == mEventData->mState )
1277 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1278 if( mEventData->mCursorBlinkEnabled )
1280 mEventData->mDecorator->StartCursorBlink();
1282 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
1283 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1284 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1285 if( mEventData->mGrabHandlePopupEnabled )
1287 mEventData->mDecorator->SetPopupActive( false );
1289 mEventData->mDecoratorUpdated = true;
1294 LineIndex Controller::Impl::GetClosestLine( float y ) const
1296 float totalHeight = 0.f;
1297 LineIndex lineIndex = 0u;
1299 const Vector<LineRun>& lines = mVisualModel->mLines;
1300 for( LineIndex endLine = lines.Count();
1301 lineIndex < endLine;
1304 const LineRun& lineRun = lines[lineIndex];
1305 totalHeight += lineRun.ascender + -lineRun.descender;
1306 if( y < totalHeight )
1312 if( lineIndex == 0 )
1320 void Controller::Impl::FindSelectionIndices( float visualX, float visualY, CharacterIndex& startIndex, CharacterIndex& endIndex )
1322 CharacterIndex hitCharacter = GetClosestCursorIndex( visualX, visualY );
1323 DALI_ASSERT_DEBUG( hitCharacter <= mLogicalModel->mText.Count() && "GetClosestCursorIndex returned out of bounds index" );
1325 if ( mLogicalModel->mText.Count() == 0 )
1327 return; // if model empty
1330 if( hitCharacter >= mLogicalModel->mText.Count() )
1332 // Closest hit character is the last character.
1333 if ( hitCharacter == mLogicalModel->mText.Count() )
1335 hitCharacter--; //Hit character index set to last character in logical model
1339 // hitCharacter is out of bounds
1344 startIndex = hitCharacter;
1345 endIndex = hitCharacter;
1347 if( !TextAbstraction::IsWhiteSpace( mLogicalModel->mText[hitCharacter] ) )
1349 // Find the start and end of the text
1350 for( startIndex = hitCharacter; startIndex > 0; --startIndex )
1352 Character charCode = mLogicalModel->mText[ startIndex-1 ];
1353 if( TextAbstraction::IsWhiteSpace( charCode ) )
1358 const CharacterIndex pastTheEnd = mLogicalModel->mText.Count();
1359 for( endIndex = hitCharacter + 1u; endIndex < pastTheEnd; ++endIndex )
1361 Character charCode = mLogicalModel->mText[ endIndex ];
1362 if( TextAbstraction::IsWhiteSpace( charCode ) )
1370 CharacterIndex Controller::Impl::GetClosestCursorIndex( float visualX,
1373 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "GetClosestCursorIndex %p closest visualX %f visualY %f\n", this, visualX, visualY );
1375 if( NULL == mEventData )
1377 // Nothing to do if there is no text input.
1381 CharacterIndex logicalIndex = 0u;
1383 const Length numberOfGlyphs = mVisualModel->mGlyphs.Count();
1384 const Length numberOfLines = mVisualModel->mLines.Count();
1385 if( 0 == numberOfGlyphs ||
1386 0 == numberOfLines )
1388 return logicalIndex;
1391 // Find which line is closest
1392 const LineIndex lineIndex = GetClosestLine( visualY );
1393 const LineRun& line = mVisualModel->mLines[lineIndex];
1395 // Get the positions of the glyphs.
1396 const Vector<Vector2>& positions = mVisualModel->mGlyphPositions;
1397 const Vector2* const positionsBuffer = positions.Begin();
1399 // Get the visual to logical conversion tables.
1400 const CharacterIndex* const visualToLogicalBuffer = ( 0u != mLogicalModel->mVisualToLogicalMap.Count() ) ? mLogicalModel->mVisualToLogicalMap.Begin() : NULL;
1401 const CharacterIndex* const visualToLogicalCursorBuffer = mLogicalModel->mVisualToLogicalCursorMap.Begin();
1403 // Get the character to glyph conversion table.
1404 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
1406 // Get the glyphs per character table.
1407 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
1408 const Length* const charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
1410 // If the vector is void, there is no right to left characters.
1411 const bool hasRightToLeftCharacters = NULL != visualToLogicalBuffer;
1413 const CharacterIndex startCharacter = line.characterRun.characterIndex;
1414 const CharacterIndex endCharacter = line.characterRun.characterIndex + line.characterRun.numberOfCharacters;
1415 DALI_ASSERT_DEBUG( endCharacter <= mLogicalModel->mText.Count() && "Invalid line info" );
1417 // Whether there is a hit on a glyph.
1418 bool matched = false;
1420 // Traverses glyphs in visual order. To do that use the visual to logical conversion table.
1421 CharacterIndex visualIndex = startCharacter;
1422 for( ; !matched && ( visualIndex < endCharacter ); ++visualIndex )
1424 // The character in logical order.
1425 const CharacterIndex characterLogicalOrderIndex = hasRightToLeftCharacters ? *( visualToLogicalBuffer + visualIndex ) : visualIndex;
1427 // Get the script of the character.
1428 const Script script = mLogicalModel->GetScript( characterLogicalOrderIndex );
1430 // The first glyph for that character in logical order.
1431 const GlyphIndex glyphLogicalOrderIndex = *( charactersToGlyphBuffer + characterLogicalOrderIndex );
1432 // The number of glyphs for that character
1433 const Length numberOfGlyphs = *( glyphsPerCharacterBuffer + characterLogicalOrderIndex );
1435 // Get the metrics for the group of glyphs.
1436 GlyphMetrics glyphMetrics;
1437 GetGlyphsMetrics( glyphLogicalOrderIndex,
1443 const Vector2& position = *( positionsBuffer + glyphLogicalOrderIndex );
1445 // Prevents to jump the whole Latin ligatures like fi, ff, or Arabic ï»»...
1446 const Length numberOfCharactersInLigature = HasLigatureMustBreak( script ) ? *( charactersPerGlyphBuffer + glyphLogicalOrderIndex ) : 1u;
1447 const float glyphAdvance = glyphMetrics.advance / static_cast<float>( numberOfCharactersInLigature );
1449 for( GlyphIndex index = 0u; !matched && ( index < numberOfCharactersInLigature ); ++index )
1451 // Find the mid-point of the area containing the glyph
1452 const float glyphCenter = -glyphMetrics.xBearing + position.x + ( static_cast<float>( index ) + 0.5f ) * glyphAdvance;
1454 if( visualX < glyphCenter )
1456 visualIndex += index;
1468 // Return the logical position of the cursor in characters.
1472 visualIndex = endCharacter;
1475 logicalIndex = hasRightToLeftCharacters ? *( visualToLogicalCursorBuffer + visualIndex ) : visualIndex;
1476 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "%p closest visualIndex %d logicalIndex %d\n", this, visualIndex, logicalIndex );
1478 DALI_ASSERT_DEBUG( ( logicalIndex <= mLogicalModel->mText.Count() && logicalIndex >= 0 ) && "GetClosestCursorIndex - Out of bounds index" );
1480 return logicalIndex;
1483 void Controller::Impl::GetCursorPosition( CharacterIndex logical,
1484 CursorInfo& cursorInfo )
1486 // TODO: Check for multiline with \n, etc...
1488 // Check if the logical position is the first or the last one of the text.
1489 const bool isFirstPosition = 0u == logical;
1490 const bool isLastPosition = mLogicalModel->mText.Count() == logical;
1492 if( isFirstPosition && isLastPosition )
1494 // There is zero characters. Get the default font's line height.
1495 cursorInfo.lineHeight = GetDefaultFontLineHeight();
1496 cursorInfo.primaryCursorHeight = cursorInfo.lineHeight;
1498 cursorInfo.primaryPosition.x = mEventData->mDecorator->GetCursorWidth();
1499 cursorInfo.primaryPosition.y = 0.f;
1501 // Nothing else to do.
1505 // 'logical' is the logical 'cursor' index.
1506 // Get the next and current logical 'character' index.
1507 const CharacterIndex nextCharacterIndex = logical;
1508 const CharacterIndex characterIndex = isFirstPosition ? logical : logical - 1u;
1510 // Get the direction of the character and the next one.
1511 const CharacterDirection* const modelCharacterDirectionsBuffer = ( 0u != mLogicalModel->mCharacterDirections.Count() ) ? mLogicalModel->mCharacterDirections.Begin() : NULL;
1513 CharacterDirection isCurrentRightToLeft = false;
1514 CharacterDirection isNextRightToLeft = false;
1515 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
1517 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + characterIndex );
1518 isNextRightToLeft = *( modelCharacterDirectionsBuffer + nextCharacterIndex );
1521 // Get the line where the character is laid-out.
1522 const LineRun* const modelLines = mVisualModel->mLines.Begin();
1524 const LineIndex lineIndex = mVisualModel->GetLineOfCharacter( characterIndex );
1525 const LineRun& line = *( modelLines + lineIndex );
1527 // Get the paragraph's direction.
1528 const CharacterDirection isRightToLeftParagraph = line.direction;
1530 // Check whether there is an alternative position:
1532 cursorInfo.isSecondaryCursor = ( !isLastPosition && ( isCurrentRightToLeft != isNextRightToLeft ) ) ||
1533 ( isLastPosition && ( isRightToLeftParagraph != isCurrentRightToLeft ) );
1535 // Set the line height.
1536 cursorInfo.lineHeight = line.ascender + -line.descender;
1538 // Calculate the primary cursor.
1540 CharacterIndex index = characterIndex;
1541 if( cursorInfo.isSecondaryCursor )
1543 // If there is a secondary position, the primary cursor may be in a different place than the logical index.
1545 if( isLastPosition )
1547 // The position of the cursor after the last character needs special
1548 // care depending on its direction and the direction of the paragraph.
1550 // Need to find the first character after the last character with the paragraph's direction.
1551 // i.e l0 l1 l2 r0 r1 should find r0.
1553 // TODO: check for more than one line!
1554 index = isRightToLeftParagraph ? line.characterRun.characterIndex : line.characterRun.characterIndex + line.characterRun.numberOfCharacters - 1u;
1555 index = mLogicalModel->GetLogicalCharacterIndex( index );
1559 index = ( isRightToLeftParagraph == isCurrentRightToLeft ) ? characterIndex : nextCharacterIndex;
1563 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
1564 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
1565 const Length* const charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
1566 const CharacterIndex* const glyphsToCharactersBuffer = mVisualModel->mGlyphsToCharacters.Begin();
1567 const Vector2* const glyphPositionsBuffer = mVisualModel->mGlyphPositions.Begin();
1569 // Convert the cursor position into the glyph position.
1570 const GlyphIndex primaryGlyphIndex = *( charactersToGlyphBuffer + index );
1571 const Length primaryNumberOfGlyphs = *( glyphsPerCharacterBuffer + index );
1572 const Length primaryNumberOfCharacters = *( charactersPerGlyphBuffer + primaryGlyphIndex );
1574 // Get the metrics for the group of glyphs.
1575 GlyphMetrics glyphMetrics;
1576 GetGlyphsMetrics( primaryGlyphIndex,
1577 primaryNumberOfGlyphs,
1582 // Whether to add the glyph's advance to the cursor position.
1583 // i.e if the paragraph is left to right and the logical cursor is zero, the position is the position of the first glyph and the advance is not added,
1584 // if the logical cursor is one, the position is the position of the first glyph and the advance is added.
1585 // A 'truth table' was build and an online Karnaugh map tool was used to simplify the logic.
1606 // Where F -> isFirstPosition
1607 // L -> isLastPosition
1608 // C -> isCurrentRightToLeft
1609 // P -> isRightToLeftParagraph
1610 // A -> Whether to add the glyph's advance.
1612 const bool addGlyphAdvance = ( ( isLastPosition && !isRightToLeftParagraph ) ||
1613 ( isFirstPosition && isRightToLeftParagraph ) ||
1614 ( !isFirstPosition && !isLastPosition && !isCurrentRightToLeft ) );
1616 float glyphAdvance = addGlyphAdvance ? glyphMetrics.advance : 0.f;
1618 if( !isLastPosition &&
1619 ( primaryNumberOfCharacters > 1u ) )
1621 const CharacterIndex firstIndex = *( glyphsToCharactersBuffer + primaryGlyphIndex );
1623 bool isCurrentRightToLeft = false;
1624 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
1626 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + index );
1629 Length numberOfGlyphAdvance = ( isFirstPosition ? 0u : 1u ) + characterIndex - firstIndex;
1630 if( isCurrentRightToLeft )
1632 numberOfGlyphAdvance = primaryNumberOfCharacters - numberOfGlyphAdvance;
1635 glyphAdvance = static_cast<float>( numberOfGlyphAdvance ) * glyphMetrics.advance / static_cast<float>( primaryNumberOfCharacters );
1638 // Get the glyph position and x bearing.
1639 const Vector2& primaryPosition = *( glyphPositionsBuffer + primaryGlyphIndex );
1641 // Set the primary cursor's height.
1642 cursorInfo.primaryCursorHeight = cursorInfo.isSecondaryCursor ? 0.5f * glyphMetrics.fontHeight : glyphMetrics.fontHeight;
1644 // Set the primary cursor's position.
1645 cursorInfo.primaryPosition.x = -glyphMetrics.xBearing + primaryPosition.x + glyphAdvance;
1646 cursorInfo.primaryPosition.y = line.ascender - glyphMetrics.ascender;
1648 // Calculate the secondary cursor.
1650 if( cursorInfo.isSecondaryCursor )
1652 // Set the secondary cursor's height.
1653 cursorInfo.secondaryCursorHeight = 0.5f * glyphMetrics.fontHeight;
1655 CharacterIndex index = characterIndex;
1656 if( !isLastPosition )
1658 index = ( isRightToLeftParagraph == isCurrentRightToLeft ) ? nextCharacterIndex : characterIndex;
1661 const GlyphIndex secondaryGlyphIndex = *( charactersToGlyphBuffer + index );
1662 const Length secondaryNumberOfGlyphs = *( glyphsPerCharacterBuffer + index );
1664 const Vector2& secondaryPosition = *( glyphPositionsBuffer + secondaryGlyphIndex );
1666 GetGlyphsMetrics( secondaryGlyphIndex,
1667 secondaryNumberOfGlyphs,
1672 // Set the secondary cursor's position.
1673 cursorInfo.secondaryPosition.x = -glyphMetrics.xBearing + secondaryPosition.x + ( isCurrentRightToLeft ? 0.f : glyphMetrics.advance );
1674 cursorInfo.secondaryPosition.y = cursorInfo.lineHeight - cursorInfo.secondaryCursorHeight - line.descender - ( glyphMetrics.fontHeight - glyphMetrics.ascender );
1678 CharacterIndex Controller::Impl::CalculateNewCursorIndex( CharacterIndex index ) const
1680 if( NULL == mEventData )
1682 // Nothing to do if there is no text input.
1686 CharacterIndex cursorIndex = mEventData->mPrimaryCursorPosition;
1688 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
1689 const Length* const charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
1691 GlyphIndex glyphIndex = *( charactersToGlyphBuffer + index );
1692 Length numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
1694 if( numberOfCharacters > 1u )
1696 const Script script = mLogicalModel->GetScript( index );
1697 if( HasLigatureMustBreak( script ) )
1699 // Prevents to jump the whole Latin ligatures like fi, ff, or Arabic ï»», ...
1700 numberOfCharacters = 1u;
1705 while( 0u == numberOfCharacters )
1708 numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
1712 if( index < mEventData->mPrimaryCursorPosition )
1714 cursorIndex -= numberOfCharacters;
1718 cursorIndex += numberOfCharacters;
1724 void Controller::Impl::UpdateCursorPosition()
1726 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "-->Controller::UpdateCursorPosition %p\n", this );
1727 if( NULL == mEventData )
1729 // Nothing to do if there is no text input.
1730 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition no event data\n" );
1734 if( IsShowingPlaceholderText() || ( 0u == mLogicalModel->mText.Count() ) )
1736 // Do not want to use the place-holder text to set the cursor position.
1738 // Use the line's height of the font's family set to set the cursor's size.
1739 // If there is no font's family set, use the default font.
1740 // Use the current alignment to place the cursor at the beginning, center or end of the box.
1742 float lineHeight = 0.f;
1744 FontId defaultFontId = 0u;
1745 if( NULL == mFontDefaults )
1747 defaultFontId = mFontClient.GetFontId( EMPTY_STRING,
1752 defaultFontId = mFontDefaults->GetFontId( mFontClient );
1755 Text::FontMetrics fontMetrics;
1756 mFontClient.GetFontMetrics( defaultFontId, fontMetrics );
1758 lineHeight = fontMetrics.ascender - fontMetrics.descender;
1761 Vector2 cursorPosition;
1763 switch( mLayoutEngine.GetHorizontalAlignment() )
1765 case LayoutEngine::HORIZONTAL_ALIGN_BEGIN:
1767 cursorPosition.x = mEventData->mDecorator->GetCursorWidth();
1770 case LayoutEngine::HORIZONTAL_ALIGN_CENTER:
1772 cursorPosition.x = floor( 0.5f * mVisualModel->mControlSize.width );
1775 case LayoutEngine::HORIZONTAL_ALIGN_END:
1777 cursorPosition.x = mVisualModel->mControlSize.width;
1782 switch( mLayoutEngine.GetVerticalAlignment() )
1784 case LayoutEngine::VERTICAL_ALIGN_TOP:
1786 cursorPosition.y = 0.f;
1789 case LayoutEngine::VERTICAL_ALIGN_CENTER:
1791 cursorPosition.y = floorf( 0.5f * ( mVisualModel->mControlSize.height - lineHeight ) );
1794 case LayoutEngine::VERTICAL_ALIGN_BOTTOM:
1796 cursorPosition.y = mVisualModel->mControlSize.height - lineHeight;
1801 mEventData->mDecorator->SetPosition( PRIMARY_CURSOR,
1809 CursorInfo cursorInfo;
1810 GetCursorPosition( mEventData->mPrimaryCursorPosition,
1813 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
1814 const Vector2 cursorPosition = cursorInfo.primaryPosition + offset;
1816 // Sets the cursor position.
1817 mEventData->mDecorator->SetPosition( PRIMARY_CURSOR,
1820 cursorInfo.primaryCursorHeight,
1821 cursorInfo.lineHeight );
1822 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Primary cursor position: %f,%f\n", cursorPosition.x, cursorPosition.y );
1824 // Sets the grab handle position.
1825 mEventData->mDecorator->SetPosition( GRAB_HANDLE,
1828 cursorInfo.lineHeight );
1830 if( cursorInfo.isSecondaryCursor )
1832 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_BOTH );
1833 mEventData->mDecorator->SetPosition( SECONDARY_CURSOR,
1834 cursorInfo.secondaryPosition.x + offset.x,
1835 cursorInfo.secondaryPosition.y + offset.y,
1836 cursorInfo.secondaryCursorHeight,
1837 cursorInfo.lineHeight );
1838 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Secondary cursor position: %f,%f\n", cursorInfo.secondaryPosition.x + offset.x, cursorInfo.secondaryPosition.y + offset.y );
1842 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1845 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition\n" );
1848 void Controller::Impl::UpdateSelectionHandle( HandleType handleType )
1850 if( ( LEFT_SELECTION_HANDLE != handleType ) &&
1851 ( RIGHT_SELECTION_HANDLE != handleType ) )
1856 const bool leftSelectionHandle = LEFT_SELECTION_HANDLE == handleType;
1857 const CharacterIndex index = leftSelectionHandle ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition;
1859 CursorInfo cursorInfo;
1860 GetCursorPosition( index,
1863 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
1864 const Vector2 cursorPosition = cursorInfo.primaryPosition + offset;
1866 // Sets the grab handle position.
1867 mEventData->mDecorator->SetPosition( handleType,
1870 cursorInfo.lineHeight );
1872 // If selection handle at start of the text and other at end of the text then all text is selected.
1873 const CharacterIndex startOfSelection = std::min( mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition );
1874 const CharacterIndex endOfSelection = std::max ( mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition );
1875 mEventData->mAllTextSelected = ( startOfSelection == 0 ) && ( endOfSelection == mLogicalModel->mText.Count() );
1878 void Controller::Impl::ClampHorizontalScroll( const Vector2& actualSize )
1880 // Clamp between -space & 0 (and the text alignment).
1881 if( actualSize.width > mVisualModel->mControlSize.width )
1883 const float space = ( actualSize.width - mVisualModel->mControlSize.width ) + mAlignmentOffset.x;
1884 mEventData->mScrollPosition.x = ( mEventData->mScrollPosition.x < -space ) ? -space : mEventData->mScrollPosition.x;
1885 mEventData->mScrollPosition.x = ( mEventData->mScrollPosition.x > -mAlignmentOffset.x ) ? -mAlignmentOffset.x : mEventData->mScrollPosition.x;
1887 mEventData->mDecoratorUpdated = true;
1891 mEventData->mScrollPosition.x = 0.f;
1895 void Controller::Impl::ClampVerticalScroll( const Vector2& actualSize )
1897 // Clamp between -space & 0 (and the text alignment).
1898 if( actualSize.height > mVisualModel->mControlSize.height )
1900 const float space = ( actualSize.height - mVisualModel->mControlSize.height ) + mAlignmentOffset.y;
1901 mEventData->mScrollPosition.y = ( mEventData->mScrollPosition.y < -space ) ? -space : mEventData->mScrollPosition.y;
1902 mEventData->mScrollPosition.y = ( mEventData->mScrollPosition.y > -mAlignmentOffset.y ) ? -mAlignmentOffset.y : mEventData->mScrollPosition.y;
1904 mEventData->mDecoratorUpdated = true;
1908 mEventData->mScrollPosition.y = 0.f;
1912 void Controller::Impl::ScrollToMakePositionVisible( const Vector2& position )
1915 bool updateDecorator = false;
1916 if( position.x < 0.f )
1918 offset.x = -position.x;
1919 mEventData->mScrollPosition.x += offset.x;
1920 updateDecorator = true;
1922 else if( position.x > mVisualModel->mControlSize.width )
1924 offset.x = mVisualModel->mControlSize.width - position.x;
1925 mEventData->mScrollPosition.x += offset.x;
1926 updateDecorator = true;
1929 if( updateDecorator && mEventData->mDecorator )
1931 mEventData->mDecorator->UpdatePositions( offset );
1934 // TODO : calculate the vertical scroll.
1937 void Controller::Impl::ScrollTextToMatchCursor()
1939 // Get the current cursor position in decorator coords.
1940 const Vector2& currentCursorPosition = mEventData->mDecorator->GetPosition( PRIMARY_CURSOR );
1942 // Calculate the new cursor position.
1943 CursorInfo cursorInfo;
1944 GetCursorPosition( mEventData->mPrimaryCursorPosition,
1947 // Calculate the offset to match the cursor position before the character was deleted.
1948 mEventData->mScrollPosition.x = currentCursorPosition.x - cursorInfo.primaryPosition.x - mAlignmentOffset.x;
1950 ClampHorizontalScroll( mVisualModel->GetActualSize() );
1952 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
1953 const Vector2 cursorPosition = cursorInfo.primaryPosition + offset;
1955 // Sets the cursor position.
1956 mEventData->mDecorator->SetPosition( PRIMARY_CURSOR,
1959 cursorInfo.primaryCursorHeight,
1960 cursorInfo.lineHeight );
1962 // Sets the grab handle position.
1963 mEventData->mDecorator->SetPosition( GRAB_HANDLE,
1966 cursorInfo.lineHeight );
1968 if( cursorInfo.isSecondaryCursor )
1970 mEventData->mDecorator->SetPosition( SECONDARY_CURSOR,
1971 cursorInfo.secondaryPosition.x + offset.x,
1972 cursorInfo.secondaryPosition.y + offset.y,
1973 cursorInfo.secondaryCursorHeight,
1974 cursorInfo.lineHeight );
1975 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Secondary cursor position: %f,%f\n", cursorInfo.secondaryPosition.x + offset.x, cursorInfo.secondaryPosition.y + offset.y );
1978 // Set which cursors are active according the state.
1979 if( ( EventData::EDITING == mEventData->mState ) ||
1980 ( EventData::EDITING_WITH_POPUP == mEventData->mState ) ||
1981 ( EventData::EDITING_WITH_GRAB_HANDLE == mEventData->mState ) ||
1982 ( EventData::GRAB_HANDLE_PANNING == mEventData->mState ) )
1984 if( cursorInfo.isSecondaryCursor )
1986 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_BOTH );
1990 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1995 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
1999 void Controller::Impl::RequestRelayout()
2001 mControlInterface.RequestTextRelayout();
2006 } // namespace Toolkit