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( IsShowingRealText() )
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,
548 // When the cursor position is changing, delay cursor blinking
549 mEventData->mDecorator->DelayCursorBlink();
553 mEventData->mPrimaryCursorPosition = 0u;
556 mEventData->mUpdateCursorPosition = true;
557 mEventData->mScrollAfterUpdatePosition = true;
562 void Controller::Impl::OnPanEvent( const Event& event )
564 if( NULL == mEventData )
566 // Nothing to do if there is no text input.
570 int state = event.p1.mInt;
572 if( Gesture::Started == state ||
573 Gesture::Continuing == state )
575 const Vector2& actualSize = mVisualModel->GetActualSize();
576 const Vector2 currentScroll = mEventData->mScrollPosition;
578 if( mEventData->mHorizontalScrollingEnabled )
580 const float displacementX = event.p2.mFloat;
581 mEventData->mScrollPosition.x += displacementX;
583 ClampHorizontalScroll( actualSize );
586 if( mEventData->mVerticalScrollingEnabled )
588 const float displacementY = event.p3.mFloat;
589 mEventData->mScrollPosition.y += displacementY;
591 ClampVerticalScroll( actualSize );
594 if( mEventData->mDecorator )
596 mEventData->mDecorator->UpdatePositions( mEventData->mScrollPosition - currentScroll );
601 void Controller::Impl::OnLongPressEvent( const Event& event )
603 if ( EventData::EDITING == mEventData->mState )
605 ChangeState ( EventData::EDITING_WITH_POPUP );
606 mEventData->mDecoratorUpdated = true;
610 void Controller::Impl::OnHandleEvent( const Event& event )
612 if( NULL == mEventData )
614 // Nothing to do if there is no text input.
618 const unsigned int state = event.p1.mUint;
619 const bool handleStopScrolling = ( HANDLE_STOP_SCROLLING == state );
621 if( HANDLE_PRESSED == state )
623 // The event.p2 and event.p3 are in decorator coords. Need to transforms to text coords.
624 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
625 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
627 const CharacterIndex handleNewPosition = GetClosestCursorIndex( xPosition, yPosition );
629 if( Event::GRAB_HANDLE_EVENT == event.type )
631 ChangeState ( EventData::GRAB_HANDLE_PANNING );
633 if( handleNewPosition != mEventData->mPrimaryCursorPosition )
635 mEventData->mPrimaryCursorPosition = handleNewPosition;
636 mEventData->mUpdateCursorPosition = true;
639 else if( Event::LEFT_SELECTION_HANDLE_EVENT == event.type )
641 ChangeState ( EventData::SELECTION_HANDLE_PANNING );
643 if( ( handleNewPosition != mEventData->mLeftSelectionPosition ) &&
644 ( handleNewPosition != mEventData->mRightSelectionPosition ) )
646 mEventData->mLeftSelectionPosition = handleNewPosition;
648 RepositionSelectionHandles( mEventData->mLeftSelectionPosition,
649 mEventData->mRightSelectionPosition );
651 mEventData->mUpdateLeftSelectionPosition = true;
654 else if( Event::RIGHT_SELECTION_HANDLE_EVENT == event.type )
656 ChangeState ( EventData::SELECTION_HANDLE_PANNING );
658 if( ( handleNewPosition != mEventData->mRightSelectionPosition ) &&
659 ( handleNewPosition != mEventData->mLeftSelectionPosition ) )
661 mEventData->mRightSelectionPosition = handleNewPosition;
663 RepositionSelectionHandles( mEventData->mLeftSelectionPosition,
664 mEventData->mRightSelectionPosition );
666 mEventData->mUpdateRightSelectionPosition = true;
669 } // end ( HANDLE_PRESSED == state )
670 else if( ( HANDLE_RELEASED == state ) ||
671 handleStopScrolling )
673 CharacterIndex handlePosition = 0u;
674 if( handleStopScrolling )
676 // The event.p2 and event.p3 are in decorator coords. Need to transforms to text coords.
677 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
678 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
680 handlePosition = GetClosestCursorIndex( xPosition, yPosition );
683 if( Event::GRAB_HANDLE_EVENT == event.type )
685 mEventData->mUpdateCursorPosition = true;
687 ChangeState( EventData::EDITING_WITH_POPUP );
689 if( handleStopScrolling )
691 mEventData->mScrollAfterUpdatePosition = mEventData->mPrimaryCursorPosition != handlePosition;
692 mEventData->mPrimaryCursorPosition = handlePosition;
695 else if( Event::LEFT_SELECTION_HANDLE_EVENT == event.type )
697 ChangeState( EventData::SELECTING );
699 if( handleStopScrolling )
701 mEventData->mUpdateLeftSelectionPosition = ( mEventData->mRightSelectionPosition != handlePosition );
702 mEventData->mScrollAfterUpdatePosition = mEventData->mUpdateLeftSelectionPosition;
704 if( mEventData->mUpdateLeftSelectionPosition )
706 mEventData->mLeftSelectionPosition = handlePosition;
708 RepositionSelectionHandles( mEventData->mLeftSelectionPosition,
709 mEventData->mRightSelectionPosition );
713 else if( Event::RIGHT_SELECTION_HANDLE_EVENT == event.type )
715 ChangeState( EventData::SELECTING );
717 if( handleStopScrolling )
719 mEventData->mUpdateRightSelectionPosition = ( mEventData->mLeftSelectionPosition != handlePosition );
720 mEventData->mScrollAfterUpdatePosition = mEventData->mUpdateRightSelectionPosition;
721 if( mEventData->mUpdateRightSelectionPosition )
723 mEventData->mRightSelectionPosition = handlePosition;
724 RepositionSelectionHandles( mEventData->mLeftSelectionPosition,
725 mEventData->mRightSelectionPosition );
730 mEventData->mDecoratorUpdated = true;
731 } // end ( ( HANDLE_RELEASED == state ) || ( HANDLE_STOP_SCROLLING == state ) )
732 else if( HANDLE_SCROLLING == state )
734 const float xSpeed = event.p2.mFloat;
735 const Vector2& actualSize = mVisualModel->GetActualSize();
736 const Vector2 currentScrollPosition = mEventData->mScrollPosition;
738 mEventData->mScrollPosition.x += xSpeed;
740 ClampHorizontalScroll( actualSize );
742 bool endOfScroll = false;
743 if( Vector2::ZERO == ( currentScrollPosition - mEventData->mScrollPosition ) )
745 // Notify the decorator there is no more text to scroll.
746 // The decorator won't send more scroll events.
747 mEventData->mDecorator->NotifyEndOfScroll();
748 // Still need to set the position of the handle.
752 // Set the position of the handle.
753 const bool scrollRightDirection = xSpeed > 0.f;
754 const bool leftSelectionHandleEvent = Event::LEFT_SELECTION_HANDLE_EVENT == event.type;
755 const bool rightSelectionHandleEvent = Event::RIGHT_SELECTION_HANDLE_EVENT == event.type;
757 if( Event::GRAB_HANDLE_EVENT == event.type )
759 ChangeState( EventData::GRAB_HANDLE_PANNING );
761 Vector2 position = mEventData->mDecorator->GetPosition( GRAB_HANDLE );
763 // Position the grag handle close to either the left or right edge.
764 position.x = scrollRightDirection ? 0.f : mVisualModel->mControlSize.width;
766 // Get the new handle position.
767 // The grab handle's position is in decorator coords. Need to transforms to text coords.
768 const CharacterIndex handlePosition = GetClosestCursorIndex( position.x - mEventData->mScrollPosition.x - mAlignmentOffset.x,
769 position.y - mEventData->mScrollPosition.y - mAlignmentOffset.y );
771 mEventData->mUpdateCursorPosition = mEventData->mPrimaryCursorPosition != handlePosition;
772 mEventData->mScrollAfterUpdatePosition = mEventData->mUpdateCursorPosition;
773 mEventData->mPrimaryCursorPosition = handlePosition;
775 else if( leftSelectionHandleEvent || rightSelectionHandleEvent )
777 // TODO: This is recalculating the selection box every time the text is scrolled with the selection handles.
778 // Think if something can be done to save power.
780 ChangeState( EventData::SELECTION_HANDLE_PANNING );
782 Vector2 position = mEventData->mDecorator->GetPosition( leftSelectionHandleEvent ? Text::LEFT_SELECTION_HANDLE : Text::RIGHT_SELECTION_HANDLE );
784 // Position the selection handle close to either the left or right edge.
785 position.x = scrollRightDirection ? 0.f : mVisualModel->mControlSize.width;
787 // Get the new handle position.
788 // The selection handle's position is in decorator coords. Need to transforms to text coords.
789 const CharacterIndex handlePosition = GetClosestCursorIndex( position.x - mEventData->mScrollPosition.x - mAlignmentOffset.x,
790 position.y - mEventData->mScrollPosition.y - mAlignmentOffset.y );
792 if( leftSelectionHandleEvent )
794 const bool differentHandles = ( mEventData->mLeftSelectionPosition != handlePosition ) && ( mEventData->mRightSelectionPosition != handlePosition );
795 mEventData->mUpdateLeftSelectionPosition = endOfScroll || differentHandles;
796 if( differentHandles )
798 mEventData->mLeftSelectionPosition = handlePosition;
803 const bool differentHandles = ( mEventData->mRightSelectionPosition != handlePosition ) && ( mEventData->mLeftSelectionPosition != handlePosition );
804 mEventData->mUpdateRightSelectionPosition = endOfScroll || differentHandles;
805 if( differentHandles )
807 mEventData->mRightSelectionPosition = handlePosition;
811 if( mEventData->mUpdateLeftSelectionPosition || mEventData->mUpdateRightSelectionPosition )
813 RepositionSelectionHandles( mEventData->mLeftSelectionPosition,
814 mEventData->mRightSelectionPosition );
816 mEventData->mScrollAfterUpdatePosition = true;
819 mEventData->mDecoratorUpdated = true;
820 } // end ( HANDLE_SCROLLING == state )
823 void Controller::Impl::OnSelectEvent( const Event& event )
825 if( NULL == mEventData )
827 // Nothing to do if there is no text.
831 if( mEventData->mSelectionEnabled )
833 // The event.p2 and event.p3 are in decorator coords. Need to transforms to text coords.
834 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
835 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
837 const CharacterIndex leftPosition = mEventData->mLeftSelectionPosition;
838 const CharacterIndex rightPosition = mEventData->mRightSelectionPosition;
840 RepositionSelectionHandles( xPosition,
843 mEventData->mUpdateLeftSelectionPosition = leftPosition != mEventData->mLeftSelectionPosition;
844 mEventData->mUpdateRightSelectionPosition = rightPosition != mEventData->mRightSelectionPosition;
846 mEventData->mScrollAfterUpdatePosition = ( ( mEventData->mUpdateLeftSelectionPosition || mEventData->mUpdateRightSelectionPosition ) &&
847 ( mEventData->mLeftSelectionPosition != mEventData->mRightSelectionPosition ) );
851 void Controller::Impl::OnSelectAllEvent()
853 if( NULL == mEventData )
855 // Nothing to do if there is no text.
859 if( mEventData->mSelectionEnabled )
861 RepositionSelectionHandles( 0u,
862 mLogicalModel->mText.Count() );
864 mEventData->mScrollAfterUpdatePosition = true;
865 mEventData->mUpdateLeftSelectionPosition = true;
866 mEventData->mUpdateRightSelectionPosition = true;
870 void Controller::Impl::RetrieveSelection( std::string& selectedText, bool deleteAfterRetreival )
872 if( mEventData->mLeftSelectionPosition == mEventData->mRightSelectionPosition )
874 // Nothing to select if handles are in the same place.
879 //Get start and end position of selection
880 uint32_t startOfSelectedText = mEventData->mLeftSelectionPosition;
881 uint32_t lengthOfSelectedText = mEventData->mRightSelectionPosition - startOfSelectedText;
883 // Validate the start and end selection points
884 if( ( startOfSelectedText >= 0 ) && ( ( startOfSelectedText + lengthOfSelectedText ) <= mLogicalModel->mText.Count() ) )
886 //Get text as a UTF8 string
887 Vector<Character>& utf32Characters = mLogicalModel->mText;
889 Utf32ToUtf8( &utf32Characters[startOfSelectedText], lengthOfSelectedText, selectedText );
891 if ( deleteAfterRetreival ) // Only delete text if copied successfully
893 // Delete text between handles
894 Vector<Character>& currentText = mLogicalModel->mText;
896 Vector<Character>::Iterator first = currentText.Begin() + startOfSelectedText;
897 Vector<Character>::Iterator last = first + lengthOfSelectedText;
898 currentText.Erase( first, last );
900 mEventData->mPrimaryCursorPosition = mEventData->mLeftSelectionPosition;
901 mEventData->mScrollAfterDelete = true;
902 mEventData->mDecoratorUpdated = true;
906 void Controller::Impl::ShowClipboard()
910 mClipboard.ShowClipboard();
914 void Controller::Impl::HideClipboard()
918 mClipboard.HideClipboard();
922 bool Controller::Impl::CopyStringToClipboard( std::string& source )
924 //Send string to clipboard
925 return ( mClipboard && mClipboard.SetItem( source ) );
928 void Controller::Impl::SendSelectionToClipboard( bool deleteAfterSending )
930 std::string selectedText;
931 RetrieveSelection( selectedText, deleteAfterSending );
932 CopyStringToClipboard( selectedText );
933 ChangeState( EventData::EDITING );
936 void Controller::Impl::GetTextFromClipboard( unsigned int itemIndex, std::string& retreivedString )
940 retreivedString = mClipboard.GetItem( itemIndex );
944 void Controller::Impl::RepositionSelectionHandles( CharacterIndex selectionStart, CharacterIndex selectionEnd )
946 if( selectionStart == selectionEnd )
948 // Nothing to select if handles are in the same place.
952 mEventData->mDecorator->ClearHighlights();
954 mEventData->mLeftSelectionPosition = selectionStart;
955 mEventData->mRightSelectionPosition = selectionEnd;
957 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
958 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
959 const GlyphInfo* const glyphsBuffer = mVisualModel->mGlyphs.Begin();
960 const Vector2* const positionsBuffer = mVisualModel->mGlyphPositions.Begin();
961 const Length* const charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
962 const CharacterIndex* const glyphToCharacterBuffer = mVisualModel->mGlyphsToCharacters.Begin();
963 const CharacterDirection* const modelCharacterDirectionsBuffer = ( 0u != mLogicalModel->mCharacterDirections.Count() ) ? mLogicalModel->mCharacterDirections.Begin() : NULL;
965 // TODO: Better algorithm to create the highlight box.
968 // Get the height of the line.
969 const Vector<LineRun>& lines = mVisualModel->mLines;
970 const LineRun& firstLine = *lines.Begin();
971 const float height = firstLine.ascender + -firstLine.descender;
973 // Swap the indices if the start is greater than the end.
974 const bool indicesSwapped = ( selectionStart > selectionEnd );
977 std::swap( selectionStart, selectionEnd );
980 // Get the indices to the first and last selected glyphs.
981 const CharacterIndex selectionEndMinusOne = selectionEnd - 1u;
982 const GlyphIndex glyphStart = *( charactersToGlyphBuffer + selectionStart );
983 const Length numberOfGlyphs = *( glyphsPerCharacterBuffer + selectionEndMinusOne );
984 const GlyphIndex glyphEnd = *( charactersToGlyphBuffer + selectionEndMinusOne ) + ( ( numberOfGlyphs > 0 ) ? numberOfGlyphs - 1u : 0u );
986 // Check if the first glyph is a ligature that must be broken like Latin ff, fi, or Arabic ï»», etc which needs special code.
987 const Length numberOfCharactersStart = *( charactersPerGlyphBuffer + glyphStart );
988 bool splitStartGlyph = ( numberOfCharactersStart > 1u ) && HasLigatureMustBreak( mLogicalModel->GetScript( selectionStart ) );
990 // Check if the last glyph is a ligature that must be broken like Latin ff, fi, or Arabic ï»», etc which needs special code.
991 const Length numberOfCharactersEnd = *( charactersPerGlyphBuffer + glyphEnd );
992 bool splitEndGlyph = ( glyphStart != glyphEnd ) && ( numberOfCharactersEnd > 1u ) && HasLigatureMustBreak( mLogicalModel->GetScript( selectionEndMinusOne ) );
994 // Tell the decorator to swap the selection handles if needed.
995 mEventData->mDecorator->SwapSelectionHandlesEnabled( firstLine.direction != indicesSwapped );
997 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
999 // Traverse the glyphs.
1000 for( GlyphIndex index = glyphStart; index <= glyphEnd; ++index )
1002 const GlyphInfo& glyph = *( glyphsBuffer + index );
1003 const Vector2& position = *( positionsBuffer + index );
1005 if( splitStartGlyph )
1007 // 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.
1009 const float glyphAdvance = glyph.advance / static_cast<float>( numberOfCharactersStart );
1010 const CharacterIndex interGlyphIndex = selectionStart - *( glyphToCharacterBuffer + glyphStart );
1011 // Get the direction of the character.
1012 CharacterDirection isCurrentRightToLeft = false;
1013 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
1015 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + selectionStart );
1018 // The end point could be in the middle of the ligature.
1019 // Calculate the number of characters selected.
1020 const Length numberOfCharacters = ( glyphStart == glyphEnd ) ? ( selectionEnd - selectionStart ) : ( numberOfCharactersStart - interGlyphIndex );
1022 const float xPosition = position.x - glyph.xBearing + offset.x + glyphAdvance * static_cast<float>( isCurrentRightToLeft ? ( numberOfCharactersStart - interGlyphIndex - numberOfCharacters ) : interGlyphIndex );
1024 mEventData->mDecorator->AddHighlight( xPosition,
1026 xPosition + static_cast<float>( numberOfCharacters ) * glyphAdvance,
1027 offset.y + height );
1029 splitStartGlyph = false;
1033 if( splitEndGlyph && ( index == glyphEnd ) )
1035 // 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.
1037 const float glyphAdvance = glyph.advance / static_cast<float>( numberOfCharactersEnd );
1038 const CharacterIndex interGlyphIndex = selectionEnd - *( glyphToCharacterBuffer + glyphEnd );
1039 // Get the direction of the character.
1040 CharacterDirection isCurrentRightToLeft = false;
1041 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
1043 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + selectionEnd );
1046 const Length numberOfCharacters = numberOfCharactersEnd - interGlyphIndex;
1048 const float xPosition = position.x - glyph.xBearing + offset.x + ( isCurrentRightToLeft ? ( glyphAdvance * static_cast<float>( numberOfCharacters ) ) : 0.f );
1049 mEventData->mDecorator->AddHighlight( xPosition,
1051 xPosition + static_cast<float>( interGlyphIndex ) * glyphAdvance,
1052 offset.y + height );
1054 splitEndGlyph = false;
1058 const float xPosition = position.x - glyph.xBearing + offset.x;
1059 mEventData->mDecorator->AddHighlight( xPosition,
1061 xPosition + glyph.advance,
1062 offset.y + height );
1065 CursorInfo primaryCursorInfo;
1066 GetCursorPosition( mEventData->mLeftSelectionPosition,
1067 primaryCursorInfo );
1069 CursorInfo secondaryCursorInfo;
1070 GetCursorPosition( mEventData->mRightSelectionPosition,
1071 secondaryCursorInfo );
1073 const Vector2 primaryPosition = primaryCursorInfo.primaryPosition + offset;
1074 const Vector2 secondaryPosition = secondaryCursorInfo.primaryPosition + offset;
1076 mEventData->mDecorator->SetPosition( LEFT_SELECTION_HANDLE, primaryPosition.x, primaryPosition.y, primaryCursorInfo.lineHeight );
1078 mEventData->mDecorator->SetPosition( RIGHT_SELECTION_HANDLE, secondaryPosition.x, secondaryPosition.y, secondaryCursorInfo.lineHeight );
1080 // Cursor to be positioned at end of selection so if selection interrupted and edit mode restarted the cursor will be at end of selection
1081 mEventData->mPrimaryCursorPosition = (indicesSwapped)?mEventData->mLeftSelectionPosition:mEventData->mRightSelectionPosition;
1083 // Set the flag to update the decorator.
1084 mEventData->mDecoratorUpdated = true;
1087 void Controller::Impl::RepositionSelectionHandles( float visualX, float visualY )
1089 if( NULL == mEventData )
1091 // Nothing to do if there is no text input.
1095 if( IsShowingPlaceholderText() )
1097 // Nothing to do if there is the place-holder text.
1101 const Length numberOfGlyphs = mVisualModel->mGlyphs.Count();
1102 const Length numberOfLines = mVisualModel->mLines.Count();
1103 if( 0 == numberOfGlyphs ||
1104 0 == numberOfLines )
1106 // Nothing to do if there is no text.
1110 // Find which word was selected
1111 CharacterIndex selectionStart( 0 );
1112 CharacterIndex selectionEnd( 0 );
1113 FindSelectionIndices( visualX, visualY, selectionStart, selectionEnd );
1114 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "%p selectionStart %d selectionEnd %d\n", this, selectionStart, selectionEnd );
1116 if( selectionStart == selectionEnd )
1118 ChangeState( EventData::EDITING );
1119 // Nothing to select. i.e. a white space, out of bounds
1123 RepositionSelectionHandles( selectionStart, selectionEnd );
1126 void Controller::Impl::SetPopupButtons()
1129 * Sets the Popup buttons to be shown depending on State.
1131 * If SELECTING : CUT & COPY + ( PASTE & CLIPBOARD if content available to paste )
1133 * If EDITING_WITH_POPUP : SELECT & SELECT_ALL
1136 TextSelectionPopup::Buttons buttonsToShow = TextSelectionPopup::NONE;
1138 if ( ( EventData::SELECTING == mEventData->mState ) || ( EventData::SELECTION_CHANGED == mEventData->mState ) )
1140 buttonsToShow = TextSelectionPopup::Buttons( TextSelectionPopup::CUT | TextSelectionPopup::COPY );
1142 if ( !IsClipboardEmpty() )
1144 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
1145 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::CLIPBOARD ) );
1148 if ( !mEventData->mAllTextSelected )
1150 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::SELECT_ALL ) );
1153 else if ( EventData::EDITING_WITH_POPUP == mEventData->mState )
1155 if ( mLogicalModel->mText.Count() && !IsShowingPlaceholderText())
1157 buttonsToShow = TextSelectionPopup::Buttons( TextSelectionPopup::SELECT | TextSelectionPopup::SELECT_ALL );
1160 if ( !IsClipboardEmpty() )
1162 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
1163 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::CLIPBOARD ) );
1167 mEventData->mDecorator->SetEnabledPopupButtons( buttonsToShow );
1170 void Controller::Impl::ChangeState( EventData::State newState )
1172 if( NULL == mEventData )
1174 // Nothing to do if there is no text input.
1178 if( mEventData->mState != newState )
1180 mEventData->mState = newState;
1182 if( EventData::INACTIVE == mEventData->mState )
1184 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
1185 mEventData->mDecorator->StopCursorBlink();
1186 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1187 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1188 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1189 mEventData->mDecorator->SetPopupActive( false );
1190 mEventData->mDecoratorUpdated = true;
1193 else if ( EventData::INTERRUPTED == mEventData->mState)
1195 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1196 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1197 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1198 mEventData->mDecorator->SetPopupActive( false );
1199 mEventData->mDecoratorUpdated = true;
1202 else if ( EventData::SELECTING == mEventData->mState )
1204 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
1205 mEventData->mDecorator->StopCursorBlink();
1206 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1207 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true );
1208 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true );
1209 if( mEventData->mGrabHandlePopupEnabled )
1212 mEventData->mDecorator->SetPopupActive( true );
1214 mEventData->mDecoratorUpdated = true;
1216 else if ( EventData::SELECTION_CHANGED == mEventData->mState )
1218 if( mEventData->mGrabHandlePopupEnabled )
1221 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
1222 mEventData->mDecorator->SetPopupActive( true );
1224 mEventData->mDecoratorUpdated = true;
1226 else if( EventData::EDITING == mEventData->mState )
1228 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1229 if( mEventData->mCursorBlinkEnabled )
1231 mEventData->mDecorator->StartCursorBlink();
1233 // Grab handle is not shown until a tap is received whilst EDITING
1234 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1235 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1236 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1237 if( mEventData->mGrabHandlePopupEnabled )
1239 mEventData->mDecorator->SetPopupActive( false );
1241 mEventData->mDecoratorUpdated = true;
1244 else if( EventData::EDITING_WITH_POPUP == mEventData->mState )
1246 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1247 if( mEventData->mCursorBlinkEnabled )
1249 mEventData->mDecorator->StartCursorBlink();
1251 if( mEventData->mSelectionEnabled )
1253 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1254 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1258 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
1260 if( mEventData->mGrabHandlePopupEnabled )
1263 mEventData->mDecorator->SetPopupActive( true );
1266 mEventData->mDecoratorUpdated = true;
1268 else if( EventData::EDITING_WITH_GRAB_HANDLE == mEventData->mState )
1270 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1271 if( mEventData->mCursorBlinkEnabled )
1273 mEventData->mDecorator->StartCursorBlink();
1275 // Grab handle is not shown until a tap is received whilst EDITING
1276 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
1277 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1278 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1279 if( mEventData->mGrabHandlePopupEnabled )
1281 mEventData->mDecorator->SetPopupActive( false );
1283 mEventData->mDecoratorUpdated = true;
1286 else if ( EventData::SELECTION_HANDLE_PANNING == mEventData->mState )
1288 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
1289 mEventData->mDecorator->StopCursorBlink();
1290 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1291 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true );
1292 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true );
1293 if( mEventData->mGrabHandlePopupEnabled )
1295 mEventData->mDecorator->SetPopupActive( false );
1297 mEventData->mDecoratorUpdated = true;
1299 else if ( EventData::GRAB_HANDLE_PANNING == mEventData->mState )
1301 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1302 if( mEventData->mCursorBlinkEnabled )
1304 mEventData->mDecorator->StartCursorBlink();
1306 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
1307 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1308 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1309 if( mEventData->mGrabHandlePopupEnabled )
1311 mEventData->mDecorator->SetPopupActive( false );
1313 mEventData->mDecoratorUpdated = true;
1318 LineIndex Controller::Impl::GetClosestLine( float y ) const
1320 float totalHeight = 0.f;
1321 LineIndex lineIndex = 0u;
1323 const Vector<LineRun>& lines = mVisualModel->mLines;
1324 for( LineIndex endLine = lines.Count();
1325 lineIndex < endLine;
1328 const LineRun& lineRun = lines[lineIndex];
1329 totalHeight += lineRun.ascender + -lineRun.descender;
1330 if( y < totalHeight )
1336 if( lineIndex == 0 )
1344 void Controller::Impl::FindSelectionIndices( float visualX, float visualY, CharacterIndex& startIndex, CharacterIndex& endIndex )
1346 CharacterIndex hitCharacter = GetClosestCursorIndex( visualX, visualY );
1347 DALI_ASSERT_DEBUG( hitCharacter <= mLogicalModel->mText.Count() && "GetClosestCursorIndex returned out of bounds index" );
1349 if ( mLogicalModel->mText.Count() == 0 )
1351 return; // if model empty
1354 if( hitCharacter >= mLogicalModel->mText.Count() )
1356 // Closest hit character is the last character.
1357 if ( hitCharacter == mLogicalModel->mText.Count() )
1359 hitCharacter--; //Hit character index set to last character in logical model
1363 // hitCharacter is out of bounds
1368 startIndex = hitCharacter;
1369 endIndex = hitCharacter;
1371 if( !TextAbstraction::IsWhiteSpace( mLogicalModel->mText[hitCharacter] ) )
1373 // Find the start and end of the text
1374 for( startIndex = hitCharacter; startIndex > 0; --startIndex )
1376 Character charCode = mLogicalModel->mText[ startIndex-1 ];
1377 if( TextAbstraction::IsWhiteSpace( charCode ) )
1382 const CharacterIndex pastTheEnd = mLogicalModel->mText.Count();
1383 for( endIndex = hitCharacter + 1u; endIndex < pastTheEnd; ++endIndex )
1385 Character charCode = mLogicalModel->mText[ endIndex ];
1386 if( TextAbstraction::IsWhiteSpace( charCode ) )
1394 CharacterIndex Controller::Impl::GetClosestCursorIndex( float visualX,
1397 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "GetClosestCursorIndex %p closest visualX %f visualY %f\n", this, visualX, visualY );
1399 if( NULL == mEventData )
1401 // Nothing to do if there is no text input.
1405 CharacterIndex logicalIndex = 0u;
1407 const Length numberOfGlyphs = mVisualModel->mGlyphs.Count();
1408 const Length numberOfLines = mVisualModel->mLines.Count();
1409 if( 0 == numberOfGlyphs ||
1410 0 == numberOfLines )
1412 return logicalIndex;
1415 // Find which line is closest
1416 const LineIndex lineIndex = GetClosestLine( visualY );
1417 const LineRun& line = mVisualModel->mLines[lineIndex];
1419 // Get the positions of the glyphs.
1420 const Vector<Vector2>& positions = mVisualModel->mGlyphPositions;
1421 const Vector2* const positionsBuffer = positions.Begin();
1423 // Get the visual to logical conversion tables.
1424 const CharacterIndex* const visualToLogicalBuffer = ( 0u != mLogicalModel->mVisualToLogicalMap.Count() ) ? mLogicalModel->mVisualToLogicalMap.Begin() : NULL;
1425 const CharacterIndex* const visualToLogicalCursorBuffer = mLogicalModel->mVisualToLogicalCursorMap.Begin();
1427 // Get the character to glyph conversion table.
1428 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
1430 // Get the glyphs per character table.
1431 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
1432 const Length* const charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
1434 // If the vector is void, there is no right to left characters.
1435 const bool hasRightToLeftCharacters = NULL != visualToLogicalBuffer;
1437 const CharacterIndex startCharacter = line.characterRun.characterIndex;
1438 const CharacterIndex endCharacter = line.characterRun.characterIndex + line.characterRun.numberOfCharacters;
1439 DALI_ASSERT_DEBUG( endCharacter <= mLogicalModel->mText.Count() && "Invalid line info" );
1441 // Whether there is a hit on a glyph.
1442 bool matched = false;
1444 // Traverses glyphs in visual order. To do that use the visual to logical conversion table.
1445 CharacterIndex visualIndex = startCharacter;
1446 for( ; !matched && ( visualIndex < endCharacter ); ++visualIndex )
1448 // The character in logical order.
1449 const CharacterIndex characterLogicalOrderIndex = hasRightToLeftCharacters ? *( visualToLogicalBuffer + visualIndex ) : visualIndex;
1451 // Get the script of the character.
1452 const Script script = mLogicalModel->GetScript( characterLogicalOrderIndex );
1454 // The first glyph for that character in logical order.
1455 const GlyphIndex glyphLogicalOrderIndex = *( charactersToGlyphBuffer + characterLogicalOrderIndex );
1456 // The number of glyphs for that character
1457 const Length numberOfGlyphs = *( glyphsPerCharacterBuffer + characterLogicalOrderIndex );
1459 // Get the metrics for the group of glyphs.
1460 GlyphMetrics glyphMetrics;
1461 GetGlyphsMetrics( glyphLogicalOrderIndex,
1467 const Vector2& position = *( positionsBuffer + glyphLogicalOrderIndex );
1469 // Prevents to jump the whole Latin ligatures like fi, ff, or Arabic ï»»...
1470 const Length numberOfCharactersInLigature = HasLigatureMustBreak( script ) ? *( charactersPerGlyphBuffer + glyphLogicalOrderIndex ) : 1u;
1471 const float glyphAdvance = glyphMetrics.advance / static_cast<float>( numberOfCharactersInLigature );
1473 for( GlyphIndex index = 0u; !matched && ( index < numberOfCharactersInLigature ); ++index )
1475 // Find the mid-point of the area containing the glyph
1476 const float glyphCenter = -glyphMetrics.xBearing + position.x + ( static_cast<float>( index ) + 0.5f ) * glyphAdvance;
1478 if( visualX < glyphCenter )
1480 visualIndex += index;
1492 // Return the logical position of the cursor in characters.
1496 visualIndex = endCharacter;
1499 logicalIndex = hasRightToLeftCharacters ? *( visualToLogicalCursorBuffer + visualIndex ) : visualIndex;
1500 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "%p closest visualIndex %d logicalIndex %d\n", this, visualIndex, logicalIndex );
1502 DALI_ASSERT_DEBUG( ( logicalIndex <= mLogicalModel->mText.Count() && logicalIndex >= 0 ) && "GetClosestCursorIndex - Out of bounds index" );
1504 return logicalIndex;
1507 void Controller::Impl::GetCursorPosition( CharacterIndex logical,
1508 CursorInfo& cursorInfo )
1510 // TODO: Check for multiline with \n, etc...
1512 // Check if the logical position is the first or the last one of the text.
1513 const bool isFirstPosition = 0u == logical;
1514 const bool isLastPosition = mLogicalModel->mText.Count() == logical;
1516 if( isFirstPosition && isLastPosition )
1518 // There is zero characters. Get the default font's line height.
1519 cursorInfo.lineHeight = GetDefaultFontLineHeight();
1520 cursorInfo.primaryCursorHeight = cursorInfo.lineHeight;
1522 cursorInfo.primaryPosition.x = mEventData->mDecorator->GetCursorWidth();
1523 cursorInfo.primaryPosition.y = 0.f;
1525 // Nothing else to do.
1529 // 'logical' is the logical 'cursor' index.
1530 // Get the next and current logical 'character' index.
1531 const CharacterIndex nextCharacterIndex = logical;
1532 const CharacterIndex characterIndex = isFirstPosition ? logical : logical - 1u;
1534 // Get the direction of the character and the next one.
1535 const CharacterDirection* const modelCharacterDirectionsBuffer = ( 0u != mLogicalModel->mCharacterDirections.Count() ) ? mLogicalModel->mCharacterDirections.Begin() : NULL;
1537 CharacterDirection isCurrentRightToLeft = false;
1538 CharacterDirection isNextRightToLeft = false;
1539 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
1541 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + characterIndex );
1542 isNextRightToLeft = *( modelCharacterDirectionsBuffer + nextCharacterIndex );
1545 // Get the line where the character is laid-out.
1546 const LineRun* const modelLines = mVisualModel->mLines.Begin();
1548 const LineIndex lineIndex = mVisualModel->GetLineOfCharacter( characterIndex );
1549 const LineRun& line = *( modelLines + lineIndex );
1551 // Get the paragraph's direction.
1552 const CharacterDirection isRightToLeftParagraph = line.direction;
1554 // Check whether there is an alternative position:
1556 cursorInfo.isSecondaryCursor = ( !isLastPosition && ( isCurrentRightToLeft != isNextRightToLeft ) ) ||
1557 ( isLastPosition && ( isRightToLeftParagraph != isCurrentRightToLeft ) );
1559 // Set the line height.
1560 cursorInfo.lineHeight = line.ascender + -line.descender;
1562 // Calculate the primary cursor.
1564 CharacterIndex index = characterIndex;
1565 if( cursorInfo.isSecondaryCursor )
1567 // If there is a secondary position, the primary cursor may be in a different place than the logical index.
1569 if( isLastPosition )
1571 // The position of the cursor after the last character needs special
1572 // care depending on its direction and the direction of the paragraph.
1574 // Need to find the first character after the last character with the paragraph's direction.
1575 // i.e l0 l1 l2 r0 r1 should find r0.
1577 // TODO: check for more than one line!
1578 index = isRightToLeftParagraph ? line.characterRun.characterIndex : line.characterRun.characterIndex + line.characterRun.numberOfCharacters - 1u;
1579 index = mLogicalModel->GetLogicalCharacterIndex( index );
1583 index = ( isRightToLeftParagraph == isCurrentRightToLeft ) ? characterIndex : nextCharacterIndex;
1587 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
1588 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
1589 const Length* const charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
1590 const CharacterIndex* const glyphsToCharactersBuffer = mVisualModel->mGlyphsToCharacters.Begin();
1591 const Vector2* const glyphPositionsBuffer = mVisualModel->mGlyphPositions.Begin();
1593 // Convert the cursor position into the glyph position.
1594 const GlyphIndex primaryGlyphIndex = *( charactersToGlyphBuffer + index );
1595 const Length primaryNumberOfGlyphs = *( glyphsPerCharacterBuffer + index );
1596 const Length primaryNumberOfCharacters = *( charactersPerGlyphBuffer + primaryGlyphIndex );
1598 // Get the metrics for the group of glyphs.
1599 GlyphMetrics glyphMetrics;
1600 GetGlyphsMetrics( primaryGlyphIndex,
1601 primaryNumberOfGlyphs,
1606 // Whether to add the glyph's advance to the cursor position.
1607 // 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,
1608 // if the logical cursor is one, the position is the position of the first glyph and the advance is added.
1609 // A 'truth table' was build and an online Karnaugh map tool was used to simplify the logic.
1630 // Where F -> isFirstPosition
1631 // L -> isLastPosition
1632 // C -> isCurrentRightToLeft
1633 // P -> isRightToLeftParagraph
1634 // A -> Whether to add the glyph's advance.
1636 const bool addGlyphAdvance = ( ( isLastPosition && !isRightToLeftParagraph ) ||
1637 ( isFirstPosition && isRightToLeftParagraph ) ||
1638 ( !isFirstPosition && !isLastPosition && !isCurrentRightToLeft ) );
1640 float glyphAdvance = addGlyphAdvance ? glyphMetrics.advance : 0.f;
1642 if( !isLastPosition &&
1643 ( primaryNumberOfCharacters > 1u ) )
1645 const CharacterIndex firstIndex = *( glyphsToCharactersBuffer + primaryGlyphIndex );
1647 bool isCurrentRightToLeft = false;
1648 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
1650 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + index );
1653 Length numberOfGlyphAdvance = ( isFirstPosition ? 0u : 1u ) + characterIndex - firstIndex;
1654 if( isCurrentRightToLeft )
1656 numberOfGlyphAdvance = primaryNumberOfCharacters - numberOfGlyphAdvance;
1659 glyphAdvance = static_cast<float>( numberOfGlyphAdvance ) * glyphMetrics.advance / static_cast<float>( primaryNumberOfCharacters );
1662 // Get the glyph position and x bearing.
1663 const Vector2& primaryPosition = *( glyphPositionsBuffer + primaryGlyphIndex );
1665 // Set the primary cursor's height.
1666 cursorInfo.primaryCursorHeight = cursorInfo.isSecondaryCursor ? 0.5f * glyphMetrics.fontHeight : glyphMetrics.fontHeight;
1668 // Set the primary cursor's position.
1669 cursorInfo.primaryPosition.x = -glyphMetrics.xBearing + primaryPosition.x + glyphAdvance;
1670 cursorInfo.primaryPosition.y = line.ascender - glyphMetrics.ascender;
1672 // Calculate the secondary cursor.
1674 if( cursorInfo.isSecondaryCursor )
1676 // Set the secondary cursor's height.
1677 cursorInfo.secondaryCursorHeight = 0.5f * glyphMetrics.fontHeight;
1679 CharacterIndex index = characterIndex;
1680 if( !isLastPosition )
1682 index = ( isRightToLeftParagraph == isCurrentRightToLeft ) ? nextCharacterIndex : characterIndex;
1685 const GlyphIndex secondaryGlyphIndex = *( charactersToGlyphBuffer + index );
1686 const Length secondaryNumberOfGlyphs = *( glyphsPerCharacterBuffer + index );
1688 const Vector2& secondaryPosition = *( glyphPositionsBuffer + secondaryGlyphIndex );
1690 GetGlyphsMetrics( secondaryGlyphIndex,
1691 secondaryNumberOfGlyphs,
1696 // Set the secondary cursor's position.
1697 cursorInfo.secondaryPosition.x = -glyphMetrics.xBearing + secondaryPosition.x + ( isCurrentRightToLeft ? 0.f : glyphMetrics.advance );
1698 cursorInfo.secondaryPosition.y = cursorInfo.lineHeight - cursorInfo.secondaryCursorHeight - line.descender - ( glyphMetrics.fontHeight - glyphMetrics.ascender );
1702 CharacterIndex Controller::Impl::CalculateNewCursorIndex( CharacterIndex index ) const
1704 if( NULL == mEventData )
1706 // Nothing to do if there is no text input.
1710 CharacterIndex cursorIndex = mEventData->mPrimaryCursorPosition;
1712 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
1713 const Length* const charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
1715 GlyphIndex glyphIndex = *( charactersToGlyphBuffer + index );
1716 Length numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
1718 if( numberOfCharacters > 1u )
1720 const Script script = mLogicalModel->GetScript( index );
1721 if( HasLigatureMustBreak( script ) )
1723 // Prevents to jump the whole Latin ligatures like fi, ff, or Arabic ï»», ...
1724 numberOfCharacters = 1u;
1729 while( 0u == numberOfCharacters )
1732 numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
1736 if( index < mEventData->mPrimaryCursorPosition )
1738 cursorIndex -= numberOfCharacters;
1742 cursorIndex += numberOfCharacters;
1748 void Controller::Impl::UpdateCursorPosition()
1750 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "-->Controller::UpdateCursorPosition %p\n", this );
1751 if( NULL == mEventData )
1753 // Nothing to do if there is no text input.
1754 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition no event data\n" );
1758 if( IsShowingPlaceholderText() || ( 0u == mLogicalModel->mText.Count() ) )
1760 // Do not want to use the place-holder text to set the cursor position.
1762 // Use the line's height of the font's family set to set the cursor's size.
1763 // If there is no font's family set, use the default font.
1764 // Use the current alignment to place the cursor at the beginning, center or end of the box.
1766 float lineHeight = 0.f;
1768 FontId defaultFontId = 0u;
1769 if( NULL == mFontDefaults )
1771 defaultFontId = mFontClient.GetFontId( EMPTY_STRING,
1776 defaultFontId = mFontDefaults->GetFontId( mFontClient );
1779 Text::FontMetrics fontMetrics;
1780 mFontClient.GetFontMetrics( defaultFontId, fontMetrics );
1782 lineHeight = fontMetrics.ascender - fontMetrics.descender;
1785 Vector2 cursorPosition;
1787 switch( mLayoutEngine.GetHorizontalAlignment() )
1789 case LayoutEngine::HORIZONTAL_ALIGN_BEGIN:
1791 cursorPosition.x = mEventData->mDecorator->GetCursorWidth();
1794 case LayoutEngine::HORIZONTAL_ALIGN_CENTER:
1796 cursorPosition.x = floor( 0.5f * mVisualModel->mControlSize.width );
1799 case LayoutEngine::HORIZONTAL_ALIGN_END:
1801 cursorPosition.x = mVisualModel->mControlSize.width;
1806 switch( mLayoutEngine.GetVerticalAlignment() )
1808 case LayoutEngine::VERTICAL_ALIGN_TOP:
1810 cursorPosition.y = 0.f;
1813 case LayoutEngine::VERTICAL_ALIGN_CENTER:
1815 cursorPosition.y = floorf( 0.5f * ( mVisualModel->mControlSize.height - lineHeight ) );
1818 case LayoutEngine::VERTICAL_ALIGN_BOTTOM:
1820 cursorPosition.y = mVisualModel->mControlSize.height - lineHeight;
1825 mEventData->mDecorator->SetPosition( PRIMARY_CURSOR,
1833 CursorInfo cursorInfo;
1834 GetCursorPosition( mEventData->mPrimaryCursorPosition,
1837 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
1838 const Vector2 cursorPosition = cursorInfo.primaryPosition + offset;
1840 // Sets the cursor position.
1841 mEventData->mDecorator->SetPosition( PRIMARY_CURSOR,
1844 cursorInfo.primaryCursorHeight,
1845 cursorInfo.lineHeight );
1846 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Primary cursor position: %f,%f\n", cursorPosition.x, cursorPosition.y );
1848 // Sets the grab handle position.
1849 mEventData->mDecorator->SetPosition( GRAB_HANDLE,
1852 cursorInfo.lineHeight );
1854 if( cursorInfo.isSecondaryCursor )
1856 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_BOTH );
1857 mEventData->mDecorator->SetPosition( SECONDARY_CURSOR,
1858 cursorInfo.secondaryPosition.x + offset.x,
1859 cursorInfo.secondaryPosition.y + offset.y,
1860 cursorInfo.secondaryCursorHeight,
1861 cursorInfo.lineHeight );
1862 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Secondary cursor position: %f,%f\n", cursorInfo.secondaryPosition.x + offset.x, cursorInfo.secondaryPosition.y + offset.y );
1866 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1869 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition\n" );
1872 void Controller::Impl::UpdateSelectionHandle( HandleType handleType )
1874 if( ( LEFT_SELECTION_HANDLE != handleType ) &&
1875 ( RIGHT_SELECTION_HANDLE != handleType ) )
1880 const bool leftSelectionHandle = LEFT_SELECTION_HANDLE == handleType;
1881 const CharacterIndex index = leftSelectionHandle ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition;
1883 CursorInfo cursorInfo;
1884 GetCursorPosition( index,
1887 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
1888 const Vector2 cursorPosition = cursorInfo.primaryPosition + offset;
1890 // Sets the grab handle position.
1891 mEventData->mDecorator->SetPosition( handleType,
1894 cursorInfo.lineHeight );
1896 // If selection handle at start of the text and other at end of the text then all text is selected.
1897 const CharacterIndex startOfSelection = std::min( mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition );
1898 const CharacterIndex endOfSelection = std::max ( mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition );
1899 mEventData->mAllTextSelected = ( startOfSelection == 0 ) && ( endOfSelection == mLogicalModel->mText.Count() );
1902 void Controller::Impl::ClampHorizontalScroll( const Vector2& actualSize )
1904 // Clamp between -space & 0 (and the text alignment).
1905 if( actualSize.width > mVisualModel->mControlSize.width )
1907 const float space = ( actualSize.width - mVisualModel->mControlSize.width ) + mAlignmentOffset.x;
1908 mEventData->mScrollPosition.x = ( mEventData->mScrollPosition.x < -space ) ? -space : mEventData->mScrollPosition.x;
1909 mEventData->mScrollPosition.x = ( mEventData->mScrollPosition.x > -mAlignmentOffset.x ) ? -mAlignmentOffset.x : mEventData->mScrollPosition.x;
1911 mEventData->mDecoratorUpdated = true;
1915 mEventData->mScrollPosition.x = 0.f;
1919 void Controller::Impl::ClampVerticalScroll( const Vector2& actualSize )
1921 // Clamp between -space & 0 (and the text alignment).
1922 if( actualSize.height > mVisualModel->mControlSize.height )
1924 const float space = ( actualSize.height - mVisualModel->mControlSize.height ) + mAlignmentOffset.y;
1925 mEventData->mScrollPosition.y = ( mEventData->mScrollPosition.y < -space ) ? -space : mEventData->mScrollPosition.y;
1926 mEventData->mScrollPosition.y = ( mEventData->mScrollPosition.y > -mAlignmentOffset.y ) ? -mAlignmentOffset.y : mEventData->mScrollPosition.y;
1928 mEventData->mDecoratorUpdated = true;
1932 mEventData->mScrollPosition.y = 0.f;
1936 void Controller::Impl::ScrollToMakePositionVisible( const Vector2& position )
1939 bool updateDecorator = false;
1940 if( position.x < 0.f )
1942 offset.x = -position.x;
1943 mEventData->mScrollPosition.x += offset.x;
1944 updateDecorator = true;
1946 else if( position.x > mVisualModel->mControlSize.width )
1948 offset.x = mVisualModel->mControlSize.width - position.x;
1949 mEventData->mScrollPosition.x += offset.x;
1950 updateDecorator = true;
1953 if( updateDecorator && mEventData->mDecorator )
1955 mEventData->mDecorator->UpdatePositions( offset );
1958 // TODO : calculate the vertical scroll.
1961 void Controller::Impl::ScrollTextToMatchCursor()
1963 // Get the current cursor position in decorator coords.
1964 const Vector2& currentCursorPosition = mEventData->mDecorator->GetPosition( PRIMARY_CURSOR );
1966 // Calculate the new cursor position.
1967 CursorInfo cursorInfo;
1968 GetCursorPosition( mEventData->mPrimaryCursorPosition,
1971 // Calculate the offset to match the cursor position before the character was deleted.
1972 mEventData->mScrollPosition.x = currentCursorPosition.x - cursorInfo.primaryPosition.x - mAlignmentOffset.x;
1974 ClampHorizontalScroll( mVisualModel->GetActualSize() );
1976 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
1977 const Vector2 cursorPosition = cursorInfo.primaryPosition + offset;
1979 // Sets the cursor position.
1980 mEventData->mDecorator->SetPosition( PRIMARY_CURSOR,
1983 cursorInfo.primaryCursorHeight,
1984 cursorInfo.lineHeight );
1986 // Sets the grab handle position.
1987 mEventData->mDecorator->SetPosition( GRAB_HANDLE,
1990 cursorInfo.lineHeight );
1992 if( cursorInfo.isSecondaryCursor )
1994 mEventData->mDecorator->SetPosition( SECONDARY_CURSOR,
1995 cursorInfo.secondaryPosition.x + offset.x,
1996 cursorInfo.secondaryPosition.y + offset.y,
1997 cursorInfo.secondaryCursorHeight,
1998 cursorInfo.lineHeight );
1999 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Secondary cursor position: %f,%f\n", cursorInfo.secondaryPosition.x + offset.x, cursorInfo.secondaryPosition.y + offset.y );
2002 // Set which cursors are active according the state.
2003 if( ( EventData::EDITING == mEventData->mState ) ||
2004 ( EventData::EDITING_WITH_POPUP == mEventData->mState ) ||
2005 ( EventData::EDITING_WITH_GRAB_HANDLE == mEventData->mState ) ||
2006 ( EventData::GRAB_HANDLE_PANNING == mEventData->mState ) )
2008 if( cursorInfo.isSecondaryCursor )
2010 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_BOTH );
2014 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
2019 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
2023 void Controller::Impl::RequestRelayout()
2025 mControlInterface.RequestTextRelayout();
2030 } // namespace Toolkit