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 if( selectionStart == selectionEnd )
718 // Nothing to select if handles are in the same place.
722 mEventData->mDecorator->ClearHighlights();
724 mEventData->mLeftSelectionPosition = selectionStart;
725 mEventData->mRightSelectionPosition = selectionEnd;
727 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
728 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
729 const GlyphInfo* const glyphsBuffer = mVisualModel->mGlyphs.Begin();
730 const Vector2* const positionsBuffer = mVisualModel->mGlyphPositions.Begin();
732 // TODO: Better algorithm to create the highlight box.
735 const Vector<LineRun>& lines = mVisualModel->mLines;
736 const LineRun& firstLine = *lines.Begin();
737 const float height = firstLine.ascender + -firstLine.descender;
739 const bool indicesSwapped = ( selectionStart > selectionEnd );
742 std::swap( selectionStart, selectionEnd );
745 GlyphIndex glyphStart = *( charactersToGlyphBuffer + selectionStart );
746 GlyphIndex glyphEnd = *( charactersToGlyphBuffer + ( selectionEnd - 1u ) ) + *( glyphsPerCharacterBuffer + ( selectionEnd - 1u ) ) - 1u;
748 mEventData->mDecorator->SwapSelectionHandlesEnabled( firstLine.direction != indicesSwapped );
750 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
752 for( GlyphIndex index = glyphStart; index <= glyphEnd; ++index )
754 const GlyphInfo& glyph = *( glyphsBuffer + index );
755 const Vector2& position = *( positionsBuffer + index );
757 const float xPosition = position.x - glyph.xBearing + offset.x;
758 mEventData->mDecorator->AddHighlight( xPosition, offset.y, xPosition + glyph.advance, height );
761 CursorInfo primaryCursorInfo;
762 GetCursorPosition( mEventData->mLeftSelectionPosition,
765 CursorInfo secondaryCursorInfo;
766 GetCursorPosition( mEventData->mRightSelectionPosition,
767 secondaryCursorInfo );
769 const Vector2 primaryPosition = primaryCursorInfo.primaryPosition + offset;
770 const Vector2 secondaryPosition = secondaryCursorInfo.primaryPosition + offset;
772 mEventData->mDecorator->SetPosition( LEFT_SELECTION_HANDLE, primaryPosition.x, primaryPosition.y, primaryCursorInfo.lineHeight );
774 mEventData->mDecorator->SetPosition( RIGHT_SELECTION_HANDLE, secondaryPosition.x, secondaryPosition.y, secondaryCursorInfo.lineHeight );
776 // Set the flag to update the decorator.
777 mEventData->mDecoratorUpdated = true;
780 void Controller::Impl::RepositionSelectionHandles( float visualX, float visualY )
782 if( NULL == mEventData )
784 // Nothing to do if there is no text input.
788 if( IsShowingPlaceholderText() )
790 // Nothing to do if there is the place-holder text.
794 const Length numberOfGlyphs = mVisualModel->mGlyphs.Count();
795 const Length numberOfLines = mVisualModel->mLines.Count();
796 if( 0 == numberOfGlyphs ||
799 // Nothing to do if there is no text.
803 // Find which word was selected
804 CharacterIndex selectionStart( 0 );
805 CharacterIndex selectionEnd( 0 );
806 FindSelectionIndices( visualX, visualY, selectionStart, selectionEnd );
807 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "%p selectionStart %d selectionEnd %d\n", this, selectionStart, selectionEnd );
809 if( selectionStart == selectionEnd )
811 ChangeState( EventData::EDITING );
812 // Nothing to select. i.e. a white space, out of bounds
816 RepositionSelectionHandles( selectionStart, selectionEnd );
819 void Controller::Impl::ChangeState( EventData::State newState )
821 if( NULL == mEventData )
823 // Nothing to do if there is no text input.
827 if( mEventData->mState != newState )
829 mEventData->mState = newState;
831 if( EventData::INACTIVE == mEventData->mState )
833 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
834 mEventData->mDecorator->StopCursorBlink();
835 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
836 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
837 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
838 mEventData->mDecorator->SetPopupActive( false );
839 mEventData->mDecoratorUpdated = true;
841 else if ( EventData::SELECTING == mEventData->mState )
843 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
844 mEventData->mDecorator->StopCursorBlink();
845 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
846 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true );
847 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true );
848 if( mEventData->mGrabHandlePopupEnabled )
850 TextSelectionPopup::Buttons selectedButtons = TextSelectionPopup::Buttons( TextSelectionPopup::CUT | TextSelectionPopup::COPY );
851 mEventData->mDecorator->SetEnabledPopupButtons( selectedButtons );
852 mEventData->mDecorator->SetPopupActive( true );
854 mEventData->mDecoratorUpdated = true;
856 else if ( EventData::SELECTION_CHANGED == mEventData->mState )
858 if( mEventData->mGrabHandlePopupEnabled )
860 TextSelectionPopup::Buttons selectedButtons = TextSelectionPopup::Buttons( TextSelectionPopup::CUT | TextSelectionPopup::COPY );
861 mEventData->mDecorator->SetEnabledPopupButtons( selectedButtons );
862 mEventData->mDecorator->SetPopupActive( true );
864 mEventData->mDecoratorUpdated = true;
866 else if( EventData::EDITING == mEventData->mState )
868 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
869 if( mEventData->mCursorBlinkEnabled )
871 mEventData->mDecorator->StartCursorBlink();
873 // Grab handle is not shown until a tap is received whilst EDITING
874 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
875 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
876 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
877 if( mEventData->mGrabHandlePopupEnabled )
879 mEventData->mDecorator->SetPopupActive( false );
881 mEventData->mDecoratorUpdated = true;
883 else if( EventData::EDITING_WITH_POPUP == mEventData->mState )
885 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
886 if( mEventData->mCursorBlinkEnabled )
888 mEventData->mDecorator->StartCursorBlink();
890 if( mEventData->mSelectionEnabled )
892 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
893 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
897 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
899 if( mEventData->mGrabHandlePopupEnabled )
901 TextSelectionPopup::Buttons selectionButtons = TextSelectionPopup::Buttons( TextSelectionPopup::SELECT | TextSelectionPopup::SELECT_ALL );
902 mEventData->mDecorator->SetEnabledPopupButtons( selectionButtons );
903 mEventData->mDecorator->SetPopupActive( true );
905 mEventData->mDecoratorUpdated = true;
907 else if ( EventData::SELECTION_HANDLE_PANNING == mEventData->mState )
909 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
910 mEventData->mDecorator->StopCursorBlink();
911 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
912 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true );
913 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true );
914 if( mEventData->mGrabHandlePopupEnabled )
916 mEventData->mDecorator->SetPopupActive( false );
918 mEventData->mDecoratorUpdated = true;
920 else if ( EventData::GRAB_HANDLE_PANNING == mEventData->mState )
922 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
923 if( mEventData->mCursorBlinkEnabled )
925 mEventData->mDecorator->StartCursorBlink();
927 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
928 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
929 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
930 if( mEventData->mGrabHandlePopupEnabled )
932 mEventData->mDecorator->SetPopupActive( false );
934 mEventData->mDecoratorUpdated = true;
939 LineIndex Controller::Impl::GetClosestLine( float y ) const
941 float totalHeight = 0.f;
942 LineIndex lineIndex = 0u;
944 const Vector<LineRun>& lines = mVisualModel->mLines;
945 for( LineIndex endLine = lines.Count();
949 const LineRun& lineRun = lines[lineIndex];
950 totalHeight += lineRun.ascender + -lineRun.descender;
951 if( y < totalHeight )
965 void Controller::Impl::FindSelectionIndices( float visualX, float visualY, CharacterIndex& startIndex, CharacterIndex& endIndex )
967 CharacterIndex hitCharacter = GetClosestCursorIndex( visualX, visualY );
968 if( hitCharacter >= mLogicalModel->mText.Count() )
970 // Selection out of bounds.
974 startIndex = hitCharacter;
975 endIndex = hitCharacter;
977 if( !TextAbstraction::IsWhiteSpace( mLogicalModel->mText[hitCharacter] ) )
979 // Find the start and end of the text
980 for( startIndex = hitCharacter; startIndex > 0; --startIndex )
982 Character charCode = mLogicalModel->mText[ startIndex-1 ];
983 if( TextAbstraction::IsWhiteSpace( charCode ) )
988 const CharacterIndex pastTheEnd = mLogicalModel->mText.Count();
989 for( endIndex = hitCharacter + 1u; endIndex < pastTheEnd; ++endIndex )
991 Character charCode = mLogicalModel->mText[ endIndex ];
992 if( TextAbstraction::IsWhiteSpace( charCode ) )
1000 CharacterIndex Controller::Impl::GetClosestCursorIndex( float visualX,
1003 if( NULL == mEventData )
1005 // Nothing to do if there is no text input.
1009 CharacterIndex logicalIndex = 0u;
1011 const Length numberOfGlyphs = mVisualModel->mGlyphs.Count();
1012 const Length numberOfLines = mVisualModel->mLines.Count();
1013 if( 0 == numberOfGlyphs ||
1014 0 == numberOfLines )
1016 return logicalIndex;
1019 // Find which line is closest
1020 const LineIndex lineIndex = GetClosestLine( visualY );
1021 const LineRun& line = mVisualModel->mLines[lineIndex];
1023 // Get the positions of the glyphs.
1024 const Vector<Vector2>& positions = mVisualModel->mGlyphPositions;
1025 const Vector2* const positionsBuffer = positions.Begin();
1027 // Get the visual to logical conversion tables.
1028 const CharacterIndex* const visualToLogicalBuffer = ( 0u != mLogicalModel->mVisualToLogicalMap.Count() ) ? mLogicalModel->mVisualToLogicalMap.Begin() : NULL;
1029 const CharacterIndex* const visualToLogicalCursorBuffer = mLogicalModel->mVisualToLogicalCursorMap.Begin();
1031 // Get the character to glyph conversion table.
1032 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
1034 // Get the glyphs per character table.
1035 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
1037 // If the vector is void, there is no right to left characters.
1038 const bool hasRightToLeftCharacters = NULL != visualToLogicalBuffer;
1040 const CharacterIndex startCharacter = line.characterRun.characterIndex;
1041 const CharacterIndex endCharacter = line.characterRun.characterIndex + line.characterRun.numberOfCharacters;
1042 DALI_ASSERT_DEBUG( endCharacter <= mLogicalModel->mText.Count() && "Invalid line info" );
1044 // Whether there is a hit on a glyph.
1045 bool matched = false;
1047 // Traverses glyphs in visual order. To do that use the visual to logical conversion table.
1048 CharacterIndex visualIndex = startCharacter;
1049 for( ; !matched && ( visualIndex < endCharacter ); ++visualIndex )
1051 // The character in logical order.
1052 const CharacterIndex characterLogicalOrderIndex = hasRightToLeftCharacters ? *( visualToLogicalBuffer + visualIndex ) : visualIndex;
1054 // The first glyph for that character in logical order.
1055 const GlyphIndex glyphLogicalOrderIndex = *( charactersToGlyphBuffer + characterLogicalOrderIndex );
1057 // The number of glyphs for that character
1058 const Length numberOfGlyphs = *( glyphsPerCharacterBuffer + characterLogicalOrderIndex );
1060 // Get the metrics for the group of glyphs.
1061 GlyphMetrics glyphMetrics;
1062 GetGlyphsMetrics( glyphLogicalOrderIndex,
1068 const Vector2& position = *( positionsBuffer + glyphLogicalOrderIndex );
1070 // Find the mid-point of the area containing the glyph
1071 const float glyphCenter = -glyphMetrics.xBearing + position.x + 0.5f * glyphMetrics.advance;
1073 if( visualX < glyphCenter )
1080 // Return the logical position of the cursor in characters.
1084 visualIndex = endCharacter;
1087 logicalIndex = hasRightToLeftCharacters ? *( visualToLogicalCursorBuffer + visualIndex ) : visualIndex;
1088 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "%p closest visualIndex %d logicalIndex %d\n", this, visualIndex, logicalIndex );
1089 return logicalIndex;
1092 void Controller::Impl::GetCursorPosition( CharacterIndex logical,
1093 CursorInfo& cursorInfo )
1095 // TODO: Check for multiline with \n, etc...
1097 // Check if the logical position is the first or the last one of the text.
1098 const bool isFirstPosition = 0u == logical;
1099 const bool isLastPosition = mLogicalModel->GetNumberOfCharacters() == logical;
1101 if( isFirstPosition && isLastPosition )
1103 // There is zero characters. Get the default font.
1105 FontId defaultFontId = 0u;
1106 if( NULL == mFontDefaults )
1108 defaultFontId = mFontClient.GetFontId( EMPTY_STRING,
1113 defaultFontId = mFontDefaults->GetFontId( mFontClient );
1116 Text::FontMetrics fontMetrics;
1117 mFontClient.GetFontMetrics( defaultFontId, fontMetrics );
1119 cursorInfo.lineHeight = fontMetrics.ascender - fontMetrics.descender;
1120 cursorInfo.primaryCursorHeight = cursorInfo.lineHeight;
1122 cursorInfo.primaryPosition.x = 0.f;
1123 cursorInfo.primaryPosition.y = 0.f;
1125 // Nothing else to do.
1129 // Get the previous logical index.
1130 const CharacterIndex previousLogical = isFirstPosition ? 0u : logical - 1u;
1132 // Decrease the logical index if it's the last one.
1133 if( isLastPosition )
1138 // Get the direction of the character and the previous one.
1139 const CharacterDirection* const modelCharacterDirectionsBuffer = ( 0u != mLogicalModel->mCharacterDirections.Count() ) ? mLogicalModel->mCharacterDirections.Begin() : NULL;
1141 CharacterDirection isCurrentRightToLeft = false;
1142 CharacterDirection isPreviousRightToLeft = false;
1143 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
1145 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + logical );
1146 isPreviousRightToLeft = *( modelCharacterDirectionsBuffer + previousLogical );
1149 // Get the line where the character is laid-out.
1150 const LineRun* modelLines = mVisualModel->mLines.Begin();
1152 const LineIndex lineIndex = mVisualModel->GetLineOfCharacter( logical );
1153 const LineRun& line = *( modelLines + lineIndex );
1155 // Get the paragraph's direction.
1156 const CharacterDirection isRightToLeftParagraph = line.direction;
1158 // Check whether there is an alternative position:
1160 cursorInfo.isSecondaryCursor = ( isCurrentRightToLeft != isPreviousRightToLeft ) ||
1161 ( isLastPosition && ( isRightToLeftParagraph != isCurrentRightToLeft ) );
1163 // Set the line height.
1164 cursorInfo.lineHeight = line.ascender + -line.descender;
1166 // Convert the cursor position into the glyph position.
1167 CharacterIndex characterIndex = logical;
1168 if( cursorInfo.isSecondaryCursor &&
1169 ( isRightToLeftParagraph != isCurrentRightToLeft ) )
1171 characterIndex = previousLogical;
1174 const GlyphIndex currentGlyphIndex = *( mVisualModel->mCharactersToGlyph.Begin() + characterIndex );
1175 const Length numberOfGlyphs = *( mVisualModel->mGlyphsPerCharacter.Begin() + characterIndex );
1176 const Length numberOfCharacters = *( mVisualModel->mCharactersPerGlyph.Begin() +currentGlyphIndex );
1178 // Get the metrics for the group of glyphs.
1179 GlyphMetrics glyphMetrics;
1180 GetGlyphsMetrics( currentGlyphIndex,
1186 float interGlyphAdvance = 0.f;
1187 if( !isLastPosition &&
1188 ( numberOfCharacters > 1u ) )
1190 const CharacterIndex firstIndex = *( mVisualModel->mGlyphsToCharacters.Begin() + currentGlyphIndex );
1191 interGlyphAdvance = static_cast<float>( characterIndex - firstIndex ) * glyphMetrics.advance / static_cast<float>( numberOfCharacters );
1194 // Get the glyph position and x bearing.
1195 const Vector2& currentPosition = *( mVisualModel->mGlyphPositions.Begin() + currentGlyphIndex );
1197 // Set the cursor's height.
1198 cursorInfo.primaryCursorHeight = glyphMetrics.fontHeight;
1200 // Set the position.
1201 cursorInfo.primaryPosition.x = -glyphMetrics.xBearing + currentPosition.x + ( isCurrentRightToLeft ? glyphMetrics.advance : interGlyphAdvance );
1202 cursorInfo.primaryPosition.y = line.ascender - glyphMetrics.ascender;
1204 if( isLastPosition )
1206 // The position of the cursor after the last character needs special
1207 // care depending on its direction and the direction of the paragraph.
1209 if( cursorInfo.isSecondaryCursor )
1211 // Need to find the first character after the last character with the paragraph's direction.
1212 // i.e l0 l1 l2 r0 r1 should find r0.
1214 // TODO: check for more than one line!
1215 characterIndex = isRightToLeftParagraph ? line.characterRun.characterIndex : line.characterRun.characterIndex + line.characterRun.numberOfCharacters - 1u;
1216 characterIndex = mLogicalModel->GetLogicalCharacterIndex( characterIndex );
1218 const GlyphIndex glyphIndex = *( mVisualModel->mCharactersToGlyph.Begin() + characterIndex );
1219 const Length numberOfGlyphs = *( mVisualModel->mGlyphsPerCharacter.Begin() + characterIndex );
1221 const Vector2& position = *( mVisualModel->mGlyphPositions.Begin() + glyphIndex );
1223 // Get the metrics for the group of glyphs.
1224 GlyphMetrics glyphMetrics;
1225 GetGlyphsMetrics( glyphIndex,
1231 cursorInfo.primaryPosition.x = -glyphMetrics.xBearing + position.x + ( isRightToLeftParagraph ? 0.f : glyphMetrics.advance );
1233 cursorInfo.primaryPosition.y = line.ascender - glyphMetrics.ascender;
1237 if( !isCurrentRightToLeft )
1239 cursorInfo.primaryPosition.x += glyphMetrics.advance;
1243 cursorInfo.primaryPosition.x -= glyphMetrics.advance;
1248 // Set the alternative cursor position.
1249 if( cursorInfo.isSecondaryCursor )
1251 // Convert the cursor position into the glyph position.
1252 const CharacterIndex previousCharacterIndex = ( ( isRightToLeftParagraph != isCurrentRightToLeft ) ? logical : previousLogical );
1253 const GlyphIndex previousGlyphIndex = *( mVisualModel->mCharactersToGlyph.Begin() + previousCharacterIndex );
1254 const Length numberOfGlyphs = *( mVisualModel->mGlyphsPerCharacter.Begin() + previousCharacterIndex );
1256 // Get the glyph position.
1257 const Vector2& previousPosition = *( mVisualModel->mGlyphPositions.Begin() + previousGlyphIndex );
1259 // Get the metrics for the group of glyphs.
1260 GlyphMetrics glyphMetrics;
1261 GetGlyphsMetrics( previousGlyphIndex,
1267 // Set the cursor position and height.
1268 cursorInfo.secondaryPosition.x = -glyphMetrics.xBearing + previousPosition.x + ( ( ( isLastPosition && !isCurrentRightToLeft ) ||
1269 ( !isLastPosition && isCurrentRightToLeft ) ) ? glyphMetrics.advance : 0.f );
1271 cursorInfo.secondaryCursorHeight = 0.5f * glyphMetrics.fontHeight;
1273 cursorInfo.secondaryPosition.y = cursorInfo.lineHeight - cursorInfo.secondaryCursorHeight - line.descender - ( glyphMetrics.fontHeight - glyphMetrics.ascender );
1275 // Update the primary cursor height as well.
1276 cursorInfo.primaryCursorHeight *= 0.5f;
1280 CharacterIndex Controller::Impl::CalculateNewCursorIndex( CharacterIndex index ) const
1282 if( NULL == mEventData )
1284 // Nothing to do if there is no text input.
1288 CharacterIndex cursorIndex = mEventData->mPrimaryCursorPosition;
1290 const Script script = mLogicalModel->GetScript( index );
1291 const GlyphIndex* charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
1292 const Length* charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
1294 Length numberOfCharacters = 0u;
1295 if( TextAbstraction::LATIN == script )
1297 // Prevents to jump the whole Latin ligatures like fi, ff, ...
1298 numberOfCharacters = 1u;
1302 GlyphIndex glyphIndex = *( charactersToGlyphBuffer + index );
1303 numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
1305 while( 0u == numberOfCharacters )
1307 numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
1312 if( index < mEventData->mPrimaryCursorPosition )
1314 cursorIndex -= numberOfCharacters;
1318 cursorIndex += numberOfCharacters;
1324 void Controller::Impl::UpdateCursorPosition()
1326 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "-->Controller::UpdateCursorPosition %p\n", this );
1327 if( NULL == mEventData )
1329 // Nothing to do if there is no text input.
1330 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition no event data\n" );
1334 if( IsShowingPlaceholderText() )
1336 // Do not want to use the place-holder text to set the cursor position.
1338 // Use the line's height of the font's family set to set the cursor's size.
1339 // If there is no font's family set, use the default font.
1340 // Use the current alignment to place the cursor at the beginning, center or end of the box.
1342 float lineHeight = 0.f;
1344 FontId defaultFontId = 0u;
1345 if( NULL == mFontDefaults )
1347 defaultFontId = mFontClient.GetFontId( EMPTY_STRING,
1352 defaultFontId = mFontDefaults->GetFontId( mFontClient );
1355 Text::FontMetrics fontMetrics;
1356 mFontClient.GetFontMetrics( defaultFontId, fontMetrics );
1358 lineHeight = fontMetrics.ascender - fontMetrics.descender;
1361 Vector2 cursorPosition;
1363 switch( mLayoutEngine.GetHorizontalAlignment() )
1365 case LayoutEngine::HORIZONTAL_ALIGN_BEGIN:
1367 cursorPosition.x = 1.f;
1370 case LayoutEngine::HORIZONTAL_ALIGN_CENTER:
1372 cursorPosition.x = floor( 0.5f * mControlSize.width );
1375 case LayoutEngine::HORIZONTAL_ALIGN_END:
1377 cursorPosition.x = mControlSize.width;
1382 switch( mLayoutEngine.GetVerticalAlignment() )
1384 case LayoutEngine::VERTICAL_ALIGN_TOP:
1386 cursorPosition.y = 0.f;
1389 case LayoutEngine::VERTICAL_ALIGN_CENTER:
1391 cursorPosition.y = floorf( 0.5f * ( mControlSize.height - lineHeight ) );
1394 case LayoutEngine::VERTICAL_ALIGN_BOTTOM:
1396 cursorPosition.y = mControlSize.height - lineHeight;
1401 mEventData->mDecorator->SetPosition( PRIMARY_CURSOR,
1409 CursorInfo cursorInfo;
1410 GetCursorPosition( mEventData->mPrimaryCursorPosition,
1413 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
1414 const Vector2 cursorPosition = cursorInfo.primaryPosition + offset;
1416 // Sets the cursor position.
1417 mEventData->mDecorator->SetPosition( PRIMARY_CURSOR,
1420 cursorInfo.primaryCursorHeight,
1421 cursorInfo.lineHeight );
1422 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Primary cursor position: %f,%f\n", cursorPosition.x, cursorPosition.y );
1424 // Sets the grab handle position.
1425 mEventData->mDecorator->SetPosition( GRAB_HANDLE,
1428 cursorInfo.lineHeight );
1430 if( cursorInfo.isSecondaryCursor )
1432 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_BOTH );
1433 mEventData->mDecorator->SetPosition( SECONDARY_CURSOR,
1434 cursorInfo.secondaryPosition.x + offset.x,
1435 cursorInfo.secondaryPosition.y + offset.y,
1436 cursorInfo.secondaryCursorHeight,
1437 cursorInfo.lineHeight );
1438 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Secondary cursor position: %f,%f\n", cursorInfo.secondaryPosition.x + offset.x, cursorInfo.secondaryPosition.y + offset.y );
1442 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1445 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition\n" );
1448 void Controller::Impl::UpdateSelectionHandle( HandleType handleType )
1450 if( ( LEFT_SELECTION_HANDLE != handleType ) &&
1451 ( RIGHT_SELECTION_HANDLE != handleType ) )
1456 const bool leftSelectionHandle = LEFT_SELECTION_HANDLE == handleType;
1457 const CharacterIndex index = leftSelectionHandle ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition;
1459 CursorInfo cursorInfo;
1460 GetCursorPosition( index,
1463 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
1464 const Vector2 cursorPosition = cursorInfo.primaryPosition + offset;
1466 // Sets the grab handle position.
1467 mEventData->mDecorator->SetPosition( handleType,
1470 cursorInfo.lineHeight );
1473 void Controller::Impl::ClampHorizontalScroll( const Vector2& actualSize )
1475 // Clamp between -space & 0 (and the text alignment).
1476 if( actualSize.width > mControlSize.width )
1478 const float space = ( actualSize.width - mControlSize.width ) + mAlignmentOffset.x;
1479 mEventData->mScrollPosition.x = ( mEventData->mScrollPosition.x < -space ) ? -space : mEventData->mScrollPosition.x;
1480 mEventData->mScrollPosition.x = ( mEventData->mScrollPosition.x > -mAlignmentOffset.x ) ? -mAlignmentOffset.x : mEventData->mScrollPosition.x;
1482 mEventData->mDecoratorUpdated = true;
1486 mEventData->mScrollPosition.x = 0.f;
1490 void Controller::Impl::ClampVerticalScroll( const Vector2& actualSize )
1492 // Clamp between -space & 0 (and the text alignment).
1493 if( actualSize.height > mControlSize.height )
1495 const float space = ( actualSize.height - mControlSize.height ) + mAlignmentOffset.y;
1496 mEventData->mScrollPosition.y = ( mEventData->mScrollPosition.y < -space ) ? -space : mEventData->mScrollPosition.y;
1497 mEventData->mScrollPosition.y = ( mEventData->mScrollPosition.y > -mAlignmentOffset.y ) ? -mAlignmentOffset.y : mEventData->mScrollPosition.y;
1499 mEventData->mDecoratorUpdated = true;
1503 mEventData->mScrollPosition.y = 0.f;
1507 void Controller::Impl::ScrollToMakePositionVisible( const Vector2& position )
1510 bool updateDecorator = false;
1511 if( position.x < 0.f )
1513 offset.x = -position.x;
1514 mEventData->mScrollPosition.x += offset.x;
1515 updateDecorator = true;
1517 else if( position.x > mControlSize.width )
1519 offset.x = mControlSize.width - position.x;
1520 mEventData->mScrollPosition.x += offset.x;
1521 updateDecorator = true;
1524 if( updateDecorator && mEventData->mDecorator )
1526 mEventData->mDecorator->UpdatePositions( offset );
1529 // TODO : calculate the vertical scroll.
1532 void Controller::Impl::ScrollTextToMatchCursor()
1534 // Get the current cursor position in decorator coords.
1535 const Vector2& currentCursorPosition = mEventData->mDecorator->GetPosition( PRIMARY_CURSOR );
1537 // Calculate the new cursor position.
1538 CursorInfo cursorInfo;
1539 GetCursorPosition( mEventData->mPrimaryCursorPosition,
1542 // Calculate the offset to match the cursor position before the character was deleted.
1543 mEventData->mScrollPosition.x = currentCursorPosition.x - cursorInfo.primaryPosition.x - mAlignmentOffset.x;
1545 ClampHorizontalScroll( mVisualModel->GetActualSize() );
1546 bool updateCursorPosition = true;
1548 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
1549 const Vector2 cursorPosition = cursorInfo.primaryPosition + offset;
1551 if( updateCursorPosition )
1553 // Sets the cursor position.
1554 mEventData->mDecorator->SetPosition( PRIMARY_CURSOR,
1557 cursorInfo.primaryCursorHeight,
1558 cursorInfo.lineHeight );
1560 // Sets the grab handle position.
1561 mEventData->mDecorator->SetPosition( GRAB_HANDLE,
1564 cursorInfo.lineHeight );
1566 if( cursorInfo.isSecondaryCursor )
1568 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_BOTH );
1569 mEventData->mDecorator->SetPosition( SECONDARY_CURSOR,
1570 cursorInfo.secondaryPosition.x + offset.x,
1571 cursorInfo.secondaryPosition.y + offset.y,
1572 cursorInfo.secondaryCursorHeight,
1573 cursorInfo.lineHeight );
1574 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Secondary cursor position: %f,%f\n", cursorInfo.secondaryPosition.x + offset.x, cursorInfo.secondaryPosition.y + offset.y );
1578 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1583 void Controller::Impl::RequestRelayout()
1585 mControlInterface.RequestTextRelayout();
1590 } // namespace Toolkit