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/color-segmentation.h>
29 #include <dali-toolkit/internal/text/multi-language-support.h>
30 #include <dali-toolkit/internal/text/segmentation.h>
31 #include <dali-toolkit/internal/text/shaper.h>
36 #if defined(DEBUG_ENABLED)
37 Debug::Filter* gLogFilter = Debug::Filter::New(Debug::NoLogging, true, "LOG_TEXT_CONTROLS");
41 * @brief Some characters can be shaped in more than one glyph.
42 * This struct is used to retrieve metrics from these group of glyphs.
56 float fontHeight; ///< The font's height of that glyphs.
57 float advance; ///< The sum of all the advances of all the glyphs.
58 float ascender; ///< The font's ascender.
59 float xBearing; ///< The x bearing of the first glyph.
74 * @brief Get some glyph's metrics of a group of glyphs formed as a result of shaping one character.
76 * @param[in] glyphIndex The index to the first glyph.
77 * @param[in] numberOfGlyphs The number of glyphs.
78 * @param[out] glyphMetrics Some glyph metrics (font height, advance, ascender and x bearing).
79 * @param[in] visualModel The visual model.
80 * @param[in] metrics Used to access metrics from FontClient.
82 void GetGlyphsMetrics( GlyphIndex glyphIndex,
83 Length numberOfGlyphs,
84 GlyphMetrics& glyphMetrics,
85 VisualModelPtr& visualModel,
88 const GlyphInfo* glyphsBuffer = visualModel->mGlyphs.Begin();
90 const GlyphInfo& firstGlyph = *( glyphsBuffer + glyphIndex );
92 Text::FontMetrics fontMetrics;
93 metrics->GetFontMetrics( firstGlyph.fontId, fontMetrics );
95 glyphMetrics.fontHeight = fontMetrics.height;
96 glyphMetrics.advance = firstGlyph.advance;
97 glyphMetrics.ascender = fontMetrics.ascender;
98 glyphMetrics.xBearing = firstGlyph.xBearing;
100 for( unsigned int i = 1u; i < numberOfGlyphs; ++i )
102 const GlyphInfo& glyphInfo = *( glyphsBuffer + glyphIndex + i );
104 glyphMetrics.advance += glyphInfo.advance;
108 EventData::EventData( DecoratorPtr decorator )
109 : 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 ),
137 mUpdateInputStyle( false )
139 mImfManager = ImfManager::Get();
142 EventData::~EventData()
145 bool Controller::Impl::ProcessInputEvents()
147 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "-->Controller::ProcessInputEvents\n" );
148 if( NULL == mEventData )
150 // Nothing to do if there is no text input.
151 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::ProcessInputEvents no event data\n" );
155 if( mEventData->mDecorator )
157 for( std::vector<Event>::iterator iter = mEventData->mEventQueue.begin();
158 iter != mEventData->mEventQueue.end();
163 case Event::CURSOR_KEY_EVENT:
165 OnCursorKeyEvent( *iter );
168 case Event::TAP_EVENT:
173 case Event::LONG_PRESS_EVENT:
175 OnLongPressEvent( *iter );
178 case Event::PAN_EVENT:
183 case Event::GRAB_HANDLE_EVENT:
184 case Event::LEFT_SELECTION_HANDLE_EVENT:
185 case Event::RIGHT_SELECTION_HANDLE_EVENT: // Fall through
187 OnHandleEvent( *iter );
192 OnSelectEvent( *iter );
195 case Event::SELECT_ALL:
204 // The cursor must also be repositioned after inserts into the model
205 if( mEventData->mUpdateCursorPosition )
207 // Updates the cursor position and scrolls the text to make it visible.
208 CursorInfo cursorInfo;
209 GetCursorPosition( mEventData->mPrimaryCursorPosition,
212 if( mEventData->mScrollAfterUpdatePosition )
214 ScrollToMakePositionVisible( cursorInfo.primaryPosition );
215 mEventData->mScrollAfterUpdatePosition = false;
217 else if( mEventData->mScrollAfterDelete )
219 ScrollTextToMatchCursor( cursorInfo );
220 mEventData->mScrollAfterDelete = false;
223 UpdateCursorPosition( cursorInfo );
225 mEventData->mDecoratorUpdated = true;
226 mEventData->mUpdateCursorPosition = false;
230 bool leftScroll = false;
231 bool rightScroll = false;
233 CursorInfo leftHandleInfo;
234 CursorInfo rightHandleInfo;
236 if( mEventData->mUpdateLeftSelectionPosition )
238 GetCursorPosition( mEventData->mLeftSelectionPosition,
241 if( mEventData->mScrollAfterUpdatePosition )
243 ScrollToMakePositionVisible( leftHandleInfo.primaryPosition );
248 if( mEventData->mUpdateRightSelectionPosition )
250 GetCursorPosition( mEventData->mRightSelectionPosition,
253 if( mEventData->mScrollAfterUpdatePosition )
255 ScrollToMakePositionVisible( rightHandleInfo.primaryPosition );
260 if( mEventData->mUpdateLeftSelectionPosition )
262 UpdateSelectionHandle( LEFT_SELECTION_HANDLE,
266 mEventData->mDecoratorUpdated = true;
269 if( mEventData->mUpdateRightSelectionPosition )
271 UpdateSelectionHandle( RIGHT_SELECTION_HANDLE,
275 mEventData->mDecoratorUpdated = true;
278 if( mEventData->mUpdateLeftSelectionPosition || mEventData->mUpdateRightSelectionPosition )
280 RepositionSelectionHandles();
282 mEventData->mUpdateLeftSelectionPosition = false;
283 mEventData->mUpdateRightSelectionPosition = false;
286 if( leftScroll || rightScroll )
288 mEventData->mScrollAfterUpdatePosition = false;
292 if( mEventData->mUpdateInputStyle )
294 // Set the default style first.
295 RetrieveDefaultInputStyle( mEventData->mInputStyle );
297 // Get the character index from the cursor index.
298 const CharacterIndex styleIndex = ( mEventData->mPrimaryCursorPosition > 0u ) ? mEventData->mPrimaryCursorPosition - 1u : 0u;
300 // Retrieve the style from the style runs stored in the logical model.
301 mLogicalModel->RetrieveStyle( styleIndex, mEventData->mInputStyle );
303 mEventData->mUpdateInputStyle = false;
306 mEventData->mEventQueue.clear();
308 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::ProcessInputEvents\n" );
310 const bool decoratorUpdated = mEventData->mDecoratorUpdated;
311 mEventData->mDecoratorUpdated = false;
313 return decoratorUpdated;
316 void Controller::Impl::UpdateModel( OperationsMask operationsRequired )
318 DALI_LOG_INFO( gLogFilter, Debug::General, "Controller::UpdateModel\n" );
320 // Calculate the operations to be done.
321 const OperationsMask operations = static_cast<OperationsMask>( mOperationsPending & operationsRequired );
323 Vector<Character>& utf32Characters = mLogicalModel->mText;
325 const Length numberOfCharacters = utf32Characters.Count();
327 Vector<LineBreakInfo>& lineBreakInfo = mLogicalModel->mLineBreakInfo;
328 CharacterIndex startIndex = 0u;
329 Length requestedNumberOfCharacters = numberOfCharacters;
330 if( GET_LINE_BREAKS & operations )
332 // Retrieves the line break info. The line break info is used to split the text in 'paragraphs' to
333 // calculate the bidirectional info for each 'paragraph'.
334 // It's also used to layout the text (where it should be a new line) or to shape the text (text in different lines
335 // is not shaped together).
336 lineBreakInfo.Resize( numberOfCharacters, TextAbstraction::LINE_NO_BREAK );
338 SetLineBreakInfo( utf32Characters,
342 Vector<WordBreakInfo>& wordBreakInfo = mLogicalModel->mWordBreakInfo;
343 if( GET_WORD_BREAKS & operations )
345 // Retrieves the word break info. The word break info is used to layout the text (where to wrap the text in lines).
346 wordBreakInfo.Resize( numberOfCharacters, TextAbstraction::WORD_NO_BREAK );
348 SetWordBreakInfo( utf32Characters,
350 requestedNumberOfCharacters,
354 const bool getScripts = GET_SCRIPTS & operations;
355 const bool validateFonts = VALIDATE_FONTS & operations;
357 Vector<ScriptRun>& scripts = mLogicalModel->mScriptRuns;
358 Vector<FontRun>& validFonts = mLogicalModel->mFontRuns;
360 if( getScripts || validateFonts )
362 // Validates the fonts assigned by the application or assigns default ones.
363 // It makes sure all the characters are going to be rendered by the correct font.
364 MultilanguageSupport multilanguageSupport = MultilanguageSupport::Get();
368 // Retrieves the scripts used in the text.
369 multilanguageSupport.SetScripts( utf32Characters,
375 // Validate the fonts set through the mark-up string.
376 Vector<FontDescriptionRun>& fontDescriptionRuns = mLogicalModel->mFontDescriptionRuns;
378 // Get the default font id.
379 const FontId defaultFontId = ( NULL == mFontDefaults ) ? 0u : mFontDefaults->GetFontId( mFontClient );
381 // Validates the fonts. If there is a character with no assigned font it sets a default one.
382 // After this call, fonts are validated.
383 multilanguageSupport.ValidateFonts( utf32Characters,
391 Vector<Character> mirroredUtf32Characters;
392 bool textMirrored = false;
393 Length numberOfParagraphs = 0u;
394 if( BIDI_INFO & operations )
396 // Count the number of LINE_NO_BREAK to reserve some space for the vector of paragraph's
397 // bidirectional info.
399 const TextAbstraction::LineBreakInfo* lineBreakInfoBuffer = lineBreakInfo.Begin();
400 for( Length index = 0u; index < numberOfCharacters; ++index )
402 if( TextAbstraction::LINE_NO_BREAK == *( lineBreakInfoBuffer + index ) )
404 ++numberOfParagraphs;
408 Vector<BidirectionalParagraphInfoRun>& bidirectionalInfo = mLogicalModel->mBidirectionalParagraphInfo;
409 bidirectionalInfo.Reserve( numberOfParagraphs );
411 // Calculates the bidirectional info for the whole paragraph if it contains right to left scripts.
412 SetBidirectionalInfo( utf32Characters,
417 if( 0u != bidirectionalInfo.Count() )
419 // Only set the character directions if there is right to left characters.
420 Vector<CharacterDirection>& directions = mLogicalModel->mCharacterDirections;
421 directions.Resize( numberOfCharacters );
423 GetCharactersDirection( bidirectionalInfo,
426 // This paragraph has right to left text. Some characters may need to be mirrored.
427 // TODO: consider if the mirrored string can be stored as well.
429 textMirrored = GetMirroredText( utf32Characters,
432 mirroredUtf32Characters );
436 // There is no right to left characters. Clear the directions vector.
437 mLogicalModel->mCharacterDirections.Clear();
441 Vector<GlyphInfo>& glyphs = mVisualModel->mGlyphs;
442 Vector<CharacterIndex>& glyphsToCharactersMap = mVisualModel->mGlyphsToCharacters;
443 Vector<Length>& charactersPerGlyph = mVisualModel->mCharactersPerGlyph;
444 Vector<GlyphIndex> newParagraphGlyphs;
445 newParagraphGlyphs.Reserve( numberOfParagraphs );
447 if( SHAPE_TEXT & operations )
449 const Vector<Character>& textToShape = textMirrored ? mirroredUtf32Characters : utf32Characters;
451 ShapeText( textToShape,
456 glyphsToCharactersMap,
458 newParagraphGlyphs );
460 // Create the 'number of glyphs' per character and the glyph to character conversion tables.
461 mVisualModel->CreateGlyphsPerCharacterTable( numberOfCharacters );
462 mVisualModel->CreateCharacterToGlyphTable( numberOfCharacters );
465 const Length numberOfGlyphs = glyphs.Count();
467 if( GET_GLYPH_METRICS & operations )
469 GlyphInfo* glyphsBuffer = glyphs.Begin();
470 mMetrics->GetGlyphMetrics( glyphsBuffer, numberOfGlyphs );
472 // Update the width and advance of all new paragraph characters.
473 for( Vector<GlyphIndex>::ConstIterator it = newParagraphGlyphs.Begin(), endIt = newParagraphGlyphs.End(); it != endIt; ++it )
475 const GlyphIndex index = *it;
476 GlyphInfo& glyph = *( glyphsBuffer + index );
478 glyph.xBearing = 0.f;
484 if( ( NULL != mEventData ) &&
485 mEventData->mPreEditFlag &&
486 ( 0u != mVisualModel->mCharactersToGlyph.Count() ) )
488 // Add the underline for the pre-edit text.
489 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
490 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
492 const GlyphIndex glyphStart = *( charactersToGlyphBuffer + mEventData->mPreEditStartPosition );
493 const CharacterIndex lastPreEditCharacter = mEventData->mPreEditStartPosition + ( ( mEventData->mPreEditLength > 0u ) ? mEventData->mPreEditLength - 1u : 0u );
494 const Length numberOfGlyphsLastCharacter = *( glyphsPerCharacterBuffer + lastPreEditCharacter );
495 const GlyphIndex glyphEnd = *( charactersToGlyphBuffer + lastPreEditCharacter ) + ( numberOfGlyphsLastCharacter > 1u ? numberOfGlyphsLastCharacter - 1u : 0u );
497 GlyphRun underlineRun;
498 underlineRun.glyphIndex = glyphStart;
499 underlineRun.numberOfGlyphs = 1u + glyphEnd - glyphStart;
501 // TODO: At the moment the underline runs are only for pre-edit.
502 mVisualModel->mUnderlineRuns.PushBack( underlineRun );
506 bool Controller::Impl::UpdateModelStyle( OperationsMask operationsRequired )
508 bool updated = false;
510 if( COLOR & operationsRequired )
512 // Set the color runs in glyphs.
513 SetColorSegmentationInfo( mLogicalModel->mColorRuns,
514 mVisualModel->mCharactersToGlyph,
515 mVisualModel->mGlyphsPerCharacter,
516 mVisualModel->mColorRuns );
524 void Controller::Impl::RetrieveDefaultInputStyle( InputStyle& inputStyle )
526 // Sets the default text's color.
527 inputStyle.textColor = mTextColor;
529 // Sets the default font's family name, weight, width, slant and size.
532 inputStyle.familyName = mFontDefaults->mFontDescription.family;
533 inputStyle.weight = mFontDefaults->mFontDescription.weight;
534 inputStyle.width = mFontDefaults->mFontDescription.width;
535 inputStyle.slant = mFontDefaults->mFontDescription.slant;
536 inputStyle.size = mFontDefaults->mDefaultPointSize;
538 inputStyle.familyDefined = mFontDefaults->familyDefined;
539 inputStyle.weightDefined = mFontDefaults->weightDefined;
540 inputStyle.widthDefined = mFontDefaults->widthDefined;
541 inputStyle.slantDefined = mFontDefaults->slantDefined;
542 inputStyle.sizeDefined = mFontDefaults->sizeDefined;
546 inputStyle.familyName.clear();
547 inputStyle.weight = TextAbstraction::FontWeight::NORMAL;
548 inputStyle.width = TextAbstraction::FontWidth::NORMAL;
549 inputStyle.slant = TextAbstraction::FontSlant::NORMAL;
550 inputStyle.size = 0.f;
552 inputStyle.familyDefined = false;
553 inputStyle.weightDefined = false;
554 inputStyle.widthDefined = false;
555 inputStyle.slantDefined = false;
556 inputStyle.sizeDefined = false;
560 float Controller::Impl::GetDefaultFontLineHeight()
562 FontId defaultFontId = 0u;
563 if( NULL == mFontDefaults )
565 TextAbstraction::FontDescription fontDescription;
566 defaultFontId = mFontClient.GetFontId( fontDescription );
570 defaultFontId = mFontDefaults->GetFontId( mFontClient );
573 Text::FontMetrics fontMetrics;
574 mMetrics->GetFontMetrics( defaultFontId, fontMetrics );
576 return( fontMetrics.ascender - fontMetrics.descender );
579 void Controller::Impl::OnCursorKeyEvent( const Event& event )
581 if( NULL == mEventData )
583 // Nothing to do if there is no text input.
587 int keyCode = event.p1.mInt;
589 if( Dali::DALI_KEY_CURSOR_LEFT == keyCode )
591 if( mEventData->mPrimaryCursorPosition > 0u )
593 mEventData->mPrimaryCursorPosition = CalculateNewCursorIndex( mEventData->mPrimaryCursorPosition - 1u );
596 else if( Dali::DALI_KEY_CURSOR_RIGHT == keyCode )
598 if( mLogicalModel->mText.Count() > mEventData->mPrimaryCursorPosition )
600 mEventData->mPrimaryCursorPosition = CalculateNewCursorIndex( mEventData->mPrimaryCursorPosition );
603 else if( Dali::DALI_KEY_CURSOR_UP == keyCode )
607 else if( Dali::DALI_KEY_CURSOR_DOWN == keyCode )
612 mEventData->mUpdateCursorPosition = true;
613 mEventData->mUpdateInputStyle = true;
614 mEventData->mScrollAfterUpdatePosition = true;
617 void Controller::Impl::OnTapEvent( const Event& event )
619 if( NULL != mEventData )
621 const unsigned int tapCount = event.p1.mUint;
625 if( IsShowingRealText() )
627 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
628 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
630 mEventData->mPrimaryCursorPosition = GetClosestCursorIndex( xPosition,
633 // When the cursor position is changing, delay cursor blinking
634 mEventData->mDecorator->DelayCursorBlink();
638 mEventData->mPrimaryCursorPosition = 0u;
641 mEventData->mUpdateCursorPosition = true;
642 mEventData->mScrollAfterUpdatePosition = true;
643 mEventData->mUpdateInputStyle = true;
645 // Notify the cursor position to the imf manager.
646 if( mEventData->mImfManager )
648 mEventData->mImfManager.SetCursorPosition( mEventData->mPrimaryCursorPosition );
649 mEventData->mImfManager.NotifyCursorPosition();
655 void Controller::Impl::OnPanEvent( const Event& event )
657 if( NULL == mEventData )
659 // Nothing to do if there is no text input.
663 int state = event.p1.mInt;
665 if( Gesture::Started == state ||
666 Gesture::Continuing == state )
668 const Vector2& actualSize = mVisualModel->GetActualSize();
669 const Vector2 currentScroll = mEventData->mScrollPosition;
671 if( mEventData->mHorizontalScrollingEnabled )
673 const float displacementX = event.p2.mFloat;
674 mEventData->mScrollPosition.x += displacementX;
676 ClampHorizontalScroll( actualSize );
679 if( mEventData->mVerticalScrollingEnabled )
681 const float displacementY = event.p3.mFloat;
682 mEventData->mScrollPosition.y += displacementY;
684 ClampVerticalScroll( actualSize );
687 if( mEventData->mDecorator )
689 mEventData->mDecorator->UpdatePositions( mEventData->mScrollPosition - currentScroll );
694 void Controller::Impl::OnLongPressEvent( const Event& event )
696 DALI_LOG_INFO( gLogFilter, Debug::General, "Controller::OnLongPressEvent\n" );
698 if( EventData::EDITING == mEventData->mState )
700 ChangeState ( EventData::EDITING_WITH_POPUP );
701 mEventData->mDecoratorUpdated = true;
705 void Controller::Impl::OnHandleEvent( const Event& event )
707 if( NULL == mEventData )
709 // Nothing to do if there is no text input.
713 const unsigned int state = event.p1.mUint;
714 const bool handleStopScrolling = ( HANDLE_STOP_SCROLLING == state );
716 if( HANDLE_PRESSED == state )
718 // The event.p2 and event.p3 are in decorator coords. Need to transforms to text coords.
719 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
720 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
722 const CharacterIndex handleNewPosition = GetClosestCursorIndex( xPosition, yPosition );
724 if( Event::GRAB_HANDLE_EVENT == event.type )
726 ChangeState ( EventData::GRAB_HANDLE_PANNING );
728 if( handleNewPosition != mEventData->mPrimaryCursorPosition )
730 mEventData->mPrimaryCursorPosition = handleNewPosition;
731 mEventData->mUpdateCursorPosition = true;
734 else if( Event::LEFT_SELECTION_HANDLE_EVENT == event.type )
736 ChangeState ( EventData::SELECTION_HANDLE_PANNING );
738 if( ( handleNewPosition != mEventData->mLeftSelectionPosition ) &&
739 ( handleNewPosition != mEventData->mRightSelectionPosition ) )
741 mEventData->mLeftSelectionPosition = handleNewPosition;
743 mEventData->mUpdateLeftSelectionPosition = true;
746 else if( Event::RIGHT_SELECTION_HANDLE_EVENT == event.type )
748 ChangeState ( EventData::SELECTION_HANDLE_PANNING );
750 if( ( handleNewPosition != mEventData->mRightSelectionPosition ) &&
751 ( handleNewPosition != mEventData->mLeftSelectionPosition ) )
753 mEventData->mRightSelectionPosition = handleNewPosition;
755 mEventData->mUpdateRightSelectionPosition = true;
758 } // end ( HANDLE_PRESSED == state )
759 else if( ( HANDLE_RELEASED == state ) ||
760 handleStopScrolling )
762 CharacterIndex handlePosition = 0u;
763 if( handleStopScrolling )
765 // The event.p2 and event.p3 are in decorator coords. Need to transforms to text coords.
766 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
767 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
769 handlePosition = GetClosestCursorIndex( xPosition, yPosition );
772 if( Event::GRAB_HANDLE_EVENT == event.type )
774 mEventData->mUpdateCursorPosition = true;
775 mEventData->mUpdateInputStyle = true;
777 if( !IsClipboardEmpty() )
779 ChangeState( EventData::EDITING_WITH_PASTE_POPUP ); // Moving grabhandle will show Paste Popup
782 if( handleStopScrolling )
784 mEventData->mScrollAfterUpdatePosition = mEventData->mPrimaryCursorPosition != handlePosition;
785 mEventData->mPrimaryCursorPosition = handlePosition;
788 else if( Event::LEFT_SELECTION_HANDLE_EVENT == event.type )
790 ChangeState( EventData::SELECTING );
792 if( handleStopScrolling )
794 mEventData->mUpdateLeftSelectionPosition = ( mEventData->mRightSelectionPosition != handlePosition );
795 mEventData->mScrollAfterUpdatePosition = mEventData->mUpdateLeftSelectionPosition;
797 if( mEventData->mUpdateLeftSelectionPosition )
799 mEventData->mLeftSelectionPosition = handlePosition;
803 else if( Event::RIGHT_SELECTION_HANDLE_EVENT == event.type )
805 ChangeState( EventData::SELECTING );
807 if( handleStopScrolling )
809 mEventData->mUpdateRightSelectionPosition = ( mEventData->mLeftSelectionPosition != handlePosition );
810 mEventData->mScrollAfterUpdatePosition = mEventData->mUpdateRightSelectionPosition;
811 if( mEventData->mUpdateRightSelectionPosition )
813 mEventData->mRightSelectionPosition = handlePosition;
818 mEventData->mDecoratorUpdated = true;
819 } // end ( ( HANDLE_RELEASED == state ) || ( HANDLE_STOP_SCROLLING == state ) )
820 else if( HANDLE_SCROLLING == state )
822 const float xSpeed = event.p2.mFloat;
823 const Vector2& actualSize = mVisualModel->GetActualSize();
824 const Vector2 currentScrollPosition = mEventData->mScrollPosition;
826 mEventData->mScrollPosition.x += xSpeed;
828 ClampHorizontalScroll( actualSize );
830 bool endOfScroll = false;
831 if( Vector2::ZERO == ( currentScrollPosition - mEventData->mScrollPosition ) )
833 // Notify the decorator there is no more text to scroll.
834 // The decorator won't send more scroll events.
835 mEventData->mDecorator->NotifyEndOfScroll();
836 // Still need to set the position of the handle.
840 // Set the position of the handle.
841 const bool scrollRightDirection = xSpeed > 0.f;
842 const bool leftSelectionHandleEvent = Event::LEFT_SELECTION_HANDLE_EVENT == event.type;
843 const bool rightSelectionHandleEvent = Event::RIGHT_SELECTION_HANDLE_EVENT == event.type;
845 if( Event::GRAB_HANDLE_EVENT == event.type )
847 ChangeState( EventData::GRAB_HANDLE_PANNING );
849 Vector2 position = mEventData->mDecorator->GetPosition( GRAB_HANDLE );
851 // Position the grag handle close to either the left or right edge.
852 position.x = scrollRightDirection ? 0.f : mVisualModel->mControlSize.width;
854 // Get the new handle position.
855 // The grab handle's position is in decorator coords. Need to transforms to text coords.
856 const CharacterIndex handlePosition = GetClosestCursorIndex( position.x - mEventData->mScrollPosition.x - mAlignmentOffset.x,
857 position.y - mEventData->mScrollPosition.y - mAlignmentOffset.y );
859 mEventData->mUpdateCursorPosition = mEventData->mPrimaryCursorPosition != handlePosition;
860 mEventData->mScrollAfterUpdatePosition = mEventData->mUpdateCursorPosition;
861 mEventData->mPrimaryCursorPosition = handlePosition;
862 mEventData->mUpdateInputStyle = mEventData->mUpdateCursorPosition;
864 else if( leftSelectionHandleEvent || rightSelectionHandleEvent )
866 // TODO: This is recalculating the selection box every time the text is scrolled with the selection handles.
867 // Think if something can be done to save power.
869 ChangeState( EventData::SELECTION_HANDLE_PANNING );
871 Vector2 position = mEventData->mDecorator->GetPosition( leftSelectionHandleEvent ? Text::LEFT_SELECTION_HANDLE : Text::RIGHT_SELECTION_HANDLE );
873 // Position the selection handle close to either the left or right edge.
874 position.x = scrollRightDirection ? 0.f : mVisualModel->mControlSize.width;
876 // Get the new handle position.
877 // The selection handle's position is in decorator coords. Need to transforms to text coords.
878 const CharacterIndex handlePosition = GetClosestCursorIndex( position.x - mEventData->mScrollPosition.x - mAlignmentOffset.x,
879 position.y - mEventData->mScrollPosition.y - mAlignmentOffset.y );
881 if( leftSelectionHandleEvent )
883 const bool differentHandles = ( mEventData->mLeftSelectionPosition != handlePosition ) && ( mEventData->mRightSelectionPosition != handlePosition );
884 mEventData->mUpdateLeftSelectionPosition = endOfScroll || differentHandles;
885 if( differentHandles )
887 mEventData->mLeftSelectionPosition = handlePosition;
892 const bool differentHandles = ( mEventData->mRightSelectionPosition != handlePosition ) && ( mEventData->mLeftSelectionPosition != handlePosition );
893 mEventData->mUpdateRightSelectionPosition = endOfScroll || differentHandles;
894 if( differentHandles )
896 mEventData->mRightSelectionPosition = handlePosition;
900 if( mEventData->mUpdateLeftSelectionPosition || mEventData->mUpdateRightSelectionPosition )
902 RepositionSelectionHandles();
904 mEventData->mScrollAfterUpdatePosition = true;
907 mEventData->mDecoratorUpdated = true;
908 } // end ( HANDLE_SCROLLING == state )
911 void Controller::Impl::OnSelectEvent( const Event& event )
913 if( NULL == mEventData )
915 // Nothing to do if there is no text.
919 if( mEventData->mSelectionEnabled )
921 // The event.p2 and event.p3 are in decorator coords. Need to transforms to text coords.
922 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
923 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
925 // Calculates the logical position from the x,y coords.
926 RepositionSelectionHandles( xPosition,
929 mEventData->mUpdateLeftSelectionPosition = true;
930 mEventData->mUpdateRightSelectionPosition = true;
932 mEventData->mScrollAfterUpdatePosition = ( mEventData->mLeftSelectionPosition != mEventData->mRightSelectionPosition );
936 void Controller::Impl::OnSelectAllEvent()
938 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "OnSelectAllEvent mEventData->mSelectionEnabled%s \n", mEventData->mSelectionEnabled?"true":"false");
940 if( NULL == mEventData )
942 // Nothing to do if there is no text.
946 if( mEventData->mSelectionEnabled )
948 mEventData->mLeftSelectionPosition = 0u;
949 mEventData->mRightSelectionPosition = mLogicalModel->mText.Count();
951 mEventData->mScrollAfterUpdatePosition = true;
952 mEventData->mUpdateLeftSelectionPosition = true;
953 mEventData->mUpdateRightSelectionPosition = true;
957 void Controller::Impl::RetrieveSelection( std::string& selectedText, bool deleteAfterRetrieval )
959 if( mEventData->mLeftSelectionPosition == mEventData->mRightSelectionPosition )
961 // Nothing to select if handles are in the same place.
962 selectedText.clear();
966 const bool handlesCrossed = mEventData->mLeftSelectionPosition > mEventData->mRightSelectionPosition;
968 //Get start and end position of selection
969 const CharacterIndex startOfSelectedText = handlesCrossed ? mEventData->mRightSelectionPosition : mEventData->mLeftSelectionPosition;
970 const Length lengthOfSelectedText = ( handlesCrossed ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition ) - startOfSelectedText;
972 // Validate the start and end selection points
973 if( ( startOfSelectedText + lengthOfSelectedText ) <= mLogicalModel->mText.Count() )
975 //Get text as a UTF8 string
976 Vector<Character>& utf32Characters = mLogicalModel->mText;
978 Utf32ToUtf8( &utf32Characters[startOfSelectedText], lengthOfSelectedText, selectedText );
980 if( deleteAfterRetrieval ) // Only delete text if copied successfully
982 // Set as input style the style of the first deleted character.
983 mLogicalModel->RetrieveStyle( startOfSelectedText, mEventData->mInputStyle );
985 mLogicalModel->UpdateTextStyleRuns( startOfSelectedText, -static_cast<int>( lengthOfSelectedText ) );
987 // Delete text between handles
988 Vector<Character>& currentText = mLogicalModel->mText;
990 Vector<Character>::Iterator first = currentText.Begin() + startOfSelectedText;
991 Vector<Character>::Iterator last = first + lengthOfSelectedText;
992 currentText.Erase( first, last );
994 // Scroll after delete.
995 mEventData->mPrimaryCursorPosition = handlesCrossed ? mEventData->mRightSelectionPosition : mEventData->mLeftSelectionPosition;
996 mEventData->mScrollAfterDelete = true;
998 // Udpade the cursor position and the decorator.
999 // Scroll after the position is updated if is not scrolling after delete.
1000 mEventData->mUpdateCursorPosition = true;
1001 mEventData->mScrollAfterUpdatePosition = !mEventData->mScrollAfterDelete;
1002 mEventData->mDecoratorUpdated = true;
1006 void Controller::Impl::ShowClipboard()
1010 mClipboard.ShowClipboard();
1014 void Controller::Impl::HideClipboard()
1018 mClipboard.HideClipboard();
1022 bool Controller::Impl::CopyStringToClipboard( std::string& source )
1024 //Send string to clipboard
1025 return ( mClipboard && mClipboard.SetItem( source ) );
1028 void Controller::Impl::SendSelectionToClipboard( bool deleteAfterSending )
1030 std::string selectedText;
1031 RetrieveSelection( selectedText, deleteAfterSending );
1032 CopyStringToClipboard( selectedText );
1033 ChangeState( EventData::EDITING );
1036 void Controller::Impl::GetTextFromClipboard( unsigned int itemIndex, std::string& retrievedString )
1040 retrievedString = mClipboard.GetItem( itemIndex );
1044 void Controller::Impl::RepositionSelectionHandles()
1046 CharacterIndex selectionStart = mEventData->mLeftSelectionPosition;
1047 CharacterIndex selectionEnd = mEventData->mRightSelectionPosition;
1049 if( selectionStart == selectionEnd )
1051 // Nothing to select if handles are in the same place.
1055 mEventData->mDecorator->ClearHighlights();
1057 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
1058 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
1059 const GlyphInfo* const glyphsBuffer = mVisualModel->mGlyphs.Begin();
1060 const Vector2* const positionsBuffer = mVisualModel->mGlyphPositions.Begin();
1061 const Length* const charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
1062 const CharacterIndex* const glyphToCharacterBuffer = mVisualModel->mGlyphsToCharacters.Begin();
1063 const CharacterDirection* const modelCharacterDirectionsBuffer = ( 0u != mLogicalModel->mCharacterDirections.Count() ) ? mLogicalModel->mCharacterDirections.Begin() : NULL;
1065 // TODO: Better algorithm to create the highlight box.
1066 // TODO: Multi-line.
1068 // Get the height of the line.
1069 const Vector<LineRun>& lines = mVisualModel->mLines;
1070 const LineRun& firstLine = *lines.Begin();
1071 const float height = firstLine.ascender + -firstLine.descender;
1073 const bool isLastCharacter = selectionEnd >= mLogicalModel->mText.Count();
1074 const bool startDirection = ( ( NULL == modelCharacterDirectionsBuffer ) ? false : *( modelCharacterDirectionsBuffer + selectionStart ) );
1075 const bool endDirection = ( ( NULL == modelCharacterDirectionsBuffer ) ? false : *( modelCharacterDirectionsBuffer + ( selectionEnd - ( isLastCharacter ? 1u : 0u ) ) ) );
1077 // Swap the indices if the start is greater than the end.
1078 const bool indicesSwapped = selectionStart > selectionEnd;
1080 // Tell the decorator to flip the selection handles if needed.
1081 mEventData->mDecorator->SetSelectionHandleFlipState( indicesSwapped, startDirection, endDirection );
1083 if( indicesSwapped )
1085 std::swap( selectionStart, selectionEnd );
1088 // Get the indices to the first and last selected glyphs.
1089 const CharacterIndex selectionEndMinusOne = selectionEnd - 1u;
1090 const GlyphIndex glyphStart = *( charactersToGlyphBuffer + selectionStart );
1091 const Length numberOfGlyphs = *( glyphsPerCharacterBuffer + selectionEndMinusOne );
1092 const GlyphIndex glyphEnd = *( charactersToGlyphBuffer + selectionEndMinusOne ) + ( ( numberOfGlyphs > 0 ) ? numberOfGlyphs - 1u : 0u );
1094 // Check if the first glyph is a ligature that must be broken like Latin ff, fi, or Arabic ﻻ, etc which needs special code.
1095 const Length numberOfCharactersStart = *( charactersPerGlyphBuffer + glyphStart );
1096 bool splitStartGlyph = ( numberOfCharactersStart > 1u ) && HasLigatureMustBreak( mLogicalModel->GetScript( selectionStart ) );
1098 // Check if the last glyph is a ligature that must be broken like Latin ff, fi, or Arabic ﻻ, etc which needs special code.
1099 const Length numberOfCharactersEnd = *( charactersPerGlyphBuffer + glyphEnd );
1100 bool splitEndGlyph = ( glyphStart != glyphEnd ) && ( numberOfCharactersEnd > 1u ) && HasLigatureMustBreak( mLogicalModel->GetScript( selectionEndMinusOne ) );
1102 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
1104 // Traverse the glyphs.
1105 for( GlyphIndex index = glyphStart; index <= glyphEnd; ++index )
1107 const GlyphInfo& glyph = *( glyphsBuffer + index );
1108 const Vector2& position = *( positionsBuffer + index );
1110 if( splitStartGlyph )
1112 // 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.
1114 const float glyphAdvance = glyph.advance / static_cast<float>( numberOfCharactersStart );
1115 const CharacterIndex interGlyphIndex = selectionStart - *( glyphToCharacterBuffer + glyphStart );
1116 // Get the direction of the character.
1117 CharacterDirection isCurrentRightToLeft = false;
1118 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
1120 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + selectionStart );
1123 // The end point could be in the middle of the ligature.
1124 // Calculate the number of characters selected.
1125 const Length numberOfCharacters = ( glyphStart == glyphEnd ) ? ( selectionEnd - selectionStart ) : ( numberOfCharactersStart - interGlyphIndex );
1127 const float xPosition = position.x - glyph.xBearing + offset.x + glyphAdvance * static_cast<float>( isCurrentRightToLeft ? ( numberOfCharactersStart - interGlyphIndex - numberOfCharacters ) : interGlyphIndex );
1129 mEventData->mDecorator->AddHighlight( xPosition,
1131 xPosition + static_cast<float>( numberOfCharacters ) * glyphAdvance,
1132 offset.y + height );
1134 splitStartGlyph = false;
1138 if( splitEndGlyph && ( index == glyphEnd ) )
1140 // 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.
1142 const float glyphAdvance = glyph.advance / static_cast<float>( numberOfCharactersEnd );
1143 const CharacterIndex interGlyphIndex = selectionEnd - *( glyphToCharacterBuffer + glyphEnd );
1144 // Get the direction of the character.
1145 CharacterDirection isCurrentRightToLeft = false;
1146 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
1148 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + selectionEnd );
1151 const Length numberOfCharacters = numberOfCharactersEnd - interGlyphIndex;
1153 const float xPosition = position.x - glyph.xBearing + offset.x + ( isCurrentRightToLeft ? ( glyphAdvance * static_cast<float>( numberOfCharacters ) ) : 0.f );
1154 mEventData->mDecorator->AddHighlight( xPosition,
1156 xPosition + static_cast<float>( interGlyphIndex ) * glyphAdvance,
1157 offset.y + height );
1159 splitEndGlyph = false;
1163 const float xPosition = position.x - glyph.xBearing + offset.x;
1164 mEventData->mDecorator->AddHighlight( xPosition,
1166 xPosition + glyph.advance,
1167 offset.y + height );
1170 CursorInfo primaryCursorInfo;
1171 GetCursorPosition( mEventData->mLeftSelectionPosition,
1172 primaryCursorInfo );
1174 CursorInfo secondaryCursorInfo;
1175 GetCursorPosition( mEventData->mRightSelectionPosition,
1176 secondaryCursorInfo );
1178 const Vector2 primaryPosition = primaryCursorInfo.primaryPosition + offset;
1179 const Vector2 secondaryPosition = secondaryCursorInfo.primaryPosition + offset;
1181 mEventData->mDecorator->SetPosition( LEFT_SELECTION_HANDLE,
1183 primaryCursorInfo.lineOffset + offset.y,
1184 primaryCursorInfo.lineHeight );
1186 mEventData->mDecorator->SetPosition( RIGHT_SELECTION_HANDLE,
1187 secondaryPosition.x,
1188 secondaryCursorInfo.lineOffset + offset.y,
1189 secondaryCursorInfo.lineHeight );
1191 // Cursor to be positioned at end of selection so if selection interrupted and edit mode restarted the cursor will be at end of selection
1192 mEventData->mPrimaryCursorPosition = ( indicesSwapped ) ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition;
1194 // Set the flag to update the decorator.
1195 mEventData->mDecoratorUpdated = true;
1198 void Controller::Impl::RepositionSelectionHandles( float visualX, float visualY )
1200 if( NULL == mEventData )
1202 // Nothing to do if there is no text input.
1206 if( IsShowingPlaceholderText() )
1208 // Nothing to do if there is the place-holder text.
1212 const Length numberOfGlyphs = mVisualModel->mGlyphs.Count();
1213 const Length numberOfLines = mVisualModel->mLines.Count();
1214 if( ( 0 == numberOfGlyphs ) ||
1215 ( 0 == numberOfLines ) )
1217 // Nothing to do if there is no text.
1221 // Find which word was selected
1222 CharacterIndex selectionStart( 0 );
1223 CharacterIndex selectionEnd( 0 );
1224 FindSelectionIndices( visualX, visualY, selectionStart, selectionEnd );
1225 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "%p selectionStart %d selectionEnd %d\n", this, selectionStart, selectionEnd );
1227 if( selectionStart == selectionEnd )
1229 ChangeState( EventData::EDITING );
1230 // Nothing to select. i.e. a white space, out of bounds
1234 mEventData->mLeftSelectionPosition = selectionStart;
1235 mEventData->mRightSelectionPosition = selectionEnd;
1238 void Controller::Impl::SetPopupButtons()
1241 * Sets the Popup buttons to be shown depending on State.
1243 * If SELECTING : CUT & COPY + ( PASTE & CLIPBOARD if content available to paste )
1245 * If EDITING_WITH_POPUP : SELECT & SELECT_ALL
1248 TextSelectionPopup::Buttons buttonsToShow = TextSelectionPopup::NONE;
1250 if( EventData::SELECTING == mEventData->mState )
1252 buttonsToShow = TextSelectionPopup::Buttons( TextSelectionPopup::CUT | TextSelectionPopup::COPY );
1254 if( !IsClipboardEmpty() )
1256 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
1257 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::CLIPBOARD ) );
1260 if( !mEventData->mAllTextSelected )
1262 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::SELECT_ALL ) );
1265 else if( EventData::EDITING_WITH_POPUP == mEventData->mState )
1267 if( mLogicalModel->mText.Count() && !IsShowingPlaceholderText() )
1269 buttonsToShow = TextSelectionPopup::Buttons( TextSelectionPopup::SELECT | TextSelectionPopup::SELECT_ALL );
1272 if( !IsClipboardEmpty() )
1274 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
1275 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::CLIPBOARD ) );
1278 else if( EventData::EDITING_WITH_PASTE_POPUP == mEventData->mState )
1280 if ( !IsClipboardEmpty() )
1282 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
1283 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::CLIPBOARD ) );
1287 mEventData->mDecorator->SetEnabledPopupButtons( buttonsToShow );
1290 void Controller::Impl::ChangeState( EventData::State newState )
1292 if( NULL == mEventData )
1294 // Nothing to do if there is no text input.
1298 DALI_LOG_INFO( gLogFilter, Debug::General, "ChangeState state:%d newstate:%d\n", mEventData->mState, newState );
1300 if( mEventData->mState != newState )
1302 mEventData->mState = newState;
1304 if( EventData::INACTIVE == mEventData->mState )
1306 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
1307 mEventData->mDecorator->StopCursorBlink();
1308 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1309 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1310 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1311 mEventData->mDecorator->SetPopupActive( false );
1312 mEventData->mDecoratorUpdated = true;
1315 else if( EventData::INTERRUPTED == mEventData->mState)
1317 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1318 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1319 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1320 mEventData->mDecorator->SetPopupActive( false );
1321 mEventData->mDecoratorUpdated = true;
1324 else if( EventData::SELECTING == mEventData->mState )
1326 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
1327 mEventData->mDecorator->StopCursorBlink();
1328 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1329 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true );
1330 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true );
1331 if( mEventData->mGrabHandlePopupEnabled )
1334 mEventData->mDecorator->SetPopupActive( true );
1336 mEventData->mDecoratorUpdated = true;
1338 else if( EventData::EDITING == mEventData->mState )
1340 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1341 if( mEventData->mCursorBlinkEnabled )
1343 mEventData->mDecorator->StartCursorBlink();
1345 // Grab handle is not shown until a tap is received whilst EDITING
1346 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1347 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1348 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1349 if( mEventData->mGrabHandlePopupEnabled )
1351 mEventData->mDecorator->SetPopupActive( false );
1353 mEventData->mDecoratorUpdated = true;
1356 else if( EventData::EDITING_WITH_POPUP == mEventData->mState )
1358 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "EDITING_WITH_POPUP \n", newState );
1360 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1361 if( mEventData->mCursorBlinkEnabled )
1363 mEventData->mDecorator->StartCursorBlink();
1365 if( mEventData->mSelectionEnabled )
1367 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1368 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1372 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
1374 if( mEventData->mGrabHandlePopupEnabled )
1377 mEventData->mDecorator->SetPopupActive( true );
1380 mEventData->mDecoratorUpdated = true;
1382 else if( EventData::EDITING_WITH_GRAB_HANDLE == mEventData->mState )
1384 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "EDITING_WITH_GRAB_HANDLE \n", newState );
1386 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1387 if( mEventData->mCursorBlinkEnabled )
1389 mEventData->mDecorator->StartCursorBlink();
1391 // Grab handle is not shown until a tap is received whilst EDITING
1392 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
1393 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1394 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1395 if( mEventData->mGrabHandlePopupEnabled )
1397 mEventData->mDecorator->SetPopupActive( false );
1399 mEventData->mDecoratorUpdated = true;
1402 else if( EventData::SELECTION_HANDLE_PANNING == mEventData->mState )
1404 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
1405 mEventData->mDecorator->StopCursorBlink();
1406 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1407 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true );
1408 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true );
1409 if( mEventData->mGrabHandlePopupEnabled )
1411 mEventData->mDecorator->SetPopupActive( false );
1413 mEventData->mDecoratorUpdated = true;
1415 else if( EventData::GRAB_HANDLE_PANNING == mEventData->mState )
1417 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "GRAB_HANDLE_PANNING \n", newState );
1419 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1420 if( mEventData->mCursorBlinkEnabled )
1422 mEventData->mDecorator->StartCursorBlink();
1424 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
1425 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1426 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1427 if( mEventData->mGrabHandlePopupEnabled )
1429 mEventData->mDecorator->SetPopupActive( false );
1431 mEventData->mDecoratorUpdated = true;
1433 else if( EventData::EDITING_WITH_PASTE_POPUP == mEventData->mState )
1435 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "EDITING_WITH_PASTE_POPUP \n", newState );
1437 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1438 if( mEventData->mCursorBlinkEnabled )
1440 mEventData->mDecorator->StartCursorBlink();
1443 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
1444 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1445 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1447 if( mEventData->mGrabHandlePopupEnabled )
1450 mEventData->mDecorator->SetPopupActive( true );
1453 mEventData->mDecoratorUpdated = true;
1458 LineIndex Controller::Impl::GetClosestLine( float y ) const
1460 float totalHeight = 0.f;
1461 LineIndex lineIndex = 0u;
1463 const Vector<LineRun>& lines = mVisualModel->mLines;
1464 for( LineIndex endLine = lines.Count();
1465 lineIndex < endLine;
1468 const LineRun& lineRun = lines[lineIndex];
1469 totalHeight += lineRun.ascender + -lineRun.descender;
1470 if( y < totalHeight )
1476 if( lineIndex == 0 )
1484 void Controller::Impl::FindSelectionIndices( float visualX, float visualY, CharacterIndex& startIndex, CharacterIndex& endIndex )
1486 CharacterIndex hitCharacter = GetClosestCursorIndex( visualX, visualY );
1487 DALI_ASSERT_DEBUG( hitCharacter <= mLogicalModel->mText.Count() && "GetClosestCursorIndex returned out of bounds index" );
1489 if( mLogicalModel->mText.Count() == 0 )
1491 return; // if model empty
1494 if( hitCharacter >= mLogicalModel->mText.Count() )
1496 // Closest hit character is the last character.
1497 if( hitCharacter == mLogicalModel->mText.Count() )
1499 hitCharacter--; //Hit character index set to last character in logical model
1503 // hitCharacter is out of bounds
1508 startIndex = hitCharacter;
1509 endIndex = hitCharacter;
1510 bool isHitCharacterWhitespace = TextAbstraction::IsWhiteSpace( mLogicalModel->mText[hitCharacter] );
1512 // Find the start and end of the text
1513 for( startIndex = hitCharacter; startIndex > 0; --startIndex )
1515 if( isHitCharacterWhitespace != TextAbstraction::IsWhiteSpace( mLogicalModel->mText[ startIndex-1 ] ) )
1520 const CharacterIndex pastTheEnd = mLogicalModel->mText.Count();
1521 for( endIndex = hitCharacter + 1u; endIndex < pastTheEnd; ++endIndex )
1523 if( isHitCharacterWhitespace != TextAbstraction::IsWhiteSpace( mLogicalModel->mText[ endIndex ] ) )
1530 CharacterIndex Controller::Impl::GetClosestCursorIndex( float visualX,
1533 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "GetClosestCursorIndex %p closest visualX %f visualY %f\n", this, visualX, visualY );
1535 if( NULL == mEventData )
1537 // Nothing to do if there is no text input.
1541 CharacterIndex logicalIndex = 0u;
1543 const Length numberOfGlyphs = mVisualModel->mGlyphs.Count();
1544 const Length numberOfLines = mVisualModel->mLines.Count();
1545 if( ( 0 == numberOfGlyphs ) ||
1546 ( 0 == numberOfLines ) )
1548 return logicalIndex;
1551 // Find which line is closest
1552 const LineIndex lineIndex = GetClosestLine( visualY );
1553 const LineRun& line = mVisualModel->mLines[lineIndex];
1555 // Get the positions of the glyphs.
1556 const Vector<Vector2>& positions = mVisualModel->mGlyphPositions;
1557 const Vector2* const positionsBuffer = positions.Begin();
1559 // Get the visual to logical conversion tables.
1560 const CharacterIndex* const visualToLogicalBuffer = ( 0u != mLogicalModel->mVisualToLogicalMap.Count() ) ? mLogicalModel->mVisualToLogicalMap.Begin() : NULL;
1561 const CharacterIndex* const visualToLogicalCursorBuffer = mLogicalModel->mVisualToLogicalCursorMap.Begin();
1563 // Get the character to glyph conversion table.
1564 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
1566 // Get the glyphs per character table.
1567 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
1569 // If the vector is void, there is no right to left characters.
1570 const bool hasRightToLeftCharacters = NULL != visualToLogicalBuffer;
1572 const CharacterIndex startCharacter = line.characterRun.characterIndex;
1573 const CharacterIndex endCharacter = line.characterRun.characterIndex + line.characterRun.numberOfCharacters;
1574 DALI_ASSERT_DEBUG( endCharacter <= mLogicalModel->mText.Count() && "Invalid line info" );
1576 // Whether there is a hit on a glyph.
1577 bool matched = false;
1579 // Traverses glyphs in visual order. To do that use the visual to logical conversion table.
1580 CharacterIndex visualIndex = startCharacter;
1581 Length numberOfCharacters = 0u;
1582 for( ; !matched && ( visualIndex < endCharacter ); ++visualIndex )
1584 // The character in logical order.
1585 const CharacterIndex characterLogicalOrderIndex = hasRightToLeftCharacters ? *( visualToLogicalBuffer + visualIndex ) : visualIndex;
1587 // Get the script of the character.
1588 const Script script = mLogicalModel->GetScript( characterLogicalOrderIndex );
1590 // The number of glyphs for that character
1591 const Length numberOfGlyphs = *( glyphsPerCharacterBuffer + characterLogicalOrderIndex );
1592 ++numberOfCharacters;
1595 if( 0u != numberOfGlyphs )
1597 // Get the first character/glyph of the group of glyphs.
1598 const CharacterIndex firstVisualCharacterIndex = 1u + visualIndex - numberOfCharacters;
1599 const CharacterIndex firstLogicalCharacterIndex = hasRightToLeftCharacters ? *( visualToLogicalBuffer + firstVisualCharacterIndex ) : firstVisualCharacterIndex;
1600 const GlyphIndex firstLogicalGlyphIndex = *( charactersToGlyphBuffer + firstLogicalCharacterIndex );
1602 // Get the metrics for the group of glyphs.
1603 GlyphMetrics glyphMetrics;
1604 GetGlyphsMetrics( firstLogicalGlyphIndex,
1610 // Get the position of the first glyph.
1611 const Vector2& position = *( positionsBuffer + firstLogicalGlyphIndex );
1613 // Whether the glyph can be split, like Latin ligatures fi, ff or Arabic ﻻ.
1614 const bool isInterglyphIndex = ( numberOfCharacters > numberOfGlyphs ) && HasLigatureMustBreak( script );
1615 const Length numberOfBlocks = isInterglyphIndex ? numberOfCharacters : 1u;
1616 const float glyphAdvance = glyphMetrics.advance / static_cast<float>( numberOfBlocks );
1618 GlyphIndex index = 0u;
1619 for( ; !matched && ( index < numberOfBlocks ); ++index )
1621 // Find the mid-point of the area containing the glyph
1622 const float glyphCenter = -glyphMetrics.xBearing + position.x + ( static_cast<float>( index ) + 0.5f ) * glyphAdvance;
1624 if( visualX < glyphCenter )
1633 visualIndex = firstVisualCharacterIndex + index;
1637 numberOfCharacters = 0u;
1643 // Return the logical position of the cursor in characters.
1647 visualIndex = endCharacter;
1650 logicalIndex = hasRightToLeftCharacters ? *( visualToLogicalCursorBuffer + visualIndex ) : visualIndex;
1651 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "%p closest visualIndex %d logicalIndex %d\n", this, visualIndex, logicalIndex );
1653 DALI_ASSERT_DEBUG( ( logicalIndex <= mLogicalModel->mText.Count() && logicalIndex >= 0 ) && "GetClosestCursorIndex - Out of bounds index" );
1655 return logicalIndex;
1658 void Controller::Impl::GetCursorPosition( CharacterIndex logical,
1659 CursorInfo& cursorInfo )
1661 // TODO: Check for multiline with \n, etc...
1663 const Length numberOfCharacters = mLogicalModel->mText.Count();
1664 if( !IsShowingRealText() )
1666 // Do not want to use the place-holder text to set the cursor position.
1668 // Use the line's height of the font's family set to set the cursor's size.
1669 // If there is no font's family set, use the default font.
1670 // Use the current alignment to place the cursor at the beginning, center or end of the box.
1672 cursorInfo.lineOffset = 0.f;
1673 cursorInfo.lineHeight = GetDefaultFontLineHeight();
1674 cursorInfo.primaryCursorHeight = cursorInfo.lineHeight;
1676 switch( mLayoutEngine.GetHorizontalAlignment() )
1678 case LayoutEngine::HORIZONTAL_ALIGN_BEGIN:
1680 cursorInfo.primaryPosition.x = 0.f;
1683 case LayoutEngine::HORIZONTAL_ALIGN_CENTER:
1685 cursorInfo.primaryPosition.x = floorf( 0.5f * mVisualModel->mControlSize.width );
1688 case LayoutEngine::HORIZONTAL_ALIGN_END:
1690 cursorInfo.primaryPosition.x = mVisualModel->mControlSize.width - mEventData->mDecorator->GetCursorWidth();
1695 switch( mLayoutEngine.GetVerticalAlignment() )
1697 case LayoutEngine::VERTICAL_ALIGN_TOP:
1699 cursorInfo.primaryPosition.y = 0.f;
1702 case LayoutEngine::VERTICAL_ALIGN_CENTER:
1704 cursorInfo.primaryPosition.y = floorf( 0.5f * ( mVisualModel->mControlSize.height - cursorInfo.lineHeight ) );
1707 case LayoutEngine::VERTICAL_ALIGN_BOTTOM:
1709 cursorInfo.primaryPosition.y = mVisualModel->mControlSize.height - cursorInfo.lineHeight;
1714 // Nothing else to do.
1718 // Check if the logical position is the first or the last one of the text.
1719 const bool isFirstPosition = 0u == logical;
1720 const bool isLastPosition = numberOfCharacters == logical;
1722 // 'logical' is the logical 'cursor' index.
1723 // Get the next and current logical 'character' index.
1724 const CharacterIndex nextCharacterIndex = logical;
1725 const CharacterIndex characterIndex = isFirstPosition ? logical : logical - 1u;
1727 // Get the direction of the character and the next one.
1728 const CharacterDirection* const modelCharacterDirectionsBuffer = ( 0u != mLogicalModel->mCharacterDirections.Count() ) ? mLogicalModel->mCharacterDirections.Begin() : NULL;
1730 CharacterDirection isCurrentRightToLeft = false;
1731 CharacterDirection isNextRightToLeft = false;
1732 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
1734 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + characterIndex );
1735 isNextRightToLeft = *( modelCharacterDirectionsBuffer + nextCharacterIndex );
1738 // Get the line where the character is laid-out.
1739 const LineRun* const modelLines = mVisualModel->mLines.Begin();
1741 const LineIndex lineIndex = mVisualModel->GetLineOfCharacter( characterIndex );
1742 const LineRun& line = *( modelLines + lineIndex );
1744 // Get the paragraph's direction.
1745 const CharacterDirection isRightToLeftParagraph = line.direction;
1747 // Check whether there is an alternative position:
1749 cursorInfo.isSecondaryCursor = ( !isLastPosition && ( isCurrentRightToLeft != isNextRightToLeft ) ) ||
1750 ( isLastPosition && ( isRightToLeftParagraph != isCurrentRightToLeft ) );
1752 // Set the line offset and height.
1753 cursorInfo.lineOffset = 0.f;
1754 cursorInfo.lineHeight = line.ascender + -line.descender;
1756 // Calculate the primary cursor.
1758 CharacterIndex index = characterIndex;
1759 if( cursorInfo.isSecondaryCursor )
1761 // If there is a secondary position, the primary cursor may be in a different place than the logical index.
1763 if( isLastPosition )
1765 // The position of the cursor after the last character needs special
1766 // care depending on its direction and the direction of the paragraph.
1768 // Need to find the first character after the last character with the paragraph's direction.
1769 // i.e l0 l1 l2 r0 r1 should find r0.
1771 // TODO: check for more than one line!
1772 index = isRightToLeftParagraph ? line.characterRun.characterIndex : line.characterRun.characterIndex + line.characterRun.numberOfCharacters - 1u;
1773 index = mLogicalModel->GetLogicalCharacterIndex( index );
1777 index = ( isRightToLeftParagraph == isCurrentRightToLeft ) ? characterIndex : nextCharacterIndex;
1781 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
1782 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
1783 const Length* const charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
1784 const CharacterIndex* const glyphsToCharactersBuffer = mVisualModel->mGlyphsToCharacters.Begin();
1785 const Vector2* const glyphPositionsBuffer = mVisualModel->mGlyphPositions.Begin();
1787 // Convert the cursor position into the glyph position.
1788 const GlyphIndex primaryGlyphIndex = *( charactersToGlyphBuffer + index );
1789 const Length primaryNumberOfGlyphs = *( glyphsPerCharacterBuffer + index );
1790 const Length primaryNumberOfCharacters = *( charactersPerGlyphBuffer + primaryGlyphIndex );
1792 // Get the metrics for the group of glyphs.
1793 GlyphMetrics glyphMetrics;
1794 GetGlyphsMetrics( primaryGlyphIndex,
1795 primaryNumberOfGlyphs,
1800 // Whether to add the glyph's advance to the cursor position.
1801 // 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,
1802 // if the logical cursor is one, the position is the position of the first glyph and the advance is added.
1803 // A 'truth table' was build and an online Karnaugh map tool was used to simplify the logic.
1824 // Where F -> isFirstPosition
1825 // L -> isLastPosition
1826 // C -> isCurrentRightToLeft
1827 // P -> isRightToLeftParagraph
1828 // A -> Whether to add the glyph's advance.
1830 const bool addGlyphAdvance = ( ( isLastPosition && !isRightToLeftParagraph ) ||
1831 ( isFirstPosition && isRightToLeftParagraph ) ||
1832 ( !isFirstPosition && !isLastPosition && !isCurrentRightToLeft ) );
1834 float glyphAdvance = addGlyphAdvance ? glyphMetrics.advance : 0.f;
1836 if( !isLastPosition &&
1837 ( primaryNumberOfCharacters > 1u ) )
1839 const CharacterIndex firstIndex = *( glyphsToCharactersBuffer + primaryGlyphIndex );
1841 bool isCurrentRightToLeft = false;
1842 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
1844 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + index );
1847 Length numberOfGlyphAdvance = ( isFirstPosition ? 0u : 1u ) + characterIndex - firstIndex;
1848 if( isCurrentRightToLeft )
1850 numberOfGlyphAdvance = primaryNumberOfCharacters - numberOfGlyphAdvance;
1853 glyphAdvance = static_cast<float>( numberOfGlyphAdvance ) * glyphMetrics.advance / static_cast<float>( primaryNumberOfCharacters );
1856 // Get the glyph position and x bearing.
1857 const Vector2& primaryPosition = *( glyphPositionsBuffer + primaryGlyphIndex );
1859 // Set the primary cursor's height.
1860 cursorInfo.primaryCursorHeight = cursorInfo.isSecondaryCursor ? 0.5f * glyphMetrics.fontHeight : glyphMetrics.fontHeight;
1862 // Set the primary cursor's position.
1863 cursorInfo.primaryPosition.x = -glyphMetrics.xBearing + primaryPosition.x + glyphAdvance;
1864 cursorInfo.primaryPosition.y = line.ascender - glyphMetrics.ascender;
1866 // Calculate the secondary cursor.
1868 if( cursorInfo.isSecondaryCursor )
1870 // Set the secondary cursor's height.
1871 cursorInfo.secondaryCursorHeight = 0.5f * glyphMetrics.fontHeight;
1873 CharacterIndex index = characterIndex;
1874 if( !isLastPosition )
1876 index = ( isRightToLeftParagraph == isCurrentRightToLeft ) ? nextCharacterIndex : characterIndex;
1879 const GlyphIndex secondaryGlyphIndex = *( charactersToGlyphBuffer + index );
1880 const Length secondaryNumberOfGlyphs = *( glyphsPerCharacterBuffer + index );
1882 const Vector2& secondaryPosition = *( glyphPositionsBuffer + secondaryGlyphIndex );
1884 GetGlyphsMetrics( secondaryGlyphIndex,
1885 secondaryNumberOfGlyphs,
1890 // Set the secondary cursor's position.
1891 cursorInfo.secondaryPosition.x = -glyphMetrics.xBearing + secondaryPosition.x + ( isCurrentRightToLeft ? 0.f : glyphMetrics.advance );
1892 cursorInfo.secondaryPosition.y = cursorInfo.lineHeight - cursorInfo.secondaryCursorHeight - line.descender - ( glyphMetrics.fontHeight - glyphMetrics.ascender );
1896 CharacterIndex Controller::Impl::CalculateNewCursorIndex( CharacterIndex index ) const
1898 if( NULL == mEventData )
1900 // Nothing to do if there is no text input.
1904 CharacterIndex cursorIndex = mEventData->mPrimaryCursorPosition;
1906 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
1907 const Length* const charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
1909 GlyphIndex glyphIndex = *( charactersToGlyphBuffer + index );
1910 Length numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
1912 if( numberOfCharacters > 1u )
1914 const Script script = mLogicalModel->GetScript( index );
1915 if( HasLigatureMustBreak( script ) )
1917 // Prevents to jump the whole Latin ligatures like fi, ff, or Arabic ﻻ, ...
1918 numberOfCharacters = 1u;
1923 while( 0u == numberOfCharacters )
1926 numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
1930 if( index < mEventData->mPrimaryCursorPosition )
1932 cursorIndex -= numberOfCharacters;
1936 cursorIndex += numberOfCharacters;
1942 void Controller::Impl::UpdateCursorPosition( const CursorInfo& cursorInfo )
1944 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "-->Controller::UpdateCursorPosition %p\n", this );
1945 if( NULL == mEventData )
1947 // Nothing to do if there is no text input.
1948 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition no event data\n" );
1952 const Vector2 offset = mEventData->mScrollPosition + ( IsShowingRealText() ? mAlignmentOffset : Vector2::ZERO );
1953 const Vector2 cursorPosition = cursorInfo.primaryPosition + offset;
1955 // Sets the cursor position.
1956 mEventData->mDecorator->SetPosition( PRIMARY_CURSOR,
1959 cursorInfo.primaryCursorHeight,
1960 cursorInfo.lineHeight );
1961 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Primary cursor position: %f,%f\n", cursorPosition.x, cursorPosition.y );
1963 // Sets the grab handle position.
1964 mEventData->mDecorator->SetPosition( GRAB_HANDLE,
1966 cursorInfo.lineOffset + offset.y,
1967 cursorInfo.lineHeight );
1969 if( cursorInfo.isSecondaryCursor )
1971 mEventData->mDecorator->SetPosition( SECONDARY_CURSOR,
1972 cursorInfo.secondaryPosition.x + offset.x,
1973 cursorInfo.secondaryPosition.y + offset.y,
1974 cursorInfo.secondaryCursorHeight,
1975 cursorInfo.lineHeight );
1976 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Secondary cursor position: %f,%f\n", cursorInfo.secondaryPosition.x + offset.x, cursorInfo.secondaryPosition.y + offset.y );
1979 // Set which cursors are active according the state.
1980 if( EventData::IsEditingState( mEventData->mState ) || ( EventData::GRAB_HANDLE_PANNING == mEventData->mState ) )
1982 if( cursorInfo.isSecondaryCursor )
1984 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_BOTH );
1988 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1993 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
1996 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition\n" );
1999 void Controller::Impl::UpdateSelectionHandle( HandleType handleType,
2000 const CursorInfo& cursorInfo )
2002 if( ( LEFT_SELECTION_HANDLE != handleType ) &&
2003 ( RIGHT_SELECTION_HANDLE != handleType ) )
2008 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
2009 const Vector2 cursorPosition = cursorInfo.primaryPosition + offset;
2011 // Sets the handle's position.
2012 mEventData->mDecorator->SetPosition( handleType,
2014 cursorInfo.lineOffset + offset.y,
2015 cursorInfo.lineHeight );
2017 // If selection handle at start of the text and other at end of the text then all text is selected.
2018 const CharacterIndex startOfSelection = std::min( mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition );
2019 const CharacterIndex endOfSelection = std::max ( mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition );
2020 mEventData->mAllTextSelected = ( startOfSelection == 0 ) && ( endOfSelection == mLogicalModel->mText.Count() );
2023 void Controller::Impl::ClampHorizontalScroll( const Vector2& actualSize )
2025 // Clamp between -space & 0 (and the text alignment).
2027 if( actualSize.width > mVisualModel->mControlSize.width )
2029 const float space = ( actualSize.width - mVisualModel->mControlSize.width ) + mAlignmentOffset.x;
2030 mEventData->mScrollPosition.x = ( mEventData->mScrollPosition.x < -space ) ? -space : mEventData->mScrollPosition.x;
2031 mEventData->mScrollPosition.x = ( mEventData->mScrollPosition.x > -mAlignmentOffset.x ) ? -mAlignmentOffset.x : mEventData->mScrollPosition.x;
2033 mEventData->mDecoratorUpdated = true;
2037 mEventData->mScrollPosition.x = 0.f;
2041 void Controller::Impl::ClampVerticalScroll( const Vector2& actualSize )
2043 // Clamp between -space & 0 (and the text alignment).
2044 if( actualSize.height > mVisualModel->mControlSize.height )
2046 const float space = ( actualSize.height - mVisualModel->mControlSize.height ) + mAlignmentOffset.y;
2047 mEventData->mScrollPosition.y = ( mEventData->mScrollPosition.y < -space ) ? -space : mEventData->mScrollPosition.y;
2048 mEventData->mScrollPosition.y = ( mEventData->mScrollPosition.y > -mAlignmentOffset.y ) ? -mAlignmentOffset.y : mEventData->mScrollPosition.y;
2050 mEventData->mDecoratorUpdated = true;
2054 mEventData->mScrollPosition.y = 0.f;
2058 void Controller::Impl::ScrollToMakePositionVisible( const Vector2& position )
2060 // position is in actor's coords.
2061 const float positionEnd = position.x + ( mEventData->mDecorator ? mEventData->mDecorator->GetCursorWidth() : 0.f );
2063 // Transform the position to decorator coords.
2064 const float offset = mEventData->mScrollPosition.x + mAlignmentOffset.x;
2065 const float decoratorPositionBegin = position.x + offset;
2066 const float decoratorPositionEnd = positionEnd + offset;
2068 if( decoratorPositionBegin < 0.f )
2070 mEventData->mScrollPosition.x = -position.x - mAlignmentOffset.x;
2072 else if( decoratorPositionEnd > mVisualModel->mControlSize.width )
2074 mEventData->mScrollPosition.x = mVisualModel->mControlSize.width - positionEnd - mAlignmentOffset.x;
2078 void Controller::Impl::ScrollTextToMatchCursor( const CursorInfo& cursorInfo )
2080 // Get the current cursor position in decorator coords.
2081 const Vector2& currentCursorPosition = mEventData->mDecorator->GetPosition( PRIMARY_CURSOR );
2083 // Calculate the offset to match the cursor position before the character was deleted.
2084 mEventData->mScrollPosition.x = currentCursorPosition.x - cursorInfo.primaryPosition.x - mAlignmentOffset.x;
2086 ClampHorizontalScroll( mVisualModel->GetActualSize() );
2089 void Controller::Impl::RequestRelayout()
2091 mControlInterface.RequestTextRelayout();
2096 } // namespace Toolkit