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/layouts/layout-parameters.h>
29 #include <dali-toolkit/internal/text/multi-language-support.h>
30 #include <dali-toolkit/internal/text/script-run.h>
31 #include <dali-toolkit/internal/text/segmentation.h>
32 #include <dali-toolkit/internal/text/shaper.h>
33 #include <dali-toolkit/internal/text/text-io.h>
34 #include <dali-toolkit/internal/text/text-view.h>
39 #if defined(DEBUG_ENABLED)
40 Debug::Filter* gLogFilter = Debug::Filter::New(Debug::Concise, true, "LOG_TEXT_CONTROLS");
44 * @brief Some characters can be shaped in more than one glyph.
45 * This struct is used to retrieve metrics from these group of glyphs.
59 float fontHeight; ///< The font's height of that glyphs.
60 float advance; ///< The sum of all the advances of all the glyphs.
61 float ascender; ///< The font's ascender.
62 float xBearing; ///< The x bearing of the first glyph.
65 const std::string EMPTY_STRING("");
79 * @brief Get some glyph's metrics of a group of glyphs formed as a result of shaping one character.
81 * @param[in] glyphIndex The index to the first glyph.
82 * @param[in] numberOfGlyphs The number of glyphs.
83 * @param[out] glyphMetrics Some glyph metrics (font height, advance, ascender and x bearing).
87 void GetGlyphsMetrics( GlyphIndex glyphIndex,
88 Length numberOfGlyphs,
89 GlyphMetrics& glyphMetrics,
90 VisualModelPtr visualModel,
91 TextAbstraction::FontClient& fontClient )
93 const GlyphInfo* glyphsBuffer = visualModel->mGlyphs.Begin();
95 const GlyphInfo& firstGlyph = *( glyphsBuffer + glyphIndex );
97 Text::FontMetrics fontMetrics;
98 fontClient.GetFontMetrics( firstGlyph.fontId, fontMetrics );
100 glyphMetrics.fontHeight = fontMetrics.height;
101 glyphMetrics.advance = firstGlyph.advance;
102 glyphMetrics.ascender = fontMetrics.ascender;
103 glyphMetrics.xBearing = firstGlyph.xBearing;
105 for( unsigned int i = 1u; i < numberOfGlyphs; ++i )
107 const GlyphInfo& glyphInfo = *( glyphsBuffer + glyphIndex + i );
109 glyphMetrics.advance += glyphInfo.advance;
113 EventData::EventData( DecoratorPtr decorator )
114 : mDecorator( decorator ),
115 mPlaceholderTextActive(),
116 mPlaceholderTextInactive(),
117 mPlaceholderTextColor( 0.8f, 0.8f, 0.8f, 0.8f ),
121 mPrimaryCursorPosition( 0u ),
122 mLeftSelectionPosition( 0u ),
123 mRightSelectionPosition( 0u ),
124 mPreEditStartPosition( 0u ),
125 mPreEditLength( 0u ),
126 mIsShowingPlaceholderText( false ),
127 mPreEditFlag( false ),
128 mDecoratorUpdated( false ),
129 mCursorBlinkEnabled( true ),
130 mGrabHandleEnabled( true ),
131 mGrabHandlePopupEnabled( true ),
132 mSelectionEnabled( true ),
133 mHorizontalScrollingEnabled( true ),
134 mVerticalScrollingEnabled( false ),
135 mUpdateCursorPosition( false ),
136 mUpdateLeftSelectionPosition( false ),
137 mUpdateRightSelectionPosition( false ),
138 mScrollAfterUpdatePosition( false ),
139 mScrollAfterDelete( false )
142 EventData::~EventData()
145 bool Controller::Impl::ProcessInputEvents()
147 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "-->Controller::ProcessInputEvents\n" );
148 if( NULL == mEventData )
150 // Nothing to do if there is no text input.
151 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::ProcessInputEvents no event data\n" );
155 if( mEventData->mDecorator )
157 for( std::vector<Event>::iterator iter = mEventData->mEventQueue.begin();
158 iter != mEventData->mEventQueue.end();
163 case Event::CURSOR_KEY_EVENT:
165 OnCursorKeyEvent( *iter );
168 case Event::TAP_EVENT:
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 );
189 // The cursor must also be repositioned after inserts into the model
190 if( mEventData->mUpdateCursorPosition )
192 // Updates the cursor position and scrolls the text to make it visible.
194 UpdateCursorPosition();
196 if( mEventData->mScrollAfterUpdatePosition )
198 const Vector2& primaryCursorPosition = mEventData->mDecorator->GetPosition( PRIMARY_CURSOR );
200 ScrollToMakePositionVisible( primaryCursorPosition );
201 mEventData->mScrollAfterUpdatePosition = false;
204 mEventData->mDecoratorUpdated = true;
205 mEventData->mUpdateCursorPosition = false;
207 else if( mEventData->mScrollAfterDelete )
209 ScrollTextToMatchCursor();
210 mEventData->mDecoratorUpdated = true;
211 mEventData->mScrollAfterDelete = false;
215 bool leftScroll = false;
216 bool rightScroll = false;
218 if( mEventData->mUpdateLeftSelectionPosition )
220 UpdateSelectionHandle( LEFT_SELECTION_HANDLE );
222 if( mEventData->mScrollAfterUpdatePosition )
224 const Vector2& leftHandlePosition = mEventData->mDecorator->GetPosition( LEFT_SELECTION_HANDLE );
226 ScrollToMakePositionVisible( leftHandlePosition );
230 mEventData->mDecoratorUpdated = true;
231 mEventData->mUpdateLeftSelectionPosition = false;
234 if( mEventData->mUpdateRightSelectionPosition )
236 UpdateSelectionHandle( RIGHT_SELECTION_HANDLE );
238 if( mEventData->mScrollAfterUpdatePosition )
240 const Vector2& rightHandlePosition = mEventData->mDecorator->GetPosition( RIGHT_SELECTION_HANDLE );
242 ScrollToMakePositionVisible( rightHandlePosition );
246 mEventData->mDecoratorUpdated = true;
247 mEventData->mUpdateRightSelectionPosition = false;
250 if( leftScroll || rightScroll )
252 mEventData->mScrollAfterUpdatePosition = false;
256 mEventData->mEventQueue.clear();
258 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::ProcessInputEvents\n" );
260 const bool decoratorUpdated = mEventData->mDecoratorUpdated;
261 mEventData->mDecoratorUpdated = false;
263 return decoratorUpdated;
266 void Controller::Impl::UpdateModel( OperationsMask operationsRequired )
268 // Calculate the operations to be done.
269 const OperationsMask operations = static_cast<OperationsMask>( mOperationsPending & operationsRequired );
271 Vector<Character>& utf32Characters = mLogicalModel->mText;
273 const Length numberOfCharacters = utf32Characters.Count();
275 Vector<LineBreakInfo>& lineBreakInfo = mLogicalModel->mLineBreakInfo;
276 if( GET_LINE_BREAKS & operations )
278 // Retrieves the line break info. The line break info is used to split the text in 'paragraphs' to
279 // calculate the bidirectional info for each 'paragraph'.
280 // It's also used to layout the text (where it should be a new line) or to shape the text (text in different lines
281 // is not shaped together).
282 lineBreakInfo.Resize( numberOfCharacters, TextAbstraction::LINE_NO_BREAK );
284 SetLineBreakInfo( utf32Characters,
288 Vector<WordBreakInfo>& wordBreakInfo = mLogicalModel->mWordBreakInfo;
289 if( GET_WORD_BREAKS & operations )
291 // Retrieves the word break info. The word break info is used to layout the text (where to wrap the text in lines).
292 wordBreakInfo.Resize( numberOfCharacters, TextAbstraction::WORD_NO_BREAK );
294 SetWordBreakInfo( utf32Characters,
298 const bool getScripts = GET_SCRIPTS & operations;
299 const bool validateFonts = VALIDATE_FONTS & operations;
301 Vector<ScriptRun>& scripts = mLogicalModel->mScriptRuns;
302 Vector<FontRun>& validFonts = mLogicalModel->mFontRuns;
304 if( getScripts || validateFonts )
306 // Validates the fonts assigned by the application or assigns default ones.
307 // It makes sure all the characters are going to be rendered by the correct font.
308 MultilanguageSupport multilanguageSupport = MultilanguageSupport::Get();
312 // Retrieves the scripts used in the text.
313 multilanguageSupport.SetScripts( utf32Characters,
320 if( 0u == validFonts.Count() )
322 // Copy the requested font defaults received via the property system.
323 // These may not be valid i.e. may not contain glyphs for the necessary scripts.
324 GetDefaultFonts( validFonts, numberOfCharacters );
327 // Validates the fonts. If there is a character with no assigned font it sets a default one.
328 // After this call, fonts are validated.
329 multilanguageSupport.ValidateFonts( utf32Characters,
335 Vector<Character> mirroredUtf32Characters;
336 bool textMirrored = false;
337 if( BIDI_INFO & operations )
339 // Count the number of LINE_NO_BREAK to reserve some space for the vector of paragraph's
340 // bidirectional info.
342 Length numberOfParagraphs = 0u;
344 const TextAbstraction::LineBreakInfo* lineBreakInfoBuffer = lineBreakInfo.Begin();
345 for( Length index = 0u; index < numberOfCharacters; ++index )
347 if( TextAbstraction::LINE_NO_BREAK == *( lineBreakInfoBuffer + index ) )
349 ++numberOfParagraphs;
353 Vector<BidirectionalParagraphInfoRun>& bidirectionalInfo = mLogicalModel->mBidirectionalParagraphInfo;
354 bidirectionalInfo.Reserve( numberOfParagraphs );
356 // Calculates the bidirectional info for the whole paragraph if it contains right to left scripts.
357 SetBidirectionalInfo( utf32Characters,
362 if( 0u != bidirectionalInfo.Count() )
364 // This paragraph has right to left text. Some characters may need to be mirrored.
365 // TODO: consider if the mirrored string can be stored as well.
367 textMirrored = GetMirroredText( utf32Characters, mirroredUtf32Characters );
369 // Only set the character directions if there is right to left characters.
370 Vector<CharacterDirection>& directions = mLogicalModel->mCharacterDirections;
371 directions.Resize( numberOfCharacters );
373 GetCharactersDirection( bidirectionalInfo,
378 // There is no right to left characters. Clear the directions vector.
379 mLogicalModel->mCharacterDirections.Clear();
384 Vector<GlyphInfo>& glyphs = mVisualModel->mGlyphs;
385 Vector<CharacterIndex>& glyphsToCharactersMap = mVisualModel->mGlyphsToCharacters;
386 Vector<Length>& charactersPerGlyph = mVisualModel->mCharactersPerGlyph;
387 if( SHAPE_TEXT & operations )
389 const Vector<Character>& textToShape = textMirrored ? mirroredUtf32Characters : utf32Characters;
391 ShapeText( textToShape,
396 glyphsToCharactersMap,
397 charactersPerGlyph );
399 // Create the 'number of glyphs' per character and the glyph to character conversion tables.
400 mVisualModel->CreateGlyphsPerCharacterTable( numberOfCharacters );
401 mVisualModel->CreateCharacterToGlyphTable( numberOfCharacters );
404 const Length numberOfGlyphs = glyphs.Count();
406 if( GET_GLYPH_METRICS & operations )
408 mFontClient.GetGlyphMetrics( glyphs.Begin(), numberOfGlyphs );
412 void Controller::Impl::GetDefaultFonts( Vector<FontRun>& fonts, Length numberOfCharacters )
417 fontRun.characterRun.characterIndex = 0;
418 fontRun.characterRun.numberOfCharacters = numberOfCharacters;
419 fontRun.fontId = mFontDefaults->GetFontId( mFontClient );
420 fontRun.isDefault = true;
422 fonts.PushBack( fontRun );
426 void Controller::Impl::OnCursorKeyEvent( const Event& event )
428 if( NULL == mEventData )
430 // Nothing to do if there is no text input.
434 int keyCode = event.p1.mInt;
436 if( Dali::DALI_KEY_CURSOR_LEFT == keyCode )
438 if( mEventData->mPrimaryCursorPosition > 0u )
440 mEventData->mPrimaryCursorPosition = CalculateNewCursorIndex( mEventData->mPrimaryCursorPosition - 1u );
443 else if( Dali::DALI_KEY_CURSOR_RIGHT == keyCode )
445 if( mLogicalModel->GetNumberOfCharacters() > mEventData->mPrimaryCursorPosition )
447 mEventData->mPrimaryCursorPosition = CalculateNewCursorIndex( mEventData->mPrimaryCursorPosition );
450 else if( Dali::DALI_KEY_CURSOR_UP == keyCode )
454 else if( Dali::DALI_KEY_CURSOR_DOWN == keyCode )
459 mEventData->mUpdateCursorPosition = true;
460 mEventData->mScrollAfterUpdatePosition = true;
463 void Controller::Impl::OnTapEvent( const Event& event )
465 if( NULL != mEventData )
467 const unsigned int tapCount = event.p1.mUint;
471 if( ! IsShowingPlaceholderText() )
473 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
474 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
476 mEventData->mPrimaryCursorPosition = GetClosestCursorIndex( xPosition,
481 mEventData->mPrimaryCursorPosition = 0u;
484 mEventData->mUpdateCursorPosition = true;
485 mEventData->mScrollAfterUpdatePosition = true;
487 else if( mEventData->mSelectionEnabled &&
490 // The event.p2 and event.p3 are in decorator coords. Need to transforms to text coords.
491 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
492 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
494 RepositionSelectionHandles( xPosition,
497 mEventData->mScrollAfterUpdatePosition = true;
498 mEventData->mUpdateLeftSelectionPosition = true;
499 mEventData->mUpdateRightSelectionPosition = true;
504 void Controller::Impl::OnPanEvent( const Event& event )
506 if( NULL == mEventData )
508 // Nothing to do if there is no text input.
512 int state = event.p1.mInt;
514 if( Gesture::Started == state ||
515 Gesture::Continuing == state )
517 const Vector2& actualSize = mVisualModel->GetActualSize();
518 const Vector2 currentScroll = mEventData->mScrollPosition;
520 if( mEventData->mHorizontalScrollingEnabled )
522 const float displacementX = event.p2.mFloat;
523 mEventData->mScrollPosition.x += displacementX;
525 ClampHorizontalScroll( actualSize );
528 if( mEventData->mVerticalScrollingEnabled )
530 const float displacementY = event.p3.mFloat;
531 mEventData->mScrollPosition.y += displacementY;
533 ClampVerticalScroll( actualSize );
536 if( mEventData->mDecorator )
538 mEventData->mDecorator->UpdatePositions( mEventData->mScrollPosition - currentScroll );
543 void Controller::Impl::OnHandleEvent( const Event& event )
545 if( NULL == mEventData )
547 // Nothing to do if there is no text input.
551 const unsigned int state = event.p1.mUint;
552 const bool handleStopScrolling = ( HANDLE_STOP_SCROLLING == state );
554 if( HANDLE_PRESSED == state )
556 // The event.p2 and event.p3 are in decorator coords. Need to transforms to text coords.
557 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
558 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
560 const CharacterIndex handleNewPosition = GetClosestCursorIndex( xPosition, yPosition );
562 if( Event::GRAB_HANDLE_EVENT == event.type )
564 ChangeState ( EventData::GRAB_HANDLE_PANNING );
566 if( handleNewPosition != mEventData->mPrimaryCursorPosition )
568 mEventData->mPrimaryCursorPosition = handleNewPosition;
569 mEventData->mUpdateCursorPosition = true;
572 else if( Event::LEFT_SELECTION_HANDLE_EVENT == event.type )
574 ChangeState ( EventData::SELECTION_HANDLE_PANNING );
576 if( handleNewPosition != mEventData->mLeftSelectionPosition )
578 mEventData->mLeftSelectionPosition = handleNewPosition;
580 RepositionSelectionHandles( mEventData->mLeftSelectionPosition,
581 mEventData->mRightSelectionPosition );
583 mEventData->mUpdateLeftSelectionPosition = true;
586 else if( Event::RIGHT_SELECTION_HANDLE_EVENT == event.type )
588 ChangeState ( EventData::SELECTION_HANDLE_PANNING );
590 if( handleNewPosition != mEventData->mRightSelectionPosition )
592 mEventData->mRightSelectionPosition = handleNewPosition;
594 RepositionSelectionHandles( mEventData->mLeftSelectionPosition,
595 mEventData->mRightSelectionPosition );
597 mEventData->mUpdateRightSelectionPosition = true;
600 } // end ( HANDLE_PRESSED == state )
601 else if( ( HANDLE_RELEASED == state ) ||
602 handleStopScrolling )
604 CharacterIndex handlePosition = 0u;
605 if( handleStopScrolling )
607 // The event.p2 and event.p3 are in decorator coords. Need to transforms to text coords.
608 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
609 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
611 handlePosition = GetClosestCursorIndex( xPosition, yPosition );
614 if( Event::GRAB_HANDLE_EVENT == event.type )
616 mEventData->mUpdateCursorPosition = true;
618 ChangeState( EventData::EDITING_WITH_POPUP );
620 if( handleStopScrolling )
622 mEventData->mScrollAfterUpdatePosition = mEventData->mPrimaryCursorPosition != handlePosition;
623 mEventData->mPrimaryCursorPosition = handlePosition;
626 else if( Event::LEFT_SELECTION_HANDLE_EVENT == event.type )
628 ChangeState( EventData::SELECTING );
630 if( handleStopScrolling )
632 mEventData->mUpdateLeftSelectionPosition = mEventData->mLeftSelectionPosition != handlePosition;
633 mEventData->mScrollAfterUpdatePosition = mEventData->mUpdateLeftSelectionPosition;
634 mEventData->mLeftSelectionPosition = handlePosition;
636 if( mEventData->mUpdateLeftSelectionPosition )
638 RepositionSelectionHandles( mEventData->mLeftSelectionPosition,
639 mEventData->mRightSelectionPosition );
643 else if( Event::RIGHT_SELECTION_HANDLE_EVENT == event.type )
645 ChangeState( EventData::SELECTING );
647 if( handleStopScrolling )
649 mEventData->mUpdateRightSelectionPosition = mEventData->mRightSelectionPosition != handlePosition;
650 mEventData->mScrollAfterUpdatePosition = mEventData->mUpdateRightSelectionPosition;
651 mEventData->mRightSelectionPosition = handlePosition;
653 if( mEventData->mUpdateRightSelectionPosition )
655 RepositionSelectionHandles( mEventData->mLeftSelectionPosition,
656 mEventData->mRightSelectionPosition );
661 mEventData->mDecoratorUpdated = true;
662 } // end ( ( HANDLE_RELEASED == state ) || ( HANDLE_STOP_SCROLLING == state ) )
663 else if( HANDLE_SCROLLING == state )
665 const float xSpeed = event.p2.mFloat;
666 const Vector2& actualSize = mVisualModel->GetActualSize();
668 mEventData->mScrollPosition.x += xSpeed;
670 ClampHorizontalScroll( actualSize );
672 const bool leftSelectionHandleEvent = Event::LEFT_SELECTION_HANDLE_EVENT == event.type;
673 const bool rightSelectionHandleEvent = Event::RIGHT_SELECTION_HANDLE_EVENT == event.type;
675 if( Event::GRAB_HANDLE_EVENT == event.type )
677 ChangeState( EventData::GRAB_HANDLE_PANNING );
679 else if( leftSelectionHandleEvent || rightSelectionHandleEvent )
681 // TODO: This is recalculating the selection box every time the text is scrolled with the selection handles.
682 // Think if something can be done to save power.
684 ChangeState( EventData::SELECTION_HANDLE_PANNING );
686 const Vector2& position = mEventData->mDecorator->GetPosition( leftSelectionHandleEvent ? Text::LEFT_SELECTION_HANDLE : Text::RIGHT_SELECTION_HANDLE );
688 // Get the new handle position.
689 // The selection handle's position is in decorator coords. Need to transforms to text coords.
690 const CharacterIndex handlePosition = GetClosestCursorIndex( position.x - mEventData->mScrollPosition.x - mAlignmentOffset.x,
691 position.y - mEventData->mScrollPosition.y - mAlignmentOffset.y );
693 if( leftSelectionHandleEvent )
695 mEventData->mUpdateLeftSelectionPosition = handlePosition != mEventData->mLeftSelectionPosition;
696 mEventData->mLeftSelectionPosition = handlePosition;
700 mEventData->mUpdateRightSelectionPosition = handlePosition != mEventData->mRightSelectionPosition;
701 mEventData->mRightSelectionPosition = handlePosition;
704 if( mEventData->mUpdateLeftSelectionPosition || mEventData->mUpdateRightSelectionPosition )
706 RepositionSelectionHandles( mEventData->mLeftSelectionPosition,
707 mEventData->mRightSelectionPosition );
710 mEventData->mDecoratorUpdated = true;
711 } // end ( HANDLE_SCROLLING == state )
714 void Controller::Impl::RepositionSelectionHandles( CharacterIndex selectionStart, CharacterIndex selectionEnd )
716 mEventData->mDecorator->ClearHighlights();
718 mEventData->mLeftSelectionPosition = selectionStart;
719 mEventData->mRightSelectionPosition = selectionEnd;
721 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
722 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
723 const GlyphInfo* const glyphsBuffer = mVisualModel->mGlyphs.Begin();
724 const Vector2* const positionsBuffer = mVisualModel->mGlyphPositions.Begin();
726 // TODO: Better algorithm to create the highlight box.
729 const Vector<LineRun>& lines = mVisualModel->mLines;
730 const float height = lines[0].ascender + -lines[0].descender;
732 const bool indicesSwapped = selectionStart > selectionEnd;
735 std::swap( selectionStart, selectionEnd );
738 GlyphIndex glyphStart = *( charactersToGlyphBuffer + selectionStart );
739 GlyphIndex glyphEnd = *( charactersToGlyphBuffer + ( selectionEnd - 1u ) ) + *( glyphsPerCharacterBuffer + ( selectionEnd - 1u ) ) - 1u;
741 mEventData->mDecorator->SwapSelectionHandlesEnabled( indicesSwapped );
743 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
745 for( GlyphIndex index = glyphStart; index <= glyphEnd; ++index )
747 const GlyphInfo& glyph = *( glyphsBuffer + index );
748 const Vector2& position = *( positionsBuffer + index );
750 const float xPosition = position.x - glyph.xBearing + offset.x;
751 mEventData->mDecorator->AddHighlight( xPosition, offset.y, xPosition + glyph.advance, height );
754 CursorInfo primaryCursorInfo;
755 GetCursorPosition( mEventData->mLeftSelectionPosition,
758 CursorInfo secondaryCursorInfo;
759 GetCursorPosition( mEventData->mRightSelectionPosition,
760 secondaryCursorInfo );
762 const Vector2 primaryPosition = primaryCursorInfo.primaryPosition + offset;
763 const Vector2 secondaryPosition = secondaryCursorInfo.primaryPosition + offset;
765 mEventData->mDecorator->SetPosition( LEFT_SELECTION_HANDLE, primaryPosition.x, primaryPosition.y, primaryCursorInfo.lineHeight );
767 mEventData->mDecorator->SetPosition( RIGHT_SELECTION_HANDLE, secondaryPosition.x, secondaryPosition.y, secondaryCursorInfo.lineHeight );
769 // Set the flag to update the decorator.
770 mEventData->mDecoratorUpdated = true;
773 void Controller::Impl::RepositionSelectionHandles( float visualX, float visualY )
775 if( NULL == mEventData )
777 // Nothing to do if there is no text input.
781 if( IsShowingPlaceholderText() )
783 // Nothing to do if there is the place-holder text.
787 const Length numberOfGlyphs = mVisualModel->mGlyphs.Count();
788 const Length numberOfLines = mVisualModel->mLines.Count();
789 if( 0 == numberOfGlyphs ||
792 // Nothing to do if there is no text.
796 // Find which word was selected
797 CharacterIndex selectionStart( 0 );
798 CharacterIndex selectionEnd( 0 );
799 FindSelectionIndices( visualX, visualY, selectionStart, selectionEnd );
800 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "%p selectionStart %d selectionEnd %d\n", this, selectionStart, selectionEnd );
802 if( selectionStart == selectionEnd )
804 ChangeState( EventData::EDITING );
805 // Nothing to select. i.e. a white space, out of bounds
809 RepositionSelectionHandles( selectionStart, selectionEnd );
812 void Controller::Impl::ChangeState( EventData::State newState )
814 if( NULL == mEventData )
816 // Nothing to do if there is no text input.
820 if( mEventData->mState != newState )
822 mEventData->mState = newState;
824 if( EventData::INACTIVE == mEventData->mState )
826 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
827 mEventData->mDecorator->StopCursorBlink();
828 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
829 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
830 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
831 mEventData->mDecorator->SetPopupActive( false );
832 mEventData->mDecoratorUpdated = true;
834 else if ( EventData::SELECTING == mEventData->mState )
836 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
837 mEventData->mDecorator->StopCursorBlink();
838 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
839 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true );
840 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true );
841 if( mEventData->mGrabHandlePopupEnabled )
843 TextSelectionPopup::Buttons selectedButtons = TextSelectionPopup::Buttons( TextSelectionPopup::COPY );
844 mEventData->mDecorator->SetEnabledPopupButtons( selectedButtons );
845 mEventData->mDecorator->SetPopupActive( true );
847 mEventData->mDecoratorUpdated = true;
849 else if( EventData::EDITING == mEventData->mState )
851 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
852 if( mEventData->mCursorBlinkEnabled )
854 mEventData->mDecorator->StartCursorBlink();
856 // Grab handle is not shown until a tap is received whilst EDITING
857 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
858 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
859 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
860 if( mEventData->mGrabHandlePopupEnabled )
862 mEventData->mDecorator->SetPopupActive( false );
864 mEventData->mDecoratorUpdated = true;
866 else if( EventData::EDITING_WITH_POPUP == mEventData->mState )
868 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
869 if( mEventData->mCursorBlinkEnabled )
871 mEventData->mDecorator->StartCursorBlink();
873 if( mEventData->mSelectionEnabled )
875 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
876 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
880 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
882 if( mEventData->mGrabHandlePopupEnabled )
884 TextSelectionPopup::Buttons selectionButtons = TextSelectionPopup::Buttons( TextSelectionPopup::SELECT | TextSelectionPopup::SELECT_ALL );
885 mEventData->mDecorator->SetEnabledPopupButtons( selectionButtons );
886 mEventData->mDecorator->SetPopupActive( true );
888 mEventData->mDecoratorUpdated = true;
890 else if ( EventData::SELECTION_HANDLE_PANNING == mEventData->mState )
892 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
893 mEventData->mDecorator->StopCursorBlink();
894 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
895 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true );
896 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true );
897 if( mEventData->mGrabHandlePopupEnabled )
899 mEventData->mDecorator->SetPopupActive( false );
901 mEventData->mDecoratorUpdated = true;
903 else if ( EventData::GRAB_HANDLE_PANNING == mEventData->mState )
905 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
906 if( mEventData->mCursorBlinkEnabled )
908 mEventData->mDecorator->StartCursorBlink();
910 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
911 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
912 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
913 if( mEventData->mGrabHandlePopupEnabled )
915 mEventData->mDecorator->SetPopupActive( false );
917 mEventData->mDecoratorUpdated = true;
922 LineIndex Controller::Impl::GetClosestLine( float y ) const
924 float totalHeight = 0.f;
925 LineIndex lineIndex = 0u;
927 const Vector<LineRun>& lines = mVisualModel->mLines;
928 for( LineIndex endLine = lines.Count();
932 const LineRun& lineRun = lines[lineIndex];
933 totalHeight += lineRun.ascender + -lineRun.descender;
934 if( y < totalHeight )
948 void Controller::Impl::FindSelectionIndices( float visualX, float visualY, CharacterIndex& startIndex, CharacterIndex& endIndex )
950 CharacterIndex hitCharacter = GetClosestCursorIndex( visualX, visualY );
951 if( hitCharacter >= mLogicalModel->mText.Count() )
953 // Selection out of bounds.
957 startIndex = hitCharacter;
958 endIndex = hitCharacter;
960 if( !TextAbstraction::IsWhiteSpace( mLogicalModel->mText[hitCharacter] ) )
962 // Find the start and end of the text
963 for( startIndex = hitCharacter; startIndex > 0; --startIndex )
965 Character charCode = mLogicalModel->mText[ startIndex-1 ];
966 if( TextAbstraction::IsWhiteSpace( charCode ) )
971 const CharacterIndex pastTheEnd = mLogicalModel->mText.Count();
972 for( endIndex = hitCharacter + 1u; endIndex < pastTheEnd; ++endIndex )
974 Character charCode = mLogicalModel->mText[ endIndex ];
975 if( TextAbstraction::IsWhiteSpace( charCode ) )
983 CharacterIndex Controller::Impl::GetClosestCursorIndex( float visualX,
986 if( NULL == mEventData )
988 // Nothing to do if there is no text input.
992 CharacterIndex logicalIndex = 0u;
994 const Length numberOfGlyphs = mVisualModel->mGlyphs.Count();
995 const Length numberOfLines = mVisualModel->mLines.Count();
996 if( 0 == numberOfGlyphs ||
1002 // Find which line is closest
1003 const LineIndex lineIndex = GetClosestLine( visualY );
1004 const LineRun& line = mVisualModel->mLines[lineIndex];
1006 // Get the positions of the glyphs.
1007 const Vector<Vector2>& positions = mVisualModel->mGlyphPositions;
1008 const Vector2* const positionsBuffer = positions.Begin();
1010 // Get the visual to logical conversion tables.
1011 const CharacterIndex* const visualToLogicalBuffer = ( 0u != mLogicalModel->mVisualToLogicalMap.Count() ) ? mLogicalModel->mVisualToLogicalMap.Begin() : NULL;
1012 const CharacterIndex* const visualToLogicalCursorBuffer = mLogicalModel->mVisualToLogicalCursorMap.Begin();
1014 // Get the character to glyph conversion table.
1015 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
1017 // Get the glyphs per character table.
1018 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
1020 // If the vector is void, there is no right to left characters.
1021 const bool hasRightToLeftCharacters = NULL != visualToLogicalBuffer;
1023 const CharacterIndex startCharacter = line.characterRun.characterIndex;
1024 const CharacterIndex endCharacter = line.characterRun.characterIndex + line.characterRun.numberOfCharacters;
1025 DALI_ASSERT_DEBUG( endCharacter <= mLogicalModel->mText.Count() && "Invalid line info" );
1027 // Whether there is a hit on a glyph.
1028 bool matched = false;
1030 // Traverses glyphs in visual order. To do that use the visual to logical conversion table.
1031 CharacterIndex visualIndex = startCharacter;
1032 for( ; !matched && ( visualIndex < endCharacter ); ++visualIndex )
1034 // The character in logical order.
1035 const CharacterIndex characterLogicalOrderIndex = hasRightToLeftCharacters ? *( visualToLogicalBuffer + visualIndex ) : visualIndex;
1037 // The first glyph for that character in logical order.
1038 const GlyphIndex glyphLogicalOrderIndex = *( charactersToGlyphBuffer + characterLogicalOrderIndex );
1040 // The number of glyphs for that character
1041 const Length numberOfGlyphs = *( glyphsPerCharacterBuffer + characterLogicalOrderIndex );
1043 // Get the metrics for the group of glyphs.
1044 GlyphMetrics glyphMetrics;
1045 GetGlyphsMetrics( glyphLogicalOrderIndex,
1051 const Vector2& position = *( positionsBuffer + glyphLogicalOrderIndex );
1053 // Find the mid-point of the area containing the glyph
1054 const float glyphCenter = -glyphMetrics.xBearing + position.x + 0.5f * glyphMetrics.advance;
1056 if( visualX < glyphCenter )
1063 // Return the logical position of the cursor in characters.
1067 visualIndex = endCharacter;
1070 logicalIndex = hasRightToLeftCharacters ? *( visualToLogicalCursorBuffer + visualIndex ) : visualIndex;
1071 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "%p closest visualIndex %d logicalIndex %d\n", this, visualIndex, logicalIndex );
1072 return logicalIndex;
1075 void Controller::Impl::GetCursorPosition( CharacterIndex logical,
1076 CursorInfo& cursorInfo )
1078 // TODO: Check for multiline with \n, etc...
1080 // Check if the logical position is the first or the last one of the text.
1081 const bool isFirstPosition = 0u == logical;
1082 const bool isLastPosition = mLogicalModel->GetNumberOfCharacters() == logical;
1084 if( isFirstPosition && isLastPosition )
1086 // There is zero characters. Get the default font.
1088 FontId defaultFontId = 0u;
1089 if( NULL == mFontDefaults )
1091 defaultFontId = mFontClient.GetFontId( EMPTY_STRING,
1096 defaultFontId = mFontDefaults->GetFontId( mFontClient );
1099 Text::FontMetrics fontMetrics;
1100 mFontClient.GetFontMetrics( defaultFontId, fontMetrics );
1102 cursorInfo.lineHeight = fontMetrics.ascender - fontMetrics.descender;
1103 cursorInfo.primaryCursorHeight = cursorInfo.lineHeight;
1105 cursorInfo.primaryPosition.x = 0.f;
1106 cursorInfo.primaryPosition.y = 0.f;
1108 // Nothing else to do.
1112 // Get the previous logical index.
1113 const CharacterIndex previousLogical = isFirstPosition ? 0u : logical - 1u;
1115 // Decrease the logical index if it's the last one.
1116 if( isLastPosition )
1121 // Get the direction of the character and the previous one.
1122 const CharacterDirection* const modelCharacterDirectionsBuffer = ( 0u != mLogicalModel->mCharacterDirections.Count() ) ? mLogicalModel->mCharacterDirections.Begin() : NULL;
1124 CharacterDirection isCurrentRightToLeft = false;
1125 CharacterDirection isPreviousRightToLeft = false;
1126 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
1128 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + logical );
1129 isPreviousRightToLeft = *( modelCharacterDirectionsBuffer + previousLogical );
1132 // Get the line where the character is laid-out.
1133 const LineRun* modelLines = mVisualModel->mLines.Begin();
1135 const LineIndex lineIndex = mVisualModel->GetLineOfCharacter( logical );
1136 const LineRun& line = *( modelLines + lineIndex );
1138 // Get the paragraph's direction.
1139 const CharacterDirection isRightToLeftParagraph = line.direction;
1141 // Check whether there is an alternative position:
1143 cursorInfo.isSecondaryCursor = ( isCurrentRightToLeft != isPreviousRightToLeft ) ||
1144 ( isLastPosition && ( isRightToLeftParagraph != isCurrentRightToLeft ) );
1146 // Set the line height.
1147 cursorInfo.lineHeight = line.ascender + -line.descender;
1149 // Convert the cursor position into the glyph position.
1150 CharacterIndex characterIndex = logical;
1151 if( cursorInfo.isSecondaryCursor &&
1152 ( isRightToLeftParagraph != isCurrentRightToLeft ) )
1154 characterIndex = previousLogical;
1157 const GlyphIndex currentGlyphIndex = *( mVisualModel->mCharactersToGlyph.Begin() + characterIndex );
1158 const Length numberOfGlyphs = *( mVisualModel->mGlyphsPerCharacter.Begin() + characterIndex );
1159 const Length numberOfCharacters = *( mVisualModel->mCharactersPerGlyph.Begin() +currentGlyphIndex );
1161 // Get the metrics for the group of glyphs.
1162 GlyphMetrics glyphMetrics;
1163 GetGlyphsMetrics( currentGlyphIndex,
1169 float interGlyphAdvance = 0.f;
1170 if( !isLastPosition &&
1171 ( numberOfCharacters > 1u ) )
1173 const CharacterIndex firstIndex = *( mVisualModel->mGlyphsToCharacters.Begin() + currentGlyphIndex );
1174 interGlyphAdvance = static_cast<float>( characterIndex - firstIndex ) * glyphMetrics.advance / static_cast<float>( numberOfCharacters );
1177 // Get the glyph position and x bearing.
1178 const Vector2& currentPosition = *( mVisualModel->mGlyphPositions.Begin() + currentGlyphIndex );
1180 // Set the cursor's height.
1181 cursorInfo.primaryCursorHeight = glyphMetrics.fontHeight;
1183 // Set the position.
1184 cursorInfo.primaryPosition.x = -glyphMetrics.xBearing + currentPosition.x + ( isCurrentRightToLeft ? glyphMetrics.advance : interGlyphAdvance );
1185 cursorInfo.primaryPosition.y = line.ascender - glyphMetrics.ascender;
1187 if( isLastPosition )
1189 // The position of the cursor after the last character needs special
1190 // care depending on its direction and the direction of the paragraph.
1192 if( cursorInfo.isSecondaryCursor )
1194 // Need to find the first character after the last character with the paragraph's direction.
1195 // i.e l0 l1 l2 r0 r1 should find r0.
1197 // TODO: check for more than one line!
1198 characterIndex = isRightToLeftParagraph ? line.characterRun.characterIndex : line.characterRun.characterIndex + line.characterRun.numberOfCharacters - 1u;
1199 characterIndex = mLogicalModel->GetLogicalCharacterIndex( characterIndex );
1201 const GlyphIndex glyphIndex = *( mVisualModel->mCharactersToGlyph.Begin() + characterIndex );
1202 const Length numberOfGlyphs = *( mVisualModel->mGlyphsPerCharacter.Begin() + characterIndex );
1204 const Vector2& position = *( mVisualModel->mGlyphPositions.Begin() + glyphIndex );
1206 // Get the metrics for the group of glyphs.
1207 GlyphMetrics glyphMetrics;
1208 GetGlyphsMetrics( glyphIndex,
1214 cursorInfo.primaryPosition.x = -glyphMetrics.xBearing + position.x + ( isRightToLeftParagraph ? 0.f : glyphMetrics.advance );
1216 cursorInfo.primaryPosition.y = line.ascender - glyphMetrics.ascender;
1220 if( !isCurrentRightToLeft )
1222 cursorInfo.primaryPosition.x += glyphMetrics.advance;
1226 cursorInfo.primaryPosition.x -= glyphMetrics.advance;
1231 // Set the alternative cursor position.
1232 if( cursorInfo.isSecondaryCursor )
1234 // Convert the cursor position into the glyph position.
1235 const CharacterIndex previousCharacterIndex = ( ( isRightToLeftParagraph != isCurrentRightToLeft ) ? logical : previousLogical );
1236 const GlyphIndex previousGlyphIndex = *( mVisualModel->mCharactersToGlyph.Begin() + previousCharacterIndex );
1237 const Length numberOfGlyphs = *( mVisualModel->mGlyphsPerCharacter.Begin() + previousCharacterIndex );
1239 // Get the glyph position.
1240 const Vector2& previousPosition = *( mVisualModel->mGlyphPositions.Begin() + previousGlyphIndex );
1242 // Get the metrics for the group of glyphs.
1243 GlyphMetrics glyphMetrics;
1244 GetGlyphsMetrics( previousGlyphIndex,
1250 // Set the cursor position and height.
1251 cursorInfo.secondaryPosition.x = -glyphMetrics.xBearing + previousPosition.x + ( ( ( isLastPosition && !isCurrentRightToLeft ) ||
1252 ( !isLastPosition && isCurrentRightToLeft ) ) ? glyphMetrics.advance : 0.f );
1254 cursorInfo.secondaryCursorHeight = 0.5f * glyphMetrics.fontHeight;
1256 cursorInfo.secondaryPosition.y = cursorInfo.lineHeight - cursorInfo.secondaryCursorHeight - line.descender - ( glyphMetrics.fontHeight - glyphMetrics.ascender );
1258 // Update the primary cursor height as well.
1259 cursorInfo.primaryCursorHeight *= 0.5f;
1263 CharacterIndex Controller::Impl::CalculateNewCursorIndex( CharacterIndex index ) const
1265 if( NULL == mEventData )
1267 // Nothing to do if there is no text input.
1271 CharacterIndex cursorIndex = mEventData->mPrimaryCursorPosition;
1273 const Script script = mLogicalModel->GetScript( index );
1274 const GlyphIndex* charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
1275 const Length* charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
1277 Length numberOfCharacters = 0u;
1278 if( TextAbstraction::LATIN == script )
1280 // Prevents to jump the whole Latin ligatures like fi, ff, ...
1281 numberOfCharacters = 1u;
1285 GlyphIndex glyphIndex = *( charactersToGlyphBuffer + index );
1286 numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
1288 while( 0u == numberOfCharacters )
1290 numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
1295 if( index < mEventData->mPrimaryCursorPosition )
1297 cursorIndex -= numberOfCharacters;
1301 cursorIndex += numberOfCharacters;
1307 void Controller::Impl::UpdateCursorPosition()
1309 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "-->Controller::UpdateCursorPosition %p\n", this );
1310 if( NULL == mEventData )
1312 // Nothing to do if there is no text input.
1313 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition no event data\n" );
1317 if( IsShowingPlaceholderText() )
1319 // Do not want to use the place-holder text to set the cursor position.
1321 // Use the line's height of the font's family set to set the cursor's size.
1322 // If there is no font's family set, use the default font.
1323 // Use the current alignment to place the cursor at the beginning, center or end of the box.
1325 float lineHeight = 0.f;
1327 FontId defaultFontId = 0u;
1328 if( NULL == mFontDefaults )
1330 defaultFontId = mFontClient.GetFontId( EMPTY_STRING,
1335 defaultFontId = mFontDefaults->GetFontId( mFontClient );
1338 Text::FontMetrics fontMetrics;
1339 mFontClient.GetFontMetrics( defaultFontId, fontMetrics );
1341 lineHeight = fontMetrics.ascender - fontMetrics.descender;
1344 Vector2 cursorPosition;
1346 switch( mLayoutEngine.GetHorizontalAlignment() )
1348 case LayoutEngine::HORIZONTAL_ALIGN_BEGIN:
1350 cursorPosition.x = 1.f;
1353 case LayoutEngine::HORIZONTAL_ALIGN_CENTER:
1355 cursorPosition.x = floor( 0.5f * mControlSize.width );
1358 case LayoutEngine::HORIZONTAL_ALIGN_END:
1360 cursorPosition.x = mControlSize.width;
1365 switch( mLayoutEngine.GetVerticalAlignment() )
1367 case LayoutEngine::VERTICAL_ALIGN_TOP:
1369 cursorPosition.y = 0.f;
1372 case LayoutEngine::VERTICAL_ALIGN_CENTER:
1374 cursorPosition.y = floorf( 0.5f * ( mControlSize.height - lineHeight ) );
1377 case LayoutEngine::VERTICAL_ALIGN_BOTTOM:
1379 cursorPosition.y = mControlSize.height - lineHeight;
1384 mEventData->mDecorator->SetPosition( PRIMARY_CURSOR,
1392 CursorInfo cursorInfo;
1393 GetCursorPosition( mEventData->mPrimaryCursorPosition,
1396 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
1397 const Vector2 cursorPosition = cursorInfo.primaryPosition + offset;
1399 // Sets the cursor position.
1400 mEventData->mDecorator->SetPosition( PRIMARY_CURSOR,
1403 cursorInfo.primaryCursorHeight,
1404 cursorInfo.lineHeight );
1405 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Primary cursor position: %f,%f\n", cursorPosition.x, cursorPosition.y );
1407 // Sets the grab handle position.
1408 mEventData->mDecorator->SetPosition( GRAB_HANDLE,
1411 cursorInfo.lineHeight );
1413 if( cursorInfo.isSecondaryCursor )
1415 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_BOTH );
1416 mEventData->mDecorator->SetPosition( SECONDARY_CURSOR,
1417 cursorInfo.secondaryPosition.x + offset.x,
1418 cursorInfo.secondaryPosition.y + offset.y,
1419 cursorInfo.secondaryCursorHeight,
1420 cursorInfo.lineHeight );
1421 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Secondary cursor position: %f,%f\n", cursorInfo.secondaryPosition.x + offset.x, cursorInfo.secondaryPosition.y + offset.y );
1425 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1428 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition\n" );
1431 void Controller::Impl::UpdateSelectionHandle( HandleType handleType )
1433 if( ( LEFT_SELECTION_HANDLE != handleType ) &&
1434 ( RIGHT_SELECTION_HANDLE != handleType ) )
1439 const bool leftSelectionHandle = LEFT_SELECTION_HANDLE == handleType;
1440 const CharacterIndex index = leftSelectionHandle ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition;
1442 CursorInfo cursorInfo;
1443 GetCursorPosition( index,
1446 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
1447 const Vector2 cursorPosition = cursorInfo.primaryPosition + offset;
1449 // Sets the grab handle position.
1450 mEventData->mDecorator->SetPosition( handleType,
1453 cursorInfo.lineHeight );
1456 void Controller::Impl::ClampHorizontalScroll( const Vector2& actualSize )
1458 // Clamp between -space & 0 (and the text alignment).
1459 if( actualSize.width > mControlSize.width )
1461 const float space = ( actualSize.width - mControlSize.width ) + mAlignmentOffset.x;
1462 mEventData->mScrollPosition.x = ( mEventData->mScrollPosition.x < -space ) ? -space : mEventData->mScrollPosition.x;
1463 mEventData->mScrollPosition.x = ( mEventData->mScrollPosition.x > -mAlignmentOffset.x ) ? -mAlignmentOffset.x : mEventData->mScrollPosition.x;
1465 mEventData->mDecoratorUpdated = true;
1469 mEventData->mScrollPosition.x = 0.f;
1473 void Controller::Impl::ClampVerticalScroll( const Vector2& actualSize )
1475 // Clamp between -space & 0 (and the text alignment).
1476 if( actualSize.height > mControlSize.height )
1478 const float space = ( actualSize.height - mControlSize.height ) + mAlignmentOffset.y;
1479 mEventData->mScrollPosition.y = ( mEventData->mScrollPosition.y < -space ) ? -space : mEventData->mScrollPosition.y;
1480 mEventData->mScrollPosition.y = ( mEventData->mScrollPosition.y > -mAlignmentOffset.y ) ? -mAlignmentOffset.y : mEventData->mScrollPosition.y;
1482 mEventData->mDecoratorUpdated = true;
1486 mEventData->mScrollPosition.y = 0.f;
1490 void Controller::Impl::ScrollToMakePositionVisible( const Vector2& position )
1493 bool updateDecorator = false;
1494 if( position.x < 0.f )
1496 offset.x = -position.x;
1497 mEventData->mScrollPosition.x += offset.x;
1498 updateDecorator = true;
1500 else if( position.x > mControlSize.width )
1502 offset.x = mControlSize.width - position.x;
1503 mEventData->mScrollPosition.x += offset.x;
1504 updateDecorator = true;
1507 if( updateDecorator && mEventData->mDecorator )
1509 mEventData->mDecorator->UpdatePositions( offset );
1512 // TODO : calculate the vertical scroll.
1515 void Controller::Impl::ScrollTextToMatchCursor()
1517 // Get the current cursor position in decorator coords.
1518 const Vector2& currentCursorPosition = mEventData->mDecorator->GetPosition( PRIMARY_CURSOR );
1520 // Calculate the new cursor position.
1521 CursorInfo cursorInfo;
1522 GetCursorPosition( mEventData->mPrimaryCursorPosition,
1525 // Calculate the offset to match the cursor position before the character was deleted.
1526 mEventData->mScrollPosition.x = currentCursorPosition.x - cursorInfo.primaryPosition.x - mAlignmentOffset.x;
1528 ClampHorizontalScroll( mVisualModel->GetActualSize() );
1529 bool updateCursorPosition = true;
1531 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
1532 const Vector2 cursorPosition = cursorInfo.primaryPosition + offset;
1534 if( updateCursorPosition )
1536 // Sets the cursor position.
1537 mEventData->mDecorator->SetPosition( PRIMARY_CURSOR,
1540 cursorInfo.primaryCursorHeight,
1541 cursorInfo.lineHeight );
1543 // Sets the grab handle position.
1544 mEventData->mDecorator->SetPosition( GRAB_HANDLE,
1547 cursorInfo.lineHeight );
1549 if( cursorInfo.isSecondaryCursor )
1551 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_BOTH );
1552 mEventData->mDecorator->SetPosition( SECONDARY_CURSOR,
1553 cursorInfo.secondaryPosition.x + offset.x,
1554 cursorInfo.secondaryPosition.y + offset.y,
1555 cursorInfo.secondaryCursorHeight,
1556 cursorInfo.lineHeight );
1557 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Secondary cursor position: %f,%f\n", cursorInfo.secondaryPosition.x + offset.x, cursorInfo.secondaryPosition.y + offset.y );
1561 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1566 void Controller::Impl::RequestRelayout()
1568 mControlInterface.RequestTextRelayout();
1573 } // namespace Toolkit