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.
73 * @brief Get some glyph's metrics of a group of glyphs formed as a result of shaping one character.
75 * @param[in] glyphIndex The index to the first glyph.
76 * @param[in] numberOfGlyphs The number of glyphs.
77 * @param[out] glyphMetrics Some glyph metrics (font height, advance, ascender and x bearing).
78 * @param[in] visualModel The visual model.
79 * @param[in] metrics Used to access metrics from FontClient.
81 void GetGlyphsMetrics( GlyphIndex glyphIndex,
82 Length numberOfGlyphs,
83 GlyphMetrics& glyphMetrics,
84 VisualModelPtr& visualModel,
87 const GlyphInfo* glyphsBuffer = visualModel->mGlyphs.Begin();
89 const GlyphInfo& firstGlyph = *( glyphsBuffer + glyphIndex );
91 Text::FontMetrics fontMetrics;
92 metrics->GetFontMetrics( firstGlyph.fontId, fontMetrics );
94 glyphMetrics.fontHeight = fontMetrics.height;
95 glyphMetrics.advance = firstGlyph.advance;
96 glyphMetrics.ascender = fontMetrics.ascender;
97 glyphMetrics.xBearing = firstGlyph.xBearing;
99 for( unsigned int i = 1u; i < numberOfGlyphs; ++i )
101 const GlyphInfo& glyphInfo = *( glyphsBuffer + glyphIndex + i );
103 glyphMetrics.advance += glyphInfo.advance;
107 EventData::EventData( DecoratorPtr decorator )
108 : mDecorator( decorator ),
109 mPlaceholderTextActive(),
110 mPlaceholderTextInactive(),
111 mPlaceholderTextColor( 0.8f, 0.8f, 0.8f, 0.8f ),
115 mPrimaryCursorPosition( 0u ),
116 mLeftSelectionPosition( 0u ),
117 mRightSelectionPosition( 0u ),
118 mPreEditStartPosition( 0u ),
119 mPreEditLength( 0u ),
120 mIsShowingPlaceholderText( false ),
121 mPreEditFlag( false ),
122 mDecoratorUpdated( false ),
123 mCursorBlinkEnabled( true ),
124 mGrabHandleEnabled( true ),
125 mGrabHandlePopupEnabled( true ),
126 mSelectionEnabled( true ),
127 mHorizontalScrollingEnabled( true ),
128 mVerticalScrollingEnabled( false ),
129 mUpdateCursorPosition( false ),
130 mUpdateLeftSelectionPosition( false ),
131 mUpdateRightSelectionPosition( false ),
132 mScrollAfterUpdatePosition( false ),
133 mScrollAfterDelete( false ),
134 mAllTextSelected( false )
137 EventData::~EventData()
140 bool Controller::Impl::ProcessInputEvents()
142 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "-->Controller::ProcessInputEvents\n" );
143 if( NULL == mEventData )
145 // Nothing to do if there is no text input.
146 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::ProcessInputEvents no event data\n" );
150 if( mEventData->mDecorator )
152 for( std::vector<Event>::iterator iter = mEventData->mEventQueue.begin();
153 iter != mEventData->mEventQueue.end();
158 case Event::CURSOR_KEY_EVENT:
160 OnCursorKeyEvent( *iter );
163 case Event::TAP_EVENT:
168 case Event::LONG_PRESS_EVENT:
170 OnLongPressEvent( *iter );
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 );
187 OnSelectEvent( *iter );
190 case Event::SELECT_ALL:
199 // The cursor must also be repositioned after inserts into the model
200 if( mEventData->mUpdateCursorPosition )
202 // Updates the cursor position and scrolls the text to make it visible.
204 UpdateCursorPosition();
206 if( mEventData->mScrollAfterUpdatePosition )
208 const Vector2& primaryCursorPosition = mEventData->mDecorator->GetPosition( PRIMARY_CURSOR );
210 ScrollToMakePositionVisible( primaryCursorPosition );
211 mEventData->mScrollAfterUpdatePosition = false;
214 mEventData->mDecoratorUpdated = true;
215 mEventData->mUpdateCursorPosition = false;
217 else if( mEventData->mScrollAfterDelete )
219 ScrollTextToMatchCursor();
220 mEventData->mDecoratorUpdated = true;
221 mEventData->mScrollAfterDelete = false;
225 bool leftScroll = false;
226 bool rightScroll = false;
228 if( mEventData->mUpdateLeftSelectionPosition )
230 UpdateSelectionHandle( LEFT_SELECTION_HANDLE );
232 if( mEventData->mScrollAfterUpdatePosition )
234 const Vector2& leftHandlePosition = mEventData->mDecorator->GetPosition( LEFT_SELECTION_HANDLE );
236 ScrollToMakePositionVisible( leftHandlePosition );
241 mEventData->mDecoratorUpdated = true;
242 mEventData->mUpdateLeftSelectionPosition = false;
245 if( mEventData->mUpdateRightSelectionPosition )
247 UpdateSelectionHandle( RIGHT_SELECTION_HANDLE );
249 if( mEventData->mScrollAfterUpdatePosition )
251 const Vector2& rightHandlePosition = mEventData->mDecorator->GetPosition( RIGHT_SELECTION_HANDLE );
253 ScrollToMakePositionVisible( rightHandlePosition );
258 mEventData->mDecoratorUpdated = true;
259 mEventData->mUpdateRightSelectionPosition = false;
262 if( leftScroll || rightScroll )
264 mEventData->mScrollAfterUpdatePosition = false;
268 mEventData->mEventQueue.clear();
270 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::ProcessInputEvents\n" );
272 const bool decoratorUpdated = mEventData->mDecoratorUpdated;
273 mEventData->mDecoratorUpdated = false;
275 return decoratorUpdated;
278 void Controller::Impl::UpdateModel( OperationsMask operationsRequired )
280 // Calculate the operations to be done.
281 const OperationsMask operations = static_cast<OperationsMask>( mOperationsPending & operationsRequired );
283 Vector<Character>& utf32Characters = mLogicalModel->mText;
285 const Length numberOfCharacters = utf32Characters.Count();
287 Vector<LineBreakInfo>& lineBreakInfo = mLogicalModel->mLineBreakInfo;
288 if( GET_LINE_BREAKS & operations )
290 // Retrieves the line break info. The line break info is used to split the text in 'paragraphs' to
291 // calculate the bidirectional info for each 'paragraph'.
292 // It's also used to layout the text (where it should be a new line) or to shape the text (text in different lines
293 // is not shaped together).
294 lineBreakInfo.Resize( numberOfCharacters, TextAbstraction::LINE_NO_BREAK );
296 SetLineBreakInfo( utf32Characters,
300 Vector<WordBreakInfo>& wordBreakInfo = mLogicalModel->mWordBreakInfo;
301 if( GET_WORD_BREAKS & operations )
303 // Retrieves the word break info. The word break info is used to layout the text (where to wrap the text in lines).
304 wordBreakInfo.Resize( numberOfCharacters, TextAbstraction::WORD_NO_BREAK );
306 SetWordBreakInfo( utf32Characters,
310 const bool getScripts = GET_SCRIPTS & operations;
311 const bool validateFonts = VALIDATE_FONTS & operations;
313 Vector<ScriptRun>& scripts = mLogicalModel->mScriptRuns;
314 Vector<FontRun>& validFonts = mLogicalModel->mFontRuns;
316 if( getScripts || validateFonts )
318 // Validates the fonts assigned by the application or assigns default ones.
319 // It makes sure all the characters are going to be rendered by the correct font.
320 MultilanguageSupport multilanguageSupport = MultilanguageSupport::Get();
324 // Retrieves the scripts used in the text.
325 multilanguageSupport.SetScripts( utf32Characters,
331 if( 0u == validFonts.Count() )
333 // Copy the requested font defaults received via the property system.
334 // These may not be valid i.e. may not contain glyphs for the necessary scripts.
335 GetDefaultFonts( validFonts, numberOfCharacters );
338 // Validates the fonts. If there is a character with no assigned font it sets a default one.
339 // After this call, fonts are validated.
340 multilanguageSupport.ValidateFonts( utf32Characters,
346 Vector<Character> mirroredUtf32Characters;
347 bool textMirrored = false;
348 Length numberOfParagraphs = 0u;
349 if( BIDI_INFO & operations )
351 // Count the number of LINE_NO_BREAK to reserve some space for the vector of paragraph's
352 // bidirectional info.
354 const TextAbstraction::LineBreakInfo* lineBreakInfoBuffer = lineBreakInfo.Begin();
355 for( Length index = 0u; index < numberOfCharacters; ++index )
357 if( TextAbstraction::LINE_NO_BREAK == *( lineBreakInfoBuffer + index ) )
359 ++numberOfParagraphs;
363 Vector<BidirectionalParagraphInfoRun>& bidirectionalInfo = mLogicalModel->mBidirectionalParagraphInfo;
364 bidirectionalInfo.Reserve( numberOfParagraphs );
366 // Calculates the bidirectional info for the whole paragraph if it contains right to left scripts.
367 SetBidirectionalInfo( utf32Characters,
372 if( 0u != bidirectionalInfo.Count() )
374 // This paragraph has right to left text. Some characters may need to be mirrored.
375 // TODO: consider if the mirrored string can be stored as well.
377 textMirrored = GetMirroredText( utf32Characters,
378 mirroredUtf32Characters,
381 // Only set the character directions if there is right to left characters.
382 Vector<CharacterDirection>& directions = mLogicalModel->mCharacterDirections;
383 directions.Resize( numberOfCharacters );
385 GetCharactersDirection( bidirectionalInfo,
390 // There is no right to left characters. Clear the directions vector.
391 mLogicalModel->mCharacterDirections.Clear();
395 Vector<GlyphInfo>& glyphs = mVisualModel->mGlyphs;
396 Vector<CharacterIndex>& glyphsToCharactersMap = mVisualModel->mGlyphsToCharacters;
397 Vector<Length>& charactersPerGlyph = mVisualModel->mCharactersPerGlyph;
398 Vector<GlyphIndex> newParagraphGlyphs;
399 newParagraphGlyphs.Reserve( numberOfParagraphs );
401 if( SHAPE_TEXT & operations )
403 const Vector<Character>& textToShape = textMirrored ? mirroredUtf32Characters : utf32Characters;
405 ShapeText( textToShape,
410 glyphsToCharactersMap,
412 newParagraphGlyphs );
414 // Create the 'number of glyphs' per character and the glyph to character conversion tables.
415 mVisualModel->CreateGlyphsPerCharacterTable( numberOfCharacters );
416 mVisualModel->CreateCharacterToGlyphTable( numberOfCharacters );
419 const Length numberOfGlyphs = glyphs.Count();
421 if( GET_GLYPH_METRICS & operations )
423 GlyphInfo* glyphsBuffer = glyphs.Begin();
424 mMetrics->GetGlyphMetrics( glyphsBuffer, numberOfGlyphs );
426 // Update the width and advance of all new paragraph characters.
427 for( Vector<GlyphIndex>::ConstIterator it = newParagraphGlyphs.Begin(), endIt = newParagraphGlyphs.End(); it != endIt; ++it )
429 const GlyphIndex index = *it;
430 GlyphInfo& glyph = *( glyphsBuffer + index );
432 glyph.xBearing = 0.f;
439 mEventData->mPreEditFlag &&
440 ( 0u != mVisualModel->mCharactersToGlyph.Count() ) )
442 // Add the underline for the pre-edit text.
443 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
444 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
446 const GlyphIndex glyphStart = *( charactersToGlyphBuffer + mEventData->mPreEditStartPosition );
447 const CharacterIndex lastPreEditCharacter = mEventData->mPreEditStartPosition + ( ( mEventData->mPreEditLength > 0u ) ? mEventData->mPreEditLength - 1u : 0u );
448 const Length numberOfGlyphsLastCharacter = *( glyphsPerCharacterBuffer + lastPreEditCharacter );
449 const GlyphIndex glyphEnd = *( charactersToGlyphBuffer + lastPreEditCharacter ) + ( numberOfGlyphsLastCharacter > 1u ? numberOfGlyphsLastCharacter - 1u : 0u );
451 GlyphRun underlineRun;
452 underlineRun.glyphIndex = glyphStart;
453 underlineRun.numberOfGlyphs = 1u + glyphEnd - glyphStart;
455 // TODO: At the moment the underline runs are only for pre-edit.
456 mVisualModel->mUnderlineRuns.PushBack( underlineRun );
460 void Controller::Impl::GetDefaultFonts( Vector<FontRun>& fonts, Length numberOfCharacters )
465 fontRun.characterRun.characterIndex = 0;
466 fontRun.characterRun.numberOfCharacters = numberOfCharacters;
467 fontRun.fontId = mFontDefaults->GetFontId( mFontClient );
468 fontRun.isDefault = true;
470 fonts.PushBack( fontRun );
474 float Controller::Impl::GetDefaultFontLineHeight()
476 FontId defaultFontId = 0u;
477 if( NULL == mFontDefaults )
479 TextAbstraction::FontDescription fontDescription;
480 defaultFontId = mFontClient.GetFontId( fontDescription );
484 defaultFontId = mFontDefaults->GetFontId( mFontClient );
487 Text::FontMetrics fontMetrics;
488 mMetrics->GetFontMetrics( defaultFontId, fontMetrics );
490 return( fontMetrics.ascender - fontMetrics.descender );
493 void Controller::Impl::OnCursorKeyEvent( const Event& event )
495 if( NULL == mEventData )
497 // Nothing to do if there is no text input.
501 int keyCode = event.p1.mInt;
503 if( Dali::DALI_KEY_CURSOR_LEFT == keyCode )
505 if( mEventData->mPrimaryCursorPosition > 0u )
507 mEventData->mPrimaryCursorPosition = CalculateNewCursorIndex( mEventData->mPrimaryCursorPosition - 1u );
510 else if( Dali::DALI_KEY_CURSOR_RIGHT == keyCode )
512 if( mLogicalModel->mText.Count() > mEventData->mPrimaryCursorPosition )
514 mEventData->mPrimaryCursorPosition = CalculateNewCursorIndex( mEventData->mPrimaryCursorPosition );
517 else if( Dali::DALI_KEY_CURSOR_UP == keyCode )
521 else if( Dali::DALI_KEY_CURSOR_DOWN == keyCode )
526 mEventData->mUpdateCursorPosition = true;
527 mEventData->mScrollAfterUpdatePosition = true;
530 void Controller::Impl::OnTapEvent( const Event& event )
532 if( NULL != mEventData )
534 const unsigned int tapCount = event.p1.mUint;
538 if( IsShowingRealText() )
540 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
541 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
543 mEventData->mPrimaryCursorPosition = GetClosestCursorIndex( xPosition,
546 // When the cursor position is changing, delay cursor blinking
547 mEventData->mDecorator->DelayCursorBlink();
551 mEventData->mPrimaryCursorPosition = 0u;
554 mEventData->mUpdateCursorPosition = true;
555 mEventData->mScrollAfterUpdatePosition = true;
560 void Controller::Impl::OnPanEvent( const Event& event )
562 if( NULL == mEventData )
564 // Nothing to do if there is no text input.
568 int state = event.p1.mInt;
570 if( Gesture::Started == state ||
571 Gesture::Continuing == state )
573 const Vector2& actualSize = mVisualModel->GetActualSize();
574 const Vector2 currentScroll = mEventData->mScrollPosition;
576 if( mEventData->mHorizontalScrollingEnabled )
578 const float displacementX = event.p2.mFloat;
579 mEventData->mScrollPosition.x += displacementX;
581 ClampHorizontalScroll( actualSize );
584 if( mEventData->mVerticalScrollingEnabled )
586 const float displacementY = event.p3.mFloat;
587 mEventData->mScrollPosition.y += displacementY;
589 ClampVerticalScroll( actualSize );
592 if( mEventData->mDecorator )
594 mEventData->mDecorator->UpdatePositions( mEventData->mScrollPosition - currentScroll );
599 void Controller::Impl::OnLongPressEvent( const Event& event )
601 if ( EventData::EDITING == mEventData->mState )
603 ChangeState ( EventData::EDITING_WITH_POPUP );
604 mEventData->mDecoratorUpdated = true;
608 void Controller::Impl::OnHandleEvent( const Event& event )
610 if( NULL == mEventData )
612 // Nothing to do if there is no text input.
616 const unsigned int state = event.p1.mUint;
617 const bool handleStopScrolling = ( HANDLE_STOP_SCROLLING == state );
619 if( HANDLE_PRESSED == state )
621 // The event.p2 and event.p3 are in decorator coords. Need to transforms to text coords.
622 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
623 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
625 const CharacterIndex handleNewPosition = GetClosestCursorIndex( xPosition, yPosition );
627 if( Event::GRAB_HANDLE_EVENT == event.type )
629 ChangeState ( EventData::GRAB_HANDLE_PANNING );
631 if( handleNewPosition != mEventData->mPrimaryCursorPosition )
633 mEventData->mPrimaryCursorPosition = handleNewPosition;
634 mEventData->mUpdateCursorPosition = true;
637 else if( Event::LEFT_SELECTION_HANDLE_EVENT == event.type )
639 ChangeState ( EventData::SELECTION_HANDLE_PANNING );
641 if( ( handleNewPosition != mEventData->mLeftSelectionPosition ) &&
642 ( handleNewPosition != mEventData->mRightSelectionPosition ) )
644 mEventData->mLeftSelectionPosition = handleNewPosition;
646 RepositionSelectionHandles( mEventData->mLeftSelectionPosition,
647 mEventData->mRightSelectionPosition );
649 mEventData->mUpdateLeftSelectionPosition = true;
652 else if( Event::RIGHT_SELECTION_HANDLE_EVENT == event.type )
654 ChangeState ( EventData::SELECTION_HANDLE_PANNING );
656 if( ( handleNewPosition != mEventData->mRightSelectionPosition ) &&
657 ( handleNewPosition != mEventData->mLeftSelectionPosition ) )
659 mEventData->mRightSelectionPosition = handleNewPosition;
661 RepositionSelectionHandles( mEventData->mLeftSelectionPosition,
662 mEventData->mRightSelectionPosition );
664 mEventData->mUpdateRightSelectionPosition = true;
667 } // end ( HANDLE_PRESSED == state )
668 else if( ( HANDLE_RELEASED == state ) ||
669 handleStopScrolling )
671 CharacterIndex handlePosition = 0u;
672 if( handleStopScrolling )
674 // The event.p2 and event.p3 are in decorator coords. Need to transforms to text coords.
675 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
676 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
678 handlePosition = GetClosestCursorIndex( xPosition, yPosition );
681 if( Event::GRAB_HANDLE_EVENT == event.type )
683 mEventData->mUpdateCursorPosition = true;
685 ChangeState( EventData::EDITING_WITH_POPUP );
687 if( handleStopScrolling )
689 mEventData->mScrollAfterUpdatePosition = mEventData->mPrimaryCursorPosition != handlePosition;
690 mEventData->mPrimaryCursorPosition = handlePosition;
693 else if( Event::LEFT_SELECTION_HANDLE_EVENT == event.type )
695 ChangeState( EventData::SELECTING );
697 if( handleStopScrolling )
699 mEventData->mUpdateLeftSelectionPosition = ( mEventData->mRightSelectionPosition != handlePosition );
700 mEventData->mScrollAfterUpdatePosition = mEventData->mUpdateLeftSelectionPosition;
702 if( mEventData->mUpdateLeftSelectionPosition )
704 mEventData->mLeftSelectionPosition = handlePosition;
706 RepositionSelectionHandles( mEventData->mLeftSelectionPosition,
707 mEventData->mRightSelectionPosition );
711 else if( Event::RIGHT_SELECTION_HANDLE_EVENT == event.type )
713 ChangeState( EventData::SELECTING );
715 if( handleStopScrolling )
717 mEventData->mUpdateRightSelectionPosition = ( mEventData->mLeftSelectionPosition != handlePosition );
718 mEventData->mScrollAfterUpdatePosition = mEventData->mUpdateRightSelectionPosition;
719 if( mEventData->mUpdateRightSelectionPosition )
721 mEventData->mRightSelectionPosition = handlePosition;
722 RepositionSelectionHandles( mEventData->mLeftSelectionPosition,
723 mEventData->mRightSelectionPosition );
728 mEventData->mDecoratorUpdated = true;
729 } // end ( ( HANDLE_RELEASED == state ) || ( HANDLE_STOP_SCROLLING == state ) )
730 else if( HANDLE_SCROLLING == state )
732 const float xSpeed = event.p2.mFloat;
733 const Vector2& actualSize = mVisualModel->GetActualSize();
734 const Vector2 currentScrollPosition = mEventData->mScrollPosition;
736 mEventData->mScrollPosition.x += xSpeed;
738 ClampHorizontalScroll( actualSize );
740 bool endOfScroll = false;
741 if( Vector2::ZERO == ( currentScrollPosition - mEventData->mScrollPosition ) )
743 // Notify the decorator there is no more text to scroll.
744 // The decorator won't send more scroll events.
745 mEventData->mDecorator->NotifyEndOfScroll();
746 // Still need to set the position of the handle.
750 // Set the position of the handle.
751 const bool scrollRightDirection = xSpeed > 0.f;
752 const bool leftSelectionHandleEvent = Event::LEFT_SELECTION_HANDLE_EVENT == event.type;
753 const bool rightSelectionHandleEvent = Event::RIGHT_SELECTION_HANDLE_EVENT == event.type;
755 if( Event::GRAB_HANDLE_EVENT == event.type )
757 ChangeState( EventData::GRAB_HANDLE_PANNING );
759 Vector2 position = mEventData->mDecorator->GetPosition( GRAB_HANDLE );
761 // Position the grag handle close to either the left or right edge.
762 position.x = scrollRightDirection ? 0.f : mVisualModel->mControlSize.width;
764 // Get the new handle position.
765 // The grab handle's position is in decorator coords. Need to transforms to text coords.
766 const CharacterIndex handlePosition = GetClosestCursorIndex( position.x - mEventData->mScrollPosition.x - mAlignmentOffset.x,
767 position.y - mEventData->mScrollPosition.y - mAlignmentOffset.y );
769 mEventData->mUpdateCursorPosition = mEventData->mPrimaryCursorPosition != handlePosition;
770 mEventData->mScrollAfterUpdatePosition = mEventData->mUpdateCursorPosition;
771 mEventData->mPrimaryCursorPosition = handlePosition;
773 else if( leftSelectionHandleEvent || rightSelectionHandleEvent )
775 // TODO: This is recalculating the selection box every time the text is scrolled with the selection handles.
776 // Think if something can be done to save power.
778 ChangeState( EventData::SELECTION_HANDLE_PANNING );
780 Vector2 position = mEventData->mDecorator->GetPosition( leftSelectionHandleEvent ? Text::LEFT_SELECTION_HANDLE : Text::RIGHT_SELECTION_HANDLE );
782 // Position the selection handle close to either the left or right edge.
783 position.x = scrollRightDirection ? 0.f : mVisualModel->mControlSize.width;
785 // Get the new handle position.
786 // The selection handle's position is in decorator coords. Need to transforms to text coords.
787 const CharacterIndex handlePosition = GetClosestCursorIndex( position.x - mEventData->mScrollPosition.x - mAlignmentOffset.x,
788 position.y - mEventData->mScrollPosition.y - mAlignmentOffset.y );
790 if( leftSelectionHandleEvent )
792 const bool differentHandles = ( mEventData->mLeftSelectionPosition != handlePosition ) && ( mEventData->mRightSelectionPosition != handlePosition );
793 mEventData->mUpdateLeftSelectionPosition = endOfScroll || differentHandles;
794 if( differentHandles )
796 mEventData->mLeftSelectionPosition = handlePosition;
801 const bool differentHandles = ( mEventData->mRightSelectionPosition != handlePosition ) && ( mEventData->mLeftSelectionPosition != handlePosition );
802 mEventData->mUpdateRightSelectionPosition = endOfScroll || differentHandles;
803 if( differentHandles )
805 mEventData->mRightSelectionPosition = handlePosition;
809 if( mEventData->mUpdateLeftSelectionPosition || mEventData->mUpdateRightSelectionPosition )
811 RepositionSelectionHandles( mEventData->mLeftSelectionPosition,
812 mEventData->mRightSelectionPosition );
814 mEventData->mScrollAfterUpdatePosition = true;
817 mEventData->mDecoratorUpdated = true;
818 } // end ( HANDLE_SCROLLING == state )
821 void Controller::Impl::OnSelectEvent( const Event& event )
823 if( NULL == mEventData )
825 // Nothing to do if there is no text.
829 if( mEventData->mSelectionEnabled )
831 // The event.p2 and event.p3 are in decorator coords. Need to transforms to text coords.
832 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
833 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
835 const CharacterIndex leftPosition = mEventData->mLeftSelectionPosition;
836 const CharacterIndex rightPosition = mEventData->mRightSelectionPosition;
838 RepositionSelectionHandles( xPosition,
841 mEventData->mUpdateLeftSelectionPosition = leftPosition != mEventData->mLeftSelectionPosition;
842 mEventData->mUpdateRightSelectionPosition = rightPosition != mEventData->mRightSelectionPosition;
844 mEventData->mScrollAfterUpdatePosition = ( ( mEventData->mUpdateLeftSelectionPosition || mEventData->mUpdateRightSelectionPosition ) &&
845 ( mEventData->mLeftSelectionPosition != mEventData->mRightSelectionPosition ) );
849 void Controller::Impl::OnSelectAllEvent()
851 if( NULL == mEventData )
853 // Nothing to do if there is no text.
857 if( mEventData->mSelectionEnabled )
859 RepositionSelectionHandles( 0u,
860 mLogicalModel->mText.Count() );
862 mEventData->mScrollAfterUpdatePosition = true;
863 mEventData->mUpdateLeftSelectionPosition = true;
864 mEventData->mUpdateRightSelectionPosition = true;
868 void Controller::Impl::RetrieveSelection( std::string& selectedText, bool deleteAfterRetreival )
870 if( mEventData->mLeftSelectionPosition == mEventData->mRightSelectionPosition )
872 // Nothing to select if handles are in the same place.
877 //Get start and end position of selection
878 uint32_t startOfSelectedText = mEventData->mLeftSelectionPosition;
879 uint32_t lengthOfSelectedText = mEventData->mRightSelectionPosition - startOfSelectedText;
881 // Validate the start and end selection points
882 if( ( startOfSelectedText + lengthOfSelectedText ) <= mLogicalModel->mText.Count() )
884 //Get text as a UTF8 string
885 Vector<Character>& utf32Characters = mLogicalModel->mText;
887 Utf32ToUtf8( &utf32Characters[startOfSelectedText], lengthOfSelectedText, selectedText );
889 if ( deleteAfterRetreival ) // Only delete text if copied successfully
891 // Delete text between handles
892 Vector<Character>& currentText = mLogicalModel->mText;
894 Vector<Character>::Iterator first = currentText.Begin() + startOfSelectedText;
895 Vector<Character>::Iterator last = first + lengthOfSelectedText;
896 currentText.Erase( first, last );
898 mEventData->mPrimaryCursorPosition = mEventData->mLeftSelectionPosition;
899 mEventData->mScrollAfterDelete = true;
900 mEventData->mDecoratorUpdated = true;
904 void Controller::Impl::ShowClipboard()
908 mClipboard.ShowClipboard();
912 void Controller::Impl::HideClipboard()
916 mClipboard.HideClipboard();
920 bool Controller::Impl::CopyStringToClipboard( std::string& source )
922 //Send string to clipboard
923 return ( mClipboard && mClipboard.SetItem( source ) );
926 void Controller::Impl::SendSelectionToClipboard( bool deleteAfterSending )
928 std::string selectedText;
929 RetrieveSelection( selectedText, deleteAfterSending );
930 CopyStringToClipboard( selectedText );
931 ChangeState( EventData::EDITING );
934 void Controller::Impl::GetTextFromClipboard( unsigned int itemIndex, std::string& retreivedString )
938 retreivedString = mClipboard.GetItem( itemIndex );
942 void Controller::Impl::RepositionSelectionHandles( CharacterIndex selectionStart, CharacterIndex selectionEnd )
944 if( selectionStart == selectionEnd )
946 // Nothing to select if handles are in the same place.
950 mEventData->mDecorator->ClearHighlights();
952 mEventData->mLeftSelectionPosition = selectionStart;
953 mEventData->mRightSelectionPosition = selectionEnd;
955 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
956 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
957 const GlyphInfo* const glyphsBuffer = mVisualModel->mGlyphs.Begin();
958 const Vector2* const positionsBuffer = mVisualModel->mGlyphPositions.Begin();
959 const Length* const charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
960 const CharacterIndex* const glyphToCharacterBuffer = mVisualModel->mGlyphsToCharacters.Begin();
961 const CharacterDirection* const modelCharacterDirectionsBuffer = ( 0u != mLogicalModel->mCharacterDirections.Count() ) ? mLogicalModel->mCharacterDirections.Begin() : NULL;
963 // TODO: Better algorithm to create the highlight box.
966 // Get the height of the line.
967 const Vector<LineRun>& lines = mVisualModel->mLines;
968 const LineRun& firstLine = *lines.Begin();
969 const float height = firstLine.ascender + -firstLine.descender;
971 // Swap the indices if the start is greater than the end.
972 const bool indicesSwapped = ( selectionStart > selectionEnd );
975 std::swap( selectionStart, selectionEnd );
978 // Get the indices to the first and last selected glyphs.
979 const CharacterIndex selectionEndMinusOne = selectionEnd - 1u;
980 const GlyphIndex glyphStart = *( charactersToGlyphBuffer + selectionStart );
981 const Length numberOfGlyphs = *( glyphsPerCharacterBuffer + selectionEndMinusOne );
982 const GlyphIndex glyphEnd = *( charactersToGlyphBuffer + selectionEndMinusOne ) + ( ( numberOfGlyphs > 0 ) ? numberOfGlyphs - 1u : 0u );
984 // Check if the first glyph is a ligature that must be broken like Latin ff, fi, or Arabic ï»», etc which needs special code.
985 const Length numberOfCharactersStart = *( charactersPerGlyphBuffer + glyphStart );
986 bool splitStartGlyph = ( numberOfCharactersStart > 1u ) && HasLigatureMustBreak( mLogicalModel->GetScript( selectionStart ) );
988 // Check if the last glyph is a ligature that must be broken like Latin ff, fi, or Arabic ï»», etc which needs special code.
989 const Length numberOfCharactersEnd = *( charactersPerGlyphBuffer + glyphEnd );
990 bool splitEndGlyph = ( glyphStart != glyphEnd ) && ( numberOfCharactersEnd > 1u ) && HasLigatureMustBreak( mLogicalModel->GetScript( selectionEndMinusOne ) );
992 // Tell the decorator to swap the selection handles if needed.
993 mEventData->mDecorator->SwapSelectionHandlesEnabled( firstLine.direction != indicesSwapped );
995 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
997 // Traverse the glyphs.
998 for( GlyphIndex index = glyphStart; index <= glyphEnd; ++index )
1000 const GlyphInfo& glyph = *( glyphsBuffer + index );
1001 const Vector2& position = *( positionsBuffer + index );
1003 if( splitStartGlyph )
1005 // 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.
1007 const float glyphAdvance = glyph.advance / static_cast<float>( numberOfCharactersStart );
1008 const CharacterIndex interGlyphIndex = selectionStart - *( glyphToCharacterBuffer + glyphStart );
1009 // Get the direction of the character.
1010 CharacterDirection isCurrentRightToLeft = false;
1011 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
1013 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + selectionStart );
1016 // The end point could be in the middle of the ligature.
1017 // Calculate the number of characters selected.
1018 const Length numberOfCharacters = ( glyphStart == glyphEnd ) ? ( selectionEnd - selectionStart ) : ( numberOfCharactersStart - interGlyphIndex );
1020 const float xPosition = position.x - glyph.xBearing + offset.x + glyphAdvance * static_cast<float>( isCurrentRightToLeft ? ( numberOfCharactersStart - interGlyphIndex - numberOfCharacters ) : interGlyphIndex );
1022 mEventData->mDecorator->AddHighlight( xPosition,
1024 xPosition + static_cast<float>( numberOfCharacters ) * glyphAdvance,
1025 offset.y + height );
1027 splitStartGlyph = false;
1031 if( splitEndGlyph && ( index == glyphEnd ) )
1033 // 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.
1035 const float glyphAdvance = glyph.advance / static_cast<float>( numberOfCharactersEnd );
1036 const CharacterIndex interGlyphIndex = selectionEnd - *( glyphToCharacterBuffer + glyphEnd );
1037 // Get the direction of the character.
1038 CharacterDirection isCurrentRightToLeft = false;
1039 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
1041 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + selectionEnd );
1044 const Length numberOfCharacters = numberOfCharactersEnd - interGlyphIndex;
1046 const float xPosition = position.x - glyph.xBearing + offset.x + ( isCurrentRightToLeft ? ( glyphAdvance * static_cast<float>( numberOfCharacters ) ) : 0.f );
1047 mEventData->mDecorator->AddHighlight( xPosition,
1049 xPosition + static_cast<float>( interGlyphIndex ) * glyphAdvance,
1050 offset.y + height );
1052 splitEndGlyph = false;
1056 const float xPosition = position.x - glyph.xBearing + offset.x;
1057 mEventData->mDecorator->AddHighlight( xPosition,
1059 xPosition + glyph.advance,
1060 offset.y + height );
1063 CursorInfo primaryCursorInfo;
1064 GetCursorPosition( mEventData->mLeftSelectionPosition,
1065 primaryCursorInfo );
1067 CursorInfo secondaryCursorInfo;
1068 GetCursorPosition( mEventData->mRightSelectionPosition,
1069 secondaryCursorInfo );
1071 const Vector2 primaryPosition = primaryCursorInfo.primaryPosition + offset;
1072 const Vector2 secondaryPosition = secondaryCursorInfo.primaryPosition + offset;
1074 mEventData->mDecorator->SetPosition( LEFT_SELECTION_HANDLE, primaryPosition.x, primaryPosition.y, primaryCursorInfo.lineHeight );
1076 mEventData->mDecorator->SetPosition( RIGHT_SELECTION_HANDLE, secondaryPosition.x, secondaryPosition.y, secondaryCursorInfo.lineHeight );
1078 // Cursor to be positioned at end of selection so if selection interrupted and edit mode restarted the cursor will be at end of selection
1079 mEventData->mPrimaryCursorPosition = (indicesSwapped)?mEventData->mLeftSelectionPosition:mEventData->mRightSelectionPosition;
1081 // Set the flag to update the decorator.
1082 mEventData->mDecoratorUpdated = true;
1085 void Controller::Impl::RepositionSelectionHandles( float visualX, float visualY )
1087 if( NULL == mEventData )
1089 // Nothing to do if there is no text input.
1093 if( IsShowingPlaceholderText() )
1095 // Nothing to do if there is the place-holder text.
1099 const Length numberOfGlyphs = mVisualModel->mGlyphs.Count();
1100 const Length numberOfLines = mVisualModel->mLines.Count();
1101 if( 0 == numberOfGlyphs ||
1102 0 == numberOfLines )
1104 // Nothing to do if there is no text.
1108 // Find which word was selected
1109 CharacterIndex selectionStart( 0 );
1110 CharacterIndex selectionEnd( 0 );
1111 FindSelectionIndices( visualX, visualY, selectionStart, selectionEnd );
1112 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "%p selectionStart %d selectionEnd %d\n", this, selectionStart, selectionEnd );
1114 if( selectionStart == selectionEnd )
1116 ChangeState( EventData::EDITING );
1117 // Nothing to select. i.e. a white space, out of bounds
1121 RepositionSelectionHandles( selectionStart, selectionEnd );
1124 void Controller::Impl::SetPopupButtons()
1127 * Sets the Popup buttons to be shown depending on State.
1129 * If SELECTING : CUT & COPY + ( PASTE & CLIPBOARD if content available to paste )
1131 * If EDITING_WITH_POPUP : SELECT & SELECT_ALL
1134 TextSelectionPopup::Buttons buttonsToShow = TextSelectionPopup::NONE;
1136 if ( ( EventData::SELECTING == mEventData->mState ) || ( EventData::SELECTION_CHANGED == mEventData->mState ) )
1138 buttonsToShow = TextSelectionPopup::Buttons( TextSelectionPopup::CUT | TextSelectionPopup::COPY );
1140 if ( !IsClipboardEmpty() )
1142 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
1143 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::CLIPBOARD ) );
1146 if ( !mEventData->mAllTextSelected )
1148 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::SELECT_ALL ) );
1151 else if ( EventData::EDITING_WITH_POPUP == mEventData->mState )
1153 if ( mLogicalModel->mText.Count() && !IsShowingPlaceholderText())
1155 buttonsToShow = TextSelectionPopup::Buttons( TextSelectionPopup::SELECT | TextSelectionPopup::SELECT_ALL );
1158 if ( !IsClipboardEmpty() )
1160 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
1161 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::CLIPBOARD ) );
1165 mEventData->mDecorator->SetEnabledPopupButtons( buttonsToShow );
1168 void Controller::Impl::ChangeState( EventData::State newState )
1170 if( NULL == mEventData )
1172 // Nothing to do if there is no text input.
1176 if( mEventData->mState != newState )
1178 mEventData->mState = newState;
1180 if( EventData::INACTIVE == mEventData->mState )
1182 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
1183 mEventData->mDecorator->StopCursorBlink();
1184 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1185 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1186 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1187 mEventData->mDecorator->SetPopupActive( false );
1188 mEventData->mDecoratorUpdated = true;
1191 else if ( EventData::INTERRUPTED == mEventData->mState)
1193 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1194 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1195 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1196 mEventData->mDecorator->SetPopupActive( false );
1197 mEventData->mDecoratorUpdated = true;
1200 else if ( EventData::SELECTING == mEventData->mState )
1202 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
1203 mEventData->mDecorator->StopCursorBlink();
1204 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1205 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true );
1206 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true );
1207 if( mEventData->mGrabHandlePopupEnabled )
1210 mEventData->mDecorator->SetPopupActive( true );
1212 mEventData->mDecoratorUpdated = true;
1214 else if ( EventData::SELECTION_CHANGED == mEventData->mState )
1216 if( mEventData->mGrabHandlePopupEnabled )
1219 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
1220 mEventData->mDecorator->SetPopupActive( true );
1222 mEventData->mDecoratorUpdated = true;
1224 else if( EventData::EDITING == mEventData->mState )
1226 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1227 if( mEventData->mCursorBlinkEnabled )
1229 mEventData->mDecorator->StartCursorBlink();
1231 // Grab handle is not shown until a tap is received whilst EDITING
1232 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1233 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1234 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1235 if( mEventData->mGrabHandlePopupEnabled )
1237 mEventData->mDecorator->SetPopupActive( false );
1239 mEventData->mDecoratorUpdated = true;
1242 else if( EventData::EDITING_WITH_POPUP == mEventData->mState )
1244 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1245 if( mEventData->mCursorBlinkEnabled )
1247 mEventData->mDecorator->StartCursorBlink();
1249 if( mEventData->mSelectionEnabled )
1251 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1252 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1256 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
1258 if( mEventData->mGrabHandlePopupEnabled )
1261 mEventData->mDecorator->SetPopupActive( true );
1264 mEventData->mDecoratorUpdated = true;
1266 else if( EventData::EDITING_WITH_GRAB_HANDLE == mEventData->mState )
1268 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1269 if( mEventData->mCursorBlinkEnabled )
1271 mEventData->mDecorator->StartCursorBlink();
1273 // Grab handle is not shown until a tap is received whilst EDITING
1274 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
1275 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1276 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1277 if( mEventData->mGrabHandlePopupEnabled )
1279 mEventData->mDecorator->SetPopupActive( false );
1281 mEventData->mDecoratorUpdated = true;
1284 else if ( EventData::SELECTION_HANDLE_PANNING == mEventData->mState )
1286 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
1287 mEventData->mDecorator->StopCursorBlink();
1288 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1289 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true );
1290 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true );
1291 if( mEventData->mGrabHandlePopupEnabled )
1293 mEventData->mDecorator->SetPopupActive( false );
1295 mEventData->mDecoratorUpdated = true;
1297 else if ( EventData::GRAB_HANDLE_PANNING == mEventData->mState )
1299 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1300 if( mEventData->mCursorBlinkEnabled )
1302 mEventData->mDecorator->StartCursorBlink();
1304 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
1305 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1306 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1307 if( mEventData->mGrabHandlePopupEnabled )
1309 mEventData->mDecorator->SetPopupActive( false );
1311 mEventData->mDecoratorUpdated = true;
1316 LineIndex Controller::Impl::GetClosestLine( float y ) const
1318 float totalHeight = 0.f;
1319 LineIndex lineIndex = 0u;
1321 const Vector<LineRun>& lines = mVisualModel->mLines;
1322 for( LineIndex endLine = lines.Count();
1323 lineIndex < endLine;
1326 const LineRun& lineRun = lines[lineIndex];
1327 totalHeight += lineRun.ascender + -lineRun.descender;
1328 if( y < totalHeight )
1334 if( lineIndex == 0 )
1342 void Controller::Impl::FindSelectionIndices( float visualX, float visualY, CharacterIndex& startIndex, CharacterIndex& endIndex )
1344 CharacterIndex hitCharacter = GetClosestCursorIndex( visualX, visualY );
1345 DALI_ASSERT_DEBUG( hitCharacter <= mLogicalModel->mText.Count() && "GetClosestCursorIndex returned out of bounds index" );
1347 if ( mLogicalModel->mText.Count() == 0 )
1349 return; // if model empty
1352 if( hitCharacter >= mLogicalModel->mText.Count() )
1354 // Closest hit character is the last character.
1355 if ( hitCharacter == mLogicalModel->mText.Count() )
1357 hitCharacter--; //Hit character index set to last character in logical model
1361 // hitCharacter is out of bounds
1366 startIndex = hitCharacter;
1367 endIndex = hitCharacter;
1369 if( !TextAbstraction::IsWhiteSpace( mLogicalModel->mText[hitCharacter] ) )
1371 // Find the start and end of the text
1372 for( startIndex = hitCharacter; startIndex > 0; --startIndex )
1374 Character charCode = mLogicalModel->mText[ startIndex-1 ];
1375 if( TextAbstraction::IsWhiteSpace( charCode ) )
1380 const CharacterIndex pastTheEnd = mLogicalModel->mText.Count();
1381 for( endIndex = hitCharacter + 1u; endIndex < pastTheEnd; ++endIndex )
1383 Character charCode = mLogicalModel->mText[ endIndex ];
1384 if( TextAbstraction::IsWhiteSpace( charCode ) )
1392 CharacterIndex Controller::Impl::GetClosestCursorIndex( float visualX,
1395 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "GetClosestCursorIndex %p closest visualX %f visualY %f\n", this, visualX, visualY );
1397 if( NULL == mEventData )
1399 // Nothing to do if there is no text input.
1403 CharacterIndex logicalIndex = 0u;
1405 const Length numberOfGlyphs = mVisualModel->mGlyphs.Count();
1406 const Length numberOfLines = mVisualModel->mLines.Count();
1407 if( 0 == numberOfGlyphs ||
1408 0 == numberOfLines )
1410 return logicalIndex;
1413 // Find which line is closest
1414 const LineIndex lineIndex = GetClosestLine( visualY );
1415 const LineRun& line = mVisualModel->mLines[lineIndex];
1417 // Get the positions of the glyphs.
1418 const Vector<Vector2>& positions = mVisualModel->mGlyphPositions;
1419 const Vector2* const positionsBuffer = positions.Begin();
1421 // Get the visual to logical conversion tables.
1422 const CharacterIndex* const visualToLogicalBuffer = ( 0u != mLogicalModel->mVisualToLogicalMap.Count() ) ? mLogicalModel->mVisualToLogicalMap.Begin() : NULL;
1423 const CharacterIndex* const visualToLogicalCursorBuffer = mLogicalModel->mVisualToLogicalCursorMap.Begin();
1425 // Get the character to glyph conversion table.
1426 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
1428 // Get the glyphs per character table.
1429 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
1430 const Length* const charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
1432 // If the vector is void, there is no right to left characters.
1433 const bool hasRightToLeftCharacters = NULL != visualToLogicalBuffer;
1435 const CharacterIndex startCharacter = line.characterRun.characterIndex;
1436 const CharacterIndex endCharacter = line.characterRun.characterIndex + line.characterRun.numberOfCharacters;
1437 DALI_ASSERT_DEBUG( endCharacter <= mLogicalModel->mText.Count() && "Invalid line info" );
1439 // Whether there is a hit on a glyph.
1440 bool matched = false;
1442 // Traverses glyphs in visual order. To do that use the visual to logical conversion table.
1443 CharacterIndex visualIndex = startCharacter;
1444 for( ; !matched && ( visualIndex < endCharacter ); ++visualIndex )
1446 // The character in logical order.
1447 const CharacterIndex characterLogicalOrderIndex = hasRightToLeftCharacters ? *( visualToLogicalBuffer + visualIndex ) : visualIndex;
1449 // Get the script of the character.
1450 const Script script = mLogicalModel->GetScript( characterLogicalOrderIndex );
1452 // The first glyph for that character in logical order.
1453 const GlyphIndex glyphLogicalOrderIndex = *( charactersToGlyphBuffer + characterLogicalOrderIndex );
1454 // The number of glyphs for that character
1455 const Length numberOfGlyphs = *( glyphsPerCharacterBuffer + characterLogicalOrderIndex );
1457 // Get the metrics for the group of glyphs.
1458 GlyphMetrics glyphMetrics;
1459 GetGlyphsMetrics( glyphLogicalOrderIndex,
1465 const Vector2& position = *( positionsBuffer + glyphLogicalOrderIndex );
1467 // Prevents to jump the whole Latin ligatures like fi, ff, or Arabic ï»»...
1468 const Length numberOfCharactersInLigature = HasLigatureMustBreak( script ) ? *( charactersPerGlyphBuffer + glyphLogicalOrderIndex ) : 1u;
1469 const float glyphAdvance = glyphMetrics.advance / static_cast<float>( numberOfCharactersInLigature );
1471 for( GlyphIndex index = 0u; !matched && ( index < numberOfCharactersInLigature ); ++index )
1473 // Find the mid-point of the area containing the glyph
1474 const float glyphCenter = -glyphMetrics.xBearing + position.x + ( static_cast<float>( index ) + 0.5f ) * glyphAdvance;
1476 if( visualX < glyphCenter )
1478 visualIndex += index;
1490 // Return the logical position of the cursor in characters.
1494 visualIndex = endCharacter;
1497 logicalIndex = hasRightToLeftCharacters ? *( visualToLogicalCursorBuffer + visualIndex ) : visualIndex;
1498 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "%p closest visualIndex %d logicalIndex %d\n", this, visualIndex, logicalIndex );
1500 DALI_ASSERT_DEBUG( ( logicalIndex <= mLogicalModel->mText.Count() && logicalIndex >= 0 ) && "GetClosestCursorIndex - Out of bounds index" );
1502 return logicalIndex;
1505 void Controller::Impl::GetCursorPosition( CharacterIndex logical,
1506 CursorInfo& cursorInfo )
1508 // TODO: Check for multiline with \n, etc...
1510 // Check if the logical position is the first or the last one of the text.
1511 const bool isFirstPosition = 0u == logical;
1512 const bool isLastPosition = mLogicalModel->mText.Count() == logical;
1514 if( isFirstPosition && isLastPosition )
1516 // There is zero characters. Get the default font's line height.
1517 cursorInfo.lineHeight = GetDefaultFontLineHeight();
1518 cursorInfo.primaryCursorHeight = cursorInfo.lineHeight;
1520 cursorInfo.primaryPosition.x = mEventData->mDecorator->GetCursorWidth();
1521 cursorInfo.primaryPosition.y = 0.f;
1523 // Nothing else to do.
1527 // 'logical' is the logical 'cursor' index.
1528 // Get the next and current logical 'character' index.
1529 const CharacterIndex nextCharacterIndex = logical;
1530 const CharacterIndex characterIndex = isFirstPosition ? logical : logical - 1u;
1532 // Get the direction of the character and the next one.
1533 const CharacterDirection* const modelCharacterDirectionsBuffer = ( 0u != mLogicalModel->mCharacterDirections.Count() ) ? mLogicalModel->mCharacterDirections.Begin() : NULL;
1535 CharacterDirection isCurrentRightToLeft = false;
1536 CharacterDirection isNextRightToLeft = false;
1537 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
1539 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + characterIndex );
1540 isNextRightToLeft = *( modelCharacterDirectionsBuffer + nextCharacterIndex );
1543 // Get the line where the character is laid-out.
1544 const LineRun* const modelLines = mVisualModel->mLines.Begin();
1546 const LineIndex lineIndex = mVisualModel->GetLineOfCharacter( characterIndex );
1547 const LineRun& line = *( modelLines + lineIndex );
1549 // Get the paragraph's direction.
1550 const CharacterDirection isRightToLeftParagraph = line.direction;
1552 // Check whether there is an alternative position:
1554 cursorInfo.isSecondaryCursor = ( !isLastPosition && ( isCurrentRightToLeft != isNextRightToLeft ) ) ||
1555 ( isLastPosition && ( isRightToLeftParagraph != isCurrentRightToLeft ) );
1557 // Set the line height.
1558 cursorInfo.lineHeight = line.ascender + -line.descender;
1560 // Calculate the primary cursor.
1562 CharacterIndex index = characterIndex;
1563 if( cursorInfo.isSecondaryCursor )
1565 // If there is a secondary position, the primary cursor may be in a different place than the logical index.
1567 if( isLastPosition )
1569 // The position of the cursor after the last character needs special
1570 // care depending on its direction and the direction of the paragraph.
1572 // Need to find the first character after the last character with the paragraph's direction.
1573 // i.e l0 l1 l2 r0 r1 should find r0.
1575 // TODO: check for more than one line!
1576 index = isRightToLeftParagraph ? line.characterRun.characterIndex : line.characterRun.characterIndex + line.characterRun.numberOfCharacters - 1u;
1577 index = mLogicalModel->GetLogicalCharacterIndex( index );
1581 index = ( isRightToLeftParagraph == isCurrentRightToLeft ) ? characterIndex : nextCharacterIndex;
1585 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
1586 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
1587 const Length* const charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
1588 const CharacterIndex* const glyphsToCharactersBuffer = mVisualModel->mGlyphsToCharacters.Begin();
1589 const Vector2* const glyphPositionsBuffer = mVisualModel->mGlyphPositions.Begin();
1591 // Convert the cursor position into the glyph position.
1592 const GlyphIndex primaryGlyphIndex = *( charactersToGlyphBuffer + index );
1593 const Length primaryNumberOfGlyphs = *( glyphsPerCharacterBuffer + index );
1594 const Length primaryNumberOfCharacters = *( charactersPerGlyphBuffer + primaryGlyphIndex );
1596 // Get the metrics for the group of glyphs.
1597 GlyphMetrics glyphMetrics;
1598 GetGlyphsMetrics( primaryGlyphIndex,
1599 primaryNumberOfGlyphs,
1604 // Whether to add the glyph's advance to the cursor position.
1605 // 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,
1606 // if the logical cursor is one, the position is the position of the first glyph and the advance is added.
1607 // A 'truth table' was build and an online Karnaugh map tool was used to simplify the logic.
1628 // Where F -> isFirstPosition
1629 // L -> isLastPosition
1630 // C -> isCurrentRightToLeft
1631 // P -> isRightToLeftParagraph
1632 // A -> Whether to add the glyph's advance.
1634 const bool addGlyphAdvance = ( ( isLastPosition && !isRightToLeftParagraph ) ||
1635 ( isFirstPosition && isRightToLeftParagraph ) ||
1636 ( !isFirstPosition && !isLastPosition && !isCurrentRightToLeft ) );
1638 float glyphAdvance = addGlyphAdvance ? glyphMetrics.advance : 0.f;
1640 if( !isLastPosition &&
1641 ( primaryNumberOfCharacters > 1u ) )
1643 const CharacterIndex firstIndex = *( glyphsToCharactersBuffer + primaryGlyphIndex );
1645 bool isCurrentRightToLeft = false;
1646 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
1648 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + index );
1651 Length numberOfGlyphAdvance = ( isFirstPosition ? 0u : 1u ) + characterIndex - firstIndex;
1652 if( isCurrentRightToLeft )
1654 numberOfGlyphAdvance = primaryNumberOfCharacters - numberOfGlyphAdvance;
1657 glyphAdvance = static_cast<float>( numberOfGlyphAdvance ) * glyphMetrics.advance / static_cast<float>( primaryNumberOfCharacters );
1660 // Get the glyph position and x bearing.
1661 const Vector2& primaryPosition = *( glyphPositionsBuffer + primaryGlyphIndex );
1663 // Set the primary cursor's height.
1664 cursorInfo.primaryCursorHeight = cursorInfo.isSecondaryCursor ? 0.5f * glyphMetrics.fontHeight : glyphMetrics.fontHeight;
1666 // Set the primary cursor's position.
1667 cursorInfo.primaryPosition.x = -glyphMetrics.xBearing + primaryPosition.x + glyphAdvance;
1668 cursorInfo.primaryPosition.y = line.ascender - glyphMetrics.ascender;
1670 // Calculate the secondary cursor.
1672 if( cursorInfo.isSecondaryCursor )
1674 // Set the secondary cursor's height.
1675 cursorInfo.secondaryCursorHeight = 0.5f * glyphMetrics.fontHeight;
1677 CharacterIndex index = characterIndex;
1678 if( !isLastPosition )
1680 index = ( isRightToLeftParagraph == isCurrentRightToLeft ) ? nextCharacterIndex : characterIndex;
1683 const GlyphIndex secondaryGlyphIndex = *( charactersToGlyphBuffer + index );
1684 const Length secondaryNumberOfGlyphs = *( glyphsPerCharacterBuffer + index );
1686 const Vector2& secondaryPosition = *( glyphPositionsBuffer + secondaryGlyphIndex );
1688 GetGlyphsMetrics( secondaryGlyphIndex,
1689 secondaryNumberOfGlyphs,
1694 // Set the secondary cursor's position.
1695 cursorInfo.secondaryPosition.x = -glyphMetrics.xBearing + secondaryPosition.x + ( isCurrentRightToLeft ? 0.f : glyphMetrics.advance );
1696 cursorInfo.secondaryPosition.y = cursorInfo.lineHeight - cursorInfo.secondaryCursorHeight - line.descender - ( glyphMetrics.fontHeight - glyphMetrics.ascender );
1700 CharacterIndex Controller::Impl::CalculateNewCursorIndex( CharacterIndex index ) const
1702 if( NULL == mEventData )
1704 // Nothing to do if there is no text input.
1708 CharacterIndex cursorIndex = mEventData->mPrimaryCursorPosition;
1710 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
1711 const Length* const charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
1713 GlyphIndex glyphIndex = *( charactersToGlyphBuffer + index );
1714 Length numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
1716 if( numberOfCharacters > 1u )
1718 const Script script = mLogicalModel->GetScript( index );
1719 if( HasLigatureMustBreak( script ) )
1721 // Prevents to jump the whole Latin ligatures like fi, ff, or Arabic ï»», ...
1722 numberOfCharacters = 1u;
1727 while( 0u == numberOfCharacters )
1730 numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
1734 if( index < mEventData->mPrimaryCursorPosition )
1736 cursorIndex -= numberOfCharacters;
1740 cursorIndex += numberOfCharacters;
1746 void Controller::Impl::UpdateCursorPosition()
1748 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "-->Controller::UpdateCursorPosition %p\n", this );
1749 if( NULL == mEventData )
1751 // Nothing to do if there is no text input.
1752 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition no event data\n" );
1756 if( IsShowingPlaceholderText() || ( 0u == mLogicalModel->mText.Count() ) )
1758 // Do not want to use the place-holder text to set the cursor position.
1760 // Use the line's height of the font's family set to set the cursor's size.
1761 // If there is no font's family set, use the default font.
1762 // Use the current alignment to place the cursor at the beginning, center or end of the box.
1764 float lineHeight = 0.f;
1766 FontId defaultFontId = 0u;
1767 if( NULL == mFontDefaults )
1769 TextAbstraction::FontDescription fontDescription;
1770 defaultFontId = mFontClient.GetFontId( fontDescription );
1774 defaultFontId = mFontDefaults->GetFontId( mFontClient );
1777 Text::FontMetrics fontMetrics;
1778 mMetrics->GetFontMetrics( defaultFontId, fontMetrics );
1780 lineHeight = fontMetrics.ascender - fontMetrics.descender;
1783 Vector2 cursorPosition;
1785 switch( mLayoutEngine.GetHorizontalAlignment() )
1787 case LayoutEngine::HORIZONTAL_ALIGN_BEGIN:
1789 cursorPosition.x = mEventData->mDecorator->GetCursorWidth();
1792 case LayoutEngine::HORIZONTAL_ALIGN_CENTER:
1794 cursorPosition.x = floor( 0.5f * mVisualModel->mControlSize.width );
1797 case LayoutEngine::HORIZONTAL_ALIGN_END:
1799 cursorPosition.x = mVisualModel->mControlSize.width;
1804 switch( mLayoutEngine.GetVerticalAlignment() )
1806 case LayoutEngine::VERTICAL_ALIGN_TOP:
1808 cursorPosition.y = 0.f;
1811 case LayoutEngine::VERTICAL_ALIGN_CENTER:
1813 cursorPosition.y = floorf( 0.5f * ( mVisualModel->mControlSize.height - lineHeight ) );
1816 case LayoutEngine::VERTICAL_ALIGN_BOTTOM:
1818 cursorPosition.y = mVisualModel->mControlSize.height - lineHeight;
1823 mEventData->mDecorator->SetPosition( PRIMARY_CURSOR,
1831 CursorInfo cursorInfo;
1832 GetCursorPosition( mEventData->mPrimaryCursorPosition,
1835 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
1836 const Vector2 cursorPosition = cursorInfo.primaryPosition + offset;
1838 // Sets the cursor position.
1839 mEventData->mDecorator->SetPosition( PRIMARY_CURSOR,
1842 cursorInfo.primaryCursorHeight,
1843 cursorInfo.lineHeight );
1844 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Primary cursor position: %f,%f\n", cursorPosition.x, cursorPosition.y );
1846 // Sets the grab handle position.
1847 mEventData->mDecorator->SetPosition( GRAB_HANDLE,
1850 cursorInfo.lineHeight );
1852 if( cursorInfo.isSecondaryCursor )
1854 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_BOTH );
1855 mEventData->mDecorator->SetPosition( SECONDARY_CURSOR,
1856 cursorInfo.secondaryPosition.x + offset.x,
1857 cursorInfo.secondaryPosition.y + offset.y,
1858 cursorInfo.secondaryCursorHeight,
1859 cursorInfo.lineHeight );
1860 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Secondary cursor position: %f,%f\n", cursorInfo.secondaryPosition.x + offset.x, cursorInfo.secondaryPosition.y + offset.y );
1864 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1867 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition\n" );
1870 void Controller::Impl::UpdateSelectionHandle( HandleType handleType )
1872 if( ( LEFT_SELECTION_HANDLE != handleType ) &&
1873 ( RIGHT_SELECTION_HANDLE != handleType ) )
1878 const bool leftSelectionHandle = LEFT_SELECTION_HANDLE == handleType;
1879 const CharacterIndex index = leftSelectionHandle ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition;
1881 CursorInfo cursorInfo;
1882 GetCursorPosition( index,
1885 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
1886 const Vector2 cursorPosition = cursorInfo.primaryPosition + offset;
1888 // Sets the grab handle position.
1889 mEventData->mDecorator->SetPosition( handleType,
1892 cursorInfo.lineHeight );
1894 // If selection handle at start of the text and other at end of the text then all text is selected.
1895 const CharacterIndex startOfSelection = std::min( mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition );
1896 const CharacterIndex endOfSelection = std::max ( mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition );
1897 mEventData->mAllTextSelected = ( startOfSelection == 0 ) && ( endOfSelection == mLogicalModel->mText.Count() );
1900 void Controller::Impl::ClampHorizontalScroll( const Vector2& actualSize )
1902 // Clamp between -space & 0 (and the text alignment).
1903 if( actualSize.width > mVisualModel->mControlSize.width )
1905 const float space = ( actualSize.width - mVisualModel->mControlSize.width ) + mAlignmentOffset.x;
1906 mEventData->mScrollPosition.x = ( mEventData->mScrollPosition.x < -space ) ? -space : mEventData->mScrollPosition.x;
1907 mEventData->mScrollPosition.x = ( mEventData->mScrollPosition.x > -mAlignmentOffset.x ) ? -mAlignmentOffset.x : mEventData->mScrollPosition.x;
1909 mEventData->mDecoratorUpdated = true;
1913 mEventData->mScrollPosition.x = 0.f;
1917 void Controller::Impl::ClampVerticalScroll( const Vector2& actualSize )
1919 // Clamp between -space & 0 (and the text alignment).
1920 if( actualSize.height > mVisualModel->mControlSize.height )
1922 const float space = ( actualSize.height - mVisualModel->mControlSize.height ) + mAlignmentOffset.y;
1923 mEventData->mScrollPosition.y = ( mEventData->mScrollPosition.y < -space ) ? -space : mEventData->mScrollPosition.y;
1924 mEventData->mScrollPosition.y = ( mEventData->mScrollPosition.y > -mAlignmentOffset.y ) ? -mAlignmentOffset.y : mEventData->mScrollPosition.y;
1926 mEventData->mDecoratorUpdated = true;
1930 mEventData->mScrollPosition.y = 0.f;
1934 void Controller::Impl::ScrollToMakePositionVisible( const Vector2& position )
1937 bool updateDecorator = false;
1938 if( position.x < 0.f )
1940 offset.x = -position.x;
1941 mEventData->mScrollPosition.x += offset.x;
1942 updateDecorator = true;
1944 else if( position.x > mVisualModel->mControlSize.width )
1946 offset.x = mVisualModel->mControlSize.width - position.x;
1947 mEventData->mScrollPosition.x += offset.x;
1948 updateDecorator = true;
1951 if( updateDecorator && mEventData->mDecorator )
1953 mEventData->mDecorator->UpdatePositions( offset );
1956 // TODO : calculate the vertical scroll.
1959 void Controller::Impl::ScrollTextToMatchCursor()
1961 // Get the current cursor position in decorator coords.
1962 const Vector2& currentCursorPosition = mEventData->mDecorator->GetPosition( PRIMARY_CURSOR );
1964 // Calculate the new cursor position.
1965 CursorInfo cursorInfo;
1966 GetCursorPosition( mEventData->mPrimaryCursorPosition,
1969 // Calculate the offset to match the cursor position before the character was deleted.
1970 mEventData->mScrollPosition.x = currentCursorPosition.x - cursorInfo.primaryPosition.x - mAlignmentOffset.x;
1972 ClampHorizontalScroll( mVisualModel->GetActualSize() );
1974 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
1975 const Vector2 cursorPosition = cursorInfo.primaryPosition + offset;
1977 // Sets the cursor position.
1978 mEventData->mDecorator->SetPosition( PRIMARY_CURSOR,
1981 cursorInfo.primaryCursorHeight,
1982 cursorInfo.lineHeight );
1984 // Sets the grab handle position.
1985 mEventData->mDecorator->SetPosition( GRAB_HANDLE,
1988 cursorInfo.lineHeight );
1990 if( cursorInfo.isSecondaryCursor )
1992 mEventData->mDecorator->SetPosition( SECONDARY_CURSOR,
1993 cursorInfo.secondaryPosition.x + offset.x,
1994 cursorInfo.secondaryPosition.y + offset.y,
1995 cursorInfo.secondaryCursorHeight,
1996 cursorInfo.lineHeight );
1997 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Secondary cursor position: %f,%f\n", cursorInfo.secondaryPosition.x + offset.x, cursorInfo.secondaryPosition.y + offset.y );
2000 // Set which cursors are active according the state.
2001 if( ( EventData::EDITING == mEventData->mState ) ||
2002 ( EventData::EDITING_WITH_POPUP == mEventData->mState ) ||
2003 ( EventData::EDITING_WITH_GRAB_HANDLE == mEventData->mState ) ||
2004 ( EventData::GRAB_HANDLE_PANNING == mEventData->mState ) )
2006 if( cursorInfo.isSecondaryCursor )
2008 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_BOTH );
2012 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
2017 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
2021 void Controller::Impl::RequestRelayout()
2023 mControlInterface.RequestTextRelayout();
2028 } // namespace Toolkit