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::COPY );
851 mEventData->mDecorator->SetEnabledPopupButtons( selectedButtons );
852 mEventData->mDecorator->SetPopupActive( true );
854 mEventData->mDecoratorUpdated = true;
856 else if( EventData::EDITING == mEventData->mState )
858 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
859 if( mEventData->mCursorBlinkEnabled )
861 mEventData->mDecorator->StartCursorBlink();
863 // Grab handle is not shown until a tap is received whilst EDITING
864 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
865 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
866 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
867 if( mEventData->mGrabHandlePopupEnabled )
869 mEventData->mDecorator->SetPopupActive( false );
871 mEventData->mDecoratorUpdated = true;
873 else if( EventData::EDITING_WITH_POPUP == mEventData->mState )
875 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
876 if( mEventData->mCursorBlinkEnabled )
878 mEventData->mDecorator->StartCursorBlink();
880 if( mEventData->mSelectionEnabled )
882 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
883 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
887 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
889 if( mEventData->mGrabHandlePopupEnabled )
891 TextSelectionPopup::Buttons selectionButtons = TextSelectionPopup::Buttons( TextSelectionPopup::SELECT | TextSelectionPopup::SELECT_ALL );
892 mEventData->mDecorator->SetEnabledPopupButtons( selectionButtons );
893 mEventData->mDecorator->SetPopupActive( true );
895 mEventData->mDecoratorUpdated = true;
897 else if ( EventData::SELECTION_HANDLE_PANNING == mEventData->mState )
899 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
900 mEventData->mDecorator->StopCursorBlink();
901 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
902 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true );
903 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true );
904 if( mEventData->mGrabHandlePopupEnabled )
906 mEventData->mDecorator->SetPopupActive( false );
908 mEventData->mDecoratorUpdated = true;
910 else if ( EventData::GRAB_HANDLE_PANNING == mEventData->mState )
912 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
913 if( mEventData->mCursorBlinkEnabled )
915 mEventData->mDecorator->StartCursorBlink();
917 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
918 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
919 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
920 if( mEventData->mGrabHandlePopupEnabled )
922 mEventData->mDecorator->SetPopupActive( false );
924 mEventData->mDecoratorUpdated = true;
929 LineIndex Controller::Impl::GetClosestLine( float y ) const
931 float totalHeight = 0.f;
932 LineIndex lineIndex = 0u;
934 const Vector<LineRun>& lines = mVisualModel->mLines;
935 for( LineIndex endLine = lines.Count();
939 const LineRun& lineRun = lines[lineIndex];
940 totalHeight += lineRun.ascender + -lineRun.descender;
941 if( y < totalHeight )
955 void Controller::Impl::FindSelectionIndices( float visualX, float visualY, CharacterIndex& startIndex, CharacterIndex& endIndex )
957 CharacterIndex hitCharacter = GetClosestCursorIndex( visualX, visualY );
958 if( hitCharacter >= mLogicalModel->mText.Count() )
960 // Selection out of bounds.
964 startIndex = hitCharacter;
965 endIndex = hitCharacter;
967 if( !TextAbstraction::IsWhiteSpace( mLogicalModel->mText[hitCharacter] ) )
969 // Find the start and end of the text
970 for( startIndex = hitCharacter; startIndex > 0; --startIndex )
972 Character charCode = mLogicalModel->mText[ startIndex-1 ];
973 if( TextAbstraction::IsWhiteSpace( charCode ) )
978 const CharacterIndex pastTheEnd = mLogicalModel->mText.Count();
979 for( endIndex = hitCharacter + 1u; endIndex < pastTheEnd; ++endIndex )
981 Character charCode = mLogicalModel->mText[ endIndex ];
982 if( TextAbstraction::IsWhiteSpace( charCode ) )
990 CharacterIndex Controller::Impl::GetClosestCursorIndex( float visualX,
993 if( NULL == mEventData )
995 // Nothing to do if there is no text input.
999 CharacterIndex logicalIndex = 0u;
1001 const Length numberOfGlyphs = mVisualModel->mGlyphs.Count();
1002 const Length numberOfLines = mVisualModel->mLines.Count();
1003 if( 0 == numberOfGlyphs ||
1004 0 == numberOfLines )
1006 return logicalIndex;
1009 // Find which line is closest
1010 const LineIndex lineIndex = GetClosestLine( visualY );
1011 const LineRun& line = mVisualModel->mLines[lineIndex];
1013 // Get the positions of the glyphs.
1014 const Vector<Vector2>& positions = mVisualModel->mGlyphPositions;
1015 const Vector2* const positionsBuffer = positions.Begin();
1017 // Get the visual to logical conversion tables.
1018 const CharacterIndex* const visualToLogicalBuffer = ( 0u != mLogicalModel->mVisualToLogicalMap.Count() ) ? mLogicalModel->mVisualToLogicalMap.Begin() : NULL;
1019 const CharacterIndex* const visualToLogicalCursorBuffer = mLogicalModel->mVisualToLogicalCursorMap.Begin();
1021 // Get the character to glyph conversion table.
1022 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
1024 // Get the glyphs per character table.
1025 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
1027 // If the vector is void, there is no right to left characters.
1028 const bool hasRightToLeftCharacters = NULL != visualToLogicalBuffer;
1030 const CharacterIndex startCharacter = line.characterRun.characterIndex;
1031 const CharacterIndex endCharacter = line.characterRun.characterIndex + line.characterRun.numberOfCharacters;
1032 DALI_ASSERT_DEBUG( endCharacter <= mLogicalModel->mText.Count() && "Invalid line info" );
1034 // Whether there is a hit on a glyph.
1035 bool matched = false;
1037 // Traverses glyphs in visual order. To do that use the visual to logical conversion table.
1038 CharacterIndex visualIndex = startCharacter;
1039 for( ; !matched && ( visualIndex < endCharacter ); ++visualIndex )
1041 // The character in logical order.
1042 const CharacterIndex characterLogicalOrderIndex = hasRightToLeftCharacters ? *( visualToLogicalBuffer + visualIndex ) : visualIndex;
1044 // The first glyph for that character in logical order.
1045 const GlyphIndex glyphLogicalOrderIndex = *( charactersToGlyphBuffer + characterLogicalOrderIndex );
1047 // The number of glyphs for that character
1048 const Length numberOfGlyphs = *( glyphsPerCharacterBuffer + characterLogicalOrderIndex );
1050 // Get the metrics for the group of glyphs.
1051 GlyphMetrics glyphMetrics;
1052 GetGlyphsMetrics( glyphLogicalOrderIndex,
1058 const Vector2& position = *( positionsBuffer + glyphLogicalOrderIndex );
1060 // Find the mid-point of the area containing the glyph
1061 const float glyphCenter = -glyphMetrics.xBearing + position.x + 0.5f * glyphMetrics.advance;
1063 if( visualX < glyphCenter )
1070 // Return the logical position of the cursor in characters.
1074 visualIndex = endCharacter;
1077 logicalIndex = hasRightToLeftCharacters ? *( visualToLogicalCursorBuffer + visualIndex ) : visualIndex;
1078 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "%p closest visualIndex %d logicalIndex %d\n", this, visualIndex, logicalIndex );
1079 return logicalIndex;
1082 void Controller::Impl::GetCursorPosition( CharacterIndex logical,
1083 CursorInfo& cursorInfo )
1085 // TODO: Check for multiline with \n, etc...
1087 // Check if the logical position is the first or the last one of the text.
1088 const bool isFirstPosition = 0u == logical;
1089 const bool isLastPosition = mLogicalModel->GetNumberOfCharacters() == logical;
1091 if( isFirstPosition && isLastPosition )
1093 // There is zero characters. Get the default font.
1095 FontId defaultFontId = 0u;
1096 if( NULL == mFontDefaults )
1098 defaultFontId = mFontClient.GetFontId( EMPTY_STRING,
1103 defaultFontId = mFontDefaults->GetFontId( mFontClient );
1106 Text::FontMetrics fontMetrics;
1107 mFontClient.GetFontMetrics( defaultFontId, fontMetrics );
1109 cursorInfo.lineHeight = fontMetrics.ascender - fontMetrics.descender;
1110 cursorInfo.primaryCursorHeight = cursorInfo.lineHeight;
1112 cursorInfo.primaryPosition.x = 0.f;
1113 cursorInfo.primaryPosition.y = 0.f;
1115 // Nothing else to do.
1119 // Get the previous logical index.
1120 const CharacterIndex previousLogical = isFirstPosition ? 0u : logical - 1u;
1122 // Decrease the logical index if it's the last one.
1123 if( isLastPosition )
1128 // Get the direction of the character and the previous one.
1129 const CharacterDirection* const modelCharacterDirectionsBuffer = ( 0u != mLogicalModel->mCharacterDirections.Count() ) ? mLogicalModel->mCharacterDirections.Begin() : NULL;
1131 CharacterDirection isCurrentRightToLeft = false;
1132 CharacterDirection isPreviousRightToLeft = false;
1133 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
1135 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + logical );
1136 isPreviousRightToLeft = *( modelCharacterDirectionsBuffer + previousLogical );
1139 // Get the line where the character is laid-out.
1140 const LineRun* modelLines = mVisualModel->mLines.Begin();
1142 const LineIndex lineIndex = mVisualModel->GetLineOfCharacter( logical );
1143 const LineRun& line = *( modelLines + lineIndex );
1145 // Get the paragraph's direction.
1146 const CharacterDirection isRightToLeftParagraph = line.direction;
1148 // Check whether there is an alternative position:
1150 cursorInfo.isSecondaryCursor = ( isCurrentRightToLeft != isPreviousRightToLeft ) ||
1151 ( isLastPosition && ( isRightToLeftParagraph != isCurrentRightToLeft ) );
1153 // Set the line height.
1154 cursorInfo.lineHeight = line.ascender + -line.descender;
1156 // Convert the cursor position into the glyph position.
1157 CharacterIndex characterIndex = logical;
1158 if( cursorInfo.isSecondaryCursor &&
1159 ( isRightToLeftParagraph != isCurrentRightToLeft ) )
1161 characterIndex = previousLogical;
1164 const GlyphIndex currentGlyphIndex = *( mVisualModel->mCharactersToGlyph.Begin() + characterIndex );
1165 const Length numberOfGlyphs = *( mVisualModel->mGlyphsPerCharacter.Begin() + characterIndex );
1166 const Length numberOfCharacters = *( mVisualModel->mCharactersPerGlyph.Begin() +currentGlyphIndex );
1168 // Get the metrics for the group of glyphs.
1169 GlyphMetrics glyphMetrics;
1170 GetGlyphsMetrics( currentGlyphIndex,
1176 float interGlyphAdvance = 0.f;
1177 if( !isLastPosition &&
1178 ( numberOfCharacters > 1u ) )
1180 const CharacterIndex firstIndex = *( mVisualModel->mGlyphsToCharacters.Begin() + currentGlyphIndex );
1181 interGlyphAdvance = static_cast<float>( characterIndex - firstIndex ) * glyphMetrics.advance / static_cast<float>( numberOfCharacters );
1184 // Get the glyph position and x bearing.
1185 const Vector2& currentPosition = *( mVisualModel->mGlyphPositions.Begin() + currentGlyphIndex );
1187 // Set the cursor's height.
1188 cursorInfo.primaryCursorHeight = glyphMetrics.fontHeight;
1190 // Set the position.
1191 cursorInfo.primaryPosition.x = -glyphMetrics.xBearing + currentPosition.x + ( isCurrentRightToLeft ? glyphMetrics.advance : interGlyphAdvance );
1192 cursorInfo.primaryPosition.y = line.ascender - glyphMetrics.ascender;
1194 if( isLastPosition )
1196 // The position of the cursor after the last character needs special
1197 // care depending on its direction and the direction of the paragraph.
1199 if( cursorInfo.isSecondaryCursor )
1201 // Need to find the first character after the last character with the paragraph's direction.
1202 // i.e l0 l1 l2 r0 r1 should find r0.
1204 // TODO: check for more than one line!
1205 characterIndex = isRightToLeftParagraph ? line.characterRun.characterIndex : line.characterRun.characterIndex + line.characterRun.numberOfCharacters - 1u;
1206 characterIndex = mLogicalModel->GetLogicalCharacterIndex( characterIndex );
1208 const GlyphIndex glyphIndex = *( mVisualModel->mCharactersToGlyph.Begin() + characterIndex );
1209 const Length numberOfGlyphs = *( mVisualModel->mGlyphsPerCharacter.Begin() + characterIndex );
1211 const Vector2& position = *( mVisualModel->mGlyphPositions.Begin() + glyphIndex );
1213 // Get the metrics for the group of glyphs.
1214 GlyphMetrics glyphMetrics;
1215 GetGlyphsMetrics( glyphIndex,
1221 cursorInfo.primaryPosition.x = -glyphMetrics.xBearing + position.x + ( isRightToLeftParagraph ? 0.f : glyphMetrics.advance );
1223 cursorInfo.primaryPosition.y = line.ascender - glyphMetrics.ascender;
1227 if( !isCurrentRightToLeft )
1229 cursorInfo.primaryPosition.x += glyphMetrics.advance;
1233 cursorInfo.primaryPosition.x -= glyphMetrics.advance;
1238 // Set the alternative cursor position.
1239 if( cursorInfo.isSecondaryCursor )
1241 // Convert the cursor position into the glyph position.
1242 const CharacterIndex previousCharacterIndex = ( ( isRightToLeftParagraph != isCurrentRightToLeft ) ? logical : previousLogical );
1243 const GlyphIndex previousGlyphIndex = *( mVisualModel->mCharactersToGlyph.Begin() + previousCharacterIndex );
1244 const Length numberOfGlyphs = *( mVisualModel->mGlyphsPerCharacter.Begin() + previousCharacterIndex );
1246 // Get the glyph position.
1247 const Vector2& previousPosition = *( mVisualModel->mGlyphPositions.Begin() + previousGlyphIndex );
1249 // Get the metrics for the group of glyphs.
1250 GlyphMetrics glyphMetrics;
1251 GetGlyphsMetrics( previousGlyphIndex,
1257 // Set the cursor position and height.
1258 cursorInfo.secondaryPosition.x = -glyphMetrics.xBearing + previousPosition.x + ( ( ( isLastPosition && !isCurrentRightToLeft ) ||
1259 ( !isLastPosition && isCurrentRightToLeft ) ) ? glyphMetrics.advance : 0.f );
1261 cursorInfo.secondaryCursorHeight = 0.5f * glyphMetrics.fontHeight;
1263 cursorInfo.secondaryPosition.y = cursorInfo.lineHeight - cursorInfo.secondaryCursorHeight - line.descender - ( glyphMetrics.fontHeight - glyphMetrics.ascender );
1265 // Update the primary cursor height as well.
1266 cursorInfo.primaryCursorHeight *= 0.5f;
1270 CharacterIndex Controller::Impl::CalculateNewCursorIndex( CharacterIndex index ) const
1272 if( NULL == mEventData )
1274 // Nothing to do if there is no text input.
1278 CharacterIndex cursorIndex = mEventData->mPrimaryCursorPosition;
1280 const Script script = mLogicalModel->GetScript( index );
1281 const GlyphIndex* charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
1282 const Length* charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
1284 Length numberOfCharacters = 0u;
1285 if( TextAbstraction::LATIN == script )
1287 // Prevents to jump the whole Latin ligatures like fi, ff, ...
1288 numberOfCharacters = 1u;
1292 GlyphIndex glyphIndex = *( charactersToGlyphBuffer + index );
1293 numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
1295 while( 0u == numberOfCharacters )
1297 numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
1302 if( index < mEventData->mPrimaryCursorPosition )
1304 cursorIndex -= numberOfCharacters;
1308 cursorIndex += numberOfCharacters;
1314 void Controller::Impl::UpdateCursorPosition()
1316 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "-->Controller::UpdateCursorPosition %p\n", this );
1317 if( NULL == mEventData )
1319 // Nothing to do if there is no text input.
1320 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition no event data\n" );
1324 if( IsShowingPlaceholderText() )
1326 // Do not want to use the place-holder text to set the cursor position.
1328 // Use the line's height of the font's family set to set the cursor's size.
1329 // If there is no font's family set, use the default font.
1330 // Use the current alignment to place the cursor at the beginning, center or end of the box.
1332 float lineHeight = 0.f;
1334 FontId defaultFontId = 0u;
1335 if( NULL == mFontDefaults )
1337 defaultFontId = mFontClient.GetFontId( EMPTY_STRING,
1342 defaultFontId = mFontDefaults->GetFontId( mFontClient );
1345 Text::FontMetrics fontMetrics;
1346 mFontClient.GetFontMetrics( defaultFontId, fontMetrics );
1348 lineHeight = fontMetrics.ascender - fontMetrics.descender;
1351 Vector2 cursorPosition;
1353 switch( mLayoutEngine.GetHorizontalAlignment() )
1355 case LayoutEngine::HORIZONTAL_ALIGN_BEGIN:
1357 cursorPosition.x = 1.f;
1360 case LayoutEngine::HORIZONTAL_ALIGN_CENTER:
1362 cursorPosition.x = floor( 0.5f * mControlSize.width );
1365 case LayoutEngine::HORIZONTAL_ALIGN_END:
1367 cursorPosition.x = mControlSize.width;
1372 switch( mLayoutEngine.GetVerticalAlignment() )
1374 case LayoutEngine::VERTICAL_ALIGN_TOP:
1376 cursorPosition.y = 0.f;
1379 case LayoutEngine::VERTICAL_ALIGN_CENTER:
1381 cursorPosition.y = floorf( 0.5f * ( mControlSize.height - lineHeight ) );
1384 case LayoutEngine::VERTICAL_ALIGN_BOTTOM:
1386 cursorPosition.y = mControlSize.height - lineHeight;
1391 mEventData->mDecorator->SetPosition( PRIMARY_CURSOR,
1399 CursorInfo cursorInfo;
1400 GetCursorPosition( mEventData->mPrimaryCursorPosition,
1403 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
1404 const Vector2 cursorPosition = cursorInfo.primaryPosition + offset;
1406 // Sets the cursor position.
1407 mEventData->mDecorator->SetPosition( PRIMARY_CURSOR,
1410 cursorInfo.primaryCursorHeight,
1411 cursorInfo.lineHeight );
1412 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Primary cursor position: %f,%f\n", cursorPosition.x, cursorPosition.y );
1414 // Sets the grab handle position.
1415 mEventData->mDecorator->SetPosition( GRAB_HANDLE,
1418 cursorInfo.lineHeight );
1420 if( cursorInfo.isSecondaryCursor )
1422 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_BOTH );
1423 mEventData->mDecorator->SetPosition( SECONDARY_CURSOR,
1424 cursorInfo.secondaryPosition.x + offset.x,
1425 cursorInfo.secondaryPosition.y + offset.y,
1426 cursorInfo.secondaryCursorHeight,
1427 cursorInfo.lineHeight );
1428 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Secondary cursor position: %f,%f\n", cursorInfo.secondaryPosition.x + offset.x, cursorInfo.secondaryPosition.y + offset.y );
1432 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1435 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition\n" );
1438 void Controller::Impl::UpdateSelectionHandle( HandleType handleType )
1440 if( ( LEFT_SELECTION_HANDLE != handleType ) &&
1441 ( RIGHT_SELECTION_HANDLE != handleType ) )
1446 const bool leftSelectionHandle = LEFT_SELECTION_HANDLE == handleType;
1447 const CharacterIndex index = leftSelectionHandle ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition;
1449 CursorInfo cursorInfo;
1450 GetCursorPosition( index,
1453 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
1454 const Vector2 cursorPosition = cursorInfo.primaryPosition + offset;
1456 // Sets the grab handle position.
1457 mEventData->mDecorator->SetPosition( handleType,
1460 cursorInfo.lineHeight );
1463 void Controller::Impl::ClampHorizontalScroll( const Vector2& actualSize )
1465 // Clamp between -space & 0 (and the text alignment).
1466 if( actualSize.width > mControlSize.width )
1468 const float space = ( actualSize.width - mControlSize.width ) + mAlignmentOffset.x;
1469 mEventData->mScrollPosition.x = ( mEventData->mScrollPosition.x < -space ) ? -space : mEventData->mScrollPosition.x;
1470 mEventData->mScrollPosition.x = ( mEventData->mScrollPosition.x > -mAlignmentOffset.x ) ? -mAlignmentOffset.x : mEventData->mScrollPosition.x;
1472 mEventData->mDecoratorUpdated = true;
1476 mEventData->mScrollPosition.x = 0.f;
1480 void Controller::Impl::ClampVerticalScroll( const Vector2& actualSize )
1482 // Clamp between -space & 0 (and the text alignment).
1483 if( actualSize.height > mControlSize.height )
1485 const float space = ( actualSize.height - mControlSize.height ) + mAlignmentOffset.y;
1486 mEventData->mScrollPosition.y = ( mEventData->mScrollPosition.y < -space ) ? -space : mEventData->mScrollPosition.y;
1487 mEventData->mScrollPosition.y = ( mEventData->mScrollPosition.y > -mAlignmentOffset.y ) ? -mAlignmentOffset.y : mEventData->mScrollPosition.y;
1489 mEventData->mDecoratorUpdated = true;
1493 mEventData->mScrollPosition.y = 0.f;
1497 void Controller::Impl::ScrollToMakePositionVisible( const Vector2& position )
1500 bool updateDecorator = false;
1501 if( position.x < 0.f )
1503 offset.x = -position.x;
1504 mEventData->mScrollPosition.x += offset.x;
1505 updateDecorator = true;
1507 else if( position.x > mControlSize.width )
1509 offset.x = mControlSize.width - position.x;
1510 mEventData->mScrollPosition.x += offset.x;
1511 updateDecorator = true;
1514 if( updateDecorator && mEventData->mDecorator )
1516 mEventData->mDecorator->UpdatePositions( offset );
1519 // TODO : calculate the vertical scroll.
1522 void Controller::Impl::ScrollTextToMatchCursor()
1524 // Get the current cursor position in decorator coords.
1525 const Vector2& currentCursorPosition = mEventData->mDecorator->GetPosition( PRIMARY_CURSOR );
1527 // Calculate the new cursor position.
1528 CursorInfo cursorInfo;
1529 GetCursorPosition( mEventData->mPrimaryCursorPosition,
1532 // Calculate the offset to match the cursor position before the character was deleted.
1533 mEventData->mScrollPosition.x = currentCursorPosition.x - cursorInfo.primaryPosition.x - mAlignmentOffset.x;
1535 ClampHorizontalScroll( mVisualModel->GetActualSize() );
1536 bool updateCursorPosition = true;
1538 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
1539 const Vector2 cursorPosition = cursorInfo.primaryPosition + offset;
1541 if( updateCursorPosition )
1543 // Sets the cursor position.
1544 mEventData->mDecorator->SetPosition( PRIMARY_CURSOR,
1547 cursorInfo.primaryCursorHeight,
1548 cursorInfo.lineHeight );
1550 // Sets the grab handle position.
1551 mEventData->mDecorator->SetPosition( GRAB_HANDLE,
1554 cursorInfo.lineHeight );
1556 if( cursorInfo.isSecondaryCursor )
1558 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_BOTH );
1559 mEventData->mDecorator->SetPosition( SECONDARY_CURSOR,
1560 cursorInfo.secondaryPosition.x + offset.x,
1561 cursorInfo.secondaryPosition.y + offset.y,
1562 cursorInfo.secondaryCursorHeight,
1563 cursorInfo.lineHeight );
1564 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Secondary cursor position: %f,%f\n", cursorInfo.secondaryPosition.x + offset.x, cursorInfo.secondaryPosition.y + offset.y );
1568 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1573 void Controller::Impl::RequestRelayout()
1575 mControlInterface.RequestTextRelayout();
1580 } // namespace Toolkit