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/glyph-metrics-helper.h>
30 #include <dali-toolkit/internal/text/multi-language-support.h>
31 #include <dali-toolkit/internal/text/segmentation.h>
32 #include <dali-toolkit/internal/text/shaper.h>
37 #if defined(DEBUG_ENABLED)
38 Debug::Filter* gLogFilter = Debug::Filter::New(Debug::NoLogging, true, "LOG_TEXT_CONTROLS");
52 EventData::EventData( DecoratorPtr decorator )
53 : mDecorator( decorator ),
55 mPlaceholderTextActive(),
56 mPlaceholderTextInactive(),
57 mPlaceholderTextColor( 0.8f, 0.8f, 0.8f, 0.8f ),
61 mPrimaryCursorPosition( 0u ),
62 mLeftSelectionPosition( 0u ),
63 mRightSelectionPosition( 0u ),
64 mPreEditStartPosition( 0u ),
66 mIsShowingPlaceholderText( false ),
67 mPreEditFlag( false ),
68 mDecoratorUpdated( false ),
69 mCursorBlinkEnabled( true ),
70 mGrabHandleEnabled( true ),
71 mGrabHandlePopupEnabled( true ),
72 mSelectionEnabled( true ),
73 mHorizontalScrollingEnabled( true ),
74 mVerticalScrollingEnabled( false ),
75 mUpdateCursorPosition( false ),
76 mUpdateLeftSelectionPosition( false ),
77 mUpdateRightSelectionPosition( false ),
78 mScrollAfterUpdatePosition( false ),
79 mScrollAfterDelete( false ),
80 mAllTextSelected( false ),
81 mUpdateInputStyle( false )
83 mImfManager = ImfManager::Get();
86 EventData::~EventData()
89 bool Controller::Impl::ProcessInputEvents()
91 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "-->Controller::ProcessInputEvents\n" );
92 if( NULL == mEventData )
94 // Nothing to do if there is no text input.
95 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::ProcessInputEvents no event data\n" );
99 if( mEventData->mDecorator )
101 for( std::vector<Event>::iterator iter = mEventData->mEventQueue.begin();
102 iter != mEventData->mEventQueue.end();
107 case Event::CURSOR_KEY_EVENT:
109 OnCursorKeyEvent( *iter );
112 case Event::TAP_EVENT:
117 case Event::LONG_PRESS_EVENT:
119 OnLongPressEvent( *iter );
122 case Event::PAN_EVENT:
127 case Event::GRAB_HANDLE_EVENT:
128 case Event::LEFT_SELECTION_HANDLE_EVENT:
129 case Event::RIGHT_SELECTION_HANDLE_EVENT: // Fall through
131 OnHandleEvent( *iter );
136 OnSelectEvent( *iter );
139 case Event::SELECT_ALL:
148 // The cursor must also be repositioned after inserts into the model
149 if( mEventData->mUpdateCursorPosition )
151 // Updates the cursor position and scrolls the text to make it visible.
152 CursorInfo cursorInfo;
153 GetCursorPosition( mEventData->mPrimaryCursorPosition,
156 if( mEventData->mScrollAfterUpdatePosition )
158 ScrollToMakePositionVisible( cursorInfo.primaryPosition );
159 mEventData->mScrollAfterUpdatePosition = false;
161 else if( mEventData->mScrollAfterDelete )
163 ScrollTextToMatchCursor( cursorInfo );
164 mEventData->mScrollAfterDelete = false;
167 UpdateCursorPosition( cursorInfo );
169 mEventData->mDecoratorUpdated = true;
170 mEventData->mUpdateCursorPosition = false;
174 bool leftScroll = false;
175 bool rightScroll = false;
177 CursorInfo leftHandleInfo;
178 CursorInfo rightHandleInfo;
180 if( mEventData->mUpdateLeftSelectionPosition )
182 GetCursorPosition( mEventData->mLeftSelectionPosition,
185 if( mEventData->mScrollAfterUpdatePosition )
187 ScrollToMakePositionVisible( leftHandleInfo.primaryPosition );
192 if( mEventData->mUpdateRightSelectionPosition )
194 GetCursorPosition( mEventData->mRightSelectionPosition,
197 if( mEventData->mScrollAfterUpdatePosition )
199 ScrollToMakePositionVisible( rightHandleInfo.primaryPosition );
204 if( mEventData->mUpdateLeftSelectionPosition )
206 UpdateSelectionHandle( LEFT_SELECTION_HANDLE,
210 mEventData->mDecoratorUpdated = true;
213 if( mEventData->mUpdateRightSelectionPosition )
215 UpdateSelectionHandle( RIGHT_SELECTION_HANDLE,
219 mEventData->mDecoratorUpdated = true;
222 if( mEventData->mUpdateLeftSelectionPosition || mEventData->mUpdateRightSelectionPosition )
224 RepositionSelectionHandles();
226 mEventData->mUpdateLeftSelectionPosition = false;
227 mEventData->mUpdateRightSelectionPosition = false;
230 if( leftScroll || rightScroll )
232 mEventData->mScrollAfterUpdatePosition = false;
236 if( mEventData->mUpdateInputStyle )
238 // Set the default style first.
239 RetrieveDefaultInputStyle( mEventData->mInputStyle );
241 // Get the character index from the cursor index.
242 const CharacterIndex styleIndex = ( mEventData->mPrimaryCursorPosition > 0u ) ? mEventData->mPrimaryCursorPosition - 1u : 0u;
244 // Retrieve the style from the style runs stored in the logical model.
245 mLogicalModel->RetrieveStyle( styleIndex, mEventData->mInputStyle );
247 mEventData->mUpdateInputStyle = false;
250 mEventData->mEventQueue.clear();
252 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::ProcessInputEvents\n" );
254 const bool decoratorUpdated = mEventData->mDecoratorUpdated;
255 mEventData->mDecoratorUpdated = false;
257 return decoratorUpdated;
260 void Controller::Impl::UpdateModel( OperationsMask operationsRequired )
262 DALI_LOG_INFO( gLogFilter, Debug::General, "Controller::UpdateModel\n" );
264 // Calculate the operations to be done.
265 const OperationsMask operations = static_cast<OperationsMask>( mOperationsPending & operationsRequired );
267 Vector<Character>& utf32Characters = mLogicalModel->mText;
269 const Length numberOfCharacters = utf32Characters.Count();
271 Vector<LineBreakInfo>& lineBreakInfo = mLogicalModel->mLineBreakInfo;
272 CharacterIndex startIndex = 0u;
273 Length requestedNumberOfCharacters = numberOfCharacters;
274 if( GET_LINE_BREAKS & operations )
276 // Retrieves the line break info. The line break info is used to split the text in 'paragraphs' to
277 // calculate the bidirectional info for each 'paragraph'.
278 // It's also used to layout the text (where it should be a new line) or to shape the text (text in different lines
279 // is not shaped together).
280 lineBreakInfo.Resize( numberOfCharacters, TextAbstraction::LINE_NO_BREAK );
282 SetLineBreakInfo( utf32Characters,
284 requestedNumberOfCharacters,
287 // Create the paragraph info.
288 mLogicalModel->CreateParagraphInfo( startIndex,
289 requestedNumberOfCharacters );
292 Vector<WordBreakInfo>& wordBreakInfo = mLogicalModel->mWordBreakInfo;
293 if( GET_WORD_BREAKS & operations )
295 // Retrieves the word break info. The word break info is used to layout the text (where to wrap the text in lines).
296 wordBreakInfo.Resize( numberOfCharacters, TextAbstraction::WORD_NO_BREAK );
298 SetWordBreakInfo( utf32Characters,
300 requestedNumberOfCharacters,
304 const bool getScripts = GET_SCRIPTS & operations;
305 const bool validateFonts = VALIDATE_FONTS & operations;
307 Vector<ScriptRun>& scripts = mLogicalModel->mScriptRuns;
308 Vector<FontRun>& validFonts = mLogicalModel->mFontRuns;
310 if( getScripts || validateFonts )
312 // Validates the fonts assigned by the application or assigns default ones.
313 // It makes sure all the characters are going to be rendered by the correct font.
314 MultilanguageSupport multilanguageSupport = MultilanguageSupport::Get();
318 // Retrieves the scripts used in the text.
319 multilanguageSupport.SetScripts( utf32Characters,
321 requestedNumberOfCharacters,
327 // Validate the fonts set through the mark-up string.
328 Vector<FontDescriptionRun>& fontDescriptionRuns = mLogicalModel->mFontDescriptionRuns;
330 // Get the default font id.
331 const FontId defaultFontId = ( NULL == mFontDefaults ) ? 0u : mFontDefaults->GetFontId( mFontClient );
333 // Validates the fonts. If there is a character with no assigned font it sets a default one.
334 // After this call, fonts are validated.
335 multilanguageSupport.ValidateFonts( utf32Characters,
340 requestedNumberOfCharacters,
345 Vector<Character> mirroredUtf32Characters;
346 bool textMirrored = false;
347 Length numberOfParagraphs = 0u;
348 if( BIDI_INFO & operations )
350 // Count the number of LINE_NO_BREAK to reserve some space for the vector of paragraph's
351 // bidirectional info.
353 const TextAbstraction::LineBreakInfo* lineBreakInfoBuffer = lineBreakInfo.Begin();
354 for( Length index = 0u; index < numberOfCharacters; ++index )
356 if( TextAbstraction::LINE_NO_BREAK == *( lineBreakInfoBuffer + index ) )
358 ++numberOfParagraphs;
362 Vector<BidirectionalParagraphInfoRun>& bidirectionalInfo = mLogicalModel->mBidirectionalParagraphInfo;
363 bidirectionalInfo.Reserve( numberOfParagraphs );
365 // Calculates the bidirectional info for the whole paragraph if it contains right to left scripts.
366 SetBidirectionalInfo( utf32Characters,
370 requestedNumberOfCharacters,
373 if( 0u != bidirectionalInfo.Count() )
375 // Only set the character directions if there is right to left characters.
376 Vector<CharacterDirection>& directions = mLogicalModel->mCharacterDirections;
377 GetCharactersDirection( bidirectionalInfo,
380 requestedNumberOfCharacters,
383 // This paragraph has right to left text. Some characters may need to be mirrored.
384 // TODO: consider if the mirrored string can be stored as well.
386 textMirrored = GetMirroredText( utf32Characters,
390 requestedNumberOfCharacters,
391 mirroredUtf32Characters );
395 // There is no right to left characters. Clear the directions vector.
396 mLogicalModel->mCharacterDirections.Clear();
400 Vector<GlyphInfo>& glyphs = mVisualModel->mGlyphs;
401 Vector<CharacterIndex>& glyphsToCharactersMap = mVisualModel->mGlyphsToCharacters;
402 Vector<Length>& charactersPerGlyph = mVisualModel->mCharactersPerGlyph;
403 Vector<GlyphIndex> newParagraphGlyphs;
404 newParagraphGlyphs.Reserve( numberOfParagraphs );
406 GlyphIndex startGlyphIndex = 0u;
407 if( SHAPE_TEXT & operations )
409 const Vector<Character>& textToShape = textMirrored ? mirroredUtf32Characters : utf32Characters;
411 ShapeText( textToShape,
417 requestedNumberOfCharacters,
419 glyphsToCharactersMap,
421 newParagraphGlyphs );
423 // Create the 'number of glyphs' per character and the glyph to character conversion tables.
424 mVisualModel->CreateGlyphsPerCharacterTable( startIndex, startGlyphIndex, numberOfCharacters );
425 mVisualModel->CreateCharacterToGlyphTable( startIndex, startGlyphIndex, numberOfCharacters );
428 const Length numberOfGlyphs = glyphs.Count();
430 if( GET_GLYPH_METRICS & operations )
432 GlyphInfo* glyphsBuffer = glyphs.Begin() + startGlyphIndex;
433 mMetrics->GetGlyphMetrics( glyphsBuffer, numberOfGlyphs );
435 // Update the width and advance of all new paragraph characters.
436 for( Vector<GlyphIndex>::ConstIterator it = newParagraphGlyphs.Begin(), endIt = newParagraphGlyphs.End(); it != endIt; ++it )
438 const GlyphIndex index = *it;
439 GlyphInfo& glyph = *( glyphsBuffer + index );
441 glyph.xBearing = 0.f;
447 if( ( NULL != mEventData ) &&
448 mEventData->mPreEditFlag &&
449 ( 0u != mVisualModel->mCharactersToGlyph.Count() ) )
451 // Add the underline for the pre-edit text.
452 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
453 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
455 const GlyphIndex glyphStart = *( charactersToGlyphBuffer + mEventData->mPreEditStartPosition );
456 const CharacterIndex lastPreEditCharacter = mEventData->mPreEditStartPosition + ( ( mEventData->mPreEditLength > 0u ) ? mEventData->mPreEditLength - 1u : 0u );
457 const Length numberOfGlyphsLastCharacter = *( glyphsPerCharacterBuffer + lastPreEditCharacter );
458 const GlyphIndex glyphEnd = *( charactersToGlyphBuffer + lastPreEditCharacter ) + ( numberOfGlyphsLastCharacter > 1u ? numberOfGlyphsLastCharacter - 1u : 0u );
460 GlyphRun underlineRun;
461 underlineRun.glyphIndex = glyphStart;
462 underlineRun.numberOfGlyphs = 1u + glyphEnd - glyphStart;
464 // TODO: At the moment the underline runs are only for pre-edit.
465 mVisualModel->mUnderlineRuns.PushBack( underlineRun );
469 bool Controller::Impl::UpdateModelStyle( OperationsMask operationsRequired )
471 bool updated = false;
473 if( COLOR & operationsRequired )
475 // Set the color runs in glyphs.
476 SetColorSegmentationInfo( mLogicalModel->mColorRuns,
477 mVisualModel->mCharactersToGlyph,
478 mVisualModel->mGlyphsPerCharacter,
479 mVisualModel->mColorRuns );
487 void Controller::Impl::RetrieveDefaultInputStyle( InputStyle& inputStyle )
489 // Sets the default text's color.
490 inputStyle.textColor = mTextColor;
492 // Sets the default font's family name, weight, width, slant and size.
495 inputStyle.familyName = mFontDefaults->mFontDescription.family;
496 inputStyle.weight = mFontDefaults->mFontDescription.weight;
497 inputStyle.width = mFontDefaults->mFontDescription.width;
498 inputStyle.slant = mFontDefaults->mFontDescription.slant;
499 inputStyle.size = mFontDefaults->mDefaultPointSize;
501 inputStyle.familyDefined = mFontDefaults->familyDefined;
502 inputStyle.weightDefined = mFontDefaults->weightDefined;
503 inputStyle.widthDefined = mFontDefaults->widthDefined;
504 inputStyle.slantDefined = mFontDefaults->slantDefined;
505 inputStyle.sizeDefined = mFontDefaults->sizeDefined;
509 inputStyle.familyName.clear();
510 inputStyle.weight = TextAbstraction::FontWeight::NORMAL;
511 inputStyle.width = TextAbstraction::FontWidth::NORMAL;
512 inputStyle.slant = TextAbstraction::FontSlant::NORMAL;
513 inputStyle.size = 0.f;
515 inputStyle.familyDefined = false;
516 inputStyle.weightDefined = false;
517 inputStyle.widthDefined = false;
518 inputStyle.slantDefined = false;
519 inputStyle.sizeDefined = false;
523 float Controller::Impl::GetDefaultFontLineHeight()
525 FontId defaultFontId = 0u;
526 if( NULL == mFontDefaults )
528 TextAbstraction::FontDescription fontDescription;
529 defaultFontId = mFontClient.GetFontId( fontDescription );
533 defaultFontId = mFontDefaults->GetFontId( mFontClient );
536 Text::FontMetrics fontMetrics;
537 mMetrics->GetFontMetrics( defaultFontId, fontMetrics );
539 return( fontMetrics.ascender - fontMetrics.descender );
542 void Controller::Impl::OnCursorKeyEvent( const Event& event )
544 if( NULL == mEventData )
546 // Nothing to do if there is no text input.
550 int keyCode = event.p1.mInt;
552 if( Dali::DALI_KEY_CURSOR_LEFT == keyCode )
554 if( mEventData->mPrimaryCursorPosition > 0u )
556 mEventData->mPrimaryCursorPosition = CalculateNewCursorIndex( mEventData->mPrimaryCursorPosition - 1u );
559 else if( Dali::DALI_KEY_CURSOR_RIGHT == keyCode )
561 if( mLogicalModel->mText.Count() > mEventData->mPrimaryCursorPosition )
563 mEventData->mPrimaryCursorPosition = CalculateNewCursorIndex( mEventData->mPrimaryCursorPosition );
566 else if( Dali::DALI_KEY_CURSOR_UP == keyCode )
570 else if( Dali::DALI_KEY_CURSOR_DOWN == keyCode )
575 mEventData->mUpdateCursorPosition = true;
576 mEventData->mUpdateInputStyle = true;
577 mEventData->mScrollAfterUpdatePosition = true;
580 void Controller::Impl::OnTapEvent( const Event& event )
582 if( NULL != mEventData )
584 const unsigned int tapCount = event.p1.mUint;
588 if( IsShowingRealText() )
590 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
591 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
593 mEventData->mPrimaryCursorPosition = GetClosestCursorIndex( xPosition,
596 // When the cursor position is changing, delay cursor blinking
597 mEventData->mDecorator->DelayCursorBlink();
601 mEventData->mPrimaryCursorPosition = 0u;
604 mEventData->mUpdateCursorPosition = true;
605 mEventData->mScrollAfterUpdatePosition = true;
606 mEventData->mUpdateInputStyle = true;
608 // Notify the cursor position to the imf manager.
609 if( mEventData->mImfManager )
611 mEventData->mImfManager.SetCursorPosition( mEventData->mPrimaryCursorPosition );
612 mEventData->mImfManager.NotifyCursorPosition();
618 void Controller::Impl::OnPanEvent( const Event& event )
620 if( NULL == mEventData )
622 // Nothing to do if there is no text input.
626 int state = event.p1.mInt;
628 if( Gesture::Started == state ||
629 Gesture::Continuing == state )
631 const Vector2& actualSize = mVisualModel->GetLayoutSize();
632 const Vector2 currentScroll = mEventData->mScrollPosition;
634 if( mEventData->mHorizontalScrollingEnabled )
636 const float displacementX = event.p2.mFloat;
637 mEventData->mScrollPosition.x += displacementX;
639 ClampHorizontalScroll( actualSize );
642 if( mEventData->mVerticalScrollingEnabled )
644 const float displacementY = event.p3.mFloat;
645 mEventData->mScrollPosition.y += displacementY;
647 ClampVerticalScroll( actualSize );
650 if( mEventData->mDecorator )
652 mEventData->mDecorator->UpdatePositions( mEventData->mScrollPosition - currentScroll );
657 void Controller::Impl::OnLongPressEvent( const Event& event )
659 DALI_LOG_INFO( gLogFilter, Debug::General, "Controller::OnLongPressEvent\n" );
661 if( EventData::EDITING == mEventData->mState )
663 ChangeState ( EventData::EDITING_WITH_POPUP );
664 mEventData->mDecoratorUpdated = true;
668 void Controller::Impl::OnHandleEvent( const Event& event )
670 if( NULL == mEventData )
672 // Nothing to do if there is no text input.
676 const unsigned int state = event.p1.mUint;
677 const bool handleStopScrolling = ( HANDLE_STOP_SCROLLING == state );
679 if( HANDLE_PRESSED == state )
681 // The event.p2 and event.p3 are in decorator coords. Need to transforms to text coords.
682 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
683 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
685 const CharacterIndex handleNewPosition = GetClosestCursorIndex( xPosition, yPosition );
687 if( Event::GRAB_HANDLE_EVENT == event.type )
689 ChangeState ( EventData::GRAB_HANDLE_PANNING );
691 if( handleNewPosition != mEventData->mPrimaryCursorPosition )
693 mEventData->mPrimaryCursorPosition = handleNewPosition;
694 mEventData->mUpdateCursorPosition = true;
697 else if( Event::LEFT_SELECTION_HANDLE_EVENT == event.type )
699 ChangeState ( EventData::SELECTION_HANDLE_PANNING );
701 if( ( handleNewPosition != mEventData->mLeftSelectionPosition ) &&
702 ( handleNewPosition != mEventData->mRightSelectionPosition ) )
704 mEventData->mLeftSelectionPosition = handleNewPosition;
706 mEventData->mUpdateLeftSelectionPosition = true;
709 else if( Event::RIGHT_SELECTION_HANDLE_EVENT == event.type )
711 ChangeState ( EventData::SELECTION_HANDLE_PANNING );
713 if( ( handleNewPosition != mEventData->mRightSelectionPosition ) &&
714 ( handleNewPosition != mEventData->mLeftSelectionPosition ) )
716 mEventData->mRightSelectionPosition = handleNewPosition;
718 mEventData->mUpdateRightSelectionPosition = true;
721 } // end ( HANDLE_PRESSED == state )
722 else if( ( HANDLE_RELEASED == state ) ||
723 handleStopScrolling )
725 CharacterIndex handlePosition = 0u;
726 if( handleStopScrolling )
728 // The event.p2 and event.p3 are in decorator coords. Need to transforms to text coords.
729 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
730 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
732 handlePosition = GetClosestCursorIndex( xPosition, yPosition );
735 if( Event::GRAB_HANDLE_EVENT == event.type )
737 mEventData->mUpdateCursorPosition = true;
738 mEventData->mUpdateInputStyle = true;
740 if( !IsClipboardEmpty() )
742 ChangeState( EventData::EDITING_WITH_PASTE_POPUP ); // Moving grabhandle will show Paste Popup
745 if( handleStopScrolling )
747 mEventData->mScrollAfterUpdatePosition = mEventData->mPrimaryCursorPosition != handlePosition;
748 mEventData->mPrimaryCursorPosition = handlePosition;
751 else if( Event::LEFT_SELECTION_HANDLE_EVENT == event.type )
753 ChangeState( EventData::SELECTING );
755 if( handleStopScrolling )
757 mEventData->mUpdateLeftSelectionPosition = ( mEventData->mRightSelectionPosition != handlePosition );
758 mEventData->mScrollAfterUpdatePosition = mEventData->mUpdateLeftSelectionPosition;
760 if( mEventData->mUpdateLeftSelectionPosition )
762 mEventData->mLeftSelectionPosition = handlePosition;
766 else if( Event::RIGHT_SELECTION_HANDLE_EVENT == event.type )
768 ChangeState( EventData::SELECTING );
770 if( handleStopScrolling )
772 mEventData->mUpdateRightSelectionPosition = ( mEventData->mLeftSelectionPosition != handlePosition );
773 mEventData->mScrollAfterUpdatePosition = mEventData->mUpdateRightSelectionPosition;
774 if( mEventData->mUpdateRightSelectionPosition )
776 mEventData->mRightSelectionPosition = handlePosition;
781 mEventData->mDecoratorUpdated = true;
782 } // end ( ( HANDLE_RELEASED == state ) || ( HANDLE_STOP_SCROLLING == state ) )
783 else if( HANDLE_SCROLLING == state )
785 const float xSpeed = event.p2.mFloat;
786 const Vector2& actualSize = mVisualModel->GetLayoutSize();
787 const Vector2 currentScrollPosition = mEventData->mScrollPosition;
789 mEventData->mScrollPosition.x += xSpeed;
791 ClampHorizontalScroll( actualSize );
793 bool endOfScroll = false;
794 if( Vector2::ZERO == ( currentScrollPosition - mEventData->mScrollPosition ) )
796 // Notify the decorator there is no more text to scroll.
797 // The decorator won't send more scroll events.
798 mEventData->mDecorator->NotifyEndOfScroll();
799 // Still need to set the position of the handle.
803 // Set the position of the handle.
804 const bool scrollRightDirection = xSpeed > 0.f;
805 const bool leftSelectionHandleEvent = Event::LEFT_SELECTION_HANDLE_EVENT == event.type;
806 const bool rightSelectionHandleEvent = Event::RIGHT_SELECTION_HANDLE_EVENT == event.type;
808 if( Event::GRAB_HANDLE_EVENT == event.type )
810 ChangeState( EventData::GRAB_HANDLE_PANNING );
812 Vector2 position = mEventData->mDecorator->GetPosition( GRAB_HANDLE );
814 // Position the grag handle close to either the left or right edge.
815 position.x = scrollRightDirection ? 0.f : mVisualModel->mControlSize.width;
817 // Get the new handle position.
818 // The grab handle's position is in decorator coords. Need to transforms to text coords.
819 const CharacterIndex handlePosition = GetClosestCursorIndex( position.x - mEventData->mScrollPosition.x - mAlignmentOffset.x,
820 position.y - mEventData->mScrollPosition.y - mAlignmentOffset.y );
822 mEventData->mUpdateCursorPosition = mEventData->mPrimaryCursorPosition != handlePosition;
823 mEventData->mScrollAfterUpdatePosition = mEventData->mUpdateCursorPosition;
824 mEventData->mPrimaryCursorPosition = handlePosition;
825 mEventData->mUpdateInputStyle = mEventData->mUpdateCursorPosition;
827 else if( leftSelectionHandleEvent || rightSelectionHandleEvent )
829 // TODO: This is recalculating the selection box every time the text is scrolled with the selection handles.
830 // Think if something can be done to save power.
832 ChangeState( EventData::SELECTION_HANDLE_PANNING );
834 Vector2 position = mEventData->mDecorator->GetPosition( leftSelectionHandleEvent ? Text::LEFT_SELECTION_HANDLE : Text::RIGHT_SELECTION_HANDLE );
836 // Position the selection handle close to either the left or right edge.
837 position.x = scrollRightDirection ? 0.f : mVisualModel->mControlSize.width;
839 // Get the new handle position.
840 // The selection handle's position is in decorator coords. Need to transforms to text coords.
841 const CharacterIndex handlePosition = GetClosestCursorIndex( position.x - mEventData->mScrollPosition.x - mAlignmentOffset.x,
842 position.y - mEventData->mScrollPosition.y - mAlignmentOffset.y );
844 if( leftSelectionHandleEvent )
846 const bool differentHandles = ( mEventData->mLeftSelectionPosition != handlePosition ) && ( mEventData->mRightSelectionPosition != handlePosition );
847 mEventData->mUpdateLeftSelectionPosition = endOfScroll || differentHandles;
848 if( differentHandles )
850 mEventData->mLeftSelectionPosition = handlePosition;
855 const bool differentHandles = ( mEventData->mRightSelectionPosition != handlePosition ) && ( mEventData->mLeftSelectionPosition != handlePosition );
856 mEventData->mUpdateRightSelectionPosition = endOfScroll || differentHandles;
857 if( differentHandles )
859 mEventData->mRightSelectionPosition = handlePosition;
863 if( mEventData->mUpdateLeftSelectionPosition || mEventData->mUpdateRightSelectionPosition )
865 RepositionSelectionHandles();
867 mEventData->mScrollAfterUpdatePosition = true;
870 mEventData->mDecoratorUpdated = true;
871 } // end ( HANDLE_SCROLLING == state )
874 void Controller::Impl::OnSelectEvent( const Event& event )
876 if( NULL == mEventData )
878 // Nothing to do if there is no text.
882 if( mEventData->mSelectionEnabled )
884 // The event.p2 and event.p3 are in decorator coords. Need to transforms to text coords.
885 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
886 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
888 // Calculates the logical position from the x,y coords.
889 RepositionSelectionHandles( xPosition,
892 mEventData->mUpdateLeftSelectionPosition = true;
893 mEventData->mUpdateRightSelectionPosition = true;
895 mEventData->mScrollAfterUpdatePosition = ( mEventData->mLeftSelectionPosition != mEventData->mRightSelectionPosition );
899 void Controller::Impl::OnSelectAllEvent()
901 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "OnSelectAllEvent mEventData->mSelectionEnabled%s \n", mEventData->mSelectionEnabled?"true":"false");
903 if( NULL == mEventData )
905 // Nothing to do if there is no text.
909 if( mEventData->mSelectionEnabled )
911 mEventData->mLeftSelectionPosition = 0u;
912 mEventData->mRightSelectionPosition = mLogicalModel->mText.Count();
914 mEventData->mScrollAfterUpdatePosition = true;
915 mEventData->mUpdateLeftSelectionPosition = true;
916 mEventData->mUpdateRightSelectionPosition = true;
920 void Controller::Impl::RetrieveSelection( std::string& selectedText, bool deleteAfterRetrieval )
922 if( mEventData->mLeftSelectionPosition == mEventData->mRightSelectionPosition )
924 // Nothing to select if handles are in the same place.
925 selectedText.clear();
929 const bool handlesCrossed = mEventData->mLeftSelectionPosition > mEventData->mRightSelectionPosition;
931 //Get start and end position of selection
932 const CharacterIndex startOfSelectedText = handlesCrossed ? mEventData->mRightSelectionPosition : mEventData->mLeftSelectionPosition;
933 const Length lengthOfSelectedText = ( handlesCrossed ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition ) - startOfSelectedText;
935 // Validate the start and end selection points
936 if( ( startOfSelectedText + lengthOfSelectedText ) <= mLogicalModel->mText.Count() )
938 //Get text as a UTF8 string
939 Vector<Character>& utf32Characters = mLogicalModel->mText;
941 Utf32ToUtf8( &utf32Characters[startOfSelectedText], lengthOfSelectedText, selectedText );
943 if( deleteAfterRetrieval ) // Only delete text if copied successfully
945 // Set as input style the style of the first deleted character.
946 mLogicalModel->RetrieveStyle( startOfSelectedText, mEventData->mInputStyle );
948 mLogicalModel->UpdateTextStyleRuns( startOfSelectedText, -static_cast<int>( lengthOfSelectedText ) );
950 // Delete text between handles
951 Vector<Character>& currentText = mLogicalModel->mText;
953 Vector<Character>::Iterator first = currentText.Begin() + startOfSelectedText;
954 Vector<Character>::Iterator last = first + lengthOfSelectedText;
955 currentText.Erase( first, last );
957 // Scroll after delete.
958 mEventData->mPrimaryCursorPosition = handlesCrossed ? mEventData->mRightSelectionPosition : mEventData->mLeftSelectionPosition;
959 mEventData->mScrollAfterDelete = true;
961 // Udpade the cursor position and the decorator.
962 // Scroll after the position is updated if is not scrolling after delete.
963 mEventData->mUpdateCursorPosition = true;
964 mEventData->mScrollAfterUpdatePosition = !mEventData->mScrollAfterDelete;
965 mEventData->mDecoratorUpdated = true;
969 void Controller::Impl::ShowClipboard()
973 mClipboard.ShowClipboard();
977 void Controller::Impl::HideClipboard()
981 mClipboard.HideClipboard();
985 bool Controller::Impl::CopyStringToClipboard( std::string& source )
987 //Send string to clipboard
988 return ( mClipboard && mClipboard.SetItem( source ) );
991 void Controller::Impl::SendSelectionToClipboard( bool deleteAfterSending )
993 std::string selectedText;
994 RetrieveSelection( selectedText, deleteAfterSending );
995 CopyStringToClipboard( selectedText );
996 ChangeState( EventData::EDITING );
999 void Controller::Impl::GetTextFromClipboard( unsigned int itemIndex, std::string& retrievedString )
1003 retrievedString = mClipboard.GetItem( itemIndex );
1007 void Controller::Impl::RepositionSelectionHandles()
1009 CharacterIndex selectionStart = mEventData->mLeftSelectionPosition;
1010 CharacterIndex selectionEnd = mEventData->mRightSelectionPosition;
1012 if( selectionStart == selectionEnd )
1014 // Nothing to select if handles are in the same place.
1018 mEventData->mDecorator->ClearHighlights();
1020 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
1021 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
1022 const GlyphInfo* const glyphsBuffer = mVisualModel->mGlyphs.Begin();
1023 const Vector2* const positionsBuffer = mVisualModel->mGlyphPositions.Begin();
1024 const Length* const charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
1025 const CharacterIndex* const glyphToCharacterBuffer = mVisualModel->mGlyphsToCharacters.Begin();
1026 const CharacterDirection* const modelCharacterDirectionsBuffer = ( 0u != mLogicalModel->mCharacterDirections.Count() ) ? mLogicalModel->mCharacterDirections.Begin() : NULL;
1028 // TODO: Better algorithm to create the highlight box.
1029 // TODO: Multi-line.
1031 // Get the height of the line.
1032 const Vector<LineRun>& lines = mVisualModel->mLines;
1033 const LineRun& firstLine = *lines.Begin();
1034 const float height = firstLine.ascender + -firstLine.descender;
1036 const bool isLastCharacter = selectionEnd >= mLogicalModel->mText.Count();
1037 const bool startDirection = ( ( NULL == modelCharacterDirectionsBuffer ) ? false : *( modelCharacterDirectionsBuffer + selectionStart ) );
1038 const bool endDirection = ( ( NULL == modelCharacterDirectionsBuffer ) ? false : *( modelCharacterDirectionsBuffer + ( selectionEnd - ( isLastCharacter ? 1u : 0u ) ) ) );
1040 // Swap the indices if the start is greater than the end.
1041 const bool indicesSwapped = selectionStart > selectionEnd;
1043 // Tell the decorator to flip the selection handles if needed.
1044 mEventData->mDecorator->SetSelectionHandleFlipState( indicesSwapped, startDirection, endDirection );
1046 if( indicesSwapped )
1048 std::swap( selectionStart, selectionEnd );
1051 // Get the indices to the first and last selected glyphs.
1052 const CharacterIndex selectionEndMinusOne = selectionEnd - 1u;
1053 const GlyphIndex glyphStart = *( charactersToGlyphBuffer + selectionStart );
1054 const Length numberOfGlyphs = *( glyphsPerCharacterBuffer + selectionEndMinusOne );
1055 const GlyphIndex glyphEnd = *( charactersToGlyphBuffer + selectionEndMinusOne ) + ( ( numberOfGlyphs > 0 ) ? numberOfGlyphs - 1u : 0u );
1057 // Check if the first glyph is a ligature that must be broken like Latin ff, fi, or Arabic ﻻ, etc which needs special code.
1058 const Length numberOfCharactersStart = *( charactersPerGlyphBuffer + glyphStart );
1059 bool splitStartGlyph = ( numberOfCharactersStart > 1u ) && HasLigatureMustBreak( mLogicalModel->GetScript( selectionStart ) );
1061 // Check if the last glyph is a ligature that must be broken like Latin ff, fi, or Arabic ﻻ, etc which needs special code.
1062 const Length numberOfCharactersEnd = *( charactersPerGlyphBuffer + glyphEnd );
1063 bool splitEndGlyph = ( glyphStart != glyphEnd ) && ( numberOfCharactersEnd > 1u ) && HasLigatureMustBreak( mLogicalModel->GetScript( selectionEndMinusOne ) );
1065 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
1067 // Traverse the glyphs.
1068 for( GlyphIndex index = glyphStart; index <= glyphEnd; ++index )
1070 const GlyphInfo& glyph = *( glyphsBuffer + index );
1071 const Vector2& position = *( positionsBuffer + index );
1073 if( splitStartGlyph )
1075 // 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.
1077 const float glyphAdvance = glyph.advance / static_cast<float>( numberOfCharactersStart );
1078 const CharacterIndex interGlyphIndex = selectionStart - *( glyphToCharacterBuffer + glyphStart );
1079 // Get the direction of the character.
1080 CharacterDirection isCurrentRightToLeft = false;
1081 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
1083 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + selectionStart );
1086 // The end point could be in the middle of the ligature.
1087 // Calculate the number of characters selected.
1088 const Length numberOfCharacters = ( glyphStart == glyphEnd ) ? ( selectionEnd - selectionStart ) : ( numberOfCharactersStart - interGlyphIndex );
1090 const float xPosition = position.x - glyph.xBearing + offset.x + glyphAdvance * static_cast<float>( isCurrentRightToLeft ? ( numberOfCharactersStart - interGlyphIndex - numberOfCharacters ) : interGlyphIndex );
1092 mEventData->mDecorator->AddHighlight( xPosition,
1094 xPosition + static_cast<float>( numberOfCharacters ) * glyphAdvance,
1095 offset.y + height );
1097 splitStartGlyph = false;
1101 if( splitEndGlyph && ( index == glyphEnd ) )
1103 // 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.
1105 const float glyphAdvance = glyph.advance / static_cast<float>( numberOfCharactersEnd );
1106 const CharacterIndex interGlyphIndex = selectionEnd - *( glyphToCharacterBuffer + glyphEnd );
1107 // Get the direction of the character.
1108 CharacterDirection isCurrentRightToLeft = false;
1109 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
1111 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + selectionEnd );
1114 const Length numberOfCharacters = numberOfCharactersEnd - interGlyphIndex;
1116 const float xPosition = position.x - glyph.xBearing + offset.x + ( isCurrentRightToLeft ? ( glyphAdvance * static_cast<float>( numberOfCharacters ) ) : 0.f );
1117 mEventData->mDecorator->AddHighlight( xPosition,
1119 xPosition + static_cast<float>( interGlyphIndex ) * glyphAdvance,
1120 offset.y + height );
1122 splitEndGlyph = false;
1126 const float xPosition = position.x - glyph.xBearing + offset.x;
1127 mEventData->mDecorator->AddHighlight( xPosition,
1129 xPosition + glyph.advance,
1130 offset.y + height );
1133 CursorInfo primaryCursorInfo;
1134 GetCursorPosition( mEventData->mLeftSelectionPosition,
1135 primaryCursorInfo );
1137 CursorInfo secondaryCursorInfo;
1138 GetCursorPosition( mEventData->mRightSelectionPosition,
1139 secondaryCursorInfo );
1141 const Vector2 primaryPosition = primaryCursorInfo.primaryPosition + offset;
1142 const Vector2 secondaryPosition = secondaryCursorInfo.primaryPosition + offset;
1144 mEventData->mDecorator->SetPosition( LEFT_SELECTION_HANDLE,
1146 primaryCursorInfo.lineOffset + offset.y,
1147 primaryCursorInfo.lineHeight );
1149 mEventData->mDecorator->SetPosition( RIGHT_SELECTION_HANDLE,
1150 secondaryPosition.x,
1151 secondaryCursorInfo.lineOffset + offset.y,
1152 secondaryCursorInfo.lineHeight );
1154 // Cursor to be positioned at end of selection so if selection interrupted and edit mode restarted the cursor will be at end of selection
1155 mEventData->mPrimaryCursorPosition = ( indicesSwapped ) ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition;
1157 // Set the flag to update the decorator.
1158 mEventData->mDecoratorUpdated = true;
1161 void Controller::Impl::RepositionSelectionHandles( float visualX, float visualY )
1163 if( NULL == mEventData )
1165 // Nothing to do if there is no text input.
1169 if( IsShowingPlaceholderText() )
1171 // Nothing to do if there is the place-holder text.
1175 const Length numberOfGlyphs = mVisualModel->mGlyphs.Count();
1176 const Length numberOfLines = mVisualModel->mLines.Count();
1177 if( ( 0 == numberOfGlyphs ) ||
1178 ( 0 == numberOfLines ) )
1180 // Nothing to do if there is no text.
1184 // Find which word was selected
1185 CharacterIndex selectionStart( 0 );
1186 CharacterIndex selectionEnd( 0 );
1187 FindSelectionIndices( visualX, visualY, selectionStart, selectionEnd );
1188 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "%p selectionStart %d selectionEnd %d\n", this, selectionStart, selectionEnd );
1190 if( selectionStart == selectionEnd )
1192 ChangeState( EventData::EDITING );
1193 // Nothing to select. i.e. a white space, out of bounds
1197 mEventData->mLeftSelectionPosition = selectionStart;
1198 mEventData->mRightSelectionPosition = selectionEnd;
1201 void Controller::Impl::SetPopupButtons()
1204 * Sets the Popup buttons to be shown depending on State.
1206 * If SELECTING : CUT & COPY + ( PASTE & CLIPBOARD if content available to paste )
1208 * If EDITING_WITH_POPUP : SELECT & SELECT_ALL
1211 TextSelectionPopup::Buttons buttonsToShow = TextSelectionPopup::NONE;
1213 if( EventData::SELECTING == mEventData->mState )
1215 buttonsToShow = TextSelectionPopup::Buttons( TextSelectionPopup::CUT | TextSelectionPopup::COPY );
1217 if( !IsClipboardEmpty() )
1219 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
1220 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::CLIPBOARD ) );
1223 if( !mEventData->mAllTextSelected )
1225 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::SELECT_ALL ) );
1228 else if( EventData::EDITING_WITH_POPUP == mEventData->mState )
1230 if( mLogicalModel->mText.Count() && !IsShowingPlaceholderText() )
1232 buttonsToShow = TextSelectionPopup::Buttons( TextSelectionPopup::SELECT | TextSelectionPopup::SELECT_ALL );
1235 if( !IsClipboardEmpty() )
1237 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
1238 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::CLIPBOARD ) );
1241 else if( EventData::EDITING_WITH_PASTE_POPUP == mEventData->mState )
1243 if ( !IsClipboardEmpty() )
1245 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
1246 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::CLIPBOARD ) );
1250 mEventData->mDecorator->SetEnabledPopupButtons( buttonsToShow );
1253 void Controller::Impl::ChangeState( EventData::State newState )
1255 if( NULL == mEventData )
1257 // Nothing to do if there is no text input.
1261 DALI_LOG_INFO( gLogFilter, Debug::General, "ChangeState state:%d newstate:%d\n", mEventData->mState, newState );
1263 if( mEventData->mState != newState )
1265 mEventData->mState = newState;
1267 if( EventData::INACTIVE == mEventData->mState )
1269 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
1270 mEventData->mDecorator->StopCursorBlink();
1271 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1272 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1273 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1274 mEventData->mDecorator->SetPopupActive( false );
1275 mEventData->mDecoratorUpdated = true;
1278 else if( EventData::INTERRUPTED == mEventData->mState)
1280 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1281 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1282 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1283 mEventData->mDecorator->SetPopupActive( false );
1284 mEventData->mDecoratorUpdated = true;
1287 else if( EventData::SELECTING == mEventData->mState )
1289 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
1290 mEventData->mDecorator->StopCursorBlink();
1291 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1292 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true );
1293 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true );
1294 if( mEventData->mGrabHandlePopupEnabled )
1297 mEventData->mDecorator->SetPopupActive( true );
1299 mEventData->mDecoratorUpdated = true;
1301 else if( EventData::EDITING == mEventData->mState )
1303 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1304 if( mEventData->mCursorBlinkEnabled )
1306 mEventData->mDecorator->StartCursorBlink();
1308 // Grab handle is not shown until a tap is received whilst EDITING
1309 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1310 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1311 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1312 if( mEventData->mGrabHandlePopupEnabled )
1314 mEventData->mDecorator->SetPopupActive( false );
1316 mEventData->mDecoratorUpdated = true;
1319 else if( EventData::EDITING_WITH_POPUP == mEventData->mState )
1321 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "EDITING_WITH_POPUP \n", newState );
1323 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1324 if( mEventData->mCursorBlinkEnabled )
1326 mEventData->mDecorator->StartCursorBlink();
1328 if( mEventData->mSelectionEnabled )
1330 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1331 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1335 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
1337 if( mEventData->mGrabHandlePopupEnabled )
1340 mEventData->mDecorator->SetPopupActive( true );
1343 mEventData->mDecoratorUpdated = true;
1345 else if( EventData::EDITING_WITH_GRAB_HANDLE == mEventData->mState )
1347 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "EDITING_WITH_GRAB_HANDLE \n", newState );
1349 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1350 if( mEventData->mCursorBlinkEnabled )
1352 mEventData->mDecorator->StartCursorBlink();
1354 // Grab handle is not shown until a tap is received whilst EDITING
1355 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
1356 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1357 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1358 if( mEventData->mGrabHandlePopupEnabled )
1360 mEventData->mDecorator->SetPopupActive( false );
1362 mEventData->mDecoratorUpdated = true;
1365 else if( EventData::SELECTION_HANDLE_PANNING == mEventData->mState )
1367 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
1368 mEventData->mDecorator->StopCursorBlink();
1369 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1370 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true );
1371 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true );
1372 if( mEventData->mGrabHandlePopupEnabled )
1374 mEventData->mDecorator->SetPopupActive( false );
1376 mEventData->mDecoratorUpdated = true;
1378 else if( EventData::GRAB_HANDLE_PANNING == mEventData->mState )
1380 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "GRAB_HANDLE_PANNING \n", newState );
1382 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1383 if( mEventData->mCursorBlinkEnabled )
1385 mEventData->mDecorator->StartCursorBlink();
1387 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
1388 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1389 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1390 if( mEventData->mGrabHandlePopupEnabled )
1392 mEventData->mDecorator->SetPopupActive( false );
1394 mEventData->mDecoratorUpdated = true;
1396 else if( EventData::EDITING_WITH_PASTE_POPUP == mEventData->mState )
1398 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "EDITING_WITH_PASTE_POPUP \n", newState );
1400 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1401 if( mEventData->mCursorBlinkEnabled )
1403 mEventData->mDecorator->StartCursorBlink();
1406 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
1407 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1408 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1410 if( mEventData->mGrabHandlePopupEnabled )
1413 mEventData->mDecorator->SetPopupActive( true );
1416 mEventData->mDecoratorUpdated = true;
1421 LineIndex Controller::Impl::GetClosestLine( float y ) const
1423 float totalHeight = 0.f;
1424 LineIndex lineIndex = 0u;
1426 const Vector<LineRun>& lines = mVisualModel->mLines;
1427 for( LineIndex endLine = lines.Count();
1428 lineIndex < endLine;
1431 const LineRun& lineRun = lines[lineIndex];
1432 totalHeight += lineRun.ascender + -lineRun.descender;
1433 if( y < totalHeight )
1439 if( lineIndex == 0 )
1447 void Controller::Impl::FindSelectionIndices( float visualX, float visualY, CharacterIndex& startIndex, CharacterIndex& endIndex )
1449 CharacterIndex hitCharacter = GetClosestCursorIndex( visualX, visualY );
1450 DALI_ASSERT_DEBUG( hitCharacter <= mLogicalModel->mText.Count() && "GetClosestCursorIndex returned out of bounds index" );
1452 if( mLogicalModel->mText.Count() == 0 )
1454 return; // if model empty
1457 if( hitCharacter >= mLogicalModel->mText.Count() )
1459 // Closest hit character is the last character.
1460 if( hitCharacter == mLogicalModel->mText.Count() )
1462 hitCharacter--; //Hit character index set to last character in logical model
1466 // hitCharacter is out of bounds
1471 startIndex = hitCharacter;
1472 endIndex = hitCharacter;
1473 bool isHitCharacterWhitespace = TextAbstraction::IsWhiteSpace( mLogicalModel->mText[hitCharacter] );
1475 // Find the start and end of the text
1476 for( startIndex = hitCharacter; startIndex > 0; --startIndex )
1478 if( isHitCharacterWhitespace != TextAbstraction::IsWhiteSpace( mLogicalModel->mText[ startIndex-1 ] ) )
1483 const CharacterIndex pastTheEnd = mLogicalModel->mText.Count();
1484 for( endIndex = hitCharacter + 1u; endIndex < pastTheEnd; ++endIndex )
1486 if( isHitCharacterWhitespace != TextAbstraction::IsWhiteSpace( mLogicalModel->mText[ endIndex ] ) )
1493 CharacterIndex Controller::Impl::GetClosestCursorIndex( float visualX,
1496 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "GetClosestCursorIndex %p closest visualX %f visualY %f\n", this, visualX, visualY );
1498 if( NULL == mEventData )
1500 // Nothing to do if there is no text input.
1504 CharacterIndex logicalIndex = 0u;
1506 const Length numberOfGlyphs = mVisualModel->mGlyphs.Count();
1507 const Length numberOfLines = mVisualModel->mLines.Count();
1508 if( ( 0 == numberOfGlyphs ) ||
1509 ( 0 == numberOfLines ) )
1511 return logicalIndex;
1514 // Find which line is closest
1515 const LineIndex lineIndex = GetClosestLine( visualY );
1516 const LineRun& line = mVisualModel->mLines[lineIndex];
1518 // Get the positions of the glyphs.
1519 const Vector<Vector2>& positions = mVisualModel->mGlyphPositions;
1520 const Vector2* const positionsBuffer = positions.Begin();
1522 // Get the visual to logical conversion tables.
1523 const CharacterIndex* const visualToLogicalBuffer = ( 0u != mLogicalModel->mVisualToLogicalMap.Count() ) ? mLogicalModel->mVisualToLogicalMap.Begin() : NULL;
1524 const CharacterIndex* const visualToLogicalCursorBuffer = mLogicalModel->mVisualToLogicalCursorMap.Begin();
1526 // Get the character to glyph conversion table.
1527 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
1529 // Get the glyphs per character table.
1530 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
1532 // Get the glyph's info buffer.
1533 const GlyphInfo* const glyphInfoBuffer = mVisualModel->mGlyphs.Begin();
1535 // If the vector is void, there is no right to left characters.
1536 const bool hasRightToLeftCharacters = NULL != visualToLogicalBuffer;
1538 const CharacterIndex startCharacter = line.characterRun.characterIndex;
1539 const CharacterIndex endCharacter = line.characterRun.characterIndex + line.characterRun.numberOfCharacters;
1540 DALI_ASSERT_DEBUG( endCharacter <= mLogicalModel->mText.Count() && "Invalid line info" );
1542 // Whether there is a hit on a glyph.
1543 bool matched = false;
1545 // Traverses glyphs in visual order. To do that use the visual to logical conversion table.
1546 CharacterIndex visualIndex = startCharacter;
1547 Length numberOfCharacters = 0u;
1548 for( ; !matched && ( visualIndex < endCharacter ); ++visualIndex )
1550 // The character in logical order.
1551 const CharacterIndex characterLogicalOrderIndex = hasRightToLeftCharacters ? *( visualToLogicalBuffer + visualIndex ) : visualIndex;
1553 // Get the script of the character.
1554 const Script script = mLogicalModel->GetScript( characterLogicalOrderIndex );
1556 // The number of glyphs for that character
1557 const Length numberOfGlyphs = *( glyphsPerCharacterBuffer + characterLogicalOrderIndex );
1558 ++numberOfCharacters;
1561 if( 0u != numberOfGlyphs )
1563 // Get the first character/glyph of the group of glyphs.
1564 const CharacterIndex firstVisualCharacterIndex = 1u + visualIndex - numberOfCharacters;
1565 const CharacterIndex firstLogicalCharacterIndex = hasRightToLeftCharacters ? *( visualToLogicalBuffer + firstVisualCharacterIndex ) : firstVisualCharacterIndex;
1566 const GlyphIndex firstLogicalGlyphIndex = *( charactersToGlyphBuffer + firstLogicalCharacterIndex );
1568 // Get the metrics for the group of glyphs.
1569 GlyphMetrics glyphMetrics;
1570 GetGlyphsMetrics( firstLogicalGlyphIndex,
1576 // Get the position of the first glyph.
1577 const Vector2& position = *( positionsBuffer + firstLogicalGlyphIndex );
1579 // Whether the glyph can be split, like Latin ligatures fi, ff or Arabic ﻻ.
1580 const bool isInterglyphIndex = ( numberOfCharacters > numberOfGlyphs ) && HasLigatureMustBreak( script );
1581 const Length numberOfBlocks = isInterglyphIndex ? numberOfCharacters : 1u;
1582 const float glyphAdvance = glyphMetrics.advance / static_cast<float>( numberOfBlocks );
1584 GlyphIndex index = 0u;
1585 for( ; !matched && ( index < numberOfBlocks ); ++index )
1587 // Find the mid-point of the area containing the glyph
1588 const float glyphCenter = -glyphMetrics.xBearing + position.x + ( static_cast<float>( index ) + 0.5f ) * glyphAdvance;
1590 if( visualX < glyphCenter )
1599 visualIndex = firstVisualCharacterIndex + index;
1603 numberOfCharacters = 0u;
1609 // Return the logical position of the cursor in characters.
1613 visualIndex = endCharacter;
1616 logicalIndex = hasRightToLeftCharacters ? *( visualToLogicalCursorBuffer + visualIndex ) : visualIndex;
1617 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "%p closest visualIndex %d logicalIndex %d\n", this, visualIndex, logicalIndex );
1619 DALI_ASSERT_DEBUG( ( logicalIndex <= mLogicalModel->mText.Count() && logicalIndex >= 0 ) && "GetClosestCursorIndex - Out of bounds index" );
1621 return logicalIndex;
1624 void Controller::Impl::GetCursorPosition( CharacterIndex logical,
1625 CursorInfo& cursorInfo )
1627 // TODO: Check for multiline with \n, etc...
1629 const Length numberOfCharacters = mLogicalModel->mText.Count();
1630 if( !IsShowingRealText() )
1632 // Do not want to use the place-holder text to set the cursor position.
1634 // Use the line's height of the font's family set to set the cursor's size.
1635 // If there is no font's family set, use the default font.
1636 // Use the current alignment to place the cursor at the beginning, center or end of the box.
1638 cursorInfo.lineOffset = 0.f;
1639 cursorInfo.lineHeight = GetDefaultFontLineHeight();
1640 cursorInfo.primaryCursorHeight = cursorInfo.lineHeight;
1642 switch( mLayoutEngine.GetHorizontalAlignment() )
1644 case LayoutEngine::HORIZONTAL_ALIGN_BEGIN:
1646 cursorInfo.primaryPosition.x = 0.f;
1649 case LayoutEngine::HORIZONTAL_ALIGN_CENTER:
1651 cursorInfo.primaryPosition.x = floorf( 0.5f * mVisualModel->mControlSize.width );
1654 case LayoutEngine::HORIZONTAL_ALIGN_END:
1656 cursorInfo.primaryPosition.x = mVisualModel->mControlSize.width - mEventData->mDecorator->GetCursorWidth();
1661 switch( mLayoutEngine.GetVerticalAlignment() )
1663 case LayoutEngine::VERTICAL_ALIGN_TOP:
1665 cursorInfo.primaryPosition.y = 0.f;
1668 case LayoutEngine::VERTICAL_ALIGN_CENTER:
1670 cursorInfo.primaryPosition.y = floorf( 0.5f * ( mVisualModel->mControlSize.height - cursorInfo.lineHeight ) );
1673 case LayoutEngine::VERTICAL_ALIGN_BOTTOM:
1675 cursorInfo.primaryPosition.y = mVisualModel->mControlSize.height - cursorInfo.lineHeight;
1680 // Nothing else to do.
1684 // Check if the logical position is the first or the last one of the text.
1685 const bool isFirstPosition = 0u == logical;
1686 const bool isLastPosition = numberOfCharacters == logical;
1688 // 'logical' is the logical 'cursor' index.
1689 // Get the next and current logical 'character' index.
1690 const CharacterIndex nextCharacterIndex = logical;
1691 const CharacterIndex characterIndex = isFirstPosition ? logical : logical - 1u;
1693 // Get the direction of the character and the next one.
1694 const CharacterDirection* const modelCharacterDirectionsBuffer = ( 0u != mLogicalModel->mCharacterDirections.Count() ) ? mLogicalModel->mCharacterDirections.Begin() : NULL;
1696 CharacterDirection isCurrentRightToLeft = false;
1697 CharacterDirection isNextRightToLeft = false;
1698 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
1700 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + characterIndex );
1701 isNextRightToLeft = *( modelCharacterDirectionsBuffer + nextCharacterIndex );
1704 // Get the line where the character is laid-out.
1705 const LineRun* const modelLines = mVisualModel->mLines.Begin();
1707 const LineIndex lineIndex = mVisualModel->GetLineOfCharacter( characterIndex );
1708 const LineRun& line = *( modelLines + lineIndex );
1710 // Get the paragraph's direction.
1711 const CharacterDirection isRightToLeftParagraph = line.direction;
1713 // Check whether there is an alternative position:
1715 cursorInfo.isSecondaryCursor = ( !isLastPosition && ( isCurrentRightToLeft != isNextRightToLeft ) ) ||
1716 ( isLastPosition && ( isRightToLeftParagraph != isCurrentRightToLeft ) );
1718 // Set the line offset and height.
1719 cursorInfo.lineOffset = 0.f;
1720 cursorInfo.lineHeight = line.ascender + -line.descender;
1722 // Calculate the primary cursor.
1724 CharacterIndex index = characterIndex;
1725 if( cursorInfo.isSecondaryCursor )
1727 // If there is a secondary position, the primary cursor may be in a different place than the logical index.
1729 if( isLastPosition )
1731 // The position of the cursor after the last character needs special
1732 // care depending on its direction and the direction of the paragraph.
1734 // Need to find the first character after the last character with the paragraph's direction.
1735 // i.e l0 l1 l2 r0 r1 should find r0.
1737 // TODO: check for more than one line!
1738 index = isRightToLeftParagraph ? line.characterRun.characterIndex : line.characterRun.characterIndex + line.characterRun.numberOfCharacters - 1u;
1739 index = mLogicalModel->GetLogicalCharacterIndex( index );
1743 index = ( isRightToLeftParagraph == isCurrentRightToLeft ) ? characterIndex : nextCharacterIndex;
1747 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
1748 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
1749 const Length* const charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
1750 const CharacterIndex* const glyphsToCharactersBuffer = mVisualModel->mGlyphsToCharacters.Begin();
1751 const Vector2* const glyphPositionsBuffer = mVisualModel->mGlyphPositions.Begin();
1752 const GlyphInfo* const glyphInfoBuffer = mVisualModel->mGlyphs.Begin();
1754 // Convert the cursor position into the glyph position.
1755 const GlyphIndex primaryGlyphIndex = *( charactersToGlyphBuffer + index );
1756 const Length primaryNumberOfGlyphs = *( glyphsPerCharacterBuffer + index );
1757 const Length primaryNumberOfCharacters = *( charactersPerGlyphBuffer + primaryGlyphIndex );
1759 // Get the metrics for the group of glyphs.
1760 GlyphMetrics glyphMetrics;
1761 GetGlyphsMetrics( primaryGlyphIndex,
1762 primaryNumberOfGlyphs,
1767 // Whether to add the glyph's advance to the cursor position.
1768 // 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,
1769 // if the logical cursor is one, the position is the position of the first glyph and the advance is added.
1770 // A 'truth table' was build and an online Karnaugh map tool was used to simplify the logic.
1791 // Where F -> isFirstPosition
1792 // L -> isLastPosition
1793 // C -> isCurrentRightToLeft
1794 // P -> isRightToLeftParagraph
1795 // A -> Whether to add the glyph's advance.
1797 const bool addGlyphAdvance = ( ( isLastPosition && !isRightToLeftParagraph ) ||
1798 ( isFirstPosition && isRightToLeftParagraph ) ||
1799 ( !isFirstPosition && !isLastPosition && !isCurrentRightToLeft ) );
1801 float glyphAdvance = addGlyphAdvance ? glyphMetrics.advance : 0.f;
1803 if( !isLastPosition &&
1804 ( primaryNumberOfCharacters > 1u ) )
1806 const CharacterIndex firstIndex = *( glyphsToCharactersBuffer + primaryGlyphIndex );
1808 bool isCurrentRightToLeft = false;
1809 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
1811 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + index );
1814 Length numberOfGlyphAdvance = ( isFirstPosition ? 0u : 1u ) + characterIndex - firstIndex;
1815 if( isCurrentRightToLeft )
1817 numberOfGlyphAdvance = primaryNumberOfCharacters - numberOfGlyphAdvance;
1820 glyphAdvance = static_cast<float>( numberOfGlyphAdvance ) * glyphMetrics.advance / static_cast<float>( primaryNumberOfCharacters );
1823 // Get the glyph position and x bearing.
1824 const Vector2& primaryPosition = *( glyphPositionsBuffer + primaryGlyphIndex );
1826 // Set the primary cursor's height.
1827 cursorInfo.primaryCursorHeight = cursorInfo.isSecondaryCursor ? 0.5f * glyphMetrics.fontHeight : glyphMetrics.fontHeight;
1829 // Set the primary cursor's position.
1830 cursorInfo.primaryPosition.x = -glyphMetrics.xBearing + primaryPosition.x + glyphAdvance;
1831 cursorInfo.primaryPosition.y = line.ascender - glyphMetrics.ascender;
1833 // Calculate the secondary cursor.
1835 if( cursorInfo.isSecondaryCursor )
1837 // Set the secondary cursor's height.
1838 cursorInfo.secondaryCursorHeight = 0.5f * glyphMetrics.fontHeight;
1840 CharacterIndex index = characterIndex;
1841 if( !isLastPosition )
1843 index = ( isRightToLeftParagraph == isCurrentRightToLeft ) ? nextCharacterIndex : characterIndex;
1846 const GlyphIndex secondaryGlyphIndex = *( charactersToGlyphBuffer + index );
1847 const Length secondaryNumberOfGlyphs = *( glyphsPerCharacterBuffer + index );
1849 const Vector2& secondaryPosition = *( glyphPositionsBuffer + secondaryGlyphIndex );
1851 GetGlyphsMetrics( secondaryGlyphIndex,
1852 secondaryNumberOfGlyphs,
1857 // Set the secondary cursor's position.
1858 cursorInfo.secondaryPosition.x = -glyphMetrics.xBearing + secondaryPosition.x + ( isCurrentRightToLeft ? 0.f : glyphMetrics.advance );
1859 cursorInfo.secondaryPosition.y = cursorInfo.lineHeight - cursorInfo.secondaryCursorHeight - line.descender - ( glyphMetrics.fontHeight - glyphMetrics.ascender );
1862 if( LayoutEngine::MULTI_LINE_BOX == mLayoutEngine.GetLayout() )
1864 // If the text is editable and multi-line, the cursor position after a white space shouldn't exceed the boundaries of the text control.
1866 // Note the white spaces laid-out at the end of the line might exceed the boundaries of the control.
1867 // The reason is a wrapped line must not start with a white space so they are laid-out at the end of the line.
1869 if( 0.f > cursorInfo.primaryPosition.x )
1871 cursorInfo.primaryPosition.x = 0.f;
1874 const float edgeWidth = mVisualModel->mControlSize.width - static_cast<float>( mEventData->mDecorator->GetCursorWidth() );
1875 if( cursorInfo.primaryPosition.x > edgeWidth )
1877 cursorInfo.primaryPosition.x = edgeWidth;
1882 CharacterIndex Controller::Impl::CalculateNewCursorIndex( CharacterIndex index ) const
1884 if( NULL == mEventData )
1886 // Nothing to do if there is no text input.
1890 CharacterIndex cursorIndex = mEventData->mPrimaryCursorPosition;
1892 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
1893 const Length* const charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
1895 GlyphIndex glyphIndex = *( charactersToGlyphBuffer + index );
1896 Length numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
1898 if( numberOfCharacters > 1u )
1900 const Script script = mLogicalModel->GetScript( index );
1901 if( HasLigatureMustBreak( script ) )
1903 // Prevents to jump the whole Latin ligatures like fi, ff, or Arabic ﻻ, ...
1904 numberOfCharacters = 1u;
1909 while( 0u == numberOfCharacters )
1912 numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
1916 if( index < mEventData->mPrimaryCursorPosition )
1918 cursorIndex -= numberOfCharacters;
1922 cursorIndex += numberOfCharacters;
1928 void Controller::Impl::UpdateCursorPosition( const CursorInfo& cursorInfo )
1930 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "-->Controller::UpdateCursorPosition %p\n", this );
1931 if( NULL == mEventData )
1933 // Nothing to do if there is no text input.
1934 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition no event data\n" );
1938 const Vector2 offset = mEventData->mScrollPosition + ( IsShowingRealText() ? mAlignmentOffset : Vector2::ZERO );
1939 const Vector2 cursorPosition = cursorInfo.primaryPosition + offset;
1941 // Sets the cursor position.
1942 mEventData->mDecorator->SetPosition( PRIMARY_CURSOR,
1945 cursorInfo.primaryCursorHeight,
1946 cursorInfo.lineHeight );
1947 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Primary cursor position: %f,%f\n", cursorPosition.x, cursorPosition.y );
1949 // Sets the grab handle position.
1950 mEventData->mDecorator->SetPosition( GRAB_HANDLE,
1952 cursorInfo.lineOffset + offset.y,
1953 cursorInfo.lineHeight );
1955 if( cursorInfo.isSecondaryCursor )
1957 mEventData->mDecorator->SetPosition( SECONDARY_CURSOR,
1958 cursorInfo.secondaryPosition.x + offset.x,
1959 cursorInfo.secondaryPosition.y + offset.y,
1960 cursorInfo.secondaryCursorHeight,
1961 cursorInfo.lineHeight );
1962 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Secondary cursor position: %f,%f\n", cursorInfo.secondaryPosition.x + offset.x, cursorInfo.secondaryPosition.y + offset.y );
1965 // Set which cursors are active according the state.
1966 if( EventData::IsEditingState( mEventData->mState ) || ( EventData::GRAB_HANDLE_PANNING == mEventData->mState ) )
1968 if( cursorInfo.isSecondaryCursor )
1970 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_BOTH );
1974 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1979 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
1982 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition\n" );
1985 void Controller::Impl::UpdateSelectionHandle( HandleType handleType,
1986 const CursorInfo& cursorInfo )
1988 if( ( LEFT_SELECTION_HANDLE != handleType ) &&
1989 ( RIGHT_SELECTION_HANDLE != handleType ) )
1994 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
1995 const Vector2 cursorPosition = cursorInfo.primaryPosition + offset;
1997 // Sets the handle's position.
1998 mEventData->mDecorator->SetPosition( handleType,
2000 cursorInfo.lineOffset + offset.y,
2001 cursorInfo.lineHeight );
2003 // If selection handle at start of the text and other at end of the text then all text is selected.
2004 const CharacterIndex startOfSelection = std::min( mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition );
2005 const CharacterIndex endOfSelection = std::max ( mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition );
2006 mEventData->mAllTextSelected = ( startOfSelection == 0 ) && ( endOfSelection == mLogicalModel->mText.Count() );
2009 void Controller::Impl::ClampHorizontalScroll( const Vector2& actualSize )
2011 // Clamp between -space & 0 (and the text alignment).
2013 if( actualSize.width > mVisualModel->mControlSize.width )
2015 const float space = ( actualSize.width - mVisualModel->mControlSize.width ) + mAlignmentOffset.x;
2016 mEventData->mScrollPosition.x = ( mEventData->mScrollPosition.x < -space ) ? -space : mEventData->mScrollPosition.x;
2017 mEventData->mScrollPosition.x = ( mEventData->mScrollPosition.x > -mAlignmentOffset.x ) ? -mAlignmentOffset.x : mEventData->mScrollPosition.x;
2019 mEventData->mDecoratorUpdated = true;
2023 mEventData->mScrollPosition.x = 0.f;
2027 void Controller::Impl::ClampVerticalScroll( const Vector2& actualSize )
2029 // Clamp between -space & 0 (and the text alignment).
2030 if( actualSize.height > mVisualModel->mControlSize.height )
2032 const float space = ( actualSize.height - mVisualModel->mControlSize.height ) + mAlignmentOffset.y;
2033 mEventData->mScrollPosition.y = ( mEventData->mScrollPosition.y < -space ) ? -space : mEventData->mScrollPosition.y;
2034 mEventData->mScrollPosition.y = ( mEventData->mScrollPosition.y > -mAlignmentOffset.y ) ? -mAlignmentOffset.y : mEventData->mScrollPosition.y;
2036 mEventData->mDecoratorUpdated = true;
2040 mEventData->mScrollPosition.y = 0.f;
2044 void Controller::Impl::ScrollToMakePositionVisible( const Vector2& position )
2046 // position is in actor's coords.
2047 const float positionEnd = position.x + ( mEventData->mDecorator ? mEventData->mDecorator->GetCursorWidth() : 0.f );
2049 // Transform the position to decorator coords.
2050 const float alignment = IsShowingRealText() ? mAlignmentOffset.x : 0.f;
2051 const float offset = mEventData->mScrollPosition.x + alignment;
2052 const float decoratorPositionBegin = position.x + offset;
2053 const float decoratorPositionEnd = positionEnd + offset;
2055 if( decoratorPositionBegin < 0.f )
2057 mEventData->mScrollPosition.x = -position.x - alignment;
2059 else if( decoratorPositionEnd > mVisualModel->mControlSize.width )
2061 mEventData->mScrollPosition.x = mVisualModel->mControlSize.width - positionEnd - alignment;
2065 void Controller::Impl::ScrollTextToMatchCursor( const CursorInfo& cursorInfo )
2067 // Get the current cursor position in decorator coords.
2068 const Vector2& currentCursorPosition = mEventData->mDecorator->GetPosition( PRIMARY_CURSOR );
2070 // Calculate the offset to match the cursor position before the character was deleted.
2071 mEventData->mScrollPosition.x = currentCursorPosition.x - cursorInfo.primaryPosition.x - mAlignmentOffset.x;
2073 ClampHorizontalScroll( mVisualModel->GetLayoutSize() );
2076 void Controller::Impl::RequestRelayout()
2078 mControlInterface.RequestTextRelayout();
2083 } // namespace Toolkit