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::NoLogging, 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.
73 * @brief Get some glyph's metrics of a group of glyphs formed as a result of shaping one character.
75 * @param[in] glyphIndex The index to the first glyph.
76 * @param[in] numberOfGlyphs The number of glyphs.
77 * @param[out] glyphMetrics Some glyph metrics (font height, advance, ascender and x bearing).
78 * @param[in] visualModel The visual model.
79 * @param[in] metrics Used to access metrics from FontClient.
81 void GetGlyphsMetrics( GlyphIndex glyphIndex,
82 Length numberOfGlyphs,
83 GlyphMetrics& glyphMetrics,
84 VisualModelPtr& visualModel,
87 const GlyphInfo* glyphsBuffer = visualModel->mGlyphs.Begin();
89 const GlyphInfo& firstGlyph = *( glyphsBuffer + glyphIndex );
91 Text::FontMetrics fontMetrics;
92 metrics->GetFontMetrics( firstGlyph.fontId, fontMetrics );
94 glyphMetrics.fontHeight = fontMetrics.height;
95 glyphMetrics.advance = firstGlyph.advance;
96 glyphMetrics.ascender = fontMetrics.ascender;
97 glyphMetrics.xBearing = firstGlyph.xBearing;
99 for( unsigned int i = 1u; i < numberOfGlyphs; ++i )
101 const GlyphInfo& glyphInfo = *( glyphsBuffer + glyphIndex + i );
103 glyphMetrics.advance += glyphInfo.advance;
107 EventData::EventData( DecoratorPtr decorator )
108 : mDecorator( decorator ),
110 mPlaceholderTextActive(),
111 mPlaceholderTextInactive(),
112 mPlaceholderTextColor( 0.8f, 0.8f, 0.8f, 0.8f ),
116 mPrimaryCursorPosition( 0u ),
117 mLeftSelectionPosition( 0u ),
118 mRightSelectionPosition( 0u ),
119 mPreEditStartPosition( 0u ),
120 mPreEditLength( 0u ),
121 mIsShowingPlaceholderText( false ),
122 mPreEditFlag( false ),
123 mDecoratorUpdated( false ),
124 mCursorBlinkEnabled( true ),
125 mGrabHandleEnabled( true ),
126 mGrabHandlePopupEnabled( true ),
127 mSelectionEnabled( true ),
128 mHorizontalScrollingEnabled( true ),
129 mVerticalScrollingEnabled( false ),
130 mUpdateCursorPosition( false ),
131 mUpdateLeftSelectionPosition( false ),
132 mUpdateRightSelectionPosition( false ),
133 mScrollAfterUpdatePosition( false ),
134 mScrollAfterDelete( false ),
135 mAllTextSelected( false )
137 mImfManager = ImfManager::Get();
140 EventData::~EventData()
143 bool Controller::Impl::ProcessInputEvents()
145 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "-->Controller::ProcessInputEvents\n" );
146 if( NULL == mEventData )
148 // Nothing to do if there is no text input.
149 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::ProcessInputEvents no event data\n" );
153 if( mEventData->mDecorator )
155 for( std::vector<Event>::iterator iter = mEventData->mEventQueue.begin();
156 iter != mEventData->mEventQueue.end();
161 case Event::CURSOR_KEY_EVENT:
163 OnCursorKeyEvent( *iter );
166 case Event::TAP_EVENT:
171 case Event::LONG_PRESS_EVENT:
173 OnLongPressEvent( *iter );
176 case Event::PAN_EVENT:
181 case Event::GRAB_HANDLE_EVENT:
182 case Event::LEFT_SELECTION_HANDLE_EVENT:
183 case Event::RIGHT_SELECTION_HANDLE_EVENT: // Fall through
185 OnHandleEvent( *iter );
190 OnSelectEvent( *iter );
193 case Event::SELECT_ALL:
202 // The cursor must also be repositioned after inserts into the model
203 if( mEventData->mUpdateCursorPosition )
205 // Updates the cursor position and scrolls the text to make it visible.
206 CursorInfo cursorInfo;
207 GetCursorPosition( mEventData->mPrimaryCursorPosition,
210 if( mEventData->mScrollAfterUpdatePosition )
212 ScrollToMakePositionVisible( cursorInfo.primaryPosition );
213 mEventData->mScrollAfterUpdatePosition = false;
215 else if( mEventData->mScrollAfterDelete )
217 ScrollTextToMatchCursor( cursorInfo );
218 mEventData->mScrollAfterDelete = false;
221 UpdateCursorPosition( cursorInfo );
223 mEventData->mDecoratorUpdated = true;
224 mEventData->mUpdateCursorPosition = false;
228 bool leftScroll = false;
229 bool rightScroll = false;
231 CursorInfo leftHandleInfo;
232 CursorInfo rightHandleInfo;
234 if( mEventData->mUpdateLeftSelectionPosition )
236 GetCursorPosition( mEventData->mLeftSelectionPosition,
239 if( mEventData->mScrollAfterUpdatePosition )
241 ScrollToMakePositionVisible( leftHandleInfo.primaryPosition );
246 if( mEventData->mUpdateRightSelectionPosition )
248 GetCursorPosition( mEventData->mRightSelectionPosition,
251 if( mEventData->mScrollAfterUpdatePosition )
253 ScrollToMakePositionVisible( rightHandleInfo.primaryPosition );
258 if( mEventData->mUpdateLeftSelectionPosition )
260 UpdateSelectionHandle( LEFT_SELECTION_HANDLE,
264 mEventData->mDecoratorUpdated = true;
267 if( mEventData->mUpdateRightSelectionPosition )
269 UpdateSelectionHandle( RIGHT_SELECTION_HANDLE,
273 mEventData->mDecoratorUpdated = true;
276 if( mEventData->mUpdateLeftSelectionPosition || mEventData->mUpdateRightSelectionPosition )
278 RepositionSelectionHandles();
280 mEventData->mUpdateLeftSelectionPosition = false;
281 mEventData->mUpdateRightSelectionPosition = false;
284 if( leftScroll || rightScroll )
286 mEventData->mScrollAfterUpdatePosition = false;
290 mEventData->mEventQueue.clear();
292 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::ProcessInputEvents\n" );
294 const bool decoratorUpdated = mEventData->mDecoratorUpdated;
295 mEventData->mDecoratorUpdated = false;
297 return decoratorUpdated;
300 void Controller::Impl::UpdateModel( OperationsMask operationsRequired )
302 DALI_LOG_INFO( gLogFilter, Debug::General, "Controller::UpdateModel\n" );
304 // Calculate the operations to be done.
305 const OperationsMask operations = static_cast<OperationsMask>( mOperationsPending & operationsRequired );
307 Vector<Character>& utf32Characters = mLogicalModel->mText;
309 const Length numberOfCharacters = utf32Characters.Count();
311 Vector<LineBreakInfo>& lineBreakInfo = mLogicalModel->mLineBreakInfo;
312 if( GET_LINE_BREAKS & operations )
314 // Retrieves the line break info. The line break info is used to split the text in 'paragraphs' to
315 // calculate the bidirectional info for each 'paragraph'.
316 // It's also used to layout the text (where it should be a new line) or to shape the text (text in different lines
317 // is not shaped together).
318 lineBreakInfo.Resize( numberOfCharacters, TextAbstraction::LINE_NO_BREAK );
320 SetLineBreakInfo( utf32Characters,
324 Vector<WordBreakInfo>& wordBreakInfo = mLogicalModel->mWordBreakInfo;
325 if( GET_WORD_BREAKS & operations )
327 // Retrieves the word break info. The word break info is used to layout the text (where to wrap the text in lines).
328 wordBreakInfo.Resize( numberOfCharacters, TextAbstraction::WORD_NO_BREAK );
330 SetWordBreakInfo( utf32Characters,
334 const bool getScripts = GET_SCRIPTS & operations;
335 const bool validateFonts = VALIDATE_FONTS & operations;
337 Vector<ScriptRun>& scripts = mLogicalModel->mScriptRuns;
338 Vector<FontRun>& validFonts = mLogicalModel->mFontRuns;
340 if( getScripts || validateFonts )
342 // Validates the fonts assigned by the application or assigns default ones.
343 // It makes sure all the characters are going to be rendered by the correct font.
344 MultilanguageSupport multilanguageSupport = MultilanguageSupport::Get();
348 // Retrieves the scripts used in the text.
349 multilanguageSupport.SetScripts( utf32Characters,
355 if( 0u == validFonts.Count() )
357 // Copy the requested font defaults received via the property system.
358 // These may not be valid i.e. may not contain glyphs for the necessary scripts.
359 GetDefaultFonts( validFonts, numberOfCharacters );
362 // Validates the fonts. If there is a character with no assigned font it sets a default one.
363 // After this call, fonts are validated.
364 multilanguageSupport.ValidateFonts( utf32Characters,
370 Vector<Character> mirroredUtf32Characters;
371 bool textMirrored = false;
372 Length numberOfParagraphs = 0u;
373 if( BIDI_INFO & operations )
375 // Count the number of LINE_NO_BREAK to reserve some space for the vector of paragraph's
376 // bidirectional info.
378 const TextAbstraction::LineBreakInfo* lineBreakInfoBuffer = lineBreakInfo.Begin();
379 for( Length index = 0u; index < numberOfCharacters; ++index )
381 if( TextAbstraction::LINE_NO_BREAK == *( lineBreakInfoBuffer + index ) )
383 ++numberOfParagraphs;
387 Vector<BidirectionalParagraphInfoRun>& bidirectionalInfo = mLogicalModel->mBidirectionalParagraphInfo;
388 bidirectionalInfo.Reserve( numberOfParagraphs );
390 // Calculates the bidirectional info for the whole paragraph if it contains right to left scripts.
391 SetBidirectionalInfo( utf32Characters,
396 if( 0u != bidirectionalInfo.Count() )
398 // This paragraph has right to left text. Some characters may need to be mirrored.
399 // TODO: consider if the mirrored string can be stored as well.
401 textMirrored = GetMirroredText( utf32Characters,
402 mirroredUtf32Characters,
405 // Only set the character directions if there is right to left characters.
406 Vector<CharacterDirection>& directions = mLogicalModel->mCharacterDirections;
407 directions.Resize( numberOfCharacters );
409 GetCharactersDirection( bidirectionalInfo,
414 // There is no right to left characters. Clear the directions vector.
415 mLogicalModel->mCharacterDirections.Clear();
419 Vector<GlyphInfo>& glyphs = mVisualModel->mGlyphs;
420 Vector<CharacterIndex>& glyphsToCharactersMap = mVisualModel->mGlyphsToCharacters;
421 Vector<Length>& charactersPerGlyph = mVisualModel->mCharactersPerGlyph;
422 Vector<GlyphIndex> newParagraphGlyphs;
423 newParagraphGlyphs.Reserve( numberOfParagraphs );
425 if( SHAPE_TEXT & operations )
427 const Vector<Character>& textToShape = textMirrored ? mirroredUtf32Characters : utf32Characters;
429 ShapeText( textToShape,
434 glyphsToCharactersMap,
436 newParagraphGlyphs );
438 // Create the 'number of glyphs' per character and the glyph to character conversion tables.
439 mVisualModel->CreateGlyphsPerCharacterTable( numberOfCharacters );
440 mVisualModel->CreateCharacterToGlyphTable( numberOfCharacters );
443 const Length numberOfGlyphs = glyphs.Count();
445 if( GET_GLYPH_METRICS & operations )
447 GlyphInfo* glyphsBuffer = glyphs.Begin();
448 mMetrics->GetGlyphMetrics( glyphsBuffer, numberOfGlyphs );
450 // Update the width and advance of all new paragraph characters.
451 for( Vector<GlyphIndex>::ConstIterator it = newParagraphGlyphs.Begin(), endIt = newParagraphGlyphs.End(); it != endIt; ++it )
453 const GlyphIndex index = *it;
454 GlyphInfo& glyph = *( glyphsBuffer + index );
456 glyph.xBearing = 0.f;
463 mEventData->mPreEditFlag &&
464 ( 0u != mVisualModel->mCharactersToGlyph.Count() ) )
466 // Add the underline for the pre-edit text.
467 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
468 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
470 const GlyphIndex glyphStart = *( charactersToGlyphBuffer + mEventData->mPreEditStartPosition );
471 const CharacterIndex lastPreEditCharacter = mEventData->mPreEditStartPosition + ( ( mEventData->mPreEditLength > 0u ) ? mEventData->mPreEditLength - 1u : 0u );
472 const Length numberOfGlyphsLastCharacter = *( glyphsPerCharacterBuffer + lastPreEditCharacter );
473 const GlyphIndex glyphEnd = *( charactersToGlyphBuffer + lastPreEditCharacter ) + ( numberOfGlyphsLastCharacter > 1u ? numberOfGlyphsLastCharacter - 1u : 0u );
475 GlyphRun underlineRun;
476 underlineRun.glyphIndex = glyphStart;
477 underlineRun.numberOfGlyphs = 1u + glyphEnd - glyphStart;
479 // TODO: At the moment the underline runs are only for pre-edit.
480 mVisualModel->mUnderlineRuns.PushBack( underlineRun );
484 void Controller::Impl::GetDefaultFonts( Vector<FontRun>& fonts, Length numberOfCharacters )
488 DALI_LOG_INFO( gLogFilter, Debug::Concise, "Controller::GetDefaultFonts font family(%s)\n", mFontDefaults->mFontDescription.family.c_str() );
490 fontRun.characterRun.characterIndex = 0;
491 fontRun.characterRun.numberOfCharacters = numberOfCharacters;
492 fontRun.fontId = mFontDefaults->GetFontId( mFontClient );
493 fontRun.isDefault = true;
495 fonts.PushBack( fontRun );
499 float Controller::Impl::GetDefaultFontLineHeight()
501 FontId defaultFontId = 0u;
502 if( NULL == mFontDefaults )
504 TextAbstraction::FontDescription fontDescription;
505 defaultFontId = mFontClient.GetFontId( fontDescription );
509 defaultFontId = mFontDefaults->GetFontId( mFontClient );
512 Text::FontMetrics fontMetrics;
513 mMetrics->GetFontMetrics( defaultFontId, fontMetrics );
515 return( fontMetrics.ascender - fontMetrics.descender );
518 void Controller::Impl::OnCursorKeyEvent( const Event& event )
520 if( NULL == mEventData )
522 // Nothing to do if there is no text input.
526 int keyCode = event.p1.mInt;
528 if( Dali::DALI_KEY_CURSOR_LEFT == keyCode )
530 if( mEventData->mPrimaryCursorPosition > 0u )
532 mEventData->mPrimaryCursorPosition = CalculateNewCursorIndex( mEventData->mPrimaryCursorPosition - 1u );
535 else if( Dali::DALI_KEY_CURSOR_RIGHT == keyCode )
537 if( mLogicalModel->mText.Count() > mEventData->mPrimaryCursorPosition )
539 mEventData->mPrimaryCursorPosition = CalculateNewCursorIndex( mEventData->mPrimaryCursorPosition );
542 else if( Dali::DALI_KEY_CURSOR_UP == keyCode )
546 else if( Dali::DALI_KEY_CURSOR_DOWN == keyCode )
551 mEventData->mUpdateCursorPosition = true;
552 mEventData->mScrollAfterUpdatePosition = true;
555 void Controller::Impl::OnTapEvent( const Event& event )
557 if( NULL != mEventData )
559 const unsigned int tapCount = event.p1.mUint;
563 if( IsShowingRealText() )
565 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
566 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
568 mEventData->mPrimaryCursorPosition = GetClosestCursorIndex( xPosition,
571 // When the cursor position is changing, delay cursor blinking
572 mEventData->mDecorator->DelayCursorBlink();
576 mEventData->mPrimaryCursorPosition = 0u;
579 mEventData->mUpdateCursorPosition = true;
580 mEventData->mScrollAfterUpdatePosition = true;
582 // Notify the cursor position to the imf manager.
583 if( mEventData->mImfManager )
585 mEventData->mImfManager.SetCursorPosition( mEventData->mPrimaryCursorPosition );
586 mEventData->mImfManager.NotifyCursorPosition();
592 void Controller::Impl::OnPanEvent( const Event& event )
594 if( NULL == mEventData )
596 // Nothing to do if there is no text input.
600 int state = event.p1.mInt;
602 if( Gesture::Started == state ||
603 Gesture::Continuing == state )
605 const Vector2& actualSize = mVisualModel->GetActualSize();
606 const Vector2 currentScroll = mEventData->mScrollPosition;
608 if( mEventData->mHorizontalScrollingEnabled )
610 const float displacementX = event.p2.mFloat;
611 mEventData->mScrollPosition.x += displacementX;
613 ClampHorizontalScroll( actualSize );
616 if( mEventData->mVerticalScrollingEnabled )
618 const float displacementY = event.p3.mFloat;
619 mEventData->mScrollPosition.y += displacementY;
621 ClampVerticalScroll( actualSize );
624 if( mEventData->mDecorator )
626 mEventData->mDecorator->UpdatePositions( mEventData->mScrollPosition - currentScroll );
631 void Controller::Impl::OnLongPressEvent( const Event& event )
633 if ( EventData::EDITING == mEventData->mState )
635 ChangeState ( EventData::EDITING_WITH_POPUP );
636 mEventData->mDecoratorUpdated = true;
640 void Controller::Impl::OnHandleEvent( const Event& event )
642 if( NULL == mEventData )
644 // Nothing to do if there is no text input.
648 const unsigned int state = event.p1.mUint;
649 const bool handleStopScrolling = ( HANDLE_STOP_SCROLLING == state );
651 if( HANDLE_PRESSED == state )
653 // The event.p2 and event.p3 are in decorator coords. Need to transforms to text coords.
654 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
655 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
657 const CharacterIndex handleNewPosition = GetClosestCursorIndex( xPosition, yPosition );
659 if( Event::GRAB_HANDLE_EVENT == event.type )
661 ChangeState ( EventData::GRAB_HANDLE_PANNING );
663 if( handleNewPosition != mEventData->mPrimaryCursorPosition )
665 mEventData->mPrimaryCursorPosition = handleNewPosition;
666 mEventData->mUpdateCursorPosition = true;
669 else if( Event::LEFT_SELECTION_HANDLE_EVENT == event.type )
671 ChangeState ( EventData::SELECTION_HANDLE_PANNING );
673 if( ( handleNewPosition != mEventData->mLeftSelectionPosition ) &&
674 ( handleNewPosition != mEventData->mRightSelectionPosition ) )
676 mEventData->mLeftSelectionPosition = handleNewPosition;
678 mEventData->mUpdateLeftSelectionPosition = true;
681 else if( Event::RIGHT_SELECTION_HANDLE_EVENT == event.type )
683 ChangeState ( EventData::SELECTION_HANDLE_PANNING );
685 if( ( handleNewPosition != mEventData->mRightSelectionPosition ) &&
686 ( handleNewPosition != mEventData->mLeftSelectionPosition ) )
688 mEventData->mRightSelectionPosition = handleNewPosition;
690 mEventData->mUpdateRightSelectionPosition = true;
693 } // end ( HANDLE_PRESSED == state )
694 else if( ( HANDLE_RELEASED == state ) ||
695 handleStopScrolling )
697 CharacterIndex handlePosition = 0u;
698 if( handleStopScrolling )
700 // The event.p2 and event.p3 are in decorator coords. Need to transforms to text coords.
701 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
702 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
704 handlePosition = GetClosestCursorIndex( xPosition, yPosition );
707 if( Event::GRAB_HANDLE_EVENT == event.type )
709 mEventData->mUpdateCursorPosition = true;
711 ChangeState( EventData::EDITING_WITH_POPUP );
713 if( handleStopScrolling )
715 mEventData->mScrollAfterUpdatePosition = mEventData->mPrimaryCursorPosition != handlePosition;
716 mEventData->mPrimaryCursorPosition = handlePosition;
719 else if( Event::LEFT_SELECTION_HANDLE_EVENT == event.type )
721 ChangeState( EventData::SELECTING );
723 if( handleStopScrolling )
725 mEventData->mUpdateLeftSelectionPosition = ( mEventData->mRightSelectionPosition != handlePosition );
726 mEventData->mScrollAfterUpdatePosition = mEventData->mUpdateLeftSelectionPosition;
728 if( mEventData->mUpdateLeftSelectionPosition )
730 mEventData->mLeftSelectionPosition = handlePosition;
734 else if( Event::RIGHT_SELECTION_HANDLE_EVENT == event.type )
736 ChangeState( EventData::SELECTING );
738 if( handleStopScrolling )
740 mEventData->mUpdateRightSelectionPosition = ( mEventData->mLeftSelectionPosition != handlePosition );
741 mEventData->mScrollAfterUpdatePosition = mEventData->mUpdateRightSelectionPosition;
742 if( mEventData->mUpdateRightSelectionPosition )
744 mEventData->mRightSelectionPosition = handlePosition;
749 mEventData->mDecoratorUpdated = true;
750 } // end ( ( HANDLE_RELEASED == state ) || ( HANDLE_STOP_SCROLLING == state ) )
751 else if( HANDLE_SCROLLING == state )
753 const float xSpeed = event.p2.mFloat;
754 const Vector2& actualSize = mVisualModel->GetActualSize();
755 const Vector2 currentScrollPosition = mEventData->mScrollPosition;
757 mEventData->mScrollPosition.x += xSpeed;
759 ClampHorizontalScroll( actualSize );
761 bool endOfScroll = false;
762 if( Vector2::ZERO == ( currentScrollPosition - mEventData->mScrollPosition ) )
764 // Notify the decorator there is no more text to scroll.
765 // The decorator won't send more scroll events.
766 mEventData->mDecorator->NotifyEndOfScroll();
767 // Still need to set the position of the handle.
771 // Set the position of the handle.
772 const bool scrollRightDirection = xSpeed > 0.f;
773 const bool leftSelectionHandleEvent = Event::LEFT_SELECTION_HANDLE_EVENT == event.type;
774 const bool rightSelectionHandleEvent = Event::RIGHT_SELECTION_HANDLE_EVENT == event.type;
776 if( Event::GRAB_HANDLE_EVENT == event.type )
778 ChangeState( EventData::GRAB_HANDLE_PANNING );
780 Vector2 position = mEventData->mDecorator->GetPosition( GRAB_HANDLE );
782 // Position the grag handle close to either the left or right edge.
783 position.x = scrollRightDirection ? 0.f : mVisualModel->mControlSize.width;
785 // Get the new handle position.
786 // The grab handle's position is in decorator coords. Need to transforms to text coords.
787 const CharacterIndex handlePosition = GetClosestCursorIndex( position.x - mEventData->mScrollPosition.x - mAlignmentOffset.x,
788 position.y - mEventData->mScrollPosition.y - mAlignmentOffset.y );
790 mEventData->mUpdateCursorPosition = mEventData->mPrimaryCursorPosition != handlePosition;
791 mEventData->mScrollAfterUpdatePosition = mEventData->mUpdateCursorPosition;
792 mEventData->mPrimaryCursorPosition = handlePosition;
794 else if( leftSelectionHandleEvent || rightSelectionHandleEvent )
796 // TODO: This is recalculating the selection box every time the text is scrolled with the selection handles.
797 // Think if something can be done to save power.
799 ChangeState( EventData::SELECTION_HANDLE_PANNING );
801 Vector2 position = mEventData->mDecorator->GetPosition( leftSelectionHandleEvent ? Text::LEFT_SELECTION_HANDLE : Text::RIGHT_SELECTION_HANDLE );
803 // Position the selection handle close to either the left or right edge.
804 position.x = scrollRightDirection ? 0.f : mVisualModel->mControlSize.width;
806 // Get the new handle position.
807 // The selection handle's position is in decorator coords. Need to transforms to text coords.
808 const CharacterIndex handlePosition = GetClosestCursorIndex( position.x - mEventData->mScrollPosition.x - mAlignmentOffset.x,
809 position.y - mEventData->mScrollPosition.y - mAlignmentOffset.y );
811 if( leftSelectionHandleEvent )
813 const bool differentHandles = ( mEventData->mLeftSelectionPosition != handlePosition ) && ( mEventData->mRightSelectionPosition != handlePosition );
814 mEventData->mUpdateLeftSelectionPosition = endOfScroll || differentHandles;
815 if( differentHandles )
817 mEventData->mLeftSelectionPosition = handlePosition;
822 const bool differentHandles = ( mEventData->mRightSelectionPosition != handlePosition ) && ( mEventData->mLeftSelectionPosition != handlePosition );
823 mEventData->mUpdateRightSelectionPosition = endOfScroll || differentHandles;
824 if( differentHandles )
826 mEventData->mRightSelectionPosition = handlePosition;
830 if( mEventData->mUpdateLeftSelectionPosition || mEventData->mUpdateRightSelectionPosition )
832 RepositionSelectionHandles();
834 mEventData->mScrollAfterUpdatePosition = true;
837 mEventData->mDecoratorUpdated = true;
838 } // end ( HANDLE_SCROLLING == state )
841 void Controller::Impl::OnSelectEvent( const Event& event )
843 if( NULL == mEventData )
845 // Nothing to do if there is no text.
849 if( mEventData->mSelectionEnabled )
851 // The event.p2 and event.p3 are in decorator coords. Need to transforms to text coords.
852 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
853 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
855 // Calculates the logical position from the x,y coords.
856 RepositionSelectionHandles( xPosition,
859 mEventData->mUpdateLeftSelectionPosition = true;
860 mEventData->mUpdateRightSelectionPosition = true;
862 mEventData->mScrollAfterUpdatePosition = ( mEventData->mLeftSelectionPosition != mEventData->mRightSelectionPosition );
866 void Controller::Impl::OnSelectAllEvent()
868 if( NULL == mEventData )
870 // Nothing to do if there is no text.
874 if( mEventData->mSelectionEnabled )
876 mEventData->mLeftSelectionPosition = 0u;
877 mEventData->mRightSelectionPosition = mLogicalModel->mText.Count();
879 mEventData->mScrollAfterUpdatePosition = true;
880 mEventData->mUpdateLeftSelectionPosition = true;
881 mEventData->mUpdateRightSelectionPosition = true;
885 void Controller::Impl::RetrieveSelection( std::string& selectedText, bool deleteAfterRetreival )
887 if( mEventData->mLeftSelectionPosition == mEventData->mRightSelectionPosition )
889 // Nothing to select if handles are in the same place.
894 const bool handlesCrossed = mEventData->mLeftSelectionPosition > mEventData->mRightSelectionPosition;
896 //Get start and end position of selection
897 uint32_t startOfSelectedText = handlesCrossed ? mEventData->mRightSelectionPosition : mEventData->mLeftSelectionPosition;
898 uint32_t lengthOfSelectedText = ( handlesCrossed ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition ) - startOfSelectedText;
900 // Validate the start and end selection points
901 if( ( startOfSelectedText + lengthOfSelectedText ) <= mLogicalModel->mText.Count() )
903 //Get text as a UTF8 string
904 Vector<Character>& utf32Characters = mLogicalModel->mText;
906 Utf32ToUtf8( &utf32Characters[startOfSelectedText], lengthOfSelectedText, selectedText );
908 if ( deleteAfterRetreival ) // Only delete text if copied successfully
910 // Delete text between handles
911 Vector<Character>& currentText = mLogicalModel->mText;
913 Vector<Character>::Iterator first = currentText.Begin() + startOfSelectedText;
914 Vector<Character>::Iterator last = first + lengthOfSelectedText;
915 currentText.Erase( first, last );
917 // Scroll after delete.
918 mEventData->mPrimaryCursorPosition = handlesCrossed ? mEventData->mRightSelectionPosition : mEventData->mLeftSelectionPosition;
919 mEventData->mScrollAfterDelete = true;
921 // Udpade the cursor position and the decorator.
922 // Scroll after the position is updated if is not scrolling after delete.
923 mEventData->mUpdateCursorPosition = true;
924 mEventData->mScrollAfterUpdatePosition = !mEventData->mScrollAfterDelete;
925 mEventData->mDecoratorUpdated = true;
929 void Controller::Impl::ShowClipboard()
933 mClipboard.ShowClipboard();
937 void Controller::Impl::HideClipboard()
941 mClipboard.HideClipboard();
945 bool Controller::Impl::CopyStringToClipboard( std::string& source )
947 //Send string to clipboard
948 return ( mClipboard && mClipboard.SetItem( source ) );
951 void Controller::Impl::SendSelectionToClipboard( bool deleteAfterSending )
953 std::string selectedText;
954 RetrieveSelection( selectedText, deleteAfterSending );
955 CopyStringToClipboard( selectedText );
956 ChangeState( EventData::EDITING );
959 void Controller::Impl::GetTextFromClipboard( unsigned int itemIndex, std::string& retreivedString )
963 retreivedString = mClipboard.GetItem( itemIndex );
967 void Controller::Impl::RepositionSelectionHandles()
969 CharacterIndex selectionStart = mEventData->mLeftSelectionPosition;
970 CharacterIndex selectionEnd = mEventData->mRightSelectionPosition;
972 if( selectionStart == selectionEnd )
974 // Nothing to select if handles are in the same place.
978 mEventData->mDecorator->ClearHighlights();
980 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
981 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
982 const GlyphInfo* const glyphsBuffer = mVisualModel->mGlyphs.Begin();
983 const Vector2* const positionsBuffer = mVisualModel->mGlyphPositions.Begin();
984 const Length* const charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
985 const CharacterIndex* const glyphToCharacterBuffer = mVisualModel->mGlyphsToCharacters.Begin();
986 const CharacterDirection* const modelCharacterDirectionsBuffer = ( 0u != mLogicalModel->mCharacterDirections.Count() ) ? mLogicalModel->mCharacterDirections.Begin() : NULL;
988 // TODO: Better algorithm to create the highlight box.
991 // Get the height of the line.
992 const Vector<LineRun>& lines = mVisualModel->mLines;
993 const LineRun& firstLine = *lines.Begin();
994 const float height = firstLine.ascender + -firstLine.descender;
996 const bool isLastCharacter = selectionEnd >= mLogicalModel->mText.Count();
997 const bool startDirection = ( ( NULL == modelCharacterDirectionsBuffer ) ? false : *( modelCharacterDirectionsBuffer + selectionStart ) );
998 const bool endDirection = ( ( NULL == modelCharacterDirectionsBuffer ) ? false : *( modelCharacterDirectionsBuffer + ( selectionEnd - ( isLastCharacter ? 1u : 0u ) ) ) );
1000 // Swap the indices if the start is greater than the end.
1001 const bool indicesSwapped = selectionStart > selectionEnd;
1003 // Tell the decorator to flip the selection handles if needed.
1004 mEventData->mDecorator->SetSelectionHandleFlipState( indicesSwapped, startDirection, endDirection );
1006 if( indicesSwapped )
1008 std::swap( selectionStart, selectionEnd );
1011 // Get the indices to the first and last selected glyphs.
1012 const CharacterIndex selectionEndMinusOne = selectionEnd - 1u;
1013 const GlyphIndex glyphStart = *( charactersToGlyphBuffer + selectionStart );
1014 const Length numberOfGlyphs = *( glyphsPerCharacterBuffer + selectionEndMinusOne );
1015 const GlyphIndex glyphEnd = *( charactersToGlyphBuffer + selectionEndMinusOne ) + ( ( numberOfGlyphs > 0 ) ? numberOfGlyphs - 1u : 0u );
1017 // Check if the first glyph is a ligature that must be broken like Latin ff, fi, or Arabic ﻻ, etc which needs special code.
1018 const Length numberOfCharactersStart = *( charactersPerGlyphBuffer + glyphStart );
1019 bool splitStartGlyph = ( numberOfCharactersStart > 1u ) && HasLigatureMustBreak( mLogicalModel->GetScript( selectionStart ) );
1021 // Check if the last glyph is a ligature that must be broken like Latin ff, fi, or Arabic ﻻ, etc which needs special code.
1022 const Length numberOfCharactersEnd = *( charactersPerGlyphBuffer + glyphEnd );
1023 bool splitEndGlyph = ( glyphStart != glyphEnd ) && ( numberOfCharactersEnd > 1u ) && HasLigatureMustBreak( mLogicalModel->GetScript( selectionEndMinusOne ) );
1025 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
1027 // Traverse the glyphs.
1028 for( GlyphIndex index = glyphStart; index <= glyphEnd; ++index )
1030 const GlyphInfo& glyph = *( glyphsBuffer + index );
1031 const Vector2& position = *( positionsBuffer + index );
1033 if( splitStartGlyph )
1035 // 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.
1037 const float glyphAdvance = glyph.advance / static_cast<float>( numberOfCharactersStart );
1038 const CharacterIndex interGlyphIndex = selectionStart - *( glyphToCharacterBuffer + glyphStart );
1039 // Get the direction of the character.
1040 CharacterDirection isCurrentRightToLeft = false;
1041 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
1043 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + selectionStart );
1046 // The end point could be in the middle of the ligature.
1047 // Calculate the number of characters selected.
1048 const Length numberOfCharacters = ( glyphStart == glyphEnd ) ? ( selectionEnd - selectionStart ) : ( numberOfCharactersStart - interGlyphIndex );
1050 const float xPosition = position.x - glyph.xBearing + offset.x + glyphAdvance * static_cast<float>( isCurrentRightToLeft ? ( numberOfCharactersStart - interGlyphIndex - numberOfCharacters ) : interGlyphIndex );
1052 mEventData->mDecorator->AddHighlight( xPosition,
1054 xPosition + static_cast<float>( numberOfCharacters ) * glyphAdvance,
1055 offset.y + height );
1057 splitStartGlyph = false;
1061 if( splitEndGlyph && ( index == glyphEnd ) )
1063 // 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.
1065 const float glyphAdvance = glyph.advance / static_cast<float>( numberOfCharactersEnd );
1066 const CharacterIndex interGlyphIndex = selectionEnd - *( glyphToCharacterBuffer + glyphEnd );
1067 // Get the direction of the character.
1068 CharacterDirection isCurrentRightToLeft = false;
1069 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
1071 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + selectionEnd );
1074 const Length numberOfCharacters = numberOfCharactersEnd - interGlyphIndex;
1076 const float xPosition = position.x - glyph.xBearing + offset.x + ( isCurrentRightToLeft ? ( glyphAdvance * static_cast<float>( numberOfCharacters ) ) : 0.f );
1077 mEventData->mDecorator->AddHighlight( xPosition,
1079 xPosition + static_cast<float>( interGlyphIndex ) * glyphAdvance,
1080 offset.y + height );
1082 splitEndGlyph = false;
1086 const float xPosition = position.x - glyph.xBearing + offset.x;
1087 mEventData->mDecorator->AddHighlight( xPosition,
1089 xPosition + glyph.advance,
1090 offset.y + height );
1093 CursorInfo primaryCursorInfo;
1094 GetCursorPosition( mEventData->mLeftSelectionPosition,
1095 primaryCursorInfo );
1097 CursorInfo secondaryCursorInfo;
1098 GetCursorPosition( mEventData->mRightSelectionPosition,
1099 secondaryCursorInfo );
1101 const Vector2 primaryPosition = primaryCursorInfo.primaryPosition + offset;
1102 const Vector2 secondaryPosition = secondaryCursorInfo.primaryPosition + offset;
1104 mEventData->mDecorator->SetPosition( LEFT_SELECTION_HANDLE, primaryPosition.x, primaryPosition.y, primaryCursorInfo.lineHeight );
1106 mEventData->mDecorator->SetPosition( RIGHT_SELECTION_HANDLE, secondaryPosition.x, secondaryPosition.y, secondaryCursorInfo.lineHeight );
1108 // Cursor to be positioned at end of selection so if selection interrupted and edit mode restarted the cursor will be at end of selection
1109 mEventData->mPrimaryCursorPosition = ( indicesSwapped ) ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition;
1111 // Set the flag to update the decorator.
1112 mEventData->mDecoratorUpdated = true;
1115 void Controller::Impl::RepositionSelectionHandles( float visualX, float visualY )
1117 if( NULL == mEventData )
1119 // Nothing to do if there is no text input.
1123 if( IsShowingPlaceholderText() )
1125 // Nothing to do if there is the place-holder text.
1129 const Length numberOfGlyphs = mVisualModel->mGlyphs.Count();
1130 const Length numberOfLines = mVisualModel->mLines.Count();
1131 if( 0 == numberOfGlyphs ||
1132 0 == numberOfLines )
1134 // Nothing to do if there is no text.
1138 // Find which word was selected
1139 CharacterIndex selectionStart( 0 );
1140 CharacterIndex selectionEnd( 0 );
1141 FindSelectionIndices( visualX, visualY, selectionStart, selectionEnd );
1142 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "%p selectionStart %d selectionEnd %d\n", this, selectionStart, selectionEnd );
1144 if( selectionStart == selectionEnd )
1146 ChangeState( EventData::EDITING );
1147 // Nothing to select. i.e. a white space, out of bounds
1151 mEventData->mLeftSelectionPosition = selectionStart;
1152 mEventData->mRightSelectionPosition = selectionEnd;
1155 void Controller::Impl::SetPopupButtons()
1158 * Sets the Popup buttons to be shown depending on State.
1160 * If SELECTING : CUT & COPY + ( PASTE & CLIPBOARD if content available to paste )
1162 * If EDITING_WITH_POPUP : SELECT & SELECT_ALL
1165 TextSelectionPopup::Buttons buttonsToShow = TextSelectionPopup::NONE;
1167 if( EventData::SELECTING == mEventData->mState )
1169 buttonsToShow = TextSelectionPopup::Buttons( TextSelectionPopup::CUT | TextSelectionPopup::COPY );
1171 if ( !IsClipboardEmpty() )
1173 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
1174 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::CLIPBOARD ) );
1177 if ( !mEventData->mAllTextSelected )
1179 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::SELECT_ALL ) );
1182 else if ( EventData::EDITING_WITH_POPUP == mEventData->mState )
1184 if ( mLogicalModel->mText.Count() && !IsShowingPlaceholderText())
1186 buttonsToShow = TextSelectionPopup::Buttons( TextSelectionPopup::SELECT | TextSelectionPopup::SELECT_ALL );
1189 if ( !IsClipboardEmpty() )
1191 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
1192 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::CLIPBOARD ) );
1196 mEventData->mDecorator->SetEnabledPopupButtons( buttonsToShow );
1199 void Controller::Impl::ChangeState( EventData::State newState )
1201 if( NULL == mEventData )
1203 // Nothing to do if there is no text input.
1207 if( mEventData->mState != newState )
1209 mEventData->mState = newState;
1211 if( EventData::INACTIVE == mEventData->mState )
1213 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
1214 mEventData->mDecorator->StopCursorBlink();
1215 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1216 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1217 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1218 mEventData->mDecorator->SetPopupActive( false );
1219 mEventData->mDecoratorUpdated = true;
1222 else if ( EventData::INTERRUPTED == mEventData->mState)
1224 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1225 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1226 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1227 mEventData->mDecorator->SetPopupActive( false );
1228 mEventData->mDecoratorUpdated = true;
1231 else if ( EventData::SELECTING == mEventData->mState )
1233 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
1234 mEventData->mDecorator->StopCursorBlink();
1235 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1236 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true );
1237 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true );
1238 if( mEventData->mGrabHandlePopupEnabled )
1241 mEventData->mDecorator->SetPopupActive( true );
1243 mEventData->mDecoratorUpdated = true;
1245 else if( EventData::EDITING == mEventData->mState )
1247 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1248 if( mEventData->mCursorBlinkEnabled )
1250 mEventData->mDecorator->StartCursorBlink();
1252 // Grab handle is not shown until a tap is received whilst EDITING
1253 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1254 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1255 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1256 if( mEventData->mGrabHandlePopupEnabled )
1258 mEventData->mDecorator->SetPopupActive( false );
1260 mEventData->mDecoratorUpdated = true;
1263 else if( EventData::EDITING_WITH_POPUP == mEventData->mState )
1265 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1266 if( mEventData->mCursorBlinkEnabled )
1268 mEventData->mDecorator->StartCursorBlink();
1270 if( mEventData->mSelectionEnabled )
1272 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1273 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1277 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
1279 if( mEventData->mGrabHandlePopupEnabled )
1282 mEventData->mDecorator->SetPopupActive( true );
1285 mEventData->mDecoratorUpdated = true;
1287 else if( EventData::EDITING_WITH_GRAB_HANDLE == mEventData->mState )
1289 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1290 if( mEventData->mCursorBlinkEnabled )
1292 mEventData->mDecorator->StartCursorBlink();
1294 // Grab handle is not shown until a tap is received whilst EDITING
1295 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
1296 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1297 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1298 if( mEventData->mGrabHandlePopupEnabled )
1300 mEventData->mDecorator->SetPopupActive( false );
1302 mEventData->mDecoratorUpdated = true;
1305 else if ( EventData::SELECTION_HANDLE_PANNING == mEventData->mState )
1307 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
1308 mEventData->mDecorator->StopCursorBlink();
1309 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1310 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true );
1311 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true );
1312 if( mEventData->mGrabHandlePopupEnabled )
1314 mEventData->mDecorator->SetPopupActive( false );
1316 mEventData->mDecoratorUpdated = true;
1318 else if ( EventData::GRAB_HANDLE_PANNING == mEventData->mState )
1320 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1321 if( mEventData->mCursorBlinkEnabled )
1323 mEventData->mDecorator->StartCursorBlink();
1325 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
1326 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1327 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1328 if( mEventData->mGrabHandlePopupEnabled )
1330 mEventData->mDecorator->SetPopupActive( false );
1332 mEventData->mDecoratorUpdated = true;
1337 LineIndex Controller::Impl::GetClosestLine( float y ) const
1339 float totalHeight = 0.f;
1340 LineIndex lineIndex = 0u;
1342 const Vector<LineRun>& lines = mVisualModel->mLines;
1343 for( LineIndex endLine = lines.Count();
1344 lineIndex < endLine;
1347 const LineRun& lineRun = lines[lineIndex];
1348 totalHeight += lineRun.ascender + -lineRun.descender;
1349 if( y < totalHeight )
1355 if( lineIndex == 0 )
1363 void Controller::Impl::FindSelectionIndices( float visualX, float visualY, CharacterIndex& startIndex, CharacterIndex& endIndex )
1365 CharacterIndex hitCharacter = GetClosestCursorIndex( visualX, visualY );
1366 DALI_ASSERT_DEBUG( hitCharacter <= mLogicalModel->mText.Count() && "GetClosestCursorIndex returned out of bounds index" );
1368 if ( mLogicalModel->mText.Count() == 0 )
1370 return; // if model empty
1373 if( hitCharacter >= mLogicalModel->mText.Count() )
1375 // Closest hit character is the last character.
1376 if ( hitCharacter == mLogicalModel->mText.Count() )
1378 hitCharacter--; //Hit character index set to last character in logical model
1382 // hitCharacter is out of bounds
1387 startIndex = hitCharacter;
1388 endIndex = hitCharacter;
1390 if( !TextAbstraction::IsWhiteSpace( mLogicalModel->mText[hitCharacter] ) )
1392 // Find the start and end of the text
1393 for( startIndex = hitCharacter; startIndex > 0; --startIndex )
1395 Character charCode = mLogicalModel->mText[ startIndex-1 ];
1396 if( TextAbstraction::IsWhiteSpace( charCode ) )
1401 const CharacterIndex pastTheEnd = mLogicalModel->mText.Count();
1402 for( endIndex = hitCharacter + 1u; endIndex < pastTheEnd; ++endIndex )
1404 Character charCode = mLogicalModel->mText[ endIndex ];
1405 if( TextAbstraction::IsWhiteSpace( charCode ) )
1413 CharacterIndex Controller::Impl::GetClosestCursorIndex( float visualX,
1416 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "GetClosestCursorIndex %p closest visualX %f visualY %f\n", this, visualX, visualY );
1418 if( NULL == mEventData )
1420 // Nothing to do if there is no text input.
1424 CharacterIndex logicalIndex = 0u;
1426 const Length numberOfGlyphs = mVisualModel->mGlyphs.Count();
1427 const Length numberOfLines = mVisualModel->mLines.Count();
1428 if( 0 == numberOfGlyphs ||
1429 0 == numberOfLines )
1431 return logicalIndex;
1434 // Find which line is closest
1435 const LineIndex lineIndex = GetClosestLine( visualY );
1436 const LineRun& line = mVisualModel->mLines[lineIndex];
1438 // Get the positions of the glyphs.
1439 const Vector<Vector2>& positions = mVisualModel->mGlyphPositions;
1440 const Vector2* const positionsBuffer = positions.Begin();
1442 // Get the visual to logical conversion tables.
1443 const CharacterIndex* const visualToLogicalBuffer = ( 0u != mLogicalModel->mVisualToLogicalMap.Count() ) ? mLogicalModel->mVisualToLogicalMap.Begin() : NULL;
1444 const CharacterIndex* const visualToLogicalCursorBuffer = mLogicalModel->mVisualToLogicalCursorMap.Begin();
1446 // Get the character to glyph conversion table.
1447 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
1449 // Get the glyphs per character table.
1450 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
1452 // If the vector is void, there is no right to left characters.
1453 const bool hasRightToLeftCharacters = NULL != visualToLogicalBuffer;
1455 const CharacterIndex startCharacter = line.characterRun.characterIndex;
1456 const CharacterIndex endCharacter = line.characterRun.characterIndex + line.characterRun.numberOfCharacters;
1457 DALI_ASSERT_DEBUG( endCharacter <= mLogicalModel->mText.Count() && "Invalid line info" );
1459 // Whether there is a hit on a glyph.
1460 bool matched = false;
1462 // Traverses glyphs in visual order. To do that use the visual to logical conversion table.
1463 CharacterIndex visualIndex = startCharacter;
1464 Length numberOfCharacters = 0u;
1465 for( ; !matched && ( visualIndex < endCharacter ); ++visualIndex )
1467 // The character in logical order.
1468 const CharacterIndex characterLogicalOrderIndex = hasRightToLeftCharacters ? *( visualToLogicalBuffer + visualIndex ) : visualIndex;
1470 // Get the script of the character.
1471 const Script script = mLogicalModel->GetScript( characterLogicalOrderIndex );
1473 // The number of glyphs for that character
1474 const Length numberOfGlyphs = *( glyphsPerCharacterBuffer + characterLogicalOrderIndex );
1475 ++numberOfCharacters;
1478 if( 0u != numberOfGlyphs )
1480 // Get the first character/glyph of the group of glyphs.
1481 const CharacterIndex firstVisualCharacterIndex = 1u + visualIndex - numberOfCharacters;
1482 const CharacterIndex firstLogicalCharacterIndex = hasRightToLeftCharacters ? *( visualToLogicalBuffer + firstVisualCharacterIndex ) : firstVisualCharacterIndex;
1483 const GlyphIndex firstLogicalGlyphIndex = *( charactersToGlyphBuffer + firstLogicalCharacterIndex );
1485 // Get the metrics for the group of glyphs.
1486 GlyphMetrics glyphMetrics;
1487 GetGlyphsMetrics( firstLogicalGlyphIndex,
1493 // Get the position of the first glyph.
1494 const Vector2& position = *( positionsBuffer + firstLogicalGlyphIndex );
1496 // Whether the glyph can be split, like Latin ligatures fi, ff or Arabic ﻻ.
1497 const bool isInterglyphIndex = ( numberOfCharacters > numberOfGlyphs ) && HasLigatureMustBreak( script );
1498 const Length numberOfBlocks = isInterglyphIndex ? numberOfCharacters : 1u;
1499 const float glyphAdvance = glyphMetrics.advance / static_cast<float>( numberOfBlocks );
1501 GlyphIndex index = 0u;
1502 for( ; !matched && ( index < numberOfBlocks ); ++index )
1504 // Find the mid-point of the area containing the glyph
1505 const float glyphCenter = -glyphMetrics.xBearing + position.x + ( static_cast<float>( index ) + 0.5f ) * glyphAdvance;
1507 if( visualX < glyphCenter )
1516 visualIndex = firstVisualCharacterIndex + index;
1520 numberOfCharacters = 0u;
1526 // Return the logical position of the cursor in characters.
1530 visualIndex = endCharacter;
1533 logicalIndex = hasRightToLeftCharacters ? *( visualToLogicalCursorBuffer + visualIndex ) : visualIndex;
1534 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "%p closest visualIndex %d logicalIndex %d\n", this, visualIndex, logicalIndex );
1536 DALI_ASSERT_DEBUG( ( logicalIndex <= mLogicalModel->mText.Count() && logicalIndex >= 0 ) && "GetClosestCursorIndex - Out of bounds index" );
1538 return logicalIndex;
1541 void Controller::Impl::GetCursorPosition( CharacterIndex logical,
1542 CursorInfo& cursorInfo )
1544 // TODO: Check for multiline with \n, etc...
1546 const Length numberOfCharacters = mLogicalModel->mText.Count();
1547 if( !IsShowingRealText() )
1549 // Do not want to use the place-holder text to set the cursor position.
1551 // Use the line's height of the font's family set to set the cursor's size.
1552 // If there is no font's family set, use the default font.
1553 // Use the current alignment to place the cursor at the beginning, center or end of the box.
1555 cursorInfo.lineHeight = GetDefaultFontLineHeight();
1556 cursorInfo.primaryCursorHeight = cursorInfo.lineHeight;
1558 switch( mLayoutEngine.GetHorizontalAlignment() )
1560 case LayoutEngine::HORIZONTAL_ALIGN_BEGIN:
1562 cursorInfo.primaryPosition.x = 0.f;
1565 case LayoutEngine::HORIZONTAL_ALIGN_CENTER:
1567 cursorInfo.primaryPosition.x = floorf( 0.5f * mVisualModel->mControlSize.width );
1570 case LayoutEngine::HORIZONTAL_ALIGN_END:
1572 cursorInfo.primaryPosition.x = mVisualModel->mControlSize.width - mEventData->mDecorator->GetCursorWidth();
1577 switch( mLayoutEngine.GetVerticalAlignment() )
1579 case LayoutEngine::VERTICAL_ALIGN_TOP:
1581 cursorInfo.primaryPosition.y = 0.f;
1584 case LayoutEngine::VERTICAL_ALIGN_CENTER:
1586 cursorInfo.primaryPosition.y = floorf( 0.5f * ( mVisualModel->mControlSize.height - cursorInfo.lineHeight ) );
1589 case LayoutEngine::VERTICAL_ALIGN_BOTTOM:
1591 cursorInfo.primaryPosition.y = mVisualModel->mControlSize.height - cursorInfo.lineHeight;
1596 // Nothing else to do.
1600 // Check if the logical position is the first or the last one of the text.
1601 const bool isFirstPosition = 0u == logical;
1602 const bool isLastPosition = numberOfCharacters == logical;
1604 // 'logical' is the logical 'cursor' index.
1605 // Get the next and current logical 'character' index.
1606 const CharacterIndex nextCharacterIndex = logical;
1607 const CharacterIndex characterIndex = isFirstPosition ? logical : logical - 1u;
1609 // Get the direction of the character and the next one.
1610 const CharacterDirection* const modelCharacterDirectionsBuffer = ( 0u != mLogicalModel->mCharacterDirections.Count() ) ? mLogicalModel->mCharacterDirections.Begin() : NULL;
1612 CharacterDirection isCurrentRightToLeft = false;
1613 CharacterDirection isNextRightToLeft = false;
1614 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
1616 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + characterIndex );
1617 isNextRightToLeft = *( modelCharacterDirectionsBuffer + nextCharacterIndex );
1620 // Get the line where the character is laid-out.
1621 const LineRun* const modelLines = mVisualModel->mLines.Begin();
1623 const LineIndex lineIndex = mVisualModel->GetLineOfCharacter( characterIndex );
1624 const LineRun& line = *( modelLines + lineIndex );
1626 // Get the paragraph's direction.
1627 const CharacterDirection isRightToLeftParagraph = line.direction;
1629 // Check whether there is an alternative position:
1631 cursorInfo.isSecondaryCursor = ( !isLastPosition && ( isCurrentRightToLeft != isNextRightToLeft ) ) ||
1632 ( isLastPosition && ( isRightToLeftParagraph != isCurrentRightToLeft ) );
1634 // Set the line height.
1635 cursorInfo.lineHeight = line.ascender + -line.descender;
1637 // Calculate the primary cursor.
1639 CharacterIndex index = characterIndex;
1640 if( cursorInfo.isSecondaryCursor )
1642 // If there is a secondary position, the primary cursor may be in a different place than the logical index.
1644 if( isLastPosition )
1646 // The position of the cursor after the last character needs special
1647 // care depending on its direction and the direction of the paragraph.
1649 // Need to find the first character after the last character with the paragraph's direction.
1650 // i.e l0 l1 l2 r0 r1 should find r0.
1652 // TODO: check for more than one line!
1653 index = isRightToLeftParagraph ? line.characterRun.characterIndex : line.characterRun.characterIndex + line.characterRun.numberOfCharacters - 1u;
1654 index = mLogicalModel->GetLogicalCharacterIndex( index );
1658 index = ( isRightToLeftParagraph == isCurrentRightToLeft ) ? characterIndex : nextCharacterIndex;
1662 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
1663 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
1664 const Length* const charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
1665 const CharacterIndex* const glyphsToCharactersBuffer = mVisualModel->mGlyphsToCharacters.Begin();
1666 const Vector2* const glyphPositionsBuffer = mVisualModel->mGlyphPositions.Begin();
1668 // Convert the cursor position into the glyph position.
1669 const GlyphIndex primaryGlyphIndex = *( charactersToGlyphBuffer + index );
1670 const Length primaryNumberOfGlyphs = *( glyphsPerCharacterBuffer + index );
1671 const Length primaryNumberOfCharacters = *( charactersPerGlyphBuffer + primaryGlyphIndex );
1673 // Get the metrics for the group of glyphs.
1674 GlyphMetrics glyphMetrics;
1675 GetGlyphsMetrics( primaryGlyphIndex,
1676 primaryNumberOfGlyphs,
1681 // Whether to add the glyph's advance to the cursor position.
1682 // 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,
1683 // if the logical cursor is one, the position is the position of the first glyph and the advance is added.
1684 // A 'truth table' was build and an online Karnaugh map tool was used to simplify the logic.
1705 // Where F -> isFirstPosition
1706 // L -> isLastPosition
1707 // C -> isCurrentRightToLeft
1708 // P -> isRightToLeftParagraph
1709 // A -> Whether to add the glyph's advance.
1711 const bool addGlyphAdvance = ( ( isLastPosition && !isRightToLeftParagraph ) ||
1712 ( isFirstPosition && isRightToLeftParagraph ) ||
1713 ( !isFirstPosition && !isLastPosition && !isCurrentRightToLeft ) );
1715 float glyphAdvance = addGlyphAdvance ? glyphMetrics.advance : 0.f;
1717 if( !isLastPosition &&
1718 ( primaryNumberOfCharacters > 1u ) )
1720 const CharacterIndex firstIndex = *( glyphsToCharactersBuffer + primaryGlyphIndex );
1722 bool isCurrentRightToLeft = false;
1723 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
1725 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + index );
1728 Length numberOfGlyphAdvance = ( isFirstPosition ? 0u : 1u ) + characterIndex - firstIndex;
1729 if( isCurrentRightToLeft )
1731 numberOfGlyphAdvance = primaryNumberOfCharacters - numberOfGlyphAdvance;
1734 glyphAdvance = static_cast<float>( numberOfGlyphAdvance ) * glyphMetrics.advance / static_cast<float>( primaryNumberOfCharacters );
1737 // Get the glyph position and x bearing.
1738 const Vector2& primaryPosition = *( glyphPositionsBuffer + primaryGlyphIndex );
1740 // Set the primary cursor's height.
1741 cursorInfo.primaryCursorHeight = cursorInfo.isSecondaryCursor ? 0.5f * glyphMetrics.fontHeight : glyphMetrics.fontHeight;
1743 // Set the primary cursor's position.
1744 cursorInfo.primaryPosition.x = -glyphMetrics.xBearing + primaryPosition.x + glyphAdvance;
1745 cursorInfo.primaryPosition.y = line.ascender - glyphMetrics.ascender;
1747 // Calculate the secondary cursor.
1749 if( cursorInfo.isSecondaryCursor )
1751 // Set the secondary cursor's height.
1752 cursorInfo.secondaryCursorHeight = 0.5f * glyphMetrics.fontHeight;
1754 CharacterIndex index = characterIndex;
1755 if( !isLastPosition )
1757 index = ( isRightToLeftParagraph == isCurrentRightToLeft ) ? nextCharacterIndex : characterIndex;
1760 const GlyphIndex secondaryGlyphIndex = *( charactersToGlyphBuffer + index );
1761 const Length secondaryNumberOfGlyphs = *( glyphsPerCharacterBuffer + index );
1763 const Vector2& secondaryPosition = *( glyphPositionsBuffer + secondaryGlyphIndex );
1765 GetGlyphsMetrics( secondaryGlyphIndex,
1766 secondaryNumberOfGlyphs,
1771 // Set the secondary cursor's position.
1772 cursorInfo.secondaryPosition.x = -glyphMetrics.xBearing + secondaryPosition.x + ( isCurrentRightToLeft ? 0.f : glyphMetrics.advance );
1773 cursorInfo.secondaryPosition.y = cursorInfo.lineHeight - cursorInfo.secondaryCursorHeight - line.descender - ( glyphMetrics.fontHeight - glyphMetrics.ascender );
1777 CharacterIndex Controller::Impl::CalculateNewCursorIndex( CharacterIndex index ) const
1779 if( NULL == mEventData )
1781 // Nothing to do if there is no text input.
1785 CharacterIndex cursorIndex = mEventData->mPrimaryCursorPosition;
1787 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
1788 const Length* const charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
1790 GlyphIndex glyphIndex = *( charactersToGlyphBuffer + index );
1791 Length numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
1793 if( numberOfCharacters > 1u )
1795 const Script script = mLogicalModel->GetScript( index );
1796 if( HasLigatureMustBreak( script ) )
1798 // Prevents to jump the whole Latin ligatures like fi, ff, or Arabic ﻻ, ...
1799 numberOfCharacters = 1u;
1804 while( 0u == numberOfCharacters )
1807 numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
1811 if( index < mEventData->mPrimaryCursorPosition )
1813 cursorIndex -= numberOfCharacters;
1817 cursorIndex += numberOfCharacters;
1823 void Controller::Impl::UpdateCursorPosition( const CursorInfo& cursorInfo )
1825 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "-->Controller::UpdateCursorPosition %p\n", this );
1826 if( NULL == mEventData )
1828 // Nothing to do if there is no text input.
1829 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition no event data\n" );
1833 const Vector2 offset = mEventData->mScrollPosition + ( IsShowingRealText() ? mAlignmentOffset : Vector2::ZERO );
1834 const Vector2 cursorPosition = cursorInfo.primaryPosition + offset;
1836 // Sets the cursor position.
1837 mEventData->mDecorator->SetPosition( PRIMARY_CURSOR,
1840 cursorInfo.primaryCursorHeight,
1841 cursorInfo.lineHeight );
1842 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Primary cursor position: %f,%f\n", cursorPosition.x, cursorPosition.y );
1844 // Sets the grab handle position.
1845 mEventData->mDecorator->SetPosition( GRAB_HANDLE,
1848 cursorInfo.lineHeight );
1850 if( cursorInfo.isSecondaryCursor )
1852 mEventData->mDecorator->SetPosition( SECONDARY_CURSOR,
1853 cursorInfo.secondaryPosition.x + offset.x,
1854 cursorInfo.secondaryPosition.y + offset.y,
1855 cursorInfo.secondaryCursorHeight,
1856 cursorInfo.lineHeight );
1857 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Secondary cursor position: %f,%f\n", cursorInfo.secondaryPosition.x + offset.x, cursorInfo.secondaryPosition.y + offset.y );
1860 // Set which cursors are active according the state.
1861 if( ( EventData::EDITING == mEventData->mState ) ||
1862 ( EventData::EDITING_WITH_POPUP == mEventData->mState ) ||
1863 ( EventData::EDITING_WITH_GRAB_HANDLE == mEventData->mState ) ||
1864 ( EventData::GRAB_HANDLE_PANNING == mEventData->mState ) )
1866 if( cursorInfo.isSecondaryCursor )
1868 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_BOTH );
1872 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1877 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
1880 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition\n" );
1883 void Controller::Impl::UpdateSelectionHandle( HandleType handleType,
1884 const CursorInfo& cursorInfo )
1886 if( ( LEFT_SELECTION_HANDLE != handleType ) &&
1887 ( RIGHT_SELECTION_HANDLE != handleType ) )
1892 const Vector2 cursorPosition = cursorInfo.primaryPosition + mEventData->mScrollPosition + mAlignmentOffset;
1894 // Sets the grab handle position.
1895 mEventData->mDecorator->SetPosition( handleType,
1898 cursorInfo.lineHeight );
1900 // If selection handle at start of the text and other at end of the text then all text is selected.
1901 const CharacterIndex startOfSelection = std::min( mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition );
1902 const CharacterIndex endOfSelection = std::max ( mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition );
1903 mEventData->mAllTextSelected = ( startOfSelection == 0 ) && ( endOfSelection == mLogicalModel->mText.Count() );
1906 void Controller::Impl::ClampHorizontalScroll( const Vector2& actualSize )
1908 // Clamp between -space & 0 (and the text alignment).
1910 if( actualSize.width > mVisualModel->mControlSize.width )
1912 const float space = ( actualSize.width - mVisualModel->mControlSize.width ) + mAlignmentOffset.x;
1913 mEventData->mScrollPosition.x = ( mEventData->mScrollPosition.x < -space ) ? -space : mEventData->mScrollPosition.x;
1914 mEventData->mScrollPosition.x = ( mEventData->mScrollPosition.x > -mAlignmentOffset.x ) ? -mAlignmentOffset.x : mEventData->mScrollPosition.x;
1916 mEventData->mDecoratorUpdated = true;
1920 mEventData->mScrollPosition.x = 0.f;
1924 void Controller::Impl::ClampVerticalScroll( const Vector2& actualSize )
1926 // Clamp between -space & 0 (and the text alignment).
1927 if( actualSize.height > mVisualModel->mControlSize.height )
1929 const float space = ( actualSize.height - mVisualModel->mControlSize.height ) + mAlignmentOffset.y;
1930 mEventData->mScrollPosition.y = ( mEventData->mScrollPosition.y < -space ) ? -space : mEventData->mScrollPosition.y;
1931 mEventData->mScrollPosition.y = ( mEventData->mScrollPosition.y > -mAlignmentOffset.y ) ? -mAlignmentOffset.y : mEventData->mScrollPosition.y;
1933 mEventData->mDecoratorUpdated = true;
1937 mEventData->mScrollPosition.y = 0.f;
1941 void Controller::Impl::ScrollToMakePositionVisible( const Vector2& position )
1943 // position is in actor's coords.
1944 const float positionEnd = position.x + ( mEventData->mDecorator ? mEventData->mDecorator->GetCursorWidth() : 0.f );
1946 // Transform the position to decorator coords.
1947 const float offset = mEventData->mScrollPosition.x + mAlignmentOffset.x;
1948 const float decoratorPositionBegin = position.x + offset;
1949 const float decoratorPositionEnd = positionEnd + offset;
1951 if( decoratorPositionBegin < 0.f )
1953 mEventData->mScrollPosition.x = -position.x - mAlignmentOffset.x;
1955 else if( decoratorPositionEnd > mVisualModel->mControlSize.width )
1957 mEventData->mScrollPosition.x = mVisualModel->mControlSize.width - positionEnd - mAlignmentOffset.x;
1961 void Controller::Impl::ScrollTextToMatchCursor( const CursorInfo& cursorInfo )
1963 // Get the current cursor position in decorator coords.
1964 const Vector2& currentCursorPosition = mEventData->mDecorator->GetPosition( PRIMARY_CURSOR );
1966 // Calculate the offset to match the cursor position before the character was deleted.
1967 mEventData->mScrollPosition.x = currentCursorPosition.x - cursorInfo.primaryPosition.x - mAlignmentOffset.x;
1969 ClampHorizontalScroll( mVisualModel->GetActualSize() );
1972 void Controller::Impl::RequestRelayout()
1974 mControlInterface.RequestTextRelayout();
1979 } // namespace Toolkit