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,
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,
1030 splitEndGlyph = false;
1034 const float xPosition = position.x - glyph.xBearing + offset.x;
1035 mEventData->mDecorator->AddHighlight( xPosition, offset.y, xPosition + glyph.advance, height );
1038 CursorInfo primaryCursorInfo;
1039 GetCursorPosition( mEventData->mLeftSelectionPosition,
1040 primaryCursorInfo );
1042 CursorInfo secondaryCursorInfo;
1043 GetCursorPosition( mEventData->mRightSelectionPosition,
1044 secondaryCursorInfo );
1046 const Vector2 primaryPosition = primaryCursorInfo.primaryPosition + offset;
1047 const Vector2 secondaryPosition = secondaryCursorInfo.primaryPosition + offset;
1049 mEventData->mDecorator->SetPosition( LEFT_SELECTION_HANDLE, primaryPosition.x, primaryPosition.y, primaryCursorInfo.lineHeight );
1051 mEventData->mDecorator->SetPosition( RIGHT_SELECTION_HANDLE, secondaryPosition.x, secondaryPosition.y, secondaryCursorInfo.lineHeight );
1053 // Set the flag to update the decorator.
1054 mEventData->mDecoratorUpdated = true;
1057 void Controller::Impl::RepositionSelectionHandles( float visualX, float visualY )
1059 if( NULL == mEventData )
1061 // Nothing to do if there is no text input.
1065 if( IsShowingPlaceholderText() )
1067 // Nothing to do if there is the place-holder text.
1071 const Length numberOfGlyphs = mVisualModel->mGlyphs.Count();
1072 const Length numberOfLines = mVisualModel->mLines.Count();
1073 if( 0 == numberOfGlyphs ||
1074 0 == numberOfLines )
1076 // Nothing to do if there is no text.
1080 // Find which word was selected
1081 CharacterIndex selectionStart( 0 );
1082 CharacterIndex selectionEnd( 0 );
1083 FindSelectionIndices( visualX, visualY, selectionStart, selectionEnd );
1084 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "%p selectionStart %d selectionEnd %d\n", this, selectionStart, selectionEnd );
1086 if( selectionStart == selectionEnd )
1088 ChangeState( EventData::EDITING );
1089 // Nothing to select. i.e. a white space, out of bounds
1093 RepositionSelectionHandles( selectionStart, selectionEnd );
1096 void Controller::Impl::SetPopupButtons()
1099 * Sets the Popup buttons to be shown depending on State.
1101 * If SELECTING : CUT & COPY + ( PASTE & CLIPBOARD if content available to paste )
1103 * If EDITING_WITH_POPUP : SELECT & SELECT_ALL
1106 TextSelectionPopup::Buttons buttonsToShow = TextSelectionPopup::NONE;
1108 if ( ( EventData::SELECTING == mEventData->mState ) || ( EventData::SELECTION_CHANGED == mEventData->mState ) )
1110 buttonsToShow = TextSelectionPopup::Buttons( TextSelectionPopup::CUT | TextSelectionPopup::COPY );
1112 if ( !IsClipboardEmpty() )
1114 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
1115 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::CLIPBOARD ) );
1118 if ( !mEventData->mAllTextSelected )
1120 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::SELECT_ALL ) );
1123 else if ( EventData::EDITING_WITH_POPUP == mEventData->mState )
1125 if ( mLogicalModel->mText.Count() && !IsShowingPlaceholderText())
1127 buttonsToShow = TextSelectionPopup::Buttons( TextSelectionPopup::SELECT | TextSelectionPopup::SELECT_ALL );
1130 if ( !IsClipboardEmpty() )
1132 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
1133 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::CLIPBOARD ) );
1137 mEventData->mDecorator->SetEnabledPopupButtons( buttonsToShow );
1140 void Controller::Impl::ChangeState( EventData::State newState )
1142 if( NULL == mEventData )
1144 // Nothing to do if there is no text input.
1148 if( mEventData->mState != newState )
1150 mEventData->mState = newState;
1152 if( EventData::INACTIVE == mEventData->mState )
1154 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
1155 mEventData->mDecorator->StopCursorBlink();
1156 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1157 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1158 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1159 mEventData->mDecorator->SetPopupActive( false );
1160 mEventData->mDecoratorUpdated = true;
1163 else if ( EventData::INTERRUPTED == mEventData->mState)
1165 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1166 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1167 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1168 mEventData->mDecorator->SetPopupActive( false );
1169 mEventData->mDecoratorUpdated = true;
1172 else if ( EventData::SELECTING == mEventData->mState )
1174 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
1175 mEventData->mDecorator->StopCursorBlink();
1176 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1177 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true );
1178 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true );
1179 if( mEventData->mGrabHandlePopupEnabled )
1182 mEventData->mDecorator->SetPopupActive( true );
1184 mEventData->mDecoratorUpdated = true;
1186 else if ( EventData::SELECTION_CHANGED == mEventData->mState )
1188 if( mEventData->mGrabHandlePopupEnabled )
1191 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
1192 mEventData->mDecorator->SetPopupActive( true );
1194 mEventData->mDecoratorUpdated = true;
1196 else if( EventData::EDITING == mEventData->mState )
1198 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1199 if( mEventData->mCursorBlinkEnabled )
1201 mEventData->mDecorator->StartCursorBlink();
1203 // Grab handle is not shown until a tap is received whilst EDITING
1204 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1205 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1206 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1207 if( mEventData->mGrabHandlePopupEnabled )
1209 mEventData->mDecorator->SetPopupActive( false );
1211 mEventData->mDecoratorUpdated = true;
1214 else if( EventData::EDITING_WITH_POPUP == mEventData->mState )
1216 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1217 if( mEventData->mCursorBlinkEnabled )
1219 mEventData->mDecorator->StartCursorBlink();
1221 if( mEventData->mSelectionEnabled )
1223 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1224 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1228 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
1230 if( mEventData->mGrabHandlePopupEnabled )
1233 mEventData->mDecorator->SetPopupActive( true );
1236 mEventData->mDecoratorUpdated = true;
1238 else if( EventData::EDITING_WITH_GRAB_HANDLE == mEventData->mState )
1240 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1241 if( mEventData->mCursorBlinkEnabled )
1243 mEventData->mDecorator->StartCursorBlink();
1245 // Grab handle is not shown until a tap is received whilst EDITING
1246 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
1247 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1248 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1249 if( mEventData->mGrabHandlePopupEnabled )
1251 mEventData->mDecorator->SetPopupActive( false );
1253 mEventData->mDecoratorUpdated = true;
1256 else if ( EventData::SELECTION_HANDLE_PANNING == mEventData->mState )
1258 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
1259 mEventData->mDecorator->StopCursorBlink();
1260 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1261 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true );
1262 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true );
1263 if( mEventData->mGrabHandlePopupEnabled )
1265 mEventData->mDecorator->SetPopupActive( false );
1267 mEventData->mDecoratorUpdated = true;
1269 else if ( EventData::GRAB_HANDLE_PANNING == mEventData->mState )
1271 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1272 if( mEventData->mCursorBlinkEnabled )
1274 mEventData->mDecorator->StartCursorBlink();
1276 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
1277 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1278 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1279 if( mEventData->mGrabHandlePopupEnabled )
1281 mEventData->mDecorator->SetPopupActive( false );
1283 mEventData->mDecoratorUpdated = true;
1288 LineIndex Controller::Impl::GetClosestLine( float y ) const
1290 float totalHeight = 0.f;
1291 LineIndex lineIndex = 0u;
1293 const Vector<LineRun>& lines = mVisualModel->mLines;
1294 for( LineIndex endLine = lines.Count();
1295 lineIndex < endLine;
1298 const LineRun& lineRun = lines[lineIndex];
1299 totalHeight += lineRun.ascender + -lineRun.descender;
1300 if( y < totalHeight )
1306 if( lineIndex == 0 )
1314 void Controller::Impl::FindSelectionIndices( float visualX, float visualY, CharacterIndex& startIndex, CharacterIndex& endIndex )
1316 CharacterIndex hitCharacter = GetClosestCursorIndex( visualX, visualY );
1317 if( hitCharacter >= mLogicalModel->mText.Count() )
1319 // Selection out of bounds.
1323 startIndex = hitCharacter;
1324 endIndex = hitCharacter;
1326 if( !TextAbstraction::IsWhiteSpace( mLogicalModel->mText[hitCharacter] ) )
1328 // Find the start and end of the text
1329 for( startIndex = hitCharacter; startIndex > 0; --startIndex )
1331 Character charCode = mLogicalModel->mText[ startIndex-1 ];
1332 if( TextAbstraction::IsWhiteSpace( charCode ) )
1337 const CharacterIndex pastTheEnd = mLogicalModel->mText.Count();
1338 for( endIndex = hitCharacter + 1u; endIndex < pastTheEnd; ++endIndex )
1340 Character charCode = mLogicalModel->mText[ endIndex ];
1341 if( TextAbstraction::IsWhiteSpace( charCode ) )
1349 CharacterIndex Controller::Impl::GetClosestCursorIndex( float visualX,
1352 if( NULL == mEventData )
1354 // Nothing to do if there is no text input.
1358 CharacterIndex logicalIndex = 0u;
1360 const Length numberOfGlyphs = mVisualModel->mGlyphs.Count();
1361 const Length numberOfLines = mVisualModel->mLines.Count();
1362 if( 0 == numberOfGlyphs ||
1363 0 == numberOfLines )
1365 return logicalIndex;
1368 // Find which line is closest
1369 const LineIndex lineIndex = GetClosestLine( visualY );
1370 const LineRun& line = mVisualModel->mLines[lineIndex];
1372 // Get the positions of the glyphs.
1373 const Vector<Vector2>& positions = mVisualModel->mGlyphPositions;
1374 const Vector2* const positionsBuffer = positions.Begin();
1376 // Get the visual to logical conversion tables.
1377 const CharacterIndex* const visualToLogicalBuffer = ( 0u != mLogicalModel->mVisualToLogicalMap.Count() ) ? mLogicalModel->mVisualToLogicalMap.Begin() : NULL;
1378 const CharacterIndex* const visualToLogicalCursorBuffer = mLogicalModel->mVisualToLogicalCursorMap.Begin();
1380 // Get the character to glyph conversion table.
1381 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
1383 // Get the glyphs per character table.
1384 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
1385 const Length* const charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
1387 // If the vector is void, there is no right to left characters.
1388 const bool hasRightToLeftCharacters = NULL != visualToLogicalBuffer;
1390 const CharacterIndex startCharacter = line.characterRun.characterIndex;
1391 const CharacterIndex endCharacter = line.characterRun.characterIndex + line.characterRun.numberOfCharacters;
1392 DALI_ASSERT_DEBUG( endCharacter <= mLogicalModel->mText.Count() && "Invalid line info" );
1394 // Whether there is a hit on a glyph.
1395 bool matched = false;
1397 // Traverses glyphs in visual order. To do that use the visual to logical conversion table.
1398 CharacterIndex visualIndex = startCharacter;
1399 for( ; !matched && ( visualIndex < endCharacter ); ++visualIndex )
1401 // The character in logical order.
1402 const CharacterIndex characterLogicalOrderIndex = hasRightToLeftCharacters ? *( visualToLogicalBuffer + visualIndex ) : visualIndex;
1404 // Get the script of the character.
1405 const Script script = mLogicalModel->GetScript( characterLogicalOrderIndex );
1407 // The first glyph for that character in logical order.
1408 const GlyphIndex glyphLogicalOrderIndex = *( charactersToGlyphBuffer + characterLogicalOrderIndex );
1409 // The number of glyphs for that character
1410 const Length numberOfGlyphs = *( glyphsPerCharacterBuffer + characterLogicalOrderIndex );
1412 // Get the metrics for the group of glyphs.
1413 GlyphMetrics glyphMetrics;
1414 GetGlyphsMetrics( glyphLogicalOrderIndex,
1420 const Vector2& position = *( positionsBuffer + glyphLogicalOrderIndex );
1422 // Prevents to jump the whole Latin ligatures like fi, ff, or Arabic ï»»...
1423 const Length numberOfCharactersInLigature = HasLigatureMustBreak( script ) ? *( charactersPerGlyphBuffer + glyphLogicalOrderIndex ) : 1u;
1424 const float glyphAdvance = glyphMetrics.advance / static_cast<float>( numberOfCharactersInLigature );
1426 for( GlyphIndex index = 0u; !matched && ( index < numberOfCharactersInLigature ); ++index )
1428 // Find the mid-point of the area containing the glyph
1429 const float glyphCenter = -glyphMetrics.xBearing + position.x + ( static_cast<float>( index ) + 0.5f ) * glyphAdvance;
1431 if( visualX < glyphCenter )
1433 visualIndex += index;
1445 // Return the logical position of the cursor in characters.
1449 visualIndex = endCharacter;
1452 logicalIndex = hasRightToLeftCharacters ? *( visualToLogicalCursorBuffer + visualIndex ) : visualIndex;
1453 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "%p closest visualIndex %d logicalIndex %d\n", this, visualIndex, logicalIndex );
1455 return logicalIndex;
1458 void Controller::Impl::GetCursorPosition( CharacterIndex logical,
1459 CursorInfo& cursorInfo )
1461 // TODO: Check for multiline with \n, etc...
1463 // Check if the logical position is the first or the last one of the text.
1464 const bool isFirstPosition = 0u == logical;
1465 const bool isLastPosition = mLogicalModel->mText.Count() == logical;
1467 if( isFirstPosition && isLastPosition )
1469 // There is zero characters. Get the default font's line height.
1470 cursorInfo.lineHeight = GetDefaultFontLineHeight();
1471 cursorInfo.primaryCursorHeight = cursorInfo.lineHeight;
1473 cursorInfo.primaryPosition.x = 1.f;
1474 cursorInfo.primaryPosition.y = 0.f;
1476 // Nothing else to do.
1480 // 'logical' is the logical 'cursor' index.
1481 // Get the next and current logical 'character' index.
1482 const CharacterIndex nextCharacterIndex = logical;
1483 const CharacterIndex characterIndex = isFirstPosition ? logical : logical - 1u;
1485 // Get the direction of the character and the next one.
1486 const CharacterDirection* const modelCharacterDirectionsBuffer = ( 0u != mLogicalModel->mCharacterDirections.Count() ) ? mLogicalModel->mCharacterDirections.Begin() : NULL;
1488 CharacterDirection isCurrentRightToLeft = false;
1489 CharacterDirection isNextRightToLeft = false;
1490 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
1492 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + characterIndex );
1493 isNextRightToLeft = *( modelCharacterDirectionsBuffer + nextCharacterIndex );
1496 // Get the line where the character is laid-out.
1497 const LineRun* const modelLines = mVisualModel->mLines.Begin();
1499 const LineIndex lineIndex = mVisualModel->GetLineOfCharacter( characterIndex );
1500 const LineRun& line = *( modelLines + lineIndex );
1502 // Get the paragraph's direction.
1503 const CharacterDirection isRightToLeftParagraph = line.direction;
1505 // Check whether there is an alternative position:
1507 cursorInfo.isSecondaryCursor = ( !isLastPosition && ( isCurrentRightToLeft != isNextRightToLeft ) ) ||
1508 ( isLastPosition && ( isRightToLeftParagraph != isCurrentRightToLeft ) );
1510 // Set the line height.
1511 cursorInfo.lineHeight = line.ascender + -line.descender;
1513 // Calculate the primary cursor.
1515 CharacterIndex index = characterIndex;
1516 if( cursorInfo.isSecondaryCursor )
1518 // If there is a secondary position, the primary cursor may be in a different place than the logical index.
1520 if( isLastPosition )
1522 // The position of the cursor after the last character needs special
1523 // care depending on its direction and the direction of the paragraph.
1525 // Need to find the first character after the last character with the paragraph's direction.
1526 // i.e l0 l1 l2 r0 r1 should find r0.
1528 // TODO: check for more than one line!
1529 index = isRightToLeftParagraph ? line.characterRun.characterIndex : line.characterRun.characterIndex + line.characterRun.numberOfCharacters - 1u;
1530 index = mLogicalModel->GetLogicalCharacterIndex( index );
1534 index = ( isRightToLeftParagraph == isCurrentRightToLeft ) ? characterIndex : nextCharacterIndex;
1538 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
1539 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
1540 const Length* const charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
1541 const CharacterIndex* const glyphsToCharactersBuffer = mVisualModel->mGlyphsToCharacters.Begin();
1542 const Vector2* const glyphPositionsBuffer = mVisualModel->mGlyphPositions.Begin();
1544 // Convert the cursor position into the glyph position.
1545 const GlyphIndex primaryGlyphIndex = *( charactersToGlyphBuffer + index );
1546 const Length primaryNumberOfGlyphs = *( glyphsPerCharacterBuffer + index );
1547 const Length primaryNumberOfCharacters = *( charactersPerGlyphBuffer + primaryGlyphIndex );
1549 // Get the metrics for the group of glyphs.
1550 GlyphMetrics glyphMetrics;
1551 GetGlyphsMetrics( primaryGlyphIndex,
1552 primaryNumberOfGlyphs,
1557 // Whether to add the glyph's advance to the cursor position.
1558 // 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,
1559 // if the logical cursor is one, the position is the position of the first glyph and the advance is added.
1560 // A 'truth table' was build and an online Karnaugh map tool was used to simplify the logic.
1581 // Where F -> isFirstPosition
1582 // L -> isLastPosition
1583 // C -> isCurrentRightToLeft
1584 // P -> isRightToLeftParagraph
1585 // A -> Whether to add the glyph's advance.
1587 const bool addGlyphAdvance = ( ( isLastPosition && !isRightToLeftParagraph ) ||
1588 ( isFirstPosition && isRightToLeftParagraph ) ||
1589 ( !isFirstPosition && !isLastPosition && !isCurrentRightToLeft ) );
1591 float glyphAdvance = addGlyphAdvance ? glyphMetrics.advance : 0.f;
1593 if( !isLastPosition &&
1594 ( primaryNumberOfCharacters > 1u ) )
1596 const CharacterIndex firstIndex = *( glyphsToCharactersBuffer + primaryGlyphIndex );
1598 bool isCurrentRightToLeft = false;
1599 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
1601 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + index );
1604 Length numberOfGlyphAdvance = ( isFirstPosition ? 0u : 1u ) + characterIndex - firstIndex;
1605 if( isCurrentRightToLeft )
1607 numberOfGlyphAdvance = primaryNumberOfCharacters - numberOfGlyphAdvance;
1610 glyphAdvance = static_cast<float>( numberOfGlyphAdvance ) * glyphMetrics.advance / static_cast<float>( primaryNumberOfCharacters );
1613 // Get the glyph position and x bearing.
1614 const Vector2& primaryPosition = *( glyphPositionsBuffer + primaryGlyphIndex );
1616 // Set the primary cursor's height.
1617 cursorInfo.primaryCursorHeight = cursorInfo.isSecondaryCursor ? 0.5f * glyphMetrics.fontHeight : glyphMetrics.fontHeight;
1619 // Set the primary cursor's position.
1620 cursorInfo.primaryPosition.x = -glyphMetrics.xBearing + primaryPosition.x + glyphAdvance;
1621 cursorInfo.primaryPosition.y = line.ascender - glyphMetrics.ascender;
1623 // Calculate the secondary cursor.
1625 if( cursorInfo.isSecondaryCursor )
1627 // Set the secondary cursor's height.
1628 cursorInfo.secondaryCursorHeight = 0.5f * glyphMetrics.fontHeight;
1630 CharacterIndex index = characterIndex;
1631 if( !isLastPosition )
1633 index = ( isRightToLeftParagraph == isCurrentRightToLeft ) ? nextCharacterIndex : characterIndex;
1636 const GlyphIndex secondaryGlyphIndex = *( charactersToGlyphBuffer + index );
1637 const Length secondaryNumberOfGlyphs = *( glyphsPerCharacterBuffer + index );
1639 const Vector2& secondaryPosition = *( glyphPositionsBuffer + secondaryGlyphIndex );
1641 GetGlyphsMetrics( secondaryGlyphIndex,
1642 secondaryNumberOfGlyphs,
1647 // Set the secondary cursor's position.
1648 cursorInfo.secondaryPosition.x = -glyphMetrics.xBearing + secondaryPosition.x + ( isCurrentRightToLeft ? 0.f : glyphMetrics.advance );
1649 cursorInfo.secondaryPosition.y = cursorInfo.lineHeight - cursorInfo.secondaryCursorHeight - line.descender - ( glyphMetrics.fontHeight - glyphMetrics.ascender );
1653 CharacterIndex Controller::Impl::CalculateNewCursorIndex( CharacterIndex index ) const
1655 if( NULL == mEventData )
1657 // Nothing to do if there is no text input.
1661 CharacterIndex cursorIndex = mEventData->mPrimaryCursorPosition;
1663 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
1664 const Length* const charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
1666 GlyphIndex glyphIndex = *( charactersToGlyphBuffer + index );
1667 Length numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
1669 if( numberOfCharacters > 1u )
1671 const Script script = mLogicalModel->GetScript( index );
1672 if( HasLigatureMustBreak( script ) )
1674 // Prevents to jump the whole Latin ligatures like fi, ff, or Arabic ï»», ...
1675 numberOfCharacters = 1u;
1680 while( 0u == numberOfCharacters )
1683 numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
1687 if( index < mEventData->mPrimaryCursorPosition )
1689 cursorIndex -= numberOfCharacters;
1693 cursorIndex += numberOfCharacters;
1699 void Controller::Impl::UpdateCursorPosition()
1701 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "-->Controller::UpdateCursorPosition %p\n", this );
1702 if( NULL == mEventData )
1704 // Nothing to do if there is no text input.
1705 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition no event data\n" );
1709 if( IsShowingPlaceholderText() )
1711 // Do not want to use the place-holder text to set the cursor position.
1713 // Use the line's height of the font's family set to set the cursor's size.
1714 // If there is no font's family set, use the default font.
1715 // Use the current alignment to place the cursor at the beginning, center or end of the box.
1717 float lineHeight = 0.f;
1719 FontId defaultFontId = 0u;
1720 if( NULL == mFontDefaults )
1722 defaultFontId = mFontClient.GetFontId( EMPTY_STRING,
1727 defaultFontId = mFontDefaults->GetFontId( mFontClient );
1730 Text::FontMetrics fontMetrics;
1731 mFontClient.GetFontMetrics( defaultFontId, fontMetrics );
1733 lineHeight = fontMetrics.ascender - fontMetrics.descender;
1736 Vector2 cursorPosition;
1738 switch( mLayoutEngine.GetHorizontalAlignment() )
1740 case LayoutEngine::HORIZONTAL_ALIGN_BEGIN:
1742 cursorPosition.x = 1.f;
1745 case LayoutEngine::HORIZONTAL_ALIGN_CENTER:
1747 cursorPosition.x = floor( 0.5f * mVisualModel->mControlSize.width );
1750 case LayoutEngine::HORIZONTAL_ALIGN_END:
1752 cursorPosition.x = mVisualModel->mControlSize.width;
1757 switch( mLayoutEngine.GetVerticalAlignment() )
1759 case LayoutEngine::VERTICAL_ALIGN_TOP:
1761 cursorPosition.y = 0.f;
1764 case LayoutEngine::VERTICAL_ALIGN_CENTER:
1766 cursorPosition.y = floorf( 0.5f * ( mVisualModel->mControlSize.height - lineHeight ) );
1769 case LayoutEngine::VERTICAL_ALIGN_BOTTOM:
1771 cursorPosition.y = mVisualModel->mControlSize.height - lineHeight;
1776 mEventData->mDecorator->SetPosition( PRIMARY_CURSOR,
1784 CursorInfo cursorInfo;
1785 GetCursorPosition( mEventData->mPrimaryCursorPosition,
1788 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
1789 const Vector2 cursorPosition = cursorInfo.primaryPosition + offset;
1791 // Sets the cursor position.
1792 mEventData->mDecorator->SetPosition( PRIMARY_CURSOR,
1795 cursorInfo.primaryCursorHeight,
1796 cursorInfo.lineHeight );
1797 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Primary cursor position: %f,%f\n", cursorPosition.x, cursorPosition.y );
1799 // Sets the grab handle position.
1800 mEventData->mDecorator->SetPosition( GRAB_HANDLE,
1803 cursorInfo.lineHeight );
1805 if( cursorInfo.isSecondaryCursor )
1807 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_BOTH );
1808 mEventData->mDecorator->SetPosition( SECONDARY_CURSOR,
1809 cursorInfo.secondaryPosition.x + offset.x,
1810 cursorInfo.secondaryPosition.y + offset.y,
1811 cursorInfo.secondaryCursorHeight,
1812 cursorInfo.lineHeight );
1813 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Secondary cursor position: %f,%f\n", cursorInfo.secondaryPosition.x + offset.x, cursorInfo.secondaryPosition.y + offset.y );
1817 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1820 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition\n" );
1823 void Controller::Impl::UpdateSelectionHandle( HandleType handleType )
1825 if( ( LEFT_SELECTION_HANDLE != handleType ) &&
1826 ( RIGHT_SELECTION_HANDLE != handleType ) )
1831 const bool leftSelectionHandle = LEFT_SELECTION_HANDLE == handleType;
1832 const CharacterIndex index = leftSelectionHandle ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition;
1834 CursorInfo cursorInfo;
1835 GetCursorPosition( index,
1838 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
1839 const Vector2 cursorPosition = cursorInfo.primaryPosition + offset;
1841 // Sets the grab handle position.
1842 mEventData->mDecorator->SetPosition( handleType,
1845 cursorInfo.lineHeight );
1847 // If selection handle at start of the text and other at end of the text then all text is selected.
1848 const CharacterIndex startOfSelection = std::min( mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition );
1849 const CharacterIndex endOfSelection = std::max ( mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition );
1850 mEventData->mAllTextSelected = ( startOfSelection == 0 ) && ( endOfSelection == mLogicalModel->mText.Count() );
1853 void Controller::Impl::ClampHorizontalScroll( const Vector2& actualSize )
1855 // Clamp between -space & 0 (and the text alignment).
1856 if( actualSize.width > mVisualModel->mControlSize.width )
1858 const float space = ( actualSize.width - mVisualModel->mControlSize.width ) + mAlignmentOffset.x;
1859 mEventData->mScrollPosition.x = ( mEventData->mScrollPosition.x < -space ) ? -space : mEventData->mScrollPosition.x;
1860 mEventData->mScrollPosition.x = ( mEventData->mScrollPosition.x > -mAlignmentOffset.x ) ? -mAlignmentOffset.x : mEventData->mScrollPosition.x;
1862 mEventData->mDecoratorUpdated = true;
1866 mEventData->mScrollPosition.x = 0.f;
1870 void Controller::Impl::ClampVerticalScroll( const Vector2& actualSize )
1872 // Clamp between -space & 0 (and the text alignment).
1873 if( actualSize.height > mVisualModel->mControlSize.height )
1875 const float space = ( actualSize.height - mVisualModel->mControlSize.height ) + mAlignmentOffset.y;
1876 mEventData->mScrollPosition.y = ( mEventData->mScrollPosition.y < -space ) ? -space : mEventData->mScrollPosition.y;
1877 mEventData->mScrollPosition.y = ( mEventData->mScrollPosition.y > -mAlignmentOffset.y ) ? -mAlignmentOffset.y : mEventData->mScrollPosition.y;
1879 mEventData->mDecoratorUpdated = true;
1883 mEventData->mScrollPosition.y = 0.f;
1887 void Controller::Impl::ScrollToMakePositionVisible( const Vector2& position )
1890 bool updateDecorator = false;
1891 if( position.x < 0.f )
1893 offset.x = -position.x;
1894 mEventData->mScrollPosition.x += offset.x;
1895 updateDecorator = true;
1897 else if( position.x > mVisualModel->mControlSize.width )
1899 offset.x = mVisualModel->mControlSize.width - position.x;
1900 mEventData->mScrollPosition.x += offset.x;
1901 updateDecorator = true;
1904 if( updateDecorator && mEventData->mDecorator )
1906 mEventData->mDecorator->UpdatePositions( offset );
1909 // TODO : calculate the vertical scroll.
1912 void Controller::Impl::ScrollTextToMatchCursor()
1914 // Get the current cursor position in decorator coords.
1915 const Vector2& currentCursorPosition = mEventData->mDecorator->GetPosition( PRIMARY_CURSOR );
1917 // Calculate the new cursor position.
1918 CursorInfo cursorInfo;
1919 GetCursorPosition( mEventData->mPrimaryCursorPosition,
1922 // Calculate the offset to match the cursor position before the character was deleted.
1923 mEventData->mScrollPosition.x = currentCursorPosition.x - cursorInfo.primaryPosition.x - mAlignmentOffset.x;
1925 ClampHorizontalScroll( mVisualModel->GetActualSize() );
1927 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
1928 const Vector2 cursorPosition = cursorInfo.primaryPosition + offset;
1930 // Sets the cursor position.
1931 mEventData->mDecorator->SetPosition( PRIMARY_CURSOR,
1934 cursorInfo.primaryCursorHeight,
1935 cursorInfo.lineHeight );
1937 // Sets the grab handle position.
1938 mEventData->mDecorator->SetPosition( GRAB_HANDLE,
1941 cursorInfo.lineHeight );
1943 if( cursorInfo.isSecondaryCursor )
1945 mEventData->mDecorator->SetPosition( SECONDARY_CURSOR,
1946 cursorInfo.secondaryPosition.x + offset.x,
1947 cursorInfo.secondaryPosition.y + offset.y,
1948 cursorInfo.secondaryCursorHeight,
1949 cursorInfo.lineHeight );
1950 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Secondary cursor position: %f,%f\n", cursorInfo.secondaryPosition.x + offset.x, cursorInfo.secondaryPosition.y + offset.y );
1953 // Set which cursors are active according the state.
1954 if( ( EventData::EDITING == mEventData->mState ) ||
1955 ( EventData::EDITING_WITH_POPUP == mEventData->mState ) ||
1956 ( EventData::GRAB_HANDLE_PANNING == mEventData->mState ) )
1958 if( cursorInfo.isSecondaryCursor )
1960 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_BOTH );
1964 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1969 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
1973 void Controller::Impl::RequestRelayout()
1975 mControlInterface.RequestTextRelayout();
1980 } // namespace Toolkit