2 * Copyright (c) 2015 Samsung Electronics Co., Ltd.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
19 #include <dali-toolkit/internal/text/text-controller-impl.h>
22 #include <dali/public-api/adaptor-framework/key.h>
23 #include <dali/integration-api/debug.h>
26 #include <dali-toolkit/internal/text/bidirectional-support.h>
27 #include <dali-toolkit/internal/text/character-set-conversion.h>
28 #include <dali-toolkit/internal/text/multi-language-support.h>
29 #include <dali-toolkit/internal/text/segmentation.h>
30 #include <dali-toolkit/internal/text/shaper.h>
35 #if defined(DEBUG_ENABLED)
36 Debug::Filter* gLogFilter = Debug::Filter::New(Debug::Concise, true, "LOG_TEXT_CONTROLS");
40 * @brief Some characters can be shaped in more than one glyph.
41 * This struct is used to retrieve metrics from these group of glyphs.
55 float fontHeight; ///< The font's height of that glyphs.
56 float advance; ///< The sum of all the advances of all the glyphs.
57 float ascender; ///< The font's ascender.
58 float xBearing; ///< The x bearing of the first glyph.
61 const std::string EMPTY_STRING("");
75 * @brief Get some glyph's metrics of a group of glyphs formed as a result of shaping one character.
77 * @param[in] glyphIndex The index to the first glyph.
78 * @param[in] numberOfGlyphs The number of glyphs.
79 * @param[out] glyphMetrics Some glyph metrics (font height, advance, ascender and x bearing).
80 * @param[in] visualModel The visual model.
81 * @param[in] fontClient The font client.
83 void GetGlyphsMetrics( GlyphIndex glyphIndex,
84 Length numberOfGlyphs,
85 GlyphMetrics& glyphMetrics,
86 VisualModelPtr visualModel,
87 TextAbstraction::FontClient& fontClient )
89 const GlyphInfo* glyphsBuffer = visualModel->mGlyphs.Begin();
91 const GlyphInfo& firstGlyph = *( glyphsBuffer + glyphIndex );
93 Text::FontMetrics fontMetrics;
94 fontClient.GetFontMetrics( firstGlyph.fontId, fontMetrics );
96 glyphMetrics.fontHeight = fontMetrics.height;
97 glyphMetrics.advance = firstGlyph.advance;
98 glyphMetrics.ascender = fontMetrics.ascender;
99 glyphMetrics.xBearing = firstGlyph.xBearing;
101 for( unsigned int i = 1u; i < numberOfGlyphs; ++i )
103 const GlyphInfo& glyphInfo = *( glyphsBuffer + glyphIndex + i );
105 glyphMetrics.advance += glyphInfo.advance;
109 EventData::EventData( DecoratorPtr decorator )
110 : mDecorator( decorator ),
111 mPlaceholderTextActive(),
112 mPlaceholderTextInactive(),
113 mPlaceholderTextColor( 0.8f, 0.8f, 0.8f, 0.8f ),
117 mPrimaryCursorPosition( 0u ),
118 mLeftSelectionPosition( 0u ),
119 mRightSelectionPosition( 0u ),
120 mPreEditStartPosition( 0u ),
121 mPreEditLength( 0u ),
122 mIsShowingPlaceholderText( false ),
123 mPreEditFlag( false ),
124 mDecoratorUpdated( false ),
125 mCursorBlinkEnabled( true ),
126 mGrabHandleEnabled( true ),
127 mGrabHandlePopupEnabled( true ),
128 mSelectionEnabled( true ),
129 mHorizontalScrollingEnabled( true ),
130 mVerticalScrollingEnabled( false ),
131 mUpdateCursorPosition( false ),
132 mUpdateLeftSelectionPosition( false ),
133 mUpdateRightSelectionPosition( false ),
134 mScrollAfterUpdatePosition( false ),
135 mScrollAfterDelete( false ),
136 mAllTextSelected( false )
139 EventData::~EventData()
142 bool Controller::Impl::ProcessInputEvents()
144 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "-->Controller::ProcessInputEvents\n" );
145 if( NULL == mEventData )
147 // Nothing to do if there is no text input.
148 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::ProcessInputEvents no event data\n" );
152 if( mEventData->mDecorator )
154 for( std::vector<Event>::iterator iter = mEventData->mEventQueue.begin();
155 iter != mEventData->mEventQueue.end();
160 case Event::CURSOR_KEY_EVENT:
162 OnCursorKeyEvent( *iter );
165 case Event::TAP_EVENT:
170 case Event::LONG_PRESS_EVENT:
172 OnLongPressEvent( *iter );
175 case Event::PAN_EVENT:
180 case Event::GRAB_HANDLE_EVENT:
181 case Event::LEFT_SELECTION_HANDLE_EVENT:
182 case Event::RIGHT_SELECTION_HANDLE_EVENT: // Fall through
184 OnHandleEvent( *iter );
189 OnSelectEvent( *iter );
192 case Event::SELECT_ALL:
201 // The cursor must also be repositioned after inserts into the model
202 if( mEventData->mUpdateCursorPosition )
204 // Updates the cursor position and scrolls the text to make it visible.
206 UpdateCursorPosition();
208 if( mEventData->mScrollAfterUpdatePosition )
210 const Vector2& primaryCursorPosition = mEventData->mDecorator->GetPosition( PRIMARY_CURSOR );
212 ScrollToMakePositionVisible( primaryCursorPosition );
213 mEventData->mScrollAfterUpdatePosition = false;
216 mEventData->mDecoratorUpdated = true;
217 mEventData->mUpdateCursorPosition = false;
219 else if( mEventData->mScrollAfterDelete )
221 ScrollTextToMatchCursor();
222 mEventData->mDecoratorUpdated = true;
223 mEventData->mScrollAfterDelete = false;
227 bool leftScroll = false;
228 bool rightScroll = false;
230 if( mEventData->mUpdateLeftSelectionPosition )
232 UpdateSelectionHandle( LEFT_SELECTION_HANDLE );
234 if( mEventData->mScrollAfterUpdatePosition )
236 const Vector2& leftHandlePosition = mEventData->mDecorator->GetPosition( LEFT_SELECTION_HANDLE );
238 ScrollToMakePositionVisible( leftHandlePosition );
243 mEventData->mDecoratorUpdated = true;
244 mEventData->mUpdateLeftSelectionPosition = false;
247 if( mEventData->mUpdateRightSelectionPosition )
249 UpdateSelectionHandle( RIGHT_SELECTION_HANDLE );
251 if( mEventData->mScrollAfterUpdatePosition )
253 const Vector2& rightHandlePosition = mEventData->mDecorator->GetPosition( RIGHT_SELECTION_HANDLE );
255 ScrollToMakePositionVisible( rightHandlePosition );
260 mEventData->mDecoratorUpdated = true;
261 mEventData->mUpdateRightSelectionPosition = false;
264 if( leftScroll || rightScroll )
266 mEventData->mScrollAfterUpdatePosition = false;
270 mEventData->mEventQueue.clear();
272 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::ProcessInputEvents\n" );
274 const bool decoratorUpdated = mEventData->mDecoratorUpdated;
275 mEventData->mDecoratorUpdated = false;
277 return decoratorUpdated;
280 void Controller::Impl::UpdateModel( OperationsMask operationsRequired )
282 // Calculate the operations to be done.
283 const OperationsMask operations = static_cast<OperationsMask>( mOperationsPending & operationsRequired );
285 Vector<Character>& utf32Characters = mLogicalModel->mText;
287 const Length numberOfCharacters = utf32Characters.Count();
289 Vector<LineBreakInfo>& lineBreakInfo = mLogicalModel->mLineBreakInfo;
290 if( GET_LINE_BREAKS & operations )
292 // Retrieves the line break info. The line break info is used to split the text in 'paragraphs' to
293 // calculate the bidirectional info for each 'paragraph'.
294 // It's also used to layout the text (where it should be a new line) or to shape the text (text in different lines
295 // is not shaped together).
296 lineBreakInfo.Resize( numberOfCharacters, TextAbstraction::LINE_NO_BREAK );
298 SetLineBreakInfo( utf32Characters,
302 Vector<WordBreakInfo>& wordBreakInfo = mLogicalModel->mWordBreakInfo;
303 if( GET_WORD_BREAKS & operations )
305 // Retrieves the word break info. The word break info is used to layout the text (where to wrap the text in lines).
306 wordBreakInfo.Resize( numberOfCharacters, TextAbstraction::WORD_NO_BREAK );
308 SetWordBreakInfo( utf32Characters,
312 const bool getScripts = GET_SCRIPTS & operations;
313 const bool validateFonts = VALIDATE_FONTS & operations;
315 Vector<ScriptRun>& scripts = mLogicalModel->mScriptRuns;
316 Vector<FontRun>& validFonts = mLogicalModel->mFontRuns;
318 if( getScripts || validateFonts )
320 // Validates the fonts assigned by the application or assigns default ones.
321 // It makes sure all the characters are going to be rendered by the correct font.
322 MultilanguageSupport multilanguageSupport = MultilanguageSupport::Get();
326 // Retrieves the scripts used in the text.
327 multilanguageSupport.SetScripts( utf32Characters,
333 if( 0u == validFonts.Count() )
335 // Copy the requested font defaults received via the property system.
336 // These may not be valid i.e. may not contain glyphs for the necessary scripts.
337 GetDefaultFonts( validFonts, numberOfCharacters );
340 // Validates the fonts. If there is a character with no assigned font it sets a default one.
341 // After this call, fonts are validated.
342 multilanguageSupport.ValidateFonts( utf32Characters,
348 Vector<Character> mirroredUtf32Characters;
349 bool textMirrored = false;
350 Length numberOfParagraphs = 0u;
351 if( BIDI_INFO & operations )
353 // Count the number of LINE_NO_BREAK to reserve some space for the vector of paragraph's
354 // bidirectional info.
356 const TextAbstraction::LineBreakInfo* lineBreakInfoBuffer = lineBreakInfo.Begin();
357 for( Length index = 0u; index < numberOfCharacters; ++index )
359 if( TextAbstraction::LINE_NO_BREAK == *( lineBreakInfoBuffer + index ) )
361 ++numberOfParagraphs;
365 Vector<BidirectionalParagraphInfoRun>& bidirectionalInfo = mLogicalModel->mBidirectionalParagraphInfo;
366 bidirectionalInfo.Reserve( numberOfParagraphs );
368 // Calculates the bidirectional info for the whole paragraph if it contains right to left scripts.
369 SetBidirectionalInfo( utf32Characters,
374 if( 0u != bidirectionalInfo.Count() )
376 // This paragraph has right to left text. Some characters may need to be mirrored.
377 // TODO: consider if the mirrored string can be stored as well.
379 textMirrored = GetMirroredText( utf32Characters,
380 mirroredUtf32Characters,
383 // Only set the character directions if there is right to left characters.
384 Vector<CharacterDirection>& directions = mLogicalModel->mCharacterDirections;
385 directions.Resize( numberOfCharacters );
387 GetCharactersDirection( bidirectionalInfo,
392 // There is no right to left characters. Clear the directions vector.
393 mLogicalModel->mCharacterDirections.Clear();
397 Vector<GlyphInfo>& glyphs = mVisualModel->mGlyphs;
398 Vector<CharacterIndex>& glyphsToCharactersMap = mVisualModel->mGlyphsToCharacters;
399 Vector<Length>& charactersPerGlyph = mVisualModel->mCharactersPerGlyph;
400 Vector<GlyphIndex> newParagraphGlyphs;
401 newParagraphGlyphs.Reserve( numberOfParagraphs );
403 if( SHAPE_TEXT & operations )
405 const Vector<Character>& textToShape = textMirrored ? mirroredUtf32Characters : utf32Characters;
407 ShapeText( textToShape,
412 glyphsToCharactersMap,
414 newParagraphGlyphs );
416 // Create the 'number of glyphs' per character and the glyph to character conversion tables.
417 mVisualModel->CreateGlyphsPerCharacterTable( numberOfCharacters );
418 mVisualModel->CreateCharacterToGlyphTable( numberOfCharacters );
421 const Length numberOfGlyphs = glyphs.Count();
423 if( GET_GLYPH_METRICS & operations )
425 GlyphInfo* glyphsBuffer = glyphs.Begin();
426 mFontClient.GetGlyphMetrics( glyphsBuffer, numberOfGlyphs );
428 // Update the width and advance of all new paragraph characters.
429 for( Vector<GlyphIndex>::ConstIterator it = newParagraphGlyphs.Begin(), endIt = newParagraphGlyphs.End(); it != endIt; ++it )
431 const GlyphIndex index = *it;
432 GlyphInfo& glyph = *( glyphsBuffer + index );
434 glyph.xBearing = 0.f;
441 mEventData->mPreEditFlag &&
442 ( 0u != mVisualModel->mCharactersToGlyph.Count() ) )
444 // Add the underline for the pre-edit text.
445 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
446 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
448 const GlyphIndex glyphStart = *( charactersToGlyphBuffer + mEventData->mPreEditStartPosition );
449 const CharacterIndex lastPreEditCharacter = mEventData->mPreEditStartPosition + ( ( mEventData->mPreEditLength > 0u ) ? mEventData->mPreEditLength - 1u : 0u );
450 const Length numberOfGlyphsLastCharacter = *( glyphsPerCharacterBuffer + lastPreEditCharacter );
451 const GlyphIndex glyphEnd = *( charactersToGlyphBuffer + lastPreEditCharacter ) + ( numberOfGlyphsLastCharacter > 1u ? numberOfGlyphsLastCharacter - 1u : 0u );
453 GlyphRun underlineRun;
454 underlineRun.glyphIndex = glyphStart;
455 underlineRun.numberOfGlyphs = 1u + glyphEnd - glyphStart;
457 // TODO: At the moment the underline runs are only for pre-edit.
458 mVisualModel->mUnderlineRuns.PushBack( underlineRun );
462 void Controller::Impl::GetDefaultFonts( Vector<FontRun>& fonts, Length numberOfCharacters )
467 fontRun.characterRun.characterIndex = 0;
468 fontRun.characterRun.numberOfCharacters = numberOfCharacters;
469 fontRun.fontId = mFontDefaults->GetFontId( mFontClient );
470 fontRun.isDefault = true;
472 fonts.PushBack( fontRun );
476 float Controller::Impl::GetDefaultFontLineHeight()
478 FontId defaultFontId = 0u;
479 if( NULL == mFontDefaults )
481 defaultFontId = mFontClient.GetFontId( EMPTY_STRING,
486 defaultFontId = mFontDefaults->GetFontId( mFontClient );
489 Text::FontMetrics fontMetrics;
490 mFontClient.GetFontMetrics( defaultFontId, fontMetrics );
492 return( fontMetrics.ascender - fontMetrics.descender );
495 void Controller::Impl::OnCursorKeyEvent( const Event& event )
497 if( NULL == mEventData )
499 // Nothing to do if there is no text input.
503 int keyCode = event.p1.mInt;
505 if( Dali::DALI_KEY_CURSOR_LEFT == keyCode )
507 if( mEventData->mPrimaryCursorPosition > 0u )
509 mEventData->mPrimaryCursorPosition = CalculateNewCursorIndex( mEventData->mPrimaryCursorPosition - 1u );
512 else if( Dali::DALI_KEY_CURSOR_RIGHT == keyCode )
514 if( mLogicalModel->mText.Count() > mEventData->mPrimaryCursorPosition )
516 mEventData->mPrimaryCursorPosition = CalculateNewCursorIndex( mEventData->mPrimaryCursorPosition );
519 else if( Dali::DALI_KEY_CURSOR_UP == keyCode )
523 else if( Dali::DALI_KEY_CURSOR_DOWN == keyCode )
528 mEventData->mUpdateCursorPosition = true;
529 mEventData->mScrollAfterUpdatePosition = true;
532 void Controller::Impl::OnTapEvent( const Event& event )
534 if( NULL != mEventData )
536 const unsigned int tapCount = event.p1.mUint;
540 if( ! IsShowingPlaceholderText() )
542 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
543 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
545 mEventData->mPrimaryCursorPosition = GetClosestCursorIndex( xPosition,
550 mEventData->mPrimaryCursorPosition = 0u;
553 mEventData->mUpdateCursorPosition = true;
554 mEventData->mScrollAfterUpdatePosition = true;
559 void Controller::Impl::OnPanEvent( const Event& event )
561 if( NULL == mEventData )
563 // Nothing to do if there is no text input.
567 int state = event.p1.mInt;
569 if( Gesture::Started == state ||
570 Gesture::Continuing == state )
572 const Vector2& actualSize = mVisualModel->GetActualSize();
573 const Vector2 currentScroll = mEventData->mScrollPosition;
575 if( mEventData->mHorizontalScrollingEnabled )
577 const float displacementX = event.p2.mFloat;
578 mEventData->mScrollPosition.x += displacementX;
580 ClampHorizontalScroll( actualSize );
583 if( mEventData->mVerticalScrollingEnabled )
585 const float displacementY = event.p3.mFloat;
586 mEventData->mScrollPosition.y += displacementY;
588 ClampVerticalScroll( actualSize );
591 if( mEventData->mDecorator )
593 mEventData->mDecorator->UpdatePositions( mEventData->mScrollPosition - currentScroll );
598 void Controller::Impl::OnLongPressEvent( const Event& event )
600 if ( EventData::EDITING == mEventData->mState )
602 ChangeState ( EventData::EDITING_WITH_POPUP );
603 mEventData->mDecoratorUpdated = true;
607 void Controller::Impl::OnHandleEvent( const Event& event )
609 if( NULL == mEventData )
611 // Nothing to do if there is no text input.
615 const unsigned int state = event.p1.mUint;
616 const bool handleStopScrolling = ( HANDLE_STOP_SCROLLING == state );
618 if( HANDLE_PRESSED == state )
620 // The event.p2 and event.p3 are in decorator coords. Need to transforms to text coords.
621 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
622 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
624 const CharacterIndex handleNewPosition = GetClosestCursorIndex( xPosition, yPosition );
626 if( Event::GRAB_HANDLE_EVENT == event.type )
628 ChangeState ( EventData::GRAB_HANDLE_PANNING );
630 if( handleNewPosition != mEventData->mPrimaryCursorPosition )
632 mEventData->mPrimaryCursorPosition = handleNewPosition;
633 mEventData->mUpdateCursorPosition = true;
636 else if( Event::LEFT_SELECTION_HANDLE_EVENT == event.type )
638 ChangeState ( EventData::SELECTION_HANDLE_PANNING );
640 if( ( handleNewPosition != mEventData->mLeftSelectionPosition ) &&
641 ( handleNewPosition != mEventData->mRightSelectionPosition ) )
643 mEventData->mLeftSelectionPosition = handleNewPosition;
645 RepositionSelectionHandles( mEventData->mLeftSelectionPosition,
646 mEventData->mRightSelectionPosition );
648 mEventData->mUpdateLeftSelectionPosition = true;
651 else if( Event::RIGHT_SELECTION_HANDLE_EVENT == event.type )
653 ChangeState ( EventData::SELECTION_HANDLE_PANNING );
655 if( ( handleNewPosition != mEventData->mRightSelectionPosition ) &&
656 ( handleNewPosition != mEventData->mLeftSelectionPosition ) )
658 mEventData->mRightSelectionPosition = handleNewPosition;
660 RepositionSelectionHandles( mEventData->mLeftSelectionPosition,
661 mEventData->mRightSelectionPosition );
663 mEventData->mUpdateRightSelectionPosition = true;
666 } // end ( HANDLE_PRESSED == state )
667 else if( ( HANDLE_RELEASED == state ) ||
668 handleStopScrolling )
670 CharacterIndex handlePosition = 0u;
671 if( handleStopScrolling )
673 // The event.p2 and event.p3 are in decorator coords. Need to transforms to text coords.
674 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
675 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
677 handlePosition = GetClosestCursorIndex( xPosition, yPosition );
680 if( Event::GRAB_HANDLE_EVENT == event.type )
682 mEventData->mUpdateCursorPosition = true;
684 ChangeState( EventData::EDITING_WITH_POPUP );
686 if( handleStopScrolling )
688 mEventData->mScrollAfterUpdatePosition = mEventData->mPrimaryCursorPosition != handlePosition;
689 mEventData->mPrimaryCursorPosition = handlePosition;
692 else if( Event::LEFT_SELECTION_HANDLE_EVENT == event.type )
694 ChangeState( EventData::SELECTING );
696 if( handleStopScrolling )
698 mEventData->mUpdateLeftSelectionPosition = ( mEventData->mRightSelectionPosition != handlePosition );
699 mEventData->mScrollAfterUpdatePosition = mEventData->mUpdateLeftSelectionPosition;
701 if( mEventData->mUpdateLeftSelectionPosition )
703 mEventData->mLeftSelectionPosition = handlePosition;
705 RepositionSelectionHandles( mEventData->mLeftSelectionPosition,
706 mEventData->mRightSelectionPosition );
710 else if( Event::RIGHT_SELECTION_HANDLE_EVENT == event.type )
712 ChangeState( EventData::SELECTING );
714 if( handleStopScrolling )
716 mEventData->mUpdateRightSelectionPosition = ( mEventData->mLeftSelectionPosition != handlePosition );
717 mEventData->mScrollAfterUpdatePosition = mEventData->mUpdateRightSelectionPosition;
718 if( mEventData->mUpdateRightSelectionPosition )
720 mEventData->mRightSelectionPosition = handlePosition;
721 RepositionSelectionHandles( mEventData->mLeftSelectionPosition,
722 mEventData->mRightSelectionPosition );
727 mEventData->mDecoratorUpdated = true;
728 } // end ( ( HANDLE_RELEASED == state ) || ( HANDLE_STOP_SCROLLING == state ) )
729 else if( HANDLE_SCROLLING == state )
731 const float xSpeed = event.p2.mFloat;
732 const Vector2& actualSize = mVisualModel->GetActualSize();
733 const Vector2 currentScrollPosition = mEventData->mScrollPosition;
735 mEventData->mScrollPosition.x += xSpeed;
737 ClampHorizontalScroll( actualSize );
739 bool endOfScroll = false;
740 if( Vector2::ZERO == ( currentScrollPosition - mEventData->mScrollPosition ) )
742 // Notify the decorator there is no more text to scroll.
743 // The decorator won't send more scroll events.
744 mEventData->mDecorator->NotifyEndOfScroll();
745 // Still need to set the position of the handle.
749 // Set the position of the handle.
750 const bool scrollRightDirection = xSpeed > 0.f;
751 const bool leftSelectionHandleEvent = Event::LEFT_SELECTION_HANDLE_EVENT == event.type;
752 const bool rightSelectionHandleEvent = Event::RIGHT_SELECTION_HANDLE_EVENT == event.type;
754 if( Event::GRAB_HANDLE_EVENT == event.type )
756 ChangeState( EventData::GRAB_HANDLE_PANNING );
758 Vector2 position = mEventData->mDecorator->GetPosition( GRAB_HANDLE );
760 // Position the grag handle close to either the left or right edge.
761 position.x = scrollRightDirection ? 0.f : mVisualModel->mControlSize.width;
763 // Get the new handle position.
764 // The grab handle's position is in decorator coords. Need to transforms to text coords.
765 const CharacterIndex handlePosition = GetClosestCursorIndex( position.x - mEventData->mScrollPosition.x - mAlignmentOffset.x,
766 position.y - mEventData->mScrollPosition.y - mAlignmentOffset.y );
768 mEventData->mUpdateCursorPosition = mEventData->mPrimaryCursorPosition != handlePosition;
769 mEventData->mScrollAfterUpdatePosition = mEventData->mUpdateCursorPosition;
770 mEventData->mPrimaryCursorPosition = handlePosition;
772 else if( leftSelectionHandleEvent || rightSelectionHandleEvent )
774 // TODO: This is recalculating the selection box every time the text is scrolled with the selection handles.
775 // Think if something can be done to save power.
777 ChangeState( EventData::SELECTION_HANDLE_PANNING );
779 Vector2 position = mEventData->mDecorator->GetPosition( leftSelectionHandleEvent ? Text::LEFT_SELECTION_HANDLE : Text::RIGHT_SELECTION_HANDLE );
781 // Position the selection handle close to either the left or right edge.
782 position.x = scrollRightDirection ? 0.f : mVisualModel->mControlSize.width;
784 // Get the new handle position.
785 // The selection handle's position is in decorator coords. Need to transforms to text coords.
786 const CharacterIndex handlePosition = GetClosestCursorIndex( position.x - mEventData->mScrollPosition.x - mAlignmentOffset.x,
787 position.y - mEventData->mScrollPosition.y - mAlignmentOffset.y );
789 if( leftSelectionHandleEvent )
791 const bool differentHandles = ( mEventData->mLeftSelectionPosition != handlePosition ) && ( mEventData->mRightSelectionPosition != handlePosition );
792 mEventData->mUpdateLeftSelectionPosition = endOfScroll || differentHandles;
793 if( differentHandles )
795 mEventData->mLeftSelectionPosition = handlePosition;
800 const bool differentHandles = ( mEventData->mRightSelectionPosition != handlePosition ) && ( mEventData->mLeftSelectionPosition != handlePosition );
801 mEventData->mUpdateRightSelectionPosition = endOfScroll || differentHandles;
802 if( differentHandles )
804 mEventData->mRightSelectionPosition = handlePosition;
808 if( mEventData->mUpdateLeftSelectionPosition || mEventData->mUpdateRightSelectionPosition )
810 RepositionSelectionHandles( mEventData->mLeftSelectionPosition,
811 mEventData->mRightSelectionPosition );
813 mEventData->mScrollAfterUpdatePosition = true;
816 mEventData->mDecoratorUpdated = true;
817 } // end ( HANDLE_SCROLLING == state )
820 void Controller::Impl::OnSelectEvent( const Event& event )
822 if( NULL == mEventData )
824 // Nothing to do if there is no text.
828 if( mEventData->mSelectionEnabled )
830 // The event.p2 and event.p3 are in decorator coords. Need to transforms to text coords.
831 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
832 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
834 const CharacterIndex leftPosition = mEventData->mLeftSelectionPosition;
835 const CharacterIndex rightPosition = mEventData->mRightSelectionPosition;
837 RepositionSelectionHandles( xPosition,
840 mEventData->mUpdateLeftSelectionPosition = leftPosition != mEventData->mLeftSelectionPosition;
841 mEventData->mUpdateRightSelectionPosition = rightPosition != mEventData->mRightSelectionPosition;
843 mEventData->mScrollAfterUpdatePosition = ( ( mEventData->mUpdateLeftSelectionPosition || mEventData->mUpdateRightSelectionPosition ) &&
844 ( mEventData->mLeftSelectionPosition != mEventData->mRightSelectionPosition ) );
848 void Controller::Impl::OnSelectAllEvent()
850 if( NULL == mEventData )
852 // Nothing to do if there is no text.
856 if( mEventData->mSelectionEnabled )
858 RepositionSelectionHandles( 0u,
859 mLogicalModel->mText.Count() );
861 mEventData->mScrollAfterUpdatePosition = true;
862 mEventData->mUpdateLeftSelectionPosition = true;
863 mEventData->mUpdateRightSelectionPosition = true;
867 void Controller::Impl::RetrieveSelection( std::string& selectedText, bool deleteAfterRetreival )
869 if( mEventData->mLeftSelectionPosition == mEventData->mRightSelectionPosition )
871 // Nothing to select if handles are in the same place.
876 //Get start and end position of selection
877 uint32_t startOfSelectedText = mEventData->mLeftSelectionPosition;
878 uint32_t lengthOfSelectedText = mEventData->mRightSelectionPosition - startOfSelectedText;
880 // Validate the start and end selection points
881 if( ( startOfSelectedText >= 0 ) && ( ( startOfSelectedText + lengthOfSelectedText ) <= mLogicalModel->mText.Count() ) )
883 //Get text as a UTF8 string
884 Vector<Character>& utf32Characters = mLogicalModel->mText;
886 Utf32ToUtf8( &utf32Characters[startOfSelectedText], lengthOfSelectedText, selectedText );
888 if ( deleteAfterRetreival ) // Only delete text if copied successfully
890 // Delete text between handles
891 Vector<Character>& currentText = mLogicalModel->mText;
893 Vector<Character>::Iterator first = currentText.Begin() + startOfSelectedText;
894 Vector<Character>::Iterator last = first + lengthOfSelectedText;
895 currentText.Erase( first, last );
897 mEventData->mPrimaryCursorPosition = mEventData->mLeftSelectionPosition;
898 mEventData->mScrollAfterDelete = true;
899 mEventData->mDecoratorUpdated = true;
903 void Controller::Impl::ShowClipboard()
907 mClipboard.ShowClipboard();
911 void Controller::Impl::HideClipboard()
915 mClipboard.HideClipboard();
919 bool Controller::Impl::CopyStringToClipboard( std::string& source )
921 //Send string to clipboard
922 return ( mClipboard && mClipboard.SetItem( source ) );
925 void Controller::Impl::SendSelectionToClipboard( bool deleteAfterSending )
927 std::string selectedText;
928 RetrieveSelection( selectedText, deleteAfterSending );
929 CopyStringToClipboard( selectedText );
930 ChangeState( EventData::EDITING );
933 void Controller::Impl::GetTextFromClipboard( unsigned int itemIndex, std::string& retreivedString )
937 retreivedString = mClipboard.GetItem( itemIndex );
941 void Controller::Impl::RepositionSelectionHandles( CharacterIndex selectionStart, CharacterIndex selectionEnd )
943 if( selectionStart == selectionEnd )
945 // Nothing to select if handles are in the same place.
949 mEventData->mDecorator->ClearHighlights();
951 mEventData->mLeftSelectionPosition = selectionStart;
952 mEventData->mRightSelectionPosition = selectionEnd;
954 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
955 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
956 const GlyphInfo* const glyphsBuffer = mVisualModel->mGlyphs.Begin();
957 const Vector2* const positionsBuffer = mVisualModel->mGlyphPositions.Begin();
958 const Length* const charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
959 const CharacterIndex* const glyphToCharacterBuffer = mVisualModel->mGlyphsToCharacters.Begin();
960 const CharacterDirection* const modelCharacterDirectionsBuffer = ( 0u != mLogicalModel->mCharacterDirections.Count() ) ? mLogicalModel->mCharacterDirections.Begin() : NULL;
962 // TODO: Better algorithm to create the highlight box.
965 // Get the height of the line.
966 const Vector<LineRun>& lines = mVisualModel->mLines;
967 const LineRun& firstLine = *lines.Begin();
968 const float height = firstLine.ascender + -firstLine.descender;
970 // Swap the indices if the start is greater than the end.
971 const bool indicesSwapped = ( selectionStart > selectionEnd );
974 std::swap( selectionStart, selectionEnd );
977 // Get the indices to the first and last selected glyphs.
978 const CharacterIndex selectionEndMinusOne = selectionEnd - 1u;
979 const GlyphIndex glyphStart = *( charactersToGlyphBuffer + selectionStart );
980 const Length numberOfGlyphs = *( glyphsPerCharacterBuffer + selectionEndMinusOne );
981 const GlyphIndex glyphEnd = *( charactersToGlyphBuffer + selectionEndMinusOne ) + ( ( numberOfGlyphs > 0 ) ? numberOfGlyphs - 1u : 0u );
983 // Check if the first glyph is a ligature that must be broken like Latin ff, fi, or Arabic ï»», etc which needs special code.
984 const Length numberOfCharactersStart = *( charactersPerGlyphBuffer + glyphStart );
985 bool splitStartGlyph = ( numberOfCharactersStart > 1u ) && HasLigatureMustBreak( mLogicalModel->GetScript( selectionStart ) );
987 // Check if the last glyph is a ligature that must be broken like Latin ff, fi, or Arabic ï»», etc which needs special code.
988 const Length numberOfCharactersEnd = *( charactersPerGlyphBuffer + glyphEnd );
989 bool splitEndGlyph = ( glyphStart != glyphEnd ) && ( numberOfCharactersEnd > 1u ) && HasLigatureMustBreak( mLogicalModel->GetScript( selectionEndMinusOne ) );
991 // Tell the decorator to swap the selection handles if needed.
992 mEventData->mDecorator->SwapSelectionHandlesEnabled( firstLine.direction != indicesSwapped );
994 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
996 // Traverse the glyphs.
997 for( GlyphIndex index = glyphStart; index <= glyphEnd; ++index )
999 const GlyphInfo& glyph = *( glyphsBuffer + index );
1000 const Vector2& position = *( positionsBuffer + index );
1002 if( splitStartGlyph )
1004 // If the first glyph is a ligature that must be broken it may be needed to add only part of the glyph to the highlight box.
1006 const float glyphAdvance = glyph.advance / static_cast<float>( numberOfCharactersStart );
1007 const CharacterIndex interGlyphIndex = selectionStart - *( glyphToCharacterBuffer + glyphStart );
1008 // Get the direction of the character.
1009 CharacterDirection isCurrentRightToLeft = false;
1010 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
1012 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + selectionStart );
1015 // The end point could be in the middle of the ligature.
1016 // Calculate the number of characters selected.
1017 const Length numberOfCharacters = ( glyphStart == glyphEnd ) ? ( selectionEnd - selectionStart ) : ( numberOfCharactersStart - interGlyphIndex );
1019 const float xPosition = position.x - glyph.xBearing + offset.x + glyphAdvance * static_cast<float>( isCurrentRightToLeft ? ( numberOfCharactersStart - interGlyphIndex - numberOfCharacters ) : interGlyphIndex );
1021 mEventData->mDecorator->AddHighlight( xPosition,
1023 xPosition + static_cast<float>( numberOfCharacters ) * glyphAdvance,
1024 offset.y + height );
1026 splitStartGlyph = false;
1030 if( splitEndGlyph && ( index == glyphEnd ) )
1032 // Equally, if the last glyph is a ligature that must be broken it may be needed to add only part of the glyph to the highlight box.
1034 const float glyphAdvance = glyph.advance / static_cast<float>( numberOfCharactersEnd );
1035 const CharacterIndex interGlyphIndex = selectionEnd - *( glyphToCharacterBuffer + glyphEnd );
1036 // Get the direction of the character.
1037 CharacterDirection isCurrentRightToLeft = false;
1038 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
1040 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + selectionEnd );
1043 const Length numberOfCharacters = numberOfCharactersEnd - interGlyphIndex;
1045 const float xPosition = position.x - glyph.xBearing + offset.x + ( isCurrentRightToLeft ? ( glyphAdvance * static_cast<float>( numberOfCharacters ) ) : 0.f );
1046 mEventData->mDecorator->AddHighlight( xPosition,
1048 xPosition + static_cast<float>( interGlyphIndex ) * glyphAdvance,
1049 offset.y + height );
1051 splitEndGlyph = false;
1055 const float xPosition = position.x - glyph.xBearing + offset.x;
1056 mEventData->mDecorator->AddHighlight( xPosition,
1058 xPosition + glyph.advance,
1059 offset.y + height );
1062 CursorInfo primaryCursorInfo;
1063 GetCursorPosition( mEventData->mLeftSelectionPosition,
1064 primaryCursorInfo );
1066 CursorInfo secondaryCursorInfo;
1067 GetCursorPosition( mEventData->mRightSelectionPosition,
1068 secondaryCursorInfo );
1070 const Vector2 primaryPosition = primaryCursorInfo.primaryPosition + offset;
1071 const Vector2 secondaryPosition = secondaryCursorInfo.primaryPosition + offset;
1073 mEventData->mDecorator->SetPosition( LEFT_SELECTION_HANDLE, primaryPosition.x, primaryPosition.y, primaryCursorInfo.lineHeight );
1075 mEventData->mDecorator->SetPosition( RIGHT_SELECTION_HANDLE, secondaryPosition.x, secondaryPosition.y, secondaryCursorInfo.lineHeight );
1077 // Cursor to be positioned at end of selection so if selection interrupted and edit mode restarted the cursor will be at end of selection
1078 mEventData->mPrimaryCursorPosition = (indicesSwapped)?mEventData->mLeftSelectionPosition:mEventData->mRightSelectionPosition;
1080 // Set the flag to update the decorator.
1081 mEventData->mDecoratorUpdated = true;
1084 void Controller::Impl::RepositionSelectionHandles( float visualX, float visualY )
1086 if( NULL == mEventData )
1088 // Nothing to do if there is no text input.
1092 if( IsShowingPlaceholderText() )
1094 // Nothing to do if there is the place-holder text.
1098 const Length numberOfGlyphs = mVisualModel->mGlyphs.Count();
1099 const Length numberOfLines = mVisualModel->mLines.Count();
1100 if( 0 == numberOfGlyphs ||
1101 0 == numberOfLines )
1103 // Nothing to do if there is no text.
1107 // Find which word was selected
1108 CharacterIndex selectionStart( 0 );
1109 CharacterIndex selectionEnd( 0 );
1110 FindSelectionIndices( visualX, visualY, selectionStart, selectionEnd );
1111 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "%p selectionStart %d selectionEnd %d\n", this, selectionStart, selectionEnd );
1113 if( selectionStart == selectionEnd )
1115 ChangeState( EventData::EDITING );
1116 // Nothing to select. i.e. a white space, out of bounds
1120 RepositionSelectionHandles( selectionStart, selectionEnd );
1123 void Controller::Impl::SetPopupButtons()
1126 * Sets the Popup buttons to be shown depending on State.
1128 * If SELECTING : CUT & COPY + ( PASTE & CLIPBOARD if content available to paste )
1130 * If EDITING_WITH_POPUP : SELECT & SELECT_ALL
1133 TextSelectionPopup::Buttons buttonsToShow = TextSelectionPopup::NONE;
1135 if ( ( EventData::SELECTING == mEventData->mState ) || ( EventData::SELECTION_CHANGED == mEventData->mState ) )
1137 buttonsToShow = TextSelectionPopup::Buttons( TextSelectionPopup::CUT | TextSelectionPopup::COPY );
1139 if ( !IsClipboardEmpty() )
1141 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
1142 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::CLIPBOARD ) );
1145 if ( !mEventData->mAllTextSelected )
1147 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::SELECT_ALL ) );
1150 else if ( EventData::EDITING_WITH_POPUP == mEventData->mState )
1152 if ( mLogicalModel->mText.Count() && !IsShowingPlaceholderText())
1154 buttonsToShow = TextSelectionPopup::Buttons( TextSelectionPopup::SELECT | TextSelectionPopup::SELECT_ALL );
1157 if ( !IsClipboardEmpty() )
1159 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
1160 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::CLIPBOARD ) );
1164 mEventData->mDecorator->SetEnabledPopupButtons( buttonsToShow );
1167 void Controller::Impl::ChangeState( EventData::State newState )
1169 if( NULL == mEventData )
1171 // Nothing to do if there is no text input.
1175 if( mEventData->mState != newState )
1177 mEventData->mState = newState;
1179 if( EventData::INACTIVE == mEventData->mState )
1181 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
1182 mEventData->mDecorator->StopCursorBlink();
1183 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1184 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1185 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1186 mEventData->mDecorator->SetPopupActive( false );
1187 mEventData->mDecoratorUpdated = true;
1190 else if ( EventData::INTERRUPTED == mEventData->mState)
1192 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1193 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1194 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1195 mEventData->mDecorator->SetPopupActive( false );
1196 mEventData->mDecoratorUpdated = true;
1199 else if ( EventData::SELECTING == mEventData->mState )
1201 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
1202 mEventData->mDecorator->StopCursorBlink();
1203 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1204 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true );
1205 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true );
1206 if( mEventData->mGrabHandlePopupEnabled )
1209 mEventData->mDecorator->SetPopupActive( true );
1211 mEventData->mDecoratorUpdated = true;
1213 else if ( EventData::SELECTION_CHANGED == mEventData->mState )
1215 if( mEventData->mGrabHandlePopupEnabled )
1218 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
1219 mEventData->mDecorator->SetPopupActive( true );
1221 mEventData->mDecoratorUpdated = true;
1223 else if( EventData::EDITING == mEventData->mState )
1225 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1226 if( mEventData->mCursorBlinkEnabled )
1228 mEventData->mDecorator->StartCursorBlink();
1230 // Grab handle is not shown until a tap is received whilst EDITING
1231 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1232 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1233 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1234 if( mEventData->mGrabHandlePopupEnabled )
1236 mEventData->mDecorator->SetPopupActive( false );
1238 mEventData->mDecoratorUpdated = true;
1241 else if( EventData::EDITING_WITH_POPUP == mEventData->mState )
1243 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1244 if( mEventData->mCursorBlinkEnabled )
1246 mEventData->mDecorator->StartCursorBlink();
1248 if( mEventData->mSelectionEnabled )
1250 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1251 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1255 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
1257 if( mEventData->mGrabHandlePopupEnabled )
1260 mEventData->mDecorator->SetPopupActive( true );
1263 mEventData->mDecoratorUpdated = true;
1265 else if( EventData::EDITING_WITH_GRAB_HANDLE == mEventData->mState )
1267 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1268 if( mEventData->mCursorBlinkEnabled )
1270 mEventData->mDecorator->StartCursorBlink();
1272 // Grab handle is not shown until a tap is received whilst EDITING
1273 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
1274 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1275 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1276 if( mEventData->mGrabHandlePopupEnabled )
1278 mEventData->mDecorator->SetPopupActive( false );
1280 mEventData->mDecoratorUpdated = true;
1283 else if ( EventData::SELECTION_HANDLE_PANNING == mEventData->mState )
1285 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
1286 mEventData->mDecorator->StopCursorBlink();
1287 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1288 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true );
1289 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true );
1290 if( mEventData->mGrabHandlePopupEnabled )
1292 mEventData->mDecorator->SetPopupActive( false );
1294 mEventData->mDecoratorUpdated = true;
1296 else if ( EventData::GRAB_HANDLE_PANNING == mEventData->mState )
1298 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1299 if( mEventData->mCursorBlinkEnabled )
1301 mEventData->mDecorator->StartCursorBlink();
1303 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
1304 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1305 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1306 if( mEventData->mGrabHandlePopupEnabled )
1308 mEventData->mDecorator->SetPopupActive( false );
1310 mEventData->mDecoratorUpdated = true;
1315 LineIndex Controller::Impl::GetClosestLine( float y ) const
1317 float totalHeight = 0.f;
1318 LineIndex lineIndex = 0u;
1320 const Vector<LineRun>& lines = mVisualModel->mLines;
1321 for( LineIndex endLine = lines.Count();
1322 lineIndex < endLine;
1325 const LineRun& lineRun = lines[lineIndex];
1326 totalHeight += lineRun.ascender + -lineRun.descender;
1327 if( y < totalHeight )
1333 if( lineIndex == 0 )
1341 void Controller::Impl::FindSelectionIndices( float visualX, float visualY, CharacterIndex& startIndex, CharacterIndex& endIndex )
1343 CharacterIndex hitCharacter = GetClosestCursorIndex( visualX, visualY );
1344 DALI_ASSERT_DEBUG( hitCharacter <= mLogicalModel->mText.Count() && "GetClosestCursorIndex returned out of bounds index" );
1346 if ( mLogicalModel->mText.Count() == 0 )
1348 return; // if model empty
1351 if( hitCharacter >= mLogicalModel->mText.Count() )
1353 // Closest hit character is the last character.
1354 if ( hitCharacter == mLogicalModel->mText.Count() )
1356 hitCharacter--; //Hit character index set to last character in logical model
1360 // hitCharacter is out of bounds
1365 startIndex = hitCharacter;
1366 endIndex = hitCharacter;
1368 if( !TextAbstraction::IsWhiteSpace( mLogicalModel->mText[hitCharacter] ) )
1370 // Find the start and end of the text
1371 for( startIndex = hitCharacter; startIndex > 0; --startIndex )
1373 Character charCode = mLogicalModel->mText[ startIndex-1 ];
1374 if( TextAbstraction::IsWhiteSpace( charCode ) )
1379 const CharacterIndex pastTheEnd = mLogicalModel->mText.Count();
1380 for( endIndex = hitCharacter + 1u; endIndex < pastTheEnd; ++endIndex )
1382 Character charCode = mLogicalModel->mText[ endIndex ];
1383 if( TextAbstraction::IsWhiteSpace( charCode ) )
1391 CharacterIndex Controller::Impl::GetClosestCursorIndex( float visualX,
1394 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "GetClosestCursorIndex %p closest visualX %f visualY %f\n", this, visualX, visualY );
1396 if( NULL == mEventData )
1398 // Nothing to do if there is no text input.
1402 CharacterIndex logicalIndex = 0u;
1404 const Length numberOfGlyphs = mVisualModel->mGlyphs.Count();
1405 const Length numberOfLines = mVisualModel->mLines.Count();
1406 if( 0 == numberOfGlyphs ||
1407 0 == numberOfLines )
1409 return logicalIndex;
1412 // Find which line is closest
1413 const LineIndex lineIndex = GetClosestLine( visualY );
1414 const LineRun& line = mVisualModel->mLines[lineIndex];
1416 // Get the positions of the glyphs.
1417 const Vector<Vector2>& positions = mVisualModel->mGlyphPositions;
1418 const Vector2* const positionsBuffer = positions.Begin();
1420 // Get the visual to logical conversion tables.
1421 const CharacterIndex* const visualToLogicalBuffer = ( 0u != mLogicalModel->mVisualToLogicalMap.Count() ) ? mLogicalModel->mVisualToLogicalMap.Begin() : NULL;
1422 const CharacterIndex* const visualToLogicalCursorBuffer = mLogicalModel->mVisualToLogicalCursorMap.Begin();
1424 // Get the character to glyph conversion table.
1425 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
1427 // Get the glyphs per character table.
1428 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
1429 const Length* const charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
1431 // If the vector is void, there is no right to left characters.
1432 const bool hasRightToLeftCharacters = NULL != visualToLogicalBuffer;
1434 const CharacterIndex startCharacter = line.characterRun.characterIndex;
1435 const CharacterIndex endCharacter = line.characterRun.characterIndex + line.characterRun.numberOfCharacters;
1436 DALI_ASSERT_DEBUG( endCharacter <= mLogicalModel->mText.Count() && "Invalid line info" );
1438 // Whether there is a hit on a glyph.
1439 bool matched = false;
1441 // Traverses glyphs in visual order. To do that use the visual to logical conversion table.
1442 CharacterIndex visualIndex = startCharacter;
1443 for( ; !matched && ( visualIndex < endCharacter ); ++visualIndex )
1445 // The character in logical order.
1446 const CharacterIndex characterLogicalOrderIndex = hasRightToLeftCharacters ? *( visualToLogicalBuffer + visualIndex ) : visualIndex;
1448 // Get the script of the character.
1449 const Script script = mLogicalModel->GetScript( characterLogicalOrderIndex );
1451 // The first glyph for that character in logical order.
1452 const GlyphIndex glyphLogicalOrderIndex = *( charactersToGlyphBuffer + characterLogicalOrderIndex );
1453 // The number of glyphs for that character
1454 const Length numberOfGlyphs = *( glyphsPerCharacterBuffer + characterLogicalOrderIndex );
1456 // Get the metrics for the group of glyphs.
1457 GlyphMetrics glyphMetrics;
1458 GetGlyphsMetrics( glyphLogicalOrderIndex,
1464 const Vector2& position = *( positionsBuffer + glyphLogicalOrderIndex );
1466 // Prevents to jump the whole Latin ligatures like fi, ff, or Arabic ï»»...
1467 const Length numberOfCharactersInLigature = HasLigatureMustBreak( script ) ? *( charactersPerGlyphBuffer + glyphLogicalOrderIndex ) : 1u;
1468 const float glyphAdvance = glyphMetrics.advance / static_cast<float>( numberOfCharactersInLigature );
1470 for( GlyphIndex index = 0u; !matched && ( index < numberOfCharactersInLigature ); ++index )
1472 // Find the mid-point of the area containing the glyph
1473 const float glyphCenter = -glyphMetrics.xBearing + position.x + ( static_cast<float>( index ) + 0.5f ) * glyphAdvance;
1475 if( visualX < glyphCenter )
1477 visualIndex += index;
1489 // Return the logical position of the cursor in characters.
1493 visualIndex = endCharacter;
1496 logicalIndex = hasRightToLeftCharacters ? *( visualToLogicalCursorBuffer + visualIndex ) : visualIndex;
1497 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "%p closest visualIndex %d logicalIndex %d\n", this, visualIndex, logicalIndex );
1499 DALI_ASSERT_DEBUG( ( logicalIndex <= mLogicalModel->mText.Count() && logicalIndex >= 0 ) && "GetClosestCursorIndex - Out of bounds index" );
1501 return logicalIndex;
1504 void Controller::Impl::GetCursorPosition( CharacterIndex logical,
1505 CursorInfo& cursorInfo )
1507 // TODO: Check for multiline with \n, etc...
1509 // Check if the logical position is the first or the last one of the text.
1510 const bool isFirstPosition = 0u == logical;
1511 const bool isLastPosition = mLogicalModel->mText.Count() == logical;
1513 if( isFirstPosition && isLastPosition )
1515 // There is zero characters. Get the default font's line height.
1516 cursorInfo.lineHeight = GetDefaultFontLineHeight();
1517 cursorInfo.primaryCursorHeight = cursorInfo.lineHeight;
1519 cursorInfo.primaryPosition.x = mEventData->mDecorator->GetCursorWidth();
1520 cursorInfo.primaryPosition.y = 0.f;
1522 // Nothing else to do.
1526 // 'logical' is the logical 'cursor' index.
1527 // Get the next and current logical 'character' index.
1528 const CharacterIndex nextCharacterIndex = logical;
1529 const CharacterIndex characterIndex = isFirstPosition ? logical : logical - 1u;
1531 // Get the direction of the character and the next one.
1532 const CharacterDirection* const modelCharacterDirectionsBuffer = ( 0u != mLogicalModel->mCharacterDirections.Count() ) ? mLogicalModel->mCharacterDirections.Begin() : NULL;
1534 CharacterDirection isCurrentRightToLeft = false;
1535 CharacterDirection isNextRightToLeft = false;
1536 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
1538 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + characterIndex );
1539 isNextRightToLeft = *( modelCharacterDirectionsBuffer + nextCharacterIndex );
1542 // Get the line where the character is laid-out.
1543 const LineRun* const modelLines = mVisualModel->mLines.Begin();
1545 const LineIndex lineIndex = mVisualModel->GetLineOfCharacter( characterIndex );
1546 const LineRun& line = *( modelLines + lineIndex );
1548 // Get the paragraph's direction.
1549 const CharacterDirection isRightToLeftParagraph = line.direction;
1551 // Check whether there is an alternative position:
1553 cursorInfo.isSecondaryCursor = ( !isLastPosition && ( isCurrentRightToLeft != isNextRightToLeft ) ) ||
1554 ( isLastPosition && ( isRightToLeftParagraph != isCurrentRightToLeft ) );
1556 // Set the line height.
1557 cursorInfo.lineHeight = line.ascender + -line.descender;
1559 // Calculate the primary cursor.
1561 CharacterIndex index = characterIndex;
1562 if( cursorInfo.isSecondaryCursor )
1564 // If there is a secondary position, the primary cursor may be in a different place than the logical index.
1566 if( isLastPosition )
1568 // The position of the cursor after the last character needs special
1569 // care depending on its direction and the direction of the paragraph.
1571 // Need to find the first character after the last character with the paragraph's direction.
1572 // i.e l0 l1 l2 r0 r1 should find r0.
1574 // TODO: check for more than one line!
1575 index = isRightToLeftParagraph ? line.characterRun.characterIndex : line.characterRun.characterIndex + line.characterRun.numberOfCharacters - 1u;
1576 index = mLogicalModel->GetLogicalCharacterIndex( index );
1580 index = ( isRightToLeftParagraph == isCurrentRightToLeft ) ? characterIndex : nextCharacterIndex;
1584 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
1585 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
1586 const Length* const charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
1587 const CharacterIndex* const glyphsToCharactersBuffer = mVisualModel->mGlyphsToCharacters.Begin();
1588 const Vector2* const glyphPositionsBuffer = mVisualModel->mGlyphPositions.Begin();
1590 // Convert the cursor position into the glyph position.
1591 const GlyphIndex primaryGlyphIndex = *( charactersToGlyphBuffer + index );
1592 const Length primaryNumberOfGlyphs = *( glyphsPerCharacterBuffer + index );
1593 const Length primaryNumberOfCharacters = *( charactersPerGlyphBuffer + primaryGlyphIndex );
1595 // Get the metrics for the group of glyphs.
1596 GlyphMetrics glyphMetrics;
1597 GetGlyphsMetrics( primaryGlyphIndex,
1598 primaryNumberOfGlyphs,
1603 // Whether to add the glyph's advance to the cursor position.
1604 // i.e if the paragraph is left to right and the logical cursor is zero, the position is the position of the first glyph and the advance is not added,
1605 // if the logical cursor is one, the position is the position of the first glyph and the advance is added.
1606 // A 'truth table' was build and an online Karnaugh map tool was used to simplify the logic.
1627 // Where F -> isFirstPosition
1628 // L -> isLastPosition
1629 // C -> isCurrentRightToLeft
1630 // P -> isRightToLeftParagraph
1631 // A -> Whether to add the glyph's advance.
1633 const bool addGlyphAdvance = ( ( isLastPosition && !isRightToLeftParagraph ) ||
1634 ( isFirstPosition && isRightToLeftParagraph ) ||
1635 ( !isFirstPosition && !isLastPosition && !isCurrentRightToLeft ) );
1637 float glyphAdvance = addGlyphAdvance ? glyphMetrics.advance : 0.f;
1639 if( !isLastPosition &&
1640 ( primaryNumberOfCharacters > 1u ) )
1642 const CharacterIndex firstIndex = *( glyphsToCharactersBuffer + primaryGlyphIndex );
1644 bool isCurrentRightToLeft = false;
1645 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
1647 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + index );
1650 Length numberOfGlyphAdvance = ( isFirstPosition ? 0u : 1u ) + characterIndex - firstIndex;
1651 if( isCurrentRightToLeft )
1653 numberOfGlyphAdvance = primaryNumberOfCharacters - numberOfGlyphAdvance;
1656 glyphAdvance = static_cast<float>( numberOfGlyphAdvance ) * glyphMetrics.advance / static_cast<float>( primaryNumberOfCharacters );
1659 // Get the glyph position and x bearing.
1660 const Vector2& primaryPosition = *( glyphPositionsBuffer + primaryGlyphIndex );
1662 // Set the primary cursor's height.
1663 cursorInfo.primaryCursorHeight = cursorInfo.isSecondaryCursor ? 0.5f * glyphMetrics.fontHeight : glyphMetrics.fontHeight;
1665 // Set the primary cursor's position.
1666 cursorInfo.primaryPosition.x = -glyphMetrics.xBearing + primaryPosition.x + glyphAdvance;
1667 cursorInfo.primaryPosition.y = line.ascender - glyphMetrics.ascender;
1669 // Calculate the secondary cursor.
1671 if( cursorInfo.isSecondaryCursor )
1673 // Set the secondary cursor's height.
1674 cursorInfo.secondaryCursorHeight = 0.5f * glyphMetrics.fontHeight;
1676 CharacterIndex index = characterIndex;
1677 if( !isLastPosition )
1679 index = ( isRightToLeftParagraph == isCurrentRightToLeft ) ? nextCharacterIndex : characterIndex;
1682 const GlyphIndex secondaryGlyphIndex = *( charactersToGlyphBuffer + index );
1683 const Length secondaryNumberOfGlyphs = *( glyphsPerCharacterBuffer + index );
1685 const Vector2& secondaryPosition = *( glyphPositionsBuffer + secondaryGlyphIndex );
1687 GetGlyphsMetrics( secondaryGlyphIndex,
1688 secondaryNumberOfGlyphs,
1693 // Set the secondary cursor's position.
1694 cursorInfo.secondaryPosition.x = -glyphMetrics.xBearing + secondaryPosition.x + ( isCurrentRightToLeft ? 0.f : glyphMetrics.advance );
1695 cursorInfo.secondaryPosition.y = cursorInfo.lineHeight - cursorInfo.secondaryCursorHeight - line.descender - ( glyphMetrics.fontHeight - glyphMetrics.ascender );
1699 CharacterIndex Controller::Impl::CalculateNewCursorIndex( CharacterIndex index ) const
1701 if( NULL == mEventData )
1703 // Nothing to do if there is no text input.
1707 CharacterIndex cursorIndex = mEventData->mPrimaryCursorPosition;
1709 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
1710 const Length* const charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
1712 GlyphIndex glyphIndex = *( charactersToGlyphBuffer + index );
1713 Length numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
1715 if( numberOfCharacters > 1u )
1717 const Script script = mLogicalModel->GetScript( index );
1718 if( HasLigatureMustBreak( script ) )
1720 // Prevents to jump the whole Latin ligatures like fi, ff, or Arabic ï»», ...
1721 numberOfCharacters = 1u;
1726 while( 0u == numberOfCharacters )
1729 numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
1733 if( index < mEventData->mPrimaryCursorPosition )
1735 cursorIndex -= numberOfCharacters;
1739 cursorIndex += numberOfCharacters;
1745 void Controller::Impl::UpdateCursorPosition()
1747 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "-->Controller::UpdateCursorPosition %p\n", this );
1748 if( NULL == mEventData )
1750 // Nothing to do if there is no text input.
1751 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition no event data\n" );
1755 if( IsShowingPlaceholderText() || ( 0u == mLogicalModel->mText.Count() ) )
1757 // Do not want to use the place-holder text to set the cursor position.
1759 // Use the line's height of the font's family set to set the cursor's size.
1760 // If there is no font's family set, use the default font.
1761 // Use the current alignment to place the cursor at the beginning, center or end of the box.
1763 float lineHeight = 0.f;
1765 FontId defaultFontId = 0u;
1766 if( NULL == mFontDefaults )
1768 defaultFontId = mFontClient.GetFontId( EMPTY_STRING,
1773 defaultFontId = mFontDefaults->GetFontId( mFontClient );
1776 Text::FontMetrics fontMetrics;
1777 mFontClient.GetFontMetrics( defaultFontId, fontMetrics );
1779 lineHeight = fontMetrics.ascender - fontMetrics.descender;
1782 Vector2 cursorPosition;
1784 switch( mLayoutEngine.GetHorizontalAlignment() )
1786 case LayoutEngine::HORIZONTAL_ALIGN_BEGIN:
1788 cursorPosition.x = mEventData->mDecorator->GetCursorWidth();
1791 case LayoutEngine::HORIZONTAL_ALIGN_CENTER:
1793 cursorPosition.x = floor( 0.5f * mVisualModel->mControlSize.width );
1796 case LayoutEngine::HORIZONTAL_ALIGN_END:
1798 cursorPosition.x = mVisualModel->mControlSize.width;
1803 switch( mLayoutEngine.GetVerticalAlignment() )
1805 case LayoutEngine::VERTICAL_ALIGN_TOP:
1807 cursorPosition.y = 0.f;
1810 case LayoutEngine::VERTICAL_ALIGN_CENTER:
1812 cursorPosition.y = floorf( 0.5f * ( mVisualModel->mControlSize.height - lineHeight ) );
1815 case LayoutEngine::VERTICAL_ALIGN_BOTTOM:
1817 cursorPosition.y = mVisualModel->mControlSize.height - lineHeight;
1822 mEventData->mDecorator->SetPosition( PRIMARY_CURSOR,
1830 CursorInfo cursorInfo;
1831 GetCursorPosition( mEventData->mPrimaryCursorPosition,
1834 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
1835 const Vector2 cursorPosition = cursorInfo.primaryPosition + offset;
1837 // Sets the cursor position.
1838 mEventData->mDecorator->SetPosition( PRIMARY_CURSOR,
1841 cursorInfo.primaryCursorHeight,
1842 cursorInfo.lineHeight );
1843 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Primary cursor position: %f,%f\n", cursorPosition.x, cursorPosition.y );
1845 // Sets the grab handle position.
1846 mEventData->mDecorator->SetPosition( GRAB_HANDLE,
1849 cursorInfo.lineHeight );
1851 if( cursorInfo.isSecondaryCursor )
1853 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_BOTH );
1854 mEventData->mDecorator->SetPosition( SECONDARY_CURSOR,
1855 cursorInfo.secondaryPosition.x + offset.x,
1856 cursorInfo.secondaryPosition.y + offset.y,
1857 cursorInfo.secondaryCursorHeight,
1858 cursorInfo.lineHeight );
1859 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Secondary cursor position: %f,%f\n", cursorInfo.secondaryPosition.x + offset.x, cursorInfo.secondaryPosition.y + offset.y );
1863 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1866 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition\n" );
1869 void Controller::Impl::UpdateSelectionHandle( HandleType handleType )
1871 if( ( LEFT_SELECTION_HANDLE != handleType ) &&
1872 ( RIGHT_SELECTION_HANDLE != handleType ) )
1877 const bool leftSelectionHandle = LEFT_SELECTION_HANDLE == handleType;
1878 const CharacterIndex index = leftSelectionHandle ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition;
1880 CursorInfo cursorInfo;
1881 GetCursorPosition( index,
1884 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
1885 const Vector2 cursorPosition = cursorInfo.primaryPosition + offset;
1887 // Sets the grab handle position.
1888 mEventData->mDecorator->SetPosition( handleType,
1891 cursorInfo.lineHeight );
1893 // If selection handle at start of the text and other at end of the text then all text is selected.
1894 const CharacterIndex startOfSelection = std::min( mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition );
1895 const CharacterIndex endOfSelection = std::max ( mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition );
1896 mEventData->mAllTextSelected = ( startOfSelection == 0 ) && ( endOfSelection == mLogicalModel->mText.Count() );
1899 void Controller::Impl::ClampHorizontalScroll( const Vector2& actualSize )
1901 // Clamp between -space & 0 (and the text alignment).
1902 if( actualSize.width > mVisualModel->mControlSize.width )
1904 const float space = ( actualSize.width - mVisualModel->mControlSize.width ) + mAlignmentOffset.x;
1905 mEventData->mScrollPosition.x = ( mEventData->mScrollPosition.x < -space ) ? -space : mEventData->mScrollPosition.x;
1906 mEventData->mScrollPosition.x = ( mEventData->mScrollPosition.x > -mAlignmentOffset.x ) ? -mAlignmentOffset.x : mEventData->mScrollPosition.x;
1908 mEventData->mDecoratorUpdated = true;
1912 mEventData->mScrollPosition.x = 0.f;
1916 void Controller::Impl::ClampVerticalScroll( const Vector2& actualSize )
1918 // Clamp between -space & 0 (and the text alignment).
1919 if( actualSize.height > mVisualModel->mControlSize.height )
1921 const float space = ( actualSize.height - mVisualModel->mControlSize.height ) + mAlignmentOffset.y;
1922 mEventData->mScrollPosition.y = ( mEventData->mScrollPosition.y < -space ) ? -space : mEventData->mScrollPosition.y;
1923 mEventData->mScrollPosition.y = ( mEventData->mScrollPosition.y > -mAlignmentOffset.y ) ? -mAlignmentOffset.y : mEventData->mScrollPosition.y;
1925 mEventData->mDecoratorUpdated = true;
1929 mEventData->mScrollPosition.y = 0.f;
1933 void Controller::Impl::ScrollToMakePositionVisible( const Vector2& position )
1936 bool updateDecorator = false;
1937 if( position.x < 0.f )
1939 offset.x = -position.x;
1940 mEventData->mScrollPosition.x += offset.x;
1941 updateDecorator = true;
1943 else if( position.x > mVisualModel->mControlSize.width )
1945 offset.x = mVisualModel->mControlSize.width - position.x;
1946 mEventData->mScrollPosition.x += offset.x;
1947 updateDecorator = true;
1950 if( updateDecorator && mEventData->mDecorator )
1952 mEventData->mDecorator->UpdatePositions( offset );
1955 // TODO : calculate the vertical scroll.
1958 void Controller::Impl::ScrollTextToMatchCursor()
1960 // Get the current cursor position in decorator coords.
1961 const Vector2& currentCursorPosition = mEventData->mDecorator->GetPosition( PRIMARY_CURSOR );
1963 // Calculate the new cursor position.
1964 CursorInfo cursorInfo;
1965 GetCursorPosition( mEventData->mPrimaryCursorPosition,
1968 // Calculate the offset to match the cursor position before the character was deleted.
1969 mEventData->mScrollPosition.x = currentCursorPosition.x - cursorInfo.primaryPosition.x - mAlignmentOffset.x;
1971 ClampHorizontalScroll( mVisualModel->GetActualSize() );
1973 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
1974 const Vector2 cursorPosition = cursorInfo.primaryPosition + offset;
1976 // Sets the cursor position.
1977 mEventData->mDecorator->SetPosition( PRIMARY_CURSOR,
1980 cursorInfo.primaryCursorHeight,
1981 cursorInfo.lineHeight );
1983 // Sets the grab handle position.
1984 mEventData->mDecorator->SetPosition( GRAB_HANDLE,
1987 cursorInfo.lineHeight );
1989 if( cursorInfo.isSecondaryCursor )
1991 mEventData->mDecorator->SetPosition( SECONDARY_CURSOR,
1992 cursorInfo.secondaryPosition.x + offset.x,
1993 cursorInfo.secondaryPosition.y + offset.y,
1994 cursorInfo.secondaryCursorHeight,
1995 cursorInfo.lineHeight );
1996 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Secondary cursor position: %f,%f\n", cursorInfo.secondaryPosition.x + offset.x, cursorInfo.secondaryPosition.y + offset.y );
1999 // Set which cursors are active according the state.
2000 if( ( EventData::EDITING == mEventData->mState ) ||
2001 ( EventData::EDITING_WITH_POPUP == mEventData->mState ) ||
2002 ( EventData::EDITING_WITH_GRAB_HANDLE == mEventData->mState ) ||
2003 ( EventData::GRAB_HANDLE_PANNING == mEventData->mState ) )
2005 if( cursorInfo.isSecondaryCursor )
2007 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_BOTH );
2011 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
2016 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
2020 void Controller::Impl::RequestRelayout()
2022 mControlInterface.RequestTextRelayout();
2027 } // namespace Toolkit