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 const bool handlesCrossed = mEventData->mLeftSelectionPosition > mEventData->mRightSelectionPosition;
879 //Get start and end position of selection
880 uint32_t startOfSelectedText = handlesCrossed ? mEventData->mRightSelectionPosition : mEventData->mLeftSelectionPosition;
881 uint32_t lengthOfSelectedText = ( handlesCrossed ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition ) - startOfSelectedText;
883 // Validate the start and end selection points
884 if( ( 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 = handlesCrossed ? mEventData->mRightSelectionPosition : 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 const bool isLastCharacter = selectionEnd >= mLogicalModel->mText.Count();
974 const bool startDirection = ( ( NULL == modelCharacterDirectionsBuffer ) ? false : *( modelCharacterDirectionsBuffer + selectionStart ) );
975 const bool endDirection = ( ( NULL == modelCharacterDirectionsBuffer ) ? false : *( modelCharacterDirectionsBuffer + ( selectionEnd - ( isLastCharacter ? 1u : 0u ) ) ) );
977 // Swap the indices if the start is greater than the end.
978 const bool indicesSwapped = selectionStart > selectionEnd;
980 // Tell the decorator to flip the selection handles if needed.
981 mEventData->mDecorator->SetSelectionHandleFlipState( indicesSwapped, startDirection, endDirection );
985 std::swap( selectionStart, selectionEnd );
988 // Get the indices to the first and last selected glyphs.
989 const CharacterIndex selectionEndMinusOne = selectionEnd - 1u;
990 const GlyphIndex glyphStart = *( charactersToGlyphBuffer + selectionStart );
991 const Length numberOfGlyphs = *( glyphsPerCharacterBuffer + selectionEndMinusOne );
992 const GlyphIndex glyphEnd = *( charactersToGlyphBuffer + selectionEndMinusOne ) + ( ( numberOfGlyphs > 0 ) ? numberOfGlyphs - 1u : 0u );
994 // Check if the first glyph is a ligature that must be broken like Latin ff, fi, or Arabic ï»», etc which needs special code.
995 const Length numberOfCharactersStart = *( charactersPerGlyphBuffer + glyphStart );
996 bool splitStartGlyph = ( numberOfCharactersStart > 1u ) && HasLigatureMustBreak( mLogicalModel->GetScript( selectionStart ) );
998 // Check if the last glyph is a ligature that must be broken like Latin ff, fi, or Arabic ï»», etc which needs special code.
999 const Length numberOfCharactersEnd = *( charactersPerGlyphBuffer + glyphEnd );
1000 bool splitEndGlyph = ( glyphStart != glyphEnd ) && ( numberOfCharactersEnd > 1u ) && HasLigatureMustBreak( mLogicalModel->GetScript( selectionEndMinusOne ) );
1002 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
1004 // Traverse the glyphs.
1005 for( GlyphIndex index = glyphStart; index <= glyphEnd; ++index )
1007 const GlyphInfo& glyph = *( glyphsBuffer + index );
1008 const Vector2& position = *( positionsBuffer + index );
1010 if( splitStartGlyph )
1012 // 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.
1014 const float glyphAdvance = glyph.advance / static_cast<float>( numberOfCharactersStart );
1015 const CharacterIndex interGlyphIndex = selectionStart - *( glyphToCharacterBuffer + glyphStart );
1016 // Get the direction of the character.
1017 CharacterDirection isCurrentRightToLeft = false;
1018 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
1020 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + selectionStart );
1023 // The end point could be in the middle of the ligature.
1024 // Calculate the number of characters selected.
1025 const Length numberOfCharacters = ( glyphStart == glyphEnd ) ? ( selectionEnd - selectionStart ) : ( numberOfCharactersStart - interGlyphIndex );
1027 const float xPosition = position.x - glyph.xBearing + offset.x + glyphAdvance * static_cast<float>( isCurrentRightToLeft ? ( numberOfCharactersStart - interGlyphIndex - numberOfCharacters ) : interGlyphIndex );
1029 mEventData->mDecorator->AddHighlight( xPosition,
1031 xPosition + static_cast<float>( numberOfCharacters ) * glyphAdvance,
1032 offset.y + height );
1034 splitStartGlyph = false;
1038 if( splitEndGlyph && ( index == glyphEnd ) )
1040 // 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.
1042 const float glyphAdvance = glyph.advance / static_cast<float>( numberOfCharactersEnd );
1043 const CharacterIndex interGlyphIndex = selectionEnd - *( glyphToCharacterBuffer + glyphEnd );
1044 // Get the direction of the character.
1045 CharacterDirection isCurrentRightToLeft = false;
1046 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
1048 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + selectionEnd );
1051 const Length numberOfCharacters = numberOfCharactersEnd - interGlyphIndex;
1053 const float xPosition = position.x - glyph.xBearing + offset.x + ( isCurrentRightToLeft ? ( glyphAdvance * static_cast<float>( numberOfCharacters ) ) : 0.f );
1054 mEventData->mDecorator->AddHighlight( xPosition,
1056 xPosition + static_cast<float>( interGlyphIndex ) * glyphAdvance,
1057 offset.y + height );
1059 splitEndGlyph = false;
1063 const float xPosition = position.x - glyph.xBearing + offset.x;
1064 mEventData->mDecorator->AddHighlight( xPosition,
1066 xPosition + glyph.advance,
1067 offset.y + height );
1070 CursorInfo primaryCursorInfo;
1071 GetCursorPosition( mEventData->mLeftSelectionPosition,
1072 primaryCursorInfo );
1074 CursorInfo secondaryCursorInfo;
1075 GetCursorPosition( mEventData->mRightSelectionPosition,
1076 secondaryCursorInfo );
1078 const Vector2 primaryPosition = primaryCursorInfo.primaryPosition + offset;
1079 const Vector2 secondaryPosition = secondaryCursorInfo.primaryPosition + offset;
1081 mEventData->mDecorator->SetPosition( LEFT_SELECTION_HANDLE, primaryPosition.x, primaryPosition.y, primaryCursorInfo.lineHeight );
1083 mEventData->mDecorator->SetPosition( RIGHT_SELECTION_HANDLE, secondaryPosition.x, secondaryPosition.y, secondaryCursorInfo.lineHeight );
1085 // Cursor to be positioned at end of selection so if selection interrupted and edit mode restarted the cursor will be at end of selection
1086 mEventData->mPrimaryCursorPosition = ( indicesSwapped ) ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition;
1088 // Set the flag to update the decorator.
1089 mEventData->mDecoratorUpdated = true;
1092 void Controller::Impl::RepositionSelectionHandles( float visualX, float visualY )
1094 if( NULL == mEventData )
1096 // Nothing to do if there is no text input.
1100 if( IsShowingPlaceholderText() )
1102 // Nothing to do if there is the place-holder text.
1106 const Length numberOfGlyphs = mVisualModel->mGlyphs.Count();
1107 const Length numberOfLines = mVisualModel->mLines.Count();
1108 if( 0 == numberOfGlyphs ||
1109 0 == numberOfLines )
1111 // Nothing to do if there is no text.
1115 // Find which word was selected
1116 CharacterIndex selectionStart( 0 );
1117 CharacterIndex selectionEnd( 0 );
1118 FindSelectionIndices( visualX, visualY, selectionStart, selectionEnd );
1119 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "%p selectionStart %d selectionEnd %d\n", this, selectionStart, selectionEnd );
1121 if( selectionStart == selectionEnd )
1123 ChangeState( EventData::EDITING );
1124 // Nothing to select. i.e. a white space, out of bounds
1128 RepositionSelectionHandles( selectionStart, selectionEnd );
1131 void Controller::Impl::SetPopupButtons()
1134 * Sets the Popup buttons to be shown depending on State.
1136 * If SELECTING : CUT & COPY + ( PASTE & CLIPBOARD if content available to paste )
1138 * If EDITING_WITH_POPUP : SELECT & SELECT_ALL
1141 TextSelectionPopup::Buttons buttonsToShow = TextSelectionPopup::NONE;
1143 if ( ( EventData::SELECTING == mEventData->mState ) || ( EventData::SELECTION_CHANGED == mEventData->mState ) )
1145 buttonsToShow = TextSelectionPopup::Buttons( TextSelectionPopup::CUT | TextSelectionPopup::COPY );
1147 if ( !IsClipboardEmpty() )
1149 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
1150 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::CLIPBOARD ) );
1153 if ( !mEventData->mAllTextSelected )
1155 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::SELECT_ALL ) );
1158 else if ( EventData::EDITING_WITH_POPUP == mEventData->mState )
1160 if ( mLogicalModel->mText.Count() && !IsShowingPlaceholderText())
1162 buttonsToShow = TextSelectionPopup::Buttons( TextSelectionPopup::SELECT | TextSelectionPopup::SELECT_ALL );
1165 if ( !IsClipboardEmpty() )
1167 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
1168 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::CLIPBOARD ) );
1172 mEventData->mDecorator->SetEnabledPopupButtons( buttonsToShow );
1175 void Controller::Impl::ChangeState( EventData::State newState )
1177 if( NULL == mEventData )
1179 // Nothing to do if there is no text input.
1183 if( mEventData->mState != newState )
1185 mEventData->mState = newState;
1187 if( EventData::INACTIVE == mEventData->mState )
1189 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
1190 mEventData->mDecorator->StopCursorBlink();
1191 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1192 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1193 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1194 mEventData->mDecorator->SetPopupActive( false );
1195 mEventData->mDecoratorUpdated = true;
1198 else if ( EventData::INTERRUPTED == mEventData->mState)
1200 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1201 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1202 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1203 mEventData->mDecorator->SetPopupActive( false );
1204 mEventData->mDecoratorUpdated = true;
1207 else if ( EventData::SELECTING == mEventData->mState )
1209 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
1210 mEventData->mDecorator->StopCursorBlink();
1211 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1212 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true );
1213 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true );
1214 if( mEventData->mGrabHandlePopupEnabled )
1217 mEventData->mDecorator->SetPopupActive( true );
1219 mEventData->mDecoratorUpdated = true;
1221 else if ( EventData::SELECTION_CHANGED == mEventData->mState )
1223 if( mEventData->mGrabHandlePopupEnabled )
1226 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
1227 mEventData->mDecorator->SetPopupActive( true );
1229 mEventData->mDecoratorUpdated = true;
1231 else if( EventData::EDITING == mEventData->mState )
1233 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1234 if( mEventData->mCursorBlinkEnabled )
1236 mEventData->mDecorator->StartCursorBlink();
1238 // Grab handle is not shown until a tap is received whilst EDITING
1239 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1240 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1241 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1242 if( mEventData->mGrabHandlePopupEnabled )
1244 mEventData->mDecorator->SetPopupActive( false );
1246 mEventData->mDecoratorUpdated = true;
1249 else if( EventData::EDITING_WITH_POPUP == mEventData->mState )
1251 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1252 if( mEventData->mCursorBlinkEnabled )
1254 mEventData->mDecorator->StartCursorBlink();
1256 if( mEventData->mSelectionEnabled )
1258 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1259 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1263 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
1265 if( mEventData->mGrabHandlePopupEnabled )
1268 mEventData->mDecorator->SetPopupActive( true );
1271 mEventData->mDecoratorUpdated = true;
1273 else if( EventData::EDITING_WITH_GRAB_HANDLE == mEventData->mState )
1275 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1276 if( mEventData->mCursorBlinkEnabled )
1278 mEventData->mDecorator->StartCursorBlink();
1280 // Grab handle is not shown until a tap is received whilst EDITING
1281 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
1282 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1283 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1284 if( mEventData->mGrabHandlePopupEnabled )
1286 mEventData->mDecorator->SetPopupActive( false );
1288 mEventData->mDecoratorUpdated = true;
1291 else if ( EventData::SELECTION_HANDLE_PANNING == mEventData->mState )
1293 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
1294 mEventData->mDecorator->StopCursorBlink();
1295 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1296 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true );
1297 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true );
1298 if( mEventData->mGrabHandlePopupEnabled )
1300 mEventData->mDecorator->SetPopupActive( false );
1302 mEventData->mDecoratorUpdated = true;
1304 else if ( EventData::GRAB_HANDLE_PANNING == mEventData->mState )
1306 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1307 if( mEventData->mCursorBlinkEnabled )
1309 mEventData->mDecorator->StartCursorBlink();
1311 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
1312 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1313 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1314 if( mEventData->mGrabHandlePopupEnabled )
1316 mEventData->mDecorator->SetPopupActive( false );
1318 mEventData->mDecoratorUpdated = true;
1323 LineIndex Controller::Impl::GetClosestLine( float y ) const
1325 float totalHeight = 0.f;
1326 LineIndex lineIndex = 0u;
1328 const Vector<LineRun>& lines = mVisualModel->mLines;
1329 for( LineIndex endLine = lines.Count();
1330 lineIndex < endLine;
1333 const LineRun& lineRun = lines[lineIndex];
1334 totalHeight += lineRun.ascender + -lineRun.descender;
1335 if( y < totalHeight )
1341 if( lineIndex == 0 )
1349 void Controller::Impl::FindSelectionIndices( float visualX, float visualY, CharacterIndex& startIndex, CharacterIndex& endIndex )
1351 CharacterIndex hitCharacter = GetClosestCursorIndex( visualX, visualY );
1352 DALI_ASSERT_DEBUG( hitCharacter <= mLogicalModel->mText.Count() && "GetClosestCursorIndex returned out of bounds index" );
1354 if ( mLogicalModel->mText.Count() == 0 )
1356 return; // if model empty
1359 if( hitCharacter >= mLogicalModel->mText.Count() )
1361 // Closest hit character is the last character.
1362 if ( hitCharacter == mLogicalModel->mText.Count() )
1364 hitCharacter--; //Hit character index set to last character in logical model
1368 // hitCharacter is out of bounds
1373 startIndex = hitCharacter;
1374 endIndex = hitCharacter;
1376 if( !TextAbstraction::IsWhiteSpace( mLogicalModel->mText[hitCharacter] ) )
1378 // Find the start and end of the text
1379 for( startIndex = hitCharacter; startIndex > 0; --startIndex )
1381 Character charCode = mLogicalModel->mText[ startIndex-1 ];
1382 if( TextAbstraction::IsWhiteSpace( charCode ) )
1387 const CharacterIndex pastTheEnd = mLogicalModel->mText.Count();
1388 for( endIndex = hitCharacter + 1u; endIndex < pastTheEnd; ++endIndex )
1390 Character charCode = mLogicalModel->mText[ endIndex ];
1391 if( TextAbstraction::IsWhiteSpace( charCode ) )
1399 CharacterIndex Controller::Impl::GetClosestCursorIndex( float visualX,
1402 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "GetClosestCursorIndex %p closest visualX %f visualY %f\n", this, visualX, visualY );
1404 if( NULL == mEventData )
1406 // Nothing to do if there is no text input.
1410 CharacterIndex logicalIndex = 0u;
1412 const Length numberOfGlyphs = mVisualModel->mGlyphs.Count();
1413 const Length numberOfLines = mVisualModel->mLines.Count();
1414 if( 0 == numberOfGlyphs ||
1415 0 == numberOfLines )
1417 return logicalIndex;
1420 // Find which line is closest
1421 const LineIndex lineIndex = GetClosestLine( visualY );
1422 const LineRun& line = mVisualModel->mLines[lineIndex];
1424 // Get the positions of the glyphs.
1425 const Vector<Vector2>& positions = mVisualModel->mGlyphPositions;
1426 const Vector2* const positionsBuffer = positions.Begin();
1428 // Get the visual to logical conversion tables.
1429 const CharacterIndex* const visualToLogicalBuffer = ( 0u != mLogicalModel->mVisualToLogicalMap.Count() ) ? mLogicalModel->mVisualToLogicalMap.Begin() : NULL;
1430 const CharacterIndex* const visualToLogicalCursorBuffer = mLogicalModel->mVisualToLogicalCursorMap.Begin();
1432 // Get the character to glyph conversion table.
1433 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
1435 // Get the glyphs per character table.
1436 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
1437 const Length* const charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
1439 // If the vector is void, there is no right to left characters.
1440 const bool hasRightToLeftCharacters = NULL != visualToLogicalBuffer;
1442 const CharacterIndex startCharacter = line.characterRun.characterIndex;
1443 const CharacterIndex endCharacter = line.characterRun.characterIndex + line.characterRun.numberOfCharacters;
1444 DALI_ASSERT_DEBUG( endCharacter <= mLogicalModel->mText.Count() && "Invalid line info" );
1446 // Whether there is a hit on a glyph.
1447 bool matched = false;
1449 // Traverses glyphs in visual order. To do that use the visual to logical conversion table.
1450 CharacterIndex visualIndex = startCharacter;
1451 for( ; !matched && ( visualIndex < endCharacter ); ++visualIndex )
1453 // The character in logical order.
1454 const CharacterIndex characterLogicalOrderIndex = hasRightToLeftCharacters ? *( visualToLogicalBuffer + visualIndex ) : visualIndex;
1456 // Get the script of the character.
1457 const Script script = mLogicalModel->GetScript( characterLogicalOrderIndex );
1459 // The first glyph for that character in logical order.
1460 const GlyphIndex glyphLogicalOrderIndex = *( charactersToGlyphBuffer + characterLogicalOrderIndex );
1461 // The number of glyphs for that character
1462 const Length numberOfGlyphs = *( glyphsPerCharacterBuffer + characterLogicalOrderIndex );
1464 // Get the metrics for the group of glyphs.
1465 GlyphMetrics glyphMetrics;
1466 GetGlyphsMetrics( glyphLogicalOrderIndex,
1472 const Vector2& position = *( positionsBuffer + glyphLogicalOrderIndex );
1474 // Prevents to jump the whole Latin ligatures like fi, ff, or Arabic ï»»...
1475 const Length numberOfCharactersInLigature = HasLigatureMustBreak( script ) ? *( charactersPerGlyphBuffer + glyphLogicalOrderIndex ) : 1u;
1476 const float glyphAdvance = glyphMetrics.advance / static_cast<float>( numberOfCharactersInLigature );
1478 for( GlyphIndex index = 0u; !matched && ( index < numberOfCharactersInLigature ); ++index )
1480 // Find the mid-point of the area containing the glyph
1481 const float glyphCenter = -glyphMetrics.xBearing + position.x + ( static_cast<float>( index ) + 0.5f ) * glyphAdvance;
1483 if( visualX < glyphCenter )
1485 visualIndex += index;
1497 // Return the logical position of the cursor in characters.
1501 visualIndex = endCharacter;
1504 logicalIndex = hasRightToLeftCharacters ? *( visualToLogicalCursorBuffer + visualIndex ) : visualIndex;
1505 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "%p closest visualIndex %d logicalIndex %d\n", this, visualIndex, logicalIndex );
1507 DALI_ASSERT_DEBUG( ( logicalIndex <= mLogicalModel->mText.Count() && logicalIndex >= 0 ) && "GetClosestCursorIndex - Out of bounds index" );
1509 return logicalIndex;
1512 void Controller::Impl::GetCursorPosition( CharacterIndex logical,
1513 CursorInfo& cursorInfo )
1515 // TODO: Check for multiline with \n, etc...
1517 // Check if the logical position is the first or the last one of the text.
1518 const bool isFirstPosition = 0u == logical;
1519 const bool isLastPosition = mLogicalModel->mText.Count() == logical;
1521 if( isFirstPosition && isLastPosition )
1523 // There is zero characters. Get the default font's line height.
1524 cursorInfo.lineHeight = GetDefaultFontLineHeight();
1525 cursorInfo.primaryCursorHeight = cursorInfo.lineHeight;
1527 cursorInfo.primaryPosition.x = mEventData->mDecorator->GetCursorWidth();
1528 cursorInfo.primaryPosition.y = 0.f;
1530 // Nothing else to do.
1534 // 'logical' is the logical 'cursor' index.
1535 // Get the next and current logical 'character' index.
1536 const CharacterIndex nextCharacterIndex = logical;
1537 const CharacterIndex characterIndex = isFirstPosition ? logical : logical - 1u;
1539 // Get the direction of the character and the next one.
1540 const CharacterDirection* const modelCharacterDirectionsBuffer = ( 0u != mLogicalModel->mCharacterDirections.Count() ) ? mLogicalModel->mCharacterDirections.Begin() : NULL;
1542 CharacterDirection isCurrentRightToLeft = false;
1543 CharacterDirection isNextRightToLeft = false;
1544 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
1546 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + characterIndex );
1547 isNextRightToLeft = *( modelCharacterDirectionsBuffer + nextCharacterIndex );
1550 // Get the line where the character is laid-out.
1551 const LineRun* const modelLines = mVisualModel->mLines.Begin();
1553 const LineIndex lineIndex = mVisualModel->GetLineOfCharacter( characterIndex );
1554 const LineRun& line = *( modelLines + lineIndex );
1556 // Get the paragraph's direction.
1557 const CharacterDirection isRightToLeftParagraph = line.direction;
1559 // Check whether there is an alternative position:
1561 cursorInfo.isSecondaryCursor = ( !isLastPosition && ( isCurrentRightToLeft != isNextRightToLeft ) ) ||
1562 ( isLastPosition && ( isRightToLeftParagraph != isCurrentRightToLeft ) );
1564 // Set the line height.
1565 cursorInfo.lineHeight = line.ascender + -line.descender;
1567 // Calculate the primary cursor.
1569 CharacterIndex index = characterIndex;
1570 if( cursorInfo.isSecondaryCursor )
1572 // If there is a secondary position, the primary cursor may be in a different place than the logical index.
1574 if( isLastPosition )
1576 // The position of the cursor after the last character needs special
1577 // care depending on its direction and the direction of the paragraph.
1579 // Need to find the first character after the last character with the paragraph's direction.
1580 // i.e l0 l1 l2 r0 r1 should find r0.
1582 // TODO: check for more than one line!
1583 index = isRightToLeftParagraph ? line.characterRun.characterIndex : line.characterRun.characterIndex + line.characterRun.numberOfCharacters - 1u;
1584 index = mLogicalModel->GetLogicalCharacterIndex( index );
1588 index = ( isRightToLeftParagraph == isCurrentRightToLeft ) ? characterIndex : nextCharacterIndex;
1592 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
1593 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
1594 const Length* const charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
1595 const CharacterIndex* const glyphsToCharactersBuffer = mVisualModel->mGlyphsToCharacters.Begin();
1596 const Vector2* const glyphPositionsBuffer = mVisualModel->mGlyphPositions.Begin();
1598 // Convert the cursor position into the glyph position.
1599 const GlyphIndex primaryGlyphIndex = *( charactersToGlyphBuffer + index );
1600 const Length primaryNumberOfGlyphs = *( glyphsPerCharacterBuffer + index );
1601 const Length primaryNumberOfCharacters = *( charactersPerGlyphBuffer + primaryGlyphIndex );
1603 // Get the metrics for the group of glyphs.
1604 GlyphMetrics glyphMetrics;
1605 GetGlyphsMetrics( primaryGlyphIndex,
1606 primaryNumberOfGlyphs,
1611 // Whether to add the glyph's advance to the cursor position.
1612 // 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,
1613 // if the logical cursor is one, the position is the position of the first glyph and the advance is added.
1614 // A 'truth table' was build and an online Karnaugh map tool was used to simplify the logic.
1635 // Where F -> isFirstPosition
1636 // L -> isLastPosition
1637 // C -> isCurrentRightToLeft
1638 // P -> isRightToLeftParagraph
1639 // A -> Whether to add the glyph's advance.
1641 const bool addGlyphAdvance = ( ( isLastPosition && !isRightToLeftParagraph ) ||
1642 ( isFirstPosition && isRightToLeftParagraph ) ||
1643 ( !isFirstPosition && !isLastPosition && !isCurrentRightToLeft ) );
1645 float glyphAdvance = addGlyphAdvance ? glyphMetrics.advance : 0.f;
1647 if( !isLastPosition &&
1648 ( primaryNumberOfCharacters > 1u ) )
1650 const CharacterIndex firstIndex = *( glyphsToCharactersBuffer + primaryGlyphIndex );
1652 bool isCurrentRightToLeft = false;
1653 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
1655 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + index );
1658 Length numberOfGlyphAdvance = ( isFirstPosition ? 0u : 1u ) + characterIndex - firstIndex;
1659 if( isCurrentRightToLeft )
1661 numberOfGlyphAdvance = primaryNumberOfCharacters - numberOfGlyphAdvance;
1664 glyphAdvance = static_cast<float>( numberOfGlyphAdvance ) * glyphMetrics.advance / static_cast<float>( primaryNumberOfCharacters );
1667 // Get the glyph position and x bearing.
1668 const Vector2& primaryPosition = *( glyphPositionsBuffer + primaryGlyphIndex );
1670 // Set the primary cursor's height.
1671 cursorInfo.primaryCursorHeight = cursorInfo.isSecondaryCursor ? 0.5f * glyphMetrics.fontHeight : glyphMetrics.fontHeight;
1673 // Set the primary cursor's position.
1674 cursorInfo.primaryPosition.x = -glyphMetrics.xBearing + primaryPosition.x + glyphAdvance;
1675 cursorInfo.primaryPosition.y = line.ascender - glyphMetrics.ascender;
1677 // Calculate the secondary cursor.
1679 if( cursorInfo.isSecondaryCursor )
1681 // Set the secondary cursor's height.
1682 cursorInfo.secondaryCursorHeight = 0.5f * glyphMetrics.fontHeight;
1684 CharacterIndex index = characterIndex;
1685 if( !isLastPosition )
1687 index = ( isRightToLeftParagraph == isCurrentRightToLeft ) ? nextCharacterIndex : characterIndex;
1690 const GlyphIndex secondaryGlyphIndex = *( charactersToGlyphBuffer + index );
1691 const Length secondaryNumberOfGlyphs = *( glyphsPerCharacterBuffer + index );
1693 const Vector2& secondaryPosition = *( glyphPositionsBuffer + secondaryGlyphIndex );
1695 GetGlyphsMetrics( secondaryGlyphIndex,
1696 secondaryNumberOfGlyphs,
1701 // Set the secondary cursor's position.
1702 cursorInfo.secondaryPosition.x = -glyphMetrics.xBearing + secondaryPosition.x + ( isCurrentRightToLeft ? 0.f : glyphMetrics.advance );
1703 cursorInfo.secondaryPosition.y = cursorInfo.lineHeight - cursorInfo.secondaryCursorHeight - line.descender - ( glyphMetrics.fontHeight - glyphMetrics.ascender );
1707 CharacterIndex Controller::Impl::CalculateNewCursorIndex( CharacterIndex index ) const
1709 if( NULL == mEventData )
1711 // Nothing to do if there is no text input.
1715 CharacterIndex cursorIndex = mEventData->mPrimaryCursorPosition;
1717 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
1718 const Length* const charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
1720 GlyphIndex glyphIndex = *( charactersToGlyphBuffer + index );
1721 Length numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
1723 if( numberOfCharacters > 1u )
1725 const Script script = mLogicalModel->GetScript( index );
1726 if( HasLigatureMustBreak( script ) )
1728 // Prevents to jump the whole Latin ligatures like fi, ff, or Arabic ï»», ...
1729 numberOfCharacters = 1u;
1734 while( 0u == numberOfCharacters )
1737 numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
1741 if( index < mEventData->mPrimaryCursorPosition )
1743 cursorIndex -= numberOfCharacters;
1747 cursorIndex += numberOfCharacters;
1753 void Controller::Impl::UpdateCursorPosition()
1755 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "-->Controller::UpdateCursorPosition %p\n", this );
1756 if( NULL == mEventData )
1758 // Nothing to do if there is no text input.
1759 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition no event data\n" );
1763 if( IsShowingPlaceholderText() || ( 0u == mLogicalModel->mText.Count() ) )
1765 // Do not want to use the place-holder text to set the cursor position.
1767 // Use the line's height of the font's family set to set the cursor's size.
1768 // If there is no font's family set, use the default font.
1769 // Use the current alignment to place the cursor at the beginning, center or end of the box.
1771 float lineHeight = 0.f;
1773 FontId defaultFontId = 0u;
1774 if( NULL == mFontDefaults )
1776 TextAbstraction::FontDescription fontDescription;
1777 defaultFontId = mFontClient.GetFontId( fontDescription );
1781 defaultFontId = mFontDefaults->GetFontId( mFontClient );
1784 Text::FontMetrics fontMetrics;
1785 mMetrics->GetFontMetrics( defaultFontId, fontMetrics );
1787 lineHeight = fontMetrics.ascender - fontMetrics.descender;
1790 Vector2 cursorPosition;
1792 switch( mLayoutEngine.GetHorizontalAlignment() )
1794 case LayoutEngine::HORIZONTAL_ALIGN_BEGIN:
1796 cursorPosition.x = mEventData->mDecorator->GetCursorWidth();
1799 case LayoutEngine::HORIZONTAL_ALIGN_CENTER:
1801 cursorPosition.x = floor( 0.5f * mVisualModel->mControlSize.width );
1804 case LayoutEngine::HORIZONTAL_ALIGN_END:
1806 cursorPosition.x = mVisualModel->mControlSize.width;
1811 switch( mLayoutEngine.GetVerticalAlignment() )
1813 case LayoutEngine::VERTICAL_ALIGN_TOP:
1815 cursorPosition.y = 0.f;
1818 case LayoutEngine::VERTICAL_ALIGN_CENTER:
1820 cursorPosition.y = floorf( 0.5f * ( mVisualModel->mControlSize.height - lineHeight ) );
1823 case LayoutEngine::VERTICAL_ALIGN_BOTTOM:
1825 cursorPosition.y = mVisualModel->mControlSize.height - lineHeight;
1830 mEventData->mDecorator->SetPosition( PRIMARY_CURSOR,
1838 CursorInfo cursorInfo;
1839 GetCursorPosition( mEventData->mPrimaryCursorPosition,
1842 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
1843 const Vector2 cursorPosition = cursorInfo.primaryPosition + offset;
1845 // Sets the cursor position.
1846 mEventData->mDecorator->SetPosition( PRIMARY_CURSOR,
1849 cursorInfo.primaryCursorHeight,
1850 cursorInfo.lineHeight );
1851 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Primary cursor position: %f,%f\n", cursorPosition.x, cursorPosition.y );
1853 // Sets the grab handle position.
1854 mEventData->mDecorator->SetPosition( GRAB_HANDLE,
1857 cursorInfo.lineHeight );
1859 if( cursorInfo.isSecondaryCursor )
1861 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_BOTH );
1862 mEventData->mDecorator->SetPosition( SECONDARY_CURSOR,
1863 cursorInfo.secondaryPosition.x + offset.x,
1864 cursorInfo.secondaryPosition.y + offset.y,
1865 cursorInfo.secondaryCursorHeight,
1866 cursorInfo.lineHeight );
1867 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Secondary cursor position: %f,%f\n", cursorInfo.secondaryPosition.x + offset.x, cursorInfo.secondaryPosition.y + offset.y );
1871 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1874 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition\n" );
1877 void Controller::Impl::UpdateSelectionHandle( HandleType handleType )
1879 if( ( LEFT_SELECTION_HANDLE != handleType ) &&
1880 ( RIGHT_SELECTION_HANDLE != handleType ) )
1885 const bool leftSelectionHandle = LEFT_SELECTION_HANDLE == handleType;
1886 const CharacterIndex index = leftSelectionHandle ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition;
1888 CursorInfo cursorInfo;
1889 GetCursorPosition( index,
1892 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
1893 const Vector2 cursorPosition = cursorInfo.primaryPosition + offset;
1895 // Sets the grab handle position.
1896 mEventData->mDecorator->SetPosition( handleType,
1899 cursorInfo.lineHeight );
1901 // If selection handle at start of the text and other at end of the text then all text is selected.
1902 const CharacterIndex startOfSelection = std::min( mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition );
1903 const CharacterIndex endOfSelection = std::max ( mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition );
1904 mEventData->mAllTextSelected = ( startOfSelection == 0 ) && ( endOfSelection == mLogicalModel->mText.Count() );
1907 void Controller::Impl::ClampHorizontalScroll( const Vector2& actualSize )
1909 // Clamp between -space & 0 (and the text alignment).
1910 if( actualSize.width > mVisualModel->mControlSize.width )
1912 const float space = ( actualSize.width - mVisualModel->mControlSize.width ) + mAlignmentOffset.x;
1913 mEventData->mScrollPosition.x = ( mEventData->mScrollPosition.x < -space ) ? -space : mEventData->mScrollPosition.x;
1914 mEventData->mScrollPosition.x = ( mEventData->mScrollPosition.x > -mAlignmentOffset.x ) ? -mAlignmentOffset.x : mEventData->mScrollPosition.x;
1916 mEventData->mDecoratorUpdated = true;
1920 mEventData->mScrollPosition.x = 0.f;
1924 void Controller::Impl::ClampVerticalScroll( const Vector2& actualSize )
1926 // Clamp between -space & 0 (and the text alignment).
1927 if( actualSize.height > mVisualModel->mControlSize.height )
1929 const float space = ( actualSize.height - mVisualModel->mControlSize.height ) + mAlignmentOffset.y;
1930 mEventData->mScrollPosition.y = ( mEventData->mScrollPosition.y < -space ) ? -space : mEventData->mScrollPosition.y;
1931 mEventData->mScrollPosition.y = ( mEventData->mScrollPosition.y > -mAlignmentOffset.y ) ? -mAlignmentOffset.y : mEventData->mScrollPosition.y;
1933 mEventData->mDecoratorUpdated = true;
1937 mEventData->mScrollPosition.y = 0.f;
1941 void Controller::Impl::ScrollToMakePositionVisible( const Vector2& position )
1944 bool updateDecorator = false;
1945 if( position.x < 0.f )
1947 offset.x = -position.x;
1948 mEventData->mScrollPosition.x += offset.x;
1949 updateDecorator = true;
1951 else if( position.x > mVisualModel->mControlSize.width )
1953 offset.x = mVisualModel->mControlSize.width - position.x;
1954 mEventData->mScrollPosition.x += offset.x;
1955 updateDecorator = true;
1958 if( updateDecorator && mEventData->mDecorator )
1960 mEventData->mDecorator->UpdatePositions( offset );
1963 // TODO : calculate the vertical scroll.
1966 void Controller::Impl::ScrollTextToMatchCursor()
1968 // Get the current cursor position in decorator coords.
1969 const Vector2& currentCursorPosition = mEventData->mDecorator->GetPosition( PRIMARY_CURSOR );
1971 // Calculate the new cursor position.
1972 CursorInfo cursorInfo;
1973 GetCursorPosition( mEventData->mPrimaryCursorPosition,
1976 // Calculate the offset to match the cursor position before the character was deleted.
1977 mEventData->mScrollPosition.x = currentCursorPosition.x - cursorInfo.primaryPosition.x - mAlignmentOffset.x;
1979 ClampHorizontalScroll( mVisualModel->GetActualSize() );
1981 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
1982 const Vector2 cursorPosition = cursorInfo.primaryPosition + offset;
1984 // Sets the cursor position.
1985 mEventData->mDecorator->SetPosition( PRIMARY_CURSOR,
1988 cursorInfo.primaryCursorHeight,
1989 cursorInfo.lineHeight );
1991 // Sets the grab handle position.
1992 mEventData->mDecorator->SetPosition( GRAB_HANDLE,
1995 cursorInfo.lineHeight );
1997 if( cursorInfo.isSecondaryCursor )
1999 mEventData->mDecorator->SetPosition( SECONDARY_CURSOR,
2000 cursorInfo.secondaryPosition.x + offset.x,
2001 cursorInfo.secondaryPosition.y + offset.y,
2002 cursorInfo.secondaryCursorHeight,
2003 cursorInfo.lineHeight );
2004 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Secondary cursor position: %f,%f\n", cursorInfo.secondaryPosition.x + offset.x, cursorInfo.secondaryPosition.y + offset.y );
2007 // Set which cursors are active according the state.
2008 if( ( EventData::EDITING == mEventData->mState ) ||
2009 ( EventData::EDITING_WITH_POPUP == mEventData->mState ) ||
2010 ( EventData::EDITING_WITH_GRAB_HANDLE == mEventData->mState ) ||
2011 ( EventData::GRAB_HANDLE_PANNING == mEventData->mState ) )
2013 if( cursorInfo.isSecondaryCursor )
2015 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_BOTH );
2019 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
2024 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
2028 void Controller::Impl::RequestRelayout()
2030 mControlInterface.RequestTextRelayout();
2035 } // namespace Toolkit