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,
340 requestedNumberOfCharacters,
343 // Create the paragraph info.
344 mLogicalModel->CreateParagraphInfo( startIndex,
345 requestedNumberOfCharacters );
348 Vector<WordBreakInfo>& wordBreakInfo = mLogicalModel->mWordBreakInfo;
349 if( GET_WORD_BREAKS & operations )
351 // Retrieves the word break info. The word break info is used to layout the text (where to wrap the text in lines).
352 wordBreakInfo.Resize( numberOfCharacters, TextAbstraction::WORD_NO_BREAK );
354 SetWordBreakInfo( utf32Characters,
356 requestedNumberOfCharacters,
360 const bool getScripts = GET_SCRIPTS & operations;
361 const bool validateFonts = VALIDATE_FONTS & operations;
363 Vector<ScriptRun>& scripts = mLogicalModel->mScriptRuns;
364 Vector<FontRun>& validFonts = mLogicalModel->mFontRuns;
366 if( getScripts || validateFonts )
368 // Validates the fonts assigned by the application or assigns default ones.
369 // It makes sure all the characters are going to be rendered by the correct font.
370 MultilanguageSupport multilanguageSupport = MultilanguageSupport::Get();
374 // Retrieves the scripts used in the text.
375 multilanguageSupport.SetScripts( utf32Characters,
377 requestedNumberOfCharacters,
383 // Validate the fonts set through the mark-up string.
384 Vector<FontDescriptionRun>& fontDescriptionRuns = mLogicalModel->mFontDescriptionRuns;
386 // Get the default font id.
387 const FontId defaultFontId = ( NULL == mFontDefaults ) ? 0u : mFontDefaults->GetFontId( mFontClient );
389 // Validates the fonts. If there is a character with no assigned font it sets a default one.
390 // After this call, fonts are validated.
391 multilanguageSupport.ValidateFonts( utf32Characters,
396 requestedNumberOfCharacters,
401 Vector<Character> mirroredUtf32Characters;
402 bool textMirrored = false;
403 Length numberOfParagraphs = 0u;
404 if( BIDI_INFO & operations )
406 // Count the number of LINE_NO_BREAK to reserve some space for the vector of paragraph's
407 // bidirectional info.
409 const TextAbstraction::LineBreakInfo* lineBreakInfoBuffer = lineBreakInfo.Begin();
410 for( Length index = 0u; index < numberOfCharacters; ++index )
412 if( TextAbstraction::LINE_NO_BREAK == *( lineBreakInfoBuffer + index ) )
414 ++numberOfParagraphs;
418 Vector<BidirectionalParagraphInfoRun>& bidirectionalInfo = mLogicalModel->mBidirectionalParagraphInfo;
419 bidirectionalInfo.Reserve( numberOfParagraphs );
421 // Calculates the bidirectional info for the whole paragraph if it contains right to left scripts.
422 SetBidirectionalInfo( utf32Characters,
426 requestedNumberOfCharacters,
429 if( 0u != bidirectionalInfo.Count() )
431 // Only set the character directions if there is right to left characters.
432 Vector<CharacterDirection>& directions = mLogicalModel->mCharacterDirections;
433 GetCharactersDirection( bidirectionalInfo,
436 requestedNumberOfCharacters,
439 // This paragraph has right to left text. Some characters may need to be mirrored.
440 // TODO: consider if the mirrored string can be stored as well.
442 textMirrored = GetMirroredText( utf32Characters,
446 requestedNumberOfCharacters,
447 mirroredUtf32Characters );
451 // There is no right to left characters. Clear the directions vector.
452 mLogicalModel->mCharacterDirections.Clear();
456 Vector<GlyphInfo>& glyphs = mVisualModel->mGlyphs;
457 Vector<CharacterIndex>& glyphsToCharactersMap = mVisualModel->mGlyphsToCharacters;
458 Vector<Length>& charactersPerGlyph = mVisualModel->mCharactersPerGlyph;
459 Vector<GlyphIndex> newParagraphGlyphs;
460 newParagraphGlyphs.Reserve( numberOfParagraphs );
462 GlyphIndex startGlyphIndex = 0u;
463 if( SHAPE_TEXT & operations )
465 const Vector<Character>& textToShape = textMirrored ? mirroredUtf32Characters : utf32Characters;
467 ShapeText( textToShape,
473 requestedNumberOfCharacters,
475 glyphsToCharactersMap,
477 newParagraphGlyphs );
479 // Create the 'number of glyphs' per character and the glyph to character conversion tables.
480 mVisualModel->CreateGlyphsPerCharacterTable( startIndex, startGlyphIndex, numberOfCharacters );
481 mVisualModel->CreateCharacterToGlyphTable( startIndex, startGlyphIndex, numberOfCharacters );
484 const Length numberOfGlyphs = glyphs.Count();
486 if( GET_GLYPH_METRICS & operations )
488 GlyphInfo* glyphsBuffer = glyphs.Begin() + startGlyphIndex;
489 mMetrics->GetGlyphMetrics( glyphsBuffer, numberOfGlyphs );
491 // Update the width and advance of all new paragraph characters.
492 for( Vector<GlyphIndex>::ConstIterator it = newParagraphGlyphs.Begin(), endIt = newParagraphGlyphs.End(); it != endIt; ++it )
494 const GlyphIndex index = *it;
495 GlyphInfo& glyph = *( glyphsBuffer + index );
497 glyph.xBearing = 0.f;
503 if( ( NULL != mEventData ) &&
504 mEventData->mPreEditFlag &&
505 ( 0u != mVisualModel->mCharactersToGlyph.Count() ) )
507 // Add the underline for the pre-edit text.
508 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
509 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
511 const GlyphIndex glyphStart = *( charactersToGlyphBuffer + mEventData->mPreEditStartPosition );
512 const CharacterIndex lastPreEditCharacter = mEventData->mPreEditStartPosition + ( ( mEventData->mPreEditLength > 0u ) ? mEventData->mPreEditLength - 1u : 0u );
513 const Length numberOfGlyphsLastCharacter = *( glyphsPerCharacterBuffer + lastPreEditCharacter );
514 const GlyphIndex glyphEnd = *( charactersToGlyphBuffer + lastPreEditCharacter ) + ( numberOfGlyphsLastCharacter > 1u ? numberOfGlyphsLastCharacter - 1u : 0u );
516 GlyphRun underlineRun;
517 underlineRun.glyphIndex = glyphStart;
518 underlineRun.numberOfGlyphs = 1u + glyphEnd - glyphStart;
520 // TODO: At the moment the underline runs are only for pre-edit.
521 mVisualModel->mUnderlineRuns.PushBack( underlineRun );
525 bool Controller::Impl::UpdateModelStyle( OperationsMask operationsRequired )
527 bool updated = false;
529 if( COLOR & operationsRequired )
531 // Set the color runs in glyphs.
532 SetColorSegmentationInfo( mLogicalModel->mColorRuns,
533 mVisualModel->mCharactersToGlyph,
534 mVisualModel->mGlyphsPerCharacter,
535 mVisualModel->mColorRuns );
543 void Controller::Impl::RetrieveDefaultInputStyle( InputStyle& inputStyle )
545 // Sets the default text's color.
546 inputStyle.textColor = mTextColor;
548 // Sets the default font's family name, weight, width, slant and size.
551 inputStyle.familyName = mFontDefaults->mFontDescription.family;
552 inputStyle.weight = mFontDefaults->mFontDescription.weight;
553 inputStyle.width = mFontDefaults->mFontDescription.width;
554 inputStyle.slant = mFontDefaults->mFontDescription.slant;
555 inputStyle.size = mFontDefaults->mDefaultPointSize;
557 inputStyle.familyDefined = mFontDefaults->familyDefined;
558 inputStyle.weightDefined = mFontDefaults->weightDefined;
559 inputStyle.widthDefined = mFontDefaults->widthDefined;
560 inputStyle.slantDefined = mFontDefaults->slantDefined;
561 inputStyle.sizeDefined = mFontDefaults->sizeDefined;
565 inputStyle.familyName.clear();
566 inputStyle.weight = TextAbstraction::FontWeight::NORMAL;
567 inputStyle.width = TextAbstraction::FontWidth::NORMAL;
568 inputStyle.slant = TextAbstraction::FontSlant::NORMAL;
569 inputStyle.size = 0.f;
571 inputStyle.familyDefined = false;
572 inputStyle.weightDefined = false;
573 inputStyle.widthDefined = false;
574 inputStyle.slantDefined = false;
575 inputStyle.sizeDefined = false;
579 float Controller::Impl::GetDefaultFontLineHeight()
581 FontId defaultFontId = 0u;
582 if( NULL == mFontDefaults )
584 TextAbstraction::FontDescription fontDescription;
585 defaultFontId = mFontClient.GetFontId( fontDescription );
589 defaultFontId = mFontDefaults->GetFontId( mFontClient );
592 Text::FontMetrics fontMetrics;
593 mMetrics->GetFontMetrics( defaultFontId, fontMetrics );
595 return( fontMetrics.ascender - fontMetrics.descender );
598 void Controller::Impl::OnCursorKeyEvent( const Event& event )
600 if( NULL == mEventData )
602 // Nothing to do if there is no text input.
606 int keyCode = event.p1.mInt;
608 if( Dali::DALI_KEY_CURSOR_LEFT == keyCode )
610 if( mEventData->mPrimaryCursorPosition > 0u )
612 mEventData->mPrimaryCursorPosition = CalculateNewCursorIndex( mEventData->mPrimaryCursorPosition - 1u );
615 else if( Dali::DALI_KEY_CURSOR_RIGHT == keyCode )
617 if( mLogicalModel->mText.Count() > mEventData->mPrimaryCursorPosition )
619 mEventData->mPrimaryCursorPosition = CalculateNewCursorIndex( mEventData->mPrimaryCursorPosition );
622 else if( Dali::DALI_KEY_CURSOR_UP == keyCode )
626 else if( Dali::DALI_KEY_CURSOR_DOWN == keyCode )
631 mEventData->mUpdateCursorPosition = true;
632 mEventData->mUpdateInputStyle = true;
633 mEventData->mScrollAfterUpdatePosition = true;
636 void Controller::Impl::OnTapEvent( const Event& event )
638 if( NULL != mEventData )
640 const unsigned int tapCount = event.p1.mUint;
644 if( IsShowingRealText() )
646 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
647 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
649 mEventData->mPrimaryCursorPosition = GetClosestCursorIndex( xPosition,
652 // When the cursor position is changing, delay cursor blinking
653 mEventData->mDecorator->DelayCursorBlink();
657 mEventData->mPrimaryCursorPosition = 0u;
660 mEventData->mUpdateCursorPosition = true;
661 mEventData->mScrollAfterUpdatePosition = true;
662 mEventData->mUpdateInputStyle = true;
664 // Notify the cursor position to the imf manager.
665 if( mEventData->mImfManager )
667 mEventData->mImfManager.SetCursorPosition( mEventData->mPrimaryCursorPosition );
668 mEventData->mImfManager.NotifyCursorPosition();
674 void Controller::Impl::OnPanEvent( const Event& event )
676 if( NULL == mEventData )
678 // Nothing to do if there is no text input.
682 int state = event.p1.mInt;
684 if( Gesture::Started == state ||
685 Gesture::Continuing == state )
687 const Vector2& actualSize = mVisualModel->GetLayoutSize();
688 const Vector2 currentScroll = mEventData->mScrollPosition;
690 if( mEventData->mHorizontalScrollingEnabled )
692 const float displacementX = event.p2.mFloat;
693 mEventData->mScrollPosition.x += displacementX;
695 ClampHorizontalScroll( actualSize );
698 if( mEventData->mVerticalScrollingEnabled )
700 const float displacementY = event.p3.mFloat;
701 mEventData->mScrollPosition.y += displacementY;
703 ClampVerticalScroll( actualSize );
706 if( mEventData->mDecorator )
708 mEventData->mDecorator->UpdatePositions( mEventData->mScrollPosition - currentScroll );
713 void Controller::Impl::OnLongPressEvent( const Event& event )
715 DALI_LOG_INFO( gLogFilter, Debug::General, "Controller::OnLongPressEvent\n" );
717 if( EventData::EDITING == mEventData->mState )
719 ChangeState ( EventData::EDITING_WITH_POPUP );
720 mEventData->mDecoratorUpdated = true;
724 void Controller::Impl::OnHandleEvent( const Event& event )
726 if( NULL == mEventData )
728 // Nothing to do if there is no text input.
732 const unsigned int state = event.p1.mUint;
733 const bool handleStopScrolling = ( HANDLE_STOP_SCROLLING == state );
735 if( HANDLE_PRESSED == state )
737 // The event.p2 and event.p3 are in decorator coords. Need to transforms to text coords.
738 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
739 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
741 const CharacterIndex handleNewPosition = GetClosestCursorIndex( xPosition, yPosition );
743 if( Event::GRAB_HANDLE_EVENT == event.type )
745 ChangeState ( EventData::GRAB_HANDLE_PANNING );
747 if( handleNewPosition != mEventData->mPrimaryCursorPosition )
749 mEventData->mPrimaryCursorPosition = handleNewPosition;
750 mEventData->mUpdateCursorPosition = true;
753 else if( Event::LEFT_SELECTION_HANDLE_EVENT == event.type )
755 ChangeState ( EventData::SELECTION_HANDLE_PANNING );
757 if( ( handleNewPosition != mEventData->mLeftSelectionPosition ) &&
758 ( handleNewPosition != mEventData->mRightSelectionPosition ) )
760 mEventData->mLeftSelectionPosition = handleNewPosition;
762 mEventData->mUpdateLeftSelectionPosition = true;
765 else if( Event::RIGHT_SELECTION_HANDLE_EVENT == event.type )
767 ChangeState ( EventData::SELECTION_HANDLE_PANNING );
769 if( ( handleNewPosition != mEventData->mRightSelectionPosition ) &&
770 ( handleNewPosition != mEventData->mLeftSelectionPosition ) )
772 mEventData->mRightSelectionPosition = handleNewPosition;
774 mEventData->mUpdateRightSelectionPosition = true;
777 } // end ( HANDLE_PRESSED == state )
778 else if( ( HANDLE_RELEASED == state ) ||
779 handleStopScrolling )
781 CharacterIndex handlePosition = 0u;
782 if( handleStopScrolling )
784 // The event.p2 and event.p3 are in decorator coords. Need to transforms to text coords.
785 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
786 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
788 handlePosition = GetClosestCursorIndex( xPosition, yPosition );
791 if( Event::GRAB_HANDLE_EVENT == event.type )
793 mEventData->mUpdateCursorPosition = true;
794 mEventData->mUpdateInputStyle = true;
796 if( !IsClipboardEmpty() )
798 ChangeState( EventData::EDITING_WITH_PASTE_POPUP ); // Moving grabhandle will show Paste Popup
801 if( handleStopScrolling )
803 mEventData->mScrollAfterUpdatePosition = mEventData->mPrimaryCursorPosition != handlePosition;
804 mEventData->mPrimaryCursorPosition = handlePosition;
807 else if( Event::LEFT_SELECTION_HANDLE_EVENT == event.type )
809 ChangeState( EventData::SELECTING );
811 if( handleStopScrolling )
813 mEventData->mUpdateLeftSelectionPosition = ( mEventData->mRightSelectionPosition != handlePosition );
814 mEventData->mScrollAfterUpdatePosition = mEventData->mUpdateLeftSelectionPosition;
816 if( mEventData->mUpdateLeftSelectionPosition )
818 mEventData->mLeftSelectionPosition = handlePosition;
822 else if( Event::RIGHT_SELECTION_HANDLE_EVENT == event.type )
824 ChangeState( EventData::SELECTING );
826 if( handleStopScrolling )
828 mEventData->mUpdateRightSelectionPosition = ( mEventData->mLeftSelectionPosition != handlePosition );
829 mEventData->mScrollAfterUpdatePosition = mEventData->mUpdateRightSelectionPosition;
830 if( mEventData->mUpdateRightSelectionPosition )
832 mEventData->mRightSelectionPosition = handlePosition;
837 mEventData->mDecoratorUpdated = true;
838 } // end ( ( HANDLE_RELEASED == state ) || ( HANDLE_STOP_SCROLLING == state ) )
839 else if( HANDLE_SCROLLING == state )
841 const float xSpeed = event.p2.mFloat;
842 const Vector2& actualSize = mVisualModel->GetLayoutSize();
843 const Vector2 currentScrollPosition = mEventData->mScrollPosition;
845 mEventData->mScrollPosition.x += xSpeed;
847 ClampHorizontalScroll( actualSize );
849 bool endOfScroll = false;
850 if( Vector2::ZERO == ( currentScrollPosition - mEventData->mScrollPosition ) )
852 // Notify the decorator there is no more text to scroll.
853 // The decorator won't send more scroll events.
854 mEventData->mDecorator->NotifyEndOfScroll();
855 // Still need to set the position of the handle.
859 // Set the position of the handle.
860 const bool scrollRightDirection = xSpeed > 0.f;
861 const bool leftSelectionHandleEvent = Event::LEFT_SELECTION_HANDLE_EVENT == event.type;
862 const bool rightSelectionHandleEvent = Event::RIGHT_SELECTION_HANDLE_EVENT == event.type;
864 if( Event::GRAB_HANDLE_EVENT == event.type )
866 ChangeState( EventData::GRAB_HANDLE_PANNING );
868 Vector2 position = mEventData->mDecorator->GetPosition( GRAB_HANDLE );
870 // Position the grag handle close to either the left or right edge.
871 position.x = scrollRightDirection ? 0.f : mVisualModel->mControlSize.width;
873 // Get the new handle position.
874 // The grab handle's position is in decorator coords. Need to transforms to text coords.
875 const CharacterIndex handlePosition = GetClosestCursorIndex( position.x - mEventData->mScrollPosition.x - mAlignmentOffset.x,
876 position.y - mEventData->mScrollPosition.y - mAlignmentOffset.y );
878 mEventData->mUpdateCursorPosition = mEventData->mPrimaryCursorPosition != handlePosition;
879 mEventData->mScrollAfterUpdatePosition = mEventData->mUpdateCursorPosition;
880 mEventData->mPrimaryCursorPosition = handlePosition;
881 mEventData->mUpdateInputStyle = mEventData->mUpdateCursorPosition;
883 else if( leftSelectionHandleEvent || rightSelectionHandleEvent )
885 // TODO: This is recalculating the selection box every time the text is scrolled with the selection handles.
886 // Think if something can be done to save power.
888 ChangeState( EventData::SELECTION_HANDLE_PANNING );
890 Vector2 position = mEventData->mDecorator->GetPosition( leftSelectionHandleEvent ? Text::LEFT_SELECTION_HANDLE : Text::RIGHT_SELECTION_HANDLE );
892 // Position the selection handle close to either the left or right edge.
893 position.x = scrollRightDirection ? 0.f : mVisualModel->mControlSize.width;
895 // Get the new handle position.
896 // The selection handle's position is in decorator coords. Need to transforms to text coords.
897 const CharacterIndex handlePosition = GetClosestCursorIndex( position.x - mEventData->mScrollPosition.x - mAlignmentOffset.x,
898 position.y - mEventData->mScrollPosition.y - mAlignmentOffset.y );
900 if( leftSelectionHandleEvent )
902 const bool differentHandles = ( mEventData->mLeftSelectionPosition != handlePosition ) && ( mEventData->mRightSelectionPosition != handlePosition );
903 mEventData->mUpdateLeftSelectionPosition = endOfScroll || differentHandles;
904 if( differentHandles )
906 mEventData->mLeftSelectionPosition = handlePosition;
911 const bool differentHandles = ( mEventData->mRightSelectionPosition != handlePosition ) && ( mEventData->mLeftSelectionPosition != handlePosition );
912 mEventData->mUpdateRightSelectionPosition = endOfScroll || differentHandles;
913 if( differentHandles )
915 mEventData->mRightSelectionPosition = handlePosition;
919 if( mEventData->mUpdateLeftSelectionPosition || mEventData->mUpdateRightSelectionPosition )
921 RepositionSelectionHandles();
923 mEventData->mScrollAfterUpdatePosition = true;
926 mEventData->mDecoratorUpdated = true;
927 } // end ( HANDLE_SCROLLING == state )
930 void Controller::Impl::OnSelectEvent( const Event& event )
932 if( NULL == mEventData )
934 // Nothing to do if there is no text.
938 if( mEventData->mSelectionEnabled )
940 // The event.p2 and event.p3 are in decorator coords. Need to transforms to text coords.
941 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
942 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
944 // Calculates the logical position from the x,y coords.
945 RepositionSelectionHandles( xPosition,
948 mEventData->mUpdateLeftSelectionPosition = true;
949 mEventData->mUpdateRightSelectionPosition = true;
951 mEventData->mScrollAfterUpdatePosition = ( mEventData->mLeftSelectionPosition != mEventData->mRightSelectionPosition );
955 void Controller::Impl::OnSelectAllEvent()
957 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "OnSelectAllEvent mEventData->mSelectionEnabled%s \n", mEventData->mSelectionEnabled?"true":"false");
959 if( NULL == mEventData )
961 // Nothing to do if there is no text.
965 if( mEventData->mSelectionEnabled )
967 mEventData->mLeftSelectionPosition = 0u;
968 mEventData->mRightSelectionPosition = mLogicalModel->mText.Count();
970 mEventData->mScrollAfterUpdatePosition = true;
971 mEventData->mUpdateLeftSelectionPosition = true;
972 mEventData->mUpdateRightSelectionPosition = true;
976 void Controller::Impl::RetrieveSelection( std::string& selectedText, bool deleteAfterRetrieval )
978 if( mEventData->mLeftSelectionPosition == mEventData->mRightSelectionPosition )
980 // Nothing to select if handles are in the same place.
981 selectedText.clear();
985 const bool handlesCrossed = mEventData->mLeftSelectionPosition > mEventData->mRightSelectionPosition;
987 //Get start and end position of selection
988 const CharacterIndex startOfSelectedText = handlesCrossed ? mEventData->mRightSelectionPosition : mEventData->mLeftSelectionPosition;
989 const Length lengthOfSelectedText = ( handlesCrossed ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition ) - startOfSelectedText;
991 // Validate the start and end selection points
992 if( ( startOfSelectedText + lengthOfSelectedText ) <= mLogicalModel->mText.Count() )
994 //Get text as a UTF8 string
995 Vector<Character>& utf32Characters = mLogicalModel->mText;
997 Utf32ToUtf8( &utf32Characters[startOfSelectedText], lengthOfSelectedText, selectedText );
999 if( deleteAfterRetrieval ) // Only delete text if copied successfully
1001 // Set as input style the style of the first deleted character.
1002 mLogicalModel->RetrieveStyle( startOfSelectedText, mEventData->mInputStyle );
1004 mLogicalModel->UpdateTextStyleRuns( startOfSelectedText, -static_cast<int>( lengthOfSelectedText ) );
1006 // Delete text between handles
1007 Vector<Character>& currentText = mLogicalModel->mText;
1009 Vector<Character>::Iterator first = currentText.Begin() + startOfSelectedText;
1010 Vector<Character>::Iterator last = first + lengthOfSelectedText;
1011 currentText.Erase( first, last );
1013 // Scroll after delete.
1014 mEventData->mPrimaryCursorPosition = handlesCrossed ? mEventData->mRightSelectionPosition : mEventData->mLeftSelectionPosition;
1015 mEventData->mScrollAfterDelete = true;
1017 // Udpade the cursor position and the decorator.
1018 // Scroll after the position is updated if is not scrolling after delete.
1019 mEventData->mUpdateCursorPosition = true;
1020 mEventData->mScrollAfterUpdatePosition = !mEventData->mScrollAfterDelete;
1021 mEventData->mDecoratorUpdated = true;
1025 void Controller::Impl::ShowClipboard()
1029 mClipboard.ShowClipboard();
1033 void Controller::Impl::HideClipboard()
1037 mClipboard.HideClipboard();
1041 bool Controller::Impl::CopyStringToClipboard( std::string& source )
1043 //Send string to clipboard
1044 return ( mClipboard && mClipboard.SetItem( source ) );
1047 void Controller::Impl::SendSelectionToClipboard( bool deleteAfterSending )
1049 std::string selectedText;
1050 RetrieveSelection( selectedText, deleteAfterSending );
1051 CopyStringToClipboard( selectedText );
1052 ChangeState( EventData::EDITING );
1055 void Controller::Impl::GetTextFromClipboard( unsigned int itemIndex, std::string& retrievedString )
1059 retrievedString = mClipboard.GetItem( itemIndex );
1063 void Controller::Impl::RepositionSelectionHandles()
1065 CharacterIndex selectionStart = mEventData->mLeftSelectionPosition;
1066 CharacterIndex selectionEnd = mEventData->mRightSelectionPosition;
1068 if( selectionStart == selectionEnd )
1070 // Nothing to select if handles are in the same place.
1074 mEventData->mDecorator->ClearHighlights();
1076 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
1077 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
1078 const GlyphInfo* const glyphsBuffer = mVisualModel->mGlyphs.Begin();
1079 const Vector2* const positionsBuffer = mVisualModel->mGlyphPositions.Begin();
1080 const Length* const charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
1081 const CharacterIndex* const glyphToCharacterBuffer = mVisualModel->mGlyphsToCharacters.Begin();
1082 const CharacterDirection* const modelCharacterDirectionsBuffer = ( 0u != mLogicalModel->mCharacterDirections.Count() ) ? mLogicalModel->mCharacterDirections.Begin() : NULL;
1084 // TODO: Better algorithm to create the highlight box.
1085 // TODO: Multi-line.
1087 // Get the height of the line.
1088 const Vector<LineRun>& lines = mVisualModel->mLines;
1089 const LineRun& firstLine = *lines.Begin();
1090 const float height = firstLine.ascender + -firstLine.descender;
1092 const bool isLastCharacter = selectionEnd >= mLogicalModel->mText.Count();
1093 const bool startDirection = ( ( NULL == modelCharacterDirectionsBuffer ) ? false : *( modelCharacterDirectionsBuffer + selectionStart ) );
1094 const bool endDirection = ( ( NULL == modelCharacterDirectionsBuffer ) ? false : *( modelCharacterDirectionsBuffer + ( selectionEnd - ( isLastCharacter ? 1u : 0u ) ) ) );
1096 // Swap the indices if the start is greater than the end.
1097 const bool indicesSwapped = selectionStart > selectionEnd;
1099 // Tell the decorator to flip the selection handles if needed.
1100 mEventData->mDecorator->SetSelectionHandleFlipState( indicesSwapped, startDirection, endDirection );
1102 if( indicesSwapped )
1104 std::swap( selectionStart, selectionEnd );
1107 // Get the indices to the first and last selected glyphs.
1108 const CharacterIndex selectionEndMinusOne = selectionEnd - 1u;
1109 const GlyphIndex glyphStart = *( charactersToGlyphBuffer + selectionStart );
1110 const Length numberOfGlyphs = *( glyphsPerCharacterBuffer + selectionEndMinusOne );
1111 const GlyphIndex glyphEnd = *( charactersToGlyphBuffer + selectionEndMinusOne ) + ( ( numberOfGlyphs > 0 ) ? numberOfGlyphs - 1u : 0u );
1113 // Check if the first glyph is a ligature that must be broken like Latin ff, fi, or Arabic ﻻ, etc which needs special code.
1114 const Length numberOfCharactersStart = *( charactersPerGlyphBuffer + glyphStart );
1115 bool splitStartGlyph = ( numberOfCharactersStart > 1u ) && HasLigatureMustBreak( mLogicalModel->GetScript( selectionStart ) );
1117 // Check if the last glyph is a ligature that must be broken like Latin ff, fi, or Arabic ﻻ, etc which needs special code.
1118 const Length numberOfCharactersEnd = *( charactersPerGlyphBuffer + glyphEnd );
1119 bool splitEndGlyph = ( glyphStart != glyphEnd ) && ( numberOfCharactersEnd > 1u ) && HasLigatureMustBreak( mLogicalModel->GetScript( selectionEndMinusOne ) );
1121 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
1123 // Traverse the glyphs.
1124 for( GlyphIndex index = glyphStart; index <= glyphEnd; ++index )
1126 const GlyphInfo& glyph = *( glyphsBuffer + index );
1127 const Vector2& position = *( positionsBuffer + index );
1129 if( splitStartGlyph )
1131 // 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.
1133 const float glyphAdvance = glyph.advance / static_cast<float>( numberOfCharactersStart );
1134 const CharacterIndex interGlyphIndex = selectionStart - *( glyphToCharacterBuffer + glyphStart );
1135 // Get the direction of the character.
1136 CharacterDirection isCurrentRightToLeft = false;
1137 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
1139 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + selectionStart );
1142 // The end point could be in the middle of the ligature.
1143 // Calculate the number of characters selected.
1144 const Length numberOfCharacters = ( glyphStart == glyphEnd ) ? ( selectionEnd - selectionStart ) : ( numberOfCharactersStart - interGlyphIndex );
1146 const float xPosition = position.x - glyph.xBearing + offset.x + glyphAdvance * static_cast<float>( isCurrentRightToLeft ? ( numberOfCharactersStart - interGlyphIndex - numberOfCharacters ) : interGlyphIndex );
1148 mEventData->mDecorator->AddHighlight( xPosition,
1150 xPosition + static_cast<float>( numberOfCharacters ) * glyphAdvance,
1151 offset.y + height );
1153 splitStartGlyph = false;
1157 if( splitEndGlyph && ( index == glyphEnd ) )
1159 // 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.
1161 const float glyphAdvance = glyph.advance / static_cast<float>( numberOfCharactersEnd );
1162 const CharacterIndex interGlyphIndex = selectionEnd - *( glyphToCharacterBuffer + glyphEnd );
1163 // Get the direction of the character.
1164 CharacterDirection isCurrentRightToLeft = false;
1165 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
1167 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + selectionEnd );
1170 const Length numberOfCharacters = numberOfCharactersEnd - interGlyphIndex;
1172 const float xPosition = position.x - glyph.xBearing + offset.x + ( isCurrentRightToLeft ? ( glyphAdvance * static_cast<float>( numberOfCharacters ) ) : 0.f );
1173 mEventData->mDecorator->AddHighlight( xPosition,
1175 xPosition + static_cast<float>( interGlyphIndex ) * glyphAdvance,
1176 offset.y + height );
1178 splitEndGlyph = false;
1182 const float xPosition = position.x - glyph.xBearing + offset.x;
1183 mEventData->mDecorator->AddHighlight( xPosition,
1185 xPosition + glyph.advance,
1186 offset.y + height );
1189 CursorInfo primaryCursorInfo;
1190 GetCursorPosition( mEventData->mLeftSelectionPosition,
1191 primaryCursorInfo );
1193 CursorInfo secondaryCursorInfo;
1194 GetCursorPosition( mEventData->mRightSelectionPosition,
1195 secondaryCursorInfo );
1197 const Vector2 primaryPosition = primaryCursorInfo.primaryPosition + offset;
1198 const Vector2 secondaryPosition = secondaryCursorInfo.primaryPosition + offset;
1200 mEventData->mDecorator->SetPosition( LEFT_SELECTION_HANDLE,
1202 primaryCursorInfo.lineOffset + offset.y,
1203 primaryCursorInfo.lineHeight );
1205 mEventData->mDecorator->SetPosition( RIGHT_SELECTION_HANDLE,
1206 secondaryPosition.x,
1207 secondaryCursorInfo.lineOffset + offset.y,
1208 secondaryCursorInfo.lineHeight );
1210 // Cursor to be positioned at end of selection so if selection interrupted and edit mode restarted the cursor will be at end of selection
1211 mEventData->mPrimaryCursorPosition = ( indicesSwapped ) ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition;
1213 // Set the flag to update the decorator.
1214 mEventData->mDecoratorUpdated = true;
1217 void Controller::Impl::RepositionSelectionHandles( float visualX, float visualY )
1219 if( NULL == mEventData )
1221 // Nothing to do if there is no text input.
1225 if( IsShowingPlaceholderText() )
1227 // Nothing to do if there is the place-holder text.
1231 const Length numberOfGlyphs = mVisualModel->mGlyphs.Count();
1232 const Length numberOfLines = mVisualModel->mLines.Count();
1233 if( ( 0 == numberOfGlyphs ) ||
1234 ( 0 == numberOfLines ) )
1236 // Nothing to do if there is no text.
1240 // Find which word was selected
1241 CharacterIndex selectionStart( 0 );
1242 CharacterIndex selectionEnd( 0 );
1243 FindSelectionIndices( visualX, visualY, selectionStart, selectionEnd );
1244 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "%p selectionStart %d selectionEnd %d\n", this, selectionStart, selectionEnd );
1246 if( selectionStart == selectionEnd )
1248 ChangeState( EventData::EDITING );
1249 // Nothing to select. i.e. a white space, out of bounds
1253 mEventData->mLeftSelectionPosition = selectionStart;
1254 mEventData->mRightSelectionPosition = selectionEnd;
1257 void Controller::Impl::SetPopupButtons()
1260 * Sets the Popup buttons to be shown depending on State.
1262 * If SELECTING : CUT & COPY + ( PASTE & CLIPBOARD if content available to paste )
1264 * If EDITING_WITH_POPUP : SELECT & SELECT_ALL
1267 TextSelectionPopup::Buttons buttonsToShow = TextSelectionPopup::NONE;
1269 if( EventData::SELECTING == mEventData->mState )
1271 buttonsToShow = TextSelectionPopup::Buttons( TextSelectionPopup::CUT | TextSelectionPopup::COPY );
1273 if( !IsClipboardEmpty() )
1275 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
1276 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::CLIPBOARD ) );
1279 if( !mEventData->mAllTextSelected )
1281 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::SELECT_ALL ) );
1284 else if( EventData::EDITING_WITH_POPUP == mEventData->mState )
1286 if( mLogicalModel->mText.Count() && !IsShowingPlaceholderText() )
1288 buttonsToShow = TextSelectionPopup::Buttons( TextSelectionPopup::SELECT | TextSelectionPopup::SELECT_ALL );
1291 if( !IsClipboardEmpty() )
1293 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
1294 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::CLIPBOARD ) );
1297 else if( EventData::EDITING_WITH_PASTE_POPUP == mEventData->mState )
1299 if ( !IsClipboardEmpty() )
1301 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
1302 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::CLIPBOARD ) );
1306 mEventData->mDecorator->SetEnabledPopupButtons( buttonsToShow );
1309 void Controller::Impl::ChangeState( EventData::State newState )
1311 if( NULL == mEventData )
1313 // Nothing to do if there is no text input.
1317 DALI_LOG_INFO( gLogFilter, Debug::General, "ChangeState state:%d newstate:%d\n", mEventData->mState, newState );
1319 if( mEventData->mState != newState )
1321 mEventData->mState = newState;
1323 if( EventData::INACTIVE == mEventData->mState )
1325 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
1326 mEventData->mDecorator->StopCursorBlink();
1327 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1328 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1329 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1330 mEventData->mDecorator->SetPopupActive( false );
1331 mEventData->mDecoratorUpdated = true;
1334 else if( EventData::INTERRUPTED == mEventData->mState)
1336 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1337 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1338 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1339 mEventData->mDecorator->SetPopupActive( false );
1340 mEventData->mDecoratorUpdated = true;
1343 else if( EventData::SELECTING == mEventData->mState )
1345 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
1346 mEventData->mDecorator->StopCursorBlink();
1347 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1348 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true );
1349 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true );
1350 if( mEventData->mGrabHandlePopupEnabled )
1353 mEventData->mDecorator->SetPopupActive( true );
1355 mEventData->mDecoratorUpdated = true;
1357 else if( EventData::EDITING == mEventData->mState )
1359 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1360 if( mEventData->mCursorBlinkEnabled )
1362 mEventData->mDecorator->StartCursorBlink();
1364 // Grab handle is not shown until a tap is received whilst EDITING
1365 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1366 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1367 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1368 if( mEventData->mGrabHandlePopupEnabled )
1370 mEventData->mDecorator->SetPopupActive( false );
1372 mEventData->mDecoratorUpdated = true;
1375 else if( EventData::EDITING_WITH_POPUP == mEventData->mState )
1377 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "EDITING_WITH_POPUP \n", newState );
1379 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1380 if( mEventData->mCursorBlinkEnabled )
1382 mEventData->mDecorator->StartCursorBlink();
1384 if( mEventData->mSelectionEnabled )
1386 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1387 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1391 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
1393 if( mEventData->mGrabHandlePopupEnabled )
1396 mEventData->mDecorator->SetPopupActive( true );
1399 mEventData->mDecoratorUpdated = true;
1401 else if( EventData::EDITING_WITH_GRAB_HANDLE == mEventData->mState )
1403 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "EDITING_WITH_GRAB_HANDLE \n", newState );
1405 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1406 if( mEventData->mCursorBlinkEnabled )
1408 mEventData->mDecorator->StartCursorBlink();
1410 // Grab handle is not shown until a tap is received whilst EDITING
1411 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
1412 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1413 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1414 if( mEventData->mGrabHandlePopupEnabled )
1416 mEventData->mDecorator->SetPopupActive( false );
1418 mEventData->mDecoratorUpdated = true;
1421 else if( EventData::SELECTION_HANDLE_PANNING == mEventData->mState )
1423 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
1424 mEventData->mDecorator->StopCursorBlink();
1425 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1426 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true );
1427 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true );
1428 if( mEventData->mGrabHandlePopupEnabled )
1430 mEventData->mDecorator->SetPopupActive( false );
1432 mEventData->mDecoratorUpdated = true;
1434 else if( EventData::GRAB_HANDLE_PANNING == mEventData->mState )
1436 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "GRAB_HANDLE_PANNING \n", newState );
1438 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1439 if( mEventData->mCursorBlinkEnabled )
1441 mEventData->mDecorator->StartCursorBlink();
1443 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
1444 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1445 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1446 if( mEventData->mGrabHandlePopupEnabled )
1448 mEventData->mDecorator->SetPopupActive( false );
1450 mEventData->mDecoratorUpdated = true;
1452 else if( EventData::EDITING_WITH_PASTE_POPUP == mEventData->mState )
1454 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "EDITING_WITH_PASTE_POPUP \n", newState );
1456 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1457 if( mEventData->mCursorBlinkEnabled )
1459 mEventData->mDecorator->StartCursorBlink();
1462 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
1463 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1464 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1466 if( mEventData->mGrabHandlePopupEnabled )
1469 mEventData->mDecorator->SetPopupActive( true );
1472 mEventData->mDecoratorUpdated = true;
1477 LineIndex Controller::Impl::GetClosestLine( float y ) const
1479 float totalHeight = 0.f;
1480 LineIndex lineIndex = 0u;
1482 const Vector<LineRun>& lines = mVisualModel->mLines;
1483 for( LineIndex endLine = lines.Count();
1484 lineIndex < endLine;
1487 const LineRun& lineRun = lines[lineIndex];
1488 totalHeight += lineRun.ascender + -lineRun.descender;
1489 if( y < totalHeight )
1495 if( lineIndex == 0 )
1503 void Controller::Impl::FindSelectionIndices( float visualX, float visualY, CharacterIndex& startIndex, CharacterIndex& endIndex )
1505 CharacterIndex hitCharacter = GetClosestCursorIndex( visualX, visualY );
1506 DALI_ASSERT_DEBUG( hitCharacter <= mLogicalModel->mText.Count() && "GetClosestCursorIndex returned out of bounds index" );
1508 if( mLogicalModel->mText.Count() == 0 )
1510 return; // if model empty
1513 if( hitCharacter >= mLogicalModel->mText.Count() )
1515 // Closest hit character is the last character.
1516 if( hitCharacter == mLogicalModel->mText.Count() )
1518 hitCharacter--; //Hit character index set to last character in logical model
1522 // hitCharacter is out of bounds
1527 startIndex = hitCharacter;
1528 endIndex = hitCharacter;
1529 bool isHitCharacterWhitespace = TextAbstraction::IsWhiteSpace( mLogicalModel->mText[hitCharacter] );
1531 // Find the start and end of the text
1532 for( startIndex = hitCharacter; startIndex > 0; --startIndex )
1534 if( isHitCharacterWhitespace != TextAbstraction::IsWhiteSpace( mLogicalModel->mText[ startIndex-1 ] ) )
1539 const CharacterIndex pastTheEnd = mLogicalModel->mText.Count();
1540 for( endIndex = hitCharacter + 1u; endIndex < pastTheEnd; ++endIndex )
1542 if( isHitCharacterWhitespace != TextAbstraction::IsWhiteSpace( mLogicalModel->mText[ endIndex ] ) )
1549 CharacterIndex Controller::Impl::GetClosestCursorIndex( float visualX,
1552 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "GetClosestCursorIndex %p closest visualX %f visualY %f\n", this, visualX, visualY );
1554 if( NULL == mEventData )
1556 // Nothing to do if there is no text input.
1560 CharacterIndex logicalIndex = 0u;
1562 const Length numberOfGlyphs = mVisualModel->mGlyphs.Count();
1563 const Length numberOfLines = mVisualModel->mLines.Count();
1564 if( ( 0 == numberOfGlyphs ) ||
1565 ( 0 == numberOfLines ) )
1567 return logicalIndex;
1570 // Find which line is closest
1571 const LineIndex lineIndex = GetClosestLine( visualY );
1572 const LineRun& line = mVisualModel->mLines[lineIndex];
1574 // Get the positions of the glyphs.
1575 const Vector<Vector2>& positions = mVisualModel->mGlyphPositions;
1576 const Vector2* const positionsBuffer = positions.Begin();
1578 // Get the visual to logical conversion tables.
1579 const CharacterIndex* const visualToLogicalBuffer = ( 0u != mLogicalModel->mVisualToLogicalMap.Count() ) ? mLogicalModel->mVisualToLogicalMap.Begin() : NULL;
1580 const CharacterIndex* const visualToLogicalCursorBuffer = mLogicalModel->mVisualToLogicalCursorMap.Begin();
1582 // Get the character to glyph conversion table.
1583 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
1585 // Get the glyphs per character table.
1586 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
1588 // If the vector is void, there is no right to left characters.
1589 const bool hasRightToLeftCharacters = NULL != visualToLogicalBuffer;
1591 const CharacterIndex startCharacter = line.characterRun.characterIndex;
1592 const CharacterIndex endCharacter = line.characterRun.characterIndex + line.characterRun.numberOfCharacters;
1593 DALI_ASSERT_DEBUG( endCharacter <= mLogicalModel->mText.Count() && "Invalid line info" );
1595 // Whether there is a hit on a glyph.
1596 bool matched = false;
1598 // Traverses glyphs in visual order. To do that use the visual to logical conversion table.
1599 CharacterIndex visualIndex = startCharacter;
1600 Length numberOfCharacters = 0u;
1601 for( ; !matched && ( visualIndex < endCharacter ); ++visualIndex )
1603 // The character in logical order.
1604 const CharacterIndex characterLogicalOrderIndex = hasRightToLeftCharacters ? *( visualToLogicalBuffer + visualIndex ) : visualIndex;
1606 // Get the script of the character.
1607 const Script script = mLogicalModel->GetScript( characterLogicalOrderIndex );
1609 // The number of glyphs for that character
1610 const Length numberOfGlyphs = *( glyphsPerCharacterBuffer + characterLogicalOrderIndex );
1611 ++numberOfCharacters;
1614 if( 0u != numberOfGlyphs )
1616 // Get the first character/glyph of the group of glyphs.
1617 const CharacterIndex firstVisualCharacterIndex = 1u + visualIndex - numberOfCharacters;
1618 const CharacterIndex firstLogicalCharacterIndex = hasRightToLeftCharacters ? *( visualToLogicalBuffer + firstVisualCharacterIndex ) : firstVisualCharacterIndex;
1619 const GlyphIndex firstLogicalGlyphIndex = *( charactersToGlyphBuffer + firstLogicalCharacterIndex );
1621 // Get the metrics for the group of glyphs.
1622 GlyphMetrics glyphMetrics;
1623 GetGlyphsMetrics( firstLogicalGlyphIndex,
1629 // Get the position of the first glyph.
1630 const Vector2& position = *( positionsBuffer + firstLogicalGlyphIndex );
1632 // Whether the glyph can be split, like Latin ligatures fi, ff or Arabic ﻻ.
1633 const bool isInterglyphIndex = ( numberOfCharacters > numberOfGlyphs ) && HasLigatureMustBreak( script );
1634 const Length numberOfBlocks = isInterglyphIndex ? numberOfCharacters : 1u;
1635 const float glyphAdvance = glyphMetrics.advance / static_cast<float>( numberOfBlocks );
1637 GlyphIndex index = 0u;
1638 for( ; !matched && ( index < numberOfBlocks ); ++index )
1640 // Find the mid-point of the area containing the glyph
1641 const float glyphCenter = -glyphMetrics.xBearing + position.x + ( static_cast<float>( index ) + 0.5f ) * glyphAdvance;
1643 if( visualX < glyphCenter )
1652 visualIndex = firstVisualCharacterIndex + index;
1656 numberOfCharacters = 0u;
1662 // Return the logical position of the cursor in characters.
1666 visualIndex = endCharacter;
1669 logicalIndex = hasRightToLeftCharacters ? *( visualToLogicalCursorBuffer + visualIndex ) : visualIndex;
1670 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "%p closest visualIndex %d logicalIndex %d\n", this, visualIndex, logicalIndex );
1672 DALI_ASSERT_DEBUG( ( logicalIndex <= mLogicalModel->mText.Count() && logicalIndex >= 0 ) && "GetClosestCursorIndex - Out of bounds index" );
1674 return logicalIndex;
1677 void Controller::Impl::GetCursorPosition( CharacterIndex logical,
1678 CursorInfo& cursorInfo )
1680 // TODO: Check for multiline with \n, etc...
1682 const Length numberOfCharacters = mLogicalModel->mText.Count();
1683 if( !IsShowingRealText() )
1685 // Do not want to use the place-holder text to set the cursor position.
1687 // Use the line's height of the font's family set to set the cursor's size.
1688 // If there is no font's family set, use the default font.
1689 // Use the current alignment to place the cursor at the beginning, center or end of the box.
1691 cursorInfo.lineOffset = 0.f;
1692 cursorInfo.lineHeight = GetDefaultFontLineHeight();
1693 cursorInfo.primaryCursorHeight = cursorInfo.lineHeight;
1695 switch( mLayoutEngine.GetHorizontalAlignment() )
1697 case LayoutEngine::HORIZONTAL_ALIGN_BEGIN:
1699 cursorInfo.primaryPosition.x = 0.f;
1702 case LayoutEngine::HORIZONTAL_ALIGN_CENTER:
1704 cursorInfo.primaryPosition.x = floorf( 0.5f * mVisualModel->mControlSize.width );
1707 case LayoutEngine::HORIZONTAL_ALIGN_END:
1709 cursorInfo.primaryPosition.x = mVisualModel->mControlSize.width - mEventData->mDecorator->GetCursorWidth();
1714 switch( mLayoutEngine.GetVerticalAlignment() )
1716 case LayoutEngine::VERTICAL_ALIGN_TOP:
1718 cursorInfo.primaryPosition.y = 0.f;
1721 case LayoutEngine::VERTICAL_ALIGN_CENTER:
1723 cursorInfo.primaryPosition.y = floorf( 0.5f * ( mVisualModel->mControlSize.height - cursorInfo.lineHeight ) );
1726 case LayoutEngine::VERTICAL_ALIGN_BOTTOM:
1728 cursorInfo.primaryPosition.y = mVisualModel->mControlSize.height - cursorInfo.lineHeight;
1733 // Nothing else to do.
1737 // Check if the logical position is the first or the last one of the text.
1738 const bool isFirstPosition = 0u == logical;
1739 const bool isLastPosition = numberOfCharacters == logical;
1741 // 'logical' is the logical 'cursor' index.
1742 // Get the next and current logical 'character' index.
1743 const CharacterIndex nextCharacterIndex = logical;
1744 const CharacterIndex characterIndex = isFirstPosition ? logical : logical - 1u;
1746 // Get the direction of the character and the next one.
1747 const CharacterDirection* const modelCharacterDirectionsBuffer = ( 0u != mLogicalModel->mCharacterDirections.Count() ) ? mLogicalModel->mCharacterDirections.Begin() : NULL;
1749 CharacterDirection isCurrentRightToLeft = false;
1750 CharacterDirection isNextRightToLeft = false;
1751 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
1753 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + characterIndex );
1754 isNextRightToLeft = *( modelCharacterDirectionsBuffer + nextCharacterIndex );
1757 // Get the line where the character is laid-out.
1758 const LineRun* const modelLines = mVisualModel->mLines.Begin();
1760 const LineIndex lineIndex = mVisualModel->GetLineOfCharacter( characterIndex );
1761 const LineRun& line = *( modelLines + lineIndex );
1763 // Get the paragraph's direction.
1764 const CharacterDirection isRightToLeftParagraph = line.direction;
1766 // Check whether there is an alternative position:
1768 cursorInfo.isSecondaryCursor = ( !isLastPosition && ( isCurrentRightToLeft != isNextRightToLeft ) ) ||
1769 ( isLastPosition && ( isRightToLeftParagraph != isCurrentRightToLeft ) );
1771 // Set the line offset and height.
1772 cursorInfo.lineOffset = 0.f;
1773 cursorInfo.lineHeight = line.ascender + -line.descender;
1775 // Calculate the primary cursor.
1777 CharacterIndex index = characterIndex;
1778 if( cursorInfo.isSecondaryCursor )
1780 // If there is a secondary position, the primary cursor may be in a different place than the logical index.
1782 if( isLastPosition )
1784 // The position of the cursor after the last character needs special
1785 // care depending on its direction and the direction of the paragraph.
1787 // Need to find the first character after the last character with the paragraph's direction.
1788 // i.e l0 l1 l2 r0 r1 should find r0.
1790 // TODO: check for more than one line!
1791 index = isRightToLeftParagraph ? line.characterRun.characterIndex : line.characterRun.characterIndex + line.characterRun.numberOfCharacters - 1u;
1792 index = mLogicalModel->GetLogicalCharacterIndex( index );
1796 index = ( isRightToLeftParagraph == isCurrentRightToLeft ) ? characterIndex : nextCharacterIndex;
1800 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
1801 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
1802 const Length* const charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
1803 const CharacterIndex* const glyphsToCharactersBuffer = mVisualModel->mGlyphsToCharacters.Begin();
1804 const Vector2* const glyphPositionsBuffer = mVisualModel->mGlyphPositions.Begin();
1806 // Convert the cursor position into the glyph position.
1807 const GlyphIndex primaryGlyphIndex = *( charactersToGlyphBuffer + index );
1808 const Length primaryNumberOfGlyphs = *( glyphsPerCharacterBuffer + index );
1809 const Length primaryNumberOfCharacters = *( charactersPerGlyphBuffer + primaryGlyphIndex );
1811 // Get the metrics for the group of glyphs.
1812 GlyphMetrics glyphMetrics;
1813 GetGlyphsMetrics( primaryGlyphIndex,
1814 primaryNumberOfGlyphs,
1819 // Whether to add the glyph's advance to the cursor position.
1820 // 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,
1821 // if the logical cursor is one, the position is the position of the first glyph and the advance is added.
1822 // A 'truth table' was build and an online Karnaugh map tool was used to simplify the logic.
1843 // Where F -> isFirstPosition
1844 // L -> isLastPosition
1845 // C -> isCurrentRightToLeft
1846 // P -> isRightToLeftParagraph
1847 // A -> Whether to add the glyph's advance.
1849 const bool addGlyphAdvance = ( ( isLastPosition && !isRightToLeftParagraph ) ||
1850 ( isFirstPosition && isRightToLeftParagraph ) ||
1851 ( !isFirstPosition && !isLastPosition && !isCurrentRightToLeft ) );
1853 float glyphAdvance = addGlyphAdvance ? glyphMetrics.advance : 0.f;
1855 if( !isLastPosition &&
1856 ( primaryNumberOfCharacters > 1u ) )
1858 const CharacterIndex firstIndex = *( glyphsToCharactersBuffer + primaryGlyphIndex );
1860 bool isCurrentRightToLeft = false;
1861 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
1863 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + index );
1866 Length numberOfGlyphAdvance = ( isFirstPosition ? 0u : 1u ) + characterIndex - firstIndex;
1867 if( isCurrentRightToLeft )
1869 numberOfGlyphAdvance = primaryNumberOfCharacters - numberOfGlyphAdvance;
1872 glyphAdvance = static_cast<float>( numberOfGlyphAdvance ) * glyphMetrics.advance / static_cast<float>( primaryNumberOfCharacters );
1875 // Get the glyph position and x bearing.
1876 const Vector2& primaryPosition = *( glyphPositionsBuffer + primaryGlyphIndex );
1878 // Set the primary cursor's height.
1879 cursorInfo.primaryCursorHeight = cursorInfo.isSecondaryCursor ? 0.5f * glyphMetrics.fontHeight : glyphMetrics.fontHeight;
1881 // Set the primary cursor's position.
1882 cursorInfo.primaryPosition.x = -glyphMetrics.xBearing + primaryPosition.x + glyphAdvance;
1883 cursorInfo.primaryPosition.y = line.ascender - glyphMetrics.ascender;
1885 // Calculate the secondary cursor.
1887 if( cursorInfo.isSecondaryCursor )
1889 // Set the secondary cursor's height.
1890 cursorInfo.secondaryCursorHeight = 0.5f * glyphMetrics.fontHeight;
1892 CharacterIndex index = characterIndex;
1893 if( !isLastPosition )
1895 index = ( isRightToLeftParagraph == isCurrentRightToLeft ) ? nextCharacterIndex : characterIndex;
1898 const GlyphIndex secondaryGlyphIndex = *( charactersToGlyphBuffer + index );
1899 const Length secondaryNumberOfGlyphs = *( glyphsPerCharacterBuffer + index );
1901 const Vector2& secondaryPosition = *( glyphPositionsBuffer + secondaryGlyphIndex );
1903 GetGlyphsMetrics( secondaryGlyphIndex,
1904 secondaryNumberOfGlyphs,
1909 // Set the secondary cursor's position.
1910 cursorInfo.secondaryPosition.x = -glyphMetrics.xBearing + secondaryPosition.x + ( isCurrentRightToLeft ? 0.f : glyphMetrics.advance );
1911 cursorInfo.secondaryPosition.y = cursorInfo.lineHeight - cursorInfo.secondaryCursorHeight - line.descender - ( glyphMetrics.fontHeight - glyphMetrics.ascender );
1914 if( LayoutEngine::MULTI_LINE_BOX == mLayoutEngine.GetLayout() )
1916 // If the text is editable and multi-line, the cursor position after a white space shouldn't exceed the boundaries of the text control.
1918 // Note the white spaces laid-out at the end of the line might exceed the boundaries of the control.
1919 // The reason is a wrapped line must not start with a white space so they are laid-out at the end of the line.
1921 if( 0.f > cursorInfo.primaryPosition.x )
1923 cursorInfo.primaryPosition.x = 0.f;
1926 const float edgeWidth = mVisualModel->mControlSize.width - static_cast<float>( mEventData->mDecorator->GetCursorWidth() );
1927 if( cursorInfo.primaryPosition.x > edgeWidth )
1929 cursorInfo.primaryPosition.x = edgeWidth;
1934 CharacterIndex Controller::Impl::CalculateNewCursorIndex( CharacterIndex index ) const
1936 if( NULL == mEventData )
1938 // Nothing to do if there is no text input.
1942 CharacterIndex cursorIndex = mEventData->mPrimaryCursorPosition;
1944 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
1945 const Length* const charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
1947 GlyphIndex glyphIndex = *( charactersToGlyphBuffer + index );
1948 Length numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
1950 if( numberOfCharacters > 1u )
1952 const Script script = mLogicalModel->GetScript( index );
1953 if( HasLigatureMustBreak( script ) )
1955 // Prevents to jump the whole Latin ligatures like fi, ff, or Arabic ﻻ, ...
1956 numberOfCharacters = 1u;
1961 while( 0u == numberOfCharacters )
1964 numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
1968 if( index < mEventData->mPrimaryCursorPosition )
1970 cursorIndex -= numberOfCharacters;
1974 cursorIndex += numberOfCharacters;
1980 void Controller::Impl::UpdateCursorPosition( const CursorInfo& cursorInfo )
1982 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "-->Controller::UpdateCursorPosition %p\n", this );
1983 if( NULL == mEventData )
1985 // Nothing to do if there is no text input.
1986 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition no event data\n" );
1990 const Vector2 offset = mEventData->mScrollPosition + ( IsShowingRealText() ? mAlignmentOffset : Vector2::ZERO );
1991 const Vector2 cursorPosition = cursorInfo.primaryPosition + offset;
1993 // Sets the cursor position.
1994 mEventData->mDecorator->SetPosition( PRIMARY_CURSOR,
1997 cursorInfo.primaryCursorHeight,
1998 cursorInfo.lineHeight );
1999 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Primary cursor position: %f,%f\n", cursorPosition.x, cursorPosition.y );
2001 // Sets the grab handle position.
2002 mEventData->mDecorator->SetPosition( GRAB_HANDLE,
2004 cursorInfo.lineOffset + offset.y,
2005 cursorInfo.lineHeight );
2007 if( cursorInfo.isSecondaryCursor )
2009 mEventData->mDecorator->SetPosition( SECONDARY_CURSOR,
2010 cursorInfo.secondaryPosition.x + offset.x,
2011 cursorInfo.secondaryPosition.y + offset.y,
2012 cursorInfo.secondaryCursorHeight,
2013 cursorInfo.lineHeight );
2014 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Secondary cursor position: %f,%f\n", cursorInfo.secondaryPosition.x + offset.x, cursorInfo.secondaryPosition.y + offset.y );
2017 // Set which cursors are active according the state.
2018 if( EventData::IsEditingState( mEventData->mState ) || ( EventData::GRAB_HANDLE_PANNING == mEventData->mState ) )
2020 if( cursorInfo.isSecondaryCursor )
2022 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_BOTH );
2026 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
2031 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
2034 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition\n" );
2037 void Controller::Impl::UpdateSelectionHandle( HandleType handleType,
2038 const CursorInfo& cursorInfo )
2040 if( ( LEFT_SELECTION_HANDLE != handleType ) &&
2041 ( RIGHT_SELECTION_HANDLE != handleType ) )
2046 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
2047 const Vector2 cursorPosition = cursorInfo.primaryPosition + offset;
2049 // Sets the handle's position.
2050 mEventData->mDecorator->SetPosition( handleType,
2052 cursorInfo.lineOffset + offset.y,
2053 cursorInfo.lineHeight );
2055 // If selection handle at start of the text and other at end of the text then all text is selected.
2056 const CharacterIndex startOfSelection = std::min( mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition );
2057 const CharacterIndex endOfSelection = std::max ( mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition );
2058 mEventData->mAllTextSelected = ( startOfSelection == 0 ) && ( endOfSelection == mLogicalModel->mText.Count() );
2061 void Controller::Impl::ClampHorizontalScroll( const Vector2& actualSize )
2063 // Clamp between -space & 0 (and the text alignment).
2065 if( actualSize.width > mVisualModel->mControlSize.width )
2067 const float space = ( actualSize.width - mVisualModel->mControlSize.width ) + mAlignmentOffset.x;
2068 mEventData->mScrollPosition.x = ( mEventData->mScrollPosition.x < -space ) ? -space : mEventData->mScrollPosition.x;
2069 mEventData->mScrollPosition.x = ( mEventData->mScrollPosition.x > -mAlignmentOffset.x ) ? -mAlignmentOffset.x : mEventData->mScrollPosition.x;
2071 mEventData->mDecoratorUpdated = true;
2075 mEventData->mScrollPosition.x = 0.f;
2079 void Controller::Impl::ClampVerticalScroll( const Vector2& actualSize )
2081 // Clamp between -space & 0 (and the text alignment).
2082 if( actualSize.height > mVisualModel->mControlSize.height )
2084 const float space = ( actualSize.height - mVisualModel->mControlSize.height ) + mAlignmentOffset.y;
2085 mEventData->mScrollPosition.y = ( mEventData->mScrollPosition.y < -space ) ? -space : mEventData->mScrollPosition.y;
2086 mEventData->mScrollPosition.y = ( mEventData->mScrollPosition.y > -mAlignmentOffset.y ) ? -mAlignmentOffset.y : mEventData->mScrollPosition.y;
2088 mEventData->mDecoratorUpdated = true;
2092 mEventData->mScrollPosition.y = 0.f;
2096 void Controller::Impl::ScrollToMakePositionVisible( const Vector2& position )
2098 // position is in actor's coords.
2099 const float positionEnd = position.x + ( mEventData->mDecorator ? mEventData->mDecorator->GetCursorWidth() : 0.f );
2101 // Transform the position to decorator coords.
2102 const float alignment = IsShowingRealText() ? mAlignmentOffset.x : 0.f;
2103 const float offset = mEventData->mScrollPosition.x + alignment;
2104 const float decoratorPositionBegin = position.x + offset;
2105 const float decoratorPositionEnd = positionEnd + offset;
2107 if( decoratorPositionBegin < 0.f )
2109 mEventData->mScrollPosition.x = -position.x - alignment;
2111 else if( decoratorPositionEnd > mVisualModel->mControlSize.width )
2113 mEventData->mScrollPosition.x = mVisualModel->mControlSize.width - positionEnd - alignment;
2117 void Controller::Impl::ScrollTextToMatchCursor( const CursorInfo& cursorInfo )
2119 // Get the current cursor position in decorator coords.
2120 const Vector2& currentCursorPosition = mEventData->mDecorator->GetPosition( PRIMARY_CURSOR );
2122 // Calculate the offset to match the cursor position before the character was deleted.
2123 mEventData->mScrollPosition.x = currentCursorPosition.x - cursorInfo.primaryPosition.x - mAlignmentOffset.x;
2125 ClampHorizontalScroll( mVisualModel->GetLayoutSize() );
2128 void Controller::Impl::RequestRelayout()
2130 mControlInterface.RequestTextRelayout();
2135 } // namespace Toolkit