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 // Set the flag to update the decorator.
1057 mEventData->mDecoratorUpdated = true;
1060 void Controller::Impl::RepositionSelectionHandles( float visualX, float visualY )
1062 if( NULL == mEventData )
1064 // Nothing to do if there is no text input.
1068 if( IsShowingPlaceholderText() )
1070 // Nothing to do if there is the place-holder text.
1074 const Length numberOfGlyphs = mVisualModel->mGlyphs.Count();
1075 const Length numberOfLines = mVisualModel->mLines.Count();
1076 if( 0 == numberOfGlyphs ||
1077 0 == numberOfLines )
1079 // Nothing to do if there is no text.
1083 // Find which word was selected
1084 CharacterIndex selectionStart( 0 );
1085 CharacterIndex selectionEnd( 0 );
1086 FindSelectionIndices( visualX, visualY, selectionStart, selectionEnd );
1087 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "%p selectionStart %d selectionEnd %d\n", this, selectionStart, selectionEnd );
1089 if( selectionStart == selectionEnd )
1091 ChangeState( EventData::EDITING );
1092 // Nothing to select. i.e. a white space, out of bounds
1096 RepositionSelectionHandles( selectionStart, selectionEnd );
1099 void Controller::Impl::SetPopupButtons()
1102 * Sets the Popup buttons to be shown depending on State.
1104 * If SELECTING : CUT & COPY + ( PASTE & CLIPBOARD if content available to paste )
1106 * If EDITING_WITH_POPUP : SELECT & SELECT_ALL
1109 TextSelectionPopup::Buttons buttonsToShow = TextSelectionPopup::NONE;
1111 if ( ( EventData::SELECTING == mEventData->mState ) || ( EventData::SELECTION_CHANGED == mEventData->mState ) )
1113 buttonsToShow = TextSelectionPopup::Buttons( TextSelectionPopup::CUT | TextSelectionPopup::COPY );
1115 if ( !IsClipboardEmpty() )
1117 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
1118 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::CLIPBOARD ) );
1121 if ( !mEventData->mAllTextSelected )
1123 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::SELECT_ALL ) );
1126 else if ( EventData::EDITING_WITH_POPUP == mEventData->mState )
1128 if ( mLogicalModel->mText.Count() && !IsShowingPlaceholderText())
1130 buttonsToShow = TextSelectionPopup::Buttons( TextSelectionPopup::SELECT | TextSelectionPopup::SELECT_ALL );
1133 if ( !IsClipboardEmpty() )
1135 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
1136 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::CLIPBOARD ) );
1140 mEventData->mDecorator->SetEnabledPopupButtons( buttonsToShow );
1143 void Controller::Impl::ChangeState( EventData::State newState )
1145 if( NULL == mEventData )
1147 // Nothing to do if there is no text input.
1151 if( mEventData->mState != newState )
1153 mEventData->mState = newState;
1155 if( EventData::INACTIVE == mEventData->mState )
1157 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
1158 mEventData->mDecorator->StopCursorBlink();
1159 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1160 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1161 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1162 mEventData->mDecorator->SetPopupActive( false );
1163 mEventData->mDecoratorUpdated = true;
1166 else if ( EventData::INTERRUPTED == mEventData->mState)
1168 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1169 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1170 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1171 mEventData->mDecorator->SetPopupActive( false );
1172 mEventData->mDecoratorUpdated = true;
1175 else if ( EventData::SELECTING == mEventData->mState )
1177 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
1178 mEventData->mDecorator->StopCursorBlink();
1179 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1180 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true );
1181 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true );
1182 if( mEventData->mGrabHandlePopupEnabled )
1185 mEventData->mDecorator->SetPopupActive( true );
1187 mEventData->mDecoratorUpdated = true;
1189 else if ( EventData::SELECTION_CHANGED == mEventData->mState )
1191 if( mEventData->mGrabHandlePopupEnabled )
1194 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
1195 mEventData->mDecorator->SetPopupActive( true );
1197 mEventData->mDecoratorUpdated = true;
1199 else if( EventData::EDITING == mEventData->mState )
1201 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1202 if( mEventData->mCursorBlinkEnabled )
1204 mEventData->mDecorator->StartCursorBlink();
1206 // Grab handle is not shown until a tap is received whilst EDITING
1207 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1208 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1209 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1210 if( mEventData->mGrabHandlePopupEnabled )
1212 mEventData->mDecorator->SetPopupActive( false );
1214 mEventData->mDecoratorUpdated = true;
1217 else if( EventData::EDITING_WITH_POPUP == mEventData->mState )
1219 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1220 if( mEventData->mCursorBlinkEnabled )
1222 mEventData->mDecorator->StartCursorBlink();
1224 if( mEventData->mSelectionEnabled )
1226 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1227 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1231 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
1233 if( mEventData->mGrabHandlePopupEnabled )
1236 mEventData->mDecorator->SetPopupActive( true );
1239 mEventData->mDecoratorUpdated = true;
1241 else if( EventData::EDITING_WITH_GRAB_HANDLE == mEventData->mState )
1243 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1244 if( mEventData->mCursorBlinkEnabled )
1246 mEventData->mDecorator->StartCursorBlink();
1248 // Grab handle is not shown until a tap is received whilst EDITING
1249 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
1250 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1251 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1252 if( mEventData->mGrabHandlePopupEnabled )
1254 mEventData->mDecorator->SetPopupActive( false );
1256 mEventData->mDecoratorUpdated = true;
1259 else if ( EventData::SELECTION_HANDLE_PANNING == mEventData->mState )
1261 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
1262 mEventData->mDecorator->StopCursorBlink();
1263 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1264 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true );
1265 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true );
1266 if( mEventData->mGrabHandlePopupEnabled )
1268 mEventData->mDecorator->SetPopupActive( false );
1270 mEventData->mDecoratorUpdated = true;
1272 else if ( EventData::GRAB_HANDLE_PANNING == mEventData->mState )
1274 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1275 if( mEventData->mCursorBlinkEnabled )
1277 mEventData->mDecorator->StartCursorBlink();
1279 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
1280 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1281 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1282 if( mEventData->mGrabHandlePopupEnabled )
1284 mEventData->mDecorator->SetPopupActive( false );
1286 mEventData->mDecoratorUpdated = true;
1291 LineIndex Controller::Impl::GetClosestLine( float y ) const
1293 float totalHeight = 0.f;
1294 LineIndex lineIndex = 0u;
1296 const Vector<LineRun>& lines = mVisualModel->mLines;
1297 for( LineIndex endLine = lines.Count();
1298 lineIndex < endLine;
1301 const LineRun& lineRun = lines[lineIndex];
1302 totalHeight += lineRun.ascender + -lineRun.descender;
1303 if( y < totalHeight )
1309 if( lineIndex == 0 )
1317 void Controller::Impl::FindSelectionIndices( float visualX, float visualY, CharacterIndex& startIndex, CharacterIndex& endIndex )
1319 CharacterIndex hitCharacter = GetClosestCursorIndex( visualX, visualY );
1320 if( hitCharacter >= mLogicalModel->mText.Count() )
1322 // Selection out of bounds.
1326 startIndex = hitCharacter;
1327 endIndex = hitCharacter;
1329 if( !TextAbstraction::IsWhiteSpace( mLogicalModel->mText[hitCharacter] ) )
1331 // Find the start and end of the text
1332 for( startIndex = hitCharacter; startIndex > 0; --startIndex )
1334 Character charCode = mLogicalModel->mText[ startIndex-1 ];
1335 if( TextAbstraction::IsWhiteSpace( charCode ) )
1340 const CharacterIndex pastTheEnd = mLogicalModel->mText.Count();
1341 for( endIndex = hitCharacter + 1u; endIndex < pastTheEnd; ++endIndex )
1343 Character charCode = mLogicalModel->mText[ endIndex ];
1344 if( TextAbstraction::IsWhiteSpace( charCode ) )
1352 CharacterIndex Controller::Impl::GetClosestCursorIndex( float visualX,
1355 if( NULL == mEventData )
1357 // Nothing to do if there is no text input.
1361 CharacterIndex logicalIndex = 0u;
1363 const Length numberOfGlyphs = mVisualModel->mGlyphs.Count();
1364 const Length numberOfLines = mVisualModel->mLines.Count();
1365 if( 0 == numberOfGlyphs ||
1366 0 == numberOfLines )
1368 return logicalIndex;
1371 // Find which line is closest
1372 const LineIndex lineIndex = GetClosestLine( visualY );
1373 const LineRun& line = mVisualModel->mLines[lineIndex];
1375 // Get the positions of the glyphs.
1376 const Vector<Vector2>& positions = mVisualModel->mGlyphPositions;
1377 const Vector2* const positionsBuffer = positions.Begin();
1379 // Get the visual to logical conversion tables.
1380 const CharacterIndex* const visualToLogicalBuffer = ( 0u != mLogicalModel->mVisualToLogicalMap.Count() ) ? mLogicalModel->mVisualToLogicalMap.Begin() : NULL;
1381 const CharacterIndex* const visualToLogicalCursorBuffer = mLogicalModel->mVisualToLogicalCursorMap.Begin();
1383 // Get the character to glyph conversion table.
1384 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
1386 // Get the glyphs per character table.
1387 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
1388 const Length* const charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
1390 // If the vector is void, there is no right to left characters.
1391 const bool hasRightToLeftCharacters = NULL != visualToLogicalBuffer;
1393 const CharacterIndex startCharacter = line.characterRun.characterIndex;
1394 const CharacterIndex endCharacter = line.characterRun.characterIndex + line.characterRun.numberOfCharacters;
1395 DALI_ASSERT_DEBUG( endCharacter <= mLogicalModel->mText.Count() && "Invalid line info" );
1397 // Whether there is a hit on a glyph.
1398 bool matched = false;
1400 // Traverses glyphs in visual order. To do that use the visual to logical conversion table.
1401 CharacterIndex visualIndex = startCharacter;
1402 for( ; !matched && ( visualIndex < endCharacter ); ++visualIndex )
1404 // The character in logical order.
1405 const CharacterIndex characterLogicalOrderIndex = hasRightToLeftCharacters ? *( visualToLogicalBuffer + visualIndex ) : visualIndex;
1407 // Get the script of the character.
1408 const Script script = mLogicalModel->GetScript( characterLogicalOrderIndex );
1410 // The first glyph for that character in logical order.
1411 const GlyphIndex glyphLogicalOrderIndex = *( charactersToGlyphBuffer + characterLogicalOrderIndex );
1412 // The number of glyphs for that character
1413 const Length numberOfGlyphs = *( glyphsPerCharacterBuffer + characterLogicalOrderIndex );
1415 // Get the metrics for the group of glyphs.
1416 GlyphMetrics glyphMetrics;
1417 GetGlyphsMetrics( glyphLogicalOrderIndex,
1423 const Vector2& position = *( positionsBuffer + glyphLogicalOrderIndex );
1425 // Prevents to jump the whole Latin ligatures like fi, ff, or Arabic ï»»...
1426 const Length numberOfCharactersInLigature = HasLigatureMustBreak( script ) ? *( charactersPerGlyphBuffer + glyphLogicalOrderIndex ) : 1u;
1427 const float glyphAdvance = glyphMetrics.advance / static_cast<float>( numberOfCharactersInLigature );
1429 for( GlyphIndex index = 0u; !matched && ( index < numberOfCharactersInLigature ); ++index )
1431 // Find the mid-point of the area containing the glyph
1432 const float glyphCenter = -glyphMetrics.xBearing + position.x + ( static_cast<float>( index ) + 0.5f ) * glyphAdvance;
1434 if( visualX < glyphCenter )
1436 visualIndex += index;
1448 // Return the logical position of the cursor in characters.
1452 visualIndex = endCharacter;
1455 logicalIndex = hasRightToLeftCharacters ? *( visualToLogicalCursorBuffer + visualIndex ) : visualIndex;
1456 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "%p closest visualIndex %d logicalIndex %d\n", this, visualIndex, logicalIndex );
1458 return logicalIndex;
1461 void Controller::Impl::GetCursorPosition( CharacterIndex logical,
1462 CursorInfo& cursorInfo )
1464 // TODO: Check for multiline with \n, etc...
1466 // Check if the logical position is the first or the last one of the text.
1467 const bool isFirstPosition = 0u == logical;
1468 const bool isLastPosition = mLogicalModel->mText.Count() == logical;
1470 if( isFirstPosition && isLastPosition )
1472 // There is zero characters. Get the default font's line height.
1473 cursorInfo.lineHeight = GetDefaultFontLineHeight();
1474 cursorInfo.primaryCursorHeight = cursorInfo.lineHeight;
1476 cursorInfo.primaryPosition.x = mEventData->mDecorator->GetCursorWidth();
1477 cursorInfo.primaryPosition.y = 0.f;
1479 // Nothing else to do.
1483 // 'logical' is the logical 'cursor' index.
1484 // Get the next and current logical 'character' index.
1485 const CharacterIndex nextCharacterIndex = logical;
1486 const CharacterIndex characterIndex = isFirstPosition ? logical : logical - 1u;
1488 // Get the direction of the character and the next one.
1489 const CharacterDirection* const modelCharacterDirectionsBuffer = ( 0u != mLogicalModel->mCharacterDirections.Count() ) ? mLogicalModel->mCharacterDirections.Begin() : NULL;
1491 CharacterDirection isCurrentRightToLeft = false;
1492 CharacterDirection isNextRightToLeft = false;
1493 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
1495 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + characterIndex );
1496 isNextRightToLeft = *( modelCharacterDirectionsBuffer + nextCharacterIndex );
1499 // Get the line where the character is laid-out.
1500 const LineRun* const modelLines = mVisualModel->mLines.Begin();
1502 const LineIndex lineIndex = mVisualModel->GetLineOfCharacter( characterIndex );
1503 const LineRun& line = *( modelLines + lineIndex );
1505 // Get the paragraph's direction.
1506 const CharacterDirection isRightToLeftParagraph = line.direction;
1508 // Check whether there is an alternative position:
1510 cursorInfo.isSecondaryCursor = ( !isLastPosition && ( isCurrentRightToLeft != isNextRightToLeft ) ) ||
1511 ( isLastPosition && ( isRightToLeftParagraph != isCurrentRightToLeft ) );
1513 // Set the line height.
1514 cursorInfo.lineHeight = line.ascender + -line.descender;
1516 // Calculate the primary cursor.
1518 CharacterIndex index = characterIndex;
1519 if( cursorInfo.isSecondaryCursor )
1521 // If there is a secondary position, the primary cursor may be in a different place than the logical index.
1523 if( isLastPosition )
1525 // The position of the cursor after the last character needs special
1526 // care depending on its direction and the direction of the paragraph.
1528 // Need to find the first character after the last character with the paragraph's direction.
1529 // i.e l0 l1 l2 r0 r1 should find r0.
1531 // TODO: check for more than one line!
1532 index = isRightToLeftParagraph ? line.characterRun.characterIndex : line.characterRun.characterIndex + line.characterRun.numberOfCharacters - 1u;
1533 index = mLogicalModel->GetLogicalCharacterIndex( index );
1537 index = ( isRightToLeftParagraph == isCurrentRightToLeft ) ? characterIndex : nextCharacterIndex;
1541 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
1542 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
1543 const Length* const charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
1544 const CharacterIndex* const glyphsToCharactersBuffer = mVisualModel->mGlyphsToCharacters.Begin();
1545 const Vector2* const glyphPositionsBuffer = mVisualModel->mGlyphPositions.Begin();
1547 // Convert the cursor position into the glyph position.
1548 const GlyphIndex primaryGlyphIndex = *( charactersToGlyphBuffer + index );
1549 const Length primaryNumberOfGlyphs = *( glyphsPerCharacterBuffer + index );
1550 const Length primaryNumberOfCharacters = *( charactersPerGlyphBuffer + primaryGlyphIndex );
1552 // Get the metrics for the group of glyphs.
1553 GlyphMetrics glyphMetrics;
1554 GetGlyphsMetrics( primaryGlyphIndex,
1555 primaryNumberOfGlyphs,
1560 // Whether to add the glyph's advance to the cursor position.
1561 // 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,
1562 // if the logical cursor is one, the position is the position of the first glyph and the advance is added.
1563 // A 'truth table' was build and an online Karnaugh map tool was used to simplify the logic.
1584 // Where F -> isFirstPosition
1585 // L -> isLastPosition
1586 // C -> isCurrentRightToLeft
1587 // P -> isRightToLeftParagraph
1588 // A -> Whether to add the glyph's advance.
1590 const bool addGlyphAdvance = ( ( isLastPosition && !isRightToLeftParagraph ) ||
1591 ( isFirstPosition && isRightToLeftParagraph ) ||
1592 ( !isFirstPosition && !isLastPosition && !isCurrentRightToLeft ) );
1594 float glyphAdvance = addGlyphAdvance ? glyphMetrics.advance : 0.f;
1596 if( !isLastPosition &&
1597 ( primaryNumberOfCharacters > 1u ) )
1599 const CharacterIndex firstIndex = *( glyphsToCharactersBuffer + primaryGlyphIndex );
1601 bool isCurrentRightToLeft = false;
1602 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
1604 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + index );
1607 Length numberOfGlyphAdvance = ( isFirstPosition ? 0u : 1u ) + characterIndex - firstIndex;
1608 if( isCurrentRightToLeft )
1610 numberOfGlyphAdvance = primaryNumberOfCharacters - numberOfGlyphAdvance;
1613 glyphAdvance = static_cast<float>( numberOfGlyphAdvance ) * glyphMetrics.advance / static_cast<float>( primaryNumberOfCharacters );
1616 // Get the glyph position and x bearing.
1617 const Vector2& primaryPosition = *( glyphPositionsBuffer + primaryGlyphIndex );
1619 // Set the primary cursor's height.
1620 cursorInfo.primaryCursorHeight = cursorInfo.isSecondaryCursor ? 0.5f * glyphMetrics.fontHeight : glyphMetrics.fontHeight;
1622 // Set the primary cursor's position.
1623 cursorInfo.primaryPosition.x = -glyphMetrics.xBearing + primaryPosition.x + glyphAdvance;
1624 cursorInfo.primaryPosition.y = line.ascender - glyphMetrics.ascender;
1626 // Calculate the secondary cursor.
1628 if( cursorInfo.isSecondaryCursor )
1630 // Set the secondary cursor's height.
1631 cursorInfo.secondaryCursorHeight = 0.5f * glyphMetrics.fontHeight;
1633 CharacterIndex index = characterIndex;
1634 if( !isLastPosition )
1636 index = ( isRightToLeftParagraph == isCurrentRightToLeft ) ? nextCharacterIndex : characterIndex;
1639 const GlyphIndex secondaryGlyphIndex = *( charactersToGlyphBuffer + index );
1640 const Length secondaryNumberOfGlyphs = *( glyphsPerCharacterBuffer + index );
1642 const Vector2& secondaryPosition = *( glyphPositionsBuffer + secondaryGlyphIndex );
1644 GetGlyphsMetrics( secondaryGlyphIndex,
1645 secondaryNumberOfGlyphs,
1650 // Set the secondary cursor's position.
1651 cursorInfo.secondaryPosition.x = -glyphMetrics.xBearing + secondaryPosition.x + ( isCurrentRightToLeft ? 0.f : glyphMetrics.advance );
1652 cursorInfo.secondaryPosition.y = cursorInfo.lineHeight - cursorInfo.secondaryCursorHeight - line.descender - ( glyphMetrics.fontHeight - glyphMetrics.ascender );
1656 CharacterIndex Controller::Impl::CalculateNewCursorIndex( CharacterIndex index ) const
1658 if( NULL == mEventData )
1660 // Nothing to do if there is no text input.
1664 CharacterIndex cursorIndex = mEventData->mPrimaryCursorPosition;
1666 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
1667 const Length* const charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
1669 GlyphIndex glyphIndex = *( charactersToGlyphBuffer + index );
1670 Length numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
1672 if( numberOfCharacters > 1u )
1674 const Script script = mLogicalModel->GetScript( index );
1675 if( HasLigatureMustBreak( script ) )
1677 // Prevents to jump the whole Latin ligatures like fi, ff, or Arabic ï»», ...
1678 numberOfCharacters = 1u;
1683 while( 0u == numberOfCharacters )
1686 numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
1690 if( index < mEventData->mPrimaryCursorPosition )
1692 cursorIndex -= numberOfCharacters;
1696 cursorIndex += numberOfCharacters;
1702 void Controller::Impl::UpdateCursorPosition()
1704 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "-->Controller::UpdateCursorPosition %p\n", this );
1705 if( NULL == mEventData )
1707 // Nothing to do if there is no text input.
1708 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition no event data\n" );
1712 if( IsShowingPlaceholderText() || ( 0u == mLogicalModel->mText.Count() ) )
1714 // Do not want to use the place-holder text to set the cursor position.
1716 // Use the line's height of the font's family set to set the cursor's size.
1717 // If there is no font's family set, use the default font.
1718 // Use the current alignment to place the cursor at the beginning, center or end of the box.
1720 float lineHeight = 0.f;
1722 FontId defaultFontId = 0u;
1723 if( NULL == mFontDefaults )
1725 defaultFontId = mFontClient.GetFontId( EMPTY_STRING,
1730 defaultFontId = mFontDefaults->GetFontId( mFontClient );
1733 Text::FontMetrics fontMetrics;
1734 mFontClient.GetFontMetrics( defaultFontId, fontMetrics );
1736 lineHeight = fontMetrics.ascender - fontMetrics.descender;
1739 Vector2 cursorPosition;
1741 switch( mLayoutEngine.GetHorizontalAlignment() )
1743 case LayoutEngine::HORIZONTAL_ALIGN_BEGIN:
1745 cursorPosition.x = mEventData->mDecorator->GetCursorWidth();
1748 case LayoutEngine::HORIZONTAL_ALIGN_CENTER:
1750 cursorPosition.x = floor( 0.5f * mVisualModel->mControlSize.width );
1753 case LayoutEngine::HORIZONTAL_ALIGN_END:
1755 cursorPosition.x = mVisualModel->mControlSize.width;
1760 switch( mLayoutEngine.GetVerticalAlignment() )
1762 case LayoutEngine::VERTICAL_ALIGN_TOP:
1764 cursorPosition.y = 0.f;
1767 case LayoutEngine::VERTICAL_ALIGN_CENTER:
1769 cursorPosition.y = floorf( 0.5f * ( mVisualModel->mControlSize.height - lineHeight ) );
1772 case LayoutEngine::VERTICAL_ALIGN_BOTTOM:
1774 cursorPosition.y = mVisualModel->mControlSize.height - lineHeight;
1779 mEventData->mDecorator->SetPosition( PRIMARY_CURSOR,
1787 CursorInfo cursorInfo;
1788 GetCursorPosition( mEventData->mPrimaryCursorPosition,
1791 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
1792 const Vector2 cursorPosition = cursorInfo.primaryPosition + offset;
1794 // Sets the cursor position.
1795 mEventData->mDecorator->SetPosition( PRIMARY_CURSOR,
1798 cursorInfo.primaryCursorHeight,
1799 cursorInfo.lineHeight );
1800 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Primary cursor position: %f,%f\n", cursorPosition.x, cursorPosition.y );
1802 // Sets the grab handle position.
1803 mEventData->mDecorator->SetPosition( GRAB_HANDLE,
1806 cursorInfo.lineHeight );
1808 if( cursorInfo.isSecondaryCursor )
1810 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_BOTH );
1811 mEventData->mDecorator->SetPosition( SECONDARY_CURSOR,
1812 cursorInfo.secondaryPosition.x + offset.x,
1813 cursorInfo.secondaryPosition.y + offset.y,
1814 cursorInfo.secondaryCursorHeight,
1815 cursorInfo.lineHeight );
1816 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Secondary cursor position: %f,%f\n", cursorInfo.secondaryPosition.x + offset.x, cursorInfo.secondaryPosition.y + offset.y );
1820 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1823 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition\n" );
1826 void Controller::Impl::UpdateSelectionHandle( HandleType handleType )
1828 if( ( LEFT_SELECTION_HANDLE != handleType ) &&
1829 ( RIGHT_SELECTION_HANDLE != handleType ) )
1834 const bool leftSelectionHandle = LEFT_SELECTION_HANDLE == handleType;
1835 const CharacterIndex index = leftSelectionHandle ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition;
1837 CursorInfo cursorInfo;
1838 GetCursorPosition( index,
1841 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
1842 const Vector2 cursorPosition = cursorInfo.primaryPosition + offset;
1844 // Sets the grab handle position.
1845 mEventData->mDecorator->SetPosition( handleType,
1848 cursorInfo.lineHeight );
1850 // If selection handle at start of the text and other at end of the text then all text is selected.
1851 const CharacterIndex startOfSelection = std::min( mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition );
1852 const CharacterIndex endOfSelection = std::max ( mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition );
1853 mEventData->mAllTextSelected = ( startOfSelection == 0 ) && ( endOfSelection == mLogicalModel->mText.Count() );
1856 void Controller::Impl::ClampHorizontalScroll( const Vector2& actualSize )
1858 // Clamp between -space & 0 (and the text alignment).
1859 if( actualSize.width > mVisualModel->mControlSize.width )
1861 const float space = ( actualSize.width - mVisualModel->mControlSize.width ) + mAlignmentOffset.x;
1862 mEventData->mScrollPosition.x = ( mEventData->mScrollPosition.x < -space ) ? -space : mEventData->mScrollPosition.x;
1863 mEventData->mScrollPosition.x = ( mEventData->mScrollPosition.x > -mAlignmentOffset.x ) ? -mAlignmentOffset.x : mEventData->mScrollPosition.x;
1865 mEventData->mDecoratorUpdated = true;
1869 mEventData->mScrollPosition.x = 0.f;
1873 void Controller::Impl::ClampVerticalScroll( const Vector2& actualSize )
1875 // Clamp between -space & 0 (and the text alignment).
1876 if( actualSize.height > mVisualModel->mControlSize.height )
1878 const float space = ( actualSize.height - mVisualModel->mControlSize.height ) + mAlignmentOffset.y;
1879 mEventData->mScrollPosition.y = ( mEventData->mScrollPosition.y < -space ) ? -space : mEventData->mScrollPosition.y;
1880 mEventData->mScrollPosition.y = ( mEventData->mScrollPosition.y > -mAlignmentOffset.y ) ? -mAlignmentOffset.y : mEventData->mScrollPosition.y;
1882 mEventData->mDecoratorUpdated = true;
1886 mEventData->mScrollPosition.y = 0.f;
1890 void Controller::Impl::ScrollToMakePositionVisible( const Vector2& position )
1893 bool updateDecorator = false;
1894 if( position.x < 0.f )
1896 offset.x = -position.x;
1897 mEventData->mScrollPosition.x += offset.x;
1898 updateDecorator = true;
1900 else if( position.x > mVisualModel->mControlSize.width )
1902 offset.x = mVisualModel->mControlSize.width - position.x;
1903 mEventData->mScrollPosition.x += offset.x;
1904 updateDecorator = true;
1907 if( updateDecorator && mEventData->mDecorator )
1909 mEventData->mDecorator->UpdatePositions( offset );
1912 // TODO : calculate the vertical scroll.
1915 void Controller::Impl::ScrollTextToMatchCursor()
1917 // Get the current cursor position in decorator coords.
1918 const Vector2& currentCursorPosition = mEventData->mDecorator->GetPosition( PRIMARY_CURSOR );
1920 // Calculate the new cursor position.
1921 CursorInfo cursorInfo;
1922 GetCursorPosition( mEventData->mPrimaryCursorPosition,
1925 // Calculate the offset to match the cursor position before the character was deleted.
1926 mEventData->mScrollPosition.x = currentCursorPosition.x - cursorInfo.primaryPosition.x - mAlignmentOffset.x;
1928 ClampHorizontalScroll( mVisualModel->GetActualSize() );
1930 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
1931 const Vector2 cursorPosition = cursorInfo.primaryPosition + offset;
1933 // Sets the cursor position.
1934 mEventData->mDecorator->SetPosition( PRIMARY_CURSOR,
1937 cursorInfo.primaryCursorHeight,
1938 cursorInfo.lineHeight );
1940 // Sets the grab handle position.
1941 mEventData->mDecorator->SetPosition( GRAB_HANDLE,
1944 cursorInfo.lineHeight );
1946 if( cursorInfo.isSecondaryCursor )
1948 mEventData->mDecorator->SetPosition( SECONDARY_CURSOR,
1949 cursorInfo.secondaryPosition.x + offset.x,
1950 cursorInfo.secondaryPosition.y + offset.y,
1951 cursorInfo.secondaryCursorHeight,
1952 cursorInfo.lineHeight );
1953 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Secondary cursor position: %f,%f\n", cursorInfo.secondaryPosition.x + offset.x, cursorInfo.secondaryPosition.y + offset.y );
1956 // Set which cursors are active according the state.
1957 if( ( EventData::EDITING == mEventData->mState ) ||
1958 ( EventData::EDITING_WITH_POPUP == mEventData->mState ) ||
1959 ( EventData::GRAB_HANDLE_PANNING == mEventData->mState ) )
1961 if( cursorInfo.isSecondaryCursor )
1963 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_BOTH );
1967 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1972 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
1976 void Controller::Impl::RequestRelayout()
1978 mControlInterface.RequestTextRelayout();
1983 } // namespace Toolkit