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 // Validate the fonts set through the mark-up string.
372 Vector<FontDescriptionRun>& fontDescriptionRuns = mLogicalModel->mFontDescriptionRuns;
374 // Get the default font id.
375 const FontId defaultFontId = ( NULL == mFontDefaults ) ? 0u : mFontDefaults->GetFontId( mFontClient );
377 // Validates the fonts. If there is a character with no assigned font it sets a default one.
378 // After this call, fonts are validated.
379 multilanguageSupport.ValidateFonts( utf32Characters,
387 Vector<Character> mirroredUtf32Characters;
388 bool textMirrored = false;
389 Length numberOfParagraphs = 0u;
390 if( BIDI_INFO & operations )
392 // Count the number of LINE_NO_BREAK to reserve some space for the vector of paragraph's
393 // bidirectional info.
395 const TextAbstraction::LineBreakInfo* lineBreakInfoBuffer = lineBreakInfo.Begin();
396 for( Length index = 0u; index < numberOfCharacters; ++index )
398 if( TextAbstraction::LINE_NO_BREAK == *( lineBreakInfoBuffer + index ) )
400 ++numberOfParagraphs;
404 Vector<BidirectionalParagraphInfoRun>& bidirectionalInfo = mLogicalModel->mBidirectionalParagraphInfo;
405 bidirectionalInfo.Reserve( numberOfParagraphs );
407 // Calculates the bidirectional info for the whole paragraph if it contains right to left scripts.
408 SetBidirectionalInfo( utf32Characters,
413 if( 0u != bidirectionalInfo.Count() )
415 // Only set the character directions if there is right to left characters.
416 Vector<CharacterDirection>& directions = mLogicalModel->mCharacterDirections;
417 directions.Resize( numberOfCharacters );
419 GetCharactersDirection( bidirectionalInfo,
422 // This paragraph has right to left text. Some characters may need to be mirrored.
423 // TODO: consider if the mirrored string can be stored as well.
425 textMirrored = GetMirroredText( utf32Characters,
428 mirroredUtf32Characters );
432 // There is no right to left characters. Clear the directions vector.
433 mLogicalModel->mCharacterDirections.Clear();
437 Vector<GlyphInfo>& glyphs = mVisualModel->mGlyphs;
438 Vector<CharacterIndex>& glyphsToCharactersMap = mVisualModel->mGlyphsToCharacters;
439 Vector<Length>& charactersPerGlyph = mVisualModel->mCharactersPerGlyph;
440 Vector<GlyphIndex> newParagraphGlyphs;
441 newParagraphGlyphs.Reserve( numberOfParagraphs );
443 if( SHAPE_TEXT & operations )
445 const Vector<Character>& textToShape = textMirrored ? mirroredUtf32Characters : utf32Characters;
447 ShapeText( textToShape,
452 glyphsToCharactersMap,
454 newParagraphGlyphs );
456 // Create the 'number of glyphs' per character and the glyph to character conversion tables.
457 mVisualModel->CreateGlyphsPerCharacterTable( numberOfCharacters );
458 mVisualModel->CreateCharacterToGlyphTable( numberOfCharacters );
461 const Length numberOfGlyphs = glyphs.Count();
463 if( GET_GLYPH_METRICS & operations )
465 GlyphInfo* glyphsBuffer = glyphs.Begin();
466 mMetrics->GetGlyphMetrics( glyphsBuffer, numberOfGlyphs );
468 // Update the width and advance of all new paragraph characters.
469 for( Vector<GlyphIndex>::ConstIterator it = newParagraphGlyphs.Begin(), endIt = newParagraphGlyphs.End(); it != endIt; ++it )
471 const GlyphIndex index = *it;
472 GlyphInfo& glyph = *( glyphsBuffer + index );
474 glyph.xBearing = 0.f;
480 if( ( NULL != mEventData ) &&
481 mEventData->mPreEditFlag &&
482 ( 0u != mVisualModel->mCharactersToGlyph.Count() ) )
484 // Add the underline for the pre-edit text.
485 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
486 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
488 const GlyphIndex glyphStart = *( charactersToGlyphBuffer + mEventData->mPreEditStartPosition );
489 const CharacterIndex lastPreEditCharacter = mEventData->mPreEditStartPosition + ( ( mEventData->mPreEditLength > 0u ) ? mEventData->mPreEditLength - 1u : 0u );
490 const Length numberOfGlyphsLastCharacter = *( glyphsPerCharacterBuffer + lastPreEditCharacter );
491 const GlyphIndex glyphEnd = *( charactersToGlyphBuffer + lastPreEditCharacter ) + ( numberOfGlyphsLastCharacter > 1u ? numberOfGlyphsLastCharacter - 1u : 0u );
493 GlyphRun underlineRun;
494 underlineRun.glyphIndex = glyphStart;
495 underlineRun.numberOfGlyphs = 1u + glyphEnd - glyphStart;
497 // TODO: At the moment the underline runs are only for pre-edit.
498 mVisualModel->mUnderlineRuns.PushBack( underlineRun );
502 bool Controller::Impl::UpdateModelStyle( OperationsMask operationsRequired )
504 bool updated = false;
506 if( COLOR & operationsRequired )
508 // Set the color runs in glyphs.
509 SetColorSegmentationInfo( mLogicalModel->mColorRuns,
510 mVisualModel->mCharactersToGlyph,
511 mVisualModel->mGlyphsPerCharacter,
512 mVisualModel->mColorRuns );
520 void Controller::Impl::RetrieveDefaultInputStyle( InputStyle& inputStyle )
522 // Sets the default text's color.
523 inputStyle.textColor = mTextColor;
525 // Sets the default font's family name, weight, width, slant and size.
528 inputStyle.familyName = mFontDefaults->mFontDescription.family;
529 inputStyle.weight = mFontDefaults->mFontDescription.weight;
530 inputStyle.width = mFontDefaults->mFontDescription.width;
531 inputStyle.slant = mFontDefaults->mFontDescription.slant;
532 inputStyle.size = mFontDefaults->mDefaultPointSize;
534 inputStyle.familyDefined = mFontDefaults->familyDefined;
535 inputStyle.weightDefined = mFontDefaults->weightDefined;
536 inputStyle.widthDefined = mFontDefaults->widthDefined;
537 inputStyle.slantDefined = mFontDefaults->slantDefined;
538 inputStyle.sizeDefined = mFontDefaults->sizeDefined;
542 inputStyle.familyName.clear();
543 inputStyle.weight = TextAbstraction::FontWeight::NORMAL;
544 inputStyle.width = TextAbstraction::FontWidth::NORMAL;
545 inputStyle.slant = TextAbstraction::FontSlant::NORMAL;
546 inputStyle.size = 0.f;
548 inputStyle.familyDefined = false;
549 inputStyle.weightDefined = false;
550 inputStyle.widthDefined = false;
551 inputStyle.slantDefined = false;
552 inputStyle.sizeDefined = false;
556 float Controller::Impl::GetDefaultFontLineHeight()
558 FontId defaultFontId = 0u;
559 if( NULL == mFontDefaults )
561 TextAbstraction::FontDescription fontDescription;
562 defaultFontId = mFontClient.GetFontId( fontDescription );
566 defaultFontId = mFontDefaults->GetFontId( mFontClient );
569 Text::FontMetrics fontMetrics;
570 mMetrics->GetFontMetrics( defaultFontId, fontMetrics );
572 return( fontMetrics.ascender - fontMetrics.descender );
575 void Controller::Impl::OnCursorKeyEvent( const Event& event )
577 if( NULL == mEventData )
579 // Nothing to do if there is no text input.
583 int keyCode = event.p1.mInt;
585 if( Dali::DALI_KEY_CURSOR_LEFT == keyCode )
587 if( mEventData->mPrimaryCursorPosition > 0u )
589 mEventData->mPrimaryCursorPosition = CalculateNewCursorIndex( mEventData->mPrimaryCursorPosition - 1u );
592 else if( Dali::DALI_KEY_CURSOR_RIGHT == keyCode )
594 if( mLogicalModel->mText.Count() > mEventData->mPrimaryCursorPosition )
596 mEventData->mPrimaryCursorPosition = CalculateNewCursorIndex( mEventData->mPrimaryCursorPosition );
599 else if( Dali::DALI_KEY_CURSOR_UP == keyCode )
603 else if( Dali::DALI_KEY_CURSOR_DOWN == keyCode )
608 mEventData->mUpdateCursorPosition = true;
609 mEventData->mUpdateInputStyle = true;
610 mEventData->mScrollAfterUpdatePosition = true;
613 void Controller::Impl::OnTapEvent( const Event& event )
615 if( NULL != mEventData )
617 const unsigned int tapCount = event.p1.mUint;
621 if( IsShowingRealText() )
623 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
624 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
626 mEventData->mPrimaryCursorPosition = GetClosestCursorIndex( xPosition,
629 // When the cursor position is changing, delay cursor blinking
630 mEventData->mDecorator->DelayCursorBlink();
634 mEventData->mPrimaryCursorPosition = 0u;
637 mEventData->mUpdateCursorPosition = true;
638 mEventData->mScrollAfterUpdatePosition = true;
639 mEventData->mUpdateInputStyle = true;
641 // Notify the cursor position to the imf manager.
642 if( mEventData->mImfManager )
644 mEventData->mImfManager.SetCursorPosition( mEventData->mPrimaryCursorPosition );
645 mEventData->mImfManager.NotifyCursorPosition();
651 void Controller::Impl::OnPanEvent( const Event& event )
653 if( NULL == mEventData )
655 // Nothing to do if there is no text input.
659 int state = event.p1.mInt;
661 if( Gesture::Started == state ||
662 Gesture::Continuing == state )
664 const Vector2& actualSize = mVisualModel->GetActualSize();
665 const Vector2 currentScroll = mEventData->mScrollPosition;
667 if( mEventData->mHorizontalScrollingEnabled )
669 const float displacementX = event.p2.mFloat;
670 mEventData->mScrollPosition.x += displacementX;
672 ClampHorizontalScroll( actualSize );
675 if( mEventData->mVerticalScrollingEnabled )
677 const float displacementY = event.p3.mFloat;
678 mEventData->mScrollPosition.y += displacementY;
680 ClampVerticalScroll( actualSize );
683 if( mEventData->mDecorator )
685 mEventData->mDecorator->UpdatePositions( mEventData->mScrollPosition - currentScroll );
690 void Controller::Impl::OnLongPressEvent( const Event& event )
692 DALI_LOG_INFO( gLogFilter, Debug::General, "Controller::OnLongPressEvent\n" );
694 if( EventData::EDITING == mEventData->mState )
696 ChangeState ( EventData::EDITING_WITH_POPUP );
697 mEventData->mDecoratorUpdated = true;
701 void Controller::Impl::OnHandleEvent( const Event& event )
703 if( NULL == mEventData )
705 // Nothing to do if there is no text input.
709 const unsigned int state = event.p1.mUint;
710 const bool handleStopScrolling = ( HANDLE_STOP_SCROLLING == state );
712 if( HANDLE_PRESSED == state )
714 // The event.p2 and event.p3 are in decorator coords. Need to transforms to text coords.
715 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
716 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
718 const CharacterIndex handleNewPosition = GetClosestCursorIndex( xPosition, yPosition );
720 if( Event::GRAB_HANDLE_EVENT == event.type )
722 ChangeState ( EventData::GRAB_HANDLE_PANNING );
724 if( handleNewPosition != mEventData->mPrimaryCursorPosition )
726 mEventData->mPrimaryCursorPosition = handleNewPosition;
727 mEventData->mUpdateCursorPosition = true;
730 else if( Event::LEFT_SELECTION_HANDLE_EVENT == event.type )
732 ChangeState ( EventData::SELECTION_HANDLE_PANNING );
734 if( ( handleNewPosition != mEventData->mLeftSelectionPosition ) &&
735 ( handleNewPosition != mEventData->mRightSelectionPosition ) )
737 mEventData->mLeftSelectionPosition = handleNewPosition;
739 mEventData->mUpdateLeftSelectionPosition = true;
742 else if( Event::RIGHT_SELECTION_HANDLE_EVENT == event.type )
744 ChangeState ( EventData::SELECTION_HANDLE_PANNING );
746 if( ( handleNewPosition != mEventData->mRightSelectionPosition ) &&
747 ( handleNewPosition != mEventData->mLeftSelectionPosition ) )
749 mEventData->mRightSelectionPosition = handleNewPosition;
751 mEventData->mUpdateRightSelectionPosition = true;
754 } // end ( HANDLE_PRESSED == state )
755 else if( ( HANDLE_RELEASED == state ) ||
756 handleStopScrolling )
758 CharacterIndex handlePosition = 0u;
759 if( handleStopScrolling )
761 // The event.p2 and event.p3 are in decorator coords. Need to transforms to text coords.
762 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
763 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
765 handlePosition = GetClosestCursorIndex( xPosition, yPosition );
768 if( Event::GRAB_HANDLE_EVENT == event.type )
770 mEventData->mUpdateCursorPosition = true;
771 mEventData->mUpdateInputStyle = true;
773 if( !IsClipboardEmpty() )
775 ChangeState( EventData::EDITING_WITH_PASTE_POPUP ); // Moving grabhandle will show Paste Popup
778 if( handleStopScrolling )
780 mEventData->mScrollAfterUpdatePosition = mEventData->mPrimaryCursorPosition != handlePosition;
781 mEventData->mPrimaryCursorPosition = handlePosition;
784 else if( Event::LEFT_SELECTION_HANDLE_EVENT == event.type )
786 ChangeState( EventData::SELECTING );
788 if( handleStopScrolling )
790 mEventData->mUpdateLeftSelectionPosition = ( mEventData->mRightSelectionPosition != handlePosition );
791 mEventData->mScrollAfterUpdatePosition = mEventData->mUpdateLeftSelectionPosition;
793 if( mEventData->mUpdateLeftSelectionPosition )
795 mEventData->mLeftSelectionPosition = handlePosition;
799 else if( Event::RIGHT_SELECTION_HANDLE_EVENT == event.type )
801 ChangeState( EventData::SELECTING );
803 if( handleStopScrolling )
805 mEventData->mUpdateRightSelectionPosition = ( mEventData->mLeftSelectionPosition != handlePosition );
806 mEventData->mScrollAfterUpdatePosition = mEventData->mUpdateRightSelectionPosition;
807 if( mEventData->mUpdateRightSelectionPosition )
809 mEventData->mRightSelectionPosition = handlePosition;
814 mEventData->mDecoratorUpdated = true;
815 } // end ( ( HANDLE_RELEASED == state ) || ( HANDLE_STOP_SCROLLING == state ) )
816 else if( HANDLE_SCROLLING == state )
818 const float xSpeed = event.p2.mFloat;
819 const Vector2& actualSize = mVisualModel->GetActualSize();
820 const Vector2 currentScrollPosition = mEventData->mScrollPosition;
822 mEventData->mScrollPosition.x += xSpeed;
824 ClampHorizontalScroll( actualSize );
826 bool endOfScroll = false;
827 if( Vector2::ZERO == ( currentScrollPosition - mEventData->mScrollPosition ) )
829 // Notify the decorator there is no more text to scroll.
830 // The decorator won't send more scroll events.
831 mEventData->mDecorator->NotifyEndOfScroll();
832 // Still need to set the position of the handle.
836 // Set the position of the handle.
837 const bool scrollRightDirection = xSpeed > 0.f;
838 const bool leftSelectionHandleEvent = Event::LEFT_SELECTION_HANDLE_EVENT == event.type;
839 const bool rightSelectionHandleEvent = Event::RIGHT_SELECTION_HANDLE_EVENT == event.type;
841 if( Event::GRAB_HANDLE_EVENT == event.type )
843 ChangeState( EventData::GRAB_HANDLE_PANNING );
845 Vector2 position = mEventData->mDecorator->GetPosition( GRAB_HANDLE );
847 // Position the grag handle close to either the left or right edge.
848 position.x = scrollRightDirection ? 0.f : mVisualModel->mControlSize.width;
850 // Get the new handle position.
851 // The grab handle's position is in decorator coords. Need to transforms to text coords.
852 const CharacterIndex handlePosition = GetClosestCursorIndex( position.x - mEventData->mScrollPosition.x - mAlignmentOffset.x,
853 position.y - mEventData->mScrollPosition.y - mAlignmentOffset.y );
855 mEventData->mUpdateCursorPosition = mEventData->mPrimaryCursorPosition != handlePosition;
856 mEventData->mScrollAfterUpdatePosition = mEventData->mUpdateCursorPosition;
857 mEventData->mPrimaryCursorPosition = handlePosition;
858 mEventData->mUpdateInputStyle = mEventData->mUpdateCursorPosition;
860 else if( leftSelectionHandleEvent || rightSelectionHandleEvent )
862 // TODO: This is recalculating the selection box every time the text is scrolled with the selection handles.
863 // Think if something can be done to save power.
865 ChangeState( EventData::SELECTION_HANDLE_PANNING );
867 Vector2 position = mEventData->mDecorator->GetPosition( leftSelectionHandleEvent ? Text::LEFT_SELECTION_HANDLE : Text::RIGHT_SELECTION_HANDLE );
869 // Position the selection handle close to either the left or right edge.
870 position.x = scrollRightDirection ? 0.f : mVisualModel->mControlSize.width;
872 // Get the new handle position.
873 // The selection handle's position is in decorator coords. Need to transforms to text coords.
874 const CharacterIndex handlePosition = GetClosestCursorIndex( position.x - mEventData->mScrollPosition.x - mAlignmentOffset.x,
875 position.y - mEventData->mScrollPosition.y - mAlignmentOffset.y );
877 if( leftSelectionHandleEvent )
879 const bool differentHandles = ( mEventData->mLeftSelectionPosition != handlePosition ) && ( mEventData->mRightSelectionPosition != handlePosition );
880 mEventData->mUpdateLeftSelectionPosition = endOfScroll || differentHandles;
881 if( differentHandles )
883 mEventData->mLeftSelectionPosition = handlePosition;
888 const bool differentHandles = ( mEventData->mRightSelectionPosition != handlePosition ) && ( mEventData->mLeftSelectionPosition != handlePosition );
889 mEventData->mUpdateRightSelectionPosition = endOfScroll || differentHandles;
890 if( differentHandles )
892 mEventData->mRightSelectionPosition = handlePosition;
896 if( mEventData->mUpdateLeftSelectionPosition || mEventData->mUpdateRightSelectionPosition )
898 RepositionSelectionHandles();
900 mEventData->mScrollAfterUpdatePosition = true;
903 mEventData->mDecoratorUpdated = true;
904 } // end ( HANDLE_SCROLLING == state )
907 void Controller::Impl::OnSelectEvent( const Event& event )
909 if( NULL == mEventData )
911 // Nothing to do if there is no text.
915 if( mEventData->mSelectionEnabled )
917 // The event.p2 and event.p3 are in decorator coords. Need to transforms to text coords.
918 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
919 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
921 // Calculates the logical position from the x,y coords.
922 RepositionSelectionHandles( xPosition,
925 mEventData->mUpdateLeftSelectionPosition = true;
926 mEventData->mUpdateRightSelectionPosition = true;
928 mEventData->mScrollAfterUpdatePosition = ( mEventData->mLeftSelectionPosition != mEventData->mRightSelectionPosition );
932 void Controller::Impl::OnSelectAllEvent()
934 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "OnSelectAllEvent mEventData->mSelectionEnabled%s \n", mEventData->mSelectionEnabled?"true":"false");
936 if( NULL == mEventData )
938 // Nothing to do if there is no text.
942 if( mEventData->mSelectionEnabled )
944 mEventData->mLeftSelectionPosition = 0u;
945 mEventData->mRightSelectionPosition = mLogicalModel->mText.Count();
947 mEventData->mScrollAfterUpdatePosition = true;
948 mEventData->mUpdateLeftSelectionPosition = true;
949 mEventData->mUpdateRightSelectionPosition = true;
953 void Controller::Impl::RetrieveSelection( std::string& selectedText, bool deleteAfterRetrieval )
955 if( mEventData->mLeftSelectionPosition == mEventData->mRightSelectionPosition )
957 // Nothing to select if handles are in the same place.
958 selectedText.clear();
962 const bool handlesCrossed = mEventData->mLeftSelectionPosition > mEventData->mRightSelectionPosition;
964 //Get start and end position of selection
965 const CharacterIndex startOfSelectedText = handlesCrossed ? mEventData->mRightSelectionPosition : mEventData->mLeftSelectionPosition;
966 const Length lengthOfSelectedText = ( handlesCrossed ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition ) - startOfSelectedText;
968 // Validate the start and end selection points
969 if( ( startOfSelectedText + lengthOfSelectedText ) <= mLogicalModel->mText.Count() )
971 //Get text as a UTF8 string
972 Vector<Character>& utf32Characters = mLogicalModel->mText;
974 Utf32ToUtf8( &utf32Characters[startOfSelectedText], lengthOfSelectedText, selectedText );
976 if( deleteAfterRetrieval ) // Only delete text if copied successfully
978 // Set as input style the style of the first deleted character.
979 mLogicalModel->RetrieveStyle( startOfSelectedText, mEventData->mInputStyle );
981 mLogicalModel->UpdateTextStyleRuns( startOfSelectedText, -static_cast<int>( lengthOfSelectedText ) );
983 // Delete text between handles
984 Vector<Character>& currentText = mLogicalModel->mText;
986 Vector<Character>::Iterator first = currentText.Begin() + startOfSelectedText;
987 Vector<Character>::Iterator last = first + lengthOfSelectedText;
988 currentText.Erase( first, last );
990 // Scroll after delete.
991 mEventData->mPrimaryCursorPosition = handlesCrossed ? mEventData->mRightSelectionPosition : mEventData->mLeftSelectionPosition;
992 mEventData->mScrollAfterDelete = true;
994 // Udpade the cursor position and the decorator.
995 // Scroll after the position is updated if is not scrolling after delete.
996 mEventData->mUpdateCursorPosition = true;
997 mEventData->mScrollAfterUpdatePosition = !mEventData->mScrollAfterDelete;
998 mEventData->mDecoratorUpdated = true;
1002 void Controller::Impl::ShowClipboard()
1006 mClipboard.ShowClipboard();
1010 void Controller::Impl::HideClipboard()
1014 mClipboard.HideClipboard();
1018 bool Controller::Impl::CopyStringToClipboard( std::string& source )
1020 //Send string to clipboard
1021 return ( mClipboard && mClipboard.SetItem( source ) );
1024 void Controller::Impl::SendSelectionToClipboard( bool deleteAfterSending )
1026 std::string selectedText;
1027 RetrieveSelection( selectedText, deleteAfterSending );
1028 CopyStringToClipboard( selectedText );
1029 ChangeState( EventData::EDITING );
1032 void Controller::Impl::GetTextFromClipboard( unsigned int itemIndex, std::string& retrievedString )
1036 retrievedString = mClipboard.GetItem( itemIndex );
1040 void Controller::Impl::RepositionSelectionHandles()
1042 CharacterIndex selectionStart = mEventData->mLeftSelectionPosition;
1043 CharacterIndex selectionEnd = mEventData->mRightSelectionPosition;
1045 if( selectionStart == selectionEnd )
1047 // Nothing to select if handles are in the same place.
1051 mEventData->mDecorator->ClearHighlights();
1053 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
1054 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
1055 const GlyphInfo* const glyphsBuffer = mVisualModel->mGlyphs.Begin();
1056 const Vector2* const positionsBuffer = mVisualModel->mGlyphPositions.Begin();
1057 const Length* const charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
1058 const CharacterIndex* const glyphToCharacterBuffer = mVisualModel->mGlyphsToCharacters.Begin();
1059 const CharacterDirection* const modelCharacterDirectionsBuffer = ( 0u != mLogicalModel->mCharacterDirections.Count() ) ? mLogicalModel->mCharacterDirections.Begin() : NULL;
1061 // TODO: Better algorithm to create the highlight box.
1062 // TODO: Multi-line.
1064 // Get the height of the line.
1065 const Vector<LineRun>& lines = mVisualModel->mLines;
1066 const LineRun& firstLine = *lines.Begin();
1067 const float height = firstLine.ascender + -firstLine.descender;
1069 const bool isLastCharacter = selectionEnd >= mLogicalModel->mText.Count();
1070 const bool startDirection = ( ( NULL == modelCharacterDirectionsBuffer ) ? false : *( modelCharacterDirectionsBuffer + selectionStart ) );
1071 const bool endDirection = ( ( NULL == modelCharacterDirectionsBuffer ) ? false : *( modelCharacterDirectionsBuffer + ( selectionEnd - ( isLastCharacter ? 1u : 0u ) ) ) );
1073 // Swap the indices if the start is greater than the end.
1074 const bool indicesSwapped = selectionStart > selectionEnd;
1076 // Tell the decorator to flip the selection handles if needed.
1077 mEventData->mDecorator->SetSelectionHandleFlipState( indicesSwapped, startDirection, endDirection );
1079 if( indicesSwapped )
1081 std::swap( selectionStart, selectionEnd );
1084 // Get the indices to the first and last selected glyphs.
1085 const CharacterIndex selectionEndMinusOne = selectionEnd - 1u;
1086 const GlyphIndex glyphStart = *( charactersToGlyphBuffer + selectionStart );
1087 const Length numberOfGlyphs = *( glyphsPerCharacterBuffer + selectionEndMinusOne );
1088 const GlyphIndex glyphEnd = *( charactersToGlyphBuffer + selectionEndMinusOne ) + ( ( numberOfGlyphs > 0 ) ? numberOfGlyphs - 1u : 0u );
1090 // Check if the first glyph is a ligature that must be broken like Latin ff, fi, or Arabic ﻻ, etc which needs special code.
1091 const Length numberOfCharactersStart = *( charactersPerGlyphBuffer + glyphStart );
1092 bool splitStartGlyph = ( numberOfCharactersStart > 1u ) && HasLigatureMustBreak( mLogicalModel->GetScript( selectionStart ) );
1094 // Check if the last glyph is a ligature that must be broken like Latin ff, fi, or Arabic ﻻ, etc which needs special code.
1095 const Length numberOfCharactersEnd = *( charactersPerGlyphBuffer + glyphEnd );
1096 bool splitEndGlyph = ( glyphStart != glyphEnd ) && ( numberOfCharactersEnd > 1u ) && HasLigatureMustBreak( mLogicalModel->GetScript( selectionEndMinusOne ) );
1098 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
1100 // Traverse the glyphs.
1101 for( GlyphIndex index = glyphStart; index <= glyphEnd; ++index )
1103 const GlyphInfo& glyph = *( glyphsBuffer + index );
1104 const Vector2& position = *( positionsBuffer + index );
1106 if( splitStartGlyph )
1108 // 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.
1110 const float glyphAdvance = glyph.advance / static_cast<float>( numberOfCharactersStart );
1111 const CharacterIndex interGlyphIndex = selectionStart - *( glyphToCharacterBuffer + glyphStart );
1112 // Get the direction of the character.
1113 CharacterDirection isCurrentRightToLeft = false;
1114 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
1116 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + selectionStart );
1119 // The end point could be in the middle of the ligature.
1120 // Calculate the number of characters selected.
1121 const Length numberOfCharacters = ( glyphStart == glyphEnd ) ? ( selectionEnd - selectionStart ) : ( numberOfCharactersStart - interGlyphIndex );
1123 const float xPosition = position.x - glyph.xBearing + offset.x + glyphAdvance * static_cast<float>( isCurrentRightToLeft ? ( numberOfCharactersStart - interGlyphIndex - numberOfCharacters ) : interGlyphIndex );
1125 mEventData->mDecorator->AddHighlight( xPosition,
1127 xPosition + static_cast<float>( numberOfCharacters ) * glyphAdvance,
1128 offset.y + height );
1130 splitStartGlyph = false;
1134 if( splitEndGlyph && ( index == glyphEnd ) )
1136 // 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.
1138 const float glyphAdvance = glyph.advance / static_cast<float>( numberOfCharactersEnd );
1139 const CharacterIndex interGlyphIndex = selectionEnd - *( glyphToCharacterBuffer + glyphEnd );
1140 // Get the direction of the character.
1141 CharacterDirection isCurrentRightToLeft = false;
1142 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
1144 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + selectionEnd );
1147 const Length numberOfCharacters = numberOfCharactersEnd - interGlyphIndex;
1149 const float xPosition = position.x - glyph.xBearing + offset.x + ( isCurrentRightToLeft ? ( glyphAdvance * static_cast<float>( numberOfCharacters ) ) : 0.f );
1150 mEventData->mDecorator->AddHighlight( xPosition,
1152 xPosition + static_cast<float>( interGlyphIndex ) * glyphAdvance,
1153 offset.y + height );
1155 splitEndGlyph = false;
1159 const float xPosition = position.x - glyph.xBearing + offset.x;
1160 mEventData->mDecorator->AddHighlight( xPosition,
1162 xPosition + glyph.advance,
1163 offset.y + height );
1166 CursorInfo primaryCursorInfo;
1167 GetCursorPosition( mEventData->mLeftSelectionPosition,
1168 primaryCursorInfo );
1170 CursorInfo secondaryCursorInfo;
1171 GetCursorPosition( mEventData->mRightSelectionPosition,
1172 secondaryCursorInfo );
1174 const Vector2 primaryPosition = primaryCursorInfo.primaryPosition + offset;
1175 const Vector2 secondaryPosition = secondaryCursorInfo.primaryPosition + offset;
1177 mEventData->mDecorator->SetPosition( LEFT_SELECTION_HANDLE,
1179 primaryCursorInfo.lineOffset + offset.y,
1180 primaryCursorInfo.lineHeight );
1182 mEventData->mDecorator->SetPosition( RIGHT_SELECTION_HANDLE,
1183 secondaryPosition.x,
1184 secondaryCursorInfo.lineOffset + offset.y,
1185 secondaryCursorInfo.lineHeight );
1187 // Cursor to be positioned at end of selection so if selection interrupted and edit mode restarted the cursor will be at end of selection
1188 mEventData->mPrimaryCursorPosition = ( indicesSwapped ) ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition;
1190 // Set the flag to update the decorator.
1191 mEventData->mDecoratorUpdated = true;
1194 void Controller::Impl::RepositionSelectionHandles( float visualX, float visualY )
1196 if( NULL == mEventData )
1198 // Nothing to do if there is no text input.
1202 if( IsShowingPlaceholderText() )
1204 // Nothing to do if there is the place-holder text.
1208 const Length numberOfGlyphs = mVisualModel->mGlyphs.Count();
1209 const Length numberOfLines = mVisualModel->mLines.Count();
1210 if( ( 0 == numberOfGlyphs ) ||
1211 ( 0 == numberOfLines ) )
1213 // Nothing to do if there is no text.
1217 // Find which word was selected
1218 CharacterIndex selectionStart( 0 );
1219 CharacterIndex selectionEnd( 0 );
1220 FindSelectionIndices( visualX, visualY, selectionStart, selectionEnd );
1221 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "%p selectionStart %d selectionEnd %d\n", this, selectionStart, selectionEnd );
1223 if( selectionStart == selectionEnd )
1225 ChangeState( EventData::EDITING );
1226 // Nothing to select. i.e. a white space, out of bounds
1230 mEventData->mLeftSelectionPosition = selectionStart;
1231 mEventData->mRightSelectionPosition = selectionEnd;
1234 void Controller::Impl::SetPopupButtons()
1237 * Sets the Popup buttons to be shown depending on State.
1239 * If SELECTING : CUT & COPY + ( PASTE & CLIPBOARD if content available to paste )
1241 * If EDITING_WITH_POPUP : SELECT & SELECT_ALL
1244 TextSelectionPopup::Buttons buttonsToShow = TextSelectionPopup::NONE;
1246 if( EventData::SELECTING == mEventData->mState )
1248 buttonsToShow = TextSelectionPopup::Buttons( TextSelectionPopup::CUT | TextSelectionPopup::COPY );
1250 if( !IsClipboardEmpty() )
1252 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
1253 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::CLIPBOARD ) );
1256 if( !mEventData->mAllTextSelected )
1258 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::SELECT_ALL ) );
1261 else if( EventData::EDITING_WITH_POPUP == mEventData->mState )
1263 if( mLogicalModel->mText.Count() && !IsShowingPlaceholderText() )
1265 buttonsToShow = TextSelectionPopup::Buttons( TextSelectionPopup::SELECT | TextSelectionPopup::SELECT_ALL );
1268 if( !IsClipboardEmpty() )
1270 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
1271 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::CLIPBOARD ) );
1274 else if( EventData::EDITING_WITH_PASTE_POPUP == mEventData->mState )
1276 if ( !IsClipboardEmpty() )
1278 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
1279 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::CLIPBOARD ) );
1283 mEventData->mDecorator->SetEnabledPopupButtons( buttonsToShow );
1286 void Controller::Impl::ChangeState( EventData::State newState )
1288 if( NULL == mEventData )
1290 // Nothing to do if there is no text input.
1294 DALI_LOG_INFO( gLogFilter, Debug::General, "ChangeState state:%d newstate:%d\n", mEventData->mState, newState );
1296 if( mEventData->mState != newState )
1298 mEventData->mState = newState;
1300 if( EventData::INACTIVE == mEventData->mState )
1302 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
1303 mEventData->mDecorator->StopCursorBlink();
1304 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1305 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1306 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1307 mEventData->mDecorator->SetPopupActive( false );
1308 mEventData->mDecoratorUpdated = true;
1311 else if( EventData::INTERRUPTED == mEventData->mState)
1313 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1314 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1315 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1316 mEventData->mDecorator->SetPopupActive( false );
1317 mEventData->mDecoratorUpdated = true;
1320 else if( EventData::SELECTING == mEventData->mState )
1322 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
1323 mEventData->mDecorator->StopCursorBlink();
1324 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1325 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true );
1326 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true );
1327 if( mEventData->mGrabHandlePopupEnabled )
1330 mEventData->mDecorator->SetPopupActive( true );
1332 mEventData->mDecoratorUpdated = true;
1334 else if( EventData::EDITING == mEventData->mState )
1336 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1337 if( mEventData->mCursorBlinkEnabled )
1339 mEventData->mDecorator->StartCursorBlink();
1341 // Grab handle is not shown until a tap is received whilst EDITING
1342 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1343 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1344 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1345 if( mEventData->mGrabHandlePopupEnabled )
1347 mEventData->mDecorator->SetPopupActive( false );
1349 mEventData->mDecoratorUpdated = true;
1352 else if( EventData::EDITING_WITH_POPUP == mEventData->mState )
1354 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "EDITING_WITH_POPUP \n", newState );
1356 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1357 if( mEventData->mCursorBlinkEnabled )
1359 mEventData->mDecorator->StartCursorBlink();
1361 if( mEventData->mSelectionEnabled )
1363 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1364 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1368 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
1370 if( mEventData->mGrabHandlePopupEnabled )
1373 mEventData->mDecorator->SetPopupActive( true );
1376 mEventData->mDecoratorUpdated = true;
1378 else if( EventData::EDITING_WITH_GRAB_HANDLE == mEventData->mState )
1380 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "EDITING_WITH_GRAB_HANDLE \n", newState );
1382 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1383 if( mEventData->mCursorBlinkEnabled )
1385 mEventData->mDecorator->StartCursorBlink();
1387 // Grab handle is not shown until a tap is received whilst EDITING
1388 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
1389 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1390 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1391 if( mEventData->mGrabHandlePopupEnabled )
1393 mEventData->mDecorator->SetPopupActive( false );
1395 mEventData->mDecoratorUpdated = true;
1398 else if( EventData::SELECTION_HANDLE_PANNING == mEventData->mState )
1400 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
1401 mEventData->mDecorator->StopCursorBlink();
1402 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1403 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true );
1404 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true );
1405 if( mEventData->mGrabHandlePopupEnabled )
1407 mEventData->mDecorator->SetPopupActive( false );
1409 mEventData->mDecoratorUpdated = true;
1411 else if( EventData::GRAB_HANDLE_PANNING == mEventData->mState )
1413 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "GRAB_HANDLE_PANNING \n", newState );
1415 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1416 if( mEventData->mCursorBlinkEnabled )
1418 mEventData->mDecorator->StartCursorBlink();
1420 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
1421 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1422 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1423 if( mEventData->mGrabHandlePopupEnabled )
1425 mEventData->mDecorator->SetPopupActive( false );
1427 mEventData->mDecoratorUpdated = true;
1429 else if( EventData::EDITING_WITH_PASTE_POPUP == mEventData->mState )
1431 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "EDITING_WITH_PASTE_POPUP \n", newState );
1433 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1434 if( mEventData->mCursorBlinkEnabled )
1436 mEventData->mDecorator->StartCursorBlink();
1439 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
1440 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1441 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1443 if( mEventData->mGrabHandlePopupEnabled )
1446 mEventData->mDecorator->SetPopupActive( true );
1449 mEventData->mDecoratorUpdated = true;
1454 LineIndex Controller::Impl::GetClosestLine( float y ) const
1456 float totalHeight = 0.f;
1457 LineIndex lineIndex = 0u;
1459 const Vector<LineRun>& lines = mVisualModel->mLines;
1460 for( LineIndex endLine = lines.Count();
1461 lineIndex < endLine;
1464 const LineRun& lineRun = lines[lineIndex];
1465 totalHeight += lineRun.ascender + -lineRun.descender;
1466 if( y < totalHeight )
1472 if( lineIndex == 0 )
1480 void Controller::Impl::FindSelectionIndices( float visualX, float visualY, CharacterIndex& startIndex, CharacterIndex& endIndex )
1482 CharacterIndex hitCharacter = GetClosestCursorIndex( visualX, visualY );
1483 DALI_ASSERT_DEBUG( hitCharacter <= mLogicalModel->mText.Count() && "GetClosestCursorIndex returned out of bounds index" );
1485 if( mLogicalModel->mText.Count() == 0 )
1487 return; // if model empty
1490 if( hitCharacter >= mLogicalModel->mText.Count() )
1492 // Closest hit character is the last character.
1493 if( hitCharacter == mLogicalModel->mText.Count() )
1495 hitCharacter--; //Hit character index set to last character in logical model
1499 // hitCharacter is out of bounds
1504 startIndex = hitCharacter;
1505 endIndex = hitCharacter;
1506 bool isHitCharacterWhitespace = TextAbstraction::IsWhiteSpace( mLogicalModel->mText[hitCharacter] );
1508 // Find the start and end of the text
1509 for( startIndex = hitCharacter; startIndex > 0; --startIndex )
1511 if( isHitCharacterWhitespace != TextAbstraction::IsWhiteSpace( mLogicalModel->mText[ startIndex-1 ] ) )
1516 const CharacterIndex pastTheEnd = mLogicalModel->mText.Count();
1517 for( endIndex = hitCharacter + 1u; endIndex < pastTheEnd; ++endIndex )
1519 if( isHitCharacterWhitespace != TextAbstraction::IsWhiteSpace( mLogicalModel->mText[ endIndex ] ) )
1526 CharacterIndex Controller::Impl::GetClosestCursorIndex( float visualX,
1529 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "GetClosestCursorIndex %p closest visualX %f visualY %f\n", this, visualX, visualY );
1531 if( NULL == mEventData )
1533 // Nothing to do if there is no text input.
1537 CharacterIndex logicalIndex = 0u;
1539 const Length numberOfGlyphs = mVisualModel->mGlyphs.Count();
1540 const Length numberOfLines = mVisualModel->mLines.Count();
1541 if( ( 0 == numberOfGlyphs ) ||
1542 ( 0 == numberOfLines ) )
1544 return logicalIndex;
1547 // Find which line is closest
1548 const LineIndex lineIndex = GetClosestLine( visualY );
1549 const LineRun& line = mVisualModel->mLines[lineIndex];
1551 // Get the positions of the glyphs.
1552 const Vector<Vector2>& positions = mVisualModel->mGlyphPositions;
1553 const Vector2* const positionsBuffer = positions.Begin();
1555 // Get the visual to logical conversion tables.
1556 const CharacterIndex* const visualToLogicalBuffer = ( 0u != mLogicalModel->mVisualToLogicalMap.Count() ) ? mLogicalModel->mVisualToLogicalMap.Begin() : NULL;
1557 const CharacterIndex* const visualToLogicalCursorBuffer = mLogicalModel->mVisualToLogicalCursorMap.Begin();
1559 // Get the character to glyph conversion table.
1560 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
1562 // Get the glyphs per character table.
1563 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
1565 // If the vector is void, there is no right to left characters.
1566 const bool hasRightToLeftCharacters = NULL != visualToLogicalBuffer;
1568 const CharacterIndex startCharacter = line.characterRun.characterIndex;
1569 const CharacterIndex endCharacter = line.characterRun.characterIndex + line.characterRun.numberOfCharacters;
1570 DALI_ASSERT_DEBUG( endCharacter <= mLogicalModel->mText.Count() && "Invalid line info" );
1572 // Whether there is a hit on a glyph.
1573 bool matched = false;
1575 // Traverses glyphs in visual order. To do that use the visual to logical conversion table.
1576 CharacterIndex visualIndex = startCharacter;
1577 Length numberOfCharacters = 0u;
1578 for( ; !matched && ( visualIndex < endCharacter ); ++visualIndex )
1580 // The character in logical order.
1581 const CharacterIndex characterLogicalOrderIndex = hasRightToLeftCharacters ? *( visualToLogicalBuffer + visualIndex ) : visualIndex;
1583 // Get the script of the character.
1584 const Script script = mLogicalModel->GetScript( characterLogicalOrderIndex );
1586 // The number of glyphs for that character
1587 const Length numberOfGlyphs = *( glyphsPerCharacterBuffer + characterLogicalOrderIndex );
1588 ++numberOfCharacters;
1591 if( 0u != numberOfGlyphs )
1593 // Get the first character/glyph of the group of glyphs.
1594 const CharacterIndex firstVisualCharacterIndex = 1u + visualIndex - numberOfCharacters;
1595 const CharacterIndex firstLogicalCharacterIndex = hasRightToLeftCharacters ? *( visualToLogicalBuffer + firstVisualCharacterIndex ) : firstVisualCharacterIndex;
1596 const GlyphIndex firstLogicalGlyphIndex = *( charactersToGlyphBuffer + firstLogicalCharacterIndex );
1598 // Get the metrics for the group of glyphs.
1599 GlyphMetrics glyphMetrics;
1600 GetGlyphsMetrics( firstLogicalGlyphIndex,
1606 // Get the position of the first glyph.
1607 const Vector2& position = *( positionsBuffer + firstLogicalGlyphIndex );
1609 // Whether the glyph can be split, like Latin ligatures fi, ff or Arabic ﻻ.
1610 const bool isInterglyphIndex = ( numberOfCharacters > numberOfGlyphs ) && HasLigatureMustBreak( script );
1611 const Length numberOfBlocks = isInterglyphIndex ? numberOfCharacters : 1u;
1612 const float glyphAdvance = glyphMetrics.advance / static_cast<float>( numberOfBlocks );
1614 GlyphIndex index = 0u;
1615 for( ; !matched && ( index < numberOfBlocks ); ++index )
1617 // Find the mid-point of the area containing the glyph
1618 const float glyphCenter = -glyphMetrics.xBearing + position.x + ( static_cast<float>( index ) + 0.5f ) * glyphAdvance;
1620 if( visualX < glyphCenter )
1629 visualIndex = firstVisualCharacterIndex + index;
1633 numberOfCharacters = 0u;
1639 // Return the logical position of the cursor in characters.
1643 visualIndex = endCharacter;
1646 logicalIndex = hasRightToLeftCharacters ? *( visualToLogicalCursorBuffer + visualIndex ) : visualIndex;
1647 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "%p closest visualIndex %d logicalIndex %d\n", this, visualIndex, logicalIndex );
1649 DALI_ASSERT_DEBUG( ( logicalIndex <= mLogicalModel->mText.Count() && logicalIndex >= 0 ) && "GetClosestCursorIndex - Out of bounds index" );
1651 return logicalIndex;
1654 void Controller::Impl::GetCursorPosition( CharacterIndex logical,
1655 CursorInfo& cursorInfo )
1657 // TODO: Check for multiline with \n, etc...
1659 const Length numberOfCharacters = mLogicalModel->mText.Count();
1660 if( !IsShowingRealText() )
1662 // Do not want to use the place-holder text to set the cursor position.
1664 // Use the line's height of the font's family set to set the cursor's size.
1665 // If there is no font's family set, use the default font.
1666 // Use the current alignment to place the cursor at the beginning, center or end of the box.
1668 cursorInfo.lineOffset = 0.f;
1669 cursorInfo.lineHeight = GetDefaultFontLineHeight();
1670 cursorInfo.primaryCursorHeight = cursorInfo.lineHeight;
1672 switch( mLayoutEngine.GetHorizontalAlignment() )
1674 case LayoutEngine::HORIZONTAL_ALIGN_BEGIN:
1676 cursorInfo.primaryPosition.x = 0.f;
1679 case LayoutEngine::HORIZONTAL_ALIGN_CENTER:
1681 cursorInfo.primaryPosition.x = floorf( 0.5f * mVisualModel->mControlSize.width );
1684 case LayoutEngine::HORIZONTAL_ALIGN_END:
1686 cursorInfo.primaryPosition.x = mVisualModel->mControlSize.width - mEventData->mDecorator->GetCursorWidth();
1691 switch( mLayoutEngine.GetVerticalAlignment() )
1693 case LayoutEngine::VERTICAL_ALIGN_TOP:
1695 cursorInfo.primaryPosition.y = 0.f;
1698 case LayoutEngine::VERTICAL_ALIGN_CENTER:
1700 cursorInfo.primaryPosition.y = floorf( 0.5f * ( mVisualModel->mControlSize.height - cursorInfo.lineHeight ) );
1703 case LayoutEngine::VERTICAL_ALIGN_BOTTOM:
1705 cursorInfo.primaryPosition.y = mVisualModel->mControlSize.height - cursorInfo.lineHeight;
1710 // Nothing else to do.
1714 // Check if the logical position is the first or the last one of the text.
1715 const bool isFirstPosition = 0u == logical;
1716 const bool isLastPosition = numberOfCharacters == logical;
1718 // 'logical' is the logical 'cursor' index.
1719 // Get the next and current logical 'character' index.
1720 const CharacterIndex nextCharacterIndex = logical;
1721 const CharacterIndex characterIndex = isFirstPosition ? logical : logical - 1u;
1723 // Get the direction of the character and the next one.
1724 const CharacterDirection* const modelCharacterDirectionsBuffer = ( 0u != mLogicalModel->mCharacterDirections.Count() ) ? mLogicalModel->mCharacterDirections.Begin() : NULL;
1726 CharacterDirection isCurrentRightToLeft = false;
1727 CharacterDirection isNextRightToLeft = false;
1728 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
1730 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + characterIndex );
1731 isNextRightToLeft = *( modelCharacterDirectionsBuffer + nextCharacterIndex );
1734 // Get the line where the character is laid-out.
1735 const LineRun* const modelLines = mVisualModel->mLines.Begin();
1737 const LineIndex lineIndex = mVisualModel->GetLineOfCharacter( characterIndex );
1738 const LineRun& line = *( modelLines + lineIndex );
1740 // Get the paragraph's direction.
1741 const CharacterDirection isRightToLeftParagraph = line.direction;
1743 // Check whether there is an alternative position:
1745 cursorInfo.isSecondaryCursor = ( !isLastPosition && ( isCurrentRightToLeft != isNextRightToLeft ) ) ||
1746 ( isLastPosition && ( isRightToLeftParagraph != isCurrentRightToLeft ) );
1748 // Set the line offset and height.
1749 cursorInfo.lineOffset = 0.f;
1750 cursorInfo.lineHeight = line.ascender + -line.descender;
1752 // Calculate the primary cursor.
1754 CharacterIndex index = characterIndex;
1755 if( cursorInfo.isSecondaryCursor )
1757 // If there is a secondary position, the primary cursor may be in a different place than the logical index.
1759 if( isLastPosition )
1761 // The position of the cursor after the last character needs special
1762 // care depending on its direction and the direction of the paragraph.
1764 // Need to find the first character after the last character with the paragraph's direction.
1765 // i.e l0 l1 l2 r0 r1 should find r0.
1767 // TODO: check for more than one line!
1768 index = isRightToLeftParagraph ? line.characterRun.characterIndex : line.characterRun.characterIndex + line.characterRun.numberOfCharacters - 1u;
1769 index = mLogicalModel->GetLogicalCharacterIndex( index );
1773 index = ( isRightToLeftParagraph == isCurrentRightToLeft ) ? characterIndex : nextCharacterIndex;
1777 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
1778 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
1779 const Length* const charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
1780 const CharacterIndex* const glyphsToCharactersBuffer = mVisualModel->mGlyphsToCharacters.Begin();
1781 const Vector2* const glyphPositionsBuffer = mVisualModel->mGlyphPositions.Begin();
1783 // Convert the cursor position into the glyph position.
1784 const GlyphIndex primaryGlyphIndex = *( charactersToGlyphBuffer + index );
1785 const Length primaryNumberOfGlyphs = *( glyphsPerCharacterBuffer + index );
1786 const Length primaryNumberOfCharacters = *( charactersPerGlyphBuffer + primaryGlyphIndex );
1788 // Get the metrics for the group of glyphs.
1789 GlyphMetrics glyphMetrics;
1790 GetGlyphsMetrics( primaryGlyphIndex,
1791 primaryNumberOfGlyphs,
1796 // Whether to add the glyph's advance to the cursor position.
1797 // 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,
1798 // if the logical cursor is one, the position is the position of the first glyph and the advance is added.
1799 // A 'truth table' was build and an online Karnaugh map tool was used to simplify the logic.
1820 // Where F -> isFirstPosition
1821 // L -> isLastPosition
1822 // C -> isCurrentRightToLeft
1823 // P -> isRightToLeftParagraph
1824 // A -> Whether to add the glyph's advance.
1826 const bool addGlyphAdvance = ( ( isLastPosition && !isRightToLeftParagraph ) ||
1827 ( isFirstPosition && isRightToLeftParagraph ) ||
1828 ( !isFirstPosition && !isLastPosition && !isCurrentRightToLeft ) );
1830 float glyphAdvance = addGlyphAdvance ? glyphMetrics.advance : 0.f;
1832 if( !isLastPosition &&
1833 ( primaryNumberOfCharacters > 1u ) )
1835 const CharacterIndex firstIndex = *( glyphsToCharactersBuffer + primaryGlyphIndex );
1837 bool isCurrentRightToLeft = false;
1838 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
1840 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + index );
1843 Length numberOfGlyphAdvance = ( isFirstPosition ? 0u : 1u ) + characterIndex - firstIndex;
1844 if( isCurrentRightToLeft )
1846 numberOfGlyphAdvance = primaryNumberOfCharacters - numberOfGlyphAdvance;
1849 glyphAdvance = static_cast<float>( numberOfGlyphAdvance ) * glyphMetrics.advance / static_cast<float>( primaryNumberOfCharacters );
1852 // Get the glyph position and x bearing.
1853 const Vector2& primaryPosition = *( glyphPositionsBuffer + primaryGlyphIndex );
1855 // Set the primary cursor's height.
1856 cursorInfo.primaryCursorHeight = cursorInfo.isSecondaryCursor ? 0.5f * glyphMetrics.fontHeight : glyphMetrics.fontHeight;
1858 // Set the primary cursor's position.
1859 cursorInfo.primaryPosition.x = -glyphMetrics.xBearing + primaryPosition.x + glyphAdvance;
1860 cursorInfo.primaryPosition.y = line.ascender - glyphMetrics.ascender;
1862 // Calculate the secondary cursor.
1864 if( cursorInfo.isSecondaryCursor )
1866 // Set the secondary cursor's height.
1867 cursorInfo.secondaryCursorHeight = 0.5f * glyphMetrics.fontHeight;
1869 CharacterIndex index = characterIndex;
1870 if( !isLastPosition )
1872 index = ( isRightToLeftParagraph == isCurrentRightToLeft ) ? nextCharacterIndex : characterIndex;
1875 const GlyphIndex secondaryGlyphIndex = *( charactersToGlyphBuffer + index );
1876 const Length secondaryNumberOfGlyphs = *( glyphsPerCharacterBuffer + index );
1878 const Vector2& secondaryPosition = *( glyphPositionsBuffer + secondaryGlyphIndex );
1880 GetGlyphsMetrics( secondaryGlyphIndex,
1881 secondaryNumberOfGlyphs,
1886 // Set the secondary cursor's position.
1887 cursorInfo.secondaryPosition.x = -glyphMetrics.xBearing + secondaryPosition.x + ( isCurrentRightToLeft ? 0.f : glyphMetrics.advance );
1888 cursorInfo.secondaryPosition.y = cursorInfo.lineHeight - cursorInfo.secondaryCursorHeight - line.descender - ( glyphMetrics.fontHeight - glyphMetrics.ascender );
1892 CharacterIndex Controller::Impl::CalculateNewCursorIndex( CharacterIndex index ) const
1894 if( NULL == mEventData )
1896 // Nothing to do if there is no text input.
1900 CharacterIndex cursorIndex = mEventData->mPrimaryCursorPosition;
1902 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
1903 const Length* const charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
1905 GlyphIndex glyphIndex = *( charactersToGlyphBuffer + index );
1906 Length numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
1908 if( numberOfCharacters > 1u )
1910 const Script script = mLogicalModel->GetScript( index );
1911 if( HasLigatureMustBreak( script ) )
1913 // Prevents to jump the whole Latin ligatures like fi, ff, or Arabic ﻻ, ...
1914 numberOfCharacters = 1u;
1919 while( 0u == numberOfCharacters )
1922 numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
1926 if( index < mEventData->mPrimaryCursorPosition )
1928 cursorIndex -= numberOfCharacters;
1932 cursorIndex += numberOfCharacters;
1938 void Controller::Impl::UpdateCursorPosition( const CursorInfo& cursorInfo )
1940 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "-->Controller::UpdateCursorPosition %p\n", this );
1941 if( NULL == mEventData )
1943 // Nothing to do if there is no text input.
1944 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition no event data\n" );
1948 const Vector2 offset = mEventData->mScrollPosition + ( IsShowingRealText() ? mAlignmentOffset : Vector2::ZERO );
1949 const Vector2 cursorPosition = cursorInfo.primaryPosition + offset;
1951 // Sets the cursor position.
1952 mEventData->mDecorator->SetPosition( PRIMARY_CURSOR,
1955 cursorInfo.primaryCursorHeight,
1956 cursorInfo.lineHeight );
1957 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Primary cursor position: %f,%f\n", cursorPosition.x, cursorPosition.y );
1959 // Sets the grab handle position.
1960 mEventData->mDecorator->SetPosition( GRAB_HANDLE,
1962 cursorInfo.lineOffset + offset.y,
1963 cursorInfo.lineHeight );
1965 if( cursorInfo.isSecondaryCursor )
1967 mEventData->mDecorator->SetPosition( SECONDARY_CURSOR,
1968 cursorInfo.secondaryPosition.x + offset.x,
1969 cursorInfo.secondaryPosition.y + offset.y,
1970 cursorInfo.secondaryCursorHeight,
1971 cursorInfo.lineHeight );
1972 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Secondary cursor position: %f,%f\n", cursorInfo.secondaryPosition.x + offset.x, cursorInfo.secondaryPosition.y + offset.y );
1975 // Set which cursors are active according the state.
1976 if( EventData::IsEditingState( mEventData->mState ) || ( EventData::GRAB_HANDLE_PANNING == mEventData->mState ) )
1978 if( cursorInfo.isSecondaryCursor )
1980 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_BOTH );
1984 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1989 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
1992 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition\n" );
1995 void Controller::Impl::UpdateSelectionHandle( HandleType handleType,
1996 const CursorInfo& cursorInfo )
1998 if( ( LEFT_SELECTION_HANDLE != handleType ) &&
1999 ( RIGHT_SELECTION_HANDLE != handleType ) )
2004 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
2005 const Vector2 cursorPosition = cursorInfo.primaryPosition + offset;
2007 // Sets the handle's position.
2008 mEventData->mDecorator->SetPosition( handleType,
2010 cursorInfo.lineOffset + offset.y,
2011 cursorInfo.lineHeight );
2013 // If selection handle at start of the text and other at end of the text then all text is selected.
2014 const CharacterIndex startOfSelection = std::min( mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition );
2015 const CharacterIndex endOfSelection = std::max ( mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition );
2016 mEventData->mAllTextSelected = ( startOfSelection == 0 ) && ( endOfSelection == mLogicalModel->mText.Count() );
2019 void Controller::Impl::ClampHorizontalScroll( const Vector2& actualSize )
2021 // Clamp between -space & 0 (and the text alignment).
2023 if( actualSize.width > mVisualModel->mControlSize.width )
2025 const float space = ( actualSize.width - mVisualModel->mControlSize.width ) + mAlignmentOffset.x;
2026 mEventData->mScrollPosition.x = ( mEventData->mScrollPosition.x < -space ) ? -space : mEventData->mScrollPosition.x;
2027 mEventData->mScrollPosition.x = ( mEventData->mScrollPosition.x > -mAlignmentOffset.x ) ? -mAlignmentOffset.x : mEventData->mScrollPosition.x;
2029 mEventData->mDecoratorUpdated = true;
2033 mEventData->mScrollPosition.x = 0.f;
2037 void Controller::Impl::ClampVerticalScroll( const Vector2& actualSize )
2039 // Clamp between -space & 0 (and the text alignment).
2040 if( actualSize.height > mVisualModel->mControlSize.height )
2042 const float space = ( actualSize.height - mVisualModel->mControlSize.height ) + mAlignmentOffset.y;
2043 mEventData->mScrollPosition.y = ( mEventData->mScrollPosition.y < -space ) ? -space : mEventData->mScrollPosition.y;
2044 mEventData->mScrollPosition.y = ( mEventData->mScrollPosition.y > -mAlignmentOffset.y ) ? -mAlignmentOffset.y : mEventData->mScrollPosition.y;
2046 mEventData->mDecoratorUpdated = true;
2050 mEventData->mScrollPosition.y = 0.f;
2054 void Controller::Impl::ScrollToMakePositionVisible( const Vector2& position )
2056 // position is in actor's coords.
2057 const float positionEnd = position.x + ( mEventData->mDecorator ? mEventData->mDecorator->GetCursorWidth() : 0.f );
2059 // Transform the position to decorator coords.
2060 const float offset = mEventData->mScrollPosition.x + mAlignmentOffset.x;
2061 const float decoratorPositionBegin = position.x + offset;
2062 const float decoratorPositionEnd = positionEnd + offset;
2064 if( decoratorPositionBegin < 0.f )
2066 mEventData->mScrollPosition.x = -position.x - mAlignmentOffset.x;
2068 else if( decoratorPositionEnd > mVisualModel->mControlSize.width )
2070 mEventData->mScrollPosition.x = mVisualModel->mControlSize.width - positionEnd - mAlignmentOffset.x;
2074 void Controller::Impl::ScrollTextToMatchCursor( const CursorInfo& cursorInfo )
2076 // Get the current cursor position in decorator coords.
2077 const Vector2& currentCursorPosition = mEventData->mDecorator->GetPosition( PRIMARY_CURSOR );
2079 // Calculate the offset to match the cursor position before the character was deleted.
2080 mEventData->mScrollPosition.x = currentCursorPosition.x - cursorInfo.primaryPosition.x - mAlignmentOffset.x;
2082 ClampHorizontalScroll( mVisualModel->GetActualSize() );
2085 void Controller::Impl::RequestRelayout()
2087 mControlInterface.RequestTextRelayout();
2092 } // namespace Toolkit