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 // This paragraph has right to left text. Some characters may need to be mirrored.
416 // TODO: consider if the mirrored string can be stored as well.
418 textMirrored = GetMirroredText( utf32Characters,
419 mirroredUtf32Characters,
422 // Only set the character directions if there is right to left characters.
423 Vector<CharacterDirection>& directions = mLogicalModel->mCharacterDirections;
424 directions.Resize( numberOfCharacters );
426 GetCharactersDirection( bidirectionalInfo,
431 // There is no right to left characters. Clear the directions vector.
432 mLogicalModel->mCharacterDirections.Clear();
436 Vector<GlyphInfo>& glyphs = mVisualModel->mGlyphs;
437 Vector<CharacterIndex>& glyphsToCharactersMap = mVisualModel->mGlyphsToCharacters;
438 Vector<Length>& charactersPerGlyph = mVisualModel->mCharactersPerGlyph;
439 Vector<GlyphIndex> newParagraphGlyphs;
440 newParagraphGlyphs.Reserve( numberOfParagraphs );
442 if( SHAPE_TEXT & operations )
444 const Vector<Character>& textToShape = textMirrored ? mirroredUtf32Characters : utf32Characters;
446 ShapeText( textToShape,
451 glyphsToCharactersMap,
453 newParagraphGlyphs );
455 // Create the 'number of glyphs' per character and the glyph to character conversion tables.
456 mVisualModel->CreateGlyphsPerCharacterTable( numberOfCharacters );
457 mVisualModel->CreateCharacterToGlyphTable( numberOfCharacters );
460 const Length numberOfGlyphs = glyphs.Count();
462 if( GET_GLYPH_METRICS & operations )
464 GlyphInfo* glyphsBuffer = glyphs.Begin();
465 mMetrics->GetGlyphMetrics( glyphsBuffer, numberOfGlyphs );
467 // Update the width and advance of all new paragraph characters.
468 for( Vector<GlyphIndex>::ConstIterator it = newParagraphGlyphs.Begin(), endIt = newParagraphGlyphs.End(); it != endIt; ++it )
470 const GlyphIndex index = *it;
471 GlyphInfo& glyph = *( glyphsBuffer + index );
473 glyph.xBearing = 0.f;
479 if( ( NULL != mEventData ) &&
480 mEventData->mPreEditFlag &&
481 ( 0u != mVisualModel->mCharactersToGlyph.Count() ) )
483 // Add the underline for the pre-edit text.
484 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
485 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
487 const GlyphIndex glyphStart = *( charactersToGlyphBuffer + mEventData->mPreEditStartPosition );
488 const CharacterIndex lastPreEditCharacter = mEventData->mPreEditStartPosition + ( ( mEventData->mPreEditLength > 0u ) ? mEventData->mPreEditLength - 1u : 0u );
489 const Length numberOfGlyphsLastCharacter = *( glyphsPerCharacterBuffer + lastPreEditCharacter );
490 const GlyphIndex glyphEnd = *( charactersToGlyphBuffer + lastPreEditCharacter ) + ( numberOfGlyphsLastCharacter > 1u ? numberOfGlyphsLastCharacter - 1u : 0u );
492 GlyphRun underlineRun;
493 underlineRun.glyphIndex = glyphStart;
494 underlineRun.numberOfGlyphs = 1u + glyphEnd - glyphStart;
496 // TODO: At the moment the underline runs are only for pre-edit.
497 mVisualModel->mUnderlineRuns.PushBack( underlineRun );
501 bool Controller::Impl::UpdateModelStyle( OperationsMask operationsRequired )
503 bool updated = false;
505 if( COLOR & operationsRequired )
507 // Set the color runs in glyphs.
508 SetColorSegmentationInfo( mLogicalModel->mColorRuns,
509 mVisualModel->mCharactersToGlyph,
510 mVisualModel->mGlyphsPerCharacter,
511 mVisualModel->mColorRuns );
519 void Controller::Impl::RetrieveDefaultInputStyle( InputStyle& inputStyle )
521 // Sets the default text's color.
522 inputStyle.textColor = mTextColor;
524 // Sets the default font's family name, weight, width, slant and size.
527 inputStyle.familyName = mFontDefaults->mFontDescription.family;
528 inputStyle.weight = mFontDefaults->mFontDescription.weight;
529 inputStyle.width = mFontDefaults->mFontDescription.width;
530 inputStyle.slant = mFontDefaults->mFontDescription.slant;
531 inputStyle.size = mFontDefaults->mDefaultPointSize;
533 inputStyle.familyDefined = mFontDefaults->familyDefined;
534 inputStyle.weightDefined = mFontDefaults->weightDefined;
535 inputStyle.widthDefined = mFontDefaults->widthDefined;
536 inputStyle.slantDefined = mFontDefaults->slantDefined;
537 inputStyle.sizeDefined = mFontDefaults->sizeDefined;
541 inputStyle.familyName.clear();
542 inputStyle.weight = TextAbstraction::FontWeight::NORMAL;
543 inputStyle.width = TextAbstraction::FontWidth::NORMAL;
544 inputStyle.slant = TextAbstraction::FontSlant::NORMAL;
545 inputStyle.size = 0.f;
547 inputStyle.familyDefined = false;
548 inputStyle.weightDefined = false;
549 inputStyle.widthDefined = false;
550 inputStyle.slantDefined = false;
551 inputStyle.sizeDefined = false;
555 float Controller::Impl::GetDefaultFontLineHeight()
557 FontId defaultFontId = 0u;
558 if( NULL == mFontDefaults )
560 TextAbstraction::FontDescription fontDescription;
561 defaultFontId = mFontClient.GetFontId( fontDescription );
565 defaultFontId = mFontDefaults->GetFontId( mFontClient );
568 Text::FontMetrics fontMetrics;
569 mMetrics->GetFontMetrics( defaultFontId, fontMetrics );
571 return( fontMetrics.ascender - fontMetrics.descender );
574 void Controller::Impl::OnCursorKeyEvent( const Event& event )
576 if( NULL == mEventData )
578 // Nothing to do if there is no text input.
582 int keyCode = event.p1.mInt;
584 if( Dali::DALI_KEY_CURSOR_LEFT == keyCode )
586 if( mEventData->mPrimaryCursorPosition > 0u )
588 mEventData->mPrimaryCursorPosition = CalculateNewCursorIndex( mEventData->mPrimaryCursorPosition - 1u );
591 else if( Dali::DALI_KEY_CURSOR_RIGHT == keyCode )
593 if( mLogicalModel->mText.Count() > mEventData->mPrimaryCursorPosition )
595 mEventData->mPrimaryCursorPosition = CalculateNewCursorIndex( mEventData->mPrimaryCursorPosition );
598 else if( Dali::DALI_KEY_CURSOR_UP == keyCode )
602 else if( Dali::DALI_KEY_CURSOR_DOWN == keyCode )
607 mEventData->mUpdateCursorPosition = true;
608 mEventData->mUpdateInputStyle = true;
609 mEventData->mScrollAfterUpdatePosition = true;
612 void Controller::Impl::OnTapEvent( const Event& event )
614 if( NULL != mEventData )
616 const unsigned int tapCount = event.p1.mUint;
620 if( IsShowingRealText() )
622 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
623 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
625 mEventData->mPrimaryCursorPosition = GetClosestCursorIndex( xPosition,
628 // When the cursor position is changing, delay cursor blinking
629 mEventData->mDecorator->DelayCursorBlink();
633 mEventData->mPrimaryCursorPosition = 0u;
636 mEventData->mUpdateCursorPosition = true;
637 mEventData->mScrollAfterUpdatePosition = true;
638 mEventData->mUpdateInputStyle = true;
640 // Notify the cursor position to the imf manager.
641 if( mEventData->mImfManager )
643 mEventData->mImfManager.SetCursorPosition( mEventData->mPrimaryCursorPosition );
644 mEventData->mImfManager.NotifyCursorPosition();
650 void Controller::Impl::OnPanEvent( const Event& event )
652 if( NULL == mEventData )
654 // Nothing to do if there is no text input.
658 int state = event.p1.mInt;
660 if( Gesture::Started == state ||
661 Gesture::Continuing == state )
663 const Vector2& actualSize = mVisualModel->GetActualSize();
664 const Vector2 currentScroll = mEventData->mScrollPosition;
666 if( mEventData->mHorizontalScrollingEnabled )
668 const float displacementX = event.p2.mFloat;
669 mEventData->mScrollPosition.x += displacementX;
671 ClampHorizontalScroll( actualSize );
674 if( mEventData->mVerticalScrollingEnabled )
676 const float displacementY = event.p3.mFloat;
677 mEventData->mScrollPosition.y += displacementY;
679 ClampVerticalScroll( actualSize );
682 if( mEventData->mDecorator )
684 mEventData->mDecorator->UpdatePositions( mEventData->mScrollPosition - currentScroll );
689 void Controller::Impl::OnLongPressEvent( const Event& event )
691 DALI_LOG_INFO( gLogFilter, Debug::General, "Controller::OnLongPressEvent\n" );
693 if( EventData::EDITING == mEventData->mState )
695 ChangeState ( EventData::EDITING_WITH_POPUP );
696 mEventData->mDecoratorUpdated = true;
700 void Controller::Impl::OnHandleEvent( const Event& event )
702 if( NULL == mEventData )
704 // Nothing to do if there is no text input.
708 const unsigned int state = event.p1.mUint;
709 const bool handleStopScrolling = ( HANDLE_STOP_SCROLLING == state );
711 if( HANDLE_PRESSED == state )
713 // The event.p2 and event.p3 are in decorator coords. Need to transforms to text coords.
714 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
715 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
717 const CharacterIndex handleNewPosition = GetClosestCursorIndex( xPosition, yPosition );
719 if( Event::GRAB_HANDLE_EVENT == event.type )
721 ChangeState ( EventData::GRAB_HANDLE_PANNING );
723 if( handleNewPosition != mEventData->mPrimaryCursorPosition )
725 mEventData->mPrimaryCursorPosition = handleNewPosition;
726 mEventData->mUpdateCursorPosition = true;
729 else if( Event::LEFT_SELECTION_HANDLE_EVENT == event.type )
731 ChangeState ( EventData::SELECTION_HANDLE_PANNING );
733 if( ( handleNewPosition != mEventData->mLeftSelectionPosition ) &&
734 ( handleNewPosition != mEventData->mRightSelectionPosition ) )
736 mEventData->mLeftSelectionPosition = handleNewPosition;
738 mEventData->mUpdateLeftSelectionPosition = true;
741 else if( Event::RIGHT_SELECTION_HANDLE_EVENT == event.type )
743 ChangeState ( EventData::SELECTION_HANDLE_PANNING );
745 if( ( handleNewPosition != mEventData->mRightSelectionPosition ) &&
746 ( handleNewPosition != mEventData->mLeftSelectionPosition ) )
748 mEventData->mRightSelectionPosition = handleNewPosition;
750 mEventData->mUpdateRightSelectionPosition = true;
753 } // end ( HANDLE_PRESSED == state )
754 else if( ( HANDLE_RELEASED == state ) ||
755 handleStopScrolling )
757 CharacterIndex handlePosition = 0u;
758 if( handleStopScrolling )
760 // The event.p2 and event.p3 are in decorator coords. Need to transforms to text coords.
761 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
762 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
764 handlePosition = GetClosestCursorIndex( xPosition, yPosition );
767 if( Event::GRAB_HANDLE_EVENT == event.type )
769 mEventData->mUpdateCursorPosition = true;
770 mEventData->mUpdateInputStyle = true;
772 if( !IsClipboardEmpty() )
774 ChangeState( EventData::EDITING_WITH_PASTE_POPUP ); // Moving grabhandle will show Paste Popup
777 if( handleStopScrolling )
779 mEventData->mScrollAfterUpdatePosition = mEventData->mPrimaryCursorPosition != handlePosition;
780 mEventData->mPrimaryCursorPosition = handlePosition;
783 else if( Event::LEFT_SELECTION_HANDLE_EVENT == event.type )
785 ChangeState( EventData::SELECTING );
787 if( handleStopScrolling )
789 mEventData->mUpdateLeftSelectionPosition = ( mEventData->mRightSelectionPosition != handlePosition );
790 mEventData->mScrollAfterUpdatePosition = mEventData->mUpdateLeftSelectionPosition;
792 if( mEventData->mUpdateLeftSelectionPosition )
794 mEventData->mLeftSelectionPosition = handlePosition;
798 else if( Event::RIGHT_SELECTION_HANDLE_EVENT == event.type )
800 ChangeState( EventData::SELECTING );
802 if( handleStopScrolling )
804 mEventData->mUpdateRightSelectionPosition = ( mEventData->mLeftSelectionPosition != handlePosition );
805 mEventData->mScrollAfterUpdatePosition = mEventData->mUpdateRightSelectionPosition;
806 if( mEventData->mUpdateRightSelectionPosition )
808 mEventData->mRightSelectionPosition = handlePosition;
813 mEventData->mDecoratorUpdated = true;
814 } // end ( ( HANDLE_RELEASED == state ) || ( HANDLE_STOP_SCROLLING == state ) )
815 else if( HANDLE_SCROLLING == state )
817 const float xSpeed = event.p2.mFloat;
818 const Vector2& actualSize = mVisualModel->GetActualSize();
819 const Vector2 currentScrollPosition = mEventData->mScrollPosition;
821 mEventData->mScrollPosition.x += xSpeed;
823 ClampHorizontalScroll( actualSize );
825 bool endOfScroll = false;
826 if( Vector2::ZERO == ( currentScrollPosition - mEventData->mScrollPosition ) )
828 // Notify the decorator there is no more text to scroll.
829 // The decorator won't send more scroll events.
830 mEventData->mDecorator->NotifyEndOfScroll();
831 // Still need to set the position of the handle.
835 // Set the position of the handle.
836 const bool scrollRightDirection = xSpeed > 0.f;
837 const bool leftSelectionHandleEvent = Event::LEFT_SELECTION_HANDLE_EVENT == event.type;
838 const bool rightSelectionHandleEvent = Event::RIGHT_SELECTION_HANDLE_EVENT == event.type;
840 if( Event::GRAB_HANDLE_EVENT == event.type )
842 ChangeState( EventData::GRAB_HANDLE_PANNING );
844 Vector2 position = mEventData->mDecorator->GetPosition( GRAB_HANDLE );
846 // Position the grag handle close to either the left or right edge.
847 position.x = scrollRightDirection ? 0.f : mVisualModel->mControlSize.width;
849 // Get the new handle position.
850 // The grab handle's position is in decorator coords. Need to transforms to text coords.
851 const CharacterIndex handlePosition = GetClosestCursorIndex( position.x - mEventData->mScrollPosition.x - mAlignmentOffset.x,
852 position.y - mEventData->mScrollPosition.y - mAlignmentOffset.y );
854 mEventData->mUpdateCursorPosition = mEventData->mPrimaryCursorPosition != handlePosition;
855 mEventData->mScrollAfterUpdatePosition = mEventData->mUpdateCursorPosition;
856 mEventData->mPrimaryCursorPosition = handlePosition;
857 mEventData->mUpdateInputStyle = mEventData->mUpdateCursorPosition;
859 else if( leftSelectionHandleEvent || rightSelectionHandleEvent )
861 // TODO: This is recalculating the selection box every time the text is scrolled with the selection handles.
862 // Think if something can be done to save power.
864 ChangeState( EventData::SELECTION_HANDLE_PANNING );
866 Vector2 position = mEventData->mDecorator->GetPosition( leftSelectionHandleEvent ? Text::LEFT_SELECTION_HANDLE : Text::RIGHT_SELECTION_HANDLE );
868 // Position the selection handle close to either the left or right edge.
869 position.x = scrollRightDirection ? 0.f : mVisualModel->mControlSize.width;
871 // Get the new handle position.
872 // The selection handle's position is in decorator coords. Need to transforms to text coords.
873 const CharacterIndex handlePosition = GetClosestCursorIndex( position.x - mEventData->mScrollPosition.x - mAlignmentOffset.x,
874 position.y - mEventData->mScrollPosition.y - mAlignmentOffset.y );
876 if( leftSelectionHandleEvent )
878 const bool differentHandles = ( mEventData->mLeftSelectionPosition != handlePosition ) && ( mEventData->mRightSelectionPosition != handlePosition );
879 mEventData->mUpdateLeftSelectionPosition = endOfScroll || differentHandles;
880 if( differentHandles )
882 mEventData->mLeftSelectionPosition = handlePosition;
887 const bool differentHandles = ( mEventData->mRightSelectionPosition != handlePosition ) && ( mEventData->mLeftSelectionPosition != handlePosition );
888 mEventData->mUpdateRightSelectionPosition = endOfScroll || differentHandles;
889 if( differentHandles )
891 mEventData->mRightSelectionPosition = handlePosition;
895 if( mEventData->mUpdateLeftSelectionPosition || mEventData->mUpdateRightSelectionPosition )
897 RepositionSelectionHandles();
899 mEventData->mScrollAfterUpdatePosition = true;
902 mEventData->mDecoratorUpdated = true;
903 } // end ( HANDLE_SCROLLING == state )
906 void Controller::Impl::OnSelectEvent( const Event& event )
908 if( NULL == mEventData )
910 // Nothing to do if there is no text.
914 if( mEventData->mSelectionEnabled )
916 // The event.p2 and event.p3 are in decorator coords. Need to transforms to text coords.
917 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
918 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
920 // Calculates the logical position from the x,y coords.
921 RepositionSelectionHandles( xPosition,
924 mEventData->mUpdateLeftSelectionPosition = true;
925 mEventData->mUpdateRightSelectionPosition = true;
927 mEventData->mScrollAfterUpdatePosition = ( mEventData->mLeftSelectionPosition != mEventData->mRightSelectionPosition );
931 void Controller::Impl::OnSelectAllEvent()
933 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "OnSelectAllEvent mEventData->mSelectionEnabled%s \n", mEventData->mSelectionEnabled?"true":"false");
935 if( NULL == mEventData )
937 // Nothing to do if there is no text.
941 if( mEventData->mSelectionEnabled )
943 mEventData->mLeftSelectionPosition = 0u;
944 mEventData->mRightSelectionPosition = mLogicalModel->mText.Count();
946 mEventData->mScrollAfterUpdatePosition = true;
947 mEventData->mUpdateLeftSelectionPosition = true;
948 mEventData->mUpdateRightSelectionPosition = true;
952 void Controller::Impl::RetrieveSelection( std::string& selectedText, bool deleteAfterRetrieval )
954 if( mEventData->mLeftSelectionPosition == mEventData->mRightSelectionPosition )
956 // Nothing to select if handles are in the same place.
957 selectedText.clear();
961 const bool handlesCrossed = mEventData->mLeftSelectionPosition > mEventData->mRightSelectionPosition;
963 //Get start and end position of selection
964 const CharacterIndex startOfSelectedText = handlesCrossed ? mEventData->mRightSelectionPosition : mEventData->mLeftSelectionPosition;
965 const Length lengthOfSelectedText = ( handlesCrossed ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition ) - startOfSelectedText;
967 // Validate the start and end selection points
968 if( ( startOfSelectedText + lengthOfSelectedText ) <= mLogicalModel->mText.Count() )
970 //Get text as a UTF8 string
971 Vector<Character>& utf32Characters = mLogicalModel->mText;
973 Utf32ToUtf8( &utf32Characters[startOfSelectedText], lengthOfSelectedText, selectedText );
975 if( deleteAfterRetrieval ) // Only delete text if copied successfully
977 // Set as input style the style of the first deleted character.
978 mLogicalModel->RetrieveStyle( startOfSelectedText, mEventData->mInputStyle );
980 mLogicalModel->UpdateTextStyleRuns( startOfSelectedText, -static_cast<int>( lengthOfSelectedText ) );
982 // Delete text between handles
983 Vector<Character>& currentText = mLogicalModel->mText;
985 Vector<Character>::Iterator first = currentText.Begin() + startOfSelectedText;
986 Vector<Character>::Iterator last = first + lengthOfSelectedText;
987 currentText.Erase( first, last );
989 // Scroll after delete.
990 mEventData->mPrimaryCursorPosition = handlesCrossed ? mEventData->mRightSelectionPosition : mEventData->mLeftSelectionPosition;
991 mEventData->mScrollAfterDelete = true;
993 // Udpade the cursor position and the decorator.
994 // Scroll after the position is updated if is not scrolling after delete.
995 mEventData->mUpdateCursorPosition = true;
996 mEventData->mScrollAfterUpdatePosition = !mEventData->mScrollAfterDelete;
997 mEventData->mDecoratorUpdated = true;
1001 void Controller::Impl::ShowClipboard()
1005 mClipboard.ShowClipboard();
1009 void Controller::Impl::HideClipboard()
1013 mClipboard.HideClipboard();
1017 bool Controller::Impl::CopyStringToClipboard( std::string& source )
1019 //Send string to clipboard
1020 return ( mClipboard && mClipboard.SetItem( source ) );
1023 void Controller::Impl::SendSelectionToClipboard( bool deleteAfterSending )
1025 std::string selectedText;
1026 RetrieveSelection( selectedText, deleteAfterSending );
1027 CopyStringToClipboard( selectedText );
1028 ChangeState( EventData::EDITING );
1031 void Controller::Impl::GetTextFromClipboard( unsigned int itemIndex, std::string& retrievedString )
1035 retrievedString = mClipboard.GetItem( itemIndex );
1039 void Controller::Impl::RepositionSelectionHandles()
1041 CharacterIndex selectionStart = mEventData->mLeftSelectionPosition;
1042 CharacterIndex selectionEnd = mEventData->mRightSelectionPosition;
1044 if( selectionStart == selectionEnd )
1046 // Nothing to select if handles are in the same place.
1050 mEventData->mDecorator->ClearHighlights();
1052 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
1053 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
1054 const GlyphInfo* const glyphsBuffer = mVisualModel->mGlyphs.Begin();
1055 const Vector2* const positionsBuffer = mVisualModel->mGlyphPositions.Begin();
1056 const Length* const charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
1057 const CharacterIndex* const glyphToCharacterBuffer = mVisualModel->mGlyphsToCharacters.Begin();
1058 const CharacterDirection* const modelCharacterDirectionsBuffer = ( 0u != mLogicalModel->mCharacterDirections.Count() ) ? mLogicalModel->mCharacterDirections.Begin() : NULL;
1060 // TODO: Better algorithm to create the highlight box.
1061 // TODO: Multi-line.
1063 // Get the height of the line.
1064 const Vector<LineRun>& lines = mVisualModel->mLines;
1065 const LineRun& firstLine = *lines.Begin();
1066 const float height = firstLine.ascender + -firstLine.descender;
1068 const bool isLastCharacter = selectionEnd >= mLogicalModel->mText.Count();
1069 const bool startDirection = ( ( NULL == modelCharacterDirectionsBuffer ) ? false : *( modelCharacterDirectionsBuffer + selectionStart ) );
1070 const bool endDirection = ( ( NULL == modelCharacterDirectionsBuffer ) ? false : *( modelCharacterDirectionsBuffer + ( selectionEnd - ( isLastCharacter ? 1u : 0u ) ) ) );
1072 // Swap the indices if the start is greater than the end.
1073 const bool indicesSwapped = selectionStart > selectionEnd;
1075 // Tell the decorator to flip the selection handles if needed.
1076 mEventData->mDecorator->SetSelectionHandleFlipState( indicesSwapped, startDirection, endDirection );
1078 if( indicesSwapped )
1080 std::swap( selectionStart, selectionEnd );
1083 // Get the indices to the first and last selected glyphs.
1084 const CharacterIndex selectionEndMinusOne = selectionEnd - 1u;
1085 const GlyphIndex glyphStart = *( charactersToGlyphBuffer + selectionStart );
1086 const Length numberOfGlyphs = *( glyphsPerCharacterBuffer + selectionEndMinusOne );
1087 const GlyphIndex glyphEnd = *( charactersToGlyphBuffer + selectionEndMinusOne ) + ( ( numberOfGlyphs > 0 ) ? numberOfGlyphs - 1u : 0u );
1089 // Check if the first glyph is a ligature that must be broken like Latin ff, fi, or Arabic ﻻ, etc which needs special code.
1090 const Length numberOfCharactersStart = *( charactersPerGlyphBuffer + glyphStart );
1091 bool splitStartGlyph = ( numberOfCharactersStart > 1u ) && HasLigatureMustBreak( mLogicalModel->GetScript( selectionStart ) );
1093 // Check if the last glyph is a ligature that must be broken like Latin ff, fi, or Arabic ﻻ, etc which needs special code.
1094 const Length numberOfCharactersEnd = *( charactersPerGlyphBuffer + glyphEnd );
1095 bool splitEndGlyph = ( glyphStart != glyphEnd ) && ( numberOfCharactersEnd > 1u ) && HasLigatureMustBreak( mLogicalModel->GetScript( selectionEndMinusOne ) );
1097 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
1099 // Traverse the glyphs.
1100 for( GlyphIndex index = glyphStart; index <= glyphEnd; ++index )
1102 const GlyphInfo& glyph = *( glyphsBuffer + index );
1103 const Vector2& position = *( positionsBuffer + index );
1105 if( splitStartGlyph )
1107 // 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.
1109 const float glyphAdvance = glyph.advance / static_cast<float>( numberOfCharactersStart );
1110 const CharacterIndex interGlyphIndex = selectionStart - *( glyphToCharacterBuffer + glyphStart );
1111 // Get the direction of the character.
1112 CharacterDirection isCurrentRightToLeft = false;
1113 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
1115 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + selectionStart );
1118 // The end point could be in the middle of the ligature.
1119 // Calculate the number of characters selected.
1120 const Length numberOfCharacters = ( glyphStart == glyphEnd ) ? ( selectionEnd - selectionStart ) : ( numberOfCharactersStart - interGlyphIndex );
1122 const float xPosition = position.x - glyph.xBearing + offset.x + glyphAdvance * static_cast<float>( isCurrentRightToLeft ? ( numberOfCharactersStart - interGlyphIndex - numberOfCharacters ) : interGlyphIndex );
1124 mEventData->mDecorator->AddHighlight( xPosition,
1126 xPosition + static_cast<float>( numberOfCharacters ) * glyphAdvance,
1127 offset.y + height );
1129 splitStartGlyph = false;
1133 if( splitEndGlyph && ( index == glyphEnd ) )
1135 // 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.
1137 const float glyphAdvance = glyph.advance / static_cast<float>( numberOfCharactersEnd );
1138 const CharacterIndex interGlyphIndex = selectionEnd - *( glyphToCharacterBuffer + glyphEnd );
1139 // Get the direction of the character.
1140 CharacterDirection isCurrentRightToLeft = false;
1141 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
1143 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + selectionEnd );
1146 const Length numberOfCharacters = numberOfCharactersEnd - interGlyphIndex;
1148 const float xPosition = position.x - glyph.xBearing + offset.x + ( isCurrentRightToLeft ? ( glyphAdvance * static_cast<float>( numberOfCharacters ) ) : 0.f );
1149 mEventData->mDecorator->AddHighlight( xPosition,
1151 xPosition + static_cast<float>( interGlyphIndex ) * glyphAdvance,
1152 offset.y + height );
1154 splitEndGlyph = false;
1158 const float xPosition = position.x - glyph.xBearing + offset.x;
1159 mEventData->mDecorator->AddHighlight( xPosition,
1161 xPosition + glyph.advance,
1162 offset.y + height );
1165 CursorInfo primaryCursorInfo;
1166 GetCursorPosition( mEventData->mLeftSelectionPosition,
1167 primaryCursorInfo );
1169 CursorInfo secondaryCursorInfo;
1170 GetCursorPosition( mEventData->mRightSelectionPosition,
1171 secondaryCursorInfo );
1173 const Vector2 primaryPosition = primaryCursorInfo.primaryPosition + offset;
1174 const Vector2 secondaryPosition = secondaryCursorInfo.primaryPosition + offset;
1176 mEventData->mDecorator->SetPosition( LEFT_SELECTION_HANDLE,
1178 primaryCursorInfo.lineOffset + offset.y,
1179 primaryCursorInfo.lineHeight );
1181 mEventData->mDecorator->SetPosition( RIGHT_SELECTION_HANDLE,
1182 secondaryPosition.x,
1183 secondaryCursorInfo.lineOffset + offset.y,
1184 secondaryCursorInfo.lineHeight );
1186 // Cursor to be positioned at end of selection so if selection interrupted and edit mode restarted the cursor will be at end of selection
1187 mEventData->mPrimaryCursorPosition = ( indicesSwapped ) ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition;
1189 // Set the flag to update the decorator.
1190 mEventData->mDecoratorUpdated = true;
1193 void Controller::Impl::RepositionSelectionHandles( float visualX, float visualY )
1195 if( NULL == mEventData )
1197 // Nothing to do if there is no text input.
1201 if( IsShowingPlaceholderText() )
1203 // Nothing to do if there is the place-holder text.
1207 const Length numberOfGlyphs = mVisualModel->mGlyphs.Count();
1208 const Length numberOfLines = mVisualModel->mLines.Count();
1209 if( ( 0 == numberOfGlyphs ) ||
1210 ( 0 == numberOfLines ) )
1212 // Nothing to do if there is no text.
1216 // Find which word was selected
1217 CharacterIndex selectionStart( 0 );
1218 CharacterIndex selectionEnd( 0 );
1219 FindSelectionIndices( visualX, visualY, selectionStart, selectionEnd );
1220 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "%p selectionStart %d selectionEnd %d\n", this, selectionStart, selectionEnd );
1222 if( selectionStart == selectionEnd )
1224 ChangeState( EventData::EDITING );
1225 // Nothing to select. i.e. a white space, out of bounds
1229 mEventData->mLeftSelectionPosition = selectionStart;
1230 mEventData->mRightSelectionPosition = selectionEnd;
1233 void Controller::Impl::SetPopupButtons()
1236 * Sets the Popup buttons to be shown depending on State.
1238 * If SELECTING : CUT & COPY + ( PASTE & CLIPBOARD if content available to paste )
1240 * If EDITING_WITH_POPUP : SELECT & SELECT_ALL
1243 TextSelectionPopup::Buttons buttonsToShow = TextSelectionPopup::NONE;
1245 if( EventData::SELECTING == mEventData->mState )
1247 buttonsToShow = TextSelectionPopup::Buttons( TextSelectionPopup::CUT | TextSelectionPopup::COPY );
1249 if( !IsClipboardEmpty() )
1251 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
1252 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::CLIPBOARD ) );
1255 if( !mEventData->mAllTextSelected )
1257 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::SELECT_ALL ) );
1260 else if( EventData::EDITING_WITH_POPUP == mEventData->mState )
1262 if( mLogicalModel->mText.Count() && !IsShowingPlaceholderText() )
1264 buttonsToShow = TextSelectionPopup::Buttons( TextSelectionPopup::SELECT | TextSelectionPopup::SELECT_ALL );
1267 if( !IsClipboardEmpty() )
1269 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
1270 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::CLIPBOARD ) );
1273 else if( EventData::EDITING_WITH_PASTE_POPUP == mEventData->mState )
1275 if ( !IsClipboardEmpty() )
1277 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
1278 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::CLIPBOARD ) );
1282 mEventData->mDecorator->SetEnabledPopupButtons( buttonsToShow );
1285 void Controller::Impl::ChangeState( EventData::State newState )
1287 if( NULL == mEventData )
1289 // Nothing to do if there is no text input.
1293 DALI_LOG_INFO( gLogFilter, Debug::General, "ChangeState state:%d newstate:%d\n", mEventData->mState, newState );
1295 if( mEventData->mState != newState )
1297 mEventData->mState = newState;
1299 if( EventData::INACTIVE == mEventData->mState )
1301 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
1302 mEventData->mDecorator->StopCursorBlink();
1303 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1304 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1305 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1306 mEventData->mDecorator->SetPopupActive( false );
1307 mEventData->mDecoratorUpdated = true;
1310 else if( EventData::INTERRUPTED == mEventData->mState)
1312 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1313 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1314 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1315 mEventData->mDecorator->SetPopupActive( false );
1316 mEventData->mDecoratorUpdated = true;
1319 else if( EventData::SELECTING == mEventData->mState )
1321 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
1322 mEventData->mDecorator->StopCursorBlink();
1323 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1324 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true );
1325 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true );
1326 if( mEventData->mGrabHandlePopupEnabled )
1329 mEventData->mDecorator->SetPopupActive( true );
1331 mEventData->mDecoratorUpdated = true;
1333 else if( EventData::EDITING == mEventData->mState )
1335 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1336 if( mEventData->mCursorBlinkEnabled )
1338 mEventData->mDecorator->StartCursorBlink();
1340 // Grab handle is not shown until a tap is received whilst EDITING
1341 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1342 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1343 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1344 if( mEventData->mGrabHandlePopupEnabled )
1346 mEventData->mDecorator->SetPopupActive( false );
1348 mEventData->mDecoratorUpdated = true;
1351 else if( EventData::EDITING_WITH_POPUP == mEventData->mState )
1353 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "EDITING_WITH_POPUP \n", newState );
1355 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1356 if( mEventData->mCursorBlinkEnabled )
1358 mEventData->mDecorator->StartCursorBlink();
1360 if( mEventData->mSelectionEnabled )
1362 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1363 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1367 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
1369 if( mEventData->mGrabHandlePopupEnabled )
1372 mEventData->mDecorator->SetPopupActive( true );
1375 mEventData->mDecoratorUpdated = true;
1377 else if( EventData::EDITING_WITH_GRAB_HANDLE == mEventData->mState )
1379 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "EDITING_WITH_GRAB_HANDLE \n", newState );
1381 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1382 if( mEventData->mCursorBlinkEnabled )
1384 mEventData->mDecorator->StartCursorBlink();
1386 // Grab handle is not shown until a tap is received whilst EDITING
1387 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
1388 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1389 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1390 if( mEventData->mGrabHandlePopupEnabled )
1392 mEventData->mDecorator->SetPopupActive( false );
1394 mEventData->mDecoratorUpdated = true;
1397 else if( EventData::SELECTION_HANDLE_PANNING == mEventData->mState )
1399 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
1400 mEventData->mDecorator->StopCursorBlink();
1401 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1402 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true );
1403 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true );
1404 if( mEventData->mGrabHandlePopupEnabled )
1406 mEventData->mDecorator->SetPopupActive( false );
1408 mEventData->mDecoratorUpdated = true;
1410 else if( EventData::GRAB_HANDLE_PANNING == mEventData->mState )
1412 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "GRAB_HANDLE_PANNING \n", newState );
1414 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1415 if( mEventData->mCursorBlinkEnabled )
1417 mEventData->mDecorator->StartCursorBlink();
1419 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
1420 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1421 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1422 if( mEventData->mGrabHandlePopupEnabled )
1424 mEventData->mDecorator->SetPopupActive( false );
1426 mEventData->mDecoratorUpdated = true;
1428 else if( EventData::EDITING_WITH_PASTE_POPUP == mEventData->mState )
1430 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "EDITING_WITH_PASTE_POPUP \n", newState );
1432 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1433 if( mEventData->mCursorBlinkEnabled )
1435 mEventData->mDecorator->StartCursorBlink();
1438 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
1439 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1440 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1442 if( mEventData->mGrabHandlePopupEnabled )
1445 mEventData->mDecorator->SetPopupActive( true );
1448 mEventData->mDecoratorUpdated = true;
1453 LineIndex Controller::Impl::GetClosestLine( float y ) const
1455 float totalHeight = 0.f;
1456 LineIndex lineIndex = 0u;
1458 const Vector<LineRun>& lines = mVisualModel->mLines;
1459 for( LineIndex endLine = lines.Count();
1460 lineIndex < endLine;
1463 const LineRun& lineRun = lines[lineIndex];
1464 totalHeight += lineRun.ascender + -lineRun.descender;
1465 if( y < totalHeight )
1471 if( lineIndex == 0 )
1479 void Controller::Impl::FindSelectionIndices( float visualX, float visualY, CharacterIndex& startIndex, CharacterIndex& endIndex )
1481 CharacterIndex hitCharacter = GetClosestCursorIndex( visualX, visualY );
1482 DALI_ASSERT_DEBUG( hitCharacter <= mLogicalModel->mText.Count() && "GetClosestCursorIndex returned out of bounds index" );
1484 if( mLogicalModel->mText.Count() == 0 )
1486 return; // if model empty
1489 if( hitCharacter >= mLogicalModel->mText.Count() )
1491 // Closest hit character is the last character.
1492 if( hitCharacter == mLogicalModel->mText.Count() )
1494 hitCharacter--; //Hit character index set to last character in logical model
1498 // hitCharacter is out of bounds
1503 startIndex = hitCharacter;
1504 endIndex = hitCharacter;
1505 bool isHitCharacterWhitespace = TextAbstraction::IsWhiteSpace( mLogicalModel->mText[hitCharacter] );
1507 // Find the start and end of the text
1508 for( startIndex = hitCharacter; startIndex > 0; --startIndex )
1510 if( isHitCharacterWhitespace != TextAbstraction::IsWhiteSpace( mLogicalModel->mText[ startIndex-1 ] ) )
1515 const CharacterIndex pastTheEnd = mLogicalModel->mText.Count();
1516 for( endIndex = hitCharacter + 1u; endIndex < pastTheEnd; ++endIndex )
1518 if( isHitCharacterWhitespace != TextAbstraction::IsWhiteSpace( mLogicalModel->mText[ endIndex ] ) )
1525 CharacterIndex Controller::Impl::GetClosestCursorIndex( float visualX,
1528 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "GetClosestCursorIndex %p closest visualX %f visualY %f\n", this, visualX, visualY );
1530 if( NULL == mEventData )
1532 // Nothing to do if there is no text input.
1536 CharacterIndex logicalIndex = 0u;
1538 const Length numberOfGlyphs = mVisualModel->mGlyphs.Count();
1539 const Length numberOfLines = mVisualModel->mLines.Count();
1540 if( ( 0 == numberOfGlyphs ) ||
1541 ( 0 == numberOfLines ) )
1543 return logicalIndex;
1546 // Find which line is closest
1547 const LineIndex lineIndex = GetClosestLine( visualY );
1548 const LineRun& line = mVisualModel->mLines[lineIndex];
1550 // Get the positions of the glyphs.
1551 const Vector<Vector2>& positions = mVisualModel->mGlyphPositions;
1552 const Vector2* const positionsBuffer = positions.Begin();
1554 // Get the visual to logical conversion tables.
1555 const CharacterIndex* const visualToLogicalBuffer = ( 0u != mLogicalModel->mVisualToLogicalMap.Count() ) ? mLogicalModel->mVisualToLogicalMap.Begin() : NULL;
1556 const CharacterIndex* const visualToLogicalCursorBuffer = mLogicalModel->mVisualToLogicalCursorMap.Begin();
1558 // Get the character to glyph conversion table.
1559 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
1561 // Get the glyphs per character table.
1562 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
1564 // If the vector is void, there is no right to left characters.
1565 const bool hasRightToLeftCharacters = NULL != visualToLogicalBuffer;
1567 const CharacterIndex startCharacter = line.characterRun.characterIndex;
1568 const CharacterIndex endCharacter = line.characterRun.characterIndex + line.characterRun.numberOfCharacters;
1569 DALI_ASSERT_DEBUG( endCharacter <= mLogicalModel->mText.Count() && "Invalid line info" );
1571 // Whether there is a hit on a glyph.
1572 bool matched = false;
1574 // Traverses glyphs in visual order. To do that use the visual to logical conversion table.
1575 CharacterIndex visualIndex = startCharacter;
1576 Length numberOfCharacters = 0u;
1577 for( ; !matched && ( visualIndex < endCharacter ); ++visualIndex )
1579 // The character in logical order.
1580 const CharacterIndex characterLogicalOrderIndex = hasRightToLeftCharacters ? *( visualToLogicalBuffer + visualIndex ) : visualIndex;
1582 // Get the script of the character.
1583 const Script script = mLogicalModel->GetScript( characterLogicalOrderIndex );
1585 // The number of glyphs for that character
1586 const Length numberOfGlyphs = *( glyphsPerCharacterBuffer + characterLogicalOrderIndex );
1587 ++numberOfCharacters;
1590 if( 0u != numberOfGlyphs )
1592 // Get the first character/glyph of the group of glyphs.
1593 const CharacterIndex firstVisualCharacterIndex = 1u + visualIndex - numberOfCharacters;
1594 const CharacterIndex firstLogicalCharacterIndex = hasRightToLeftCharacters ? *( visualToLogicalBuffer + firstVisualCharacterIndex ) : firstVisualCharacterIndex;
1595 const GlyphIndex firstLogicalGlyphIndex = *( charactersToGlyphBuffer + firstLogicalCharacterIndex );
1597 // Get the metrics for the group of glyphs.
1598 GlyphMetrics glyphMetrics;
1599 GetGlyphsMetrics( firstLogicalGlyphIndex,
1605 // Get the position of the first glyph.
1606 const Vector2& position = *( positionsBuffer + firstLogicalGlyphIndex );
1608 // Whether the glyph can be split, like Latin ligatures fi, ff or Arabic ﻻ.
1609 const bool isInterglyphIndex = ( numberOfCharacters > numberOfGlyphs ) && HasLigatureMustBreak( script );
1610 const Length numberOfBlocks = isInterglyphIndex ? numberOfCharacters : 1u;
1611 const float glyphAdvance = glyphMetrics.advance / static_cast<float>( numberOfBlocks );
1613 GlyphIndex index = 0u;
1614 for( ; !matched && ( index < numberOfBlocks ); ++index )
1616 // Find the mid-point of the area containing the glyph
1617 const float glyphCenter = -glyphMetrics.xBearing + position.x + ( static_cast<float>( index ) + 0.5f ) * glyphAdvance;
1619 if( visualX < glyphCenter )
1628 visualIndex = firstVisualCharacterIndex + index;
1632 numberOfCharacters = 0u;
1638 // Return the logical position of the cursor in characters.
1642 visualIndex = endCharacter;
1645 logicalIndex = hasRightToLeftCharacters ? *( visualToLogicalCursorBuffer + visualIndex ) : visualIndex;
1646 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "%p closest visualIndex %d logicalIndex %d\n", this, visualIndex, logicalIndex );
1648 DALI_ASSERT_DEBUG( ( logicalIndex <= mLogicalModel->mText.Count() && logicalIndex >= 0 ) && "GetClosestCursorIndex - Out of bounds index" );
1650 return logicalIndex;
1653 void Controller::Impl::GetCursorPosition( CharacterIndex logical,
1654 CursorInfo& cursorInfo )
1656 // TODO: Check for multiline with \n, etc...
1658 const Length numberOfCharacters = mLogicalModel->mText.Count();
1659 if( !IsShowingRealText() )
1661 // Do not want to use the place-holder text to set the cursor position.
1663 // Use the line's height of the font's family set to set the cursor's size.
1664 // If there is no font's family set, use the default font.
1665 // Use the current alignment to place the cursor at the beginning, center or end of the box.
1667 cursorInfo.lineOffset = 0.f;
1668 cursorInfo.lineHeight = GetDefaultFontLineHeight();
1669 cursorInfo.primaryCursorHeight = cursorInfo.lineHeight;
1671 switch( mLayoutEngine.GetHorizontalAlignment() )
1673 case LayoutEngine::HORIZONTAL_ALIGN_BEGIN:
1675 cursorInfo.primaryPosition.x = 0.f;
1678 case LayoutEngine::HORIZONTAL_ALIGN_CENTER:
1680 cursorInfo.primaryPosition.x = floorf( 0.5f * mVisualModel->mControlSize.width );
1683 case LayoutEngine::HORIZONTAL_ALIGN_END:
1685 cursorInfo.primaryPosition.x = mVisualModel->mControlSize.width - mEventData->mDecorator->GetCursorWidth();
1690 switch( mLayoutEngine.GetVerticalAlignment() )
1692 case LayoutEngine::VERTICAL_ALIGN_TOP:
1694 cursorInfo.primaryPosition.y = 0.f;
1697 case LayoutEngine::VERTICAL_ALIGN_CENTER:
1699 cursorInfo.primaryPosition.y = floorf( 0.5f * ( mVisualModel->mControlSize.height - cursorInfo.lineHeight ) );
1702 case LayoutEngine::VERTICAL_ALIGN_BOTTOM:
1704 cursorInfo.primaryPosition.y = mVisualModel->mControlSize.height - cursorInfo.lineHeight;
1709 // Nothing else to do.
1713 // Check if the logical position is the first or the last one of the text.
1714 const bool isFirstPosition = 0u == logical;
1715 const bool isLastPosition = numberOfCharacters == logical;
1717 // 'logical' is the logical 'cursor' index.
1718 // Get the next and current logical 'character' index.
1719 const CharacterIndex nextCharacterIndex = logical;
1720 const CharacterIndex characterIndex = isFirstPosition ? logical : logical - 1u;
1722 // Get the direction of the character and the next one.
1723 const CharacterDirection* const modelCharacterDirectionsBuffer = ( 0u != mLogicalModel->mCharacterDirections.Count() ) ? mLogicalModel->mCharacterDirections.Begin() : NULL;
1725 CharacterDirection isCurrentRightToLeft = false;
1726 CharacterDirection isNextRightToLeft = false;
1727 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
1729 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + characterIndex );
1730 isNextRightToLeft = *( modelCharacterDirectionsBuffer + nextCharacterIndex );
1733 // Get the line where the character is laid-out.
1734 const LineRun* const modelLines = mVisualModel->mLines.Begin();
1736 const LineIndex lineIndex = mVisualModel->GetLineOfCharacter( characterIndex );
1737 const LineRun& line = *( modelLines + lineIndex );
1739 // Get the paragraph's direction.
1740 const CharacterDirection isRightToLeftParagraph = line.direction;
1742 // Check whether there is an alternative position:
1744 cursorInfo.isSecondaryCursor = ( !isLastPosition && ( isCurrentRightToLeft != isNextRightToLeft ) ) ||
1745 ( isLastPosition && ( isRightToLeftParagraph != isCurrentRightToLeft ) );
1747 // Set the line offset and height.
1748 cursorInfo.lineOffset = 0.f;
1749 cursorInfo.lineHeight = line.ascender + -line.descender;
1751 // Calculate the primary cursor.
1753 CharacterIndex index = characterIndex;
1754 if( cursorInfo.isSecondaryCursor )
1756 // If there is a secondary position, the primary cursor may be in a different place than the logical index.
1758 if( isLastPosition )
1760 // The position of the cursor after the last character needs special
1761 // care depending on its direction and the direction of the paragraph.
1763 // Need to find the first character after the last character with the paragraph's direction.
1764 // i.e l0 l1 l2 r0 r1 should find r0.
1766 // TODO: check for more than one line!
1767 index = isRightToLeftParagraph ? line.characterRun.characterIndex : line.characterRun.characterIndex + line.characterRun.numberOfCharacters - 1u;
1768 index = mLogicalModel->GetLogicalCharacterIndex( index );
1772 index = ( isRightToLeftParagraph == isCurrentRightToLeft ) ? characterIndex : nextCharacterIndex;
1776 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
1777 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
1778 const Length* const charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
1779 const CharacterIndex* const glyphsToCharactersBuffer = mVisualModel->mGlyphsToCharacters.Begin();
1780 const Vector2* const glyphPositionsBuffer = mVisualModel->mGlyphPositions.Begin();
1782 // Convert the cursor position into the glyph position.
1783 const GlyphIndex primaryGlyphIndex = *( charactersToGlyphBuffer + index );
1784 const Length primaryNumberOfGlyphs = *( glyphsPerCharacterBuffer + index );
1785 const Length primaryNumberOfCharacters = *( charactersPerGlyphBuffer + primaryGlyphIndex );
1787 // Get the metrics for the group of glyphs.
1788 GlyphMetrics glyphMetrics;
1789 GetGlyphsMetrics( primaryGlyphIndex,
1790 primaryNumberOfGlyphs,
1795 // Whether to add the glyph's advance to the cursor position.
1796 // 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,
1797 // if the logical cursor is one, the position is the position of the first glyph and the advance is added.
1798 // A 'truth table' was build and an online Karnaugh map tool was used to simplify the logic.
1819 // Where F -> isFirstPosition
1820 // L -> isLastPosition
1821 // C -> isCurrentRightToLeft
1822 // P -> isRightToLeftParagraph
1823 // A -> Whether to add the glyph's advance.
1825 const bool addGlyphAdvance = ( ( isLastPosition && !isRightToLeftParagraph ) ||
1826 ( isFirstPosition && isRightToLeftParagraph ) ||
1827 ( !isFirstPosition && !isLastPosition && !isCurrentRightToLeft ) );
1829 float glyphAdvance = addGlyphAdvance ? glyphMetrics.advance : 0.f;
1831 if( !isLastPosition &&
1832 ( primaryNumberOfCharacters > 1u ) )
1834 const CharacterIndex firstIndex = *( glyphsToCharactersBuffer + primaryGlyphIndex );
1836 bool isCurrentRightToLeft = false;
1837 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
1839 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + index );
1842 Length numberOfGlyphAdvance = ( isFirstPosition ? 0u : 1u ) + characterIndex - firstIndex;
1843 if( isCurrentRightToLeft )
1845 numberOfGlyphAdvance = primaryNumberOfCharacters - numberOfGlyphAdvance;
1848 glyphAdvance = static_cast<float>( numberOfGlyphAdvance ) * glyphMetrics.advance / static_cast<float>( primaryNumberOfCharacters );
1851 // Get the glyph position and x bearing.
1852 const Vector2& primaryPosition = *( glyphPositionsBuffer + primaryGlyphIndex );
1854 // Set the primary cursor's height.
1855 cursorInfo.primaryCursorHeight = cursorInfo.isSecondaryCursor ? 0.5f * glyphMetrics.fontHeight : glyphMetrics.fontHeight;
1857 // Set the primary cursor's position.
1858 cursorInfo.primaryPosition.x = -glyphMetrics.xBearing + primaryPosition.x + glyphAdvance;
1859 cursorInfo.primaryPosition.y = line.ascender - glyphMetrics.ascender;
1861 // Calculate the secondary cursor.
1863 if( cursorInfo.isSecondaryCursor )
1865 // Set the secondary cursor's height.
1866 cursorInfo.secondaryCursorHeight = 0.5f * glyphMetrics.fontHeight;
1868 CharacterIndex index = characterIndex;
1869 if( !isLastPosition )
1871 index = ( isRightToLeftParagraph == isCurrentRightToLeft ) ? nextCharacterIndex : characterIndex;
1874 const GlyphIndex secondaryGlyphIndex = *( charactersToGlyphBuffer + index );
1875 const Length secondaryNumberOfGlyphs = *( glyphsPerCharacterBuffer + index );
1877 const Vector2& secondaryPosition = *( glyphPositionsBuffer + secondaryGlyphIndex );
1879 GetGlyphsMetrics( secondaryGlyphIndex,
1880 secondaryNumberOfGlyphs,
1885 // Set the secondary cursor's position.
1886 cursorInfo.secondaryPosition.x = -glyphMetrics.xBearing + secondaryPosition.x + ( isCurrentRightToLeft ? 0.f : glyphMetrics.advance );
1887 cursorInfo.secondaryPosition.y = cursorInfo.lineHeight - cursorInfo.secondaryCursorHeight - line.descender - ( glyphMetrics.fontHeight - glyphMetrics.ascender );
1891 CharacterIndex Controller::Impl::CalculateNewCursorIndex( CharacterIndex index ) const
1893 if( NULL == mEventData )
1895 // Nothing to do if there is no text input.
1899 CharacterIndex cursorIndex = mEventData->mPrimaryCursorPosition;
1901 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
1902 const Length* const charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
1904 GlyphIndex glyphIndex = *( charactersToGlyphBuffer + index );
1905 Length numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
1907 if( numberOfCharacters > 1u )
1909 const Script script = mLogicalModel->GetScript( index );
1910 if( HasLigatureMustBreak( script ) )
1912 // Prevents to jump the whole Latin ligatures like fi, ff, or Arabic ﻻ, ...
1913 numberOfCharacters = 1u;
1918 while( 0u == numberOfCharacters )
1921 numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
1925 if( index < mEventData->mPrimaryCursorPosition )
1927 cursorIndex -= numberOfCharacters;
1931 cursorIndex += numberOfCharacters;
1937 void Controller::Impl::UpdateCursorPosition( const CursorInfo& cursorInfo )
1939 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "-->Controller::UpdateCursorPosition %p\n", this );
1940 if( NULL == mEventData )
1942 // Nothing to do if there is no text input.
1943 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition no event data\n" );
1947 const Vector2 offset = mEventData->mScrollPosition + ( IsShowingRealText() ? mAlignmentOffset : Vector2::ZERO );
1948 const Vector2 cursorPosition = cursorInfo.primaryPosition + offset;
1950 // Sets the cursor position.
1951 mEventData->mDecorator->SetPosition( PRIMARY_CURSOR,
1954 cursorInfo.primaryCursorHeight,
1955 cursorInfo.lineHeight );
1956 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Primary cursor position: %f,%f\n", cursorPosition.x, cursorPosition.y );
1958 // Sets the grab handle position.
1959 mEventData->mDecorator->SetPosition( GRAB_HANDLE,
1961 cursorInfo.lineOffset + offset.y,
1962 cursorInfo.lineHeight );
1964 if( cursorInfo.isSecondaryCursor )
1966 mEventData->mDecorator->SetPosition( SECONDARY_CURSOR,
1967 cursorInfo.secondaryPosition.x + offset.x,
1968 cursorInfo.secondaryPosition.y + offset.y,
1969 cursorInfo.secondaryCursorHeight,
1970 cursorInfo.lineHeight );
1971 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Secondary cursor position: %f,%f\n", cursorInfo.secondaryPosition.x + offset.x, cursorInfo.secondaryPosition.y + offset.y );
1974 // Set which cursors are active according the state.
1975 if( EventData::IsEditingState( mEventData->mState ) || ( EventData::GRAB_HANDLE_PANNING == mEventData->mState ) )
1977 if( cursorInfo.isSecondaryCursor )
1979 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_BOTH );
1983 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1988 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
1991 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition\n" );
1994 void Controller::Impl::UpdateSelectionHandle( HandleType handleType,
1995 const CursorInfo& cursorInfo )
1997 if( ( LEFT_SELECTION_HANDLE != handleType ) &&
1998 ( RIGHT_SELECTION_HANDLE != handleType ) )
2003 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
2004 const Vector2 cursorPosition = cursorInfo.primaryPosition + offset;
2006 // Sets the handle's position.
2007 mEventData->mDecorator->SetPosition( handleType,
2009 cursorInfo.lineOffset + offset.y,
2010 cursorInfo.lineHeight );
2012 // If selection handle at start of the text and other at end of the text then all text is selected.
2013 const CharacterIndex startOfSelection = std::min( mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition );
2014 const CharacterIndex endOfSelection = std::max ( mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition );
2015 mEventData->mAllTextSelected = ( startOfSelection == 0 ) && ( endOfSelection == mLogicalModel->mText.Count() );
2018 void Controller::Impl::ClampHorizontalScroll( const Vector2& actualSize )
2020 // Clamp between -space & 0 (and the text alignment).
2022 if( actualSize.width > mVisualModel->mControlSize.width )
2024 const float space = ( actualSize.width - mVisualModel->mControlSize.width ) + mAlignmentOffset.x;
2025 mEventData->mScrollPosition.x = ( mEventData->mScrollPosition.x < -space ) ? -space : mEventData->mScrollPosition.x;
2026 mEventData->mScrollPosition.x = ( mEventData->mScrollPosition.x > -mAlignmentOffset.x ) ? -mAlignmentOffset.x : mEventData->mScrollPosition.x;
2028 mEventData->mDecoratorUpdated = true;
2032 mEventData->mScrollPosition.x = 0.f;
2036 void Controller::Impl::ClampVerticalScroll( const Vector2& actualSize )
2038 // Clamp between -space & 0 (and the text alignment).
2039 if( actualSize.height > mVisualModel->mControlSize.height )
2041 const float space = ( actualSize.height - mVisualModel->mControlSize.height ) + mAlignmentOffset.y;
2042 mEventData->mScrollPosition.y = ( mEventData->mScrollPosition.y < -space ) ? -space : mEventData->mScrollPosition.y;
2043 mEventData->mScrollPosition.y = ( mEventData->mScrollPosition.y > -mAlignmentOffset.y ) ? -mAlignmentOffset.y : mEventData->mScrollPosition.y;
2045 mEventData->mDecoratorUpdated = true;
2049 mEventData->mScrollPosition.y = 0.f;
2053 void Controller::Impl::ScrollToMakePositionVisible( const Vector2& position )
2055 // position is in actor's coords.
2056 const float positionEnd = position.x + ( mEventData->mDecorator ? mEventData->mDecorator->GetCursorWidth() : 0.f );
2058 // Transform the position to decorator coords.
2059 const float offset = mEventData->mScrollPosition.x + mAlignmentOffset.x;
2060 const float decoratorPositionBegin = position.x + offset;
2061 const float decoratorPositionEnd = positionEnd + offset;
2063 if( decoratorPositionBegin < 0.f )
2065 mEventData->mScrollPosition.x = -position.x - mAlignmentOffset.x;
2067 else if( decoratorPositionEnd > mVisualModel->mControlSize.width )
2069 mEventData->mScrollPosition.x = mVisualModel->mControlSize.width - positionEnd - mAlignmentOffset.x;
2073 void Controller::Impl::ScrollTextToMatchCursor( const CursorInfo& cursorInfo )
2075 // Get the current cursor position in decorator coords.
2076 const Vector2& currentCursorPosition = mEventData->mDecorator->GetPosition( PRIMARY_CURSOR );
2078 // Calculate the offset to match the cursor position before the character was deleted.
2079 mEventData->mScrollPosition.x = currentCursorPosition.x - cursorInfo.primaryPosition.x - mAlignmentOffset.x;
2081 ClampHorizontalScroll( mVisualModel->GetActualSize() );
2084 void Controller::Impl::RequestRelayout()
2086 mControlInterface.RequestTextRelayout();
2091 } // namespace Toolkit