2 * Copyright (c) 2015 Samsung Electronics Co., Ltd.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
19 #include <dali-toolkit/internal/text/text-controller-impl.h>
22 #include <dali/public-api/adaptor-framework/key.h>
23 #include <dali/integration-api/debug.h>
26 #include <dali-toolkit/internal/text/bidirectional-support.h>
27 #include <dali-toolkit/internal/text/character-set-conversion.h>
28 #include <dali-toolkit/internal/text/color-segmentation.h>
29 #include <dali-toolkit/internal/text/multi-language-support.h>
30 #include <dali-toolkit/internal/text/segmentation.h>
31 #include <dali-toolkit/internal/text/shaper.h>
36 #if defined(DEBUG_ENABLED)
37 Debug::Filter* gLogFilter = Debug::Filter::New(Debug::NoLogging, true, "LOG_TEXT_CONTROLS");
41 * @brief Some characters can be shaped in more than one glyph.
42 * This struct is used to retrieve metrics from these group of glyphs.
56 float fontHeight; ///< The font's height of that glyphs.
57 float advance; ///< The sum of all the advances of all the glyphs.
58 float ascender; ///< The font's ascender.
59 float xBearing; ///< The x bearing of the first glyph.
74 * @brief Get some glyph's metrics of a group of glyphs formed as a result of shaping one character.
76 * @param[in] glyphIndex The index to the first glyph.
77 * @param[in] numberOfGlyphs The number of glyphs.
78 * @param[out] glyphMetrics Some glyph metrics (font height, advance, ascender and x bearing).
79 * @param[in] visualModel The visual model.
80 * @param[in] metrics Used to access metrics from FontClient.
82 void GetGlyphsMetrics( GlyphIndex glyphIndex,
83 Length numberOfGlyphs,
84 GlyphMetrics& glyphMetrics,
85 VisualModelPtr& visualModel,
88 const GlyphInfo* glyphsBuffer = visualModel->mGlyphs.Begin();
90 const GlyphInfo& firstGlyph = *( glyphsBuffer + glyphIndex );
92 Text::FontMetrics fontMetrics;
93 metrics->GetFontMetrics( firstGlyph.fontId, fontMetrics );
95 glyphMetrics.fontHeight = fontMetrics.height;
96 glyphMetrics.advance = firstGlyph.advance;
97 glyphMetrics.ascender = fontMetrics.ascender;
98 glyphMetrics.xBearing = firstGlyph.xBearing;
100 for( unsigned int i = 1u; i < numberOfGlyphs; ++i )
102 const GlyphInfo& glyphInfo = *( glyphsBuffer + glyphIndex + i );
104 glyphMetrics.advance += glyphInfo.advance;
108 EventData::EventData( DecoratorPtr decorator )
109 : mDecorator( decorator ),
111 mPlaceholderTextActive(),
112 mPlaceholderTextInactive(),
113 mPlaceholderTextColor( 0.8f, 0.8f, 0.8f, 0.8f ),
117 mPrimaryCursorPosition( 0u ),
118 mLeftSelectionPosition( 0u ),
119 mRightSelectionPosition( 0u ),
120 mPreEditStartPosition( 0u ),
121 mPreEditLength( 0u ),
122 mIsShowingPlaceholderText( false ),
123 mPreEditFlag( false ),
124 mDecoratorUpdated( false ),
125 mCursorBlinkEnabled( true ),
126 mGrabHandleEnabled( true ),
127 mGrabHandlePopupEnabled( true ),
128 mSelectionEnabled( true ),
129 mHorizontalScrollingEnabled( true ),
130 mVerticalScrollingEnabled( false ),
131 mUpdateCursorPosition( false ),
132 mUpdateLeftSelectionPosition( false ),
133 mUpdateRightSelectionPosition( false ),
134 mScrollAfterUpdatePosition( false ),
135 mScrollAfterDelete( false ),
136 mAllTextSelected( false ),
137 mUpdateInputStyle( false )
139 mImfManager = ImfManager::Get();
142 EventData::~EventData()
145 bool Controller::Impl::ProcessInputEvents()
147 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "-->Controller::ProcessInputEvents\n" );
148 if( NULL == mEventData )
150 // Nothing to do if there is no text input.
151 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::ProcessInputEvents no event data\n" );
155 if( mEventData->mDecorator )
157 for( std::vector<Event>::iterator iter = mEventData->mEventQueue.begin();
158 iter != mEventData->mEventQueue.end();
163 case Event::CURSOR_KEY_EVENT:
165 OnCursorKeyEvent( *iter );
168 case Event::TAP_EVENT:
173 case Event::LONG_PRESS_EVENT:
175 OnLongPressEvent( *iter );
178 case Event::PAN_EVENT:
183 case Event::GRAB_HANDLE_EVENT:
184 case Event::LEFT_SELECTION_HANDLE_EVENT:
185 case Event::RIGHT_SELECTION_HANDLE_EVENT: // Fall through
187 OnHandleEvent( *iter );
192 OnSelectEvent( *iter );
195 case Event::SELECT_ALL:
204 // The cursor must also be repositioned after inserts into the model
205 if( mEventData->mUpdateCursorPosition )
207 // Updates the cursor position and scrolls the text to make it visible.
208 CursorInfo cursorInfo;
209 GetCursorPosition( mEventData->mPrimaryCursorPosition,
212 if( mEventData->mScrollAfterUpdatePosition )
214 ScrollToMakePositionVisible( cursorInfo.primaryPosition );
215 mEventData->mScrollAfterUpdatePosition = false;
217 else if( mEventData->mScrollAfterDelete )
219 ScrollTextToMatchCursor( cursorInfo );
220 mEventData->mScrollAfterDelete = false;
223 UpdateCursorPosition( cursorInfo );
225 mEventData->mDecoratorUpdated = true;
226 mEventData->mUpdateCursorPosition = false;
230 bool leftScroll = false;
231 bool rightScroll = false;
233 CursorInfo leftHandleInfo;
234 CursorInfo rightHandleInfo;
236 if( mEventData->mUpdateLeftSelectionPosition )
238 GetCursorPosition( mEventData->mLeftSelectionPosition,
241 if( mEventData->mScrollAfterUpdatePosition )
243 ScrollToMakePositionVisible( leftHandleInfo.primaryPosition );
248 if( mEventData->mUpdateRightSelectionPosition )
250 GetCursorPosition( mEventData->mRightSelectionPosition,
253 if( mEventData->mScrollAfterUpdatePosition )
255 ScrollToMakePositionVisible( rightHandleInfo.primaryPosition );
260 if( mEventData->mUpdateLeftSelectionPosition )
262 UpdateSelectionHandle( LEFT_SELECTION_HANDLE,
266 mEventData->mDecoratorUpdated = true;
269 if( mEventData->mUpdateRightSelectionPosition )
271 UpdateSelectionHandle( RIGHT_SELECTION_HANDLE,
275 mEventData->mDecoratorUpdated = true;
278 if( mEventData->mUpdateLeftSelectionPosition || mEventData->mUpdateRightSelectionPosition )
280 RepositionSelectionHandles();
282 mEventData->mUpdateLeftSelectionPosition = false;
283 mEventData->mUpdateRightSelectionPosition = false;
286 if( leftScroll || rightScroll )
288 mEventData->mScrollAfterUpdatePosition = false;
292 if( mEventData->mUpdateInputStyle )
294 // Set the default style first.
295 RetrieveDefaultInputStyle( mEventData->mInputStyle );
297 // Get the character index from the cursor index.
298 const CharacterIndex styleIndex = ( mEventData->mPrimaryCursorPosition > 0u ) ? mEventData->mPrimaryCursorPosition - 1u : 0u;
300 // Retrieve the style from the style runs stored in the logical model.
301 mLogicalModel->RetrieveStyle( styleIndex, mEventData->mInputStyle );
303 mEventData->mUpdateInputStyle = false;
306 mEventData->mEventQueue.clear();
308 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::ProcessInputEvents\n" );
310 const bool decoratorUpdated = mEventData->mDecoratorUpdated;
311 mEventData->mDecoratorUpdated = false;
313 return decoratorUpdated;
316 void Controller::Impl::UpdateModel( OperationsMask operationsRequired )
318 DALI_LOG_INFO( gLogFilter, Debug::General, "Controller::UpdateModel\n" );
320 // Calculate the operations to be done.
321 const OperationsMask operations = static_cast<OperationsMask>( mOperationsPending & operationsRequired );
323 Vector<Character>& utf32Characters = mLogicalModel->mText;
325 const Length numberOfCharacters = utf32Characters.Count();
327 Vector<LineBreakInfo>& lineBreakInfo = mLogicalModel->mLineBreakInfo;
328 CharacterIndex startIndex = 0u;
329 Length requestedNumberOfCharacters = numberOfCharacters;
330 if( GET_LINE_BREAKS & operations )
332 // Retrieves the line break info. The line break info is used to split the text in 'paragraphs' to
333 // calculate the bidirectional info for each 'paragraph'.
334 // It's also used to layout the text (where it should be a new line) or to shape the text (text in different lines
335 // is not shaped together).
336 lineBreakInfo.Resize( numberOfCharacters, TextAbstraction::LINE_NO_BREAK );
338 SetLineBreakInfo( utf32Characters,
342 Vector<WordBreakInfo>& wordBreakInfo = mLogicalModel->mWordBreakInfo;
343 if( GET_WORD_BREAKS & operations )
345 // Retrieves the word break info. The word break info is used to layout the text (where to wrap the text in lines).
346 wordBreakInfo.Resize( numberOfCharacters, TextAbstraction::WORD_NO_BREAK );
348 SetWordBreakInfo( utf32Characters,
350 requestedNumberOfCharacters,
354 const bool getScripts = GET_SCRIPTS & operations;
355 const bool validateFonts = VALIDATE_FONTS & operations;
357 Vector<ScriptRun>& scripts = mLogicalModel->mScriptRuns;
358 Vector<FontRun>& validFonts = mLogicalModel->mFontRuns;
360 if( getScripts || validateFonts )
362 // Validates the fonts assigned by the application or assigns default ones.
363 // It makes sure all the characters are going to be rendered by the correct font.
364 MultilanguageSupport multilanguageSupport = MultilanguageSupport::Get();
368 // Retrieves the scripts used in the text.
369 multilanguageSupport.SetScripts( utf32Characters,
371 requestedNumberOfCharacters,
377 // Validate the fonts set through the mark-up string.
378 Vector<FontDescriptionRun>& fontDescriptionRuns = mLogicalModel->mFontDescriptionRuns;
380 // Get the default font id.
381 const FontId defaultFontId = ( NULL == mFontDefaults ) ? 0u : mFontDefaults->GetFontId( mFontClient );
383 // Validates the fonts. If there is a character with no assigned font it sets a default one.
384 // After this call, fonts are validated.
385 multilanguageSupport.ValidateFonts( utf32Characters,
390 requestedNumberOfCharacters,
395 Vector<Character> mirroredUtf32Characters;
396 bool textMirrored = false;
397 Length numberOfParagraphs = 0u;
398 if( BIDI_INFO & operations )
400 // Count the number of LINE_NO_BREAK to reserve some space for the vector of paragraph's
401 // bidirectional info.
403 const TextAbstraction::LineBreakInfo* lineBreakInfoBuffer = lineBreakInfo.Begin();
404 for( Length index = 0u; index < numberOfCharacters; ++index )
406 if( TextAbstraction::LINE_NO_BREAK == *( lineBreakInfoBuffer + index ) )
408 ++numberOfParagraphs;
412 Vector<BidirectionalParagraphInfoRun>& bidirectionalInfo = mLogicalModel->mBidirectionalParagraphInfo;
413 bidirectionalInfo.Reserve( numberOfParagraphs );
415 // Calculates the bidirectional info for the whole paragraph if it contains right to left scripts.
416 SetBidirectionalInfo( utf32Characters,
420 requestedNumberOfCharacters,
423 if( 0u != bidirectionalInfo.Count() )
425 // Only set the character directions if there is right to left characters.
426 Vector<CharacterDirection>& directions = mLogicalModel->mCharacterDirections;
427 GetCharactersDirection( bidirectionalInfo,
430 requestedNumberOfCharacters,
433 // This paragraph has right to left text. Some characters may need to be mirrored.
434 // TODO: consider if the mirrored string can be stored as well.
436 textMirrored = GetMirroredText( utf32Characters,
440 requestedNumberOfCharacters,
441 mirroredUtf32Characters );
445 // There is no right to left characters. Clear the directions vector.
446 mLogicalModel->mCharacterDirections.Clear();
450 Vector<GlyphInfo>& glyphs = mVisualModel->mGlyphs;
451 Vector<CharacterIndex>& glyphsToCharactersMap = mVisualModel->mGlyphsToCharacters;
452 Vector<Length>& charactersPerGlyph = mVisualModel->mCharactersPerGlyph;
453 Vector<GlyphIndex> newParagraphGlyphs;
454 newParagraphGlyphs.Reserve( numberOfParagraphs );
456 if( SHAPE_TEXT & operations )
458 const Vector<Character>& textToShape = textMirrored ? mirroredUtf32Characters : utf32Characters;
460 ShapeText( textToShape,
465 glyphsToCharactersMap,
467 newParagraphGlyphs );
469 // Create the 'number of glyphs' per character and the glyph to character conversion tables.
470 mVisualModel->CreateGlyphsPerCharacterTable( numberOfCharacters );
471 mVisualModel->CreateCharacterToGlyphTable( numberOfCharacters );
474 const Length numberOfGlyphs = glyphs.Count();
476 if( GET_GLYPH_METRICS & operations )
478 GlyphInfo* glyphsBuffer = glyphs.Begin();
479 mMetrics->GetGlyphMetrics( glyphsBuffer, numberOfGlyphs );
481 // Update the width and advance of all new paragraph characters.
482 for( Vector<GlyphIndex>::ConstIterator it = newParagraphGlyphs.Begin(), endIt = newParagraphGlyphs.End(); it != endIt; ++it )
484 const GlyphIndex index = *it;
485 GlyphInfo& glyph = *( glyphsBuffer + index );
487 glyph.xBearing = 0.f;
493 if( ( NULL != mEventData ) &&
494 mEventData->mPreEditFlag &&
495 ( 0u != mVisualModel->mCharactersToGlyph.Count() ) )
497 // Add the underline for the pre-edit text.
498 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
499 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
501 const GlyphIndex glyphStart = *( charactersToGlyphBuffer + mEventData->mPreEditStartPosition );
502 const CharacterIndex lastPreEditCharacter = mEventData->mPreEditStartPosition + ( ( mEventData->mPreEditLength > 0u ) ? mEventData->mPreEditLength - 1u : 0u );
503 const Length numberOfGlyphsLastCharacter = *( glyphsPerCharacterBuffer + lastPreEditCharacter );
504 const GlyphIndex glyphEnd = *( charactersToGlyphBuffer + lastPreEditCharacter ) + ( numberOfGlyphsLastCharacter > 1u ? numberOfGlyphsLastCharacter - 1u : 0u );
506 GlyphRun underlineRun;
507 underlineRun.glyphIndex = glyphStart;
508 underlineRun.numberOfGlyphs = 1u + glyphEnd - glyphStart;
510 // TODO: At the moment the underline runs are only for pre-edit.
511 mVisualModel->mUnderlineRuns.PushBack( underlineRun );
515 bool Controller::Impl::UpdateModelStyle( OperationsMask operationsRequired )
517 bool updated = false;
519 if( COLOR & operationsRequired )
521 // Set the color runs in glyphs.
522 SetColorSegmentationInfo( mLogicalModel->mColorRuns,
523 mVisualModel->mCharactersToGlyph,
524 mVisualModel->mGlyphsPerCharacter,
525 mVisualModel->mColorRuns );
533 void Controller::Impl::RetrieveDefaultInputStyle( InputStyle& inputStyle )
535 // Sets the default text's color.
536 inputStyle.textColor = mTextColor;
538 // Sets the default font's family name, weight, width, slant and size.
541 inputStyle.familyName = mFontDefaults->mFontDescription.family;
542 inputStyle.weight = mFontDefaults->mFontDescription.weight;
543 inputStyle.width = mFontDefaults->mFontDescription.width;
544 inputStyle.slant = mFontDefaults->mFontDescription.slant;
545 inputStyle.size = mFontDefaults->mDefaultPointSize;
547 inputStyle.familyDefined = mFontDefaults->familyDefined;
548 inputStyle.weightDefined = mFontDefaults->weightDefined;
549 inputStyle.widthDefined = mFontDefaults->widthDefined;
550 inputStyle.slantDefined = mFontDefaults->slantDefined;
551 inputStyle.sizeDefined = mFontDefaults->sizeDefined;
555 inputStyle.familyName.clear();
556 inputStyle.weight = TextAbstraction::FontWeight::NORMAL;
557 inputStyle.width = TextAbstraction::FontWidth::NORMAL;
558 inputStyle.slant = TextAbstraction::FontSlant::NORMAL;
559 inputStyle.size = 0.f;
561 inputStyle.familyDefined = false;
562 inputStyle.weightDefined = false;
563 inputStyle.widthDefined = false;
564 inputStyle.slantDefined = false;
565 inputStyle.sizeDefined = false;
569 float Controller::Impl::GetDefaultFontLineHeight()
571 FontId defaultFontId = 0u;
572 if( NULL == mFontDefaults )
574 TextAbstraction::FontDescription fontDescription;
575 defaultFontId = mFontClient.GetFontId( fontDescription );
579 defaultFontId = mFontDefaults->GetFontId( mFontClient );
582 Text::FontMetrics fontMetrics;
583 mMetrics->GetFontMetrics( defaultFontId, fontMetrics );
585 return( fontMetrics.ascender - fontMetrics.descender );
588 void Controller::Impl::OnCursorKeyEvent( const Event& event )
590 if( NULL == mEventData )
592 // Nothing to do if there is no text input.
596 int keyCode = event.p1.mInt;
598 if( Dali::DALI_KEY_CURSOR_LEFT == keyCode )
600 if( mEventData->mPrimaryCursorPosition > 0u )
602 mEventData->mPrimaryCursorPosition = CalculateNewCursorIndex( mEventData->mPrimaryCursorPosition - 1u );
605 else if( Dali::DALI_KEY_CURSOR_RIGHT == keyCode )
607 if( mLogicalModel->mText.Count() > mEventData->mPrimaryCursorPosition )
609 mEventData->mPrimaryCursorPosition = CalculateNewCursorIndex( mEventData->mPrimaryCursorPosition );
612 else if( Dali::DALI_KEY_CURSOR_UP == keyCode )
616 else if( Dali::DALI_KEY_CURSOR_DOWN == keyCode )
621 mEventData->mUpdateCursorPosition = true;
622 mEventData->mUpdateInputStyle = true;
623 mEventData->mScrollAfterUpdatePosition = true;
626 void Controller::Impl::OnTapEvent( const Event& event )
628 if( NULL != mEventData )
630 const unsigned int tapCount = event.p1.mUint;
634 if( IsShowingRealText() )
636 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
637 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
639 mEventData->mPrimaryCursorPosition = GetClosestCursorIndex( xPosition,
642 // When the cursor position is changing, delay cursor blinking
643 mEventData->mDecorator->DelayCursorBlink();
647 mEventData->mPrimaryCursorPosition = 0u;
650 mEventData->mUpdateCursorPosition = true;
651 mEventData->mScrollAfterUpdatePosition = true;
652 mEventData->mUpdateInputStyle = true;
654 // Notify the cursor position to the imf manager.
655 if( mEventData->mImfManager )
657 mEventData->mImfManager.SetCursorPosition( mEventData->mPrimaryCursorPosition );
658 mEventData->mImfManager.NotifyCursorPosition();
664 void Controller::Impl::OnPanEvent( const Event& event )
666 if( NULL == mEventData )
668 // Nothing to do if there is no text input.
672 int state = event.p1.mInt;
674 if( Gesture::Started == state ||
675 Gesture::Continuing == state )
677 const Vector2& actualSize = mVisualModel->GetActualSize();
678 const Vector2 currentScroll = mEventData->mScrollPosition;
680 if( mEventData->mHorizontalScrollingEnabled )
682 const float displacementX = event.p2.mFloat;
683 mEventData->mScrollPosition.x += displacementX;
685 ClampHorizontalScroll( actualSize );
688 if( mEventData->mVerticalScrollingEnabled )
690 const float displacementY = event.p3.mFloat;
691 mEventData->mScrollPosition.y += displacementY;
693 ClampVerticalScroll( actualSize );
696 if( mEventData->mDecorator )
698 mEventData->mDecorator->UpdatePositions( mEventData->mScrollPosition - currentScroll );
703 void Controller::Impl::OnLongPressEvent( const Event& event )
705 DALI_LOG_INFO( gLogFilter, Debug::General, "Controller::OnLongPressEvent\n" );
707 if( EventData::EDITING == mEventData->mState )
709 ChangeState ( EventData::EDITING_WITH_POPUP );
710 mEventData->mDecoratorUpdated = true;
714 void Controller::Impl::OnHandleEvent( const Event& event )
716 if( NULL == mEventData )
718 // Nothing to do if there is no text input.
722 const unsigned int state = event.p1.mUint;
723 const bool handleStopScrolling = ( HANDLE_STOP_SCROLLING == state );
725 if( HANDLE_PRESSED == state )
727 // The event.p2 and event.p3 are in decorator coords. Need to transforms to text coords.
728 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
729 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
731 const CharacterIndex handleNewPosition = GetClosestCursorIndex( xPosition, yPosition );
733 if( Event::GRAB_HANDLE_EVENT == event.type )
735 ChangeState ( EventData::GRAB_HANDLE_PANNING );
737 if( handleNewPosition != mEventData->mPrimaryCursorPosition )
739 mEventData->mPrimaryCursorPosition = handleNewPosition;
740 mEventData->mUpdateCursorPosition = true;
743 else if( Event::LEFT_SELECTION_HANDLE_EVENT == event.type )
745 ChangeState ( EventData::SELECTION_HANDLE_PANNING );
747 if( ( handleNewPosition != mEventData->mLeftSelectionPosition ) &&
748 ( handleNewPosition != mEventData->mRightSelectionPosition ) )
750 mEventData->mLeftSelectionPosition = handleNewPosition;
752 mEventData->mUpdateLeftSelectionPosition = true;
755 else if( Event::RIGHT_SELECTION_HANDLE_EVENT == event.type )
757 ChangeState ( EventData::SELECTION_HANDLE_PANNING );
759 if( ( handleNewPosition != mEventData->mRightSelectionPosition ) &&
760 ( handleNewPosition != mEventData->mLeftSelectionPosition ) )
762 mEventData->mRightSelectionPosition = handleNewPosition;
764 mEventData->mUpdateRightSelectionPosition = true;
767 } // end ( HANDLE_PRESSED == state )
768 else if( ( HANDLE_RELEASED == state ) ||
769 handleStopScrolling )
771 CharacterIndex handlePosition = 0u;
772 if( handleStopScrolling )
774 // The event.p2 and event.p3 are in decorator coords. Need to transforms to text coords.
775 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
776 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
778 handlePosition = GetClosestCursorIndex( xPosition, yPosition );
781 if( Event::GRAB_HANDLE_EVENT == event.type )
783 mEventData->mUpdateCursorPosition = true;
784 mEventData->mUpdateInputStyle = true;
786 if( !IsClipboardEmpty() )
788 ChangeState( EventData::EDITING_WITH_PASTE_POPUP ); // Moving grabhandle will show Paste Popup
791 if( handleStopScrolling )
793 mEventData->mScrollAfterUpdatePosition = mEventData->mPrimaryCursorPosition != handlePosition;
794 mEventData->mPrimaryCursorPosition = handlePosition;
797 else if( Event::LEFT_SELECTION_HANDLE_EVENT == event.type )
799 ChangeState( EventData::SELECTING );
801 if( handleStopScrolling )
803 mEventData->mUpdateLeftSelectionPosition = ( mEventData->mRightSelectionPosition != handlePosition );
804 mEventData->mScrollAfterUpdatePosition = mEventData->mUpdateLeftSelectionPosition;
806 if( mEventData->mUpdateLeftSelectionPosition )
808 mEventData->mLeftSelectionPosition = handlePosition;
812 else if( Event::RIGHT_SELECTION_HANDLE_EVENT == event.type )
814 ChangeState( EventData::SELECTING );
816 if( handleStopScrolling )
818 mEventData->mUpdateRightSelectionPosition = ( mEventData->mLeftSelectionPosition != handlePosition );
819 mEventData->mScrollAfterUpdatePosition = mEventData->mUpdateRightSelectionPosition;
820 if( mEventData->mUpdateRightSelectionPosition )
822 mEventData->mRightSelectionPosition = handlePosition;
827 mEventData->mDecoratorUpdated = true;
828 } // end ( ( HANDLE_RELEASED == state ) || ( HANDLE_STOP_SCROLLING == state ) )
829 else if( HANDLE_SCROLLING == state )
831 const float xSpeed = event.p2.mFloat;
832 const Vector2& actualSize = mVisualModel->GetActualSize();
833 const Vector2 currentScrollPosition = mEventData->mScrollPosition;
835 mEventData->mScrollPosition.x += xSpeed;
837 ClampHorizontalScroll( actualSize );
839 bool endOfScroll = false;
840 if( Vector2::ZERO == ( currentScrollPosition - mEventData->mScrollPosition ) )
842 // Notify the decorator there is no more text to scroll.
843 // The decorator won't send more scroll events.
844 mEventData->mDecorator->NotifyEndOfScroll();
845 // Still need to set the position of the handle.
849 // Set the position of the handle.
850 const bool scrollRightDirection = xSpeed > 0.f;
851 const bool leftSelectionHandleEvent = Event::LEFT_SELECTION_HANDLE_EVENT == event.type;
852 const bool rightSelectionHandleEvent = Event::RIGHT_SELECTION_HANDLE_EVENT == event.type;
854 if( Event::GRAB_HANDLE_EVENT == event.type )
856 ChangeState( EventData::GRAB_HANDLE_PANNING );
858 Vector2 position = mEventData->mDecorator->GetPosition( GRAB_HANDLE );
860 // Position the grag handle close to either the left or right edge.
861 position.x = scrollRightDirection ? 0.f : mVisualModel->mControlSize.width;
863 // Get the new handle position.
864 // The grab handle's position is in decorator coords. Need to transforms to text coords.
865 const CharacterIndex handlePosition = GetClosestCursorIndex( position.x - mEventData->mScrollPosition.x - mAlignmentOffset.x,
866 position.y - mEventData->mScrollPosition.y - mAlignmentOffset.y );
868 mEventData->mUpdateCursorPosition = mEventData->mPrimaryCursorPosition != handlePosition;
869 mEventData->mScrollAfterUpdatePosition = mEventData->mUpdateCursorPosition;
870 mEventData->mPrimaryCursorPosition = handlePosition;
871 mEventData->mUpdateInputStyle = mEventData->mUpdateCursorPosition;
873 else if( leftSelectionHandleEvent || rightSelectionHandleEvent )
875 // TODO: This is recalculating the selection box every time the text is scrolled with the selection handles.
876 // Think if something can be done to save power.
878 ChangeState( EventData::SELECTION_HANDLE_PANNING );
880 Vector2 position = mEventData->mDecorator->GetPosition( leftSelectionHandleEvent ? Text::LEFT_SELECTION_HANDLE : Text::RIGHT_SELECTION_HANDLE );
882 // Position the selection handle close to either the left or right edge.
883 position.x = scrollRightDirection ? 0.f : mVisualModel->mControlSize.width;
885 // Get the new handle position.
886 // The selection handle's position is in decorator coords. Need to transforms to text coords.
887 const CharacterIndex handlePosition = GetClosestCursorIndex( position.x - mEventData->mScrollPosition.x - mAlignmentOffset.x,
888 position.y - mEventData->mScrollPosition.y - mAlignmentOffset.y );
890 if( leftSelectionHandleEvent )
892 const bool differentHandles = ( mEventData->mLeftSelectionPosition != handlePosition ) && ( mEventData->mRightSelectionPosition != handlePosition );
893 mEventData->mUpdateLeftSelectionPosition = endOfScroll || differentHandles;
894 if( differentHandles )
896 mEventData->mLeftSelectionPosition = handlePosition;
901 const bool differentHandles = ( mEventData->mRightSelectionPosition != handlePosition ) && ( mEventData->mLeftSelectionPosition != handlePosition );
902 mEventData->mUpdateRightSelectionPosition = endOfScroll || differentHandles;
903 if( differentHandles )
905 mEventData->mRightSelectionPosition = handlePosition;
909 if( mEventData->mUpdateLeftSelectionPosition || mEventData->mUpdateRightSelectionPosition )
911 RepositionSelectionHandles();
913 mEventData->mScrollAfterUpdatePosition = true;
916 mEventData->mDecoratorUpdated = true;
917 } // end ( HANDLE_SCROLLING == state )
920 void Controller::Impl::OnSelectEvent( const Event& event )
922 if( NULL == mEventData )
924 // Nothing to do if there is no text.
928 if( mEventData->mSelectionEnabled )
930 // The event.p2 and event.p3 are in decorator coords. Need to transforms to text coords.
931 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
932 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
934 // Calculates the logical position from the x,y coords.
935 RepositionSelectionHandles( xPosition,
938 mEventData->mUpdateLeftSelectionPosition = true;
939 mEventData->mUpdateRightSelectionPosition = true;
941 mEventData->mScrollAfterUpdatePosition = ( mEventData->mLeftSelectionPosition != mEventData->mRightSelectionPosition );
945 void Controller::Impl::OnSelectAllEvent()
947 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "OnSelectAllEvent mEventData->mSelectionEnabled%s \n", mEventData->mSelectionEnabled?"true":"false");
949 if( NULL == mEventData )
951 // Nothing to do if there is no text.
955 if( mEventData->mSelectionEnabled )
957 mEventData->mLeftSelectionPosition = 0u;
958 mEventData->mRightSelectionPosition = mLogicalModel->mText.Count();
960 mEventData->mScrollAfterUpdatePosition = true;
961 mEventData->mUpdateLeftSelectionPosition = true;
962 mEventData->mUpdateRightSelectionPosition = true;
966 void Controller::Impl::RetrieveSelection( std::string& selectedText, bool deleteAfterRetrieval )
968 if( mEventData->mLeftSelectionPosition == mEventData->mRightSelectionPosition )
970 // Nothing to select if handles are in the same place.
971 selectedText.clear();
975 const bool handlesCrossed = mEventData->mLeftSelectionPosition > mEventData->mRightSelectionPosition;
977 //Get start and end position of selection
978 const CharacterIndex startOfSelectedText = handlesCrossed ? mEventData->mRightSelectionPosition : mEventData->mLeftSelectionPosition;
979 const Length lengthOfSelectedText = ( handlesCrossed ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition ) - startOfSelectedText;
981 // Validate the start and end selection points
982 if( ( startOfSelectedText + lengthOfSelectedText ) <= mLogicalModel->mText.Count() )
984 //Get text as a UTF8 string
985 Vector<Character>& utf32Characters = mLogicalModel->mText;
987 Utf32ToUtf8( &utf32Characters[startOfSelectedText], lengthOfSelectedText, selectedText );
989 if( deleteAfterRetrieval ) // Only delete text if copied successfully
991 // Set as input style the style of the first deleted character.
992 mLogicalModel->RetrieveStyle( startOfSelectedText, mEventData->mInputStyle );
994 mLogicalModel->UpdateTextStyleRuns( startOfSelectedText, -static_cast<int>( lengthOfSelectedText ) );
996 // Delete text between handles
997 Vector<Character>& currentText = mLogicalModel->mText;
999 Vector<Character>::Iterator first = currentText.Begin() + startOfSelectedText;
1000 Vector<Character>::Iterator last = first + lengthOfSelectedText;
1001 currentText.Erase( first, last );
1003 // Scroll after delete.
1004 mEventData->mPrimaryCursorPosition = handlesCrossed ? mEventData->mRightSelectionPosition : mEventData->mLeftSelectionPosition;
1005 mEventData->mScrollAfterDelete = true;
1007 // Udpade the cursor position and the decorator.
1008 // Scroll after the position is updated if is not scrolling after delete.
1009 mEventData->mUpdateCursorPosition = true;
1010 mEventData->mScrollAfterUpdatePosition = !mEventData->mScrollAfterDelete;
1011 mEventData->mDecoratorUpdated = true;
1015 void Controller::Impl::ShowClipboard()
1019 mClipboard.ShowClipboard();
1023 void Controller::Impl::HideClipboard()
1027 mClipboard.HideClipboard();
1031 bool Controller::Impl::CopyStringToClipboard( std::string& source )
1033 //Send string to clipboard
1034 return ( mClipboard && mClipboard.SetItem( source ) );
1037 void Controller::Impl::SendSelectionToClipboard( bool deleteAfterSending )
1039 std::string selectedText;
1040 RetrieveSelection( selectedText, deleteAfterSending );
1041 CopyStringToClipboard( selectedText );
1042 ChangeState( EventData::EDITING );
1045 void Controller::Impl::GetTextFromClipboard( unsigned int itemIndex, std::string& retrievedString )
1049 retrievedString = mClipboard.GetItem( itemIndex );
1053 void Controller::Impl::RepositionSelectionHandles()
1055 CharacterIndex selectionStart = mEventData->mLeftSelectionPosition;
1056 CharacterIndex selectionEnd = mEventData->mRightSelectionPosition;
1058 if( selectionStart == selectionEnd )
1060 // Nothing to select if handles are in the same place.
1064 mEventData->mDecorator->ClearHighlights();
1066 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
1067 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
1068 const GlyphInfo* const glyphsBuffer = mVisualModel->mGlyphs.Begin();
1069 const Vector2* const positionsBuffer = mVisualModel->mGlyphPositions.Begin();
1070 const Length* const charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
1071 const CharacterIndex* const glyphToCharacterBuffer = mVisualModel->mGlyphsToCharacters.Begin();
1072 const CharacterDirection* const modelCharacterDirectionsBuffer = ( 0u != mLogicalModel->mCharacterDirections.Count() ) ? mLogicalModel->mCharacterDirections.Begin() : NULL;
1074 // TODO: Better algorithm to create the highlight box.
1075 // TODO: Multi-line.
1077 // Get the height of the line.
1078 const Vector<LineRun>& lines = mVisualModel->mLines;
1079 const LineRun& firstLine = *lines.Begin();
1080 const float height = firstLine.ascender + -firstLine.descender;
1082 const bool isLastCharacter = selectionEnd >= mLogicalModel->mText.Count();
1083 const bool startDirection = ( ( NULL == modelCharacterDirectionsBuffer ) ? false : *( modelCharacterDirectionsBuffer + selectionStart ) );
1084 const bool endDirection = ( ( NULL == modelCharacterDirectionsBuffer ) ? false : *( modelCharacterDirectionsBuffer + ( selectionEnd - ( isLastCharacter ? 1u : 0u ) ) ) );
1086 // Swap the indices if the start is greater than the end.
1087 const bool indicesSwapped = selectionStart > selectionEnd;
1089 // Tell the decorator to flip the selection handles if needed.
1090 mEventData->mDecorator->SetSelectionHandleFlipState( indicesSwapped, startDirection, endDirection );
1092 if( indicesSwapped )
1094 std::swap( selectionStart, selectionEnd );
1097 // Get the indices to the first and last selected glyphs.
1098 const CharacterIndex selectionEndMinusOne = selectionEnd - 1u;
1099 const GlyphIndex glyphStart = *( charactersToGlyphBuffer + selectionStart );
1100 const Length numberOfGlyphs = *( glyphsPerCharacterBuffer + selectionEndMinusOne );
1101 const GlyphIndex glyphEnd = *( charactersToGlyphBuffer + selectionEndMinusOne ) + ( ( numberOfGlyphs > 0 ) ? numberOfGlyphs - 1u : 0u );
1103 // Check if the first glyph is a ligature that must be broken like Latin ff, fi, or Arabic ﻻ, etc which needs special code.
1104 const Length numberOfCharactersStart = *( charactersPerGlyphBuffer + glyphStart );
1105 bool splitStartGlyph = ( numberOfCharactersStart > 1u ) && HasLigatureMustBreak( mLogicalModel->GetScript( selectionStart ) );
1107 // Check if the last glyph is a ligature that must be broken like Latin ff, fi, or Arabic ﻻ, etc which needs special code.
1108 const Length numberOfCharactersEnd = *( charactersPerGlyphBuffer + glyphEnd );
1109 bool splitEndGlyph = ( glyphStart != glyphEnd ) && ( numberOfCharactersEnd > 1u ) && HasLigatureMustBreak( mLogicalModel->GetScript( selectionEndMinusOne ) );
1111 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
1113 // Traverse the glyphs.
1114 for( GlyphIndex index = glyphStart; index <= glyphEnd; ++index )
1116 const GlyphInfo& glyph = *( glyphsBuffer + index );
1117 const Vector2& position = *( positionsBuffer + index );
1119 if( splitStartGlyph )
1121 // 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.
1123 const float glyphAdvance = glyph.advance / static_cast<float>( numberOfCharactersStart );
1124 const CharacterIndex interGlyphIndex = selectionStart - *( glyphToCharacterBuffer + glyphStart );
1125 // Get the direction of the character.
1126 CharacterDirection isCurrentRightToLeft = false;
1127 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
1129 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + selectionStart );
1132 // The end point could be in the middle of the ligature.
1133 // Calculate the number of characters selected.
1134 const Length numberOfCharacters = ( glyphStart == glyphEnd ) ? ( selectionEnd - selectionStart ) : ( numberOfCharactersStart - interGlyphIndex );
1136 const float xPosition = position.x - glyph.xBearing + offset.x + glyphAdvance * static_cast<float>( isCurrentRightToLeft ? ( numberOfCharactersStart - interGlyphIndex - numberOfCharacters ) : interGlyphIndex );
1138 mEventData->mDecorator->AddHighlight( xPosition,
1140 xPosition + static_cast<float>( numberOfCharacters ) * glyphAdvance,
1141 offset.y + height );
1143 splitStartGlyph = false;
1147 if( splitEndGlyph && ( index == glyphEnd ) )
1149 // 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.
1151 const float glyphAdvance = glyph.advance / static_cast<float>( numberOfCharactersEnd );
1152 const CharacterIndex interGlyphIndex = selectionEnd - *( glyphToCharacterBuffer + glyphEnd );
1153 // Get the direction of the character.
1154 CharacterDirection isCurrentRightToLeft = false;
1155 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
1157 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + selectionEnd );
1160 const Length numberOfCharacters = numberOfCharactersEnd - interGlyphIndex;
1162 const float xPosition = position.x - glyph.xBearing + offset.x + ( isCurrentRightToLeft ? ( glyphAdvance * static_cast<float>( numberOfCharacters ) ) : 0.f );
1163 mEventData->mDecorator->AddHighlight( xPosition,
1165 xPosition + static_cast<float>( interGlyphIndex ) * glyphAdvance,
1166 offset.y + height );
1168 splitEndGlyph = false;
1172 const float xPosition = position.x - glyph.xBearing + offset.x;
1173 mEventData->mDecorator->AddHighlight( xPosition,
1175 xPosition + glyph.advance,
1176 offset.y + height );
1179 CursorInfo primaryCursorInfo;
1180 GetCursorPosition( mEventData->mLeftSelectionPosition,
1181 primaryCursorInfo );
1183 CursorInfo secondaryCursorInfo;
1184 GetCursorPosition( mEventData->mRightSelectionPosition,
1185 secondaryCursorInfo );
1187 const Vector2 primaryPosition = primaryCursorInfo.primaryPosition + offset;
1188 const Vector2 secondaryPosition = secondaryCursorInfo.primaryPosition + offset;
1190 mEventData->mDecorator->SetPosition( LEFT_SELECTION_HANDLE,
1192 primaryCursorInfo.lineOffset + offset.y,
1193 primaryCursorInfo.lineHeight );
1195 mEventData->mDecorator->SetPosition( RIGHT_SELECTION_HANDLE,
1196 secondaryPosition.x,
1197 secondaryCursorInfo.lineOffset + offset.y,
1198 secondaryCursorInfo.lineHeight );
1200 // Cursor to be positioned at end of selection so if selection interrupted and edit mode restarted the cursor will be at end of selection
1201 mEventData->mPrimaryCursorPosition = ( indicesSwapped ) ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition;
1203 // Set the flag to update the decorator.
1204 mEventData->mDecoratorUpdated = true;
1207 void Controller::Impl::RepositionSelectionHandles( float visualX, float visualY )
1209 if( NULL == mEventData )
1211 // Nothing to do if there is no text input.
1215 if( IsShowingPlaceholderText() )
1217 // Nothing to do if there is the place-holder text.
1221 const Length numberOfGlyphs = mVisualModel->mGlyphs.Count();
1222 const Length numberOfLines = mVisualModel->mLines.Count();
1223 if( ( 0 == numberOfGlyphs ) ||
1224 ( 0 == numberOfLines ) )
1226 // Nothing to do if there is no text.
1230 // Find which word was selected
1231 CharacterIndex selectionStart( 0 );
1232 CharacterIndex selectionEnd( 0 );
1233 FindSelectionIndices( visualX, visualY, selectionStart, selectionEnd );
1234 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "%p selectionStart %d selectionEnd %d\n", this, selectionStart, selectionEnd );
1236 if( selectionStart == selectionEnd )
1238 ChangeState( EventData::EDITING );
1239 // Nothing to select. i.e. a white space, out of bounds
1243 mEventData->mLeftSelectionPosition = selectionStart;
1244 mEventData->mRightSelectionPosition = selectionEnd;
1247 void Controller::Impl::SetPopupButtons()
1250 * Sets the Popup buttons to be shown depending on State.
1252 * If SELECTING : CUT & COPY + ( PASTE & CLIPBOARD if content available to paste )
1254 * If EDITING_WITH_POPUP : SELECT & SELECT_ALL
1257 TextSelectionPopup::Buttons buttonsToShow = TextSelectionPopup::NONE;
1259 if( EventData::SELECTING == mEventData->mState )
1261 buttonsToShow = TextSelectionPopup::Buttons( TextSelectionPopup::CUT | TextSelectionPopup::COPY );
1263 if( !IsClipboardEmpty() )
1265 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
1266 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::CLIPBOARD ) );
1269 if( !mEventData->mAllTextSelected )
1271 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::SELECT_ALL ) );
1274 else if( EventData::EDITING_WITH_POPUP == mEventData->mState )
1276 if( mLogicalModel->mText.Count() && !IsShowingPlaceholderText() )
1278 buttonsToShow = TextSelectionPopup::Buttons( TextSelectionPopup::SELECT | TextSelectionPopup::SELECT_ALL );
1281 if( !IsClipboardEmpty() )
1283 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
1284 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::CLIPBOARD ) );
1287 else if( EventData::EDITING_WITH_PASTE_POPUP == mEventData->mState )
1289 if ( !IsClipboardEmpty() )
1291 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
1292 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::CLIPBOARD ) );
1296 mEventData->mDecorator->SetEnabledPopupButtons( buttonsToShow );
1299 void Controller::Impl::ChangeState( EventData::State newState )
1301 if( NULL == mEventData )
1303 // Nothing to do if there is no text input.
1307 DALI_LOG_INFO( gLogFilter, Debug::General, "ChangeState state:%d newstate:%d\n", mEventData->mState, newState );
1309 if( mEventData->mState != newState )
1311 mEventData->mState = newState;
1313 if( EventData::INACTIVE == mEventData->mState )
1315 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
1316 mEventData->mDecorator->StopCursorBlink();
1317 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1318 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1319 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1320 mEventData->mDecorator->SetPopupActive( false );
1321 mEventData->mDecoratorUpdated = true;
1324 else if( EventData::INTERRUPTED == mEventData->mState)
1326 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1327 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1328 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1329 mEventData->mDecorator->SetPopupActive( false );
1330 mEventData->mDecoratorUpdated = true;
1333 else if( EventData::SELECTING == mEventData->mState )
1335 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
1336 mEventData->mDecorator->StopCursorBlink();
1337 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1338 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true );
1339 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true );
1340 if( mEventData->mGrabHandlePopupEnabled )
1343 mEventData->mDecorator->SetPopupActive( true );
1345 mEventData->mDecoratorUpdated = true;
1347 else if( EventData::EDITING == mEventData->mState )
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, false );
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::EDITING_WITH_POPUP == mEventData->mState )
1367 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "EDITING_WITH_POPUP \n", newState );
1369 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1370 if( mEventData->mCursorBlinkEnabled )
1372 mEventData->mDecorator->StartCursorBlink();
1374 if( mEventData->mSelectionEnabled )
1376 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1377 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1381 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
1383 if( mEventData->mGrabHandlePopupEnabled )
1386 mEventData->mDecorator->SetPopupActive( true );
1389 mEventData->mDecoratorUpdated = true;
1391 else if( EventData::EDITING_WITH_GRAB_HANDLE == mEventData->mState )
1393 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "EDITING_WITH_GRAB_HANDLE \n", newState );
1395 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1396 if( mEventData->mCursorBlinkEnabled )
1398 mEventData->mDecorator->StartCursorBlink();
1400 // Grab handle is not shown until a tap is received whilst EDITING
1401 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
1402 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1403 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1404 if( mEventData->mGrabHandlePopupEnabled )
1406 mEventData->mDecorator->SetPopupActive( false );
1408 mEventData->mDecoratorUpdated = true;
1411 else if( EventData::SELECTION_HANDLE_PANNING == mEventData->mState )
1413 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
1414 mEventData->mDecorator->StopCursorBlink();
1415 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1416 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true );
1417 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true );
1418 if( mEventData->mGrabHandlePopupEnabled )
1420 mEventData->mDecorator->SetPopupActive( false );
1422 mEventData->mDecoratorUpdated = true;
1424 else if( EventData::GRAB_HANDLE_PANNING == mEventData->mState )
1426 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "GRAB_HANDLE_PANNING \n", newState );
1428 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1429 if( mEventData->mCursorBlinkEnabled )
1431 mEventData->mDecorator->StartCursorBlink();
1433 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
1434 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1435 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1436 if( mEventData->mGrabHandlePopupEnabled )
1438 mEventData->mDecorator->SetPopupActive( false );
1440 mEventData->mDecoratorUpdated = true;
1442 else if( EventData::EDITING_WITH_PASTE_POPUP == mEventData->mState )
1444 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "EDITING_WITH_PASTE_POPUP \n", newState );
1446 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1447 if( mEventData->mCursorBlinkEnabled )
1449 mEventData->mDecorator->StartCursorBlink();
1452 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
1453 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1454 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1456 if( mEventData->mGrabHandlePopupEnabled )
1459 mEventData->mDecorator->SetPopupActive( true );
1462 mEventData->mDecoratorUpdated = true;
1467 LineIndex Controller::Impl::GetClosestLine( float y ) const
1469 float totalHeight = 0.f;
1470 LineIndex lineIndex = 0u;
1472 const Vector<LineRun>& lines = mVisualModel->mLines;
1473 for( LineIndex endLine = lines.Count();
1474 lineIndex < endLine;
1477 const LineRun& lineRun = lines[lineIndex];
1478 totalHeight += lineRun.ascender + -lineRun.descender;
1479 if( y < totalHeight )
1485 if( lineIndex == 0 )
1493 void Controller::Impl::FindSelectionIndices( float visualX, float visualY, CharacterIndex& startIndex, CharacterIndex& endIndex )
1495 CharacterIndex hitCharacter = GetClosestCursorIndex( visualX, visualY );
1496 DALI_ASSERT_DEBUG( hitCharacter <= mLogicalModel->mText.Count() && "GetClosestCursorIndex returned out of bounds index" );
1498 if( mLogicalModel->mText.Count() == 0 )
1500 return; // if model empty
1503 if( hitCharacter >= mLogicalModel->mText.Count() )
1505 // Closest hit character is the last character.
1506 if( hitCharacter == mLogicalModel->mText.Count() )
1508 hitCharacter--; //Hit character index set to last character in logical model
1512 // hitCharacter is out of bounds
1517 startIndex = hitCharacter;
1518 endIndex = hitCharacter;
1519 bool isHitCharacterWhitespace = TextAbstraction::IsWhiteSpace( mLogicalModel->mText[hitCharacter] );
1521 // Find the start and end of the text
1522 for( startIndex = hitCharacter; startIndex > 0; --startIndex )
1524 if( isHitCharacterWhitespace != TextAbstraction::IsWhiteSpace( mLogicalModel->mText[ startIndex-1 ] ) )
1529 const CharacterIndex pastTheEnd = mLogicalModel->mText.Count();
1530 for( endIndex = hitCharacter + 1u; endIndex < pastTheEnd; ++endIndex )
1532 if( isHitCharacterWhitespace != TextAbstraction::IsWhiteSpace( mLogicalModel->mText[ endIndex ] ) )
1539 CharacterIndex Controller::Impl::GetClosestCursorIndex( float visualX,
1542 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "GetClosestCursorIndex %p closest visualX %f visualY %f\n", this, visualX, visualY );
1544 if( NULL == mEventData )
1546 // Nothing to do if there is no text input.
1550 CharacterIndex logicalIndex = 0u;
1552 const Length numberOfGlyphs = mVisualModel->mGlyphs.Count();
1553 const Length numberOfLines = mVisualModel->mLines.Count();
1554 if( ( 0 == numberOfGlyphs ) ||
1555 ( 0 == numberOfLines ) )
1557 return logicalIndex;
1560 // Find which line is closest
1561 const LineIndex lineIndex = GetClosestLine( visualY );
1562 const LineRun& line = mVisualModel->mLines[lineIndex];
1564 // Get the positions of the glyphs.
1565 const Vector<Vector2>& positions = mVisualModel->mGlyphPositions;
1566 const Vector2* const positionsBuffer = positions.Begin();
1568 // Get the visual to logical conversion tables.
1569 const CharacterIndex* const visualToLogicalBuffer = ( 0u != mLogicalModel->mVisualToLogicalMap.Count() ) ? mLogicalModel->mVisualToLogicalMap.Begin() : NULL;
1570 const CharacterIndex* const visualToLogicalCursorBuffer = mLogicalModel->mVisualToLogicalCursorMap.Begin();
1572 // Get the character to glyph conversion table.
1573 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
1575 // Get the glyphs per character table.
1576 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
1578 // If the vector is void, there is no right to left characters.
1579 const bool hasRightToLeftCharacters = NULL != visualToLogicalBuffer;
1581 const CharacterIndex startCharacter = line.characterRun.characterIndex;
1582 const CharacterIndex endCharacter = line.characterRun.characterIndex + line.characterRun.numberOfCharacters;
1583 DALI_ASSERT_DEBUG( endCharacter <= mLogicalModel->mText.Count() && "Invalid line info" );
1585 // Whether there is a hit on a glyph.
1586 bool matched = false;
1588 // Traverses glyphs in visual order. To do that use the visual to logical conversion table.
1589 CharacterIndex visualIndex = startCharacter;
1590 Length numberOfCharacters = 0u;
1591 for( ; !matched && ( visualIndex < endCharacter ); ++visualIndex )
1593 // The character in logical order.
1594 const CharacterIndex characterLogicalOrderIndex = hasRightToLeftCharacters ? *( visualToLogicalBuffer + visualIndex ) : visualIndex;
1596 // Get the script of the character.
1597 const Script script = mLogicalModel->GetScript( characterLogicalOrderIndex );
1599 // The number of glyphs for that character
1600 const Length numberOfGlyphs = *( glyphsPerCharacterBuffer + characterLogicalOrderIndex );
1601 ++numberOfCharacters;
1604 if( 0u != numberOfGlyphs )
1606 // Get the first character/glyph of the group of glyphs.
1607 const CharacterIndex firstVisualCharacterIndex = 1u + visualIndex - numberOfCharacters;
1608 const CharacterIndex firstLogicalCharacterIndex = hasRightToLeftCharacters ? *( visualToLogicalBuffer + firstVisualCharacterIndex ) : firstVisualCharacterIndex;
1609 const GlyphIndex firstLogicalGlyphIndex = *( charactersToGlyphBuffer + firstLogicalCharacterIndex );
1611 // Get the metrics for the group of glyphs.
1612 GlyphMetrics glyphMetrics;
1613 GetGlyphsMetrics( firstLogicalGlyphIndex,
1619 // Get the position of the first glyph.
1620 const Vector2& position = *( positionsBuffer + firstLogicalGlyphIndex );
1622 // Whether the glyph can be split, like Latin ligatures fi, ff or Arabic ﻻ.
1623 const bool isInterglyphIndex = ( numberOfCharacters > numberOfGlyphs ) && HasLigatureMustBreak( script );
1624 const Length numberOfBlocks = isInterglyphIndex ? numberOfCharacters : 1u;
1625 const float glyphAdvance = glyphMetrics.advance / static_cast<float>( numberOfBlocks );
1627 GlyphIndex index = 0u;
1628 for( ; !matched && ( index < numberOfBlocks ); ++index )
1630 // Find the mid-point of the area containing the glyph
1631 const float glyphCenter = -glyphMetrics.xBearing + position.x + ( static_cast<float>( index ) + 0.5f ) * glyphAdvance;
1633 if( visualX < glyphCenter )
1642 visualIndex = firstVisualCharacterIndex + index;
1646 numberOfCharacters = 0u;
1652 // Return the logical position of the cursor in characters.
1656 visualIndex = endCharacter;
1659 logicalIndex = hasRightToLeftCharacters ? *( visualToLogicalCursorBuffer + visualIndex ) : visualIndex;
1660 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "%p closest visualIndex %d logicalIndex %d\n", this, visualIndex, logicalIndex );
1662 DALI_ASSERT_DEBUG( ( logicalIndex <= mLogicalModel->mText.Count() && logicalIndex >= 0 ) && "GetClosestCursorIndex - Out of bounds index" );
1664 return logicalIndex;
1667 void Controller::Impl::GetCursorPosition( CharacterIndex logical,
1668 CursorInfo& cursorInfo )
1670 // TODO: Check for multiline with \n, etc...
1672 const Length numberOfCharacters = mLogicalModel->mText.Count();
1673 if( !IsShowingRealText() )
1675 // Do not want to use the place-holder text to set the cursor position.
1677 // Use the line's height of the font's family set to set the cursor's size.
1678 // If there is no font's family set, use the default font.
1679 // Use the current alignment to place the cursor at the beginning, center or end of the box.
1681 cursorInfo.lineOffset = 0.f;
1682 cursorInfo.lineHeight = GetDefaultFontLineHeight();
1683 cursorInfo.primaryCursorHeight = cursorInfo.lineHeight;
1685 switch( mLayoutEngine.GetHorizontalAlignment() )
1687 case LayoutEngine::HORIZONTAL_ALIGN_BEGIN:
1689 cursorInfo.primaryPosition.x = 0.f;
1692 case LayoutEngine::HORIZONTAL_ALIGN_CENTER:
1694 cursorInfo.primaryPosition.x = floorf( 0.5f * mVisualModel->mControlSize.width );
1697 case LayoutEngine::HORIZONTAL_ALIGN_END:
1699 cursorInfo.primaryPosition.x = mVisualModel->mControlSize.width - mEventData->mDecorator->GetCursorWidth();
1704 switch( mLayoutEngine.GetVerticalAlignment() )
1706 case LayoutEngine::VERTICAL_ALIGN_TOP:
1708 cursorInfo.primaryPosition.y = 0.f;
1711 case LayoutEngine::VERTICAL_ALIGN_CENTER:
1713 cursorInfo.primaryPosition.y = floorf( 0.5f * ( mVisualModel->mControlSize.height - cursorInfo.lineHeight ) );
1716 case LayoutEngine::VERTICAL_ALIGN_BOTTOM:
1718 cursorInfo.primaryPosition.y = mVisualModel->mControlSize.height - cursorInfo.lineHeight;
1723 // Nothing else to do.
1727 // Check if the logical position is the first or the last one of the text.
1728 const bool isFirstPosition = 0u == logical;
1729 const bool isLastPosition = numberOfCharacters == logical;
1731 // 'logical' is the logical 'cursor' index.
1732 // Get the next and current logical 'character' index.
1733 const CharacterIndex nextCharacterIndex = logical;
1734 const CharacterIndex characterIndex = isFirstPosition ? logical : logical - 1u;
1736 // Get the direction of the character and the next one.
1737 const CharacterDirection* const modelCharacterDirectionsBuffer = ( 0u != mLogicalModel->mCharacterDirections.Count() ) ? mLogicalModel->mCharacterDirections.Begin() : NULL;
1739 CharacterDirection isCurrentRightToLeft = false;
1740 CharacterDirection isNextRightToLeft = false;
1741 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
1743 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + characterIndex );
1744 isNextRightToLeft = *( modelCharacterDirectionsBuffer + nextCharacterIndex );
1747 // Get the line where the character is laid-out.
1748 const LineRun* const modelLines = mVisualModel->mLines.Begin();
1750 const LineIndex lineIndex = mVisualModel->GetLineOfCharacter( characterIndex );
1751 const LineRun& line = *( modelLines + lineIndex );
1753 // Get the paragraph's direction.
1754 const CharacterDirection isRightToLeftParagraph = line.direction;
1756 // Check whether there is an alternative position:
1758 cursorInfo.isSecondaryCursor = ( !isLastPosition && ( isCurrentRightToLeft != isNextRightToLeft ) ) ||
1759 ( isLastPosition && ( isRightToLeftParagraph != isCurrentRightToLeft ) );
1761 // Set the line offset and height.
1762 cursorInfo.lineOffset = 0.f;
1763 cursorInfo.lineHeight = line.ascender + -line.descender;
1765 // Calculate the primary cursor.
1767 CharacterIndex index = characterIndex;
1768 if( cursorInfo.isSecondaryCursor )
1770 // If there is a secondary position, the primary cursor may be in a different place than the logical index.
1772 if( isLastPosition )
1774 // The position of the cursor after the last character needs special
1775 // care depending on its direction and the direction of the paragraph.
1777 // Need to find the first character after the last character with the paragraph's direction.
1778 // i.e l0 l1 l2 r0 r1 should find r0.
1780 // TODO: check for more than one line!
1781 index = isRightToLeftParagraph ? line.characterRun.characterIndex : line.characterRun.characterIndex + line.characterRun.numberOfCharacters - 1u;
1782 index = mLogicalModel->GetLogicalCharacterIndex( index );
1786 index = ( isRightToLeftParagraph == isCurrentRightToLeft ) ? characterIndex : nextCharacterIndex;
1790 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
1791 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
1792 const Length* const charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
1793 const CharacterIndex* const glyphsToCharactersBuffer = mVisualModel->mGlyphsToCharacters.Begin();
1794 const Vector2* const glyphPositionsBuffer = mVisualModel->mGlyphPositions.Begin();
1796 // Convert the cursor position into the glyph position.
1797 const GlyphIndex primaryGlyphIndex = *( charactersToGlyphBuffer + index );
1798 const Length primaryNumberOfGlyphs = *( glyphsPerCharacterBuffer + index );
1799 const Length primaryNumberOfCharacters = *( charactersPerGlyphBuffer + primaryGlyphIndex );
1801 // Get the metrics for the group of glyphs.
1802 GlyphMetrics glyphMetrics;
1803 GetGlyphsMetrics( primaryGlyphIndex,
1804 primaryNumberOfGlyphs,
1809 // Whether to add the glyph's advance to the cursor position.
1810 // 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,
1811 // if the logical cursor is one, the position is the position of the first glyph and the advance is added.
1812 // A 'truth table' was build and an online Karnaugh map tool was used to simplify the logic.
1833 // Where F -> isFirstPosition
1834 // L -> isLastPosition
1835 // C -> isCurrentRightToLeft
1836 // P -> isRightToLeftParagraph
1837 // A -> Whether to add the glyph's advance.
1839 const bool addGlyphAdvance = ( ( isLastPosition && !isRightToLeftParagraph ) ||
1840 ( isFirstPosition && isRightToLeftParagraph ) ||
1841 ( !isFirstPosition && !isLastPosition && !isCurrentRightToLeft ) );
1843 float glyphAdvance = addGlyphAdvance ? glyphMetrics.advance : 0.f;
1845 if( !isLastPosition &&
1846 ( primaryNumberOfCharacters > 1u ) )
1848 const CharacterIndex firstIndex = *( glyphsToCharactersBuffer + primaryGlyphIndex );
1850 bool isCurrentRightToLeft = false;
1851 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
1853 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + index );
1856 Length numberOfGlyphAdvance = ( isFirstPosition ? 0u : 1u ) + characterIndex - firstIndex;
1857 if( isCurrentRightToLeft )
1859 numberOfGlyphAdvance = primaryNumberOfCharacters - numberOfGlyphAdvance;
1862 glyphAdvance = static_cast<float>( numberOfGlyphAdvance ) * glyphMetrics.advance / static_cast<float>( primaryNumberOfCharacters );
1865 // Get the glyph position and x bearing.
1866 const Vector2& primaryPosition = *( glyphPositionsBuffer + primaryGlyphIndex );
1868 // Set the primary cursor's height.
1869 cursorInfo.primaryCursorHeight = cursorInfo.isSecondaryCursor ? 0.5f * glyphMetrics.fontHeight : glyphMetrics.fontHeight;
1871 // Set the primary cursor's position.
1872 cursorInfo.primaryPosition.x = -glyphMetrics.xBearing + primaryPosition.x + glyphAdvance;
1873 cursorInfo.primaryPosition.y = line.ascender - glyphMetrics.ascender;
1875 // Calculate the secondary cursor.
1877 if( cursorInfo.isSecondaryCursor )
1879 // Set the secondary cursor's height.
1880 cursorInfo.secondaryCursorHeight = 0.5f * glyphMetrics.fontHeight;
1882 CharacterIndex index = characterIndex;
1883 if( !isLastPosition )
1885 index = ( isRightToLeftParagraph == isCurrentRightToLeft ) ? nextCharacterIndex : characterIndex;
1888 const GlyphIndex secondaryGlyphIndex = *( charactersToGlyphBuffer + index );
1889 const Length secondaryNumberOfGlyphs = *( glyphsPerCharacterBuffer + index );
1891 const Vector2& secondaryPosition = *( glyphPositionsBuffer + secondaryGlyphIndex );
1893 GetGlyphsMetrics( secondaryGlyphIndex,
1894 secondaryNumberOfGlyphs,
1899 // Set the secondary cursor's position.
1900 cursorInfo.secondaryPosition.x = -glyphMetrics.xBearing + secondaryPosition.x + ( isCurrentRightToLeft ? 0.f : glyphMetrics.advance );
1901 cursorInfo.secondaryPosition.y = cursorInfo.lineHeight - cursorInfo.secondaryCursorHeight - line.descender - ( glyphMetrics.fontHeight - glyphMetrics.ascender );
1905 CharacterIndex Controller::Impl::CalculateNewCursorIndex( CharacterIndex index ) const
1907 if( NULL == mEventData )
1909 // Nothing to do if there is no text input.
1913 CharacterIndex cursorIndex = mEventData->mPrimaryCursorPosition;
1915 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
1916 const Length* const charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
1918 GlyphIndex glyphIndex = *( charactersToGlyphBuffer + index );
1919 Length numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
1921 if( numberOfCharacters > 1u )
1923 const Script script = mLogicalModel->GetScript( index );
1924 if( HasLigatureMustBreak( script ) )
1926 // Prevents to jump the whole Latin ligatures like fi, ff, or Arabic ﻻ, ...
1927 numberOfCharacters = 1u;
1932 while( 0u == numberOfCharacters )
1935 numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
1939 if( index < mEventData->mPrimaryCursorPosition )
1941 cursorIndex -= numberOfCharacters;
1945 cursorIndex += numberOfCharacters;
1951 void Controller::Impl::UpdateCursorPosition( const CursorInfo& cursorInfo )
1953 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "-->Controller::UpdateCursorPosition %p\n", this );
1954 if( NULL == mEventData )
1956 // Nothing to do if there is no text input.
1957 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition no event data\n" );
1961 const Vector2 offset = mEventData->mScrollPosition + ( IsShowingRealText() ? mAlignmentOffset : Vector2::ZERO );
1962 const Vector2 cursorPosition = cursorInfo.primaryPosition + offset;
1964 // Sets the cursor position.
1965 mEventData->mDecorator->SetPosition( PRIMARY_CURSOR,
1968 cursorInfo.primaryCursorHeight,
1969 cursorInfo.lineHeight );
1970 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Primary cursor position: %f,%f\n", cursorPosition.x, cursorPosition.y );
1972 // Sets the grab handle position.
1973 mEventData->mDecorator->SetPosition( GRAB_HANDLE,
1975 cursorInfo.lineOffset + offset.y,
1976 cursorInfo.lineHeight );
1978 if( cursorInfo.isSecondaryCursor )
1980 mEventData->mDecorator->SetPosition( SECONDARY_CURSOR,
1981 cursorInfo.secondaryPosition.x + offset.x,
1982 cursorInfo.secondaryPosition.y + offset.y,
1983 cursorInfo.secondaryCursorHeight,
1984 cursorInfo.lineHeight );
1985 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Secondary cursor position: %f,%f\n", cursorInfo.secondaryPosition.x + offset.x, cursorInfo.secondaryPosition.y + offset.y );
1988 // Set which cursors are active according the state.
1989 if( EventData::IsEditingState( mEventData->mState ) || ( EventData::GRAB_HANDLE_PANNING == mEventData->mState ) )
1991 if( cursorInfo.isSecondaryCursor )
1993 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_BOTH );
1997 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
2002 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
2005 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition\n" );
2008 void Controller::Impl::UpdateSelectionHandle( HandleType handleType,
2009 const CursorInfo& cursorInfo )
2011 if( ( LEFT_SELECTION_HANDLE != handleType ) &&
2012 ( RIGHT_SELECTION_HANDLE != handleType ) )
2017 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
2018 const Vector2 cursorPosition = cursorInfo.primaryPosition + offset;
2020 // Sets the handle's position.
2021 mEventData->mDecorator->SetPosition( handleType,
2023 cursorInfo.lineOffset + offset.y,
2024 cursorInfo.lineHeight );
2026 // If selection handle at start of the text and other at end of the text then all text is selected.
2027 const CharacterIndex startOfSelection = std::min( mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition );
2028 const CharacterIndex endOfSelection = std::max ( mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition );
2029 mEventData->mAllTextSelected = ( startOfSelection == 0 ) && ( endOfSelection == mLogicalModel->mText.Count() );
2032 void Controller::Impl::ClampHorizontalScroll( const Vector2& actualSize )
2034 // Clamp between -space & 0 (and the text alignment).
2036 if( actualSize.width > mVisualModel->mControlSize.width )
2038 const float space = ( actualSize.width - mVisualModel->mControlSize.width ) + mAlignmentOffset.x;
2039 mEventData->mScrollPosition.x = ( mEventData->mScrollPosition.x < -space ) ? -space : mEventData->mScrollPosition.x;
2040 mEventData->mScrollPosition.x = ( mEventData->mScrollPosition.x > -mAlignmentOffset.x ) ? -mAlignmentOffset.x : mEventData->mScrollPosition.x;
2042 mEventData->mDecoratorUpdated = true;
2046 mEventData->mScrollPosition.x = 0.f;
2050 void Controller::Impl::ClampVerticalScroll( const Vector2& actualSize )
2052 // Clamp between -space & 0 (and the text alignment).
2053 if( actualSize.height > mVisualModel->mControlSize.height )
2055 const float space = ( actualSize.height - mVisualModel->mControlSize.height ) + mAlignmentOffset.y;
2056 mEventData->mScrollPosition.y = ( mEventData->mScrollPosition.y < -space ) ? -space : mEventData->mScrollPosition.y;
2057 mEventData->mScrollPosition.y = ( mEventData->mScrollPosition.y > -mAlignmentOffset.y ) ? -mAlignmentOffset.y : mEventData->mScrollPosition.y;
2059 mEventData->mDecoratorUpdated = true;
2063 mEventData->mScrollPosition.y = 0.f;
2067 void Controller::Impl::ScrollToMakePositionVisible( const Vector2& position )
2069 // position is in actor's coords.
2070 const float positionEnd = position.x + ( mEventData->mDecorator ? mEventData->mDecorator->GetCursorWidth() : 0.f );
2072 // Transform the position to decorator coords.
2073 const float offset = mEventData->mScrollPosition.x + mAlignmentOffset.x;
2074 const float decoratorPositionBegin = position.x + offset;
2075 const float decoratorPositionEnd = positionEnd + offset;
2077 if( decoratorPositionBegin < 0.f )
2079 mEventData->mScrollPosition.x = -position.x - mAlignmentOffset.x;
2081 else if( decoratorPositionEnd > mVisualModel->mControlSize.width )
2083 mEventData->mScrollPosition.x = mVisualModel->mControlSize.width - positionEnd - mAlignmentOffset.x;
2087 void Controller::Impl::ScrollTextToMatchCursor( const CursorInfo& cursorInfo )
2089 // Get the current cursor position in decorator coords.
2090 const Vector2& currentCursorPosition = mEventData->mDecorator->GetPosition( PRIMARY_CURSOR );
2092 // Calculate the offset to match the cursor position before the character was deleted.
2093 mEventData->mScrollPosition.x = currentCursorPosition.x - cursorInfo.primaryPosition.x - mAlignmentOffset.x;
2095 ClampHorizontalScroll( mVisualModel->GetActualSize() );
2098 void Controller::Impl::RequestRelayout()
2100 mControlInterface.RequestTextRelayout();
2105 } // namespace Toolkit