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 buttonsToShow = TextSelectionPopup::Buttons( TextSelectionPopup::CUT | TextSelectionPopup::COPY );
851 if ( !IsClipboardEmpty() )
853 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
856 mEventData->mDecorator->SetEnabledPopupButtons( buttonsToShow );
857 mEventData->mDecorator->SetPopupActive( true );
859 mEventData->mDecoratorUpdated = true;
861 else if ( EventData::SELECTION_CHANGED == mEventData->mState )
863 if( mEventData->mGrabHandlePopupEnabled )
865 TextSelectionPopup::Buttons buttonsToShow = TextSelectionPopup::Buttons( TextSelectionPopup::CUT | TextSelectionPopup::COPY );
866 if ( !IsClipboardEmpty() )
868 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
870 mEventData->mDecorator->SetEnabledPopupButtons( buttonsToShow );
871 mEventData->mDecorator->SetPopupActive( true );
873 mEventData->mDecoratorUpdated = true;
875 else if( EventData::EDITING == mEventData->mState )
877 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
878 if( mEventData->mCursorBlinkEnabled )
880 mEventData->mDecorator->StartCursorBlink();
882 // Grab handle is not shown until a tap is received whilst EDITING
883 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
884 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
885 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
886 if( mEventData->mGrabHandlePopupEnabled )
888 mEventData->mDecorator->SetPopupActive( false );
890 mEventData->mDecoratorUpdated = true;
892 else if( EventData::EDITING_WITH_POPUP == mEventData->mState )
894 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
895 if( mEventData->mCursorBlinkEnabled )
897 mEventData->mDecorator->StartCursorBlink();
899 if( mEventData->mSelectionEnabled )
901 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
902 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
906 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
908 if( mEventData->mGrabHandlePopupEnabled )
910 TextSelectionPopup::Buttons buttonsToShow = TextSelectionPopup::Buttons( TextSelectionPopup::SELECT | TextSelectionPopup::SELECT_ALL );
912 if ( !IsClipboardEmpty() )
914 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
917 mEventData->mDecorator->SetEnabledPopupButtons( buttonsToShow );
918 mEventData->mDecorator->SetPopupActive( true );
920 mEventData->mDecoratorUpdated = true;
922 else if ( EventData::SELECTION_HANDLE_PANNING == mEventData->mState )
924 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
925 mEventData->mDecorator->StopCursorBlink();
926 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
927 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true );
928 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true );
929 if( mEventData->mGrabHandlePopupEnabled )
931 mEventData->mDecorator->SetPopupActive( false );
933 mEventData->mDecoratorUpdated = true;
935 else if ( EventData::GRAB_HANDLE_PANNING == mEventData->mState )
937 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
938 if( mEventData->mCursorBlinkEnabled )
940 mEventData->mDecorator->StartCursorBlink();
942 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
943 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
944 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
945 if( mEventData->mGrabHandlePopupEnabled )
947 mEventData->mDecorator->SetPopupActive( false );
949 mEventData->mDecoratorUpdated = true;
954 LineIndex Controller::Impl::GetClosestLine( float y ) const
956 float totalHeight = 0.f;
957 LineIndex lineIndex = 0u;
959 const Vector<LineRun>& lines = mVisualModel->mLines;
960 for( LineIndex endLine = lines.Count();
964 const LineRun& lineRun = lines[lineIndex];
965 totalHeight += lineRun.ascender + -lineRun.descender;
966 if( y < totalHeight )
980 void Controller::Impl::FindSelectionIndices( float visualX, float visualY, CharacterIndex& startIndex, CharacterIndex& endIndex )
982 CharacterIndex hitCharacter = GetClosestCursorIndex( visualX, visualY );
983 if( hitCharacter >= mLogicalModel->mText.Count() )
985 // Selection out of bounds.
989 startIndex = hitCharacter;
990 endIndex = hitCharacter;
992 if( !TextAbstraction::IsWhiteSpace( mLogicalModel->mText[hitCharacter] ) )
994 // Find the start and end of the text
995 for( startIndex = hitCharacter; startIndex > 0; --startIndex )
997 Character charCode = mLogicalModel->mText[ startIndex-1 ];
998 if( TextAbstraction::IsWhiteSpace( charCode ) )
1003 const CharacterIndex pastTheEnd = mLogicalModel->mText.Count();
1004 for( endIndex = hitCharacter + 1u; endIndex < pastTheEnd; ++endIndex )
1006 Character charCode = mLogicalModel->mText[ endIndex ];
1007 if( TextAbstraction::IsWhiteSpace( charCode ) )
1015 CharacterIndex Controller::Impl::GetClosestCursorIndex( float visualX,
1018 if( NULL == mEventData )
1020 // Nothing to do if there is no text input.
1024 CharacterIndex logicalIndex = 0u;
1026 const Length numberOfGlyphs = mVisualModel->mGlyphs.Count();
1027 const Length numberOfLines = mVisualModel->mLines.Count();
1028 if( 0 == numberOfGlyphs ||
1029 0 == numberOfLines )
1031 return logicalIndex;
1034 // Find which line is closest
1035 const LineIndex lineIndex = GetClosestLine( visualY );
1036 const LineRun& line = mVisualModel->mLines[lineIndex];
1038 // Get the positions of the glyphs.
1039 const Vector<Vector2>& positions = mVisualModel->mGlyphPositions;
1040 const Vector2* const positionsBuffer = positions.Begin();
1042 // Get the visual to logical conversion tables.
1043 const CharacterIndex* const visualToLogicalBuffer = ( 0u != mLogicalModel->mVisualToLogicalMap.Count() ) ? mLogicalModel->mVisualToLogicalMap.Begin() : NULL;
1044 const CharacterIndex* const visualToLogicalCursorBuffer = mLogicalModel->mVisualToLogicalCursorMap.Begin();
1046 // Get the character to glyph conversion table.
1047 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
1049 // Get the glyphs per character table.
1050 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
1052 // If the vector is void, there is no right to left characters.
1053 const bool hasRightToLeftCharacters = NULL != visualToLogicalBuffer;
1055 const CharacterIndex startCharacter = line.characterRun.characterIndex;
1056 const CharacterIndex endCharacter = line.characterRun.characterIndex + line.characterRun.numberOfCharacters;
1057 DALI_ASSERT_DEBUG( endCharacter <= mLogicalModel->mText.Count() && "Invalid line info" );
1059 // Whether there is a hit on a glyph.
1060 bool matched = false;
1062 // Traverses glyphs in visual order. To do that use the visual to logical conversion table.
1063 CharacterIndex visualIndex = startCharacter;
1064 for( ; !matched && ( visualIndex < endCharacter ); ++visualIndex )
1066 // The character in logical order.
1067 const CharacterIndex characterLogicalOrderIndex = hasRightToLeftCharacters ? *( visualToLogicalBuffer + visualIndex ) : visualIndex;
1069 // The first glyph for that character in logical order.
1070 const GlyphIndex glyphLogicalOrderIndex = *( charactersToGlyphBuffer + characterLogicalOrderIndex );
1072 // The number of glyphs for that character
1073 const Length numberOfGlyphs = *( glyphsPerCharacterBuffer + characterLogicalOrderIndex );
1075 // Get the metrics for the group of glyphs.
1076 GlyphMetrics glyphMetrics;
1077 GetGlyphsMetrics( glyphLogicalOrderIndex,
1083 const Vector2& position = *( positionsBuffer + glyphLogicalOrderIndex );
1085 // Find the mid-point of the area containing the glyph
1086 const float glyphCenter = -glyphMetrics.xBearing + position.x + 0.5f * glyphMetrics.advance;
1088 if( visualX < glyphCenter )
1095 // Return the logical position of the cursor in characters.
1099 visualIndex = endCharacter;
1102 logicalIndex = hasRightToLeftCharacters ? *( visualToLogicalCursorBuffer + visualIndex ) : visualIndex;
1103 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "%p closest visualIndex %d logicalIndex %d\n", this, visualIndex, logicalIndex );
1104 return logicalIndex;
1107 void Controller::Impl::GetCursorPosition( CharacterIndex logical,
1108 CursorInfo& cursorInfo )
1110 // TODO: Check for multiline with \n, etc...
1112 // Check if the logical position is the first or the last one of the text.
1113 const bool isFirstPosition = 0u == logical;
1114 const bool isLastPosition = mLogicalModel->GetNumberOfCharacters() == logical;
1116 if( isFirstPosition && isLastPosition )
1118 // There is zero characters. Get the default font.
1120 FontId defaultFontId = 0u;
1121 if( NULL == mFontDefaults )
1123 defaultFontId = mFontClient.GetFontId( EMPTY_STRING,
1128 defaultFontId = mFontDefaults->GetFontId( mFontClient );
1131 Text::FontMetrics fontMetrics;
1132 mFontClient.GetFontMetrics( defaultFontId, fontMetrics );
1134 cursorInfo.lineHeight = fontMetrics.ascender - fontMetrics.descender;
1135 cursorInfo.primaryCursorHeight = cursorInfo.lineHeight;
1137 cursorInfo.primaryPosition.x = 0.f;
1138 cursorInfo.primaryPosition.y = 0.f;
1140 // Nothing else to do.
1144 // Get the previous logical index.
1145 const CharacterIndex previousLogical = isFirstPosition ? 0u : logical - 1u;
1147 // Decrease the logical index if it's the last one.
1148 if( isLastPosition )
1153 // Get the direction of the character and the previous one.
1154 const CharacterDirection* const modelCharacterDirectionsBuffer = ( 0u != mLogicalModel->mCharacterDirections.Count() ) ? mLogicalModel->mCharacterDirections.Begin() : NULL;
1156 CharacterDirection isCurrentRightToLeft = false;
1157 CharacterDirection isPreviousRightToLeft = false;
1158 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
1160 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + logical );
1161 isPreviousRightToLeft = *( modelCharacterDirectionsBuffer + previousLogical );
1164 // Get the line where the character is laid-out.
1165 const LineRun* modelLines = mVisualModel->mLines.Begin();
1167 const LineIndex lineIndex = mVisualModel->GetLineOfCharacter( logical );
1168 const LineRun& line = *( modelLines + lineIndex );
1170 // Get the paragraph's direction.
1171 const CharacterDirection isRightToLeftParagraph = line.direction;
1173 // Check whether there is an alternative position:
1175 cursorInfo.isSecondaryCursor = ( isCurrentRightToLeft != isPreviousRightToLeft ) ||
1176 ( isLastPosition && ( isRightToLeftParagraph != isCurrentRightToLeft ) );
1178 // Set the line height.
1179 cursorInfo.lineHeight = line.ascender + -line.descender;
1181 // Convert the cursor position into the glyph position.
1182 CharacterIndex characterIndex = logical;
1183 if( cursorInfo.isSecondaryCursor &&
1184 ( isRightToLeftParagraph != isCurrentRightToLeft ) )
1186 characterIndex = previousLogical;
1189 const GlyphIndex currentGlyphIndex = *( mVisualModel->mCharactersToGlyph.Begin() + characterIndex );
1190 const Length numberOfGlyphs = *( mVisualModel->mGlyphsPerCharacter.Begin() + characterIndex );
1191 const Length numberOfCharacters = *( mVisualModel->mCharactersPerGlyph.Begin() +currentGlyphIndex );
1193 // Get the metrics for the group of glyphs.
1194 GlyphMetrics glyphMetrics;
1195 GetGlyphsMetrics( currentGlyphIndex,
1201 float interGlyphAdvance = 0.f;
1202 if( !isLastPosition &&
1203 ( numberOfCharacters > 1u ) )
1205 const CharacterIndex firstIndex = *( mVisualModel->mGlyphsToCharacters.Begin() + currentGlyphIndex );
1206 interGlyphAdvance = static_cast<float>( characterIndex - firstIndex ) * glyphMetrics.advance / static_cast<float>( numberOfCharacters );
1209 // Get the glyph position and x bearing.
1210 const Vector2& currentPosition = *( mVisualModel->mGlyphPositions.Begin() + currentGlyphIndex );
1212 // Set the cursor's height.
1213 cursorInfo.primaryCursorHeight = glyphMetrics.fontHeight;
1215 // Set the position.
1216 cursorInfo.primaryPosition.x = -glyphMetrics.xBearing + currentPosition.x + ( isCurrentRightToLeft ? glyphMetrics.advance : interGlyphAdvance );
1217 cursorInfo.primaryPosition.y = line.ascender - glyphMetrics.ascender;
1219 if( isLastPosition )
1221 // The position of the cursor after the last character needs special
1222 // care depending on its direction and the direction of the paragraph.
1224 if( cursorInfo.isSecondaryCursor )
1226 // Need to find the first character after the last character with the paragraph's direction.
1227 // i.e l0 l1 l2 r0 r1 should find r0.
1229 // TODO: check for more than one line!
1230 characterIndex = isRightToLeftParagraph ? line.characterRun.characterIndex : line.characterRun.characterIndex + line.characterRun.numberOfCharacters - 1u;
1231 characterIndex = mLogicalModel->GetLogicalCharacterIndex( characterIndex );
1233 const GlyphIndex glyphIndex = *( mVisualModel->mCharactersToGlyph.Begin() + characterIndex );
1234 const Length numberOfGlyphs = *( mVisualModel->mGlyphsPerCharacter.Begin() + characterIndex );
1236 const Vector2& position = *( mVisualModel->mGlyphPositions.Begin() + glyphIndex );
1238 // Get the metrics for the group of glyphs.
1239 GlyphMetrics glyphMetrics;
1240 GetGlyphsMetrics( glyphIndex,
1246 cursorInfo.primaryPosition.x = -glyphMetrics.xBearing + position.x + ( isRightToLeftParagraph ? 0.f : glyphMetrics.advance );
1248 cursorInfo.primaryPosition.y = line.ascender - glyphMetrics.ascender;
1252 if( !isCurrentRightToLeft )
1254 cursorInfo.primaryPosition.x += glyphMetrics.advance;
1258 cursorInfo.primaryPosition.x -= glyphMetrics.advance;
1263 // Set the alternative cursor position.
1264 if( cursorInfo.isSecondaryCursor )
1266 // Convert the cursor position into the glyph position.
1267 const CharacterIndex previousCharacterIndex = ( ( isRightToLeftParagraph != isCurrentRightToLeft ) ? logical : previousLogical );
1268 const GlyphIndex previousGlyphIndex = *( mVisualModel->mCharactersToGlyph.Begin() + previousCharacterIndex );
1269 const Length numberOfGlyphs = *( mVisualModel->mGlyphsPerCharacter.Begin() + previousCharacterIndex );
1271 // Get the glyph position.
1272 const Vector2& previousPosition = *( mVisualModel->mGlyphPositions.Begin() + previousGlyphIndex );
1274 // Get the metrics for the group of glyphs.
1275 GlyphMetrics glyphMetrics;
1276 GetGlyphsMetrics( previousGlyphIndex,
1282 // Set the cursor position and height.
1283 cursorInfo.secondaryPosition.x = -glyphMetrics.xBearing + previousPosition.x + ( ( ( isLastPosition && !isCurrentRightToLeft ) ||
1284 ( !isLastPosition && isCurrentRightToLeft ) ) ? glyphMetrics.advance : 0.f );
1286 cursorInfo.secondaryCursorHeight = 0.5f * glyphMetrics.fontHeight;
1288 cursorInfo.secondaryPosition.y = cursorInfo.lineHeight - cursorInfo.secondaryCursorHeight - line.descender - ( glyphMetrics.fontHeight - glyphMetrics.ascender );
1290 // Update the primary cursor height as well.
1291 cursorInfo.primaryCursorHeight *= 0.5f;
1295 CharacterIndex Controller::Impl::CalculateNewCursorIndex( CharacterIndex index ) const
1297 if( NULL == mEventData )
1299 // Nothing to do if there is no text input.
1303 CharacterIndex cursorIndex = mEventData->mPrimaryCursorPosition;
1305 const Script script = mLogicalModel->GetScript( index );
1306 const GlyphIndex* charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
1307 const Length* charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
1309 Length numberOfCharacters = 0u;
1310 if( TextAbstraction::LATIN == script )
1312 // Prevents to jump the whole Latin ligatures like fi, ff, ...
1313 numberOfCharacters = 1u;
1317 GlyphIndex glyphIndex = *( charactersToGlyphBuffer + index );
1318 numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
1320 while( 0u == numberOfCharacters )
1322 numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
1327 if( index < mEventData->mPrimaryCursorPosition )
1329 cursorIndex -= numberOfCharacters;
1333 cursorIndex += numberOfCharacters;
1339 void Controller::Impl::UpdateCursorPosition()
1341 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "-->Controller::UpdateCursorPosition %p\n", this );
1342 if( NULL == mEventData )
1344 // Nothing to do if there is no text input.
1345 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition no event data\n" );
1349 if( IsShowingPlaceholderText() )
1351 // Do not want to use the place-holder text to set the cursor position.
1353 // Use the line's height of the font's family set to set the cursor's size.
1354 // If there is no font's family set, use the default font.
1355 // Use the current alignment to place the cursor at the beginning, center or end of the box.
1357 float lineHeight = 0.f;
1359 FontId defaultFontId = 0u;
1360 if( NULL == mFontDefaults )
1362 defaultFontId = mFontClient.GetFontId( EMPTY_STRING,
1367 defaultFontId = mFontDefaults->GetFontId( mFontClient );
1370 Text::FontMetrics fontMetrics;
1371 mFontClient.GetFontMetrics( defaultFontId, fontMetrics );
1373 lineHeight = fontMetrics.ascender - fontMetrics.descender;
1376 Vector2 cursorPosition;
1378 switch( mLayoutEngine.GetHorizontalAlignment() )
1380 case LayoutEngine::HORIZONTAL_ALIGN_BEGIN:
1382 cursorPosition.x = 1.f;
1385 case LayoutEngine::HORIZONTAL_ALIGN_CENTER:
1387 cursorPosition.x = floor( 0.5f * mControlSize.width );
1390 case LayoutEngine::HORIZONTAL_ALIGN_END:
1392 cursorPosition.x = mControlSize.width;
1397 switch( mLayoutEngine.GetVerticalAlignment() )
1399 case LayoutEngine::VERTICAL_ALIGN_TOP:
1401 cursorPosition.y = 0.f;
1404 case LayoutEngine::VERTICAL_ALIGN_CENTER:
1406 cursorPosition.y = floorf( 0.5f * ( mControlSize.height - lineHeight ) );
1409 case LayoutEngine::VERTICAL_ALIGN_BOTTOM:
1411 cursorPosition.y = mControlSize.height - lineHeight;
1416 mEventData->mDecorator->SetPosition( PRIMARY_CURSOR,
1424 CursorInfo cursorInfo;
1425 GetCursorPosition( mEventData->mPrimaryCursorPosition,
1428 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
1429 const Vector2 cursorPosition = cursorInfo.primaryPosition + offset;
1431 // Sets the cursor position.
1432 mEventData->mDecorator->SetPosition( PRIMARY_CURSOR,
1435 cursorInfo.primaryCursorHeight,
1436 cursorInfo.lineHeight );
1437 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Primary cursor position: %f,%f\n", cursorPosition.x, cursorPosition.y );
1439 // Sets the grab handle position.
1440 mEventData->mDecorator->SetPosition( GRAB_HANDLE,
1443 cursorInfo.lineHeight );
1445 if( cursorInfo.isSecondaryCursor )
1447 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_BOTH );
1448 mEventData->mDecorator->SetPosition( SECONDARY_CURSOR,
1449 cursorInfo.secondaryPosition.x + offset.x,
1450 cursorInfo.secondaryPosition.y + offset.y,
1451 cursorInfo.secondaryCursorHeight,
1452 cursorInfo.lineHeight );
1453 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Secondary cursor position: %f,%f\n", cursorInfo.secondaryPosition.x + offset.x, cursorInfo.secondaryPosition.y + offset.y );
1457 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1460 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition\n" );
1463 void Controller::Impl::UpdateSelectionHandle( HandleType handleType )
1465 if( ( LEFT_SELECTION_HANDLE != handleType ) &&
1466 ( RIGHT_SELECTION_HANDLE != handleType ) )
1471 const bool leftSelectionHandle = LEFT_SELECTION_HANDLE == handleType;
1472 const CharacterIndex index = leftSelectionHandle ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition;
1474 CursorInfo cursorInfo;
1475 GetCursorPosition( index,
1478 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
1479 const Vector2 cursorPosition = cursorInfo.primaryPosition + offset;
1481 // Sets the grab handle position.
1482 mEventData->mDecorator->SetPosition( handleType,
1485 cursorInfo.lineHeight );
1488 void Controller::Impl::ClampHorizontalScroll( const Vector2& actualSize )
1490 // Clamp between -space & 0 (and the text alignment).
1491 if( actualSize.width > mControlSize.width )
1493 const float space = ( actualSize.width - mControlSize.width ) + mAlignmentOffset.x;
1494 mEventData->mScrollPosition.x = ( mEventData->mScrollPosition.x < -space ) ? -space : mEventData->mScrollPosition.x;
1495 mEventData->mScrollPosition.x = ( mEventData->mScrollPosition.x > -mAlignmentOffset.x ) ? -mAlignmentOffset.x : mEventData->mScrollPosition.x;
1497 mEventData->mDecoratorUpdated = true;
1501 mEventData->mScrollPosition.x = 0.f;
1505 void Controller::Impl::ClampVerticalScroll( const Vector2& actualSize )
1507 // Clamp between -space & 0 (and the text alignment).
1508 if( actualSize.height > mControlSize.height )
1510 const float space = ( actualSize.height - mControlSize.height ) + mAlignmentOffset.y;
1511 mEventData->mScrollPosition.y = ( mEventData->mScrollPosition.y < -space ) ? -space : mEventData->mScrollPosition.y;
1512 mEventData->mScrollPosition.y = ( mEventData->mScrollPosition.y > -mAlignmentOffset.y ) ? -mAlignmentOffset.y : mEventData->mScrollPosition.y;
1514 mEventData->mDecoratorUpdated = true;
1518 mEventData->mScrollPosition.y = 0.f;
1522 void Controller::Impl::ScrollToMakePositionVisible( const Vector2& position )
1525 bool updateDecorator = false;
1526 if( position.x < 0.f )
1528 offset.x = -position.x;
1529 mEventData->mScrollPosition.x += offset.x;
1530 updateDecorator = true;
1532 else if( position.x > mControlSize.width )
1534 offset.x = mControlSize.width - position.x;
1535 mEventData->mScrollPosition.x += offset.x;
1536 updateDecorator = true;
1539 if( updateDecorator && mEventData->mDecorator )
1541 mEventData->mDecorator->UpdatePositions( offset );
1544 // TODO : calculate the vertical scroll.
1547 void Controller::Impl::ScrollTextToMatchCursor()
1549 // Get the current cursor position in decorator coords.
1550 const Vector2& currentCursorPosition = mEventData->mDecorator->GetPosition( PRIMARY_CURSOR );
1552 // Calculate the new cursor position.
1553 CursorInfo cursorInfo;
1554 GetCursorPosition( mEventData->mPrimaryCursorPosition,
1557 // Calculate the offset to match the cursor position before the character was deleted.
1558 mEventData->mScrollPosition.x = currentCursorPosition.x - cursorInfo.primaryPosition.x - mAlignmentOffset.x;
1560 ClampHorizontalScroll( mVisualModel->GetActualSize() );
1561 bool updateCursorPosition = true;
1563 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
1564 const Vector2 cursorPosition = cursorInfo.primaryPosition + offset;
1566 if( updateCursorPosition )
1568 // Sets the cursor position.
1569 mEventData->mDecorator->SetPosition( PRIMARY_CURSOR,
1572 cursorInfo.primaryCursorHeight,
1573 cursorInfo.lineHeight );
1575 // Sets the grab handle position.
1576 mEventData->mDecorator->SetPosition( GRAB_HANDLE,
1579 cursorInfo.lineHeight );
1581 if( cursorInfo.isSecondaryCursor )
1583 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_BOTH );
1584 mEventData->mDecorator->SetPosition( SECONDARY_CURSOR,
1585 cursorInfo.secondaryPosition.x + offset.x,
1586 cursorInfo.secondaryPosition.y + offset.y,
1587 cursorInfo.secondaryCursorHeight,
1588 cursorInfo.lineHeight );
1589 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Secondary cursor position: %f,%f\n", cursorInfo.secondaryPosition.x + offset.x, cursorInfo.secondaryPosition.y + offset.y );
1593 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1598 void Controller::Impl::RequestRelayout()
1600 mControlInterface.RequestTextRelayout();
1605 } // namespace Toolkit