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 if( hitCharacter >= mLogicalModel->mText.Count() )
1325 // Selection out of bounds.
1329 startIndex = hitCharacter;
1330 endIndex = hitCharacter;
1332 if( !TextAbstraction::IsWhiteSpace( mLogicalModel->mText[hitCharacter] ) )
1334 // Find the start and end of the text
1335 for( startIndex = hitCharacter; startIndex > 0; --startIndex )
1337 Character charCode = mLogicalModel->mText[ startIndex-1 ];
1338 if( TextAbstraction::IsWhiteSpace( charCode ) )
1343 const CharacterIndex pastTheEnd = mLogicalModel->mText.Count();
1344 for( endIndex = hitCharacter + 1u; endIndex < pastTheEnd; ++endIndex )
1346 Character charCode = mLogicalModel->mText[ endIndex ];
1347 if( TextAbstraction::IsWhiteSpace( charCode ) )
1355 CharacterIndex Controller::Impl::GetClosestCursorIndex( float visualX,
1358 if( NULL == mEventData )
1360 // Nothing to do if there is no text input.
1364 CharacterIndex logicalIndex = 0u;
1366 const Length numberOfGlyphs = mVisualModel->mGlyphs.Count();
1367 const Length numberOfLines = mVisualModel->mLines.Count();
1368 if( 0 == numberOfGlyphs ||
1369 0 == numberOfLines )
1371 return logicalIndex;
1374 // Find which line is closest
1375 const LineIndex lineIndex = GetClosestLine( visualY );
1376 const LineRun& line = mVisualModel->mLines[lineIndex];
1378 // Get the positions of the glyphs.
1379 const Vector<Vector2>& positions = mVisualModel->mGlyphPositions;
1380 const Vector2* const positionsBuffer = positions.Begin();
1382 // Get the visual to logical conversion tables.
1383 const CharacterIndex* const visualToLogicalBuffer = ( 0u != mLogicalModel->mVisualToLogicalMap.Count() ) ? mLogicalModel->mVisualToLogicalMap.Begin() : NULL;
1384 const CharacterIndex* const visualToLogicalCursorBuffer = mLogicalModel->mVisualToLogicalCursorMap.Begin();
1386 // Get the character to glyph conversion table.
1387 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
1389 // Get the glyphs per character table.
1390 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
1391 const Length* const charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
1393 // If the vector is void, there is no right to left characters.
1394 const bool hasRightToLeftCharacters = NULL != visualToLogicalBuffer;
1396 const CharacterIndex startCharacter = line.characterRun.characterIndex;
1397 const CharacterIndex endCharacter = line.characterRun.characterIndex + line.characterRun.numberOfCharacters;
1398 DALI_ASSERT_DEBUG( endCharacter <= mLogicalModel->mText.Count() && "Invalid line info" );
1400 // Whether there is a hit on a glyph.
1401 bool matched = false;
1403 // Traverses glyphs in visual order. To do that use the visual to logical conversion table.
1404 CharacterIndex visualIndex = startCharacter;
1405 for( ; !matched && ( visualIndex < endCharacter ); ++visualIndex )
1407 // The character in logical order.
1408 const CharacterIndex characterLogicalOrderIndex = hasRightToLeftCharacters ? *( visualToLogicalBuffer + visualIndex ) : visualIndex;
1410 // Get the script of the character.
1411 const Script script = mLogicalModel->GetScript( characterLogicalOrderIndex );
1413 // The first glyph for that character in logical order.
1414 const GlyphIndex glyphLogicalOrderIndex = *( charactersToGlyphBuffer + characterLogicalOrderIndex );
1415 // The number of glyphs for that character
1416 const Length numberOfGlyphs = *( glyphsPerCharacterBuffer + characterLogicalOrderIndex );
1418 // Get the metrics for the group of glyphs.
1419 GlyphMetrics glyphMetrics;
1420 GetGlyphsMetrics( glyphLogicalOrderIndex,
1426 const Vector2& position = *( positionsBuffer + glyphLogicalOrderIndex );
1428 // Prevents to jump the whole Latin ligatures like fi, ff, or Arabic ï»»...
1429 const Length numberOfCharactersInLigature = HasLigatureMustBreak( script ) ? *( charactersPerGlyphBuffer + glyphLogicalOrderIndex ) : 1u;
1430 const float glyphAdvance = glyphMetrics.advance / static_cast<float>( numberOfCharactersInLigature );
1432 for( GlyphIndex index = 0u; !matched && ( index < numberOfCharactersInLigature ); ++index )
1434 // Find the mid-point of the area containing the glyph
1435 const float glyphCenter = -glyphMetrics.xBearing + position.x + ( static_cast<float>( index ) + 0.5f ) * glyphAdvance;
1437 if( visualX < glyphCenter )
1439 visualIndex += index;
1451 // Return the logical position of the cursor in characters.
1455 visualIndex = endCharacter;
1458 logicalIndex = hasRightToLeftCharacters ? *( visualToLogicalCursorBuffer + visualIndex ) : visualIndex;
1459 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "%p closest visualIndex %d logicalIndex %d\n", this, visualIndex, logicalIndex );
1461 return logicalIndex;
1464 void Controller::Impl::GetCursorPosition( CharacterIndex logical,
1465 CursorInfo& cursorInfo )
1467 // TODO: Check for multiline with \n, etc...
1469 // Check if the logical position is the first or the last one of the text.
1470 const bool isFirstPosition = 0u == logical;
1471 const bool isLastPosition = mLogicalModel->mText.Count() == logical;
1473 if( isFirstPosition && isLastPosition )
1475 // There is zero characters. Get the default font's line height.
1476 cursorInfo.lineHeight = GetDefaultFontLineHeight();
1477 cursorInfo.primaryCursorHeight = cursorInfo.lineHeight;
1479 cursorInfo.primaryPosition.x = mEventData->mDecorator->GetCursorWidth();
1480 cursorInfo.primaryPosition.y = 0.f;
1482 // Nothing else to do.
1486 // 'logical' is the logical 'cursor' index.
1487 // Get the next and current logical 'character' index.
1488 const CharacterIndex nextCharacterIndex = logical;
1489 const CharacterIndex characterIndex = isFirstPosition ? logical : logical - 1u;
1491 // Get the direction of the character and the next one.
1492 const CharacterDirection* const modelCharacterDirectionsBuffer = ( 0u != mLogicalModel->mCharacterDirections.Count() ) ? mLogicalModel->mCharacterDirections.Begin() : NULL;
1494 CharacterDirection isCurrentRightToLeft = false;
1495 CharacterDirection isNextRightToLeft = false;
1496 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
1498 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + characterIndex );
1499 isNextRightToLeft = *( modelCharacterDirectionsBuffer + nextCharacterIndex );
1502 // Get the line where the character is laid-out.
1503 const LineRun* const modelLines = mVisualModel->mLines.Begin();
1505 const LineIndex lineIndex = mVisualModel->GetLineOfCharacter( characterIndex );
1506 const LineRun& line = *( modelLines + lineIndex );
1508 // Get the paragraph's direction.
1509 const CharacterDirection isRightToLeftParagraph = line.direction;
1511 // Check whether there is an alternative position:
1513 cursorInfo.isSecondaryCursor = ( !isLastPosition && ( isCurrentRightToLeft != isNextRightToLeft ) ) ||
1514 ( isLastPosition && ( isRightToLeftParagraph != isCurrentRightToLeft ) );
1516 // Set the line height.
1517 cursorInfo.lineHeight = line.ascender + -line.descender;
1519 // Calculate the primary cursor.
1521 CharacterIndex index = characterIndex;
1522 if( cursorInfo.isSecondaryCursor )
1524 // If there is a secondary position, the primary cursor may be in a different place than the logical index.
1526 if( isLastPosition )
1528 // The position of the cursor after the last character needs special
1529 // care depending on its direction and the direction of the paragraph.
1531 // Need to find the first character after the last character with the paragraph's direction.
1532 // i.e l0 l1 l2 r0 r1 should find r0.
1534 // TODO: check for more than one line!
1535 index = isRightToLeftParagraph ? line.characterRun.characterIndex : line.characterRun.characterIndex + line.characterRun.numberOfCharacters - 1u;
1536 index = mLogicalModel->GetLogicalCharacterIndex( index );
1540 index = ( isRightToLeftParagraph == isCurrentRightToLeft ) ? characterIndex : nextCharacterIndex;
1544 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
1545 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
1546 const Length* const charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
1547 const CharacterIndex* const glyphsToCharactersBuffer = mVisualModel->mGlyphsToCharacters.Begin();
1548 const Vector2* const glyphPositionsBuffer = mVisualModel->mGlyphPositions.Begin();
1550 // Convert the cursor position into the glyph position.
1551 const GlyphIndex primaryGlyphIndex = *( charactersToGlyphBuffer + index );
1552 const Length primaryNumberOfGlyphs = *( glyphsPerCharacterBuffer + index );
1553 const Length primaryNumberOfCharacters = *( charactersPerGlyphBuffer + primaryGlyphIndex );
1555 // Get the metrics for the group of glyphs.
1556 GlyphMetrics glyphMetrics;
1557 GetGlyphsMetrics( primaryGlyphIndex,
1558 primaryNumberOfGlyphs,
1563 // Whether to add the glyph's advance to the cursor position.
1564 // 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,
1565 // if the logical cursor is one, the position is the position of the first glyph and the advance is added.
1566 // A 'truth table' was build and an online Karnaugh map tool was used to simplify the logic.
1587 // Where F -> isFirstPosition
1588 // L -> isLastPosition
1589 // C -> isCurrentRightToLeft
1590 // P -> isRightToLeftParagraph
1591 // A -> Whether to add the glyph's advance.
1593 const bool addGlyphAdvance = ( ( isLastPosition && !isRightToLeftParagraph ) ||
1594 ( isFirstPosition && isRightToLeftParagraph ) ||
1595 ( !isFirstPosition && !isLastPosition && !isCurrentRightToLeft ) );
1597 float glyphAdvance = addGlyphAdvance ? glyphMetrics.advance : 0.f;
1599 if( !isLastPosition &&
1600 ( primaryNumberOfCharacters > 1u ) )
1602 const CharacterIndex firstIndex = *( glyphsToCharactersBuffer + primaryGlyphIndex );
1604 bool isCurrentRightToLeft = false;
1605 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
1607 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + index );
1610 Length numberOfGlyphAdvance = ( isFirstPosition ? 0u : 1u ) + characterIndex - firstIndex;
1611 if( isCurrentRightToLeft )
1613 numberOfGlyphAdvance = primaryNumberOfCharacters - numberOfGlyphAdvance;
1616 glyphAdvance = static_cast<float>( numberOfGlyphAdvance ) * glyphMetrics.advance / static_cast<float>( primaryNumberOfCharacters );
1619 // Get the glyph position and x bearing.
1620 const Vector2& primaryPosition = *( glyphPositionsBuffer + primaryGlyphIndex );
1622 // Set the primary cursor's height.
1623 cursorInfo.primaryCursorHeight = cursorInfo.isSecondaryCursor ? 0.5f * glyphMetrics.fontHeight : glyphMetrics.fontHeight;
1625 // Set the primary cursor's position.
1626 cursorInfo.primaryPosition.x = -glyphMetrics.xBearing + primaryPosition.x + glyphAdvance;
1627 cursorInfo.primaryPosition.y = line.ascender - glyphMetrics.ascender;
1629 // Calculate the secondary cursor.
1631 if( cursorInfo.isSecondaryCursor )
1633 // Set the secondary cursor's height.
1634 cursorInfo.secondaryCursorHeight = 0.5f * glyphMetrics.fontHeight;
1636 CharacterIndex index = characterIndex;
1637 if( !isLastPosition )
1639 index = ( isRightToLeftParagraph == isCurrentRightToLeft ) ? nextCharacterIndex : characterIndex;
1642 const GlyphIndex secondaryGlyphIndex = *( charactersToGlyphBuffer + index );
1643 const Length secondaryNumberOfGlyphs = *( glyphsPerCharacterBuffer + index );
1645 const Vector2& secondaryPosition = *( glyphPositionsBuffer + secondaryGlyphIndex );
1647 GetGlyphsMetrics( secondaryGlyphIndex,
1648 secondaryNumberOfGlyphs,
1653 // Set the secondary cursor's position.
1654 cursorInfo.secondaryPosition.x = -glyphMetrics.xBearing + secondaryPosition.x + ( isCurrentRightToLeft ? 0.f : glyphMetrics.advance );
1655 cursorInfo.secondaryPosition.y = cursorInfo.lineHeight - cursorInfo.secondaryCursorHeight - line.descender - ( glyphMetrics.fontHeight - glyphMetrics.ascender );
1659 CharacterIndex Controller::Impl::CalculateNewCursorIndex( CharacterIndex index ) const
1661 if( NULL == mEventData )
1663 // Nothing to do if there is no text input.
1667 CharacterIndex cursorIndex = mEventData->mPrimaryCursorPosition;
1669 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
1670 const Length* const charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
1672 GlyphIndex glyphIndex = *( charactersToGlyphBuffer + index );
1673 Length numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
1675 if( numberOfCharacters > 1u )
1677 const Script script = mLogicalModel->GetScript( index );
1678 if( HasLigatureMustBreak( script ) )
1680 // Prevents to jump the whole Latin ligatures like fi, ff, or Arabic ï»», ...
1681 numberOfCharacters = 1u;
1686 while( 0u == numberOfCharacters )
1689 numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
1693 if( index < mEventData->mPrimaryCursorPosition )
1695 cursorIndex -= numberOfCharacters;
1699 cursorIndex += numberOfCharacters;
1705 void Controller::Impl::UpdateCursorPosition()
1707 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "-->Controller::UpdateCursorPosition %p\n", this );
1708 if( NULL == mEventData )
1710 // Nothing to do if there is no text input.
1711 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition no event data\n" );
1715 if( IsShowingPlaceholderText() || ( 0u == mLogicalModel->mText.Count() ) )
1717 // Do not want to use the place-holder text to set the cursor position.
1719 // Use the line's height of the font's family set to set the cursor's size.
1720 // If there is no font's family set, use the default font.
1721 // Use the current alignment to place the cursor at the beginning, center or end of the box.
1723 float lineHeight = 0.f;
1725 FontId defaultFontId = 0u;
1726 if( NULL == mFontDefaults )
1728 defaultFontId = mFontClient.GetFontId( EMPTY_STRING,
1733 defaultFontId = mFontDefaults->GetFontId( mFontClient );
1736 Text::FontMetrics fontMetrics;
1737 mFontClient.GetFontMetrics( defaultFontId, fontMetrics );
1739 lineHeight = fontMetrics.ascender - fontMetrics.descender;
1742 Vector2 cursorPosition;
1744 switch( mLayoutEngine.GetHorizontalAlignment() )
1746 case LayoutEngine::HORIZONTAL_ALIGN_BEGIN:
1748 cursorPosition.x = mEventData->mDecorator->GetCursorWidth();
1751 case LayoutEngine::HORIZONTAL_ALIGN_CENTER:
1753 cursorPosition.x = floor( 0.5f * mVisualModel->mControlSize.width );
1756 case LayoutEngine::HORIZONTAL_ALIGN_END:
1758 cursorPosition.x = mVisualModel->mControlSize.width;
1763 switch( mLayoutEngine.GetVerticalAlignment() )
1765 case LayoutEngine::VERTICAL_ALIGN_TOP:
1767 cursorPosition.y = 0.f;
1770 case LayoutEngine::VERTICAL_ALIGN_CENTER:
1772 cursorPosition.y = floorf( 0.5f * ( mVisualModel->mControlSize.height - lineHeight ) );
1775 case LayoutEngine::VERTICAL_ALIGN_BOTTOM:
1777 cursorPosition.y = mVisualModel->mControlSize.height - lineHeight;
1782 mEventData->mDecorator->SetPosition( PRIMARY_CURSOR,
1790 CursorInfo cursorInfo;
1791 GetCursorPosition( mEventData->mPrimaryCursorPosition,
1794 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
1795 const Vector2 cursorPosition = cursorInfo.primaryPosition + offset;
1797 // Sets the cursor position.
1798 mEventData->mDecorator->SetPosition( PRIMARY_CURSOR,
1801 cursorInfo.primaryCursorHeight,
1802 cursorInfo.lineHeight );
1803 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Primary cursor position: %f,%f\n", cursorPosition.x, cursorPosition.y );
1805 // Sets the grab handle position.
1806 mEventData->mDecorator->SetPosition( GRAB_HANDLE,
1809 cursorInfo.lineHeight );
1811 if( cursorInfo.isSecondaryCursor )
1813 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_BOTH );
1814 mEventData->mDecorator->SetPosition( SECONDARY_CURSOR,
1815 cursorInfo.secondaryPosition.x + offset.x,
1816 cursorInfo.secondaryPosition.y + offset.y,
1817 cursorInfo.secondaryCursorHeight,
1818 cursorInfo.lineHeight );
1819 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Secondary cursor position: %f,%f\n", cursorInfo.secondaryPosition.x + offset.x, cursorInfo.secondaryPosition.y + offset.y );
1823 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1826 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition\n" );
1829 void Controller::Impl::UpdateSelectionHandle( HandleType handleType )
1831 if( ( LEFT_SELECTION_HANDLE != handleType ) &&
1832 ( RIGHT_SELECTION_HANDLE != handleType ) )
1837 const bool leftSelectionHandle = LEFT_SELECTION_HANDLE == handleType;
1838 const CharacterIndex index = leftSelectionHandle ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition;
1840 CursorInfo cursorInfo;
1841 GetCursorPosition( index,
1844 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
1845 const Vector2 cursorPosition = cursorInfo.primaryPosition + offset;
1847 // Sets the grab handle position.
1848 mEventData->mDecorator->SetPosition( handleType,
1851 cursorInfo.lineHeight );
1853 // If selection handle at start of the text and other at end of the text then all text is selected.
1854 const CharacterIndex startOfSelection = std::min( mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition );
1855 const CharacterIndex endOfSelection = std::max ( mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition );
1856 mEventData->mAllTextSelected = ( startOfSelection == 0 ) && ( endOfSelection == mLogicalModel->mText.Count() );
1859 void Controller::Impl::ClampHorizontalScroll( const Vector2& actualSize )
1861 // Clamp between -space & 0 (and the text alignment).
1862 if( actualSize.width > mVisualModel->mControlSize.width )
1864 const float space = ( actualSize.width - mVisualModel->mControlSize.width ) + mAlignmentOffset.x;
1865 mEventData->mScrollPosition.x = ( mEventData->mScrollPosition.x < -space ) ? -space : mEventData->mScrollPosition.x;
1866 mEventData->mScrollPosition.x = ( mEventData->mScrollPosition.x > -mAlignmentOffset.x ) ? -mAlignmentOffset.x : mEventData->mScrollPosition.x;
1868 mEventData->mDecoratorUpdated = true;
1872 mEventData->mScrollPosition.x = 0.f;
1876 void Controller::Impl::ClampVerticalScroll( const Vector2& actualSize )
1878 // Clamp between -space & 0 (and the text alignment).
1879 if( actualSize.height > mVisualModel->mControlSize.height )
1881 const float space = ( actualSize.height - mVisualModel->mControlSize.height ) + mAlignmentOffset.y;
1882 mEventData->mScrollPosition.y = ( mEventData->mScrollPosition.y < -space ) ? -space : mEventData->mScrollPosition.y;
1883 mEventData->mScrollPosition.y = ( mEventData->mScrollPosition.y > -mAlignmentOffset.y ) ? -mAlignmentOffset.y : mEventData->mScrollPosition.y;
1885 mEventData->mDecoratorUpdated = true;
1889 mEventData->mScrollPosition.y = 0.f;
1893 void Controller::Impl::ScrollToMakePositionVisible( const Vector2& position )
1896 bool updateDecorator = false;
1897 if( position.x < 0.f )
1899 offset.x = -position.x;
1900 mEventData->mScrollPosition.x += offset.x;
1901 updateDecorator = true;
1903 else if( position.x > mVisualModel->mControlSize.width )
1905 offset.x = mVisualModel->mControlSize.width - position.x;
1906 mEventData->mScrollPosition.x += offset.x;
1907 updateDecorator = true;
1910 if( updateDecorator && mEventData->mDecorator )
1912 mEventData->mDecorator->UpdatePositions( offset );
1915 // TODO : calculate the vertical scroll.
1918 void Controller::Impl::ScrollTextToMatchCursor()
1920 // Get the current cursor position in decorator coords.
1921 const Vector2& currentCursorPosition = mEventData->mDecorator->GetPosition( PRIMARY_CURSOR );
1923 // Calculate the new cursor position.
1924 CursorInfo cursorInfo;
1925 GetCursorPosition( mEventData->mPrimaryCursorPosition,
1928 // Calculate the offset to match the cursor position before the character was deleted.
1929 mEventData->mScrollPosition.x = currentCursorPosition.x - cursorInfo.primaryPosition.x - mAlignmentOffset.x;
1931 ClampHorizontalScroll( mVisualModel->GetActualSize() );
1933 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
1934 const Vector2 cursorPosition = cursorInfo.primaryPosition + offset;
1936 // Sets the cursor position.
1937 mEventData->mDecorator->SetPosition( PRIMARY_CURSOR,
1940 cursorInfo.primaryCursorHeight,
1941 cursorInfo.lineHeight );
1943 // Sets the grab handle position.
1944 mEventData->mDecorator->SetPosition( GRAB_HANDLE,
1947 cursorInfo.lineHeight );
1949 if( cursorInfo.isSecondaryCursor )
1951 mEventData->mDecorator->SetPosition( SECONDARY_CURSOR,
1952 cursorInfo.secondaryPosition.x + offset.x,
1953 cursorInfo.secondaryPosition.y + offset.y,
1954 cursorInfo.secondaryCursorHeight,
1955 cursorInfo.lineHeight );
1956 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Secondary cursor position: %f,%f\n", cursorInfo.secondaryPosition.x + offset.x, cursorInfo.secondaryPosition.y + offset.y );
1959 // Set which cursors are active according the state.
1960 if( ( EventData::EDITING == mEventData->mState ) ||
1961 ( EventData::EDITING_WITH_POPUP == mEventData->mState ) ||
1962 ( EventData::EDITING_WITH_GRAB_HANDLE == mEventData->mState ) ||
1963 ( EventData::GRAB_HANDLE_PANNING == mEventData->mState ) )
1965 if( cursorInfo.isSecondaryCursor )
1967 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_BOTH );
1971 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1976 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
1980 void Controller::Impl::RequestRelayout()
1982 mControlInterface.RequestTextRelayout();
1987 } // namespace Toolkit