2 * Copyright (c) 2015 Samsung Electronics Co., Ltd.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
19 #include <dali-toolkit/internal/text/text-controller-impl.h>
22 #include <dali/public-api/adaptor-framework/key.h>
23 #include <dali/integration-api/debug.h>
26 #include <dali-toolkit/internal/text/bidirectional-support.h>
27 #include <dali-toolkit/internal/text/character-set-conversion.h>
28 #include <dali-toolkit/internal/text/color-segmentation.h>
29 #include <dali-toolkit/internal/text/multi-language-support.h>
30 #include <dali-toolkit/internal/text/segmentation.h>
31 #include <dali-toolkit/internal/text/shaper.h>
36 #if defined(DEBUG_ENABLED)
37 Debug::Filter* gLogFilter = Debug::Filter::New(Debug::NoLogging, true, "LOG_TEXT_CONTROLS");
41 * @brief Some characters can be shaped in more than one glyph.
42 * This struct is used to retrieve metrics from these group of glyphs.
56 float fontHeight; ///< The font's height of that glyphs.
57 float advance; ///< The sum of all the advances of all the glyphs.
58 float ascender; ///< The font's ascender.
59 float xBearing; ///< The x bearing of the first glyph.
74 * @brief Get some glyph's metrics of a group of glyphs formed as a result of shaping one character.
76 * @param[in] glyphIndex The index to the first glyph.
77 * @param[in] numberOfGlyphs The number of glyphs.
78 * @param[out] glyphMetrics Some glyph metrics (font height, advance, ascender and x bearing).
79 * @param[in] visualModel The visual model.
80 * @param[in] metrics Used to access metrics from FontClient.
82 void GetGlyphsMetrics( GlyphIndex glyphIndex,
83 Length numberOfGlyphs,
84 GlyphMetrics& glyphMetrics,
85 VisualModelPtr& visualModel,
88 const GlyphInfo* glyphsBuffer = visualModel->mGlyphs.Begin();
90 const GlyphInfo& firstGlyph = *( glyphsBuffer + glyphIndex );
92 Text::FontMetrics fontMetrics;
93 metrics->GetFontMetrics( firstGlyph.fontId, fontMetrics );
95 glyphMetrics.fontHeight = fontMetrics.height;
96 glyphMetrics.advance = firstGlyph.advance;
97 glyphMetrics.ascender = fontMetrics.ascender;
98 glyphMetrics.xBearing = firstGlyph.xBearing;
100 for( unsigned int i = 1u; i < numberOfGlyphs; ++i )
102 const GlyphInfo& glyphInfo = *( glyphsBuffer + glyphIndex + i );
104 glyphMetrics.advance += glyphInfo.advance;
108 EventData::EventData( DecoratorPtr decorator )
109 : mDecorator( decorator ),
111 mPlaceholderTextActive(),
112 mPlaceholderTextInactive(),
113 mPlaceholderTextColor( 0.8f, 0.8f, 0.8f, 0.8f ),
117 mPrimaryCursorPosition( 0u ),
118 mLeftSelectionPosition( 0u ),
119 mRightSelectionPosition( 0u ),
120 mPreEditStartPosition( 0u ),
121 mPreEditLength( 0u ),
122 mIsShowingPlaceholderText( false ),
123 mPreEditFlag( false ),
124 mDecoratorUpdated( false ),
125 mCursorBlinkEnabled( true ),
126 mGrabHandleEnabled( true ),
127 mGrabHandlePopupEnabled( true ),
128 mSelectionEnabled( true ),
129 mHorizontalScrollingEnabled( true ),
130 mVerticalScrollingEnabled( false ),
131 mUpdateCursorPosition( false ),
132 mUpdateLeftSelectionPosition( false ),
133 mUpdateRightSelectionPosition( false ),
134 mScrollAfterUpdatePosition( false ),
135 mScrollAfterDelete( false ),
136 mAllTextSelected( false ),
137 mUpdateInputStyle( false )
139 mImfManager = ImfManager::Get();
142 EventData::~EventData()
145 bool Controller::Impl::ProcessInputEvents()
147 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "-->Controller::ProcessInputEvents\n" );
148 if( NULL == mEventData )
150 // Nothing to do if there is no text input.
151 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::ProcessInputEvents no event data\n" );
155 if( mEventData->mDecorator )
157 for( std::vector<Event>::iterator iter = mEventData->mEventQueue.begin();
158 iter != mEventData->mEventQueue.end();
163 case Event::CURSOR_KEY_EVENT:
165 OnCursorKeyEvent( *iter );
168 case Event::TAP_EVENT:
173 case Event::LONG_PRESS_EVENT:
175 OnLongPressEvent( *iter );
178 case Event::PAN_EVENT:
183 case Event::GRAB_HANDLE_EVENT:
184 case Event::LEFT_SELECTION_HANDLE_EVENT:
185 case Event::RIGHT_SELECTION_HANDLE_EVENT: // Fall through
187 OnHandleEvent( *iter );
192 OnSelectEvent( *iter );
195 case Event::SELECT_ALL:
204 // The cursor must also be repositioned after inserts into the model
205 if( mEventData->mUpdateCursorPosition )
207 // Updates the cursor position and scrolls the text to make it visible.
208 CursorInfo cursorInfo;
209 GetCursorPosition( mEventData->mPrimaryCursorPosition,
212 if( mEventData->mScrollAfterUpdatePosition )
214 ScrollToMakePositionVisible( cursorInfo.primaryPosition );
215 mEventData->mScrollAfterUpdatePosition = false;
217 else if( mEventData->mScrollAfterDelete )
219 ScrollTextToMatchCursor( cursorInfo );
220 mEventData->mScrollAfterDelete = false;
223 UpdateCursorPosition( cursorInfo );
225 mEventData->mDecoratorUpdated = true;
226 mEventData->mUpdateCursorPosition = false;
230 bool leftScroll = false;
231 bool rightScroll = false;
233 CursorInfo leftHandleInfo;
234 CursorInfo rightHandleInfo;
236 if( mEventData->mUpdateLeftSelectionPosition )
238 GetCursorPosition( mEventData->mLeftSelectionPosition,
241 if( mEventData->mScrollAfterUpdatePosition )
243 ScrollToMakePositionVisible( leftHandleInfo.primaryPosition );
248 if( mEventData->mUpdateRightSelectionPosition )
250 GetCursorPosition( mEventData->mRightSelectionPosition,
253 if( mEventData->mScrollAfterUpdatePosition )
255 ScrollToMakePositionVisible( rightHandleInfo.primaryPosition );
260 if( mEventData->mUpdateLeftSelectionPosition )
262 UpdateSelectionHandle( LEFT_SELECTION_HANDLE,
266 mEventData->mDecoratorUpdated = true;
269 if( mEventData->mUpdateRightSelectionPosition )
271 UpdateSelectionHandle( RIGHT_SELECTION_HANDLE,
275 mEventData->mDecoratorUpdated = true;
278 if( mEventData->mUpdateLeftSelectionPosition || mEventData->mUpdateRightSelectionPosition )
280 RepositionSelectionHandles();
282 mEventData->mUpdateLeftSelectionPosition = false;
283 mEventData->mUpdateRightSelectionPosition = false;
286 if( leftScroll || rightScroll )
288 mEventData->mScrollAfterUpdatePosition = false;
292 if( mEventData->mUpdateInputStyle )
294 // Set the default style first.
295 RetrieveDefaultInputStyle( mEventData->mInputStyle );
297 // Get the character index from the cursor index.
298 const CharacterIndex styleIndex = ( mEventData->mPrimaryCursorPosition > 0u ) ? mEventData->mPrimaryCursorPosition - 1u : 0u;
300 // Retrieve the style from the style runs stored in the logical model.
301 mLogicalModel->RetrieveStyle( styleIndex, mEventData->mInputStyle );
303 mEventData->mUpdateInputStyle = false;
306 mEventData->mEventQueue.clear();
308 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::ProcessInputEvents\n" );
310 const bool decoratorUpdated = mEventData->mDecoratorUpdated;
311 mEventData->mDecoratorUpdated = false;
313 return decoratorUpdated;
316 void Controller::Impl::UpdateModel( OperationsMask operationsRequired )
318 DALI_LOG_INFO( gLogFilter, Debug::General, "Controller::UpdateModel\n" );
320 // Calculate the operations to be done.
321 const OperationsMask operations = static_cast<OperationsMask>( mOperationsPending & operationsRequired );
323 Vector<Character>& utf32Characters = mLogicalModel->mText;
325 const Length numberOfCharacters = utf32Characters.Count();
327 Vector<LineBreakInfo>& lineBreakInfo = mLogicalModel->mLineBreakInfo;
328 CharacterIndex startIndex = 0u;
329 Length requestedNumberOfCharacters = numberOfCharacters;
330 if( GET_LINE_BREAKS & operations )
332 // Retrieves the line break info. The line break info is used to split the text in 'paragraphs' to
333 // calculate the bidirectional info for each 'paragraph'.
334 // It's also used to layout the text (where it should be a new line) or to shape the text (text in different lines
335 // is not shaped together).
336 lineBreakInfo.Resize( numberOfCharacters, TextAbstraction::LINE_NO_BREAK );
338 SetLineBreakInfo( utf32Characters,
342 Vector<WordBreakInfo>& wordBreakInfo = mLogicalModel->mWordBreakInfo;
343 if( GET_WORD_BREAKS & operations )
345 // Retrieves the word break info. The word break info is used to layout the text (where to wrap the text in lines).
346 wordBreakInfo.Resize( numberOfCharacters, TextAbstraction::WORD_NO_BREAK );
348 SetWordBreakInfo( utf32Characters,
350 requestedNumberOfCharacters,
354 const bool getScripts = GET_SCRIPTS & operations;
355 const bool validateFonts = VALIDATE_FONTS & operations;
357 Vector<ScriptRun>& scripts = mLogicalModel->mScriptRuns;
358 Vector<FontRun>& validFonts = mLogicalModel->mFontRuns;
360 if( getScripts || validateFonts )
362 // Validates the fonts assigned by the application or assigns default ones.
363 // It makes sure all the characters are going to be rendered by the correct font.
364 MultilanguageSupport multilanguageSupport = MultilanguageSupport::Get();
368 // Retrieves the scripts used in the text.
369 multilanguageSupport.SetScripts( utf32Characters,
371 requestedNumberOfCharacters,
377 // Validate the fonts set through the mark-up string.
378 Vector<FontDescriptionRun>& fontDescriptionRuns = mLogicalModel->mFontDescriptionRuns;
380 // Get the default font id.
381 const FontId defaultFontId = ( NULL == mFontDefaults ) ? 0u : mFontDefaults->GetFontId( mFontClient );
383 // Validates the fonts. If there is a character with no assigned font it sets a default one.
384 // After this call, fonts are validated.
385 multilanguageSupport.ValidateFonts( utf32Characters,
390 requestedNumberOfCharacters,
395 Vector<Character> mirroredUtf32Characters;
396 bool textMirrored = false;
397 Length numberOfParagraphs = 0u;
398 if( BIDI_INFO & operations )
400 // Count the number of LINE_NO_BREAK to reserve some space for the vector of paragraph's
401 // bidirectional info.
403 const TextAbstraction::LineBreakInfo* lineBreakInfoBuffer = lineBreakInfo.Begin();
404 for( Length index = 0u; index < numberOfCharacters; ++index )
406 if( TextAbstraction::LINE_NO_BREAK == *( lineBreakInfoBuffer + index ) )
408 ++numberOfParagraphs;
412 Vector<BidirectionalParagraphInfoRun>& bidirectionalInfo = mLogicalModel->mBidirectionalParagraphInfo;
413 bidirectionalInfo.Reserve( numberOfParagraphs );
415 // Calculates the bidirectional info for the whole paragraph if it contains right to left scripts.
416 SetBidirectionalInfo( utf32Characters,
420 requestedNumberOfCharacters,
423 if( 0u != bidirectionalInfo.Count() )
425 // Only set the character directions if there is right to left characters.
426 Vector<CharacterDirection>& directions = mLogicalModel->mCharacterDirections;
427 GetCharactersDirection( bidirectionalInfo,
430 requestedNumberOfCharacters,
433 // This paragraph has right to left text. Some characters may need to be mirrored.
434 // TODO: consider if the mirrored string can be stored as well.
436 textMirrored = GetMirroredText( utf32Characters,
440 requestedNumberOfCharacters,
441 mirroredUtf32Characters );
445 // There is no right to left characters. Clear the directions vector.
446 mLogicalModel->mCharacterDirections.Clear();
450 Vector<GlyphInfo>& glyphs = mVisualModel->mGlyphs;
451 Vector<CharacterIndex>& glyphsToCharactersMap = mVisualModel->mGlyphsToCharacters;
452 Vector<Length>& charactersPerGlyph = mVisualModel->mCharactersPerGlyph;
453 Vector<GlyphIndex> newParagraphGlyphs;
454 newParagraphGlyphs.Reserve( numberOfParagraphs );
456 GlyphIndex startGlyphIndex = 0u;
457 if( SHAPE_TEXT & operations )
459 const Vector<Character>& textToShape = textMirrored ? mirroredUtf32Characters : utf32Characters;
461 ShapeText( textToShape,
467 requestedNumberOfCharacters,
469 glyphsToCharactersMap,
471 newParagraphGlyphs );
473 // Create the 'number of glyphs' per character and the glyph to character conversion tables.
474 mVisualModel->CreateGlyphsPerCharacterTable( startIndex, numberOfCharacters );
475 mVisualModel->CreateCharacterToGlyphTable( startIndex, numberOfCharacters );
478 const Length numberOfGlyphs = glyphs.Count();
480 if( GET_GLYPH_METRICS & operations )
482 GlyphInfo* glyphsBuffer = glyphs.Begin() + startGlyphIndex;
483 mMetrics->GetGlyphMetrics( glyphsBuffer, numberOfGlyphs );
485 // Update the width and advance of all new paragraph characters.
486 for( Vector<GlyphIndex>::ConstIterator it = newParagraphGlyphs.Begin(), endIt = newParagraphGlyphs.End(); it != endIt; ++it )
488 const GlyphIndex index = *it;
489 GlyphInfo& glyph = *( glyphsBuffer + index );
491 glyph.xBearing = 0.f;
497 if( ( NULL != mEventData ) &&
498 mEventData->mPreEditFlag &&
499 ( 0u != mVisualModel->mCharactersToGlyph.Count() ) )
501 // Add the underline for the pre-edit text.
502 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
503 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
505 const GlyphIndex glyphStart = *( charactersToGlyphBuffer + mEventData->mPreEditStartPosition );
506 const CharacterIndex lastPreEditCharacter = mEventData->mPreEditStartPosition + ( ( mEventData->mPreEditLength > 0u ) ? mEventData->mPreEditLength - 1u : 0u );
507 const Length numberOfGlyphsLastCharacter = *( glyphsPerCharacterBuffer + lastPreEditCharacter );
508 const GlyphIndex glyphEnd = *( charactersToGlyphBuffer + lastPreEditCharacter ) + ( numberOfGlyphsLastCharacter > 1u ? numberOfGlyphsLastCharacter - 1u : 0u );
510 GlyphRun underlineRun;
511 underlineRun.glyphIndex = glyphStart;
512 underlineRun.numberOfGlyphs = 1u + glyphEnd - glyphStart;
514 // TODO: At the moment the underline runs are only for pre-edit.
515 mVisualModel->mUnderlineRuns.PushBack( underlineRun );
519 bool Controller::Impl::UpdateModelStyle( OperationsMask operationsRequired )
521 bool updated = false;
523 if( COLOR & operationsRequired )
525 // Set the color runs in glyphs.
526 SetColorSegmentationInfo( mLogicalModel->mColorRuns,
527 mVisualModel->mCharactersToGlyph,
528 mVisualModel->mGlyphsPerCharacter,
529 mVisualModel->mColorRuns );
537 void Controller::Impl::RetrieveDefaultInputStyle( InputStyle& inputStyle )
539 // Sets the default text's color.
540 inputStyle.textColor = mTextColor;
542 // Sets the default font's family name, weight, width, slant and size.
545 inputStyle.familyName = mFontDefaults->mFontDescription.family;
546 inputStyle.weight = mFontDefaults->mFontDescription.weight;
547 inputStyle.width = mFontDefaults->mFontDescription.width;
548 inputStyle.slant = mFontDefaults->mFontDescription.slant;
549 inputStyle.size = mFontDefaults->mDefaultPointSize;
551 inputStyle.familyDefined = mFontDefaults->familyDefined;
552 inputStyle.weightDefined = mFontDefaults->weightDefined;
553 inputStyle.widthDefined = mFontDefaults->widthDefined;
554 inputStyle.slantDefined = mFontDefaults->slantDefined;
555 inputStyle.sizeDefined = mFontDefaults->sizeDefined;
559 inputStyle.familyName.clear();
560 inputStyle.weight = TextAbstraction::FontWeight::NORMAL;
561 inputStyle.width = TextAbstraction::FontWidth::NORMAL;
562 inputStyle.slant = TextAbstraction::FontSlant::NORMAL;
563 inputStyle.size = 0.f;
565 inputStyle.familyDefined = false;
566 inputStyle.weightDefined = false;
567 inputStyle.widthDefined = false;
568 inputStyle.slantDefined = false;
569 inputStyle.sizeDefined = false;
573 float Controller::Impl::GetDefaultFontLineHeight()
575 FontId defaultFontId = 0u;
576 if( NULL == mFontDefaults )
578 TextAbstraction::FontDescription fontDescription;
579 defaultFontId = mFontClient.GetFontId( fontDescription );
583 defaultFontId = mFontDefaults->GetFontId( mFontClient );
586 Text::FontMetrics fontMetrics;
587 mMetrics->GetFontMetrics( defaultFontId, fontMetrics );
589 return( fontMetrics.ascender - fontMetrics.descender );
592 void Controller::Impl::OnCursorKeyEvent( const Event& event )
594 if( NULL == mEventData )
596 // Nothing to do if there is no text input.
600 int keyCode = event.p1.mInt;
602 if( Dali::DALI_KEY_CURSOR_LEFT == keyCode )
604 if( mEventData->mPrimaryCursorPosition > 0u )
606 mEventData->mPrimaryCursorPosition = CalculateNewCursorIndex( mEventData->mPrimaryCursorPosition - 1u );
609 else if( Dali::DALI_KEY_CURSOR_RIGHT == keyCode )
611 if( mLogicalModel->mText.Count() > mEventData->mPrimaryCursorPosition )
613 mEventData->mPrimaryCursorPosition = CalculateNewCursorIndex( mEventData->mPrimaryCursorPosition );
616 else if( Dali::DALI_KEY_CURSOR_UP == keyCode )
620 else if( Dali::DALI_KEY_CURSOR_DOWN == keyCode )
625 mEventData->mUpdateCursorPosition = true;
626 mEventData->mUpdateInputStyle = true;
627 mEventData->mScrollAfterUpdatePosition = true;
630 void Controller::Impl::OnTapEvent( const Event& event )
632 if( NULL != mEventData )
634 const unsigned int tapCount = event.p1.mUint;
638 if( IsShowingRealText() )
640 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
641 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
643 mEventData->mPrimaryCursorPosition = GetClosestCursorIndex( xPosition,
646 // When the cursor position is changing, delay cursor blinking
647 mEventData->mDecorator->DelayCursorBlink();
651 mEventData->mPrimaryCursorPosition = 0u;
654 mEventData->mUpdateCursorPosition = true;
655 mEventData->mScrollAfterUpdatePosition = true;
656 mEventData->mUpdateInputStyle = true;
658 // Notify the cursor position to the imf manager.
659 if( mEventData->mImfManager )
661 mEventData->mImfManager.SetCursorPosition( mEventData->mPrimaryCursorPosition );
662 mEventData->mImfManager.NotifyCursorPosition();
668 void Controller::Impl::OnPanEvent( const Event& event )
670 if( NULL == mEventData )
672 // Nothing to do if there is no text input.
676 int state = event.p1.mInt;
678 if( Gesture::Started == state ||
679 Gesture::Continuing == state )
681 const Vector2& actualSize = mVisualModel->GetLayoutSize();
682 const Vector2 currentScroll = mEventData->mScrollPosition;
684 if( mEventData->mHorizontalScrollingEnabled )
686 const float displacementX = event.p2.mFloat;
687 mEventData->mScrollPosition.x += displacementX;
689 ClampHorizontalScroll( actualSize );
692 if( mEventData->mVerticalScrollingEnabled )
694 const float displacementY = event.p3.mFloat;
695 mEventData->mScrollPosition.y += displacementY;
697 ClampVerticalScroll( actualSize );
700 if( mEventData->mDecorator )
702 mEventData->mDecorator->UpdatePositions( mEventData->mScrollPosition - currentScroll );
707 void Controller::Impl::OnLongPressEvent( const Event& event )
709 DALI_LOG_INFO( gLogFilter, Debug::General, "Controller::OnLongPressEvent\n" );
711 if( EventData::EDITING == mEventData->mState )
713 ChangeState ( EventData::EDITING_WITH_POPUP );
714 mEventData->mDecoratorUpdated = true;
718 void Controller::Impl::OnHandleEvent( const Event& event )
720 if( NULL == mEventData )
722 // Nothing to do if there is no text input.
726 const unsigned int state = event.p1.mUint;
727 const bool handleStopScrolling = ( HANDLE_STOP_SCROLLING == state );
729 if( HANDLE_PRESSED == state )
731 // The event.p2 and event.p3 are in decorator coords. Need to transforms to text coords.
732 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
733 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
735 const CharacterIndex handleNewPosition = GetClosestCursorIndex( xPosition, yPosition );
737 if( Event::GRAB_HANDLE_EVENT == event.type )
739 ChangeState ( EventData::GRAB_HANDLE_PANNING );
741 if( handleNewPosition != mEventData->mPrimaryCursorPosition )
743 mEventData->mPrimaryCursorPosition = handleNewPosition;
744 mEventData->mUpdateCursorPosition = true;
747 else if( Event::LEFT_SELECTION_HANDLE_EVENT == event.type )
749 ChangeState ( EventData::SELECTION_HANDLE_PANNING );
751 if( ( handleNewPosition != mEventData->mLeftSelectionPosition ) &&
752 ( handleNewPosition != mEventData->mRightSelectionPosition ) )
754 mEventData->mLeftSelectionPosition = handleNewPosition;
756 mEventData->mUpdateLeftSelectionPosition = true;
759 else if( Event::RIGHT_SELECTION_HANDLE_EVENT == event.type )
761 ChangeState ( EventData::SELECTION_HANDLE_PANNING );
763 if( ( handleNewPosition != mEventData->mRightSelectionPosition ) &&
764 ( handleNewPosition != mEventData->mLeftSelectionPosition ) )
766 mEventData->mRightSelectionPosition = handleNewPosition;
768 mEventData->mUpdateRightSelectionPosition = true;
771 } // end ( HANDLE_PRESSED == state )
772 else if( ( HANDLE_RELEASED == state ) ||
773 handleStopScrolling )
775 CharacterIndex handlePosition = 0u;
776 if( handleStopScrolling )
778 // The event.p2 and event.p3 are in decorator coords. Need to transforms to text coords.
779 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
780 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
782 handlePosition = GetClosestCursorIndex( xPosition, yPosition );
785 if( Event::GRAB_HANDLE_EVENT == event.type )
787 mEventData->mUpdateCursorPosition = true;
788 mEventData->mUpdateInputStyle = true;
790 if( !IsClipboardEmpty() )
792 ChangeState( EventData::EDITING_WITH_PASTE_POPUP ); // Moving grabhandle will show Paste Popup
795 if( handleStopScrolling )
797 mEventData->mScrollAfterUpdatePosition = mEventData->mPrimaryCursorPosition != handlePosition;
798 mEventData->mPrimaryCursorPosition = handlePosition;
801 else if( Event::LEFT_SELECTION_HANDLE_EVENT == event.type )
803 ChangeState( EventData::SELECTING );
805 if( handleStopScrolling )
807 mEventData->mUpdateLeftSelectionPosition = ( mEventData->mRightSelectionPosition != handlePosition );
808 mEventData->mScrollAfterUpdatePosition = mEventData->mUpdateLeftSelectionPosition;
810 if( mEventData->mUpdateLeftSelectionPosition )
812 mEventData->mLeftSelectionPosition = handlePosition;
816 else if( Event::RIGHT_SELECTION_HANDLE_EVENT == event.type )
818 ChangeState( EventData::SELECTING );
820 if( handleStopScrolling )
822 mEventData->mUpdateRightSelectionPosition = ( mEventData->mLeftSelectionPosition != handlePosition );
823 mEventData->mScrollAfterUpdatePosition = mEventData->mUpdateRightSelectionPosition;
824 if( mEventData->mUpdateRightSelectionPosition )
826 mEventData->mRightSelectionPosition = handlePosition;
831 mEventData->mDecoratorUpdated = true;
832 } // end ( ( HANDLE_RELEASED == state ) || ( HANDLE_STOP_SCROLLING == state ) )
833 else if( HANDLE_SCROLLING == state )
835 const float xSpeed = event.p2.mFloat;
836 const Vector2& actualSize = mVisualModel->GetLayoutSize();
837 const Vector2 currentScrollPosition = mEventData->mScrollPosition;
839 mEventData->mScrollPosition.x += xSpeed;
841 ClampHorizontalScroll( actualSize );
843 bool endOfScroll = false;
844 if( Vector2::ZERO == ( currentScrollPosition - mEventData->mScrollPosition ) )
846 // Notify the decorator there is no more text to scroll.
847 // The decorator won't send more scroll events.
848 mEventData->mDecorator->NotifyEndOfScroll();
849 // Still need to set the position of the handle.
853 // Set the position of the handle.
854 const bool scrollRightDirection = xSpeed > 0.f;
855 const bool leftSelectionHandleEvent = Event::LEFT_SELECTION_HANDLE_EVENT == event.type;
856 const bool rightSelectionHandleEvent = Event::RIGHT_SELECTION_HANDLE_EVENT == event.type;
858 if( Event::GRAB_HANDLE_EVENT == event.type )
860 ChangeState( EventData::GRAB_HANDLE_PANNING );
862 Vector2 position = mEventData->mDecorator->GetPosition( GRAB_HANDLE );
864 // Position the grag handle close to either the left or right edge.
865 position.x = scrollRightDirection ? 0.f : mVisualModel->mControlSize.width;
867 // Get the new handle position.
868 // The grab handle's position is in decorator coords. Need to transforms to text coords.
869 const CharacterIndex handlePosition = GetClosestCursorIndex( position.x - mEventData->mScrollPosition.x - mAlignmentOffset.x,
870 position.y - mEventData->mScrollPosition.y - mAlignmentOffset.y );
872 mEventData->mUpdateCursorPosition = mEventData->mPrimaryCursorPosition != handlePosition;
873 mEventData->mScrollAfterUpdatePosition = mEventData->mUpdateCursorPosition;
874 mEventData->mPrimaryCursorPosition = handlePosition;
875 mEventData->mUpdateInputStyle = mEventData->mUpdateCursorPosition;
877 else if( leftSelectionHandleEvent || rightSelectionHandleEvent )
879 // TODO: This is recalculating the selection box every time the text is scrolled with the selection handles.
880 // Think if something can be done to save power.
882 ChangeState( EventData::SELECTION_HANDLE_PANNING );
884 Vector2 position = mEventData->mDecorator->GetPosition( leftSelectionHandleEvent ? Text::LEFT_SELECTION_HANDLE : Text::RIGHT_SELECTION_HANDLE );
886 // Position the selection handle close to either the left or right edge.
887 position.x = scrollRightDirection ? 0.f : mVisualModel->mControlSize.width;
889 // Get the new handle position.
890 // The selection handle's position is in decorator coords. Need to transforms to text coords.
891 const CharacterIndex handlePosition = GetClosestCursorIndex( position.x - mEventData->mScrollPosition.x - mAlignmentOffset.x,
892 position.y - mEventData->mScrollPosition.y - mAlignmentOffset.y );
894 if( leftSelectionHandleEvent )
896 const bool differentHandles = ( mEventData->mLeftSelectionPosition != handlePosition ) && ( mEventData->mRightSelectionPosition != handlePosition );
897 mEventData->mUpdateLeftSelectionPosition = endOfScroll || differentHandles;
898 if( differentHandles )
900 mEventData->mLeftSelectionPosition = handlePosition;
905 const bool differentHandles = ( mEventData->mRightSelectionPosition != handlePosition ) && ( mEventData->mLeftSelectionPosition != handlePosition );
906 mEventData->mUpdateRightSelectionPosition = endOfScroll || differentHandles;
907 if( differentHandles )
909 mEventData->mRightSelectionPosition = handlePosition;
913 if( mEventData->mUpdateLeftSelectionPosition || mEventData->mUpdateRightSelectionPosition )
915 RepositionSelectionHandles();
917 mEventData->mScrollAfterUpdatePosition = true;
920 mEventData->mDecoratorUpdated = true;
921 } // end ( HANDLE_SCROLLING == state )
924 void Controller::Impl::OnSelectEvent( const Event& event )
926 if( NULL == mEventData )
928 // Nothing to do if there is no text.
932 if( mEventData->mSelectionEnabled )
934 // The event.p2 and event.p3 are in decorator coords. Need to transforms to text coords.
935 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
936 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
938 // Calculates the logical position from the x,y coords.
939 RepositionSelectionHandles( xPosition,
942 mEventData->mUpdateLeftSelectionPosition = true;
943 mEventData->mUpdateRightSelectionPosition = true;
945 mEventData->mScrollAfterUpdatePosition = ( mEventData->mLeftSelectionPosition != mEventData->mRightSelectionPosition );
949 void Controller::Impl::OnSelectAllEvent()
951 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "OnSelectAllEvent mEventData->mSelectionEnabled%s \n", mEventData->mSelectionEnabled?"true":"false");
953 if( NULL == mEventData )
955 // Nothing to do if there is no text.
959 if( mEventData->mSelectionEnabled )
961 mEventData->mLeftSelectionPosition = 0u;
962 mEventData->mRightSelectionPosition = mLogicalModel->mText.Count();
964 mEventData->mScrollAfterUpdatePosition = true;
965 mEventData->mUpdateLeftSelectionPosition = true;
966 mEventData->mUpdateRightSelectionPosition = true;
970 void Controller::Impl::RetrieveSelection( std::string& selectedText, bool deleteAfterRetrieval )
972 if( mEventData->mLeftSelectionPosition == mEventData->mRightSelectionPosition )
974 // Nothing to select if handles are in the same place.
975 selectedText.clear();
979 const bool handlesCrossed = mEventData->mLeftSelectionPosition > mEventData->mRightSelectionPosition;
981 //Get start and end position of selection
982 const CharacterIndex startOfSelectedText = handlesCrossed ? mEventData->mRightSelectionPosition : mEventData->mLeftSelectionPosition;
983 const Length lengthOfSelectedText = ( handlesCrossed ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition ) - startOfSelectedText;
985 // Validate the start and end selection points
986 if( ( startOfSelectedText + lengthOfSelectedText ) <= mLogicalModel->mText.Count() )
988 //Get text as a UTF8 string
989 Vector<Character>& utf32Characters = mLogicalModel->mText;
991 Utf32ToUtf8( &utf32Characters[startOfSelectedText], lengthOfSelectedText, selectedText );
993 if( deleteAfterRetrieval ) // Only delete text if copied successfully
995 // Set as input style the style of the first deleted character.
996 mLogicalModel->RetrieveStyle( startOfSelectedText, mEventData->mInputStyle );
998 mLogicalModel->UpdateTextStyleRuns( startOfSelectedText, -static_cast<int>( lengthOfSelectedText ) );
1000 // Delete text between handles
1001 Vector<Character>& currentText = mLogicalModel->mText;
1003 Vector<Character>::Iterator first = currentText.Begin() + startOfSelectedText;
1004 Vector<Character>::Iterator last = first + lengthOfSelectedText;
1005 currentText.Erase( first, last );
1007 // Scroll after delete.
1008 mEventData->mPrimaryCursorPosition = handlesCrossed ? mEventData->mRightSelectionPosition : mEventData->mLeftSelectionPosition;
1009 mEventData->mScrollAfterDelete = true;
1011 // Udpade the cursor position and the decorator.
1012 // Scroll after the position is updated if is not scrolling after delete.
1013 mEventData->mUpdateCursorPosition = true;
1014 mEventData->mScrollAfterUpdatePosition = !mEventData->mScrollAfterDelete;
1015 mEventData->mDecoratorUpdated = true;
1019 void Controller::Impl::ShowClipboard()
1023 mClipboard.ShowClipboard();
1027 void Controller::Impl::HideClipboard()
1031 mClipboard.HideClipboard();
1035 bool Controller::Impl::CopyStringToClipboard( std::string& source )
1037 //Send string to clipboard
1038 return ( mClipboard && mClipboard.SetItem( source ) );
1041 void Controller::Impl::SendSelectionToClipboard( bool deleteAfterSending )
1043 std::string selectedText;
1044 RetrieveSelection( selectedText, deleteAfterSending );
1045 CopyStringToClipboard( selectedText );
1046 ChangeState( EventData::EDITING );
1049 void Controller::Impl::GetTextFromClipboard( unsigned int itemIndex, std::string& retrievedString )
1053 retrievedString = mClipboard.GetItem( itemIndex );
1057 void Controller::Impl::RepositionSelectionHandles()
1059 CharacterIndex selectionStart = mEventData->mLeftSelectionPosition;
1060 CharacterIndex selectionEnd = mEventData->mRightSelectionPosition;
1062 if( selectionStart == selectionEnd )
1064 // Nothing to select if handles are in the same place.
1068 mEventData->mDecorator->ClearHighlights();
1070 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
1071 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
1072 const GlyphInfo* const glyphsBuffer = mVisualModel->mGlyphs.Begin();
1073 const Vector2* const positionsBuffer = mVisualModel->mGlyphPositions.Begin();
1074 const Length* const charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
1075 const CharacterIndex* const glyphToCharacterBuffer = mVisualModel->mGlyphsToCharacters.Begin();
1076 const CharacterDirection* const modelCharacterDirectionsBuffer = ( 0u != mLogicalModel->mCharacterDirections.Count() ) ? mLogicalModel->mCharacterDirections.Begin() : NULL;
1078 // TODO: Better algorithm to create the highlight box.
1079 // TODO: Multi-line.
1081 // Get the height of the line.
1082 const Vector<LineRun>& lines = mVisualModel->mLines;
1083 const LineRun& firstLine = *lines.Begin();
1084 const float height = firstLine.ascender + -firstLine.descender;
1086 const bool isLastCharacter = selectionEnd >= mLogicalModel->mText.Count();
1087 const bool startDirection = ( ( NULL == modelCharacterDirectionsBuffer ) ? false : *( modelCharacterDirectionsBuffer + selectionStart ) );
1088 const bool endDirection = ( ( NULL == modelCharacterDirectionsBuffer ) ? false : *( modelCharacterDirectionsBuffer + ( selectionEnd - ( isLastCharacter ? 1u : 0u ) ) ) );
1090 // Swap the indices if the start is greater than the end.
1091 const bool indicesSwapped = selectionStart > selectionEnd;
1093 // Tell the decorator to flip the selection handles if needed.
1094 mEventData->mDecorator->SetSelectionHandleFlipState( indicesSwapped, startDirection, endDirection );
1096 if( indicesSwapped )
1098 std::swap( selectionStart, selectionEnd );
1101 // Get the indices to the first and last selected glyphs.
1102 const CharacterIndex selectionEndMinusOne = selectionEnd - 1u;
1103 const GlyphIndex glyphStart = *( charactersToGlyphBuffer + selectionStart );
1104 const Length numberOfGlyphs = *( glyphsPerCharacterBuffer + selectionEndMinusOne );
1105 const GlyphIndex glyphEnd = *( charactersToGlyphBuffer + selectionEndMinusOne ) + ( ( numberOfGlyphs > 0 ) ? numberOfGlyphs - 1u : 0u );
1107 // Check if the first glyph is a ligature that must be broken like Latin ff, fi, or Arabic ﻻ, etc which needs special code.
1108 const Length numberOfCharactersStart = *( charactersPerGlyphBuffer + glyphStart );
1109 bool splitStartGlyph = ( numberOfCharactersStart > 1u ) && HasLigatureMustBreak( mLogicalModel->GetScript( selectionStart ) );
1111 // Check if the last glyph is a ligature that must be broken like Latin ff, fi, or Arabic ﻻ, etc which needs special code.
1112 const Length numberOfCharactersEnd = *( charactersPerGlyphBuffer + glyphEnd );
1113 bool splitEndGlyph = ( glyphStart != glyphEnd ) && ( numberOfCharactersEnd > 1u ) && HasLigatureMustBreak( mLogicalModel->GetScript( selectionEndMinusOne ) );
1115 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
1117 // Traverse the glyphs.
1118 for( GlyphIndex index = glyphStart; index <= glyphEnd; ++index )
1120 const GlyphInfo& glyph = *( glyphsBuffer + index );
1121 const Vector2& position = *( positionsBuffer + index );
1123 if( splitStartGlyph )
1125 // 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.
1127 const float glyphAdvance = glyph.advance / static_cast<float>( numberOfCharactersStart );
1128 const CharacterIndex interGlyphIndex = selectionStart - *( glyphToCharacterBuffer + glyphStart );
1129 // Get the direction of the character.
1130 CharacterDirection isCurrentRightToLeft = false;
1131 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
1133 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + selectionStart );
1136 // The end point could be in the middle of the ligature.
1137 // Calculate the number of characters selected.
1138 const Length numberOfCharacters = ( glyphStart == glyphEnd ) ? ( selectionEnd - selectionStart ) : ( numberOfCharactersStart - interGlyphIndex );
1140 const float xPosition = position.x - glyph.xBearing + offset.x + glyphAdvance * static_cast<float>( isCurrentRightToLeft ? ( numberOfCharactersStart - interGlyphIndex - numberOfCharacters ) : interGlyphIndex );
1142 mEventData->mDecorator->AddHighlight( xPosition,
1144 xPosition + static_cast<float>( numberOfCharacters ) * glyphAdvance,
1145 offset.y + height );
1147 splitStartGlyph = false;
1151 if( splitEndGlyph && ( index == glyphEnd ) )
1153 // 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.
1155 const float glyphAdvance = glyph.advance / static_cast<float>( numberOfCharactersEnd );
1156 const CharacterIndex interGlyphIndex = selectionEnd - *( glyphToCharacterBuffer + glyphEnd );
1157 // Get the direction of the character.
1158 CharacterDirection isCurrentRightToLeft = false;
1159 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
1161 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + selectionEnd );
1164 const Length numberOfCharacters = numberOfCharactersEnd - interGlyphIndex;
1166 const float xPosition = position.x - glyph.xBearing + offset.x + ( isCurrentRightToLeft ? ( glyphAdvance * static_cast<float>( numberOfCharacters ) ) : 0.f );
1167 mEventData->mDecorator->AddHighlight( xPosition,
1169 xPosition + static_cast<float>( interGlyphIndex ) * glyphAdvance,
1170 offset.y + height );
1172 splitEndGlyph = false;
1176 const float xPosition = position.x - glyph.xBearing + offset.x;
1177 mEventData->mDecorator->AddHighlight( xPosition,
1179 xPosition + glyph.advance,
1180 offset.y + height );
1183 CursorInfo primaryCursorInfo;
1184 GetCursorPosition( mEventData->mLeftSelectionPosition,
1185 primaryCursorInfo );
1187 CursorInfo secondaryCursorInfo;
1188 GetCursorPosition( mEventData->mRightSelectionPosition,
1189 secondaryCursorInfo );
1191 const Vector2 primaryPosition = primaryCursorInfo.primaryPosition + offset;
1192 const Vector2 secondaryPosition = secondaryCursorInfo.primaryPosition + offset;
1194 mEventData->mDecorator->SetPosition( LEFT_SELECTION_HANDLE,
1196 primaryCursorInfo.lineOffset + offset.y,
1197 primaryCursorInfo.lineHeight );
1199 mEventData->mDecorator->SetPosition( RIGHT_SELECTION_HANDLE,
1200 secondaryPosition.x,
1201 secondaryCursorInfo.lineOffset + offset.y,
1202 secondaryCursorInfo.lineHeight );
1204 // Cursor to be positioned at end of selection so if selection interrupted and edit mode restarted the cursor will be at end of selection
1205 mEventData->mPrimaryCursorPosition = ( indicesSwapped ) ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition;
1207 // Set the flag to update the decorator.
1208 mEventData->mDecoratorUpdated = true;
1211 void Controller::Impl::RepositionSelectionHandles( float visualX, float visualY )
1213 if( NULL == mEventData )
1215 // Nothing to do if there is no text input.
1219 if( IsShowingPlaceholderText() )
1221 // Nothing to do if there is the place-holder text.
1225 const Length numberOfGlyphs = mVisualModel->mGlyphs.Count();
1226 const Length numberOfLines = mVisualModel->mLines.Count();
1227 if( ( 0 == numberOfGlyphs ) ||
1228 ( 0 == numberOfLines ) )
1230 // Nothing to do if there is no text.
1234 // Find which word was selected
1235 CharacterIndex selectionStart( 0 );
1236 CharacterIndex selectionEnd( 0 );
1237 FindSelectionIndices( visualX, visualY, selectionStart, selectionEnd );
1238 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "%p selectionStart %d selectionEnd %d\n", this, selectionStart, selectionEnd );
1240 if( selectionStart == selectionEnd )
1242 ChangeState( EventData::EDITING );
1243 // Nothing to select. i.e. a white space, out of bounds
1247 mEventData->mLeftSelectionPosition = selectionStart;
1248 mEventData->mRightSelectionPosition = selectionEnd;
1251 void Controller::Impl::SetPopupButtons()
1254 * Sets the Popup buttons to be shown depending on State.
1256 * If SELECTING : CUT & COPY + ( PASTE & CLIPBOARD if content available to paste )
1258 * If EDITING_WITH_POPUP : SELECT & SELECT_ALL
1261 TextSelectionPopup::Buttons buttonsToShow = TextSelectionPopup::NONE;
1263 if( EventData::SELECTING == mEventData->mState )
1265 buttonsToShow = TextSelectionPopup::Buttons( TextSelectionPopup::CUT | TextSelectionPopup::COPY );
1267 if( !IsClipboardEmpty() )
1269 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
1270 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::CLIPBOARD ) );
1273 if( !mEventData->mAllTextSelected )
1275 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::SELECT_ALL ) );
1278 else if( EventData::EDITING_WITH_POPUP == mEventData->mState )
1280 if( mLogicalModel->mText.Count() && !IsShowingPlaceholderText() )
1282 buttonsToShow = TextSelectionPopup::Buttons( TextSelectionPopup::SELECT | TextSelectionPopup::SELECT_ALL );
1285 if( !IsClipboardEmpty() )
1287 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
1288 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::CLIPBOARD ) );
1291 else if( EventData::EDITING_WITH_PASTE_POPUP == mEventData->mState )
1293 if ( !IsClipboardEmpty() )
1295 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
1296 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::CLIPBOARD ) );
1300 mEventData->mDecorator->SetEnabledPopupButtons( buttonsToShow );
1303 void Controller::Impl::ChangeState( EventData::State newState )
1305 if( NULL == mEventData )
1307 // Nothing to do if there is no text input.
1311 DALI_LOG_INFO( gLogFilter, Debug::General, "ChangeState state:%d newstate:%d\n", mEventData->mState, newState );
1313 if( mEventData->mState != newState )
1315 mEventData->mState = newState;
1317 if( EventData::INACTIVE == mEventData->mState )
1319 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
1320 mEventData->mDecorator->StopCursorBlink();
1321 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1322 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1323 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1324 mEventData->mDecorator->SetPopupActive( false );
1325 mEventData->mDecoratorUpdated = true;
1328 else if( EventData::INTERRUPTED == mEventData->mState)
1330 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1331 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1332 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1333 mEventData->mDecorator->SetPopupActive( false );
1334 mEventData->mDecoratorUpdated = true;
1337 else if( EventData::SELECTING == mEventData->mState )
1339 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
1340 mEventData->mDecorator->StopCursorBlink();
1341 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1342 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true );
1343 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true );
1344 if( mEventData->mGrabHandlePopupEnabled )
1347 mEventData->mDecorator->SetPopupActive( true );
1349 mEventData->mDecoratorUpdated = true;
1351 else if( EventData::EDITING == mEventData->mState )
1353 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1354 if( mEventData->mCursorBlinkEnabled )
1356 mEventData->mDecorator->StartCursorBlink();
1358 // Grab handle is not shown until a tap is received whilst EDITING
1359 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1360 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1361 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1362 if( mEventData->mGrabHandlePopupEnabled )
1364 mEventData->mDecorator->SetPopupActive( false );
1366 mEventData->mDecoratorUpdated = true;
1369 else if( EventData::EDITING_WITH_POPUP == mEventData->mState )
1371 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "EDITING_WITH_POPUP \n", newState );
1373 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1374 if( mEventData->mCursorBlinkEnabled )
1376 mEventData->mDecorator->StartCursorBlink();
1378 if( mEventData->mSelectionEnabled )
1380 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1381 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1385 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
1387 if( mEventData->mGrabHandlePopupEnabled )
1390 mEventData->mDecorator->SetPopupActive( true );
1393 mEventData->mDecoratorUpdated = true;
1395 else if( EventData::EDITING_WITH_GRAB_HANDLE == mEventData->mState )
1397 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "EDITING_WITH_GRAB_HANDLE \n", newState );
1399 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1400 if( mEventData->mCursorBlinkEnabled )
1402 mEventData->mDecorator->StartCursorBlink();
1404 // Grab handle is not shown until a tap is received whilst EDITING
1405 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
1406 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1407 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1408 if( mEventData->mGrabHandlePopupEnabled )
1410 mEventData->mDecorator->SetPopupActive( false );
1412 mEventData->mDecoratorUpdated = true;
1415 else if( EventData::SELECTION_HANDLE_PANNING == mEventData->mState )
1417 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
1418 mEventData->mDecorator->StopCursorBlink();
1419 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1420 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true );
1421 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true );
1422 if( mEventData->mGrabHandlePopupEnabled )
1424 mEventData->mDecorator->SetPopupActive( false );
1426 mEventData->mDecoratorUpdated = true;
1428 else if( EventData::GRAB_HANDLE_PANNING == mEventData->mState )
1430 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "GRAB_HANDLE_PANNING \n", newState );
1432 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1433 if( mEventData->mCursorBlinkEnabled )
1435 mEventData->mDecorator->StartCursorBlink();
1437 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
1438 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1439 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1440 if( mEventData->mGrabHandlePopupEnabled )
1442 mEventData->mDecorator->SetPopupActive( false );
1444 mEventData->mDecoratorUpdated = true;
1446 else if( EventData::EDITING_WITH_PASTE_POPUP == mEventData->mState )
1448 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "EDITING_WITH_PASTE_POPUP \n", newState );
1450 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1451 if( mEventData->mCursorBlinkEnabled )
1453 mEventData->mDecorator->StartCursorBlink();
1456 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
1457 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1458 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1460 if( mEventData->mGrabHandlePopupEnabled )
1463 mEventData->mDecorator->SetPopupActive( true );
1466 mEventData->mDecoratorUpdated = true;
1471 LineIndex Controller::Impl::GetClosestLine( float y ) const
1473 float totalHeight = 0.f;
1474 LineIndex lineIndex = 0u;
1476 const Vector<LineRun>& lines = mVisualModel->mLines;
1477 for( LineIndex endLine = lines.Count();
1478 lineIndex < endLine;
1481 const LineRun& lineRun = lines[lineIndex];
1482 totalHeight += lineRun.ascender + -lineRun.descender;
1483 if( y < totalHeight )
1489 if( lineIndex == 0 )
1497 void Controller::Impl::FindSelectionIndices( float visualX, float visualY, CharacterIndex& startIndex, CharacterIndex& endIndex )
1499 CharacterIndex hitCharacter = GetClosestCursorIndex( visualX, visualY );
1500 DALI_ASSERT_DEBUG( hitCharacter <= mLogicalModel->mText.Count() && "GetClosestCursorIndex returned out of bounds index" );
1502 if( mLogicalModel->mText.Count() == 0 )
1504 return; // if model empty
1507 if( hitCharacter >= mLogicalModel->mText.Count() )
1509 // Closest hit character is the last character.
1510 if( hitCharacter == mLogicalModel->mText.Count() )
1512 hitCharacter--; //Hit character index set to last character in logical model
1516 // hitCharacter is out of bounds
1521 startIndex = hitCharacter;
1522 endIndex = hitCharacter;
1523 bool isHitCharacterWhitespace = TextAbstraction::IsWhiteSpace( mLogicalModel->mText[hitCharacter] );
1525 // Find the start and end of the text
1526 for( startIndex = hitCharacter; startIndex > 0; --startIndex )
1528 if( isHitCharacterWhitespace != TextAbstraction::IsWhiteSpace( mLogicalModel->mText[ startIndex-1 ] ) )
1533 const CharacterIndex pastTheEnd = mLogicalModel->mText.Count();
1534 for( endIndex = hitCharacter + 1u; endIndex < pastTheEnd; ++endIndex )
1536 if( isHitCharacterWhitespace != TextAbstraction::IsWhiteSpace( mLogicalModel->mText[ endIndex ] ) )
1543 CharacterIndex Controller::Impl::GetClosestCursorIndex( float visualX,
1546 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "GetClosestCursorIndex %p closest visualX %f visualY %f\n", this, visualX, visualY );
1548 if( NULL == mEventData )
1550 // Nothing to do if there is no text input.
1554 CharacterIndex logicalIndex = 0u;
1556 const Length numberOfGlyphs = mVisualModel->mGlyphs.Count();
1557 const Length numberOfLines = mVisualModel->mLines.Count();
1558 if( ( 0 == numberOfGlyphs ) ||
1559 ( 0 == numberOfLines ) )
1561 return logicalIndex;
1564 // Find which line is closest
1565 const LineIndex lineIndex = GetClosestLine( visualY );
1566 const LineRun& line = mVisualModel->mLines[lineIndex];
1568 // Get the positions of the glyphs.
1569 const Vector<Vector2>& positions = mVisualModel->mGlyphPositions;
1570 const Vector2* const positionsBuffer = positions.Begin();
1572 // Get the visual to logical conversion tables.
1573 const CharacterIndex* const visualToLogicalBuffer = ( 0u != mLogicalModel->mVisualToLogicalMap.Count() ) ? mLogicalModel->mVisualToLogicalMap.Begin() : NULL;
1574 const CharacterIndex* const visualToLogicalCursorBuffer = mLogicalModel->mVisualToLogicalCursorMap.Begin();
1576 // Get the character to glyph conversion table.
1577 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
1579 // Get the glyphs per character table.
1580 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
1582 // If the vector is void, there is no right to left characters.
1583 const bool hasRightToLeftCharacters = NULL != visualToLogicalBuffer;
1585 const CharacterIndex startCharacter = line.characterRun.characterIndex;
1586 const CharacterIndex endCharacter = line.characterRun.characterIndex + line.characterRun.numberOfCharacters;
1587 DALI_ASSERT_DEBUG( endCharacter <= mLogicalModel->mText.Count() && "Invalid line info" );
1589 // Whether there is a hit on a glyph.
1590 bool matched = false;
1592 // Traverses glyphs in visual order. To do that use the visual to logical conversion table.
1593 CharacterIndex visualIndex = startCharacter;
1594 Length numberOfCharacters = 0u;
1595 for( ; !matched && ( visualIndex < endCharacter ); ++visualIndex )
1597 // The character in logical order.
1598 const CharacterIndex characterLogicalOrderIndex = hasRightToLeftCharacters ? *( visualToLogicalBuffer + visualIndex ) : visualIndex;
1600 // Get the script of the character.
1601 const Script script = mLogicalModel->GetScript( characterLogicalOrderIndex );
1603 // The number of glyphs for that character
1604 const Length numberOfGlyphs = *( glyphsPerCharacterBuffer + characterLogicalOrderIndex );
1605 ++numberOfCharacters;
1608 if( 0u != numberOfGlyphs )
1610 // Get the first character/glyph of the group of glyphs.
1611 const CharacterIndex firstVisualCharacterIndex = 1u + visualIndex - numberOfCharacters;
1612 const CharacterIndex firstLogicalCharacterIndex = hasRightToLeftCharacters ? *( visualToLogicalBuffer + firstVisualCharacterIndex ) : firstVisualCharacterIndex;
1613 const GlyphIndex firstLogicalGlyphIndex = *( charactersToGlyphBuffer + firstLogicalCharacterIndex );
1615 // Get the metrics for the group of glyphs.
1616 GlyphMetrics glyphMetrics;
1617 GetGlyphsMetrics( firstLogicalGlyphIndex,
1623 // Get the position of the first glyph.
1624 const Vector2& position = *( positionsBuffer + firstLogicalGlyphIndex );
1626 // Whether the glyph can be split, like Latin ligatures fi, ff or Arabic ﻻ.
1627 const bool isInterglyphIndex = ( numberOfCharacters > numberOfGlyphs ) && HasLigatureMustBreak( script );
1628 const Length numberOfBlocks = isInterglyphIndex ? numberOfCharacters : 1u;
1629 const float glyphAdvance = glyphMetrics.advance / static_cast<float>( numberOfBlocks );
1631 GlyphIndex index = 0u;
1632 for( ; !matched && ( index < numberOfBlocks ); ++index )
1634 // Find the mid-point of the area containing the glyph
1635 const float glyphCenter = -glyphMetrics.xBearing + position.x + ( static_cast<float>( index ) + 0.5f ) * glyphAdvance;
1637 if( visualX < glyphCenter )
1646 visualIndex = firstVisualCharacterIndex + index;
1650 numberOfCharacters = 0u;
1656 // Return the logical position of the cursor in characters.
1660 visualIndex = endCharacter;
1663 logicalIndex = hasRightToLeftCharacters ? *( visualToLogicalCursorBuffer + visualIndex ) : visualIndex;
1664 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "%p closest visualIndex %d logicalIndex %d\n", this, visualIndex, logicalIndex );
1666 DALI_ASSERT_DEBUG( ( logicalIndex <= mLogicalModel->mText.Count() && logicalIndex >= 0 ) && "GetClosestCursorIndex - Out of bounds index" );
1668 return logicalIndex;
1671 void Controller::Impl::GetCursorPosition( CharacterIndex logical,
1672 CursorInfo& cursorInfo )
1674 // TODO: Check for multiline with \n, etc...
1676 const Length numberOfCharacters = mLogicalModel->mText.Count();
1677 if( !IsShowingRealText() )
1679 // Do not want to use the place-holder text to set the cursor position.
1681 // Use the line's height of the font's family set to set the cursor's size.
1682 // If there is no font's family set, use the default font.
1683 // Use the current alignment to place the cursor at the beginning, center or end of the box.
1685 cursorInfo.lineOffset = 0.f;
1686 cursorInfo.lineHeight = GetDefaultFontLineHeight();
1687 cursorInfo.primaryCursorHeight = cursorInfo.lineHeight;
1689 switch( mLayoutEngine.GetHorizontalAlignment() )
1691 case LayoutEngine::HORIZONTAL_ALIGN_BEGIN:
1693 cursorInfo.primaryPosition.x = 0.f;
1696 case LayoutEngine::HORIZONTAL_ALIGN_CENTER:
1698 cursorInfo.primaryPosition.x = floorf( 0.5f * mVisualModel->mControlSize.width );
1701 case LayoutEngine::HORIZONTAL_ALIGN_END:
1703 cursorInfo.primaryPosition.x = mVisualModel->mControlSize.width - mEventData->mDecorator->GetCursorWidth();
1708 switch( mLayoutEngine.GetVerticalAlignment() )
1710 case LayoutEngine::VERTICAL_ALIGN_TOP:
1712 cursorInfo.primaryPosition.y = 0.f;
1715 case LayoutEngine::VERTICAL_ALIGN_CENTER:
1717 cursorInfo.primaryPosition.y = floorf( 0.5f * ( mVisualModel->mControlSize.height - cursorInfo.lineHeight ) );
1720 case LayoutEngine::VERTICAL_ALIGN_BOTTOM:
1722 cursorInfo.primaryPosition.y = mVisualModel->mControlSize.height - cursorInfo.lineHeight;
1727 // Nothing else to do.
1731 // Check if the logical position is the first or the last one of the text.
1732 const bool isFirstPosition = 0u == logical;
1733 const bool isLastPosition = numberOfCharacters == logical;
1735 // 'logical' is the logical 'cursor' index.
1736 // Get the next and current logical 'character' index.
1737 const CharacterIndex nextCharacterIndex = logical;
1738 const CharacterIndex characterIndex = isFirstPosition ? logical : logical - 1u;
1740 // Get the direction of the character and the next one.
1741 const CharacterDirection* const modelCharacterDirectionsBuffer = ( 0u != mLogicalModel->mCharacterDirections.Count() ) ? mLogicalModel->mCharacterDirections.Begin() : NULL;
1743 CharacterDirection isCurrentRightToLeft = false;
1744 CharacterDirection isNextRightToLeft = false;
1745 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
1747 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + characterIndex );
1748 isNextRightToLeft = *( modelCharacterDirectionsBuffer + nextCharacterIndex );
1751 // Get the line where the character is laid-out.
1752 const LineRun* const modelLines = mVisualModel->mLines.Begin();
1754 const LineIndex lineIndex = mVisualModel->GetLineOfCharacter( characterIndex );
1755 const LineRun& line = *( modelLines + lineIndex );
1757 // Get the paragraph's direction.
1758 const CharacterDirection isRightToLeftParagraph = line.direction;
1760 // Check whether there is an alternative position:
1762 cursorInfo.isSecondaryCursor = ( !isLastPosition && ( isCurrentRightToLeft != isNextRightToLeft ) ) ||
1763 ( isLastPosition && ( isRightToLeftParagraph != isCurrentRightToLeft ) );
1765 // Set the line offset and height.
1766 cursorInfo.lineOffset = 0.f;
1767 cursorInfo.lineHeight = line.ascender + -line.descender;
1769 // Calculate the primary cursor.
1771 CharacterIndex index = characterIndex;
1772 if( cursorInfo.isSecondaryCursor )
1774 // If there is a secondary position, the primary cursor may be in a different place than the logical index.
1776 if( isLastPosition )
1778 // The position of the cursor after the last character needs special
1779 // care depending on its direction and the direction of the paragraph.
1781 // Need to find the first character after the last character with the paragraph's direction.
1782 // i.e l0 l1 l2 r0 r1 should find r0.
1784 // TODO: check for more than one line!
1785 index = isRightToLeftParagraph ? line.characterRun.characterIndex : line.characterRun.characterIndex + line.characterRun.numberOfCharacters - 1u;
1786 index = mLogicalModel->GetLogicalCharacterIndex( index );
1790 index = ( isRightToLeftParagraph == isCurrentRightToLeft ) ? characterIndex : nextCharacterIndex;
1794 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
1795 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
1796 const Length* const charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
1797 const CharacterIndex* const glyphsToCharactersBuffer = mVisualModel->mGlyphsToCharacters.Begin();
1798 const Vector2* const glyphPositionsBuffer = mVisualModel->mGlyphPositions.Begin();
1800 // Convert the cursor position into the glyph position.
1801 const GlyphIndex primaryGlyphIndex = *( charactersToGlyphBuffer + index );
1802 const Length primaryNumberOfGlyphs = *( glyphsPerCharacterBuffer + index );
1803 const Length primaryNumberOfCharacters = *( charactersPerGlyphBuffer + primaryGlyphIndex );
1805 // Get the metrics for the group of glyphs.
1806 GlyphMetrics glyphMetrics;
1807 GetGlyphsMetrics( primaryGlyphIndex,
1808 primaryNumberOfGlyphs,
1813 // Whether to add the glyph's advance to the cursor position.
1814 // 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,
1815 // if the logical cursor is one, the position is the position of the first glyph and the advance is added.
1816 // A 'truth table' was build and an online Karnaugh map tool was used to simplify the logic.
1837 // Where F -> isFirstPosition
1838 // L -> isLastPosition
1839 // C -> isCurrentRightToLeft
1840 // P -> isRightToLeftParagraph
1841 // A -> Whether to add the glyph's advance.
1843 const bool addGlyphAdvance = ( ( isLastPosition && !isRightToLeftParagraph ) ||
1844 ( isFirstPosition && isRightToLeftParagraph ) ||
1845 ( !isFirstPosition && !isLastPosition && !isCurrentRightToLeft ) );
1847 float glyphAdvance = addGlyphAdvance ? glyphMetrics.advance : 0.f;
1849 if( !isLastPosition &&
1850 ( primaryNumberOfCharacters > 1u ) )
1852 const CharacterIndex firstIndex = *( glyphsToCharactersBuffer + primaryGlyphIndex );
1854 bool isCurrentRightToLeft = false;
1855 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
1857 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + index );
1860 Length numberOfGlyphAdvance = ( isFirstPosition ? 0u : 1u ) + characterIndex - firstIndex;
1861 if( isCurrentRightToLeft )
1863 numberOfGlyphAdvance = primaryNumberOfCharacters - numberOfGlyphAdvance;
1866 glyphAdvance = static_cast<float>( numberOfGlyphAdvance ) * glyphMetrics.advance / static_cast<float>( primaryNumberOfCharacters );
1869 // Get the glyph position and x bearing.
1870 const Vector2& primaryPosition = *( glyphPositionsBuffer + primaryGlyphIndex );
1872 // Set the primary cursor's height.
1873 cursorInfo.primaryCursorHeight = cursorInfo.isSecondaryCursor ? 0.5f * glyphMetrics.fontHeight : glyphMetrics.fontHeight;
1875 // Set the primary cursor's position.
1876 cursorInfo.primaryPosition.x = -glyphMetrics.xBearing + primaryPosition.x + glyphAdvance;
1877 cursorInfo.primaryPosition.y = line.ascender - glyphMetrics.ascender;
1879 // Calculate the secondary cursor.
1881 if( cursorInfo.isSecondaryCursor )
1883 // Set the secondary cursor's height.
1884 cursorInfo.secondaryCursorHeight = 0.5f * glyphMetrics.fontHeight;
1886 CharacterIndex index = characterIndex;
1887 if( !isLastPosition )
1889 index = ( isRightToLeftParagraph == isCurrentRightToLeft ) ? nextCharacterIndex : characterIndex;
1892 const GlyphIndex secondaryGlyphIndex = *( charactersToGlyphBuffer + index );
1893 const Length secondaryNumberOfGlyphs = *( glyphsPerCharacterBuffer + index );
1895 const Vector2& secondaryPosition = *( glyphPositionsBuffer + secondaryGlyphIndex );
1897 GetGlyphsMetrics( secondaryGlyphIndex,
1898 secondaryNumberOfGlyphs,
1903 // Set the secondary cursor's position.
1904 cursorInfo.secondaryPosition.x = -glyphMetrics.xBearing + secondaryPosition.x + ( isCurrentRightToLeft ? 0.f : glyphMetrics.advance );
1905 cursorInfo.secondaryPosition.y = cursorInfo.lineHeight - cursorInfo.secondaryCursorHeight - line.descender - ( glyphMetrics.fontHeight - glyphMetrics.ascender );
1908 if( LayoutEngine::MULTI_LINE_BOX == mLayoutEngine.GetLayout() )
1910 // If the text is editable and multi-line, the cursor position after a white space shouldn't exceed the boundaries of the text control.
1912 // Note the white spaces laid-out at the end of the line might exceed the boundaries of the control.
1913 // The reason is a wrapped line must not start with a white space so they are laid-out at the end of the line.
1915 if( 0.f > cursorInfo.primaryPosition.x )
1917 cursorInfo.primaryPosition.x = 0.f;
1920 const float edgeWidth = mVisualModel->mControlSize.width - static_cast<float>( mEventData->mDecorator->GetCursorWidth() );
1921 if( cursorInfo.primaryPosition.x > edgeWidth )
1923 cursorInfo.primaryPosition.x = edgeWidth;
1928 CharacterIndex Controller::Impl::CalculateNewCursorIndex( CharacterIndex index ) const
1930 if( NULL == mEventData )
1932 // Nothing to do if there is no text input.
1936 CharacterIndex cursorIndex = mEventData->mPrimaryCursorPosition;
1938 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
1939 const Length* const charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
1941 GlyphIndex glyphIndex = *( charactersToGlyphBuffer + index );
1942 Length numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
1944 if( numberOfCharacters > 1u )
1946 const Script script = mLogicalModel->GetScript( index );
1947 if( HasLigatureMustBreak( script ) )
1949 // Prevents to jump the whole Latin ligatures like fi, ff, or Arabic ﻻ, ...
1950 numberOfCharacters = 1u;
1955 while( 0u == numberOfCharacters )
1958 numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
1962 if( index < mEventData->mPrimaryCursorPosition )
1964 cursorIndex -= numberOfCharacters;
1968 cursorIndex += numberOfCharacters;
1974 void Controller::Impl::UpdateCursorPosition( const CursorInfo& cursorInfo )
1976 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "-->Controller::UpdateCursorPosition %p\n", this );
1977 if( NULL == mEventData )
1979 // Nothing to do if there is no text input.
1980 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition no event data\n" );
1984 const Vector2 offset = mEventData->mScrollPosition + ( IsShowingRealText() ? mAlignmentOffset : Vector2::ZERO );
1985 const Vector2 cursorPosition = cursorInfo.primaryPosition + offset;
1987 // Sets the cursor position.
1988 mEventData->mDecorator->SetPosition( PRIMARY_CURSOR,
1991 cursorInfo.primaryCursorHeight,
1992 cursorInfo.lineHeight );
1993 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Primary cursor position: %f,%f\n", cursorPosition.x, cursorPosition.y );
1995 // Sets the grab handle position.
1996 mEventData->mDecorator->SetPosition( GRAB_HANDLE,
1998 cursorInfo.lineOffset + offset.y,
1999 cursorInfo.lineHeight );
2001 if( cursorInfo.isSecondaryCursor )
2003 mEventData->mDecorator->SetPosition( SECONDARY_CURSOR,
2004 cursorInfo.secondaryPosition.x + offset.x,
2005 cursorInfo.secondaryPosition.y + offset.y,
2006 cursorInfo.secondaryCursorHeight,
2007 cursorInfo.lineHeight );
2008 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Secondary cursor position: %f,%f\n", cursorInfo.secondaryPosition.x + offset.x, cursorInfo.secondaryPosition.y + offset.y );
2011 // Set which cursors are active according the state.
2012 if( EventData::IsEditingState( mEventData->mState ) || ( EventData::GRAB_HANDLE_PANNING == mEventData->mState ) )
2014 if( cursorInfo.isSecondaryCursor )
2016 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_BOTH );
2020 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
2025 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
2028 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition\n" );
2031 void Controller::Impl::UpdateSelectionHandle( HandleType handleType,
2032 const CursorInfo& cursorInfo )
2034 if( ( LEFT_SELECTION_HANDLE != handleType ) &&
2035 ( RIGHT_SELECTION_HANDLE != handleType ) )
2040 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
2041 const Vector2 cursorPosition = cursorInfo.primaryPosition + offset;
2043 // Sets the handle's position.
2044 mEventData->mDecorator->SetPosition( handleType,
2046 cursorInfo.lineOffset + offset.y,
2047 cursorInfo.lineHeight );
2049 // If selection handle at start of the text and other at end of the text then all text is selected.
2050 const CharacterIndex startOfSelection = std::min( mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition );
2051 const CharacterIndex endOfSelection = std::max ( mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition );
2052 mEventData->mAllTextSelected = ( startOfSelection == 0 ) && ( endOfSelection == mLogicalModel->mText.Count() );
2055 void Controller::Impl::ClampHorizontalScroll( const Vector2& actualSize )
2057 // Clamp between -space & 0 (and the text alignment).
2059 if( actualSize.width > mVisualModel->mControlSize.width )
2061 const float space = ( actualSize.width - mVisualModel->mControlSize.width ) + mAlignmentOffset.x;
2062 mEventData->mScrollPosition.x = ( mEventData->mScrollPosition.x < -space ) ? -space : mEventData->mScrollPosition.x;
2063 mEventData->mScrollPosition.x = ( mEventData->mScrollPosition.x > -mAlignmentOffset.x ) ? -mAlignmentOffset.x : mEventData->mScrollPosition.x;
2065 mEventData->mDecoratorUpdated = true;
2069 mEventData->mScrollPosition.x = 0.f;
2073 void Controller::Impl::ClampVerticalScroll( const Vector2& actualSize )
2075 // Clamp between -space & 0 (and the text alignment).
2076 if( actualSize.height > mVisualModel->mControlSize.height )
2078 const float space = ( actualSize.height - mVisualModel->mControlSize.height ) + mAlignmentOffset.y;
2079 mEventData->mScrollPosition.y = ( mEventData->mScrollPosition.y < -space ) ? -space : mEventData->mScrollPosition.y;
2080 mEventData->mScrollPosition.y = ( mEventData->mScrollPosition.y > -mAlignmentOffset.y ) ? -mAlignmentOffset.y : mEventData->mScrollPosition.y;
2082 mEventData->mDecoratorUpdated = true;
2086 mEventData->mScrollPosition.y = 0.f;
2090 void Controller::Impl::ScrollToMakePositionVisible( const Vector2& position )
2092 // position is in actor's coords.
2093 const float positionEnd = position.x + ( mEventData->mDecorator ? mEventData->mDecorator->GetCursorWidth() : 0.f );
2095 // Transform the position to decorator coords.
2096 const float alignment = IsShowingRealText() ? mAlignmentOffset.x : 0.f;
2097 const float offset = mEventData->mScrollPosition.x + alignment;
2098 const float decoratorPositionBegin = position.x + offset;
2099 const float decoratorPositionEnd = positionEnd + offset;
2101 if( decoratorPositionBegin < 0.f )
2103 mEventData->mScrollPosition.x = -position.x - alignment;
2105 else if( decoratorPositionEnd > mVisualModel->mControlSize.width )
2107 mEventData->mScrollPosition.x = mVisualModel->mControlSize.width - positionEnd - alignment;
2111 void Controller::Impl::ScrollTextToMatchCursor( const CursorInfo& cursorInfo )
2113 // Get the current cursor position in decorator coords.
2114 const Vector2& currentCursorPosition = mEventData->mDecorator->GetPosition( PRIMARY_CURSOR );
2116 // Calculate the offset to match the cursor position before the character was deleted.
2117 mEventData->mScrollPosition.x = currentCursorPosition.x - cursorInfo.primaryPosition.x - mAlignmentOffset.x;
2119 ClampHorizontalScroll( mVisualModel->GetLayoutSize() );
2122 void Controller::Impl::RequestRelayout()
2124 mControlInterface.RequestTextRelayout();
2129 } // namespace Toolkit