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 ),
109 mPlaceholderTextActive(),
110 mPlaceholderTextInactive(),
111 mPlaceholderTextColor( 0.8f, 0.8f, 0.8f, 0.8f ),
115 mPrimaryCursorPosition( 0u ),
116 mLeftSelectionPosition( 0u ),
117 mRightSelectionPosition( 0u ),
118 mPreEditStartPosition( 0u ),
119 mPreEditLength( 0u ),
120 mIsShowingPlaceholderText( false ),
121 mPreEditFlag( false ),
122 mDecoratorUpdated( false ),
123 mCursorBlinkEnabled( true ),
124 mGrabHandleEnabled( true ),
125 mGrabHandlePopupEnabled( true ),
126 mSelectionEnabled( true ),
127 mHorizontalScrollingEnabled( true ),
128 mVerticalScrollingEnabled( false ),
129 mUpdateCursorPosition( false ),
130 mUpdateLeftSelectionPosition( false ),
131 mUpdateRightSelectionPosition( false ),
132 mScrollAfterUpdatePosition( false ),
133 mScrollAfterDelete( false ),
134 mAllTextSelected( false )
137 EventData::~EventData()
140 bool Controller::Impl::ProcessInputEvents()
142 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "-->Controller::ProcessInputEvents\n" );
143 if( NULL == mEventData )
145 // Nothing to do if there is no text input.
146 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::ProcessInputEvents no event data\n" );
150 if( mEventData->mDecorator )
152 for( std::vector<Event>::iterator iter = mEventData->mEventQueue.begin();
153 iter != mEventData->mEventQueue.end();
158 case Event::CURSOR_KEY_EVENT:
160 OnCursorKeyEvent( *iter );
163 case Event::TAP_EVENT:
168 case Event::LONG_PRESS_EVENT:
170 OnLongPressEvent( *iter );
173 case Event::PAN_EVENT:
178 case Event::GRAB_HANDLE_EVENT:
179 case Event::LEFT_SELECTION_HANDLE_EVENT:
180 case Event::RIGHT_SELECTION_HANDLE_EVENT: // Fall through
182 OnHandleEvent( *iter );
187 OnSelectEvent( *iter );
190 case Event::SELECT_ALL:
199 // The cursor must also be repositioned after inserts into the model
200 if( mEventData->mUpdateCursorPosition )
202 // Updates the cursor position and scrolls the text to make it visible.
203 CursorInfo cursorInfo;
204 GetCursorPosition( mEventData->mPrimaryCursorPosition,
207 if( mEventData->mScrollAfterUpdatePosition )
209 ScrollToMakePositionVisible( cursorInfo.primaryPosition );
210 mEventData->mScrollAfterUpdatePosition = false;
212 else if( mEventData->mScrollAfterDelete )
214 ScrollTextToMatchCursor( cursorInfo );
215 mEventData->mScrollAfterDelete = false;
218 UpdateCursorPosition( cursorInfo );
220 mEventData->mDecoratorUpdated = true;
221 mEventData->mUpdateCursorPosition = false;
225 bool leftScroll = false;
226 bool rightScroll = false;
228 CursorInfo leftHandleInfo;
229 CursorInfo rightHandleInfo;
231 if( mEventData->mUpdateLeftSelectionPosition )
233 GetCursorPosition( mEventData->mLeftSelectionPosition,
236 if( mEventData->mScrollAfterUpdatePosition )
238 ScrollToMakePositionVisible( leftHandleInfo.primaryPosition );
243 if( mEventData->mUpdateRightSelectionPosition )
245 GetCursorPosition( mEventData->mRightSelectionPosition,
248 if( mEventData->mScrollAfterUpdatePosition )
250 ScrollToMakePositionVisible( rightHandleInfo.primaryPosition );
255 if( mEventData->mUpdateLeftSelectionPosition )
257 UpdateSelectionHandle( LEFT_SELECTION_HANDLE,
261 mEventData->mDecoratorUpdated = true;
264 if( mEventData->mUpdateRightSelectionPosition )
266 UpdateSelectionHandle( RIGHT_SELECTION_HANDLE,
270 mEventData->mDecoratorUpdated = true;
273 if( mEventData->mUpdateLeftSelectionPosition || mEventData->mUpdateRightSelectionPosition )
275 RepositionSelectionHandles();
277 mEventData->mUpdateLeftSelectionPosition = false;
278 mEventData->mUpdateRightSelectionPosition = false;
281 if( leftScroll || rightScroll )
283 mEventData->mScrollAfterUpdatePosition = false;
287 mEventData->mEventQueue.clear();
289 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::ProcessInputEvents\n" );
291 const bool decoratorUpdated = mEventData->mDecoratorUpdated;
292 mEventData->mDecoratorUpdated = false;
294 return decoratorUpdated;
297 void Controller::Impl::UpdateModel( OperationsMask operationsRequired )
299 DALI_LOG_INFO( gLogFilter, Debug::General, "Controller::UpdateModel\n" );
301 // Calculate the operations to be done.
302 const OperationsMask operations = static_cast<OperationsMask>( mOperationsPending & operationsRequired );
304 Vector<Character>& utf32Characters = mLogicalModel->mText;
306 const Length numberOfCharacters = utf32Characters.Count();
308 Vector<LineBreakInfo>& lineBreakInfo = mLogicalModel->mLineBreakInfo;
309 if( GET_LINE_BREAKS & operations )
311 // Retrieves the line break info. The line break info is used to split the text in 'paragraphs' to
312 // calculate the bidirectional info for each 'paragraph'.
313 // It's also used to layout the text (where it should be a new line) or to shape the text (text in different lines
314 // is not shaped together).
315 lineBreakInfo.Resize( numberOfCharacters, TextAbstraction::LINE_NO_BREAK );
317 SetLineBreakInfo( utf32Characters,
321 Vector<WordBreakInfo>& wordBreakInfo = mLogicalModel->mWordBreakInfo;
322 if( GET_WORD_BREAKS & operations )
324 // Retrieves the word break info. The word break info is used to layout the text (where to wrap the text in lines).
325 wordBreakInfo.Resize( numberOfCharacters, TextAbstraction::WORD_NO_BREAK );
327 SetWordBreakInfo( utf32Characters,
331 const bool getScripts = GET_SCRIPTS & operations;
332 const bool validateFonts = VALIDATE_FONTS & operations;
334 Vector<ScriptRun>& scripts = mLogicalModel->mScriptRuns;
335 Vector<FontRun>& validFonts = mLogicalModel->mFontRuns;
337 if( getScripts || validateFonts )
339 // Validates the fonts assigned by the application or assigns default ones.
340 // It makes sure all the characters are going to be rendered by the correct font.
341 MultilanguageSupport multilanguageSupport = MultilanguageSupport::Get();
345 // Retrieves the scripts used in the text.
346 multilanguageSupport.SetScripts( utf32Characters,
352 if( 0u == validFonts.Count() )
354 // Copy the requested font defaults received via the property system.
355 // These may not be valid i.e. may not contain glyphs for the necessary scripts.
356 GetDefaultFonts( validFonts, numberOfCharacters );
359 // Validates the fonts. If there is a character with no assigned font it sets a default one.
360 // After this call, fonts are validated.
361 multilanguageSupport.ValidateFonts( utf32Characters,
367 Vector<Character> mirroredUtf32Characters;
368 bool textMirrored = false;
369 Length numberOfParagraphs = 0u;
370 if( BIDI_INFO & operations )
372 // Count the number of LINE_NO_BREAK to reserve some space for the vector of paragraph's
373 // bidirectional info.
375 const TextAbstraction::LineBreakInfo* lineBreakInfoBuffer = lineBreakInfo.Begin();
376 for( Length index = 0u; index < numberOfCharacters; ++index )
378 if( TextAbstraction::LINE_NO_BREAK == *( lineBreakInfoBuffer + index ) )
380 ++numberOfParagraphs;
384 Vector<BidirectionalParagraphInfoRun>& bidirectionalInfo = mLogicalModel->mBidirectionalParagraphInfo;
385 bidirectionalInfo.Reserve( numberOfParagraphs );
387 // Calculates the bidirectional info for the whole paragraph if it contains right to left scripts.
388 SetBidirectionalInfo( utf32Characters,
393 if( 0u != bidirectionalInfo.Count() )
395 // This paragraph has right to left text. Some characters may need to be mirrored.
396 // TODO: consider if the mirrored string can be stored as well.
398 textMirrored = GetMirroredText( utf32Characters,
399 mirroredUtf32Characters,
402 // Only set the character directions if there is right to left characters.
403 Vector<CharacterDirection>& directions = mLogicalModel->mCharacterDirections;
404 directions.Resize( numberOfCharacters );
406 GetCharactersDirection( bidirectionalInfo,
411 // There is no right to left characters. Clear the directions vector.
412 mLogicalModel->mCharacterDirections.Clear();
416 Vector<GlyphInfo>& glyphs = mVisualModel->mGlyphs;
417 Vector<CharacterIndex>& glyphsToCharactersMap = mVisualModel->mGlyphsToCharacters;
418 Vector<Length>& charactersPerGlyph = mVisualModel->mCharactersPerGlyph;
419 Vector<GlyphIndex> newParagraphGlyphs;
420 newParagraphGlyphs.Reserve( numberOfParagraphs );
422 if( SHAPE_TEXT & operations )
424 const Vector<Character>& textToShape = textMirrored ? mirroredUtf32Characters : utf32Characters;
426 ShapeText( textToShape,
431 glyphsToCharactersMap,
433 newParagraphGlyphs );
435 // Create the 'number of glyphs' per character and the glyph to character conversion tables.
436 mVisualModel->CreateGlyphsPerCharacterTable( numberOfCharacters );
437 mVisualModel->CreateCharacterToGlyphTable( numberOfCharacters );
440 const Length numberOfGlyphs = glyphs.Count();
442 if( GET_GLYPH_METRICS & operations )
444 GlyphInfo* glyphsBuffer = glyphs.Begin();
445 mMetrics->GetGlyphMetrics( glyphsBuffer, numberOfGlyphs );
447 // Update the width and advance of all new paragraph characters.
448 for( Vector<GlyphIndex>::ConstIterator it = newParagraphGlyphs.Begin(), endIt = newParagraphGlyphs.End(); it != endIt; ++it )
450 const GlyphIndex index = *it;
451 GlyphInfo& glyph = *( glyphsBuffer + index );
453 glyph.xBearing = 0.f;
460 mEventData->mPreEditFlag &&
461 ( 0u != mVisualModel->mCharactersToGlyph.Count() ) )
463 // Add the underline for the pre-edit text.
464 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
465 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
467 const GlyphIndex glyphStart = *( charactersToGlyphBuffer + mEventData->mPreEditStartPosition );
468 const CharacterIndex lastPreEditCharacter = mEventData->mPreEditStartPosition + ( ( mEventData->mPreEditLength > 0u ) ? mEventData->mPreEditLength - 1u : 0u );
469 const Length numberOfGlyphsLastCharacter = *( glyphsPerCharacterBuffer + lastPreEditCharacter );
470 const GlyphIndex glyphEnd = *( charactersToGlyphBuffer + lastPreEditCharacter ) + ( numberOfGlyphsLastCharacter > 1u ? numberOfGlyphsLastCharacter - 1u : 0u );
472 GlyphRun underlineRun;
473 underlineRun.glyphIndex = glyphStart;
474 underlineRun.numberOfGlyphs = 1u + glyphEnd - glyphStart;
476 // TODO: At the moment the underline runs are only for pre-edit.
477 mVisualModel->mUnderlineRuns.PushBack( underlineRun );
481 void Controller::Impl::GetDefaultFonts( Vector<FontRun>& fonts, Length numberOfCharacters )
485 DALI_LOG_INFO( gLogFilter, Debug::Concise, "Controller::GetDefaultFonts font family(%s)\n", mFontDefaults->mFontDescription.family.c_str() );
487 fontRun.characterRun.characterIndex = 0;
488 fontRun.characterRun.numberOfCharacters = numberOfCharacters;
489 fontRun.fontId = mFontDefaults->GetFontId( mFontClient );
490 fontRun.isDefault = true;
492 fonts.PushBack( fontRun );
496 float Controller::Impl::GetDefaultFontLineHeight()
498 FontId defaultFontId = 0u;
499 if( NULL == mFontDefaults )
501 TextAbstraction::FontDescription fontDescription;
502 defaultFontId = mFontClient.GetFontId( fontDescription );
506 defaultFontId = mFontDefaults->GetFontId( mFontClient );
509 Text::FontMetrics fontMetrics;
510 mMetrics->GetFontMetrics( defaultFontId, fontMetrics );
512 return( fontMetrics.ascender - fontMetrics.descender );
515 void Controller::Impl::OnCursorKeyEvent( const Event& event )
517 if( NULL == mEventData )
519 // Nothing to do if there is no text input.
523 int keyCode = event.p1.mInt;
525 if( Dali::DALI_KEY_CURSOR_LEFT == keyCode )
527 if( mEventData->mPrimaryCursorPosition > 0u )
529 mEventData->mPrimaryCursorPosition = CalculateNewCursorIndex( mEventData->mPrimaryCursorPosition - 1u );
532 else if( Dali::DALI_KEY_CURSOR_RIGHT == keyCode )
534 if( mLogicalModel->mText.Count() > mEventData->mPrimaryCursorPosition )
536 mEventData->mPrimaryCursorPosition = CalculateNewCursorIndex( mEventData->mPrimaryCursorPosition );
539 else if( Dali::DALI_KEY_CURSOR_UP == keyCode )
543 else if( Dali::DALI_KEY_CURSOR_DOWN == keyCode )
548 mEventData->mUpdateCursorPosition = true;
549 mEventData->mScrollAfterUpdatePosition = true;
552 void Controller::Impl::OnTapEvent( const Event& event )
554 if( NULL != mEventData )
556 const unsigned int tapCount = event.p1.mUint;
560 if( IsShowingRealText() )
562 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
563 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
565 mEventData->mPrimaryCursorPosition = GetClosestCursorIndex( xPosition,
568 // When the cursor position is changing, delay cursor blinking
569 mEventData->mDecorator->DelayCursorBlink();
573 mEventData->mPrimaryCursorPosition = 0u;
576 mEventData->mUpdateCursorPosition = true;
577 mEventData->mScrollAfterUpdatePosition = true;
582 void Controller::Impl::OnPanEvent( const Event& event )
584 if( NULL == mEventData )
586 // Nothing to do if there is no text input.
590 int state = event.p1.mInt;
592 if( Gesture::Started == state ||
593 Gesture::Continuing == state )
595 const Vector2& actualSize = mVisualModel->GetActualSize();
596 const Vector2 currentScroll = mEventData->mScrollPosition;
598 if( mEventData->mHorizontalScrollingEnabled )
600 const float displacementX = event.p2.mFloat;
601 mEventData->mScrollPosition.x += displacementX;
603 ClampHorizontalScroll( actualSize );
606 if( mEventData->mVerticalScrollingEnabled )
608 const float displacementY = event.p3.mFloat;
609 mEventData->mScrollPosition.y += displacementY;
611 ClampVerticalScroll( actualSize );
614 if( mEventData->mDecorator )
616 mEventData->mDecorator->UpdatePositions( mEventData->mScrollPosition - currentScroll );
621 void Controller::Impl::OnLongPressEvent( const Event& event )
623 if ( EventData::EDITING == mEventData->mState )
625 ChangeState ( EventData::EDITING_WITH_POPUP );
626 mEventData->mDecoratorUpdated = true;
630 void Controller::Impl::OnHandleEvent( const Event& event )
632 if( NULL == mEventData )
634 // Nothing to do if there is no text input.
638 const unsigned int state = event.p1.mUint;
639 const bool handleStopScrolling = ( HANDLE_STOP_SCROLLING == state );
641 if( HANDLE_PRESSED == state )
643 // The event.p2 and event.p3 are in decorator coords. Need to transforms to text coords.
644 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
645 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
647 const CharacterIndex handleNewPosition = GetClosestCursorIndex( xPosition, yPosition );
649 if( Event::GRAB_HANDLE_EVENT == event.type )
651 ChangeState ( EventData::GRAB_HANDLE_PANNING );
653 if( handleNewPosition != mEventData->mPrimaryCursorPosition )
655 mEventData->mPrimaryCursorPosition = handleNewPosition;
656 mEventData->mUpdateCursorPosition = true;
659 else if( Event::LEFT_SELECTION_HANDLE_EVENT == event.type )
661 ChangeState ( EventData::SELECTION_HANDLE_PANNING );
663 if( ( handleNewPosition != mEventData->mLeftSelectionPosition ) &&
664 ( handleNewPosition != mEventData->mRightSelectionPosition ) )
666 mEventData->mLeftSelectionPosition = handleNewPosition;
668 mEventData->mUpdateLeftSelectionPosition = true;
671 else if( Event::RIGHT_SELECTION_HANDLE_EVENT == event.type )
673 ChangeState ( EventData::SELECTION_HANDLE_PANNING );
675 if( ( handleNewPosition != mEventData->mRightSelectionPosition ) &&
676 ( handleNewPosition != mEventData->mLeftSelectionPosition ) )
678 mEventData->mRightSelectionPosition = handleNewPosition;
680 mEventData->mUpdateRightSelectionPosition = true;
683 } // end ( HANDLE_PRESSED == state )
684 else if( ( HANDLE_RELEASED == state ) ||
685 handleStopScrolling )
687 CharacterIndex handlePosition = 0u;
688 if( handleStopScrolling )
690 // The event.p2 and event.p3 are in decorator coords. Need to transforms to text coords.
691 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
692 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
694 handlePosition = GetClosestCursorIndex( xPosition, yPosition );
697 if( Event::GRAB_HANDLE_EVENT == event.type )
699 mEventData->mUpdateCursorPosition = true;
701 ChangeState( EventData::EDITING_WITH_POPUP );
703 if( handleStopScrolling )
705 mEventData->mScrollAfterUpdatePosition = mEventData->mPrimaryCursorPosition != handlePosition;
706 mEventData->mPrimaryCursorPosition = handlePosition;
709 else if( Event::LEFT_SELECTION_HANDLE_EVENT == event.type )
711 ChangeState( EventData::SELECTING );
713 if( handleStopScrolling )
715 mEventData->mUpdateLeftSelectionPosition = ( mEventData->mRightSelectionPosition != handlePosition );
716 mEventData->mScrollAfterUpdatePosition = mEventData->mUpdateLeftSelectionPosition;
718 if( mEventData->mUpdateLeftSelectionPosition )
720 mEventData->mLeftSelectionPosition = handlePosition;
724 else if( Event::RIGHT_SELECTION_HANDLE_EVENT == event.type )
726 ChangeState( EventData::SELECTING );
728 if( handleStopScrolling )
730 mEventData->mUpdateRightSelectionPosition = ( mEventData->mLeftSelectionPosition != handlePosition );
731 mEventData->mScrollAfterUpdatePosition = mEventData->mUpdateRightSelectionPosition;
732 if( mEventData->mUpdateRightSelectionPosition )
734 mEventData->mRightSelectionPosition = handlePosition;
739 mEventData->mDecoratorUpdated = true;
740 } // end ( ( HANDLE_RELEASED == state ) || ( HANDLE_STOP_SCROLLING == state ) )
741 else if( HANDLE_SCROLLING == state )
743 const float xSpeed = event.p2.mFloat;
744 const Vector2& actualSize = mVisualModel->GetActualSize();
745 const Vector2 currentScrollPosition = mEventData->mScrollPosition;
747 mEventData->mScrollPosition.x += xSpeed;
749 ClampHorizontalScroll( actualSize );
751 bool endOfScroll = false;
752 if( Vector2::ZERO == ( currentScrollPosition - mEventData->mScrollPosition ) )
754 // Notify the decorator there is no more text to scroll.
755 // The decorator won't send more scroll events.
756 mEventData->mDecorator->NotifyEndOfScroll();
757 // Still need to set the position of the handle.
761 // Set the position of the handle.
762 const bool scrollRightDirection = xSpeed > 0.f;
763 const bool leftSelectionHandleEvent = Event::LEFT_SELECTION_HANDLE_EVENT == event.type;
764 const bool rightSelectionHandleEvent = Event::RIGHT_SELECTION_HANDLE_EVENT == event.type;
766 if( Event::GRAB_HANDLE_EVENT == event.type )
768 ChangeState( EventData::GRAB_HANDLE_PANNING );
770 Vector2 position = mEventData->mDecorator->GetPosition( GRAB_HANDLE );
772 // Position the grag handle close to either the left or right edge.
773 position.x = scrollRightDirection ? 0.f : mVisualModel->mControlSize.width;
775 // Get the new handle position.
776 // The grab handle's position is in decorator coords. Need to transforms to text coords.
777 const CharacterIndex handlePosition = GetClosestCursorIndex( position.x - mEventData->mScrollPosition.x - mAlignmentOffset.x,
778 position.y - mEventData->mScrollPosition.y - mAlignmentOffset.y );
780 mEventData->mUpdateCursorPosition = mEventData->mPrimaryCursorPosition != handlePosition;
781 mEventData->mScrollAfterUpdatePosition = mEventData->mUpdateCursorPosition;
782 mEventData->mPrimaryCursorPosition = handlePosition;
784 else if( leftSelectionHandleEvent || rightSelectionHandleEvent )
786 // TODO: This is recalculating the selection box every time the text is scrolled with the selection handles.
787 // Think if something can be done to save power.
789 ChangeState( EventData::SELECTION_HANDLE_PANNING );
791 Vector2 position = mEventData->mDecorator->GetPosition( leftSelectionHandleEvent ? Text::LEFT_SELECTION_HANDLE : Text::RIGHT_SELECTION_HANDLE );
793 // Position the selection handle close to either the left or right edge.
794 position.x = scrollRightDirection ? 0.f : mVisualModel->mControlSize.width;
796 // Get the new handle position.
797 // The selection handle's position is in decorator coords. Need to transforms to text coords.
798 const CharacterIndex handlePosition = GetClosestCursorIndex( position.x - mEventData->mScrollPosition.x - mAlignmentOffset.x,
799 position.y - mEventData->mScrollPosition.y - mAlignmentOffset.y );
801 if( leftSelectionHandleEvent )
803 const bool differentHandles = ( mEventData->mLeftSelectionPosition != handlePosition ) && ( mEventData->mRightSelectionPosition != handlePosition );
804 mEventData->mUpdateLeftSelectionPosition = endOfScroll || differentHandles;
805 if( differentHandles )
807 mEventData->mLeftSelectionPosition = handlePosition;
812 const bool differentHandles = ( mEventData->mRightSelectionPosition != handlePosition ) && ( mEventData->mLeftSelectionPosition != handlePosition );
813 mEventData->mUpdateRightSelectionPosition = endOfScroll || differentHandles;
814 if( differentHandles )
816 mEventData->mRightSelectionPosition = handlePosition;
820 if( mEventData->mUpdateLeftSelectionPosition || mEventData->mUpdateRightSelectionPosition )
822 RepositionSelectionHandles();
824 mEventData->mScrollAfterUpdatePosition = true;
827 mEventData->mDecoratorUpdated = true;
828 } // end ( HANDLE_SCROLLING == state )
831 void Controller::Impl::OnSelectEvent( const Event& event )
833 if( NULL == mEventData )
835 // Nothing to do if there is no text.
839 if( mEventData->mSelectionEnabled )
841 // The event.p2 and event.p3 are in decorator coords. Need to transforms to text coords.
842 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
843 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
845 // Calculates the logical position from the x,y coords.
846 RepositionSelectionHandles( xPosition,
849 mEventData->mUpdateLeftSelectionPosition = true;
850 mEventData->mUpdateRightSelectionPosition = true;
852 mEventData->mScrollAfterUpdatePosition = ( mEventData->mLeftSelectionPosition != mEventData->mRightSelectionPosition );
856 void Controller::Impl::OnSelectAllEvent()
858 if( NULL == mEventData )
860 // Nothing to do if there is no text.
864 if( mEventData->mSelectionEnabled )
866 mEventData->mLeftSelectionPosition = 0u;
867 mEventData->mRightSelectionPosition = mLogicalModel->mText.Count();
869 mEventData->mScrollAfterUpdatePosition = true;
870 mEventData->mUpdateLeftSelectionPosition = true;
871 mEventData->mUpdateRightSelectionPosition = true;
875 void Controller::Impl::RetrieveSelection( std::string& selectedText, bool deleteAfterRetreival )
877 if( mEventData->mLeftSelectionPosition == mEventData->mRightSelectionPosition )
879 // Nothing to select if handles are in the same place.
884 const bool handlesCrossed = mEventData->mLeftSelectionPosition > mEventData->mRightSelectionPosition;
886 //Get start and end position of selection
887 uint32_t startOfSelectedText = handlesCrossed ? mEventData->mRightSelectionPosition : mEventData->mLeftSelectionPosition;
888 uint32_t lengthOfSelectedText = ( handlesCrossed ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition ) - startOfSelectedText;
890 // Validate the start and end selection points
891 if( ( startOfSelectedText + lengthOfSelectedText ) <= mLogicalModel->mText.Count() )
893 //Get text as a UTF8 string
894 Vector<Character>& utf32Characters = mLogicalModel->mText;
896 Utf32ToUtf8( &utf32Characters[startOfSelectedText], lengthOfSelectedText, selectedText );
898 if ( deleteAfterRetreival ) // Only delete text if copied successfully
900 // Delete text between handles
901 Vector<Character>& currentText = mLogicalModel->mText;
903 Vector<Character>::Iterator first = currentText.Begin() + startOfSelectedText;
904 Vector<Character>::Iterator last = first + lengthOfSelectedText;
905 currentText.Erase( first, last );
907 // Scroll after delete.
908 mEventData->mPrimaryCursorPosition = handlesCrossed ? mEventData->mRightSelectionPosition : mEventData->mLeftSelectionPosition;
909 mEventData->mScrollAfterDelete = true;
911 // Udpade the cursor position and the decorator.
912 // Scroll after the position is updated if is not scrolling after delete.
913 mEventData->mUpdateCursorPosition = true;
914 mEventData->mScrollAfterUpdatePosition = !mEventData->mScrollAfterDelete;
915 mEventData->mDecoratorUpdated = true;
919 void Controller::Impl::ShowClipboard()
923 mClipboard.ShowClipboard();
927 void Controller::Impl::HideClipboard()
931 mClipboard.HideClipboard();
935 bool Controller::Impl::CopyStringToClipboard( std::string& source )
937 //Send string to clipboard
938 return ( mClipboard && mClipboard.SetItem( source ) );
941 void Controller::Impl::SendSelectionToClipboard( bool deleteAfterSending )
943 std::string selectedText;
944 RetrieveSelection( selectedText, deleteAfterSending );
945 CopyStringToClipboard( selectedText );
946 ChangeState( EventData::EDITING );
949 void Controller::Impl::GetTextFromClipboard( unsigned int itemIndex, std::string& retreivedString )
953 retreivedString = mClipboard.GetItem( itemIndex );
957 void Controller::Impl::RepositionSelectionHandles()
959 CharacterIndex selectionStart = mEventData->mLeftSelectionPosition;
960 CharacterIndex selectionEnd = mEventData->mRightSelectionPosition;
962 if( selectionStart == selectionEnd )
964 // Nothing to select if handles are in the same place.
968 mEventData->mDecorator->ClearHighlights();
970 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
971 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
972 const GlyphInfo* const glyphsBuffer = mVisualModel->mGlyphs.Begin();
973 const Vector2* const positionsBuffer = mVisualModel->mGlyphPositions.Begin();
974 const Length* const charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
975 const CharacterIndex* const glyphToCharacterBuffer = mVisualModel->mGlyphsToCharacters.Begin();
976 const CharacterDirection* const modelCharacterDirectionsBuffer = ( 0u != mLogicalModel->mCharacterDirections.Count() ) ? mLogicalModel->mCharacterDirections.Begin() : NULL;
978 // TODO: Better algorithm to create the highlight box.
981 // Get the height of the line.
982 const Vector<LineRun>& lines = mVisualModel->mLines;
983 const LineRun& firstLine = *lines.Begin();
984 const float height = firstLine.ascender + -firstLine.descender;
986 const bool isLastCharacter = selectionEnd >= mLogicalModel->mText.Count();
987 const bool startDirection = ( ( NULL == modelCharacterDirectionsBuffer ) ? false : *( modelCharacterDirectionsBuffer + selectionStart ) );
988 const bool endDirection = ( ( NULL == modelCharacterDirectionsBuffer ) ? false : *( modelCharacterDirectionsBuffer + ( selectionEnd - ( isLastCharacter ? 1u : 0u ) ) ) );
990 // Swap the indices if the start is greater than the end.
991 const bool indicesSwapped = selectionStart > selectionEnd;
993 // Tell the decorator to flip the selection handles if needed.
994 mEventData->mDecorator->SetSelectionHandleFlipState( indicesSwapped, startDirection, endDirection );
998 std::swap( selectionStart, selectionEnd );
1001 // Get the indices to the first and last selected glyphs.
1002 const CharacterIndex selectionEndMinusOne = selectionEnd - 1u;
1003 const GlyphIndex glyphStart = *( charactersToGlyphBuffer + selectionStart );
1004 const Length numberOfGlyphs = *( glyphsPerCharacterBuffer + selectionEndMinusOne );
1005 const GlyphIndex glyphEnd = *( charactersToGlyphBuffer + selectionEndMinusOne ) + ( ( numberOfGlyphs > 0 ) ? numberOfGlyphs - 1u : 0u );
1007 // Check if the first glyph is a ligature that must be broken like Latin ff, fi, or Arabic ﻻ, etc which needs special code.
1008 const Length numberOfCharactersStart = *( charactersPerGlyphBuffer + glyphStart );
1009 bool splitStartGlyph = ( numberOfCharactersStart > 1u ) && HasLigatureMustBreak( mLogicalModel->GetScript( selectionStart ) );
1011 // Check if the last glyph is a ligature that must be broken like Latin ff, fi, or Arabic ﻻ, etc which needs special code.
1012 const Length numberOfCharactersEnd = *( charactersPerGlyphBuffer + glyphEnd );
1013 bool splitEndGlyph = ( glyphStart != glyphEnd ) && ( numberOfCharactersEnd > 1u ) && HasLigatureMustBreak( mLogicalModel->GetScript( selectionEndMinusOne ) );
1015 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
1017 // Traverse the glyphs.
1018 for( GlyphIndex index = glyphStart; index <= glyphEnd; ++index )
1020 const GlyphInfo& glyph = *( glyphsBuffer + index );
1021 const Vector2& position = *( positionsBuffer + index );
1023 if( splitStartGlyph )
1025 // 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.
1027 const float glyphAdvance = glyph.advance / static_cast<float>( numberOfCharactersStart );
1028 const CharacterIndex interGlyphIndex = selectionStart - *( glyphToCharacterBuffer + glyphStart );
1029 // Get the direction of the character.
1030 CharacterDirection isCurrentRightToLeft = false;
1031 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
1033 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + selectionStart );
1036 // The end point could be in the middle of the ligature.
1037 // Calculate the number of characters selected.
1038 const Length numberOfCharacters = ( glyphStart == glyphEnd ) ? ( selectionEnd - selectionStart ) : ( numberOfCharactersStart - interGlyphIndex );
1040 const float xPosition = position.x - glyph.xBearing + offset.x + glyphAdvance * static_cast<float>( isCurrentRightToLeft ? ( numberOfCharactersStart - interGlyphIndex - numberOfCharacters ) : interGlyphIndex );
1042 mEventData->mDecorator->AddHighlight( xPosition,
1044 xPosition + static_cast<float>( numberOfCharacters ) * glyphAdvance,
1045 offset.y + height );
1047 splitStartGlyph = false;
1051 if( splitEndGlyph && ( index == glyphEnd ) )
1053 // 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.
1055 const float glyphAdvance = glyph.advance / static_cast<float>( numberOfCharactersEnd );
1056 const CharacterIndex interGlyphIndex = selectionEnd - *( glyphToCharacterBuffer + glyphEnd );
1057 // Get the direction of the character.
1058 CharacterDirection isCurrentRightToLeft = false;
1059 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
1061 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + selectionEnd );
1064 const Length numberOfCharacters = numberOfCharactersEnd - interGlyphIndex;
1066 const float xPosition = position.x - glyph.xBearing + offset.x + ( isCurrentRightToLeft ? ( glyphAdvance * static_cast<float>( numberOfCharacters ) ) : 0.f );
1067 mEventData->mDecorator->AddHighlight( xPosition,
1069 xPosition + static_cast<float>( interGlyphIndex ) * glyphAdvance,
1070 offset.y + height );
1072 splitEndGlyph = false;
1076 const float xPosition = position.x - glyph.xBearing + offset.x;
1077 mEventData->mDecorator->AddHighlight( xPosition,
1079 xPosition + glyph.advance,
1080 offset.y + height );
1083 CursorInfo primaryCursorInfo;
1084 GetCursorPosition( mEventData->mLeftSelectionPosition,
1085 primaryCursorInfo );
1087 CursorInfo secondaryCursorInfo;
1088 GetCursorPosition( mEventData->mRightSelectionPosition,
1089 secondaryCursorInfo );
1091 const Vector2 primaryPosition = primaryCursorInfo.primaryPosition + offset;
1092 const Vector2 secondaryPosition = secondaryCursorInfo.primaryPosition + offset;
1094 mEventData->mDecorator->SetPosition( LEFT_SELECTION_HANDLE, primaryPosition.x, primaryPosition.y, primaryCursorInfo.lineHeight );
1096 mEventData->mDecorator->SetPosition( RIGHT_SELECTION_HANDLE, secondaryPosition.x, secondaryPosition.y, secondaryCursorInfo.lineHeight );
1098 // Cursor to be positioned at end of selection so if selection interrupted and edit mode restarted the cursor will be at end of selection
1099 mEventData->mPrimaryCursorPosition = ( indicesSwapped ) ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition;
1101 // Set the flag to update the decorator.
1102 mEventData->mDecoratorUpdated = true;
1105 void Controller::Impl::RepositionSelectionHandles( float visualX, float visualY )
1107 if( NULL == mEventData )
1109 // Nothing to do if there is no text input.
1113 if( IsShowingPlaceholderText() )
1115 // Nothing to do if there is the place-holder text.
1119 const Length numberOfGlyphs = mVisualModel->mGlyphs.Count();
1120 const Length numberOfLines = mVisualModel->mLines.Count();
1121 if( 0 == numberOfGlyphs ||
1122 0 == numberOfLines )
1124 // Nothing to do if there is no text.
1128 // Find which word was selected
1129 CharacterIndex selectionStart( 0 );
1130 CharacterIndex selectionEnd( 0 );
1131 FindSelectionIndices( visualX, visualY, selectionStart, selectionEnd );
1132 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "%p selectionStart %d selectionEnd %d\n", this, selectionStart, selectionEnd );
1134 if( selectionStart == selectionEnd )
1136 ChangeState( EventData::EDITING );
1137 // Nothing to select. i.e. a white space, out of bounds
1141 mEventData->mLeftSelectionPosition = selectionStart;
1142 mEventData->mRightSelectionPosition = selectionEnd;
1145 void Controller::Impl::SetPopupButtons()
1148 * Sets the Popup buttons to be shown depending on State.
1150 * If SELECTING : CUT & COPY + ( PASTE & CLIPBOARD if content available to paste )
1152 * If EDITING_WITH_POPUP : SELECT & SELECT_ALL
1155 TextSelectionPopup::Buttons buttonsToShow = TextSelectionPopup::NONE;
1157 if ( ( EventData::SELECTING == mEventData->mState ) || ( EventData::SELECTION_CHANGED == mEventData->mState ) )
1159 buttonsToShow = TextSelectionPopup::Buttons( TextSelectionPopup::CUT | TextSelectionPopup::COPY );
1161 if ( !IsClipboardEmpty() )
1163 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
1164 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::CLIPBOARD ) );
1167 if ( !mEventData->mAllTextSelected )
1169 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::SELECT_ALL ) );
1172 else if ( EventData::EDITING_WITH_POPUP == mEventData->mState )
1174 if ( mLogicalModel->mText.Count() && !IsShowingPlaceholderText())
1176 buttonsToShow = TextSelectionPopup::Buttons( TextSelectionPopup::SELECT | TextSelectionPopup::SELECT_ALL );
1179 if ( !IsClipboardEmpty() )
1181 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
1182 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::CLIPBOARD ) );
1186 mEventData->mDecorator->SetEnabledPopupButtons( buttonsToShow );
1189 void Controller::Impl::ChangeState( EventData::State newState )
1191 if( NULL == mEventData )
1193 // Nothing to do if there is no text input.
1197 if( mEventData->mState != newState )
1199 mEventData->mState = newState;
1201 if( EventData::INACTIVE == mEventData->mState )
1203 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
1204 mEventData->mDecorator->StopCursorBlink();
1205 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1206 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1207 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1208 mEventData->mDecorator->SetPopupActive( false );
1209 mEventData->mDecoratorUpdated = true;
1212 else if ( EventData::INTERRUPTED == mEventData->mState)
1214 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1215 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1216 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1217 mEventData->mDecorator->SetPopupActive( false );
1218 mEventData->mDecoratorUpdated = true;
1221 else if ( EventData::SELECTING == mEventData->mState )
1223 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
1224 mEventData->mDecorator->StopCursorBlink();
1225 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1226 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true );
1227 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true );
1228 if( mEventData->mGrabHandlePopupEnabled )
1231 mEventData->mDecorator->SetPopupActive( true );
1233 mEventData->mDecoratorUpdated = true;
1235 else if ( EventData::SELECTION_CHANGED == mEventData->mState )
1237 if( mEventData->mGrabHandlePopupEnabled )
1240 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
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();
1451 const Length* const charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
1453 // If the vector is void, there is no right to left characters.
1454 const bool hasRightToLeftCharacters = NULL != visualToLogicalBuffer;
1456 const CharacterIndex startCharacter = line.characterRun.characterIndex;
1457 const CharacterIndex endCharacter = line.characterRun.characterIndex + line.characterRun.numberOfCharacters;
1458 DALI_ASSERT_DEBUG( endCharacter <= mLogicalModel->mText.Count() && "Invalid line info" );
1460 // Whether there is a hit on a glyph.
1461 bool matched = false;
1463 // Traverses glyphs in visual order. To do that use the visual to logical conversion table.
1464 CharacterIndex visualIndex = startCharacter;
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 first glyph for that character in logical order.
1474 const GlyphIndex glyphLogicalOrderIndex = *( charactersToGlyphBuffer + characterLogicalOrderIndex );
1475 // The number of glyphs for that character
1476 const Length numberOfGlyphs = *( glyphsPerCharacterBuffer + characterLogicalOrderIndex );
1478 // Get the metrics for the group of glyphs.
1479 GlyphMetrics glyphMetrics;
1480 GetGlyphsMetrics( glyphLogicalOrderIndex,
1486 const Vector2& position = *( positionsBuffer + glyphLogicalOrderIndex );
1488 // Prevents to jump the whole Latin ligatures like fi, ff, or Arabic ﻻ...
1489 const Length numberOfCharactersInLigature = HasLigatureMustBreak( script ) ? *( charactersPerGlyphBuffer + glyphLogicalOrderIndex ) : 1u;
1490 const float glyphAdvance = glyphMetrics.advance / static_cast<float>( numberOfCharactersInLigature );
1492 for( GlyphIndex index = 0u; !matched && ( index < numberOfCharactersInLigature ); ++index )
1494 // Find the mid-point of the area containing the glyph
1495 const float glyphCenter = -glyphMetrics.xBearing + position.x + ( static_cast<float>( index ) + 0.5f ) * glyphAdvance;
1497 if( visualX < glyphCenter )
1499 visualIndex += index;
1511 // Return the logical position of the cursor in characters.
1515 visualIndex = endCharacter;
1518 logicalIndex = hasRightToLeftCharacters ? *( visualToLogicalCursorBuffer + visualIndex ) : visualIndex;
1519 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "%p closest visualIndex %d logicalIndex %d\n", this, visualIndex, logicalIndex );
1521 DALI_ASSERT_DEBUG( ( logicalIndex <= mLogicalModel->mText.Count() && logicalIndex >= 0 ) && "GetClosestCursorIndex - Out of bounds index" );
1523 return logicalIndex;
1526 void Controller::Impl::GetCursorPosition( CharacterIndex logical,
1527 CursorInfo& cursorInfo )
1529 // TODO: Check for multiline with \n, etc...
1531 const Length numberOfCharacters = mLogicalModel->mText.Count();
1532 if( !IsShowingRealText() )
1534 // Do not want to use the place-holder text to set the cursor position.
1536 // Use the line's height of the font's family set to set the cursor's size.
1537 // If there is no font's family set, use the default font.
1538 // Use the current alignment to place the cursor at the beginning, center or end of the box.
1540 cursorInfo.lineHeight = GetDefaultFontLineHeight();
1541 cursorInfo.primaryCursorHeight = cursorInfo.lineHeight;
1543 switch( mLayoutEngine.GetHorizontalAlignment() )
1545 case LayoutEngine::HORIZONTAL_ALIGN_BEGIN:
1547 cursorInfo.primaryPosition.x = 0.f;
1550 case LayoutEngine::HORIZONTAL_ALIGN_CENTER:
1552 cursorInfo.primaryPosition.x = floorf( 0.5f * mVisualModel->mControlSize.width );
1555 case LayoutEngine::HORIZONTAL_ALIGN_END:
1557 cursorInfo.primaryPosition.x = mVisualModel->mControlSize.width - mEventData->mDecorator->GetCursorWidth();
1562 switch( mLayoutEngine.GetVerticalAlignment() )
1564 case LayoutEngine::VERTICAL_ALIGN_TOP:
1566 cursorInfo.primaryPosition.y = 0.f;
1569 case LayoutEngine::VERTICAL_ALIGN_CENTER:
1571 cursorInfo.primaryPosition.y = floorf( 0.5f * ( mVisualModel->mControlSize.height - cursorInfo.lineHeight ) );
1574 case LayoutEngine::VERTICAL_ALIGN_BOTTOM:
1576 cursorInfo.primaryPosition.y = mVisualModel->mControlSize.height - cursorInfo.lineHeight;
1581 // Nothing else to do.
1585 // Check if the logical position is the first or the last one of the text.
1586 const bool isFirstPosition = 0u == logical;
1587 const bool isLastPosition = numberOfCharacters == logical;
1589 // 'logical' is the logical 'cursor' index.
1590 // Get the next and current logical 'character' index.
1591 const CharacterIndex nextCharacterIndex = logical;
1592 const CharacterIndex characterIndex = isFirstPosition ? logical : logical - 1u;
1594 // Get the direction of the character and the next one.
1595 const CharacterDirection* const modelCharacterDirectionsBuffer = ( 0u != mLogicalModel->mCharacterDirections.Count() ) ? mLogicalModel->mCharacterDirections.Begin() : NULL;
1597 CharacterDirection isCurrentRightToLeft = false;
1598 CharacterDirection isNextRightToLeft = false;
1599 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
1601 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + characterIndex );
1602 isNextRightToLeft = *( modelCharacterDirectionsBuffer + nextCharacterIndex );
1605 // Get the line where the character is laid-out.
1606 const LineRun* const modelLines = mVisualModel->mLines.Begin();
1608 const LineIndex lineIndex = mVisualModel->GetLineOfCharacter( characterIndex );
1609 const LineRun& line = *( modelLines + lineIndex );
1611 // Get the paragraph's direction.
1612 const CharacterDirection isRightToLeftParagraph = line.direction;
1614 // Check whether there is an alternative position:
1616 cursorInfo.isSecondaryCursor = ( !isLastPosition && ( isCurrentRightToLeft != isNextRightToLeft ) ) ||
1617 ( isLastPosition && ( isRightToLeftParagraph != isCurrentRightToLeft ) );
1619 // Set the line height.
1620 cursorInfo.lineHeight = line.ascender + -line.descender;
1622 // Calculate the primary cursor.
1624 CharacterIndex index = characterIndex;
1625 if( cursorInfo.isSecondaryCursor )
1627 // If there is a secondary position, the primary cursor may be in a different place than the logical index.
1629 if( isLastPosition )
1631 // The position of the cursor after the last character needs special
1632 // care depending on its direction and the direction of the paragraph.
1634 // Need to find the first character after the last character with the paragraph's direction.
1635 // i.e l0 l1 l2 r0 r1 should find r0.
1637 // TODO: check for more than one line!
1638 index = isRightToLeftParagraph ? line.characterRun.characterIndex : line.characterRun.characterIndex + line.characterRun.numberOfCharacters - 1u;
1639 index = mLogicalModel->GetLogicalCharacterIndex( index );
1643 index = ( isRightToLeftParagraph == isCurrentRightToLeft ) ? characterIndex : nextCharacterIndex;
1647 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
1648 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
1649 const Length* const charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
1650 const CharacterIndex* const glyphsToCharactersBuffer = mVisualModel->mGlyphsToCharacters.Begin();
1651 const Vector2* const glyphPositionsBuffer = mVisualModel->mGlyphPositions.Begin();
1653 // Convert the cursor position into the glyph position.
1654 const GlyphIndex primaryGlyphIndex = *( charactersToGlyphBuffer + index );
1655 const Length primaryNumberOfGlyphs = *( glyphsPerCharacterBuffer + index );
1656 const Length primaryNumberOfCharacters = *( charactersPerGlyphBuffer + primaryGlyphIndex );
1658 // Get the metrics for the group of glyphs.
1659 GlyphMetrics glyphMetrics;
1660 GetGlyphsMetrics( primaryGlyphIndex,
1661 primaryNumberOfGlyphs,
1666 // Whether to add the glyph's advance to the cursor position.
1667 // 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,
1668 // if the logical cursor is one, the position is the position of the first glyph and the advance is added.
1669 // A 'truth table' was build and an online Karnaugh map tool was used to simplify the logic.
1690 // Where F -> isFirstPosition
1691 // L -> isLastPosition
1692 // C -> isCurrentRightToLeft
1693 // P -> isRightToLeftParagraph
1694 // A -> Whether to add the glyph's advance.
1696 const bool addGlyphAdvance = ( ( isLastPosition && !isRightToLeftParagraph ) ||
1697 ( isFirstPosition && isRightToLeftParagraph ) ||
1698 ( !isFirstPosition && !isLastPosition && !isCurrentRightToLeft ) );
1700 float glyphAdvance = addGlyphAdvance ? glyphMetrics.advance : 0.f;
1702 if( !isLastPosition &&
1703 ( primaryNumberOfCharacters > 1u ) )
1705 const CharacterIndex firstIndex = *( glyphsToCharactersBuffer + primaryGlyphIndex );
1707 bool isCurrentRightToLeft = false;
1708 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
1710 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + index );
1713 Length numberOfGlyphAdvance = ( isFirstPosition ? 0u : 1u ) + characterIndex - firstIndex;
1714 if( isCurrentRightToLeft )
1716 numberOfGlyphAdvance = primaryNumberOfCharacters - numberOfGlyphAdvance;
1719 glyphAdvance = static_cast<float>( numberOfGlyphAdvance ) * glyphMetrics.advance / static_cast<float>( primaryNumberOfCharacters );
1722 // Get the glyph position and x bearing.
1723 const Vector2& primaryPosition = *( glyphPositionsBuffer + primaryGlyphIndex );
1725 // Set the primary cursor's height.
1726 cursorInfo.primaryCursorHeight = cursorInfo.isSecondaryCursor ? 0.5f * glyphMetrics.fontHeight : glyphMetrics.fontHeight;
1728 // Set the primary cursor's position.
1729 cursorInfo.primaryPosition.x = -glyphMetrics.xBearing + primaryPosition.x + glyphAdvance;
1730 cursorInfo.primaryPosition.y = line.ascender - glyphMetrics.ascender;
1732 // Calculate the secondary cursor.
1734 if( cursorInfo.isSecondaryCursor )
1736 // Set the secondary cursor's height.
1737 cursorInfo.secondaryCursorHeight = 0.5f * glyphMetrics.fontHeight;
1739 CharacterIndex index = characterIndex;
1740 if( !isLastPosition )
1742 index = ( isRightToLeftParagraph == isCurrentRightToLeft ) ? nextCharacterIndex : characterIndex;
1745 const GlyphIndex secondaryGlyphIndex = *( charactersToGlyphBuffer + index );
1746 const Length secondaryNumberOfGlyphs = *( glyphsPerCharacterBuffer + index );
1748 const Vector2& secondaryPosition = *( glyphPositionsBuffer + secondaryGlyphIndex );
1750 GetGlyphsMetrics( secondaryGlyphIndex,
1751 secondaryNumberOfGlyphs,
1756 // Set the secondary cursor's position.
1757 cursorInfo.secondaryPosition.x = -glyphMetrics.xBearing + secondaryPosition.x + ( isCurrentRightToLeft ? 0.f : glyphMetrics.advance );
1758 cursorInfo.secondaryPosition.y = cursorInfo.lineHeight - cursorInfo.secondaryCursorHeight - line.descender - ( glyphMetrics.fontHeight - glyphMetrics.ascender );
1762 CharacterIndex Controller::Impl::CalculateNewCursorIndex( CharacterIndex index ) const
1764 if( NULL == mEventData )
1766 // Nothing to do if there is no text input.
1770 CharacterIndex cursorIndex = mEventData->mPrimaryCursorPosition;
1772 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
1773 const Length* const charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
1775 GlyphIndex glyphIndex = *( charactersToGlyphBuffer + index );
1776 Length numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
1778 if( numberOfCharacters > 1u )
1780 const Script script = mLogicalModel->GetScript( index );
1781 if( HasLigatureMustBreak( script ) )
1783 // Prevents to jump the whole Latin ligatures like fi, ff, or Arabic ﻻ, ...
1784 numberOfCharacters = 1u;
1789 while( 0u == numberOfCharacters )
1792 numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
1796 if( index < mEventData->mPrimaryCursorPosition )
1798 cursorIndex -= numberOfCharacters;
1802 cursorIndex += numberOfCharacters;
1808 void Controller::Impl::UpdateCursorPosition( const CursorInfo& cursorInfo )
1810 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "-->Controller::UpdateCursorPosition %p\n", this );
1811 if( NULL == mEventData )
1813 // Nothing to do if there is no text input.
1814 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition no event data\n" );
1818 const Vector2 offset = mEventData->mScrollPosition + ( IsShowingRealText() ? mAlignmentOffset : Vector2::ZERO );
1819 const Vector2 cursorPosition = cursorInfo.primaryPosition + offset;
1821 // Sets the cursor position.
1822 mEventData->mDecorator->SetPosition( PRIMARY_CURSOR,
1825 cursorInfo.primaryCursorHeight,
1826 cursorInfo.lineHeight );
1827 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Primary cursor position: %f,%f\n", cursorPosition.x, cursorPosition.y );
1829 // Sets the grab handle position.
1830 mEventData->mDecorator->SetPosition( GRAB_HANDLE,
1833 cursorInfo.lineHeight );
1835 if( cursorInfo.isSecondaryCursor )
1837 mEventData->mDecorator->SetPosition( SECONDARY_CURSOR,
1838 cursorInfo.secondaryPosition.x + offset.x,
1839 cursorInfo.secondaryPosition.y + offset.y,
1840 cursorInfo.secondaryCursorHeight,
1841 cursorInfo.lineHeight );
1842 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Secondary cursor position: %f,%f\n", cursorInfo.secondaryPosition.x + offset.x, cursorInfo.secondaryPosition.y + offset.y );
1845 // Set which cursors are active according the state.
1846 if( ( EventData::EDITING == mEventData->mState ) ||
1847 ( EventData::EDITING_WITH_POPUP == mEventData->mState ) ||
1848 ( EventData::EDITING_WITH_GRAB_HANDLE == mEventData->mState ) ||
1849 ( EventData::GRAB_HANDLE_PANNING == mEventData->mState ) )
1851 if( cursorInfo.isSecondaryCursor )
1853 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_BOTH );
1857 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1862 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
1865 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition\n" );
1868 void Controller::Impl::UpdateSelectionHandle( HandleType handleType,
1869 const CursorInfo& cursorInfo )
1871 if( ( LEFT_SELECTION_HANDLE != handleType ) &&
1872 ( RIGHT_SELECTION_HANDLE != handleType ) )
1877 const Vector2 cursorPosition = cursorInfo.primaryPosition + mEventData->mScrollPosition + mAlignmentOffset;
1879 // Sets the grab handle position.
1880 mEventData->mDecorator->SetPosition( handleType,
1883 cursorInfo.lineHeight );
1885 // If selection handle at start of the text and other at end of the text then all text is selected.
1886 const CharacterIndex startOfSelection = std::min( mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition );
1887 const CharacterIndex endOfSelection = std::max ( mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition );
1888 mEventData->mAllTextSelected = ( startOfSelection == 0 ) && ( endOfSelection == mLogicalModel->mText.Count() );
1891 void Controller::Impl::ClampHorizontalScroll( const Vector2& actualSize )
1893 // Clamp between -space & 0 (and the text alignment).
1895 if( actualSize.width > mVisualModel->mControlSize.width )
1897 const float space = ( actualSize.width - mVisualModel->mControlSize.width ) + mAlignmentOffset.x;
1898 mEventData->mScrollPosition.x = ( mEventData->mScrollPosition.x < -space ) ? -space : mEventData->mScrollPosition.x;
1899 mEventData->mScrollPosition.x = ( mEventData->mScrollPosition.x > -mAlignmentOffset.x ) ? -mAlignmentOffset.x : mEventData->mScrollPosition.x;
1901 mEventData->mDecoratorUpdated = true;
1905 mEventData->mScrollPosition.x = 0.f;
1909 void Controller::Impl::ClampVerticalScroll( const Vector2& actualSize )
1911 // Clamp between -space & 0 (and the text alignment).
1912 if( actualSize.height > mVisualModel->mControlSize.height )
1914 const float space = ( actualSize.height - mVisualModel->mControlSize.height ) + mAlignmentOffset.y;
1915 mEventData->mScrollPosition.y = ( mEventData->mScrollPosition.y < -space ) ? -space : mEventData->mScrollPosition.y;
1916 mEventData->mScrollPosition.y = ( mEventData->mScrollPosition.y > -mAlignmentOffset.y ) ? -mAlignmentOffset.y : mEventData->mScrollPosition.y;
1918 mEventData->mDecoratorUpdated = true;
1922 mEventData->mScrollPosition.y = 0.f;
1926 void Controller::Impl::ScrollToMakePositionVisible( const Vector2& position )
1928 // position is in actor's coords.
1929 const float positionEnd = position.x + ( mEventData->mDecorator ? mEventData->mDecorator->GetCursorWidth() : 0.f );
1931 // Transform the position to decorator coords.
1932 const float offset = mEventData->mScrollPosition.x + mAlignmentOffset.x;
1933 const float decoratorPositionBegin = position.x + offset;
1934 const float decoratorPositionEnd = positionEnd + offset;
1936 if( decoratorPositionBegin < 0.f )
1938 mEventData->mScrollPosition.x = -position.x - mAlignmentOffset.x;
1940 else if( decoratorPositionEnd > mVisualModel->mControlSize.width )
1942 mEventData->mScrollPosition.x = mVisualModel->mControlSize.width - positionEnd - mAlignmentOffset.x;
1946 void Controller::Impl::ScrollTextToMatchCursor( const CursorInfo& cursorInfo )
1948 // Get the current cursor position in decorator coords.
1949 const Vector2& currentCursorPosition = mEventData->mDecorator->GetPosition( PRIMARY_CURSOR );
1951 // Calculate the offset to match the cursor position before the character was deleted.
1952 mEventData->mScrollPosition.x = currentCursorPosition.x - cursorInfo.primaryPosition.x - mAlignmentOffset.x;
1954 ClampHorizontalScroll( mVisualModel->GetActualSize() );
1957 void Controller::Impl::RequestRelayout()
1959 mControlInterface.RequestTextRelayout();
1964 } // namespace Toolkit