2 * Copyright (c) 2017 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>
27 #include <dali-toolkit/internal/text/bidirectional-support.h>
28 #include <dali-toolkit/internal/text/character-set-conversion.h>
29 #include <dali-toolkit/internal/text/color-segmentation.h>
30 #include <dali-toolkit/internal/text/cursor-helper-functions.h>
31 #include <dali-toolkit/internal/text/multi-language-support.h>
32 #include <dali-toolkit/internal/text/segmentation.h>
33 #include <dali-toolkit/internal/text/shaper.h>
34 #include <dali-toolkit/internal/text/text-control-interface.h>
35 #include <dali-toolkit/internal/text/text-run-container.h>
41 * @brief Struct used to calculate the selection box.
43 struct SelectionBoxInfo
51 #if defined(DEBUG_ENABLED)
52 Debug::Filter* gLogFilter = Debug::Filter::New(Debug::NoLogging, true, "LOG_TEXT_CONTROLS");
55 const float MAX_FLOAT = std::numeric_limits<float>::max();
56 const float MIN_FLOAT = std::numeric_limits<float>::min();
57 const Dali::Toolkit::Text::CharacterDirection LTR = false; ///< Left To Right direction
70 EventData::EventData( DecoratorPtr decorator, InputMethodContext& inputMethodContext )
71 : mDecorator( decorator ),
72 mInputMethodContext( inputMethodContext ),
73 mPlaceholderFont( NULL ),
74 mPlaceholderTextActive(),
75 mPlaceholderTextInactive(),
76 mPlaceholderTextColor( 0.8f, 0.8f, 0.8f, 0.8f ), // This color has been published in the Public API (placeholder-properties.h).
78 mInputStyleChangedQueue(),
79 mPreviousState( INACTIVE ),
81 mPrimaryCursorPosition( 0u ),
82 mLeftSelectionPosition( 0u ),
83 mRightSelectionPosition( 0u ),
84 mPreEditStartPosition( 0u ),
86 mCursorHookPositionX( 0.f ),
87 mDoubleTapAction( Controller::NoTextTap::NO_ACTION ),
88 mLongPressAction( Controller::NoTextTap::SHOW_SELECTION_POPUP ),
89 mIsShowingPlaceholderText( false ),
90 mPreEditFlag( false ),
91 mDecoratorUpdated( false ),
92 mCursorBlinkEnabled( true ),
93 mGrabHandleEnabled( true ),
94 mGrabHandlePopupEnabled( true ),
95 mSelectionEnabled( true ),
96 mUpdateCursorHookPosition( false ),
97 mUpdateCursorPosition( false ),
98 mUpdateGrabHandlePosition( false ),
99 mUpdateLeftSelectionPosition( false ),
100 mUpdateRightSelectionPosition( false ),
101 mIsLeftHandleSelected( false ),
102 mIsRightHandleSelected( false ),
103 mUpdateHighlightBox( false ),
104 mScrollAfterUpdatePosition( false ),
105 mScrollAfterDelete( false ),
106 mAllTextSelected( false ),
107 mUpdateInputStyle( false ),
108 mPasswordInput( false ),
109 mCheckScrollAmount( false ),
110 mIsPlaceholderPixelSize( false ),
111 mIsPlaceholderElideEnabled( false ),
112 mPlaceholderEllipsisFlag( false ),
113 mShiftSelectionFlag( true ),
114 mUpdateAlignment( false )
118 EventData::~EventData()
121 bool Controller::Impl::ProcessInputEvents()
123 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "-->Controller::ProcessInputEvents\n" );
124 if( NULL == mEventData )
126 // Nothing to do if there is no text input.
127 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::ProcessInputEvents no event data\n" );
131 if( mEventData->mDecorator )
133 for( std::vector<Event>::iterator iter = mEventData->mEventQueue.begin();
134 iter != mEventData->mEventQueue.end();
139 case Event::CURSOR_KEY_EVENT:
141 OnCursorKeyEvent( *iter );
144 case Event::TAP_EVENT:
149 case Event::LONG_PRESS_EVENT:
151 OnLongPressEvent( *iter );
154 case Event::PAN_EVENT:
159 case Event::GRAB_HANDLE_EVENT:
160 case Event::LEFT_SELECTION_HANDLE_EVENT:
161 case Event::RIGHT_SELECTION_HANDLE_EVENT: // Fall through
163 OnHandleEvent( *iter );
168 OnSelectEvent( *iter );
171 case Event::SELECT_ALL:
180 if( mEventData->mUpdateCursorPosition ||
181 mEventData->mUpdateHighlightBox )
183 NotifyInputMethodContext();
186 // The cursor must also be repositioned after inserts into the model
187 if( mEventData->mUpdateCursorPosition )
189 // Updates the cursor position and scrolls the text to make it visible.
190 CursorInfo cursorInfo;
191 // Calculate the cursor position from the new cursor index.
192 GetCursorPosition( mEventData->mPrimaryCursorPosition,
195 if( mEventData->mUpdateCursorHookPosition )
197 // Update the cursor hook position. Used to move the cursor with the keys 'up' and 'down'.
198 mEventData->mCursorHookPositionX = cursorInfo.primaryPosition.x;
199 mEventData->mUpdateCursorHookPosition = false;
202 // Scroll first the text after delete ...
203 if( mEventData->mScrollAfterDelete )
205 ScrollTextToMatchCursor( cursorInfo );
208 // ... then, text can be scrolled to make the cursor visible.
209 if( mEventData->mScrollAfterUpdatePosition )
211 const Vector2 currentCursorPosition( cursorInfo.primaryPosition.x, cursorInfo.lineOffset );
212 ScrollToMakePositionVisible( currentCursorPosition, cursorInfo.lineHeight );
214 mEventData->mScrollAfterUpdatePosition = false;
215 mEventData->mScrollAfterDelete = false;
217 UpdateCursorPosition( cursorInfo );
219 mEventData->mDecoratorUpdated = true;
220 mEventData->mUpdateCursorPosition = false;
221 mEventData->mUpdateGrabHandlePosition = false;
225 CursorInfo leftHandleInfo;
226 CursorInfo rightHandleInfo;
228 if( mEventData->mUpdateHighlightBox )
230 GetCursorPosition( mEventData->mLeftSelectionPosition,
233 GetCursorPosition( mEventData->mRightSelectionPosition,
236 if( mEventData->mScrollAfterUpdatePosition && ( mEventData->mIsLeftHandleSelected ? mEventData->mUpdateLeftSelectionPosition : mEventData->mUpdateRightSelectionPosition ) )
238 if( mEventData->mIsLeftHandleSelected && mEventData->mIsRightHandleSelected )
240 CursorInfo& infoLeft = leftHandleInfo;
242 const Vector2 currentCursorPositionLeft( infoLeft.primaryPosition.x, infoLeft.lineOffset );
243 ScrollToMakePositionVisible( currentCursorPositionLeft, infoLeft.lineHeight );
245 CursorInfo& infoRight = rightHandleInfo;
247 const Vector2 currentCursorPositionRight( infoRight.primaryPosition.x, infoRight.lineOffset );
248 ScrollToMakePositionVisible( currentCursorPositionRight, infoRight.lineHeight );
252 CursorInfo& info = mEventData->mIsLeftHandleSelected ? leftHandleInfo : rightHandleInfo;
254 const Vector2 currentCursorPosition( info.primaryPosition.x, info.lineOffset );
255 ScrollToMakePositionVisible( currentCursorPosition, info.lineHeight );
260 if( mEventData->mUpdateLeftSelectionPosition )
262 UpdateSelectionHandle( LEFT_SELECTION_HANDLE,
266 mEventData->mDecoratorUpdated = true;
267 mEventData->mUpdateLeftSelectionPosition = false;
270 if( mEventData->mUpdateRightSelectionPosition )
272 UpdateSelectionHandle( RIGHT_SELECTION_HANDLE,
276 mEventData->mDecoratorUpdated = true;
277 mEventData->mUpdateRightSelectionPosition = false;
280 if( mEventData->mUpdateHighlightBox )
282 RepositionSelectionHandles();
284 mEventData->mUpdateLeftSelectionPosition = false;
285 mEventData->mUpdateRightSelectionPosition = false;
286 mEventData->mUpdateHighlightBox = false;
287 mEventData->mIsLeftHandleSelected = false;
288 mEventData->mIsRightHandleSelected = false;
291 mEventData->mScrollAfterUpdatePosition = false;
294 if( mEventData->mUpdateInputStyle )
296 // Keep a copy of the current input style.
297 InputStyle currentInputStyle;
298 currentInputStyle.Copy( mEventData->mInputStyle );
300 // Set the default style first.
301 RetrieveDefaultInputStyle( mEventData->mInputStyle );
303 // Get the character index from the cursor index.
304 const CharacterIndex styleIndex = ( mEventData->mPrimaryCursorPosition > 0u ) ? mEventData->mPrimaryCursorPosition - 1u : 0u;
306 // Retrieve the style from the style runs stored in the logical model.
307 mModel->mLogicalModel->RetrieveStyle( styleIndex, mEventData->mInputStyle );
309 // Compare if the input style has changed.
310 const bool hasInputStyleChanged = !currentInputStyle.Equal( mEventData->mInputStyle );
312 if( hasInputStyleChanged )
314 const InputStyle::Mask styleChangedMask = currentInputStyle.GetInputStyleChangeMask( mEventData->mInputStyle );
315 // Queue the input style changed signal.
316 mEventData->mInputStyleChangedQueue.PushBack( styleChangedMask );
319 mEventData->mUpdateInputStyle = false;
322 mEventData->mEventQueue.clear();
324 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::ProcessInputEvents\n" );
326 const bool decoratorUpdated = mEventData->mDecoratorUpdated;
327 mEventData->mDecoratorUpdated = false;
329 return decoratorUpdated;
332 void Controller::Impl::NotifyInputMethodContext()
334 if( mEventData && mEventData->mInputMethodContext )
336 CharacterIndex cursorPosition = GetLogicalCursorPosition();
338 const Length numberOfWhiteSpaces = GetNumberOfWhiteSpaces( 0u );
340 // Update the cursor position by removing the initial white spaces.
341 if( cursorPosition < numberOfWhiteSpaces )
347 cursorPosition -= numberOfWhiteSpaces;
350 mEventData->mInputMethodContext.SetCursorPosition( cursorPosition );
351 mEventData->mInputMethodContext.NotifyCursorPosition();
355 void Controller::Impl::NotifyInputMethodContextMultiLineStatus()
357 if ( mEventData && mEventData->mInputMethodContext )
359 Text::Layout::Engine::Type layout = mLayoutEngine.GetLayout();
360 mEventData->mInputMethodContext.NotifyTextInputMultiLine( layout == Text::Layout::Engine::MULTI_LINE_BOX );
364 CharacterIndex Controller::Impl::GetLogicalCursorPosition() const
366 CharacterIndex cursorPosition = 0u;
370 if( ( EventData::SELECTING == mEventData->mState ) ||
371 ( EventData::SELECTION_HANDLE_PANNING == mEventData->mState ) )
373 cursorPosition = std::min( mEventData->mRightSelectionPosition, mEventData->mLeftSelectionPosition );
377 cursorPosition = mEventData->mPrimaryCursorPosition;
381 return cursorPosition;
384 Length Controller::Impl::GetNumberOfWhiteSpaces( CharacterIndex index ) const
386 Length numberOfWhiteSpaces = 0u;
388 // Get the buffer to the text.
389 Character* utf32CharacterBuffer = mModel->mLogicalModel->mText.Begin();
391 const Length totalNumberOfCharacters = mModel->mLogicalModel->mText.Count();
392 for( ; index < totalNumberOfCharacters; ++index, ++numberOfWhiteSpaces )
394 if( !TextAbstraction::IsWhiteSpace( *( utf32CharacterBuffer + index ) ) )
400 return numberOfWhiteSpaces;
403 void Controller::Impl::GetText( CharacterIndex index, std::string& text ) const
405 // Get the total number of characters.
406 Length numberOfCharacters = mModel->mLogicalModel->mText.Count();
408 // Retrieve the text.
409 if( 0u != numberOfCharacters )
411 Utf32ToUtf8( mModel->mLogicalModel->mText.Begin() + index, numberOfCharacters - index, text );
415 void Controller::Impl::CalculateTextUpdateIndices( Length& numberOfCharacters )
417 mTextUpdateInfo.mParagraphCharacterIndex = 0u;
418 mTextUpdateInfo.mStartGlyphIndex = 0u;
419 mTextUpdateInfo.mStartLineIndex = 0u;
420 numberOfCharacters = 0u;
422 const Length numberOfParagraphs = mModel->mLogicalModel->mParagraphInfo.Count();
423 if( 0u == numberOfParagraphs )
425 mTextUpdateInfo.mParagraphCharacterIndex = 0u;
426 numberOfCharacters = 0u;
428 mTextUpdateInfo.mRequestedNumberOfCharacters = mTextUpdateInfo.mNumberOfCharactersToAdd - mTextUpdateInfo.mNumberOfCharactersToRemove;
430 // Nothing else to do if there are no paragraphs.
434 // Find the paragraphs to be updated.
435 Vector<ParagraphRunIndex> paragraphsToBeUpdated;
436 if( mTextUpdateInfo.mCharacterIndex >= mTextUpdateInfo.mPreviousNumberOfCharacters )
438 // Text is being added at the end of the current text.
439 if( mTextUpdateInfo.mIsLastCharacterNewParagraph )
441 // Text is being added in a new paragraph after the last character of the text.
442 mTextUpdateInfo.mParagraphCharacterIndex = mTextUpdateInfo.mPreviousNumberOfCharacters;
443 numberOfCharacters = 0u;
444 mTextUpdateInfo.mRequestedNumberOfCharacters = mTextUpdateInfo.mNumberOfCharactersToAdd - mTextUpdateInfo.mNumberOfCharactersToRemove;
446 mTextUpdateInfo.mStartGlyphIndex = mModel->mVisualModel->mGlyphs.Count();
447 mTextUpdateInfo.mStartLineIndex = mModel->mVisualModel->mLines.Count() - 1u;
449 // Nothing else to do;
453 paragraphsToBeUpdated.PushBack( numberOfParagraphs - 1u );
457 Length numberOfCharactersToUpdate = 0u;
458 if( mTextUpdateInfo.mFullRelayoutNeeded )
460 numberOfCharactersToUpdate = mTextUpdateInfo.mPreviousNumberOfCharacters;
464 numberOfCharactersToUpdate = ( mTextUpdateInfo.mNumberOfCharactersToRemove > 0u ) ? mTextUpdateInfo.mNumberOfCharactersToRemove : 1u;
466 mModel->mLogicalModel->FindParagraphs( mTextUpdateInfo.mCharacterIndex,
467 numberOfCharactersToUpdate,
468 paragraphsToBeUpdated );
471 if( 0u != paragraphsToBeUpdated.Count() )
473 const ParagraphRunIndex firstParagraphIndex = *( paragraphsToBeUpdated.Begin() );
474 const ParagraphRun& firstParagraph = *( mModel->mLogicalModel->mParagraphInfo.Begin() + firstParagraphIndex );
475 mTextUpdateInfo.mParagraphCharacterIndex = firstParagraph.characterRun.characterIndex;
477 ParagraphRunIndex lastParagraphIndex = *( paragraphsToBeUpdated.End() - 1u );
478 const ParagraphRun& lastParagraph = *( mModel->mLogicalModel->mParagraphInfo.Begin() + lastParagraphIndex );
480 if( ( mTextUpdateInfo.mNumberOfCharactersToRemove > 0u ) && // Some character are removed.
481 ( lastParagraphIndex < numberOfParagraphs - 1u ) && // There is a next paragraph.
482 ( ( lastParagraph.characterRun.characterIndex + lastParagraph.characterRun.numberOfCharacters ) == // The last removed character is the new paragraph character.
483 ( mTextUpdateInfo.mCharacterIndex + mTextUpdateInfo.mNumberOfCharactersToRemove ) ) )
485 // The new paragraph character of the last updated paragraph has been removed so is going to be merged with the next one.
486 const ParagraphRun& lastParagraph = *( mModel->mLogicalModel->mParagraphInfo.Begin() + lastParagraphIndex + 1u );
488 numberOfCharacters = lastParagraph.characterRun.characterIndex + lastParagraph.characterRun.numberOfCharacters - mTextUpdateInfo.mParagraphCharacterIndex;
492 numberOfCharacters = lastParagraph.characterRun.characterIndex + lastParagraph.characterRun.numberOfCharacters - mTextUpdateInfo.mParagraphCharacterIndex;
496 mTextUpdateInfo.mRequestedNumberOfCharacters = numberOfCharacters + mTextUpdateInfo.mNumberOfCharactersToAdd - mTextUpdateInfo.mNumberOfCharactersToRemove;
497 mTextUpdateInfo.mStartGlyphIndex = *( mModel->mVisualModel->mCharactersToGlyph.Begin() + mTextUpdateInfo.mParagraphCharacterIndex );
500 void Controller::Impl::ClearFullModelData( OperationsMask operations )
502 if( NO_OPERATION != ( GET_LINE_BREAKS & operations ) )
504 mModel->mLogicalModel->mLineBreakInfo.Clear();
505 mModel->mLogicalModel->mParagraphInfo.Clear();
508 if( NO_OPERATION != ( GET_WORD_BREAKS & operations ) )
510 mModel->mLogicalModel->mLineBreakInfo.Clear();
513 if( NO_OPERATION != ( GET_SCRIPTS & operations ) )
515 mModel->mLogicalModel->mScriptRuns.Clear();
518 if( NO_OPERATION != ( VALIDATE_FONTS & operations ) )
520 mModel->mLogicalModel->mFontRuns.Clear();
523 if( 0u != mModel->mLogicalModel->mBidirectionalParagraphInfo.Count() )
525 if( NO_OPERATION != ( BIDI_INFO & operations ) )
527 mModel->mLogicalModel->mBidirectionalParagraphInfo.Clear();
528 mModel->mLogicalModel->mCharacterDirections.Clear();
531 if( NO_OPERATION != ( REORDER & operations ) )
533 // Free the allocated memory used to store the conversion table in the bidirectional line info run.
534 for( Vector<BidirectionalLineInfoRun>::Iterator it = mModel->mLogicalModel->mBidirectionalLineInfo.Begin(),
535 endIt = mModel->mLogicalModel->mBidirectionalLineInfo.End();
539 BidirectionalLineInfoRun& bidiLineInfo = *it;
541 free( bidiLineInfo.visualToLogicalMap );
542 bidiLineInfo.visualToLogicalMap = NULL;
544 mModel->mLogicalModel->mBidirectionalLineInfo.Clear();
548 if( NO_OPERATION != ( SHAPE_TEXT & operations ) )
550 mModel->mVisualModel->mGlyphs.Clear();
551 mModel->mVisualModel->mGlyphsToCharacters.Clear();
552 mModel->mVisualModel->mCharactersToGlyph.Clear();
553 mModel->mVisualModel->mCharactersPerGlyph.Clear();
554 mModel->mVisualModel->mGlyphsPerCharacter.Clear();
555 mModel->mVisualModel->mGlyphPositions.Clear();
558 if( NO_OPERATION != ( LAYOUT & operations ) )
560 mModel->mVisualModel->mLines.Clear();
563 if( NO_OPERATION != ( COLOR & operations ) )
565 mModel->mVisualModel->mColorIndices.Clear();
569 void Controller::Impl::ClearCharacterModelData( CharacterIndex startIndex, CharacterIndex endIndex, OperationsMask operations )
571 const CharacterIndex endIndexPlusOne = endIndex + 1u;
573 if( NO_OPERATION != ( GET_LINE_BREAKS & operations ) )
575 // Clear the line break info.
576 LineBreakInfo* lineBreakInfoBuffer = mModel->mLogicalModel->mLineBreakInfo.Begin();
578 mModel->mLogicalModel->mLineBreakInfo.Erase( lineBreakInfoBuffer + startIndex,
579 lineBreakInfoBuffer + endIndexPlusOne );
581 // Clear the paragraphs.
582 ClearCharacterRuns( startIndex,
584 mModel->mLogicalModel->mParagraphInfo );
587 if( NO_OPERATION != ( GET_WORD_BREAKS & operations ) )
589 // Clear the word break info.
590 WordBreakInfo* wordBreakInfoBuffer = mModel->mLogicalModel->mWordBreakInfo.Begin();
592 mModel->mLogicalModel->mWordBreakInfo.Erase( wordBreakInfoBuffer + startIndex,
593 wordBreakInfoBuffer + endIndexPlusOne );
596 if( NO_OPERATION != ( GET_SCRIPTS & operations ) )
598 // Clear the scripts.
599 ClearCharacterRuns( startIndex,
601 mModel->mLogicalModel->mScriptRuns );
604 if( NO_OPERATION != ( VALIDATE_FONTS & operations ) )
607 ClearCharacterRuns( startIndex,
609 mModel->mLogicalModel->mFontRuns );
612 if( 0u != mModel->mLogicalModel->mBidirectionalParagraphInfo.Count() )
614 if( NO_OPERATION != ( BIDI_INFO & operations ) )
616 // Clear the bidirectional paragraph info.
617 ClearCharacterRuns( startIndex,
619 mModel->mLogicalModel->mBidirectionalParagraphInfo );
621 // Clear the character's directions.
622 CharacterDirection* characterDirectionsBuffer = mModel->mLogicalModel->mCharacterDirections.Begin();
624 mModel->mLogicalModel->mCharacterDirections.Erase( characterDirectionsBuffer + startIndex,
625 characterDirectionsBuffer + endIndexPlusOne );
628 if( NO_OPERATION != ( REORDER & operations ) )
630 uint32_t startRemoveIndex = mModel->mLogicalModel->mBidirectionalLineInfo.Count();
631 uint32_t endRemoveIndex = startRemoveIndex;
632 ClearCharacterRuns( startIndex,
634 mModel->mLogicalModel->mBidirectionalLineInfo,
638 BidirectionalLineInfoRun* bidirectionalLineInfoBuffer = mModel->mLogicalModel->mBidirectionalLineInfo.Begin();
640 // Free the allocated memory used to store the conversion table in the bidirectional line info run.
641 for( Vector<BidirectionalLineInfoRun>::Iterator it = bidirectionalLineInfoBuffer + startRemoveIndex,
642 endIt = bidirectionalLineInfoBuffer + endRemoveIndex;
646 BidirectionalLineInfoRun& bidiLineInfo = *it;
648 free( bidiLineInfo.visualToLogicalMap );
649 bidiLineInfo.visualToLogicalMap = NULL;
652 mModel->mLogicalModel->mBidirectionalLineInfo.Erase( bidirectionalLineInfoBuffer + startRemoveIndex,
653 bidirectionalLineInfoBuffer + endRemoveIndex );
658 void Controller::Impl::ClearGlyphModelData( CharacterIndex startIndex, CharacterIndex endIndex, OperationsMask operations )
660 const CharacterIndex endIndexPlusOne = endIndex + 1u;
661 const Length numberOfCharactersRemoved = endIndexPlusOne - startIndex;
663 // Convert the character index to glyph index before deleting the character to glyph and the glyphs per character buffers.
664 GlyphIndex* charactersToGlyphBuffer = mModel->mVisualModel->mCharactersToGlyph.Begin();
665 Length* glyphsPerCharacterBuffer = mModel->mVisualModel->mGlyphsPerCharacter.Begin();
667 const GlyphIndex endGlyphIndexPlusOne = *( charactersToGlyphBuffer + endIndex ) + *( glyphsPerCharacterBuffer + endIndex );
668 const Length numberOfGlyphsRemoved = endGlyphIndexPlusOne - mTextUpdateInfo.mStartGlyphIndex;
670 if( NO_OPERATION != ( SHAPE_TEXT & operations ) )
672 // Update the character to glyph indices.
673 for( Vector<GlyphIndex>::Iterator it = charactersToGlyphBuffer + endIndexPlusOne,
674 endIt = charactersToGlyphBuffer + mModel->mVisualModel->mCharactersToGlyph.Count();
678 CharacterIndex& index = *it;
679 index -= numberOfGlyphsRemoved;
682 // Clear the character to glyph conversion table.
683 mModel->mVisualModel->mCharactersToGlyph.Erase( charactersToGlyphBuffer + startIndex,
684 charactersToGlyphBuffer + endIndexPlusOne );
686 // Clear the glyphs per character table.
687 mModel->mVisualModel->mGlyphsPerCharacter.Erase( glyphsPerCharacterBuffer + startIndex,
688 glyphsPerCharacterBuffer + endIndexPlusOne );
690 // Clear the glyphs buffer.
691 GlyphInfo* glyphsBuffer = mModel->mVisualModel->mGlyphs.Begin();
692 mModel->mVisualModel->mGlyphs.Erase( glyphsBuffer + mTextUpdateInfo.mStartGlyphIndex,
693 glyphsBuffer + endGlyphIndexPlusOne );
695 CharacterIndex* glyphsToCharactersBuffer = mModel->mVisualModel->mGlyphsToCharacters.Begin();
697 // Update the glyph to character indices.
698 for( Vector<CharacterIndex>::Iterator it = glyphsToCharactersBuffer + endGlyphIndexPlusOne,
699 endIt = glyphsToCharactersBuffer + mModel->mVisualModel->mGlyphsToCharacters.Count();
703 CharacterIndex& index = *it;
704 index -= numberOfCharactersRemoved;
707 // Clear the glyphs to characters buffer.
708 mModel->mVisualModel->mGlyphsToCharacters.Erase( glyphsToCharactersBuffer + mTextUpdateInfo.mStartGlyphIndex,
709 glyphsToCharactersBuffer + endGlyphIndexPlusOne );
711 // Clear the characters per glyph buffer.
712 Length* charactersPerGlyphBuffer = mModel->mVisualModel->mCharactersPerGlyph.Begin();
713 mModel->mVisualModel->mCharactersPerGlyph.Erase( charactersPerGlyphBuffer + mTextUpdateInfo.mStartGlyphIndex,
714 charactersPerGlyphBuffer + endGlyphIndexPlusOne );
716 // Clear the positions buffer.
717 Vector2* positionsBuffer = mModel->mVisualModel->mGlyphPositions.Begin();
718 mModel->mVisualModel->mGlyphPositions.Erase( positionsBuffer + mTextUpdateInfo.mStartGlyphIndex,
719 positionsBuffer + endGlyphIndexPlusOne );
722 if( NO_OPERATION != ( LAYOUT & operations ) )
725 uint32_t startRemoveIndex = mModel->mVisualModel->mLines.Count();
726 uint32_t endRemoveIndex = startRemoveIndex;
727 ClearCharacterRuns( startIndex,
729 mModel->mVisualModel->mLines,
733 // Will update the glyph runs.
734 startRemoveIndex = mModel->mVisualModel->mLines.Count();
735 endRemoveIndex = startRemoveIndex;
736 ClearGlyphRuns( mTextUpdateInfo.mStartGlyphIndex,
737 endGlyphIndexPlusOne - 1u,
738 mModel->mVisualModel->mLines,
742 // Set the line index from where to insert the new laid-out lines.
743 mTextUpdateInfo.mStartLineIndex = startRemoveIndex;
745 LineRun* linesBuffer = mModel->mVisualModel->mLines.Begin();
746 mModel->mVisualModel->mLines.Erase( linesBuffer + startRemoveIndex,
747 linesBuffer + endRemoveIndex );
750 if( NO_OPERATION != ( COLOR & operations ) )
752 if( 0u != mModel->mVisualModel->mColorIndices.Count() )
754 ColorIndex* colorIndexBuffer = mModel->mVisualModel->mColorIndices.Begin();
755 mModel->mVisualModel->mColorIndices.Erase( colorIndexBuffer + mTextUpdateInfo.mStartGlyphIndex,
756 colorIndexBuffer + endGlyphIndexPlusOne );
761 void Controller::Impl::ClearModelData( CharacterIndex startIndex, CharacterIndex endIndex, OperationsMask operations )
763 if( mTextUpdateInfo.mClearAll ||
764 ( ( 0u == startIndex ) &&
765 ( mTextUpdateInfo.mPreviousNumberOfCharacters == endIndex + 1u ) ) )
767 ClearFullModelData( operations );
771 // Clear the model data related with characters.
772 ClearCharacterModelData( startIndex, endIndex, operations );
774 // Clear the model data related with glyphs.
775 ClearGlyphModelData( startIndex, endIndex, operations );
778 // The estimated number of lines. Used to avoid reallocations when layouting.
779 mTextUpdateInfo.mEstimatedNumberOfLines = std::max( mModel->mVisualModel->mLines.Count(), mModel->mLogicalModel->mParagraphInfo.Count() );
781 mModel->mVisualModel->ClearCaches();
784 bool Controller::Impl::UpdateModel( OperationsMask operationsRequired )
786 DALI_LOG_INFO( gLogFilter, Debug::General, "Controller::UpdateModel\n" );
788 // Calculate the operations to be done.
789 const OperationsMask operations = static_cast<OperationsMask>( mOperationsPending & operationsRequired );
791 if( NO_OPERATION == operations )
793 // Nothing to do if no operations are pending and required.
797 Vector<Character>& srcCharacters = mModel->mLogicalModel->mText;
798 Vector<Character> displayCharacters;
799 bool useHiddenText = false;
800 if ( mHiddenInput && mEventData != NULL && !mEventData->mIsShowingPlaceholderText)
802 mHiddenInput->Substitute( srcCharacters,displayCharacters );
803 useHiddenText = true;
806 Vector<Character>& utf32Characters = useHiddenText ? displayCharacters : srcCharacters;
807 const Length numberOfCharacters = utf32Characters.Count();
809 // Index to the first character of the first paragraph to be updated.
810 CharacterIndex startIndex = 0u;
811 // Number of characters of the paragraphs to be removed.
812 Length paragraphCharacters = 0u;
814 CalculateTextUpdateIndices( paragraphCharacters );
816 // Check whether the indices for updating the text is valid
817 if ( numberOfCharacters > 0u &&
818 ( mTextUpdateInfo.mParagraphCharacterIndex > numberOfCharacters ||
819 mTextUpdateInfo.mRequestedNumberOfCharacters > numberOfCharacters ) )
821 std::string currentText;
822 Utf32ToUtf8( mModel->mLogicalModel->mText.Begin(), numberOfCharacters, currentText );
824 DALI_LOG_ERROR( "Controller::Impl::UpdateModel: mTextUpdateInfo has invalid indices\n" );
825 DALI_LOG_ERROR( "Number of characters: %d, current text is: %s\n", numberOfCharacters, currentText.c_str() );
827 // Dump mTextUpdateInfo
828 DALI_LOG_ERROR( "Dump mTextUpdateInfo:\n" );
829 DALI_LOG_ERROR( " mTextUpdateInfo.mCharacterIndex = %u\n", mTextUpdateInfo.mCharacterIndex );
830 DALI_LOG_ERROR( " mTextUpdateInfo.mNumberOfCharactersToRemove = %u\n", mTextUpdateInfo.mNumberOfCharactersToRemove );
831 DALI_LOG_ERROR( " mTextUpdateInfo.mNumberOfCharactersToAdd = %u\n", mTextUpdateInfo.mNumberOfCharactersToAdd );
832 DALI_LOG_ERROR( " mTextUpdateInfo.mPreviousNumberOfCharacters = %u\n", mTextUpdateInfo.mPreviousNumberOfCharacters );
833 DALI_LOG_ERROR( " mTextUpdateInfo.mParagraphCharacterIndex = %u\n", mTextUpdateInfo.mParagraphCharacterIndex );
834 DALI_LOG_ERROR( " mTextUpdateInfo.mRequestedNumberOfCharacters = %u\n", mTextUpdateInfo.mRequestedNumberOfCharacters );
835 DALI_LOG_ERROR( " mTextUpdateInfo.mStartGlyphIndex = %u\n", mTextUpdateInfo.mStartGlyphIndex );
836 DALI_LOG_ERROR( " mTextUpdateInfo.mStartLineIndex = %u\n", mTextUpdateInfo.mStartLineIndex );
837 DALI_LOG_ERROR( " mTextUpdateInfo.mEstimatedNumberOfLines = %u\n", mTextUpdateInfo.mEstimatedNumberOfLines );
838 DALI_LOG_ERROR( " mTextUpdateInfo.mClearAll = %d\n", mTextUpdateInfo.mClearAll );
839 DALI_LOG_ERROR( " mTextUpdateInfo.mFullRelayoutNeeded = %d\n", mTextUpdateInfo.mFullRelayoutNeeded );
840 DALI_LOG_ERROR( " mTextUpdateInfo.mIsLastCharacterNewParagraph = %d\n", mTextUpdateInfo.mIsLastCharacterNewParagraph );
845 startIndex = mTextUpdateInfo.mParagraphCharacterIndex;
847 if( mTextUpdateInfo.mClearAll ||
848 ( 0u != paragraphCharacters ) )
850 ClearModelData( startIndex, startIndex + ( ( paragraphCharacters > 0u ) ? paragraphCharacters - 1u : 0u ), operations );
853 mTextUpdateInfo.mClearAll = false;
855 // Whether the model is updated.
856 bool updated = false;
858 Vector<LineBreakInfo>& lineBreakInfo = mModel->mLogicalModel->mLineBreakInfo;
859 const Length requestedNumberOfCharacters = mTextUpdateInfo.mRequestedNumberOfCharacters;
861 if( NO_OPERATION != ( GET_LINE_BREAKS & operations ) )
863 // Retrieves the line break info. The line break info is used to split the text in 'paragraphs' to
864 // calculate the bidirectional info for each 'paragraph'.
865 // It's also used to layout the text (where it should be a new line) or to shape the text (text in different lines
866 // is not shaped together).
867 lineBreakInfo.Resize( numberOfCharacters, TextAbstraction::LINE_NO_BREAK );
869 SetLineBreakInfo( utf32Characters,
871 requestedNumberOfCharacters,
874 // Create the paragraph info.
875 mModel->mLogicalModel->CreateParagraphInfo( startIndex,
876 requestedNumberOfCharacters );
880 Vector<WordBreakInfo>& wordBreakInfo = mModel->mLogicalModel->mWordBreakInfo;
881 if( NO_OPERATION != ( GET_WORD_BREAKS & operations ) )
883 // Retrieves the word break info. The word break info is used to layout the text (where to wrap the text in lines).
884 wordBreakInfo.Resize( numberOfCharacters, TextAbstraction::WORD_NO_BREAK );
886 SetWordBreakInfo( utf32Characters,
888 requestedNumberOfCharacters,
893 const bool getScripts = NO_OPERATION != ( GET_SCRIPTS & operations );
894 const bool validateFonts = NO_OPERATION != ( VALIDATE_FONTS & operations );
896 Vector<ScriptRun>& scripts = mModel->mLogicalModel->mScriptRuns;
897 Vector<FontRun>& validFonts = mModel->mLogicalModel->mFontRuns;
899 if( getScripts || validateFonts )
901 // Validates the fonts assigned by the application or assigns default ones.
902 // It makes sure all the characters are going to be rendered by the correct font.
903 MultilanguageSupport multilanguageSupport = MultilanguageSupport::Get();
907 // Retrieves the scripts used in the text.
908 multilanguageSupport.SetScripts( utf32Characters,
910 requestedNumberOfCharacters,
916 // Validate the fonts set through the mark-up string.
917 Vector<FontDescriptionRun>& fontDescriptionRuns = mModel->mLogicalModel->mFontDescriptionRuns;
919 // Get the default font's description.
920 TextAbstraction::FontDescription defaultFontDescription;
921 TextAbstraction::PointSize26Dot6 defaultPointSize = TextAbstraction::FontClient::DEFAULT_POINT_SIZE;
923 if( IsShowingPlaceholderText() && mEventData && ( NULL != mEventData->mPlaceholderFont ) )
925 // If the placeholder font is set specifically, only placeholder font is changed.
926 defaultFontDescription = mEventData->mPlaceholderFont->mFontDescription;
927 if( mEventData->mPlaceholderFont->sizeDefined )
929 defaultPointSize = mEventData->mPlaceholderFont->mDefaultPointSize * 64u;
932 else if( NULL != mFontDefaults )
934 // Set the normal font and the placeholder font.
935 defaultFontDescription = mFontDefaults->mFontDescription;
937 if( mTextFitEnabled )
939 defaultPointSize = mFontDefaults->mFitPointSize * 64u;
943 defaultPointSize = mFontDefaults->mDefaultPointSize * 64u;
947 // Validates the fonts. If there is a character with no assigned font it sets a default one.
948 // After this call, fonts are validated.
949 multilanguageSupport.ValidateFonts( utf32Characters,
952 defaultFontDescription,
955 requestedNumberOfCharacters,
961 Vector<Character> mirroredUtf32Characters;
962 bool textMirrored = false;
963 const Length numberOfParagraphs = mModel->mLogicalModel->mParagraphInfo.Count();
964 if( NO_OPERATION != ( BIDI_INFO & operations ) )
966 Vector<BidirectionalParagraphInfoRun>& bidirectionalInfo = mModel->mLogicalModel->mBidirectionalParagraphInfo;
967 bidirectionalInfo.Reserve( numberOfParagraphs );
969 // Calculates the bidirectional info for the whole paragraph if it contains right to left scripts.
970 SetBidirectionalInfo( utf32Characters,
974 requestedNumberOfCharacters,
976 mModel->mMatchSystemLanguageDirection,
979 if( 0u != bidirectionalInfo.Count() )
981 // Only set the character directions if there is right to left characters.
982 Vector<CharacterDirection>& directions = mModel->mLogicalModel->mCharacterDirections;
983 GetCharactersDirection( bidirectionalInfo,
986 requestedNumberOfCharacters,
989 // This paragraph has right to left text. Some characters may need to be mirrored.
990 // TODO: consider if the mirrored string can be stored as well.
992 textMirrored = GetMirroredText( utf32Characters,
996 requestedNumberOfCharacters,
997 mirroredUtf32Characters );
1001 // There is no right to left characters. Clear the directions vector.
1002 mModel->mLogicalModel->mCharacterDirections.Clear();
1007 Vector<GlyphInfo>& glyphs = mModel->mVisualModel->mGlyphs;
1008 Vector<CharacterIndex>& glyphsToCharactersMap = mModel->mVisualModel->mGlyphsToCharacters;
1009 Vector<Length>& charactersPerGlyph = mModel->mVisualModel->mCharactersPerGlyph;
1010 Vector<GlyphIndex> newParagraphGlyphs;
1011 newParagraphGlyphs.Reserve( numberOfParagraphs );
1013 const Length currentNumberOfGlyphs = glyphs.Count();
1014 if( NO_OPERATION != ( SHAPE_TEXT & operations ) )
1016 const Vector<Character>& textToShape = textMirrored ? mirroredUtf32Characters : utf32Characters;
1018 ShapeText( textToShape,
1023 mTextUpdateInfo.mStartGlyphIndex,
1024 requestedNumberOfCharacters,
1026 glyphsToCharactersMap,
1028 newParagraphGlyphs );
1030 // Create the 'number of glyphs' per character and the glyph to character conversion tables.
1031 mModel->mVisualModel->CreateGlyphsPerCharacterTable( startIndex, mTextUpdateInfo.mStartGlyphIndex, requestedNumberOfCharacters );
1032 mModel->mVisualModel->CreateCharacterToGlyphTable( startIndex, mTextUpdateInfo.mStartGlyphIndex, requestedNumberOfCharacters );
1036 const Length numberOfGlyphs = glyphs.Count() - currentNumberOfGlyphs;
1038 if( NO_OPERATION != ( GET_GLYPH_METRICS & operations ) )
1040 GlyphInfo* glyphsBuffer = glyphs.Begin();
1041 mMetrics->GetGlyphMetrics( glyphsBuffer + mTextUpdateInfo.mStartGlyphIndex, numberOfGlyphs );
1043 // Update the width and advance of all new paragraph characters.
1044 for( Vector<GlyphIndex>::ConstIterator it = newParagraphGlyphs.Begin(), endIt = newParagraphGlyphs.End(); it != endIt; ++it )
1046 const GlyphIndex index = *it;
1047 GlyphInfo& glyph = *( glyphsBuffer + index );
1049 glyph.xBearing = 0.f;
1051 glyph.advance = 0.f;
1056 if( NO_OPERATION != ( COLOR & operations ) )
1058 // Set the color runs in glyphs.
1059 SetColorSegmentationInfo( mModel->mLogicalModel->mColorRuns,
1060 mModel->mVisualModel->mCharactersToGlyph,
1061 mModel->mVisualModel->mGlyphsPerCharacter,
1063 mTextUpdateInfo.mStartGlyphIndex,
1064 requestedNumberOfCharacters,
1065 mModel->mVisualModel->mColors,
1066 mModel->mVisualModel->mColorIndices );
1071 if( ( NULL != mEventData ) &&
1072 mEventData->mPreEditFlag &&
1073 ( 0u != mModel->mVisualModel->mCharactersToGlyph.Count() ) )
1075 Dali::InputMethodContext::PreeditStyle type = mEventData->mInputMethodContext.GetPreeditStyle();
1079 case Dali::InputMethodContext::PreeditStyle::UNDERLINE:
1081 // Add the underline for the pre-edit text.
1082 const GlyphIndex* const charactersToGlyphBuffer = mModel->mVisualModel->mCharactersToGlyph.Begin();
1083 const Length* const glyphsPerCharacterBuffer = mModel->mVisualModel->mGlyphsPerCharacter.Begin();
1085 const GlyphIndex glyphStart = *( charactersToGlyphBuffer + mEventData->mPreEditStartPosition );
1086 const CharacterIndex lastPreEditCharacter = mEventData->mPreEditStartPosition + ( ( mEventData->mPreEditLength > 0u ) ? mEventData->mPreEditLength - 1u : 0u );
1087 const Length numberOfGlyphsLastCharacter = *( glyphsPerCharacterBuffer + lastPreEditCharacter );
1088 const GlyphIndex glyphEnd = *( charactersToGlyphBuffer + lastPreEditCharacter ) + ( numberOfGlyphsLastCharacter > 1u ? numberOfGlyphsLastCharacter - 1u : 0u );
1090 GlyphRun underlineRun;
1091 underlineRun.glyphIndex = glyphStart;
1092 underlineRun.numberOfGlyphs = 1u + glyphEnd - glyphStart;
1094 mModel->mVisualModel->mUnderlineRuns.PushBack( underlineRun );
1097 // TODO : At this moment, other styles for preedit are not implemented yet.
1098 case Dali::InputMethodContext::PreeditStyle::REVERSE:
1099 case Dali::InputMethodContext::PreeditStyle::HIGHLIGHT:
1100 case Dali::InputMethodContext::PreeditStyle::NONE:
1106 // The estimated number of lines. Used to avoid reallocations when layouting.
1107 mTextUpdateInfo.mEstimatedNumberOfLines = std::max( mModel->mVisualModel->mLines.Count(), mModel->mLogicalModel->mParagraphInfo.Count() );
1109 // Set the previous number of characters for the next time the text is updated.
1110 mTextUpdateInfo.mPreviousNumberOfCharacters = numberOfCharacters;
1115 void Controller::Impl::RetrieveDefaultInputStyle( InputStyle& inputStyle )
1117 // Sets the default text's color.
1118 inputStyle.textColor = mTextColor;
1119 inputStyle.isDefaultColor = true;
1121 inputStyle.familyName.clear();
1122 inputStyle.weight = TextAbstraction::FontWeight::NORMAL;
1123 inputStyle.width = TextAbstraction::FontWidth::NORMAL;
1124 inputStyle.slant = TextAbstraction::FontSlant::NORMAL;
1125 inputStyle.size = 0.f;
1127 inputStyle.lineSpacing = 0.f;
1129 inputStyle.underlineProperties.clear();
1130 inputStyle.shadowProperties.clear();
1131 inputStyle.embossProperties.clear();
1132 inputStyle.outlineProperties.clear();
1134 inputStyle.isFamilyDefined = false;
1135 inputStyle.isWeightDefined = false;
1136 inputStyle.isWidthDefined = false;
1137 inputStyle.isSlantDefined = false;
1138 inputStyle.isSizeDefined = false;
1140 inputStyle.isLineSpacingDefined = false;
1142 inputStyle.isUnderlineDefined = false;
1143 inputStyle.isShadowDefined = false;
1144 inputStyle.isEmbossDefined = false;
1145 inputStyle.isOutlineDefined = false;
1147 // Sets the default font's family name, weight, width, slant and size.
1150 if( mFontDefaults->familyDefined )
1152 inputStyle.familyName = mFontDefaults->mFontDescription.family;
1153 inputStyle.isFamilyDefined = true;
1156 if( mFontDefaults->weightDefined )
1158 inputStyle.weight = mFontDefaults->mFontDescription.weight;
1159 inputStyle.isWeightDefined = true;
1162 if( mFontDefaults->widthDefined )
1164 inputStyle.width = mFontDefaults->mFontDescription.width;
1165 inputStyle.isWidthDefined = true;
1168 if( mFontDefaults->slantDefined )
1170 inputStyle.slant = mFontDefaults->mFontDescription.slant;
1171 inputStyle.isSlantDefined = true;
1174 if( mFontDefaults->sizeDefined )
1176 inputStyle.size = mFontDefaults->mDefaultPointSize;
1177 inputStyle.isSizeDefined = true;
1182 float Controller::Impl::GetDefaultFontLineHeight()
1184 FontId defaultFontId = 0u;
1185 if( NULL == mFontDefaults )
1187 TextAbstraction::FontDescription fontDescription;
1188 defaultFontId = mFontClient.GetFontId( fontDescription );
1192 defaultFontId = mFontDefaults->GetFontId( mFontClient );
1195 Text::FontMetrics fontMetrics;
1196 mMetrics->GetFontMetrics( defaultFontId, fontMetrics );
1198 return( fontMetrics.ascender - fontMetrics.descender );
1201 void Controller::Impl::OnCursorKeyEvent( const Event& event )
1203 if( NULL == mEventData || !IsShowingRealText() )
1205 // Nothing to do if there is no text input.
1209 int keyCode = event.p1.mInt;
1210 bool isShiftModifier = event.p2.mBool;
1212 CharacterIndex previousPrimaryCursorPosition = mEventData->mPrimaryCursorPosition;
1214 if( Dali::DALI_KEY_CURSOR_LEFT == keyCode )
1216 if( mEventData->mPrimaryCursorPosition > 0u )
1218 if ( !isShiftModifier && mEventData->mDecorator->IsHighlightVisible() )
1220 mEventData->mPrimaryCursorPosition = std::min(mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition);
1224 mEventData->mPrimaryCursorPosition = CalculateNewCursorIndex( mEventData->mPrimaryCursorPosition - 1u );
1228 else if( Dali::DALI_KEY_CURSOR_RIGHT == keyCode )
1230 if( mModel->mLogicalModel->mText.Count() > mEventData->mPrimaryCursorPosition )
1232 if ( !isShiftModifier && mEventData->mDecorator->IsHighlightVisible() )
1234 mEventData->mPrimaryCursorPosition = std::max(mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition);
1238 mEventData->mPrimaryCursorPosition = CalculateNewCursorIndex( mEventData->mPrimaryCursorPosition );
1242 else if( Dali::DALI_KEY_CURSOR_UP == keyCode && !isShiftModifier )
1244 // Ignore Shift-Up for text selection for now.
1246 // Get first the line index of the current cursor position index.
1247 CharacterIndex characterIndex = 0u;
1249 if( mEventData->mPrimaryCursorPosition > 0u )
1251 characterIndex = mEventData->mPrimaryCursorPosition - 1u;
1254 const LineIndex lineIndex = mModel->mVisualModel->GetLineOfCharacter( characterIndex );
1255 const LineIndex previousLineIndex = ( lineIndex > 0 ? lineIndex - 1u : lineIndex );
1257 // Retrieve the cursor position info.
1258 CursorInfo cursorInfo;
1259 GetCursorPosition( mEventData->mPrimaryCursorPosition,
1262 // Get the line above.
1263 const LineRun& line = *( mModel->mVisualModel->mLines.Begin() + previousLineIndex );
1265 // Get the next hit 'y' point.
1266 const float hitPointY = cursorInfo.lineOffset - 0.5f * ( line.ascender - line.descender );
1268 // Use the cursor hook position 'x' and the next hit 'y' position to calculate the new cursor index.
1269 bool matchedCharacter = false;
1270 mEventData->mPrimaryCursorPosition = Text::GetClosestCursorIndex( mModel->mVisualModel,
1271 mModel->mLogicalModel,
1273 mEventData->mCursorHookPositionX,
1275 CharacterHitTest::TAP,
1278 else if( Dali::DALI_KEY_CURSOR_DOWN == keyCode && !isShiftModifier )
1280 // Ignore Shift-Down for text selection for now.
1282 // Get first the line index of the current cursor position index.
1283 CharacterIndex characterIndex = 0u;
1285 if( mEventData->mPrimaryCursorPosition > 0u )
1287 characterIndex = mEventData->mPrimaryCursorPosition - 1u;
1290 const LineIndex lineIndex = mModel->mVisualModel->GetLineOfCharacter( characterIndex );
1292 if( lineIndex + 1u < mModel->mVisualModel->mLines.Count() )
1294 // Retrieve the cursor position info.
1295 CursorInfo cursorInfo;
1296 GetCursorPosition( mEventData->mPrimaryCursorPosition,
1299 // Get the line below.
1300 const LineRun& line = *( mModel->mVisualModel->mLines.Begin() + lineIndex + 1u );
1302 // Get the next hit 'y' point.
1303 const float hitPointY = cursorInfo.lineOffset + cursorInfo.lineHeight + 0.5f * ( line.ascender - line.descender );
1305 // Use the cursor hook position 'x' and the next hit 'y' position to calculate the new cursor index.
1306 bool matchedCharacter = false;
1307 mEventData->mPrimaryCursorPosition = Text::GetClosestCursorIndex( mModel->mVisualModel,
1308 mModel->mLogicalModel,
1310 mEventData->mCursorHookPositionX,
1312 CharacterHitTest::TAP,
1317 if ( !isShiftModifier && mEventData->mState != EventData::SELECTING )
1319 // Update selection position after moving the cursor
1320 mEventData->mLeftSelectionPosition = mEventData->mPrimaryCursorPosition;
1321 mEventData->mRightSelectionPosition = mEventData->mPrimaryCursorPosition;
1324 if ( isShiftModifier && IsShowingRealText() && mEventData->mShiftSelectionFlag )
1326 // Handle text selection
1327 bool selecting = false;
1329 if ( Dali::DALI_KEY_CURSOR_LEFT == keyCode || Dali::DALI_KEY_CURSOR_RIGHT == keyCode )
1331 // Shift-Left/Right to select the text
1332 int cursorPositionDelta = mEventData->mPrimaryCursorPosition - previousPrimaryCursorPosition;
1333 if ( cursorPositionDelta > 0 || mEventData->mRightSelectionPosition > 0u ) // Check the boundary
1335 mEventData->mRightSelectionPosition += cursorPositionDelta;
1339 else if ( mEventData->mLeftSelectionPosition != mEventData->mRightSelectionPosition )
1341 // Show no grab handles and text highlight if Shift-Up/Down pressed but no selected text
1347 // Notify the cursor position to the InputMethodContext.
1348 if( mEventData->mInputMethodContext )
1350 mEventData->mInputMethodContext.SetCursorPosition( mEventData->mPrimaryCursorPosition );
1351 mEventData->mInputMethodContext.NotifyCursorPosition();
1354 ChangeState( EventData::SELECTING );
1356 mEventData->mUpdateLeftSelectionPosition = true;
1357 mEventData->mUpdateRightSelectionPosition = true;
1358 mEventData->mUpdateGrabHandlePosition = true;
1359 mEventData->mUpdateHighlightBox = true;
1361 // Hide the text selection popup if select the text using keyboard instead of moving grab handles
1362 if( mEventData->mGrabHandlePopupEnabled )
1364 mEventData->mDecorator->SetPopupActive( false );
1370 // Handle normal cursor move
1371 ChangeState( EventData::EDITING );
1372 mEventData->mUpdateCursorPosition = true;
1375 mEventData->mUpdateInputStyle = true;
1376 mEventData->mScrollAfterUpdatePosition = true;
1379 void Controller::Impl::OnTapEvent( const Event& event )
1381 if( NULL != mEventData )
1383 const unsigned int tapCount = event.p1.mUint;
1385 if( 1u == tapCount )
1387 if( IsShowingRealText() )
1389 // Convert from control's coords to text's coords.
1390 const float xPosition = event.p2.mFloat - mModel->mScrollPosition.x;
1391 const float yPosition = event.p3.mFloat - mModel->mScrollPosition.y;
1393 // Keep the tap 'x' position. Used to move the cursor.
1394 mEventData->mCursorHookPositionX = xPosition;
1396 // Whether to touch point hits on a glyph.
1397 bool matchedCharacter = false;
1398 mEventData->mPrimaryCursorPosition = Text::GetClosestCursorIndex( mModel->mVisualModel,
1399 mModel->mLogicalModel,
1403 CharacterHitTest::TAP,
1406 // When the cursor position is changing, delay cursor blinking
1407 mEventData->mDecorator->DelayCursorBlink();
1411 mEventData->mPrimaryCursorPosition = 0u;
1414 // Update selection position after tapping
1415 mEventData->mLeftSelectionPosition = mEventData->mPrimaryCursorPosition;
1416 mEventData->mRightSelectionPosition = mEventData->mPrimaryCursorPosition;
1418 mEventData->mUpdateCursorPosition = true;
1419 mEventData->mUpdateGrabHandlePosition = true;
1420 mEventData->mScrollAfterUpdatePosition = true;
1421 mEventData->mUpdateInputStyle = true;
1423 // Notify the cursor position to the InputMethodContext.
1424 if( mEventData->mInputMethodContext )
1426 mEventData->mInputMethodContext.SetCursorPosition( mEventData->mPrimaryCursorPosition );
1427 mEventData->mInputMethodContext.NotifyCursorPosition();
1430 else if( 2u == tapCount )
1432 if( mEventData->mSelectionEnabled )
1434 // Convert from control's coords to text's coords.
1435 const float xPosition = event.p2.mFloat - mModel->mScrollPosition.x;
1436 const float yPosition = event.p3.mFloat - mModel->mScrollPosition.y;
1438 // Calculates the logical position from the x,y coords.
1439 RepositionSelectionHandles( xPosition,
1441 mEventData->mDoubleTapAction );
1447 void Controller::Impl::OnPanEvent( const Event& event )
1449 if( NULL == mEventData )
1451 // Nothing to do if there is no text input.
1455 const bool isHorizontalScrollEnabled = mEventData->mDecorator->IsHorizontalScrollEnabled();
1456 const bool isVerticalScrollEnabled = mEventData->mDecorator->IsVerticalScrollEnabled();
1458 if( !isHorizontalScrollEnabled && !isVerticalScrollEnabled )
1460 // Nothing to do if scrolling is not enabled.
1464 const int state = event.p1.mInt;
1468 case Gesture::Started:
1470 // Will remove the cursor, handles or text's popup, ...
1471 ChangeState( EventData::TEXT_PANNING );
1474 case Gesture::Continuing:
1476 const Vector2& layoutSize = mModel->mVisualModel->GetLayoutSize();
1477 const Vector2 currentScroll = mModel->mScrollPosition;
1479 if( isHorizontalScrollEnabled )
1481 const float displacementX = event.p2.mFloat;
1482 mModel->mScrollPosition.x += displacementX;
1484 ClampHorizontalScroll( layoutSize );
1487 if( isVerticalScrollEnabled )
1489 const float displacementY = event.p3.mFloat;
1490 mModel->mScrollPosition.y += displacementY;
1492 ClampVerticalScroll( layoutSize );
1495 mEventData->mDecorator->UpdatePositions( mModel->mScrollPosition - currentScroll );
1498 case Gesture::Finished:
1499 case Gesture::Cancelled: // FALLTHROUGH
1501 // Will go back to the previous state to show the cursor, handles, the text's popup, ...
1502 ChangeState( mEventData->mPreviousState );
1510 void Controller::Impl::OnLongPressEvent( const Event& event )
1512 DALI_LOG_INFO( gLogFilter, Debug::General, "Controller::OnLongPressEvent\n" );
1514 if( !IsShowingRealText() && ( EventData::EDITING == mEventData->mState ) )
1516 ChangeState( EventData::EDITING_WITH_POPUP );
1517 mEventData->mDecoratorUpdated = true;
1518 mEventData->mUpdateInputStyle = true;
1522 if( mEventData->mSelectionEnabled )
1524 // Convert from control's coords to text's coords.
1525 const float xPosition = event.p2.mFloat - mModel->mScrollPosition.x;
1526 const float yPosition = event.p3.mFloat - mModel->mScrollPosition.y;
1528 // Calculates the logical position from the x,y coords.
1529 RepositionSelectionHandles( xPosition,
1531 mEventData->mLongPressAction );
1536 void Controller::Impl::OnHandleEvent( const Event& event )
1538 if( NULL == mEventData )
1540 // Nothing to do if there is no text input.
1544 const unsigned int state = event.p1.mUint;
1545 const bool handleStopScrolling = ( HANDLE_STOP_SCROLLING == state );
1546 const bool isSmoothHandlePanEnabled = mEventData->mDecorator->IsSmoothHandlePanEnabled();
1548 if( HANDLE_PRESSED == state )
1550 // Convert from decorator's coords to text's coords.
1551 const float xPosition = event.p2.mFloat - mModel->mScrollPosition.x;
1552 const float yPosition = event.p3.mFloat - mModel->mScrollPosition.y;
1554 // Need to calculate the handle's new position.
1555 bool matchedCharacter = false;
1556 const CharacterIndex handleNewPosition = Text::GetClosestCursorIndex( mModel->mVisualModel,
1557 mModel->mLogicalModel,
1561 CharacterHitTest::SCROLL,
1564 if( Event::GRAB_HANDLE_EVENT == event.type )
1566 ChangeState ( EventData::GRAB_HANDLE_PANNING );
1568 if( handleNewPosition != mEventData->mPrimaryCursorPosition )
1570 // Updates the cursor position if the handle's new position is different than the current one.
1571 mEventData->mUpdateCursorPosition = true;
1572 // Does not update the grab handle position if the smooth panning is enabled. (The decorator does it smooth).
1573 mEventData->mUpdateGrabHandlePosition = !isSmoothHandlePanEnabled;
1574 mEventData->mPrimaryCursorPosition = handleNewPosition;
1577 // Updates the decorator if the soft handle panning is enabled. It triggers a relayout in the decorator and the new position of the handle is set.
1578 mEventData->mDecoratorUpdated = isSmoothHandlePanEnabled;
1580 else if( Event::LEFT_SELECTION_HANDLE_EVENT == event.type )
1582 ChangeState ( EventData::SELECTION_HANDLE_PANNING );
1584 if( ( handleNewPosition != mEventData->mLeftSelectionPosition ) &&
1585 ( handleNewPosition != mEventData->mRightSelectionPosition ) )
1587 // Updates the highlight box if the handle's new position is different than the current one.
1588 mEventData->mUpdateHighlightBox = true;
1589 // Does not update the selection handle position if the smooth panning is enabled. (The decorator does it smooth).
1590 mEventData->mUpdateLeftSelectionPosition = !isSmoothHandlePanEnabled;
1591 mEventData->mLeftSelectionPosition = handleNewPosition;
1594 // Updates the decorator if the soft handle panning is enabled. It triggers a relayout in the decorator and the new position of the handle is set.
1595 mEventData->mDecoratorUpdated = isSmoothHandlePanEnabled;
1597 // Will define the order to scroll the text to match the handle position.
1598 mEventData->mIsLeftHandleSelected = true;
1599 mEventData->mIsRightHandleSelected = false;
1601 else if( Event::RIGHT_SELECTION_HANDLE_EVENT == event.type )
1603 ChangeState ( EventData::SELECTION_HANDLE_PANNING );
1605 if( ( handleNewPosition != mEventData->mRightSelectionPosition ) &&
1606 ( handleNewPosition != mEventData->mLeftSelectionPosition ) )
1608 // Updates the highlight box if the handle's new position is different than the current one.
1609 mEventData->mUpdateHighlightBox = true;
1610 // Does not update the selection handle position if the smooth panning is enabled. (The decorator does it smooth).
1611 mEventData->mUpdateRightSelectionPosition = !isSmoothHandlePanEnabled;
1612 mEventData->mRightSelectionPosition = handleNewPosition;
1615 // Updates the decorator if the soft handle panning is enabled. It triggers a relayout in the decorator and the new position of the handle is set.
1616 mEventData->mDecoratorUpdated = isSmoothHandlePanEnabled;
1618 // Will define the order to scroll the text to match the handle position.
1619 mEventData->mIsLeftHandleSelected = false;
1620 mEventData->mIsRightHandleSelected = true;
1622 } // end ( HANDLE_PRESSED == state )
1623 else if( ( HANDLE_RELEASED == state ) ||
1624 handleStopScrolling )
1626 CharacterIndex handlePosition = 0u;
1627 if( handleStopScrolling || isSmoothHandlePanEnabled )
1629 // Convert from decorator's coords to text's coords.
1630 const float xPosition = event.p2.mFloat - mModel->mScrollPosition.x;
1631 const float yPosition = event.p3.mFloat - mModel->mScrollPosition.y;
1633 bool matchedCharacter = false;
1634 handlePosition = Text::GetClosestCursorIndex( mModel->mVisualModel,
1635 mModel->mLogicalModel,
1639 CharacterHitTest::SCROLL,
1643 if( Event::GRAB_HANDLE_EVENT == event.type )
1645 mEventData->mUpdateCursorPosition = true;
1646 mEventData->mUpdateGrabHandlePosition = true;
1647 mEventData->mUpdateInputStyle = true;
1649 if( !IsClipboardEmpty() )
1651 ChangeState( EventData::EDITING_WITH_PASTE_POPUP ); // Moving grabhandle will show Paste Popup
1654 if( handleStopScrolling || isSmoothHandlePanEnabled )
1656 mEventData->mScrollAfterUpdatePosition = true;
1657 mEventData->mPrimaryCursorPosition = handlePosition;
1660 else if( Event::LEFT_SELECTION_HANDLE_EVENT == event.type )
1662 ChangeState( EventData::SELECTING );
1664 mEventData->mUpdateHighlightBox = true;
1665 mEventData->mUpdateLeftSelectionPosition = true;
1666 mEventData->mUpdateRightSelectionPosition = true;
1668 if( handleStopScrolling || isSmoothHandlePanEnabled )
1670 mEventData->mScrollAfterUpdatePosition = true;
1672 if( ( handlePosition != mEventData->mRightSelectionPosition ) &&
1673 ( handlePosition != mEventData->mLeftSelectionPosition ) )
1675 mEventData->mLeftSelectionPosition = handlePosition;
1679 else if( Event::RIGHT_SELECTION_HANDLE_EVENT == event.type )
1681 ChangeState( EventData::SELECTING );
1683 mEventData->mUpdateHighlightBox = true;
1684 mEventData->mUpdateRightSelectionPosition = true;
1685 mEventData->mUpdateLeftSelectionPosition = true;
1687 if( handleStopScrolling || isSmoothHandlePanEnabled )
1689 mEventData->mScrollAfterUpdatePosition = true;
1690 if( ( handlePosition != mEventData->mRightSelectionPosition ) &&
1691 ( handlePosition != mEventData->mLeftSelectionPosition ) )
1693 mEventData->mRightSelectionPosition = handlePosition;
1698 mEventData->mDecoratorUpdated = true;
1699 } // end ( ( HANDLE_RELEASED == state ) || ( HANDLE_STOP_SCROLLING == state ) )
1700 else if( HANDLE_SCROLLING == state )
1702 const float xSpeed = event.p2.mFloat;
1703 const float ySpeed = event.p3.mFloat;
1704 const Vector2& layoutSize = mModel->mVisualModel->GetLayoutSize();
1705 const Vector2 currentScrollPosition = mModel->mScrollPosition;
1707 mModel->mScrollPosition.x += xSpeed;
1708 mModel->mScrollPosition.y += ySpeed;
1710 ClampHorizontalScroll( layoutSize );
1711 ClampVerticalScroll( layoutSize );
1713 bool endOfScroll = false;
1714 if( Vector2::ZERO == ( currentScrollPosition - mModel->mScrollPosition ) )
1716 // Notify the decorator there is no more text to scroll.
1717 // The decorator won't send more scroll events.
1718 mEventData->mDecorator->NotifyEndOfScroll();
1719 // Still need to set the position of the handle.
1723 // Set the position of the handle.
1724 const bool scrollRightDirection = xSpeed > 0.f;
1725 const bool scrollBottomDirection = ySpeed > 0.f;
1726 const bool leftSelectionHandleEvent = Event::LEFT_SELECTION_HANDLE_EVENT == event.type;
1727 const bool rightSelectionHandleEvent = Event::RIGHT_SELECTION_HANDLE_EVENT == event.type;
1729 if( Event::GRAB_HANDLE_EVENT == event.type )
1731 ChangeState( EventData::GRAB_HANDLE_PANNING );
1733 // Get the grab handle position in decorator coords.
1734 Vector2 position = mEventData->mDecorator->GetPosition( GRAB_HANDLE );
1736 if( mEventData->mDecorator->IsHorizontalScrollEnabled() )
1738 // Position the grag handle close to either the left or right edge.
1739 position.x = scrollRightDirection ? 0.f : mModel->mVisualModel->mControlSize.width;
1742 if( mEventData->mDecorator->IsVerticalScrollEnabled() )
1744 position.x = mEventData->mCursorHookPositionX;
1746 // Position the grag handle close to either the top or bottom edge.
1747 position.y = scrollBottomDirection ? 0.f : mModel->mVisualModel->mControlSize.height;
1750 // Get the new handle position.
1751 // The grab handle's position is in decorator's coords. Need to transforms to text's coords.
1752 bool matchedCharacter = false;
1753 const CharacterIndex handlePosition = Text::GetClosestCursorIndex( mModel->mVisualModel,
1754 mModel->mLogicalModel,
1756 position.x - mModel->mScrollPosition.x,
1757 position.y - mModel->mScrollPosition.y,
1758 CharacterHitTest::SCROLL,
1761 if( mEventData->mPrimaryCursorPosition != handlePosition )
1763 mEventData->mUpdateCursorPosition = true;
1764 mEventData->mUpdateGrabHandlePosition = !isSmoothHandlePanEnabled;
1765 mEventData->mScrollAfterUpdatePosition = true;
1766 mEventData->mPrimaryCursorPosition = handlePosition;
1768 mEventData->mUpdateInputStyle = mEventData->mUpdateCursorPosition;
1770 // Updates the decorator if the soft handle panning is enabled.
1771 mEventData->mDecoratorUpdated = isSmoothHandlePanEnabled;
1773 else if( leftSelectionHandleEvent || rightSelectionHandleEvent )
1775 ChangeState( EventData::SELECTION_HANDLE_PANNING );
1777 // Get the selection handle position in decorator coords.
1778 Vector2 position = mEventData->mDecorator->GetPosition( leftSelectionHandleEvent ? Text::LEFT_SELECTION_HANDLE : Text::RIGHT_SELECTION_HANDLE );
1780 if( mEventData->mDecorator->IsHorizontalScrollEnabled() )
1782 // Position the selection handle close to either the left or right edge.
1783 position.x = scrollRightDirection ? 0.f : mModel->mVisualModel->mControlSize.width;
1786 if( mEventData->mDecorator->IsVerticalScrollEnabled() )
1788 position.x = mEventData->mCursorHookPositionX;
1790 // Position the grag handle close to either the top or bottom edge.
1791 position.y = scrollBottomDirection ? 0.f : mModel->mVisualModel->mControlSize.height;
1794 // Get the new handle position.
1795 // The selection handle's position is in decorator's coords. Need to transform to text's coords.
1796 bool matchedCharacter = false;
1797 const CharacterIndex handlePosition = Text::GetClosestCursorIndex( mModel->mVisualModel,
1798 mModel->mLogicalModel,
1800 position.x - mModel->mScrollPosition.x,
1801 position.y - mModel->mScrollPosition.y,
1802 CharacterHitTest::SCROLL,
1805 if( leftSelectionHandleEvent )
1807 const bool differentHandles = ( mEventData->mLeftSelectionPosition != handlePosition ) && ( mEventData->mRightSelectionPosition != handlePosition );
1809 if( differentHandles || endOfScroll )
1811 mEventData->mUpdateHighlightBox = true;
1812 mEventData->mUpdateLeftSelectionPosition = !isSmoothHandlePanEnabled;
1813 mEventData->mUpdateRightSelectionPosition = isSmoothHandlePanEnabled;
1814 mEventData->mLeftSelectionPosition = handlePosition;
1819 const bool differentHandles = ( mEventData->mRightSelectionPosition != handlePosition ) && ( mEventData->mLeftSelectionPosition != handlePosition );
1820 if( differentHandles || endOfScroll )
1822 mEventData->mUpdateHighlightBox = true;
1823 mEventData->mUpdateRightSelectionPosition = !isSmoothHandlePanEnabled;
1824 mEventData->mUpdateLeftSelectionPosition = isSmoothHandlePanEnabled;
1825 mEventData->mRightSelectionPosition = handlePosition;
1829 if( mEventData->mUpdateLeftSelectionPosition || mEventData->mUpdateRightSelectionPosition )
1831 RepositionSelectionHandles();
1833 mEventData->mScrollAfterUpdatePosition = !isSmoothHandlePanEnabled;
1836 mEventData->mDecoratorUpdated = true;
1837 } // end ( HANDLE_SCROLLING == state )
1840 void Controller::Impl::OnSelectEvent( const Event& event )
1842 if( NULL == mEventData )
1844 // Nothing to do if there is no text.
1848 if( mEventData->mSelectionEnabled )
1850 // Convert from control's coords to text's coords.
1851 const float xPosition = event.p2.mFloat - mModel->mScrollPosition.x;
1852 const float yPosition = event.p3.mFloat - mModel->mScrollPosition.y;
1854 // Calculates the logical position from the x,y coords.
1855 RepositionSelectionHandles( xPosition,
1857 Controller::NoTextTap::HIGHLIGHT );
1861 void Controller::Impl::OnSelectAllEvent()
1863 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "OnSelectAllEvent mEventData->mSelectionEnabled%s \n", mEventData->mSelectionEnabled?"true":"false");
1865 if( NULL == mEventData )
1867 // Nothing to do if there is no text.
1871 if( mEventData->mSelectionEnabled )
1873 // Calculates the logical position from the start.
1874 RepositionSelectionHandles( 0.f - mModel->mScrollPosition.x,
1875 0.f - mModel->mScrollPosition.y,
1876 Controller::NoTextTap::HIGHLIGHT );
1878 mEventData->mLeftSelectionPosition = 0u;
1879 mEventData->mRightSelectionPosition = mModel->mLogicalModel->mText.Count();
1883 void Controller::Impl::RetrieveSelection( std::string& selectedText, bool deleteAfterRetrieval )
1885 if( mEventData->mLeftSelectionPosition == mEventData->mRightSelectionPosition )
1887 // Nothing to select if handles are in the same place.
1888 selectedText.clear();
1892 const bool handlesCrossed = mEventData->mLeftSelectionPosition > mEventData->mRightSelectionPosition;
1894 //Get start and end position of selection
1895 const CharacterIndex startOfSelectedText = handlesCrossed ? mEventData->mRightSelectionPosition : mEventData->mLeftSelectionPosition;
1896 const Length lengthOfSelectedText = ( handlesCrossed ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition ) - startOfSelectedText;
1898 Vector<Character>& utf32Characters = mModel->mLogicalModel->mText;
1899 const Length numberOfCharacters = utf32Characters.Count();
1901 // Validate the start and end selection points
1902 if( ( startOfSelectedText + lengthOfSelectedText ) <= numberOfCharacters )
1904 //Get text as a UTF8 string
1905 Utf32ToUtf8( &utf32Characters[startOfSelectedText], lengthOfSelectedText, selectedText );
1907 if( deleteAfterRetrieval ) // Only delete text if copied successfully
1909 // Keep a copy of the current input style.
1910 InputStyle currentInputStyle;
1911 currentInputStyle.Copy( mEventData->mInputStyle );
1913 // Set as input style the style of the first deleted character.
1914 mModel->mLogicalModel->RetrieveStyle( startOfSelectedText, mEventData->mInputStyle );
1916 // Compare if the input style has changed.
1917 const bool hasInputStyleChanged = !currentInputStyle.Equal( mEventData->mInputStyle );
1919 if( hasInputStyleChanged )
1921 const InputStyle::Mask styleChangedMask = currentInputStyle.GetInputStyleChangeMask( mEventData->mInputStyle );
1922 // Queue the input style changed signal.
1923 mEventData->mInputStyleChangedQueue.PushBack( styleChangedMask );
1926 mModel->mLogicalModel->UpdateTextStyleRuns( startOfSelectedText, -static_cast<int>( lengthOfSelectedText ) );
1928 // Mark the paragraphs to be updated.
1929 if( Layout::Engine::SINGLE_LINE_BOX == mLayoutEngine.GetLayout() )
1931 mTextUpdateInfo.mCharacterIndex = 0;
1932 mTextUpdateInfo.mNumberOfCharactersToRemove = mTextUpdateInfo.mPreviousNumberOfCharacters;
1933 mTextUpdateInfo.mNumberOfCharactersToAdd = mTextUpdateInfo.mPreviousNumberOfCharacters - lengthOfSelectedText;
1934 mTextUpdateInfo.mClearAll = true;
1938 mTextUpdateInfo.mCharacterIndex = startOfSelectedText;
1939 mTextUpdateInfo.mNumberOfCharactersToRemove = lengthOfSelectedText;
1942 // Delete text between handles
1943 Vector<Character>::Iterator first = utf32Characters.Begin() + startOfSelectedText;
1944 Vector<Character>::Iterator last = first + lengthOfSelectedText;
1945 utf32Characters.Erase( first, last );
1947 // Will show the cursor at the first character of the selection.
1948 mEventData->mPrimaryCursorPosition = handlesCrossed ? mEventData->mRightSelectionPosition : mEventData->mLeftSelectionPosition;
1952 // Will show the cursor at the last character of the selection.
1953 mEventData->mPrimaryCursorPosition = handlesCrossed ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition;
1956 mEventData->mDecoratorUpdated = true;
1960 void Controller::Impl::ShowClipboard()
1964 mClipboard.ShowClipboard();
1968 void Controller::Impl::HideClipboard()
1970 if( mClipboard && mClipboardHideEnabled )
1972 mClipboard.HideClipboard();
1976 void Controller::Impl::SetClipboardHideEnable(bool enable)
1978 mClipboardHideEnabled = enable;
1981 bool Controller::Impl::CopyStringToClipboard( std::string& source )
1983 //Send string to clipboard
1984 return ( mClipboard && mClipboard.SetItem( source ) );
1987 void Controller::Impl::SendSelectionToClipboard( bool deleteAfterSending )
1989 std::string selectedText;
1990 RetrieveSelection( selectedText, deleteAfterSending );
1991 CopyStringToClipboard( selectedText );
1992 ChangeState( EventData::EDITING );
1995 void Controller::Impl::RequestGetTextFromClipboard()
1999 mClipboard.RequestItem();
2003 void Controller::Impl::RepositionSelectionHandles()
2005 CharacterIndex selectionStart = mEventData->mLeftSelectionPosition;
2006 CharacterIndex selectionEnd = mEventData->mRightSelectionPosition;
2008 if( selectionStart == selectionEnd )
2010 // Nothing to select if handles are in the same place.
2011 // So, deactive Highlight box.
2012 mEventData->mDecorator->SetHighlightActive( false );
2016 mEventData->mDecorator->ClearHighlights();
2018 const GlyphIndex* const charactersToGlyphBuffer = mModel->mVisualModel->mCharactersToGlyph.Begin();
2019 const Length* const glyphsPerCharacterBuffer = mModel->mVisualModel->mGlyphsPerCharacter.Begin();
2020 const GlyphInfo* const glyphsBuffer = mModel->mVisualModel->mGlyphs.Begin();
2021 const Vector2* const positionsBuffer = mModel->mVisualModel->mGlyphPositions.Begin();
2022 const Length* const charactersPerGlyphBuffer = mModel->mVisualModel->mCharactersPerGlyph.Begin();
2023 const CharacterIndex* const glyphToCharacterBuffer = mModel->mVisualModel->mGlyphsToCharacters.Begin();
2024 const CharacterDirection* const modelCharacterDirectionsBuffer = ( 0u != mModel->mLogicalModel->mCharacterDirections.Count() ) ? mModel->mLogicalModel->mCharacterDirections.Begin() : NULL;
2026 const bool isLastCharacter = selectionEnd >= mModel->mLogicalModel->mText.Count();
2027 const CharacterDirection startDirection = ( ( NULL == modelCharacterDirectionsBuffer ) ? false : *( modelCharacterDirectionsBuffer + selectionStart ) );
2028 const CharacterDirection endDirection = ( ( NULL == modelCharacterDirectionsBuffer ) ? false : *( modelCharacterDirectionsBuffer + ( selectionEnd - ( isLastCharacter ? 1u : 0u ) ) ) );
2030 // Swap the indices if the start is greater than the end.
2031 const bool indicesSwapped = selectionStart > selectionEnd;
2033 // Tell the decorator to flip the selection handles if needed.
2034 mEventData->mDecorator->SetSelectionHandleFlipState( indicesSwapped, startDirection, endDirection );
2036 if( indicesSwapped )
2038 std::swap( selectionStart, selectionEnd );
2041 // Get the indices to the first and last selected glyphs.
2042 const CharacterIndex selectionEndMinusOne = selectionEnd - 1u;
2043 const GlyphIndex glyphStart = *( charactersToGlyphBuffer + selectionStart );
2044 const Length numberOfGlyphs = *( glyphsPerCharacterBuffer + selectionEndMinusOne );
2045 const GlyphIndex glyphEnd = *( charactersToGlyphBuffer + selectionEndMinusOne ) + ( ( numberOfGlyphs > 0 ) ? numberOfGlyphs - 1u : 0u );
2047 // Get the lines where the glyphs are laid-out.
2048 const LineRun* lineRun = mModel->mVisualModel->mLines.Begin();
2050 LineIndex lineIndex = 0u;
2051 Length numberOfLines = 0u;
2052 mModel->mVisualModel->GetNumberOfLines( glyphStart,
2053 1u + glyphEnd - glyphStart,
2056 const LineIndex firstLineIndex = lineIndex;
2058 // Create the structure to store some selection box info.
2059 Vector<SelectionBoxInfo> selectionBoxLinesInfo;
2060 selectionBoxLinesInfo.Resize( numberOfLines );
2062 SelectionBoxInfo* selectionBoxInfo = selectionBoxLinesInfo.Begin();
2063 selectionBoxInfo->minX = MAX_FLOAT;
2064 selectionBoxInfo->maxX = MIN_FLOAT;
2066 // Keep the min and max 'x' position to calculate the size and position of the highlighed text.
2067 float minHighlightX = std::numeric_limits<float>::max();
2068 float maxHighlightX = std::numeric_limits<float>::min();
2070 Vector2 highLightPosition; // The highlight position in decorator's coords.
2072 // Retrieve the first line and get the line's vertical offset, the line's height and the index to the last glyph.
2074 // The line's vertical offset of all the lines before the line where the first glyph is laid-out.
2075 selectionBoxInfo->lineOffset = CalculateLineOffset( mModel->mVisualModel->mLines,
2078 // Transform to decorator's (control) coords.
2079 selectionBoxInfo->lineOffset += mModel->mScrollPosition.y;
2081 lineRun += firstLineIndex;
2083 // The line height is the addition of the line ascender and the line descender.
2084 // However, the line descender has a negative value, hence the subtraction.
2085 selectionBoxInfo->lineHeight = lineRun->ascender - lineRun->descender;
2087 GlyphIndex lastGlyphOfLine = lineRun->glyphRun.glyphIndex + lineRun->glyphRun.numberOfGlyphs - 1u;
2089 // Check if the first glyph is a ligature that must be broken like Latin ff, fi, or Arabic ï»», etc which needs special code.
2090 const Length numberOfCharactersStart = *( charactersPerGlyphBuffer + glyphStart );
2091 bool splitStartGlyph = ( numberOfCharactersStart > 1u ) && HasLigatureMustBreak( mModel->mLogicalModel->GetScript( selectionStart ) );
2093 // Check if the last glyph is a ligature that must be broken like Latin ff, fi, or Arabic ï»», etc which needs special code.
2094 const Length numberOfCharactersEnd = *( charactersPerGlyphBuffer + glyphEnd );
2095 bool splitEndGlyph = ( glyphStart != glyphEnd ) && ( numberOfCharactersEnd > 1u ) && HasLigatureMustBreak( mModel->mLogicalModel->GetScript( selectionEndMinusOne ) );
2097 // The number of quads of the selection box.
2098 const unsigned int numberOfQuads = 1u + ( glyphEnd - glyphStart ) + ( ( numberOfLines > 1u ) ? 2u * numberOfLines : 0u );
2099 mEventData->mDecorator->ResizeHighlightQuads( numberOfQuads );
2101 // Count the actual number of quads.
2102 unsigned int actualNumberOfQuads = 0u;
2105 // Traverse the glyphs.
2106 for( GlyphIndex index = glyphStart; index <= glyphEnd; ++index )
2108 const GlyphInfo& glyph = *( glyphsBuffer + index );
2109 const Vector2& position = *( positionsBuffer + index );
2111 if( splitStartGlyph )
2113 // 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.
2115 const float glyphAdvance = glyph.advance / static_cast<float>( numberOfCharactersStart );
2116 const CharacterIndex interGlyphIndex = selectionStart - *( glyphToCharacterBuffer + glyphStart );
2117 // Get the direction of the character.
2118 CharacterDirection isCurrentRightToLeft = false;
2119 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
2121 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + selectionStart );
2124 // The end point could be in the middle of the ligature.
2125 // Calculate the number of characters selected.
2126 const Length numberOfCharacters = ( glyphStart == glyphEnd ) ? ( selectionEnd - selectionStart ) : ( numberOfCharactersStart - interGlyphIndex );
2128 quad.x = lineRun->alignmentOffset + position.x - glyph.xBearing + mModel->mScrollPosition.x + glyphAdvance * static_cast<float>( isCurrentRightToLeft ? ( numberOfCharactersStart - interGlyphIndex - numberOfCharacters ) : interGlyphIndex );
2129 quad.y = selectionBoxInfo->lineOffset;
2130 quad.z = quad.x + static_cast<float>( numberOfCharacters ) * glyphAdvance;
2131 quad.w = selectionBoxInfo->lineOffset + selectionBoxInfo->lineHeight;
2133 // Store the min and max 'x' for each line.
2134 selectionBoxInfo->minX = std::min( selectionBoxInfo->minX, quad.x );
2135 selectionBoxInfo->maxX = std::max( selectionBoxInfo->maxX, quad.z );
2137 mEventData->mDecorator->AddHighlight( actualNumberOfQuads, quad );
2138 ++actualNumberOfQuads;
2140 splitStartGlyph = false;
2144 if( splitEndGlyph && ( index == glyphEnd ) )
2146 // 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.
2148 const float glyphAdvance = glyph.advance / static_cast<float>( numberOfCharactersEnd );
2149 const CharacterIndex interGlyphIndex = selectionEnd - *( glyphToCharacterBuffer + glyphEnd );
2150 // Get the direction of the character.
2151 CharacterDirection isCurrentRightToLeft = false;
2152 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
2154 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + selectionEnd );
2157 const Length numberOfCharacters = numberOfCharactersEnd - interGlyphIndex;
2159 quad.x = lineRun->alignmentOffset + position.x - glyph.xBearing + mModel->mScrollPosition.x + ( isCurrentRightToLeft ? ( glyphAdvance * static_cast<float>( numberOfCharacters ) ) : 0.f );
2160 quad.y = selectionBoxInfo->lineOffset;
2161 quad.z = quad.x + static_cast<float>( interGlyphIndex ) * glyphAdvance;
2162 quad.w = quad.y + selectionBoxInfo->lineHeight;
2164 // Store the min and max 'x' for each line.
2165 selectionBoxInfo->minX = std::min( selectionBoxInfo->minX, quad.x );
2166 selectionBoxInfo->maxX = std::max( selectionBoxInfo->maxX, quad.z );
2168 mEventData->mDecorator->AddHighlight( actualNumberOfQuads,
2170 ++actualNumberOfQuads;
2172 splitEndGlyph = false;
2176 quad.x = lineRun->alignmentOffset + position.x - glyph.xBearing + mModel->mScrollPosition.x;
2177 quad.y = selectionBoxInfo->lineOffset;
2178 quad.z = quad.x + glyph.advance;
2179 quad.w = quad.y + selectionBoxInfo->lineHeight;
2181 // Store the min and max 'x' for each line.
2182 selectionBoxInfo->minX = std::min( selectionBoxInfo->minX, quad.x );
2183 selectionBoxInfo->maxX = std::max( selectionBoxInfo->maxX, quad.z );
2185 mEventData->mDecorator->AddHighlight( actualNumberOfQuads,
2187 ++actualNumberOfQuads;
2189 // Whether to retrieve the next line.
2190 if( index == lastGlyphOfLine )
2193 if( lineIndex < firstLineIndex + numberOfLines )
2195 // Retrieve the next line.
2198 // Get the last glyph of the new line.
2199 lastGlyphOfLine = lineRun->glyphRun.glyphIndex + lineRun->glyphRun.numberOfGlyphs - 1u;
2201 // Keep the offset and height of the current selection box.
2202 const float currentLineOffset = selectionBoxInfo->lineOffset;
2203 const float currentLineHeight = selectionBoxInfo->lineHeight;
2205 // Get the selection box info for the next line.
2208 selectionBoxInfo->minX = MAX_FLOAT;
2209 selectionBoxInfo->maxX = MIN_FLOAT;
2211 // Update the line's vertical offset.
2212 selectionBoxInfo->lineOffset = currentLineOffset + currentLineHeight;
2214 // The line height is the addition of the line ascender and the line descender.
2215 // However, the line descender has a negative value, hence the subtraction.
2216 selectionBoxInfo->lineHeight = lineRun->ascender - lineRun->descender;
2221 // Traverses all the lines and updates the min and max 'x' positions and the total height.
2222 // The final width is calculated after 'boxifying' the selection.
2223 for( Vector<SelectionBoxInfo>::ConstIterator it = selectionBoxLinesInfo.Begin(),
2224 endIt = selectionBoxLinesInfo.End();
2228 const SelectionBoxInfo& info = *it;
2230 // Update the size of the highlighted text.
2231 highLightSize.height += info.lineHeight;
2232 minHighlightX = std::min( minHighlightX, info.minX );
2233 maxHighlightX = std::max( maxHighlightX, info.maxX );
2236 // Add extra geometry to 'boxify' the selection.
2238 if( 1u < numberOfLines )
2240 // Boxify the first line.
2241 lineRun = mModel->mVisualModel->mLines.Begin() + firstLineIndex;
2242 const SelectionBoxInfo& firstSelectionBoxLineInfo = *( selectionBoxLinesInfo.Begin() );
2244 bool boxifyBegin = ( LTR != lineRun->direction ) && ( LTR != startDirection );
2245 bool boxifyEnd = ( LTR == lineRun->direction ) && ( LTR == startDirection );
2250 quad.y = firstSelectionBoxLineInfo.lineOffset;
2251 quad.z = firstSelectionBoxLineInfo.minX;
2252 quad.w = firstSelectionBoxLineInfo.lineOffset + firstSelectionBoxLineInfo.lineHeight;
2254 // Boxify at the beginning of the line.
2255 mEventData->mDecorator->AddHighlight( actualNumberOfQuads,
2257 ++actualNumberOfQuads;
2259 // Update the size of the highlighted text.
2260 minHighlightX = 0.f;
2265 quad.x = firstSelectionBoxLineInfo.maxX;
2266 quad.y = firstSelectionBoxLineInfo.lineOffset;
2267 quad.z = mModel->mVisualModel->mControlSize.width;
2268 quad.w = firstSelectionBoxLineInfo.lineOffset + firstSelectionBoxLineInfo.lineHeight;
2270 // Boxify at the end of the line.
2271 mEventData->mDecorator->AddHighlight( actualNumberOfQuads,
2273 ++actualNumberOfQuads;
2275 // Update the size of the highlighted text.
2276 maxHighlightX = mModel->mVisualModel->mControlSize.width;
2279 // Boxify the central lines.
2280 if( 2u < numberOfLines )
2282 for( Vector<SelectionBoxInfo>::ConstIterator it = selectionBoxLinesInfo.Begin() + 1u,
2283 endIt = selectionBoxLinesInfo.End() - 1u;
2287 const SelectionBoxInfo& info = *it;
2290 quad.y = info.lineOffset;
2292 quad.w = info.lineOffset + info.lineHeight;
2294 mEventData->mDecorator->AddHighlight( actualNumberOfQuads,
2296 ++actualNumberOfQuads;
2299 quad.y = info.lineOffset;
2300 quad.z = mModel->mVisualModel->mControlSize.width;
2301 quad.w = info.lineOffset + info.lineHeight;
2303 mEventData->mDecorator->AddHighlight( actualNumberOfQuads,
2305 ++actualNumberOfQuads;
2308 // Update the size of the highlighted text.
2309 minHighlightX = 0.f;
2310 maxHighlightX = mModel->mVisualModel->mControlSize.width;
2313 // Boxify the last line.
2314 lineRun = mModel->mVisualModel->mLines.Begin() + firstLineIndex + numberOfLines - 1u;
2315 const SelectionBoxInfo& lastSelectionBoxLineInfo = *( selectionBoxLinesInfo.End() - 1u );
2317 boxifyBegin = ( LTR == lineRun->direction ) && ( LTR == endDirection );
2318 boxifyEnd = ( LTR != lineRun->direction ) && ( LTR != endDirection );
2323 quad.y = lastSelectionBoxLineInfo.lineOffset;
2324 quad.z = lastSelectionBoxLineInfo.minX;
2325 quad.w = lastSelectionBoxLineInfo.lineOffset + lastSelectionBoxLineInfo.lineHeight;
2327 // Boxify at the beginning of the line.
2328 mEventData->mDecorator->AddHighlight( actualNumberOfQuads,
2330 ++actualNumberOfQuads;
2332 // Update the size of the highlighted text.
2333 minHighlightX = 0.f;
2338 quad.x = lastSelectionBoxLineInfo.maxX;
2339 quad.y = lastSelectionBoxLineInfo.lineOffset;
2340 quad.z = mModel->mVisualModel->mControlSize.width;
2341 quad.w = lastSelectionBoxLineInfo.lineOffset + lastSelectionBoxLineInfo.lineHeight;
2343 // Boxify at the end of the line.
2344 mEventData->mDecorator->AddHighlight( actualNumberOfQuads,
2346 ++actualNumberOfQuads;
2348 // Update the size of the highlighted text.
2349 maxHighlightX = mModel->mVisualModel->mControlSize.width;
2353 // Set the actual number of quads.
2354 mEventData->mDecorator->ResizeHighlightQuads( actualNumberOfQuads );
2356 // Sets the highlight's size and position. In decorator's coords.
2357 // The highlight's height has been calculated above (before 'boxifying' the highlight).
2358 highLightSize.width = maxHighlightX - minHighlightX;
2360 highLightPosition.x = minHighlightX;
2361 const SelectionBoxInfo& firstSelectionBoxLineInfo = *( selectionBoxLinesInfo.Begin() );
2362 highLightPosition.y = firstSelectionBoxLineInfo.lineOffset;
2364 mEventData->mDecorator->SetHighLightBox( highLightPosition, highLightSize, static_cast<float>( mModel->GetOutlineWidth() ) );
2366 if( !mEventData->mDecorator->IsSmoothHandlePanEnabled() )
2368 CursorInfo primaryCursorInfo;
2369 GetCursorPosition( mEventData->mLeftSelectionPosition,
2370 primaryCursorInfo );
2372 const Vector2 primaryPosition = primaryCursorInfo.primaryPosition + mModel->mScrollPosition;
2374 mEventData->mDecorator->SetPosition( LEFT_SELECTION_HANDLE,
2376 primaryCursorInfo.lineOffset + mModel->mScrollPosition.y,
2377 primaryCursorInfo.lineHeight );
2379 CursorInfo secondaryCursorInfo;
2380 GetCursorPosition( mEventData->mRightSelectionPosition,
2381 secondaryCursorInfo );
2383 const Vector2 secondaryPosition = secondaryCursorInfo.primaryPosition + mModel->mScrollPosition;
2385 mEventData->mDecorator->SetPosition( RIGHT_SELECTION_HANDLE,
2386 secondaryPosition.x,
2387 secondaryCursorInfo.lineOffset + mModel->mScrollPosition.y,
2388 secondaryCursorInfo.lineHeight );
2391 // Set the flag to update the decorator.
2392 mEventData->mDecoratorUpdated = true;
2395 void Controller::Impl::RepositionSelectionHandles( float visualX, float visualY, Controller::NoTextTap::Action action )
2397 if( NULL == mEventData )
2399 // Nothing to do if there is no text input.
2403 if( IsShowingPlaceholderText() )
2405 // Nothing to do if there is the place-holder text.
2409 const Length numberOfGlyphs = mModel->mVisualModel->mGlyphs.Count();
2410 const Length numberOfLines = mModel->mVisualModel->mLines.Count();
2411 if( ( 0 == numberOfGlyphs ) ||
2412 ( 0 == numberOfLines ) )
2414 // Nothing to do if there is no text.
2418 // Find which word was selected
2419 CharacterIndex selectionStart( 0 );
2420 CharacterIndex selectionEnd( 0 );
2421 CharacterIndex noTextHitIndex( 0 );
2422 const bool characterHit = FindSelectionIndices( mModel->mVisualModel,
2423 mModel->mLogicalModel,
2430 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "%p selectionStart %d selectionEnd %d\n", this, selectionStart, selectionEnd );
2432 if( characterHit || ( Controller::NoTextTap::HIGHLIGHT == action ) )
2434 ChangeState( EventData::SELECTING );
2436 mEventData->mLeftSelectionPosition = selectionStart;
2437 mEventData->mRightSelectionPosition = selectionEnd;
2439 mEventData->mUpdateLeftSelectionPosition = true;
2440 mEventData->mUpdateRightSelectionPosition = true;
2441 mEventData->mUpdateHighlightBox = true;
2443 // It may happen an InputMethodContext commit event arrives before the selection event
2444 // if the InputMethodContext is in pre-edit state. The commit event will set the
2445 // mEventData->mUpdateCursorPosition flag to true. If it's not set back
2446 // to false, the highlight box won't be updated.
2447 mEventData->mUpdateCursorPosition = false;
2449 mEventData->mScrollAfterUpdatePosition = ( mEventData->mLeftSelectionPosition != mEventData->mRightSelectionPosition );
2451 // Cursor to be positioned at end of selection so if selection interrupted and edit mode restarted the cursor will be at end of selection
2452 mEventData->mPrimaryCursorPosition = std::max( mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition );
2454 else if( Controller::NoTextTap::SHOW_SELECTION_POPUP == action )
2456 // Nothing to select. i.e. a white space, out of bounds
2457 ChangeState( EventData::EDITING_WITH_POPUP );
2459 mEventData->mPrimaryCursorPosition = noTextHitIndex;
2461 mEventData->mUpdateCursorPosition = true;
2462 mEventData->mUpdateGrabHandlePosition = true;
2463 mEventData->mScrollAfterUpdatePosition = true;
2464 mEventData->mUpdateInputStyle = true;
2466 else if( Controller::NoTextTap::NO_ACTION == action )
2468 // Nothing to select. i.e. a white space, out of bounds
2469 mEventData->mPrimaryCursorPosition = noTextHitIndex;
2471 mEventData->mUpdateCursorPosition = true;
2472 mEventData->mUpdateGrabHandlePosition = true;
2473 mEventData->mScrollAfterUpdatePosition = true;
2474 mEventData->mUpdateInputStyle = true;
2478 void Controller::Impl::SetPopupButtons()
2481 * Sets the Popup buttons to be shown depending on State.
2483 * If SELECTING : CUT & COPY + ( PASTE & CLIPBOARD if content available to paste )
2485 * If EDITING_WITH_POPUP : SELECT & SELECT_ALL
2488 TextSelectionPopup::Buttons buttonsToShow = TextSelectionPopup::NONE;
2490 if( EventData::SELECTING == mEventData->mState )
2492 buttonsToShow = TextSelectionPopup::Buttons( TextSelectionPopup::CUT | TextSelectionPopup::COPY );
2494 if( !IsClipboardEmpty() )
2496 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
2497 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::CLIPBOARD ) );
2500 if( !mEventData->mAllTextSelected )
2502 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::SELECT_ALL ) );
2505 else if( EventData::EDITING_WITH_POPUP == mEventData->mState )
2507 if( mModel->mLogicalModel->mText.Count() && !IsShowingPlaceholderText() )
2509 buttonsToShow = TextSelectionPopup::Buttons( TextSelectionPopup::SELECT | TextSelectionPopup::SELECT_ALL );
2512 if( !IsClipboardEmpty() )
2514 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
2515 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::CLIPBOARD ) );
2518 else if( EventData::EDITING_WITH_PASTE_POPUP == mEventData->mState )
2520 if ( !IsClipboardEmpty() )
2522 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
2523 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::CLIPBOARD ) );
2527 mEventData->mDecorator->SetEnabledPopupButtons( buttonsToShow );
2530 void Controller::Impl::ChangeState( EventData::State newState )
2532 if( NULL == mEventData )
2534 // Nothing to do if there is no text input.
2538 DALI_LOG_INFO( gLogFilter, Debug::General, "ChangeState state:%d newstate:%d\n", mEventData->mState, newState );
2540 if( mEventData->mState != newState )
2542 mEventData->mPreviousState = mEventData->mState;
2543 mEventData->mState = newState;
2545 switch( mEventData->mState )
2547 case EventData::INACTIVE:
2549 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
2550 mEventData->mDecorator->StopCursorBlink();
2551 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
2552 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
2553 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
2554 mEventData->mDecorator->SetHighlightActive( false );
2555 mEventData->mDecorator->SetPopupActive( false );
2556 mEventData->mDecoratorUpdated = true;
2559 case EventData::INTERRUPTED:
2561 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
2562 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
2563 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
2564 mEventData->mDecorator->SetHighlightActive( false );
2565 mEventData->mDecorator->SetPopupActive( false );
2566 mEventData->mDecoratorUpdated = true;
2569 case EventData::SELECTING:
2571 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
2572 mEventData->mDecorator->StopCursorBlink();
2573 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
2574 if ( mEventData->mGrabHandleEnabled )
2576 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true );
2577 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true );
2579 mEventData->mDecorator->SetHighlightActive( true );
2580 if( mEventData->mGrabHandlePopupEnabled )
2583 mEventData->mDecorator->SetPopupActive( true );
2585 mEventData->mDecoratorUpdated = true;
2588 case EventData::EDITING:
2590 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
2591 if( mEventData->mCursorBlinkEnabled )
2593 mEventData->mDecorator->StartCursorBlink();
2595 // Grab handle is not shown until a tap is received whilst EDITING
2596 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
2597 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
2598 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
2599 mEventData->mDecorator->SetHighlightActive( false );
2600 if( mEventData->mGrabHandlePopupEnabled )
2602 mEventData->mDecorator->SetPopupActive( false );
2604 mEventData->mDecoratorUpdated = true;
2607 case EventData::EDITING_WITH_POPUP:
2609 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "EDITING_WITH_POPUP \n", newState );
2611 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
2612 if( mEventData->mCursorBlinkEnabled )
2614 mEventData->mDecorator->StartCursorBlink();
2616 if( mEventData->mSelectionEnabled )
2618 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
2619 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
2620 mEventData->mDecorator->SetHighlightActive( false );
2622 else if ( mEventData->mGrabHandleEnabled )
2624 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
2626 if( mEventData->mGrabHandlePopupEnabled )
2629 mEventData->mDecorator->SetPopupActive( true );
2631 mEventData->mDecoratorUpdated = true;
2634 case EventData::EDITING_WITH_GRAB_HANDLE:
2636 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "EDITING_WITH_GRAB_HANDLE \n", newState );
2638 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
2639 if( mEventData->mCursorBlinkEnabled )
2641 mEventData->mDecorator->StartCursorBlink();
2643 // Grab handle is not shown until a tap is received whilst EDITING
2644 if ( mEventData->mGrabHandleEnabled )
2646 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
2648 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
2649 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
2650 mEventData->mDecorator->SetHighlightActive( false );
2651 if( mEventData->mGrabHandlePopupEnabled )
2653 mEventData->mDecorator->SetPopupActive( false );
2655 mEventData->mDecoratorUpdated = true;
2658 case EventData::SELECTION_HANDLE_PANNING:
2660 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
2661 mEventData->mDecorator->StopCursorBlink();
2662 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
2663 if ( mEventData->mGrabHandleEnabled )
2665 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true );
2666 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true );
2668 mEventData->mDecorator->SetHighlightActive( true );
2669 if( mEventData->mGrabHandlePopupEnabled )
2671 mEventData->mDecorator->SetPopupActive( false );
2673 mEventData->mDecoratorUpdated = true;
2676 case EventData::GRAB_HANDLE_PANNING:
2678 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "GRAB_HANDLE_PANNING \n", newState );
2680 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
2681 if( mEventData->mCursorBlinkEnabled )
2683 mEventData->mDecorator->StartCursorBlink();
2685 if ( mEventData->mGrabHandleEnabled )
2687 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
2689 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
2690 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
2691 mEventData->mDecorator->SetHighlightActive( false );
2692 if( mEventData->mGrabHandlePopupEnabled )
2694 mEventData->mDecorator->SetPopupActive( false );
2696 mEventData->mDecoratorUpdated = true;
2699 case EventData::EDITING_WITH_PASTE_POPUP:
2701 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "EDITING_WITH_PASTE_POPUP \n", newState );
2703 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
2704 if( mEventData->mCursorBlinkEnabled )
2706 mEventData->mDecorator->StartCursorBlink();
2709 if ( mEventData->mGrabHandleEnabled )
2711 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
2713 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
2714 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
2715 mEventData->mDecorator->SetHighlightActive( false );
2717 if( mEventData->mGrabHandlePopupEnabled )
2720 mEventData->mDecorator->SetPopupActive( true );
2722 mEventData->mDecoratorUpdated = true;
2725 case EventData::TEXT_PANNING:
2727 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
2728 mEventData->mDecorator->StopCursorBlink();
2729 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
2730 if( mEventData->mDecorator->IsHandleActive( LEFT_SELECTION_HANDLE ) ||
2731 mEventData->mDecorator->IsHandleActive( RIGHT_SELECTION_HANDLE ) )
2733 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
2734 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
2735 mEventData->mDecorator->SetHighlightActive( true );
2738 if( mEventData->mGrabHandlePopupEnabled )
2740 mEventData->mDecorator->SetPopupActive( false );
2743 mEventData->mDecoratorUpdated = true;
2750 void Controller::Impl::GetCursorPosition( CharacterIndex logical,
2751 CursorInfo& cursorInfo )
2753 if( !IsShowingRealText() )
2755 // Do not want to use the place-holder text to set the cursor position.
2757 // Use the line's height of the font's family set to set the cursor's size.
2758 // If there is no font's family set, use the default font.
2759 // Use the current alignment to place the cursor at the beginning, center or end of the box.
2761 cursorInfo.lineOffset = 0.f;
2762 cursorInfo.lineHeight = GetDefaultFontLineHeight();
2763 cursorInfo.primaryCursorHeight = cursorInfo.lineHeight;
2766 if( mModel->mMatchSystemLanguageDirection )
2768 isRTL = mLayoutDirection == LayoutDirection::RIGHT_TO_LEFT;
2771 switch( mModel->mHorizontalAlignment )
2773 case Text::HorizontalAlignment::BEGIN :
2777 cursorInfo.primaryPosition.x = mModel->mVisualModel->mControlSize.width - mEventData->mDecorator->GetCursorWidth();
2781 cursorInfo.primaryPosition.x = 0.f;
2785 case Text::HorizontalAlignment::CENTER:
2787 cursorInfo.primaryPosition.x = floorf( 0.5f * mModel->mVisualModel->mControlSize.width );
2790 case Text::HorizontalAlignment::END:
2794 cursorInfo.primaryPosition.x = 0.f;
2798 cursorInfo.primaryPosition.x = mModel->mVisualModel->mControlSize.width - mEventData->mDecorator->GetCursorWidth();
2804 // Nothing else to do.
2808 const bool isMultiLine = ( Layout::Engine::MULTI_LINE_BOX == mLayoutEngine.GetLayout() );
2809 GetCursorPositionParameters parameters;
2810 parameters.visualModel = mModel->mVisualModel;
2811 parameters.logicalModel = mModel->mLogicalModel;
2812 parameters.metrics = mMetrics;
2813 parameters.logical = logical;
2814 parameters.isMultiline = isMultiLine;
2816 Text::GetCursorPosition( parameters,
2819 // Adds Outline offset.
2820 const float outlineWidth = static_cast<float>( mModel->GetOutlineWidth() );
2821 cursorInfo.primaryPosition.x += outlineWidth;
2822 cursorInfo.primaryPosition.y += outlineWidth;
2823 cursorInfo.secondaryPosition.x += outlineWidth;
2824 cursorInfo.secondaryPosition.y += outlineWidth;
2828 // If the text is editable and multi-line, the cursor position after a white space shouldn't exceed the boundaries of the text control.
2830 // Note the white spaces laid-out at the end of the line might exceed the boundaries of the control.
2831 // The reason is a wrapped line must not start with a white space so they are laid-out at the end of the line.
2833 if( 0.f > cursorInfo.primaryPosition.x )
2835 cursorInfo.primaryPosition.x = 0.f;
2838 const float edgeWidth = mModel->mVisualModel->mControlSize.width - static_cast<float>( mEventData->mDecorator->GetCursorWidth() );
2839 if( cursorInfo.primaryPosition.x > edgeWidth )
2841 cursorInfo.primaryPosition.x = edgeWidth;
2846 CharacterIndex Controller::Impl::CalculateNewCursorIndex( CharacterIndex index ) const
2848 if( NULL == mEventData )
2850 // Nothing to do if there is no text input.
2854 CharacterIndex cursorIndex = mEventData->mPrimaryCursorPosition;
2856 const GlyphIndex* const charactersToGlyphBuffer = mModel->mVisualModel->mCharactersToGlyph.Begin();
2857 const Length* const charactersPerGlyphBuffer = mModel->mVisualModel->mCharactersPerGlyph.Begin();
2859 GlyphIndex glyphIndex = *( charactersToGlyphBuffer + index );
2860 Length numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
2862 if( numberOfCharacters > 1u )
2864 const Script script = mModel->mLogicalModel->GetScript( index );
2865 if( HasLigatureMustBreak( script ) )
2867 // Prevents to jump the whole Latin ligatures like fi, ff, or Arabic ï»», ...
2868 numberOfCharacters = 1u;
2873 while( 0u == numberOfCharacters )
2876 numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
2880 if( index < mEventData->mPrimaryCursorPosition )
2882 cursorIndex -= numberOfCharacters;
2886 cursorIndex += numberOfCharacters;
2889 // Will update the cursor hook position.
2890 mEventData->mUpdateCursorHookPosition = true;
2895 void Controller::Impl::UpdateCursorPosition( const CursorInfo& cursorInfo )
2897 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "-->Controller::UpdateCursorPosition %p\n", this );
2898 if( NULL == mEventData )
2900 // Nothing to do if there is no text input.
2901 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition no event data\n" );
2905 const Vector2 cursorPosition = cursorInfo.primaryPosition + mModel->mScrollPosition;
2907 mEventData->mDecorator->SetGlyphOffset( PRIMARY_CURSOR, cursorInfo.glyphOffset );
2909 // Sets the cursor position.
2910 mEventData->mDecorator->SetPosition( PRIMARY_CURSOR,
2913 cursorInfo.primaryCursorHeight,
2914 cursorInfo.lineHeight );
2915 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Primary cursor position: %f,%f\n", cursorPosition.x, cursorPosition.y );
2917 if( mEventData->mUpdateGrabHandlePosition )
2919 // Sets the grab handle position.
2920 mEventData->mDecorator->SetPosition( GRAB_HANDLE,
2922 cursorInfo.lineOffset + mModel->mScrollPosition.y,
2923 cursorInfo.lineHeight );
2926 if( cursorInfo.isSecondaryCursor )
2928 mEventData->mDecorator->SetPosition( SECONDARY_CURSOR,
2929 cursorInfo.secondaryPosition.x + mModel->mScrollPosition.x,
2930 cursorInfo.secondaryPosition.y + mModel->mScrollPosition.y,
2931 cursorInfo.secondaryCursorHeight,
2932 cursorInfo.lineHeight );
2933 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Secondary cursor position: %f,%f\n", cursorInfo.secondaryPosition.x + mModel->mScrollPosition.x, cursorInfo.secondaryPosition.y + mModel->mScrollPosition.y );
2936 // Set which cursors are active according the state.
2937 if( EventData::IsEditingState( mEventData->mState ) || ( EventData::GRAB_HANDLE_PANNING == mEventData->mState ) )
2939 if( cursorInfo.isSecondaryCursor )
2941 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_BOTH );
2945 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
2950 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
2953 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition\n" );
2956 void Controller::Impl::UpdateSelectionHandle( HandleType handleType,
2957 const CursorInfo& cursorInfo )
2959 if( ( LEFT_SELECTION_HANDLE != handleType ) &&
2960 ( RIGHT_SELECTION_HANDLE != handleType ) )
2965 const Vector2 cursorPosition = cursorInfo.primaryPosition + mModel->mScrollPosition;
2967 // Sets the handle's position.
2968 mEventData->mDecorator->SetPosition( handleType,
2970 cursorInfo.lineOffset + mModel->mScrollPosition.y,
2971 cursorInfo.lineHeight );
2973 // If selection handle at start of the text and other at end of the text then all text is selected.
2974 const CharacterIndex startOfSelection = std::min( mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition );
2975 const CharacterIndex endOfSelection = std::max ( mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition );
2976 mEventData->mAllTextSelected = ( startOfSelection == 0 ) && ( endOfSelection == mModel->mLogicalModel->mText.Count() );
2979 void Controller::Impl::ClampHorizontalScroll( const Vector2& layoutSize )
2981 // Clamp between -space & -alignment offset.
2983 if( layoutSize.width > mModel->mVisualModel->mControlSize.width )
2985 const float space = ( layoutSize.width - mModel->mVisualModel->mControlSize.width ) + mModel->mAlignmentOffset;
2986 mModel->mScrollPosition.x = ( mModel->mScrollPosition.x < -space ) ? -space : mModel->mScrollPosition.x;
2987 mModel->mScrollPosition.x = ( mModel->mScrollPosition.x > -mModel->mAlignmentOffset ) ? -mModel->mAlignmentOffset : mModel->mScrollPosition.x;
2989 mEventData->mDecoratorUpdated = true;
2993 mModel->mScrollPosition.x = 0.f;
2997 void Controller::Impl::ClampVerticalScroll( const Vector2& layoutSize )
2999 if( Layout::Engine::SINGLE_LINE_BOX == mLayoutEngine.GetLayout() )
3001 // Nothing to do if the text is single line.
3005 // Clamp between -space & 0.
3006 if( layoutSize.height > mModel->mVisualModel->mControlSize.height )
3008 const float space = ( layoutSize.height - mModel->mVisualModel->mControlSize.height );
3009 mModel->mScrollPosition.y = ( mModel->mScrollPosition.y < -space ) ? -space : mModel->mScrollPosition.y;
3010 mModel->mScrollPosition.y = ( mModel->mScrollPosition.y > 0.f ) ? 0.f : mModel->mScrollPosition.y;
3012 mEventData->mDecoratorUpdated = true;
3016 mModel->mScrollPosition.y = 0.f;
3020 void Controller::Impl::ScrollToMakePositionVisible( const Vector2& position, float lineHeight )
3022 const float cursorWidth = mEventData->mDecorator ? static_cast<float>( mEventData->mDecorator->GetCursorWidth() ) : 0.f;
3024 // position is in actor's coords.
3025 const float positionEndX = position.x + cursorWidth;
3026 const float positionEndY = position.y + lineHeight;
3028 // Transform the position to decorator coords.
3029 const float decoratorPositionBeginX = position.x + mModel->mScrollPosition.x;
3030 const float decoratorPositionEndX = positionEndX + mModel->mScrollPosition.x;
3032 const float decoratorPositionBeginY = position.y + mModel->mScrollPosition.y;
3033 const float decoratorPositionEndY = positionEndY + mModel->mScrollPosition.y;
3035 if( decoratorPositionBeginX < 0.f )
3037 mModel->mScrollPosition.x = -position.x;
3039 else if( decoratorPositionEndX > mModel->mVisualModel->mControlSize.width )
3041 mModel->mScrollPosition.x = mModel->mVisualModel->mControlSize.width - positionEndX;
3044 if( Layout::Engine::MULTI_LINE_BOX == mLayoutEngine.GetLayout() )
3046 if( decoratorPositionBeginY < 0.f )
3048 mModel->mScrollPosition.y = -position.y;
3050 else if( decoratorPositionEndY > mModel->mVisualModel->mControlSize.height )
3052 mModel->mScrollPosition.y = mModel->mVisualModel->mControlSize.height - positionEndY;
3057 void Controller::Impl::ScrollTextToMatchCursor( const CursorInfo& cursorInfo )
3059 // Get the current cursor position in decorator coords.
3060 const Vector2& currentCursorPosition = mEventData->mDecorator->GetPosition( PRIMARY_CURSOR );
3062 const LineIndex lineIndex = mModel->mVisualModel->GetLineOfCharacter( mEventData->mPrimaryCursorPosition );
3066 // Calculate the offset to match the cursor position before the character was deleted.
3067 mModel->mScrollPosition.x = currentCursorPosition.x - cursorInfo.primaryPosition.x;
3069 //If text control has more than two lines and current line index is not last, calculate scrollpositionY
3070 if( mModel->mVisualModel->mLines.Count() > 1u && lineIndex != mModel->mVisualModel->mLines.Count() -1u )
3072 const float currentCursorGlyphOffset = mEventData->mDecorator->GetGlyphOffset( PRIMARY_CURSOR );
3073 mModel->mScrollPosition.y = currentCursorPosition.y - cursorInfo.lineOffset - currentCursorGlyphOffset;
3077 ClampHorizontalScroll( mModel->mVisualModel->GetLayoutSize() );
3078 ClampVerticalScroll( mModel->mVisualModel->GetLayoutSize() );
3080 // Makes the new cursor position visible if needed.
3081 ScrollToMakePositionVisible( cursorInfo.primaryPosition, cursorInfo.lineHeight );
3084 void Controller::Impl::RequestRelayout()
3086 if( NULL != mControlInterface )
3088 mControlInterface->RequestTextRelayout();
3094 } // namespace Toolkit