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 if( GET_LINE_BREAKS & operations )
330 // Retrieves the line break info. The line break info is used to split the text in 'paragraphs' to
331 // calculate the bidirectional info for each 'paragraph'.
332 // It's also used to layout the text (where it should be a new line) or to shape the text (text in different lines
333 // is not shaped together).
334 lineBreakInfo.Resize( numberOfCharacters, TextAbstraction::LINE_NO_BREAK );
336 SetLineBreakInfo( utf32Characters,
340 Vector<WordBreakInfo>& wordBreakInfo = mLogicalModel->mWordBreakInfo;
341 if( GET_WORD_BREAKS & operations )
343 // Retrieves the word break info. The word break info is used to layout the text (where to wrap the text in lines).
344 wordBreakInfo.Resize( numberOfCharacters, TextAbstraction::WORD_NO_BREAK );
346 SetWordBreakInfo( utf32Characters,
350 const bool getScripts = GET_SCRIPTS & operations;
351 const bool validateFonts = VALIDATE_FONTS & operations;
353 Vector<ScriptRun>& scripts = mLogicalModel->mScriptRuns;
354 Vector<FontRun>& validFonts = mLogicalModel->mFontRuns;
356 if( getScripts || validateFonts )
358 // Validates the fonts assigned by the application or assigns default ones.
359 // It makes sure all the characters are going to be rendered by the correct font.
360 MultilanguageSupport multilanguageSupport = MultilanguageSupport::Get();
364 // Retrieves the scripts used in the text.
365 multilanguageSupport.SetScripts( utf32Characters,
371 if( 0u == validFonts.Count() )
373 // Copy the requested font defaults received via the property system.
374 // These may not be valid i.e. may not contain glyphs for the necessary scripts.
375 GetDefaultFonts( validFonts, numberOfCharacters );
378 // Validates the fonts. If there is a character with no assigned font it sets a default one.
379 // After this call, fonts are validated.
380 multilanguageSupport.ValidateFonts( utf32Characters,
386 Vector<Character> mirroredUtf32Characters;
387 bool textMirrored = false;
388 Length numberOfParagraphs = 0u;
389 if( BIDI_INFO & operations )
391 // Count the number of LINE_NO_BREAK to reserve some space for the vector of paragraph's
392 // bidirectional info.
394 const TextAbstraction::LineBreakInfo* lineBreakInfoBuffer = lineBreakInfo.Begin();
395 for( Length index = 0u; index < numberOfCharacters; ++index )
397 if( TextAbstraction::LINE_NO_BREAK == *( lineBreakInfoBuffer + index ) )
399 ++numberOfParagraphs;
403 Vector<BidirectionalParagraphInfoRun>& bidirectionalInfo = mLogicalModel->mBidirectionalParagraphInfo;
404 bidirectionalInfo.Reserve( numberOfParagraphs );
406 // Calculates the bidirectional info for the whole paragraph if it contains right to left scripts.
407 SetBidirectionalInfo( utf32Characters,
412 if( 0u != bidirectionalInfo.Count() )
414 // This paragraph has right to left text. Some characters may need to be mirrored.
415 // TODO: consider if the mirrored string can be stored as well.
417 textMirrored = GetMirroredText( utf32Characters,
418 mirroredUtf32Characters,
421 // Only set the character directions if there is right to left characters.
422 Vector<CharacterDirection>& directions = mLogicalModel->mCharacterDirections;
423 directions.Resize( numberOfCharacters );
425 GetCharactersDirection( bidirectionalInfo,
430 // There is no right to left characters. Clear the directions vector.
431 mLogicalModel->mCharacterDirections.Clear();
435 Vector<GlyphInfo>& glyphs = mVisualModel->mGlyphs;
436 Vector<CharacterIndex>& glyphsToCharactersMap = mVisualModel->mGlyphsToCharacters;
437 Vector<Length>& charactersPerGlyph = mVisualModel->mCharactersPerGlyph;
438 Vector<GlyphIndex> newParagraphGlyphs;
439 newParagraphGlyphs.Reserve( numberOfParagraphs );
441 if( SHAPE_TEXT & operations )
443 const Vector<Character>& textToShape = textMirrored ? mirroredUtf32Characters : utf32Characters;
445 ShapeText( textToShape,
450 glyphsToCharactersMap,
452 newParagraphGlyphs );
454 // Create the 'number of glyphs' per character and the glyph to character conversion tables.
455 mVisualModel->CreateGlyphsPerCharacterTable( numberOfCharacters );
456 mVisualModel->CreateCharacterToGlyphTable( numberOfCharacters );
459 const Length numberOfGlyphs = glyphs.Count();
461 if( GET_GLYPH_METRICS & operations )
463 GlyphInfo* glyphsBuffer = glyphs.Begin();
464 mMetrics->GetGlyphMetrics( glyphsBuffer, numberOfGlyphs );
466 // Update the width and advance of all new paragraph characters.
467 for( Vector<GlyphIndex>::ConstIterator it = newParagraphGlyphs.Begin(), endIt = newParagraphGlyphs.End(); it != endIt; ++it )
469 const GlyphIndex index = *it;
470 GlyphInfo& glyph = *( glyphsBuffer + index );
472 glyph.xBearing = 0.f;
478 if( ( NULL != mEventData ) &&
479 mEventData->mPreEditFlag &&
480 ( 0u != mVisualModel->mCharactersToGlyph.Count() ) )
482 // Add the underline for the pre-edit text.
483 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
484 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
486 const GlyphIndex glyphStart = *( charactersToGlyphBuffer + mEventData->mPreEditStartPosition );
487 const CharacterIndex lastPreEditCharacter = mEventData->mPreEditStartPosition + ( ( mEventData->mPreEditLength > 0u ) ? mEventData->mPreEditLength - 1u : 0u );
488 const Length numberOfGlyphsLastCharacter = *( glyphsPerCharacterBuffer + lastPreEditCharacter );
489 const GlyphIndex glyphEnd = *( charactersToGlyphBuffer + lastPreEditCharacter ) + ( numberOfGlyphsLastCharacter > 1u ? numberOfGlyphsLastCharacter - 1u : 0u );
491 GlyphRun underlineRun;
492 underlineRun.glyphIndex = glyphStart;
493 underlineRun.numberOfGlyphs = 1u + glyphEnd - glyphStart;
495 // TODO: At the moment the underline runs are only for pre-edit.
496 mVisualModel->mUnderlineRuns.PushBack( underlineRun );
500 bool Controller::Impl::UpdateModelStyle( OperationsMask operationsRequired )
502 bool updated = false;
504 if( COLOR & operationsRequired )
506 // Set the color runs in glyphs.
507 SetColorSegmentationInfo( mLogicalModel->mColorRuns,
508 mVisualModel->mCharactersToGlyph,
509 mVisualModel->mGlyphsPerCharacter,
510 mVisualModel->mColorRuns );
518 void Controller::Impl::RetrieveDefaultInputStyle( InputStyle& inputStyle )
520 // Set the default text's color.
521 inputStyle.textColor = mTextColor;
524 void Controller::Impl::GetDefaultFonts( Vector<FontRun>& fonts, Length numberOfCharacters )
528 DALI_LOG_INFO( gLogFilter, Debug::General, "Controller::GetDefaultFonts font family(%s)\n", mFontDefaults->mFontDescription.family.c_str() );
530 fontRun.characterRun.characterIndex = 0;
531 fontRun.characterRun.numberOfCharacters = numberOfCharacters;
532 fontRun.fontId = mFontDefaults->GetFontId( mFontClient );
533 fontRun.isDefault = true;
535 fonts.PushBack( fontRun );
539 float Controller::Impl::GetDefaultFontLineHeight()
541 FontId defaultFontId = 0u;
542 if( NULL == mFontDefaults )
544 TextAbstraction::FontDescription fontDescription;
545 defaultFontId = mFontClient.GetFontId( fontDescription );
549 defaultFontId = mFontDefaults->GetFontId( mFontClient );
552 Text::FontMetrics fontMetrics;
553 mMetrics->GetFontMetrics( defaultFontId, fontMetrics );
555 return( fontMetrics.ascender - fontMetrics.descender );
558 void Controller::Impl::OnCursorKeyEvent( const Event& event )
560 if( NULL == mEventData )
562 // Nothing to do if there is no text input.
566 int keyCode = event.p1.mInt;
568 if( Dali::DALI_KEY_CURSOR_LEFT == keyCode )
570 if( mEventData->mPrimaryCursorPosition > 0u )
572 mEventData->mPrimaryCursorPosition = CalculateNewCursorIndex( mEventData->mPrimaryCursorPosition - 1u );
575 else if( Dali::DALI_KEY_CURSOR_RIGHT == keyCode )
577 if( mLogicalModel->mText.Count() > mEventData->mPrimaryCursorPosition )
579 mEventData->mPrimaryCursorPosition = CalculateNewCursorIndex( mEventData->mPrimaryCursorPosition );
582 else if( Dali::DALI_KEY_CURSOR_UP == keyCode )
586 else if( Dali::DALI_KEY_CURSOR_DOWN == keyCode )
591 mEventData->mUpdateCursorPosition = true;
592 mEventData->mUpdateInputStyle = true;
593 mEventData->mScrollAfterUpdatePosition = true;
596 void Controller::Impl::OnTapEvent( const Event& event )
598 if( NULL != mEventData )
600 const unsigned int tapCount = event.p1.mUint;
604 if( IsShowingRealText() )
606 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
607 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
609 mEventData->mPrimaryCursorPosition = GetClosestCursorIndex( xPosition,
612 // When the cursor position is changing, delay cursor blinking
613 mEventData->mDecorator->DelayCursorBlink();
617 mEventData->mPrimaryCursorPosition = 0u;
620 mEventData->mUpdateCursorPosition = true;
621 mEventData->mScrollAfterUpdatePosition = true;
622 mEventData->mUpdateInputStyle = true;
624 // Notify the cursor position to the imf manager.
625 if( mEventData->mImfManager )
627 mEventData->mImfManager.SetCursorPosition( mEventData->mPrimaryCursorPosition );
628 mEventData->mImfManager.NotifyCursorPosition();
634 void Controller::Impl::OnPanEvent( const Event& event )
636 if( NULL == mEventData )
638 // Nothing to do if there is no text input.
642 int state = event.p1.mInt;
644 if( Gesture::Started == state ||
645 Gesture::Continuing == state )
647 const Vector2& actualSize = mVisualModel->GetActualSize();
648 const Vector2 currentScroll = mEventData->mScrollPosition;
650 if( mEventData->mHorizontalScrollingEnabled )
652 const float displacementX = event.p2.mFloat;
653 mEventData->mScrollPosition.x += displacementX;
655 ClampHorizontalScroll( actualSize );
658 if( mEventData->mVerticalScrollingEnabled )
660 const float displacementY = event.p3.mFloat;
661 mEventData->mScrollPosition.y += displacementY;
663 ClampVerticalScroll( actualSize );
666 if( mEventData->mDecorator )
668 mEventData->mDecorator->UpdatePositions( mEventData->mScrollPosition - currentScroll );
673 void Controller::Impl::OnLongPressEvent( const Event& event )
675 DALI_LOG_INFO( gLogFilter, Debug::General, "Controller::OnLongPressEvent\n" );
677 if( EventData::EDITING == mEventData->mState )
679 ChangeState ( EventData::EDITING_WITH_POPUP );
680 mEventData->mDecoratorUpdated = true;
684 void Controller::Impl::OnHandleEvent( const Event& event )
686 if( NULL == mEventData )
688 // Nothing to do if there is no text input.
692 const unsigned int state = event.p1.mUint;
693 const bool handleStopScrolling = ( HANDLE_STOP_SCROLLING == state );
695 if( HANDLE_PRESSED == state )
697 // The event.p2 and event.p3 are in decorator coords. Need to transforms to text coords.
698 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
699 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
701 const CharacterIndex handleNewPosition = GetClosestCursorIndex( xPosition, yPosition );
703 if( Event::GRAB_HANDLE_EVENT == event.type )
705 ChangeState ( EventData::GRAB_HANDLE_PANNING );
707 if( handleNewPosition != mEventData->mPrimaryCursorPosition )
709 mEventData->mPrimaryCursorPosition = handleNewPosition;
710 mEventData->mUpdateCursorPosition = true;
713 else if( Event::LEFT_SELECTION_HANDLE_EVENT == event.type )
715 ChangeState ( EventData::SELECTION_HANDLE_PANNING );
717 if( ( handleNewPosition != mEventData->mLeftSelectionPosition ) &&
718 ( handleNewPosition != mEventData->mRightSelectionPosition ) )
720 mEventData->mLeftSelectionPosition = handleNewPosition;
722 mEventData->mUpdateLeftSelectionPosition = true;
725 else if( Event::RIGHT_SELECTION_HANDLE_EVENT == event.type )
727 ChangeState ( EventData::SELECTION_HANDLE_PANNING );
729 if( ( handleNewPosition != mEventData->mRightSelectionPosition ) &&
730 ( handleNewPosition != mEventData->mLeftSelectionPosition ) )
732 mEventData->mRightSelectionPosition = handleNewPosition;
734 mEventData->mUpdateRightSelectionPosition = true;
737 } // end ( HANDLE_PRESSED == state )
738 else if( ( HANDLE_RELEASED == state ) ||
739 handleStopScrolling )
741 CharacterIndex handlePosition = 0u;
742 if( handleStopScrolling )
744 // The event.p2 and event.p3 are in decorator coords. Need to transforms to text coords.
745 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
746 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
748 handlePosition = GetClosestCursorIndex( xPosition, yPosition );
751 if( Event::GRAB_HANDLE_EVENT == event.type )
753 mEventData->mUpdateCursorPosition = true;
754 mEventData->mUpdateInputStyle = true;
756 if( !IsClipboardEmpty() )
758 ChangeState( EventData::EDITING_WITH_PASTE_POPUP ); // Moving grabhandle will show Paste Popup
761 if( handleStopScrolling )
763 mEventData->mScrollAfterUpdatePosition = mEventData->mPrimaryCursorPosition != handlePosition;
764 mEventData->mPrimaryCursorPosition = handlePosition;
767 else if( Event::LEFT_SELECTION_HANDLE_EVENT == event.type )
769 ChangeState( EventData::SELECTING );
771 if( handleStopScrolling )
773 mEventData->mUpdateLeftSelectionPosition = ( mEventData->mRightSelectionPosition != handlePosition );
774 mEventData->mScrollAfterUpdatePosition = mEventData->mUpdateLeftSelectionPosition;
776 if( mEventData->mUpdateLeftSelectionPosition )
778 mEventData->mLeftSelectionPosition = handlePosition;
782 else if( Event::RIGHT_SELECTION_HANDLE_EVENT == event.type )
784 ChangeState( EventData::SELECTING );
786 if( handleStopScrolling )
788 mEventData->mUpdateRightSelectionPosition = ( mEventData->mLeftSelectionPosition != handlePosition );
789 mEventData->mScrollAfterUpdatePosition = mEventData->mUpdateRightSelectionPosition;
790 if( mEventData->mUpdateRightSelectionPosition )
792 mEventData->mRightSelectionPosition = handlePosition;
797 mEventData->mDecoratorUpdated = true;
798 } // end ( ( HANDLE_RELEASED == state ) || ( HANDLE_STOP_SCROLLING == state ) )
799 else if( HANDLE_SCROLLING == state )
801 const float xSpeed = event.p2.mFloat;
802 const Vector2& actualSize = mVisualModel->GetActualSize();
803 const Vector2 currentScrollPosition = mEventData->mScrollPosition;
805 mEventData->mScrollPosition.x += xSpeed;
807 ClampHorizontalScroll( actualSize );
809 bool endOfScroll = false;
810 if( Vector2::ZERO == ( currentScrollPosition - mEventData->mScrollPosition ) )
812 // Notify the decorator there is no more text to scroll.
813 // The decorator won't send more scroll events.
814 mEventData->mDecorator->NotifyEndOfScroll();
815 // Still need to set the position of the handle.
819 // Set the position of the handle.
820 const bool scrollRightDirection = xSpeed > 0.f;
821 const bool leftSelectionHandleEvent = Event::LEFT_SELECTION_HANDLE_EVENT == event.type;
822 const bool rightSelectionHandleEvent = Event::RIGHT_SELECTION_HANDLE_EVENT == event.type;
824 if( Event::GRAB_HANDLE_EVENT == event.type )
826 ChangeState( EventData::GRAB_HANDLE_PANNING );
828 Vector2 position = mEventData->mDecorator->GetPosition( GRAB_HANDLE );
830 // Position the grag handle close to either the left or right edge.
831 position.x = scrollRightDirection ? 0.f : mVisualModel->mControlSize.width;
833 // Get the new handle position.
834 // The grab handle's position is in decorator coords. Need to transforms to text coords.
835 const CharacterIndex handlePosition = GetClosestCursorIndex( position.x - mEventData->mScrollPosition.x - mAlignmentOffset.x,
836 position.y - mEventData->mScrollPosition.y - mAlignmentOffset.y );
838 mEventData->mUpdateCursorPosition = mEventData->mPrimaryCursorPosition != handlePosition;
839 mEventData->mScrollAfterUpdatePosition = mEventData->mUpdateCursorPosition;
840 mEventData->mPrimaryCursorPosition = handlePosition;
841 mEventData->mUpdateInputStyle = mEventData->mUpdateCursorPosition;
843 else if( leftSelectionHandleEvent || rightSelectionHandleEvent )
845 // TODO: This is recalculating the selection box every time the text is scrolled with the selection handles.
846 // Think if something can be done to save power.
848 ChangeState( EventData::SELECTION_HANDLE_PANNING );
850 Vector2 position = mEventData->mDecorator->GetPosition( leftSelectionHandleEvent ? Text::LEFT_SELECTION_HANDLE : Text::RIGHT_SELECTION_HANDLE );
852 // Position the selection handle close to either the left or right edge.
853 position.x = scrollRightDirection ? 0.f : mVisualModel->mControlSize.width;
855 // Get the new handle position.
856 // The selection handle's position is in decorator coords. Need to transforms to text coords.
857 const CharacterIndex handlePosition = GetClosestCursorIndex( position.x - mEventData->mScrollPosition.x - mAlignmentOffset.x,
858 position.y - mEventData->mScrollPosition.y - mAlignmentOffset.y );
860 if( leftSelectionHandleEvent )
862 const bool differentHandles = ( mEventData->mLeftSelectionPosition != handlePosition ) && ( mEventData->mRightSelectionPosition != handlePosition );
863 mEventData->mUpdateLeftSelectionPosition = endOfScroll || differentHandles;
864 if( differentHandles )
866 mEventData->mLeftSelectionPosition = handlePosition;
871 const bool differentHandles = ( mEventData->mRightSelectionPosition != handlePosition ) && ( mEventData->mLeftSelectionPosition != handlePosition );
872 mEventData->mUpdateRightSelectionPosition = endOfScroll || differentHandles;
873 if( differentHandles )
875 mEventData->mRightSelectionPosition = handlePosition;
879 if( mEventData->mUpdateLeftSelectionPosition || mEventData->mUpdateRightSelectionPosition )
881 RepositionSelectionHandles();
883 mEventData->mScrollAfterUpdatePosition = true;
886 mEventData->mDecoratorUpdated = true;
887 } // end ( HANDLE_SCROLLING == state )
890 void Controller::Impl::OnSelectEvent( const Event& event )
892 if( NULL == mEventData )
894 // Nothing to do if there is no text.
898 if( mEventData->mSelectionEnabled )
900 // The event.p2 and event.p3 are in decorator coords. Need to transforms to text coords.
901 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
902 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
904 // Calculates the logical position from the x,y coords.
905 RepositionSelectionHandles( xPosition,
908 mEventData->mUpdateLeftSelectionPosition = true;
909 mEventData->mUpdateRightSelectionPosition = true;
911 mEventData->mScrollAfterUpdatePosition = ( mEventData->mLeftSelectionPosition != mEventData->mRightSelectionPosition );
915 void Controller::Impl::OnSelectAllEvent()
917 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "OnSelectAllEvent mEventData->mSelectionEnabled%s \n", mEventData->mSelectionEnabled?"true":"false");
919 if( NULL == mEventData )
921 // Nothing to do if there is no text.
925 if( mEventData->mSelectionEnabled )
927 mEventData->mLeftSelectionPosition = 0u;
928 mEventData->mRightSelectionPosition = mLogicalModel->mText.Count();
930 mEventData->mScrollAfterUpdatePosition = true;
931 mEventData->mUpdateLeftSelectionPosition = true;
932 mEventData->mUpdateRightSelectionPosition = true;
936 void Controller::Impl::RetrieveSelection( std::string& selectedText, bool deleteAfterRetrieval )
938 if( mEventData->mLeftSelectionPosition == mEventData->mRightSelectionPosition )
940 // Nothing to select if handles are in the same place.
941 selectedText.clear();
945 const bool handlesCrossed = mEventData->mLeftSelectionPosition > mEventData->mRightSelectionPosition;
947 //Get start and end position of selection
948 const CharacterIndex startOfSelectedText = handlesCrossed ? mEventData->mRightSelectionPosition : mEventData->mLeftSelectionPosition;
949 const Length lengthOfSelectedText = ( handlesCrossed ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition ) - startOfSelectedText;
951 // Validate the start and end selection points
952 if( ( startOfSelectedText + lengthOfSelectedText ) <= mLogicalModel->mText.Count() )
954 //Get text as a UTF8 string
955 Vector<Character>& utf32Characters = mLogicalModel->mText;
957 Utf32ToUtf8( &utf32Characters[startOfSelectedText], lengthOfSelectedText, selectedText );
959 if( deleteAfterRetrieval ) // Only delete text if copied successfully
961 // Set as input style the style of the first deleted character.
962 mLogicalModel->RetrieveStyle( startOfSelectedText, mEventData->mInputStyle );
964 mLogicalModel->UpdateTextStyleRuns( startOfSelectedText, -static_cast<int>( lengthOfSelectedText ) );
966 // Delete text between handles
967 Vector<Character>& currentText = mLogicalModel->mText;
969 Vector<Character>::Iterator first = currentText.Begin() + startOfSelectedText;
970 Vector<Character>::Iterator last = first + lengthOfSelectedText;
971 currentText.Erase( first, last );
973 // Scroll after delete.
974 mEventData->mPrimaryCursorPosition = handlesCrossed ? mEventData->mRightSelectionPosition : mEventData->mLeftSelectionPosition;
975 mEventData->mScrollAfterDelete = true;
977 // Udpade the cursor position and the decorator.
978 // Scroll after the position is updated if is not scrolling after delete.
979 mEventData->mUpdateCursorPosition = true;
980 mEventData->mScrollAfterUpdatePosition = !mEventData->mScrollAfterDelete;
981 mEventData->mDecoratorUpdated = true;
985 void Controller::Impl::ShowClipboard()
989 mClipboard.ShowClipboard();
993 void Controller::Impl::HideClipboard()
997 mClipboard.HideClipboard();
1001 bool Controller::Impl::CopyStringToClipboard( std::string& source )
1003 //Send string to clipboard
1004 return ( mClipboard && mClipboard.SetItem( source ) );
1007 void Controller::Impl::SendSelectionToClipboard( bool deleteAfterSending )
1009 std::string selectedText;
1010 RetrieveSelection( selectedText, deleteAfterSending );
1011 CopyStringToClipboard( selectedText );
1012 ChangeState( EventData::EDITING );
1015 void Controller::Impl::GetTextFromClipboard( unsigned int itemIndex, std::string& retrievedString )
1019 retrievedString = mClipboard.GetItem( itemIndex );
1023 void Controller::Impl::RepositionSelectionHandles()
1025 CharacterIndex selectionStart = mEventData->mLeftSelectionPosition;
1026 CharacterIndex selectionEnd = mEventData->mRightSelectionPosition;
1028 if( selectionStart == selectionEnd )
1030 // Nothing to select if handles are in the same place.
1034 mEventData->mDecorator->ClearHighlights();
1036 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
1037 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
1038 const GlyphInfo* const glyphsBuffer = mVisualModel->mGlyphs.Begin();
1039 const Vector2* const positionsBuffer = mVisualModel->mGlyphPositions.Begin();
1040 const Length* const charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
1041 const CharacterIndex* const glyphToCharacterBuffer = mVisualModel->mGlyphsToCharacters.Begin();
1042 const CharacterDirection* const modelCharacterDirectionsBuffer = ( 0u != mLogicalModel->mCharacterDirections.Count() ) ? mLogicalModel->mCharacterDirections.Begin() : NULL;
1044 // TODO: Better algorithm to create the highlight box.
1045 // TODO: Multi-line.
1047 // Get the height of the line.
1048 const Vector<LineRun>& lines = mVisualModel->mLines;
1049 const LineRun& firstLine = *lines.Begin();
1050 const float height = firstLine.ascender + -firstLine.descender;
1052 const bool isLastCharacter = selectionEnd >= mLogicalModel->mText.Count();
1053 const bool startDirection = ( ( NULL == modelCharacterDirectionsBuffer ) ? false : *( modelCharacterDirectionsBuffer + selectionStart ) );
1054 const bool endDirection = ( ( NULL == modelCharacterDirectionsBuffer ) ? false : *( modelCharacterDirectionsBuffer + ( selectionEnd - ( isLastCharacter ? 1u : 0u ) ) ) );
1056 // Swap the indices if the start is greater than the end.
1057 const bool indicesSwapped = selectionStart > selectionEnd;
1059 // Tell the decorator to flip the selection handles if needed.
1060 mEventData->mDecorator->SetSelectionHandleFlipState( indicesSwapped, startDirection, endDirection );
1062 if( indicesSwapped )
1064 std::swap( selectionStart, selectionEnd );
1067 // Get the indices to the first and last selected glyphs.
1068 const CharacterIndex selectionEndMinusOne = selectionEnd - 1u;
1069 const GlyphIndex glyphStart = *( charactersToGlyphBuffer + selectionStart );
1070 const Length numberOfGlyphs = *( glyphsPerCharacterBuffer + selectionEndMinusOne );
1071 const GlyphIndex glyphEnd = *( charactersToGlyphBuffer + selectionEndMinusOne ) + ( ( numberOfGlyphs > 0 ) ? numberOfGlyphs - 1u : 0u );
1073 // Check if the first glyph is a ligature that must be broken like Latin ff, fi, or Arabic ﻻ, etc which needs special code.
1074 const Length numberOfCharactersStart = *( charactersPerGlyphBuffer + glyphStart );
1075 bool splitStartGlyph = ( numberOfCharactersStart > 1u ) && HasLigatureMustBreak( mLogicalModel->GetScript( selectionStart ) );
1077 // Check if the last glyph is a ligature that must be broken like Latin ff, fi, or Arabic ﻻ, etc which needs special code.
1078 const Length numberOfCharactersEnd = *( charactersPerGlyphBuffer + glyphEnd );
1079 bool splitEndGlyph = ( glyphStart != glyphEnd ) && ( numberOfCharactersEnd > 1u ) && HasLigatureMustBreak( mLogicalModel->GetScript( selectionEndMinusOne ) );
1081 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
1083 // Traverse the glyphs.
1084 for( GlyphIndex index = glyphStart; index <= glyphEnd; ++index )
1086 const GlyphInfo& glyph = *( glyphsBuffer + index );
1087 const Vector2& position = *( positionsBuffer + index );
1089 if( splitStartGlyph )
1091 // 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.
1093 const float glyphAdvance = glyph.advance / static_cast<float>( numberOfCharactersStart );
1094 const CharacterIndex interGlyphIndex = selectionStart - *( glyphToCharacterBuffer + glyphStart );
1095 // Get the direction of the character.
1096 CharacterDirection isCurrentRightToLeft = false;
1097 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
1099 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + selectionStart );
1102 // The end point could be in the middle of the ligature.
1103 // Calculate the number of characters selected.
1104 const Length numberOfCharacters = ( glyphStart == glyphEnd ) ? ( selectionEnd - selectionStart ) : ( numberOfCharactersStart - interGlyphIndex );
1106 const float xPosition = position.x - glyph.xBearing + offset.x + glyphAdvance * static_cast<float>( isCurrentRightToLeft ? ( numberOfCharactersStart - interGlyphIndex - numberOfCharacters ) : interGlyphIndex );
1108 mEventData->mDecorator->AddHighlight( xPosition,
1110 xPosition + static_cast<float>( numberOfCharacters ) * glyphAdvance,
1111 offset.y + height );
1113 splitStartGlyph = false;
1117 if( splitEndGlyph && ( index == glyphEnd ) )
1119 // 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.
1121 const float glyphAdvance = glyph.advance / static_cast<float>( numberOfCharactersEnd );
1122 const CharacterIndex interGlyphIndex = selectionEnd - *( glyphToCharacterBuffer + glyphEnd );
1123 // Get the direction of the character.
1124 CharacterDirection isCurrentRightToLeft = false;
1125 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
1127 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + selectionEnd );
1130 const Length numberOfCharacters = numberOfCharactersEnd - interGlyphIndex;
1132 const float xPosition = position.x - glyph.xBearing + offset.x + ( isCurrentRightToLeft ? ( glyphAdvance * static_cast<float>( numberOfCharacters ) ) : 0.f );
1133 mEventData->mDecorator->AddHighlight( xPosition,
1135 xPosition + static_cast<float>( interGlyphIndex ) * glyphAdvance,
1136 offset.y + height );
1138 splitEndGlyph = false;
1142 const float xPosition = position.x - glyph.xBearing + offset.x;
1143 mEventData->mDecorator->AddHighlight( xPosition,
1145 xPosition + glyph.advance,
1146 offset.y + height );
1149 CursorInfo primaryCursorInfo;
1150 GetCursorPosition( mEventData->mLeftSelectionPosition,
1151 primaryCursorInfo );
1153 CursorInfo secondaryCursorInfo;
1154 GetCursorPosition( mEventData->mRightSelectionPosition,
1155 secondaryCursorInfo );
1157 const Vector2 primaryPosition = primaryCursorInfo.primaryPosition + offset;
1158 const Vector2 secondaryPosition = secondaryCursorInfo.primaryPosition + offset;
1160 mEventData->mDecorator->SetPosition( LEFT_SELECTION_HANDLE, primaryPosition.x, primaryPosition.y, primaryCursorInfo.lineHeight );
1162 mEventData->mDecorator->SetPosition( RIGHT_SELECTION_HANDLE, secondaryPosition.x, secondaryPosition.y, secondaryCursorInfo.lineHeight );
1164 // Cursor to be positioned at end of selection so if selection interrupted and edit mode restarted the cursor will be at end of selection
1165 mEventData->mPrimaryCursorPosition = ( indicesSwapped ) ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition;
1167 // Set the flag to update the decorator.
1168 mEventData->mDecoratorUpdated = true;
1171 void Controller::Impl::RepositionSelectionHandles( float visualX, float visualY )
1173 if( NULL == mEventData )
1175 // Nothing to do if there is no text input.
1179 if( IsShowingPlaceholderText() )
1181 // Nothing to do if there is the place-holder text.
1185 const Length numberOfGlyphs = mVisualModel->mGlyphs.Count();
1186 const Length numberOfLines = mVisualModel->mLines.Count();
1187 if( ( 0 == numberOfGlyphs ) ||
1188 ( 0 == numberOfLines ) )
1190 // Nothing to do if there is no text.
1194 // Find which word was selected
1195 CharacterIndex selectionStart( 0 );
1196 CharacterIndex selectionEnd( 0 );
1197 FindSelectionIndices( visualX, visualY, selectionStart, selectionEnd );
1198 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "%p selectionStart %d selectionEnd %d\n", this, selectionStart, selectionEnd );
1200 if( selectionStart == selectionEnd )
1202 ChangeState( EventData::EDITING );
1203 // Nothing to select. i.e. a white space, out of bounds
1207 mEventData->mLeftSelectionPosition = selectionStart;
1208 mEventData->mRightSelectionPosition = selectionEnd;
1211 void Controller::Impl::SetPopupButtons()
1214 * Sets the Popup buttons to be shown depending on State.
1216 * If SELECTING : CUT & COPY + ( PASTE & CLIPBOARD if content available to paste )
1218 * If EDITING_WITH_POPUP : SELECT & SELECT_ALL
1221 TextSelectionPopup::Buttons buttonsToShow = TextSelectionPopup::NONE;
1223 if( EventData::SELECTING == mEventData->mState )
1225 buttonsToShow = TextSelectionPopup::Buttons( TextSelectionPopup::CUT | TextSelectionPopup::COPY );
1227 if( !IsClipboardEmpty() )
1229 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
1230 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::CLIPBOARD ) );
1233 if( !mEventData->mAllTextSelected )
1235 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::SELECT_ALL ) );
1238 else if( EventData::EDITING_WITH_POPUP == mEventData->mState )
1240 if( mLogicalModel->mText.Count() && !IsShowingPlaceholderText() )
1242 buttonsToShow = TextSelectionPopup::Buttons( TextSelectionPopup::SELECT | TextSelectionPopup::SELECT_ALL );
1245 if( !IsClipboardEmpty() )
1247 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
1248 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::CLIPBOARD ) );
1251 else if( EventData::EDITING_WITH_PASTE_POPUP == mEventData->mState )
1253 if ( !IsClipboardEmpty() )
1255 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
1256 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::CLIPBOARD ) );
1260 mEventData->mDecorator->SetEnabledPopupButtons( buttonsToShow );
1263 void Controller::Impl::ChangeState( EventData::State newState )
1265 if( NULL == mEventData )
1267 // Nothing to do if there is no text input.
1271 DALI_LOG_INFO( gLogFilter, Debug::General, "ChangeState state:%d newstate:%d\n", mEventData->mState, newState );
1273 if( mEventData->mState != newState )
1275 mEventData->mState = newState;
1277 if( EventData::INACTIVE == mEventData->mState )
1279 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
1280 mEventData->mDecorator->StopCursorBlink();
1281 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1282 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1283 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1284 mEventData->mDecorator->SetPopupActive( false );
1285 mEventData->mDecoratorUpdated = true;
1288 else if( EventData::INTERRUPTED == mEventData->mState)
1290 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1291 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1292 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1293 mEventData->mDecorator->SetPopupActive( false );
1294 mEventData->mDecoratorUpdated = true;
1297 else if( EventData::SELECTING == mEventData->mState )
1299 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
1300 mEventData->mDecorator->StopCursorBlink();
1301 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1302 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true );
1303 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true );
1304 if( mEventData->mGrabHandlePopupEnabled )
1307 mEventData->mDecorator->SetPopupActive( true );
1309 mEventData->mDecoratorUpdated = true;
1311 else if( EventData::EDITING == mEventData->mState )
1313 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1314 if( mEventData->mCursorBlinkEnabled )
1316 mEventData->mDecorator->StartCursorBlink();
1318 // Grab handle is not shown until a tap is received whilst EDITING
1319 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1320 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1321 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1322 if( mEventData->mGrabHandlePopupEnabled )
1324 mEventData->mDecorator->SetPopupActive( false );
1326 mEventData->mDecoratorUpdated = true;
1329 else if( EventData::EDITING_WITH_POPUP == mEventData->mState )
1331 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "EDITING_WITH_POPUP \n", newState );
1333 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1334 if( mEventData->mCursorBlinkEnabled )
1336 mEventData->mDecorator->StartCursorBlink();
1338 if( mEventData->mSelectionEnabled )
1340 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1341 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1345 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
1347 if( mEventData->mGrabHandlePopupEnabled )
1350 mEventData->mDecorator->SetPopupActive( true );
1353 mEventData->mDecoratorUpdated = true;
1355 else if( EventData::EDITING_WITH_GRAB_HANDLE == mEventData->mState )
1357 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "EDITING_WITH_GRAB_HANDLE \n", newState );
1359 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1360 if( mEventData->mCursorBlinkEnabled )
1362 mEventData->mDecorator->StartCursorBlink();
1364 // Grab handle is not shown until a tap is received whilst EDITING
1365 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
1366 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1367 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1368 if( mEventData->mGrabHandlePopupEnabled )
1370 mEventData->mDecorator->SetPopupActive( false );
1372 mEventData->mDecoratorUpdated = true;
1375 else if( EventData::SELECTION_HANDLE_PANNING == mEventData->mState )
1377 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
1378 mEventData->mDecorator->StopCursorBlink();
1379 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1380 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true );
1381 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true );
1382 if( mEventData->mGrabHandlePopupEnabled )
1384 mEventData->mDecorator->SetPopupActive( false );
1386 mEventData->mDecoratorUpdated = true;
1388 else if( EventData::GRAB_HANDLE_PANNING == mEventData->mState )
1390 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "GRAB_HANDLE_PANNING \n", newState );
1392 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1393 if( mEventData->mCursorBlinkEnabled )
1395 mEventData->mDecorator->StartCursorBlink();
1397 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
1398 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1399 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1400 if( mEventData->mGrabHandlePopupEnabled )
1402 mEventData->mDecorator->SetPopupActive( false );
1404 mEventData->mDecoratorUpdated = true;
1406 else if( EventData::EDITING_WITH_PASTE_POPUP == mEventData->mState )
1408 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "EDITING_WITH_PASTE_POPUP \n", newState );
1410 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1411 if( mEventData->mCursorBlinkEnabled )
1413 mEventData->mDecorator->StartCursorBlink();
1416 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
1417 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1418 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1420 if( mEventData->mGrabHandlePopupEnabled )
1423 mEventData->mDecorator->SetPopupActive( true );
1426 mEventData->mDecoratorUpdated = true;
1431 LineIndex Controller::Impl::GetClosestLine( float y ) const
1433 float totalHeight = 0.f;
1434 LineIndex lineIndex = 0u;
1436 const Vector<LineRun>& lines = mVisualModel->mLines;
1437 for( LineIndex endLine = lines.Count();
1438 lineIndex < endLine;
1441 const LineRun& lineRun = lines[lineIndex];
1442 totalHeight += lineRun.ascender + -lineRun.descender;
1443 if( y < totalHeight )
1449 if( lineIndex == 0 )
1457 void Controller::Impl::FindSelectionIndices( float visualX, float visualY, CharacterIndex& startIndex, CharacterIndex& endIndex )
1459 CharacterIndex hitCharacter = GetClosestCursorIndex( visualX, visualY );
1460 DALI_ASSERT_DEBUG( hitCharacter <= mLogicalModel->mText.Count() && "GetClosestCursorIndex returned out of bounds index" );
1462 if( mLogicalModel->mText.Count() == 0 )
1464 return; // if model empty
1467 if( hitCharacter >= mLogicalModel->mText.Count() )
1469 // Closest hit character is the last character.
1470 if( hitCharacter == mLogicalModel->mText.Count() )
1472 hitCharacter--; //Hit character index set to last character in logical model
1476 // hitCharacter is out of bounds
1481 startIndex = hitCharacter;
1482 endIndex = hitCharacter;
1483 bool isHitCharacterWhitespace = TextAbstraction::IsWhiteSpace( mLogicalModel->mText[hitCharacter] );
1485 // Find the start and end of the text
1486 for( startIndex = hitCharacter; startIndex > 0; --startIndex )
1488 if( isHitCharacterWhitespace != TextAbstraction::IsWhiteSpace( mLogicalModel->mText[ startIndex-1 ] ) )
1493 const CharacterIndex pastTheEnd = mLogicalModel->mText.Count();
1494 for( endIndex = hitCharacter + 1u; endIndex < pastTheEnd; ++endIndex )
1496 if( isHitCharacterWhitespace != TextAbstraction::IsWhiteSpace( mLogicalModel->mText[ endIndex ] ) )
1503 CharacterIndex Controller::Impl::GetClosestCursorIndex( float visualX,
1506 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "GetClosestCursorIndex %p closest visualX %f visualY %f\n", this, visualX, visualY );
1508 if( NULL == mEventData )
1510 // Nothing to do if there is no text input.
1514 CharacterIndex logicalIndex = 0u;
1516 const Length numberOfGlyphs = mVisualModel->mGlyphs.Count();
1517 const Length numberOfLines = mVisualModel->mLines.Count();
1518 if( ( 0 == numberOfGlyphs ) ||
1519 ( 0 == numberOfLines ) )
1521 return logicalIndex;
1524 // Find which line is closest
1525 const LineIndex lineIndex = GetClosestLine( visualY );
1526 const LineRun& line = mVisualModel->mLines[lineIndex];
1528 // Get the positions of the glyphs.
1529 const Vector<Vector2>& positions = mVisualModel->mGlyphPositions;
1530 const Vector2* const positionsBuffer = positions.Begin();
1532 // Get the visual to logical conversion tables.
1533 const CharacterIndex* const visualToLogicalBuffer = ( 0u != mLogicalModel->mVisualToLogicalMap.Count() ) ? mLogicalModel->mVisualToLogicalMap.Begin() : NULL;
1534 const CharacterIndex* const visualToLogicalCursorBuffer = mLogicalModel->mVisualToLogicalCursorMap.Begin();
1536 // Get the character to glyph conversion table.
1537 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
1539 // Get the glyphs per character table.
1540 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
1542 // If the vector is void, there is no right to left characters.
1543 const bool hasRightToLeftCharacters = NULL != visualToLogicalBuffer;
1545 const CharacterIndex startCharacter = line.characterRun.characterIndex;
1546 const CharacterIndex endCharacter = line.characterRun.characterIndex + line.characterRun.numberOfCharacters;
1547 DALI_ASSERT_DEBUG( endCharacter <= mLogicalModel->mText.Count() && "Invalid line info" );
1549 // Whether there is a hit on a glyph.
1550 bool matched = false;
1552 // Traverses glyphs in visual order. To do that use the visual to logical conversion table.
1553 CharacterIndex visualIndex = startCharacter;
1554 Length numberOfCharacters = 0u;
1555 for( ; !matched && ( visualIndex < endCharacter ); ++visualIndex )
1557 // The character in logical order.
1558 const CharacterIndex characterLogicalOrderIndex = hasRightToLeftCharacters ? *( visualToLogicalBuffer + visualIndex ) : visualIndex;
1560 // Get the script of the character.
1561 const Script script = mLogicalModel->GetScript( characterLogicalOrderIndex );
1563 // The number of glyphs for that character
1564 const Length numberOfGlyphs = *( glyphsPerCharacterBuffer + characterLogicalOrderIndex );
1565 ++numberOfCharacters;
1568 if( 0u != numberOfGlyphs )
1570 // Get the first character/glyph of the group of glyphs.
1571 const CharacterIndex firstVisualCharacterIndex = 1u + visualIndex - numberOfCharacters;
1572 const CharacterIndex firstLogicalCharacterIndex = hasRightToLeftCharacters ? *( visualToLogicalBuffer + firstVisualCharacterIndex ) : firstVisualCharacterIndex;
1573 const GlyphIndex firstLogicalGlyphIndex = *( charactersToGlyphBuffer + firstLogicalCharacterIndex );
1575 // Get the metrics for the group of glyphs.
1576 GlyphMetrics glyphMetrics;
1577 GetGlyphsMetrics( firstLogicalGlyphIndex,
1583 // Get the position of the first glyph.
1584 const Vector2& position = *( positionsBuffer + firstLogicalGlyphIndex );
1586 // Whether the glyph can be split, like Latin ligatures fi, ff or Arabic ﻻ.
1587 const bool isInterglyphIndex = ( numberOfCharacters > numberOfGlyphs ) && HasLigatureMustBreak( script );
1588 const Length numberOfBlocks = isInterglyphIndex ? numberOfCharacters : 1u;
1589 const float glyphAdvance = glyphMetrics.advance / static_cast<float>( numberOfBlocks );
1591 GlyphIndex index = 0u;
1592 for( ; !matched && ( index < numberOfBlocks ); ++index )
1594 // Find the mid-point of the area containing the glyph
1595 const float glyphCenter = -glyphMetrics.xBearing + position.x + ( static_cast<float>( index ) + 0.5f ) * glyphAdvance;
1597 if( visualX < glyphCenter )
1606 visualIndex = firstVisualCharacterIndex + index;
1610 numberOfCharacters = 0u;
1616 // Return the logical position of the cursor in characters.
1620 visualIndex = endCharacter;
1623 logicalIndex = hasRightToLeftCharacters ? *( visualToLogicalCursorBuffer + visualIndex ) : visualIndex;
1624 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "%p closest visualIndex %d logicalIndex %d\n", this, visualIndex, logicalIndex );
1626 DALI_ASSERT_DEBUG( ( logicalIndex <= mLogicalModel->mText.Count() && logicalIndex >= 0 ) && "GetClosestCursorIndex - Out of bounds index" );
1628 return logicalIndex;
1631 void Controller::Impl::GetCursorPosition( CharacterIndex logical,
1632 CursorInfo& cursorInfo )
1634 // TODO: Check for multiline with \n, etc...
1636 const Length numberOfCharacters = mLogicalModel->mText.Count();
1637 if( !IsShowingRealText() )
1639 // Do not want to use the place-holder text to set the cursor position.
1641 // Use the line's height of the font's family set to set the cursor's size.
1642 // If there is no font's family set, use the default font.
1643 // Use the current alignment to place the cursor at the beginning, center or end of the box.
1645 cursorInfo.lineHeight = GetDefaultFontLineHeight();
1646 cursorInfo.primaryCursorHeight = cursorInfo.lineHeight;
1648 switch( mLayoutEngine.GetHorizontalAlignment() )
1650 case LayoutEngine::HORIZONTAL_ALIGN_BEGIN:
1652 cursorInfo.primaryPosition.x = 0.f;
1655 case LayoutEngine::HORIZONTAL_ALIGN_CENTER:
1657 cursorInfo.primaryPosition.x = floorf( 0.5f * mVisualModel->mControlSize.width );
1660 case LayoutEngine::HORIZONTAL_ALIGN_END:
1662 cursorInfo.primaryPosition.x = mVisualModel->mControlSize.width - mEventData->mDecorator->GetCursorWidth();
1667 switch( mLayoutEngine.GetVerticalAlignment() )
1669 case LayoutEngine::VERTICAL_ALIGN_TOP:
1671 cursorInfo.primaryPosition.y = 0.f;
1674 case LayoutEngine::VERTICAL_ALIGN_CENTER:
1676 cursorInfo.primaryPosition.y = floorf( 0.5f * ( mVisualModel->mControlSize.height - cursorInfo.lineHeight ) );
1679 case LayoutEngine::VERTICAL_ALIGN_BOTTOM:
1681 cursorInfo.primaryPosition.y = mVisualModel->mControlSize.height - cursorInfo.lineHeight;
1686 // Nothing else to do.
1690 // Check if the logical position is the first or the last one of the text.
1691 const bool isFirstPosition = 0u == logical;
1692 const bool isLastPosition = numberOfCharacters == logical;
1694 // 'logical' is the logical 'cursor' index.
1695 // Get the next and current logical 'character' index.
1696 const CharacterIndex nextCharacterIndex = logical;
1697 const CharacterIndex characterIndex = isFirstPosition ? logical : logical - 1u;
1699 // Get the direction of the character and the next one.
1700 const CharacterDirection* const modelCharacterDirectionsBuffer = ( 0u != mLogicalModel->mCharacterDirections.Count() ) ? mLogicalModel->mCharacterDirections.Begin() : NULL;
1702 CharacterDirection isCurrentRightToLeft = false;
1703 CharacterDirection isNextRightToLeft = false;
1704 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
1706 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + characterIndex );
1707 isNextRightToLeft = *( modelCharacterDirectionsBuffer + nextCharacterIndex );
1710 // Get the line where the character is laid-out.
1711 const LineRun* const modelLines = mVisualModel->mLines.Begin();
1713 const LineIndex lineIndex = mVisualModel->GetLineOfCharacter( characterIndex );
1714 const LineRun& line = *( modelLines + lineIndex );
1716 // Get the paragraph's direction.
1717 const CharacterDirection isRightToLeftParagraph = line.direction;
1719 // Check whether there is an alternative position:
1721 cursorInfo.isSecondaryCursor = ( !isLastPosition && ( isCurrentRightToLeft != isNextRightToLeft ) ) ||
1722 ( isLastPosition && ( isRightToLeftParagraph != isCurrentRightToLeft ) );
1724 // Set the line height.
1725 cursorInfo.lineHeight = line.ascender + -line.descender;
1727 // Calculate the primary cursor.
1729 CharacterIndex index = characterIndex;
1730 if( cursorInfo.isSecondaryCursor )
1732 // If there is a secondary position, the primary cursor may be in a different place than the logical index.
1734 if( isLastPosition )
1736 // The position of the cursor after the last character needs special
1737 // care depending on its direction and the direction of the paragraph.
1739 // Need to find the first character after the last character with the paragraph's direction.
1740 // i.e l0 l1 l2 r0 r1 should find r0.
1742 // TODO: check for more than one line!
1743 index = isRightToLeftParagraph ? line.characterRun.characterIndex : line.characterRun.characterIndex + line.characterRun.numberOfCharacters - 1u;
1744 index = mLogicalModel->GetLogicalCharacterIndex( index );
1748 index = ( isRightToLeftParagraph == isCurrentRightToLeft ) ? characterIndex : nextCharacterIndex;
1752 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
1753 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
1754 const Length* const charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
1755 const CharacterIndex* const glyphsToCharactersBuffer = mVisualModel->mGlyphsToCharacters.Begin();
1756 const Vector2* const glyphPositionsBuffer = mVisualModel->mGlyphPositions.Begin();
1758 // Convert the cursor position into the glyph position.
1759 const GlyphIndex primaryGlyphIndex = *( charactersToGlyphBuffer + index );
1760 const Length primaryNumberOfGlyphs = *( glyphsPerCharacterBuffer + index );
1761 const Length primaryNumberOfCharacters = *( charactersPerGlyphBuffer + primaryGlyphIndex );
1763 // Get the metrics for the group of glyphs.
1764 GlyphMetrics glyphMetrics;
1765 GetGlyphsMetrics( primaryGlyphIndex,
1766 primaryNumberOfGlyphs,
1771 // Whether to add the glyph's advance to the cursor position.
1772 // 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,
1773 // if the logical cursor is one, the position is the position of the first glyph and the advance is added.
1774 // A 'truth table' was build and an online Karnaugh map tool was used to simplify the logic.
1795 // Where F -> isFirstPosition
1796 // L -> isLastPosition
1797 // C -> isCurrentRightToLeft
1798 // P -> isRightToLeftParagraph
1799 // A -> Whether to add the glyph's advance.
1801 const bool addGlyphAdvance = ( ( isLastPosition && !isRightToLeftParagraph ) ||
1802 ( isFirstPosition && isRightToLeftParagraph ) ||
1803 ( !isFirstPosition && !isLastPosition && !isCurrentRightToLeft ) );
1805 float glyphAdvance = addGlyphAdvance ? glyphMetrics.advance : 0.f;
1807 if( !isLastPosition &&
1808 ( primaryNumberOfCharacters > 1u ) )
1810 const CharacterIndex firstIndex = *( glyphsToCharactersBuffer + primaryGlyphIndex );
1812 bool isCurrentRightToLeft = false;
1813 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
1815 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + index );
1818 Length numberOfGlyphAdvance = ( isFirstPosition ? 0u : 1u ) + characterIndex - firstIndex;
1819 if( isCurrentRightToLeft )
1821 numberOfGlyphAdvance = primaryNumberOfCharacters - numberOfGlyphAdvance;
1824 glyphAdvance = static_cast<float>( numberOfGlyphAdvance ) * glyphMetrics.advance / static_cast<float>( primaryNumberOfCharacters );
1827 // Get the glyph position and x bearing.
1828 const Vector2& primaryPosition = *( glyphPositionsBuffer + primaryGlyphIndex );
1830 // Set the primary cursor's height.
1831 cursorInfo.primaryCursorHeight = cursorInfo.isSecondaryCursor ? 0.5f * glyphMetrics.fontHeight : glyphMetrics.fontHeight;
1833 // Set the primary cursor's position.
1834 cursorInfo.primaryPosition.x = -glyphMetrics.xBearing + primaryPosition.x + glyphAdvance;
1835 cursorInfo.primaryPosition.y = line.ascender - glyphMetrics.ascender;
1837 // Calculate the secondary cursor.
1839 if( cursorInfo.isSecondaryCursor )
1841 // Set the secondary cursor's height.
1842 cursorInfo.secondaryCursorHeight = 0.5f * glyphMetrics.fontHeight;
1844 CharacterIndex index = characterIndex;
1845 if( !isLastPosition )
1847 index = ( isRightToLeftParagraph == isCurrentRightToLeft ) ? nextCharacterIndex : characterIndex;
1850 const GlyphIndex secondaryGlyphIndex = *( charactersToGlyphBuffer + index );
1851 const Length secondaryNumberOfGlyphs = *( glyphsPerCharacterBuffer + index );
1853 const Vector2& secondaryPosition = *( glyphPositionsBuffer + secondaryGlyphIndex );
1855 GetGlyphsMetrics( secondaryGlyphIndex,
1856 secondaryNumberOfGlyphs,
1861 // Set the secondary cursor's position.
1862 cursorInfo.secondaryPosition.x = -glyphMetrics.xBearing + secondaryPosition.x + ( isCurrentRightToLeft ? 0.f : glyphMetrics.advance );
1863 cursorInfo.secondaryPosition.y = cursorInfo.lineHeight - cursorInfo.secondaryCursorHeight - line.descender - ( glyphMetrics.fontHeight - glyphMetrics.ascender );
1867 CharacterIndex Controller::Impl::CalculateNewCursorIndex( CharacterIndex index ) const
1869 if( NULL == mEventData )
1871 // Nothing to do if there is no text input.
1875 CharacterIndex cursorIndex = mEventData->mPrimaryCursorPosition;
1877 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
1878 const Length* const charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
1880 GlyphIndex glyphIndex = *( charactersToGlyphBuffer + index );
1881 Length numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
1883 if( numberOfCharacters > 1u )
1885 const Script script = mLogicalModel->GetScript( index );
1886 if( HasLigatureMustBreak( script ) )
1888 // Prevents to jump the whole Latin ligatures like fi, ff, or Arabic ﻻ, ...
1889 numberOfCharacters = 1u;
1894 while( 0u == numberOfCharacters )
1897 numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
1901 if( index < mEventData->mPrimaryCursorPosition )
1903 cursorIndex -= numberOfCharacters;
1907 cursorIndex += numberOfCharacters;
1913 void Controller::Impl::UpdateCursorPosition( const CursorInfo& cursorInfo )
1915 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "-->Controller::UpdateCursorPosition %p\n", this );
1916 if( NULL == mEventData )
1918 // Nothing to do if there is no text input.
1919 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition no event data\n" );
1923 const Vector2 offset = mEventData->mScrollPosition + ( IsShowingRealText() ? mAlignmentOffset : Vector2::ZERO );
1924 const Vector2 cursorPosition = cursorInfo.primaryPosition + offset;
1926 // Sets the cursor position.
1927 mEventData->mDecorator->SetPosition( PRIMARY_CURSOR,
1930 cursorInfo.primaryCursorHeight,
1931 cursorInfo.lineHeight );
1932 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Primary cursor position: %f,%f\n", cursorPosition.x, cursorPosition.y );
1934 // Sets the grab handle position.
1935 mEventData->mDecorator->SetPosition( GRAB_HANDLE,
1938 cursorInfo.lineHeight );
1940 if( cursorInfo.isSecondaryCursor )
1942 mEventData->mDecorator->SetPosition( SECONDARY_CURSOR,
1943 cursorInfo.secondaryPosition.x + offset.x,
1944 cursorInfo.secondaryPosition.y + offset.y,
1945 cursorInfo.secondaryCursorHeight,
1946 cursorInfo.lineHeight );
1947 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Secondary cursor position: %f,%f\n", cursorInfo.secondaryPosition.x + offset.x, cursorInfo.secondaryPosition.y + offset.y );
1950 // Set which cursors are active according the state.
1951 if( EventData::IsEditingState( mEventData->mState ) || ( EventData::GRAB_HANDLE_PANNING == mEventData->mState ) )
1953 if( cursorInfo.isSecondaryCursor )
1955 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_BOTH );
1959 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1964 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
1967 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition\n" );
1970 void Controller::Impl::UpdateSelectionHandle( HandleType handleType,
1971 const CursorInfo& cursorInfo )
1973 if( ( LEFT_SELECTION_HANDLE != handleType ) &&
1974 ( RIGHT_SELECTION_HANDLE != handleType ) )
1979 const Vector2 cursorPosition = cursorInfo.primaryPosition + mEventData->mScrollPosition + mAlignmentOffset;
1981 // Sets the handle's position.
1982 mEventData->mDecorator->SetPosition( handleType,
1985 cursorInfo.lineHeight );
1987 // If selection handle at start of the text and other at end of the text then all text is selected.
1988 const CharacterIndex startOfSelection = std::min( mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition );
1989 const CharacterIndex endOfSelection = std::max ( mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition );
1990 mEventData->mAllTextSelected = ( startOfSelection == 0 ) && ( endOfSelection == mLogicalModel->mText.Count() );
1993 void Controller::Impl::ClampHorizontalScroll( const Vector2& actualSize )
1995 // Clamp between -space & 0 (and the text alignment).
1997 if( actualSize.width > mVisualModel->mControlSize.width )
1999 const float space = ( actualSize.width - mVisualModel->mControlSize.width ) + mAlignmentOffset.x;
2000 mEventData->mScrollPosition.x = ( mEventData->mScrollPosition.x < -space ) ? -space : mEventData->mScrollPosition.x;
2001 mEventData->mScrollPosition.x = ( mEventData->mScrollPosition.x > -mAlignmentOffset.x ) ? -mAlignmentOffset.x : mEventData->mScrollPosition.x;
2003 mEventData->mDecoratorUpdated = true;
2007 mEventData->mScrollPosition.x = 0.f;
2011 void Controller::Impl::ClampVerticalScroll( const Vector2& actualSize )
2013 // Clamp between -space & 0 (and the text alignment).
2014 if( actualSize.height > mVisualModel->mControlSize.height )
2016 const float space = ( actualSize.height - mVisualModel->mControlSize.height ) + mAlignmentOffset.y;
2017 mEventData->mScrollPosition.y = ( mEventData->mScrollPosition.y < -space ) ? -space : mEventData->mScrollPosition.y;
2018 mEventData->mScrollPosition.y = ( mEventData->mScrollPosition.y > -mAlignmentOffset.y ) ? -mAlignmentOffset.y : mEventData->mScrollPosition.y;
2020 mEventData->mDecoratorUpdated = true;
2024 mEventData->mScrollPosition.y = 0.f;
2028 void Controller::Impl::ScrollToMakePositionVisible( const Vector2& position )
2030 // position is in actor's coords.
2031 const float positionEnd = position.x + ( mEventData->mDecorator ? mEventData->mDecorator->GetCursorWidth() : 0.f );
2033 // Transform the position to decorator coords.
2034 const float offset = mEventData->mScrollPosition.x + mAlignmentOffset.x;
2035 const float decoratorPositionBegin = position.x + offset;
2036 const float decoratorPositionEnd = positionEnd + offset;
2038 if( decoratorPositionBegin < 0.f )
2040 mEventData->mScrollPosition.x = -position.x - mAlignmentOffset.x;
2042 else if( decoratorPositionEnd > mVisualModel->mControlSize.width )
2044 mEventData->mScrollPosition.x = mVisualModel->mControlSize.width - positionEnd - mAlignmentOffset.x;
2048 void Controller::Impl::ScrollTextToMatchCursor( const CursorInfo& cursorInfo )
2050 // Get the current cursor position in decorator coords.
2051 const Vector2& currentCursorPosition = mEventData->mDecorator->GetPosition( PRIMARY_CURSOR );
2053 // Calculate the offset to match the cursor position before the character was deleted.
2054 mEventData->mScrollPosition.x = currentCursorPosition.x - cursorInfo.primaryPosition.x - mAlignmentOffset.x;
2056 ClampHorizontalScroll( mVisualModel->GetActualSize() );
2059 void Controller::Impl::RequestRelayout()
2061 mControlInterface.RequestTextRelayout();
2066 } // namespace Toolkit