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>
36 #include <dali-toolkit/internal/text/text-editable-control-interface.h>
42 * @brief Struct used to calculate the selection box.
44 struct SelectionBoxInfo
52 #if defined(DEBUG_ENABLED)
53 Debug::Filter* gLogFilter = Debug::Filter::New(Debug::NoLogging, true, "LOG_TEXT_CONTROLS");
56 const float MAX_FLOAT = std::numeric_limits<float>::max();
57 const float MIN_FLOAT = std::numeric_limits<float>::min();
58 const Dali::Toolkit::Text::CharacterDirection LTR = false; ///< Left To Right direction
71 EventData::EventData( DecoratorPtr decorator, InputMethodContext& inputMethodContext )
72 : mDecorator( decorator ),
73 mInputMethodContext( inputMethodContext ),
74 mPlaceholderFont( NULL ),
75 mPlaceholderTextActive(),
76 mPlaceholderTextInactive(),
77 mPlaceholderTextColor( 0.8f, 0.8f, 0.8f, 0.8f ), // This color has been published in the Public API (placeholder-properties.h).
79 mInputStyleChangedQueue(),
80 mPreviousState( INACTIVE ),
82 mPrimaryCursorPosition( 0u ),
83 mLeftSelectionPosition( 0u ),
84 mRightSelectionPosition( 0u ),
85 mPreEditStartPosition( 0u ),
87 mCursorHookPositionX( 0.f ),
88 mDoubleTapAction( Controller::NoTextTap::NO_ACTION ),
89 mLongPressAction( Controller::NoTextTap::SHOW_SELECTION_POPUP ),
90 mIsShowingPlaceholderText( false ),
91 mPreEditFlag( false ),
92 mDecoratorUpdated( false ),
93 mCursorBlinkEnabled( true ),
94 mGrabHandleEnabled( true ),
95 mGrabHandlePopupEnabled( true ),
96 mSelectionEnabled( true ),
97 mUpdateCursorHookPosition( false ),
98 mUpdateCursorPosition( false ),
99 mUpdateGrabHandlePosition( false ),
100 mUpdateLeftSelectionPosition( false ),
101 mUpdateRightSelectionPosition( false ),
102 mIsLeftHandleSelected( false ),
103 mIsRightHandleSelected( false ),
104 mUpdateHighlightBox( false ),
105 mScrollAfterUpdatePosition( false ),
106 mScrollAfterDelete( false ),
107 mAllTextSelected( false ),
108 mUpdateInputStyle( false ),
109 mPasswordInput( false ),
110 mCheckScrollAmount( false ),
111 mIsPlaceholderPixelSize( false ),
112 mIsPlaceholderElideEnabled( false ),
113 mPlaceholderEllipsisFlag( false ),
114 mShiftSelectionFlag( true )
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,
196 if( NULL != mEditableControlInterface )
198 mEditableControlInterface->CaretMoved( mEventData->mPrimaryCursorPosition );
201 if( mEventData->mUpdateCursorHookPosition )
203 // Update the cursor hook position. Used to move the cursor with the keys 'up' and 'down'.
204 mEventData->mCursorHookPositionX = cursorInfo.primaryPosition.x;
205 mEventData->mUpdateCursorHookPosition = false;
208 // Scroll first the text after delete ...
209 if( mEventData->mScrollAfterDelete )
211 ScrollTextToMatchCursor( cursorInfo );
214 // ... then, text can be scrolled to make the cursor visible.
215 if( mEventData->mScrollAfterUpdatePosition )
217 const Vector2 currentCursorPosition( cursorInfo.primaryPosition.x, cursorInfo.lineOffset );
218 ScrollToMakePositionVisible( currentCursorPosition, cursorInfo.lineHeight );
220 mEventData->mScrollAfterUpdatePosition = false;
221 mEventData->mScrollAfterDelete = false;
223 UpdateCursorPosition( cursorInfo );
225 mEventData->mDecoratorUpdated = true;
226 mEventData->mUpdateCursorPosition = false;
227 mEventData->mUpdateGrabHandlePosition = false;
231 CursorInfo leftHandleInfo;
232 CursorInfo rightHandleInfo;
234 if( mEventData->mUpdateHighlightBox )
236 GetCursorPosition( mEventData->mLeftSelectionPosition,
239 GetCursorPosition( mEventData->mRightSelectionPosition,
242 if( mEventData->mScrollAfterUpdatePosition && ( mEventData->mIsLeftHandleSelected ? mEventData->mUpdateLeftSelectionPosition : mEventData->mUpdateRightSelectionPosition ) )
244 if( mEventData->mIsLeftHandleSelected && mEventData->mIsRightHandleSelected )
246 CursorInfo& infoLeft = leftHandleInfo;
248 const Vector2 currentCursorPositionLeft( infoLeft.primaryPosition.x, infoLeft.lineOffset );
249 ScrollToMakePositionVisible( currentCursorPositionLeft, infoLeft.lineHeight );
251 CursorInfo& infoRight = rightHandleInfo;
253 const Vector2 currentCursorPositionRight( infoRight.primaryPosition.x, infoRight.lineOffset );
254 ScrollToMakePositionVisible( currentCursorPositionRight, infoRight.lineHeight );
258 CursorInfo& info = mEventData->mIsLeftHandleSelected ? leftHandleInfo : rightHandleInfo;
260 const Vector2 currentCursorPosition( info.primaryPosition.x, info.lineOffset );
261 ScrollToMakePositionVisible( currentCursorPosition, info.lineHeight );
266 if( mEventData->mUpdateLeftSelectionPosition )
268 UpdateSelectionHandle( LEFT_SELECTION_HANDLE,
272 mEventData->mDecoratorUpdated = true;
273 mEventData->mUpdateLeftSelectionPosition = false;
276 if( mEventData->mUpdateRightSelectionPosition )
278 UpdateSelectionHandle( RIGHT_SELECTION_HANDLE,
282 mEventData->mDecoratorUpdated = true;
283 mEventData->mUpdateRightSelectionPosition = false;
286 if( mEventData->mUpdateHighlightBox )
288 RepositionSelectionHandles();
290 mEventData->mUpdateLeftSelectionPosition = false;
291 mEventData->mUpdateRightSelectionPosition = false;
292 mEventData->mUpdateHighlightBox = false;
293 mEventData->mIsLeftHandleSelected = false;
294 mEventData->mIsRightHandleSelected = false;
297 mEventData->mScrollAfterUpdatePosition = false;
300 if( mEventData->mUpdateInputStyle )
302 // Keep a copy of the current input style.
303 InputStyle currentInputStyle;
304 currentInputStyle.Copy( mEventData->mInputStyle );
306 // Set the default style first.
307 RetrieveDefaultInputStyle( mEventData->mInputStyle );
309 // Get the character index from the cursor index.
310 const CharacterIndex styleIndex = ( mEventData->mPrimaryCursorPosition > 0u ) ? mEventData->mPrimaryCursorPosition - 1u : 0u;
312 // Retrieve the style from the style runs stored in the logical model.
313 mModel->mLogicalModel->RetrieveStyle( styleIndex, mEventData->mInputStyle );
315 // Compare if the input style has changed.
316 const bool hasInputStyleChanged = !currentInputStyle.Equal( mEventData->mInputStyle );
318 if( hasInputStyleChanged )
320 const InputStyle::Mask styleChangedMask = currentInputStyle.GetInputStyleChangeMask( mEventData->mInputStyle );
321 // Queue the input style changed signal.
322 mEventData->mInputStyleChangedQueue.PushBack( styleChangedMask );
325 mEventData->mUpdateInputStyle = false;
328 mEventData->mEventQueue.clear();
330 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::ProcessInputEvents\n" );
332 const bool decoratorUpdated = mEventData->mDecoratorUpdated;
333 mEventData->mDecoratorUpdated = false;
335 return decoratorUpdated;
338 void Controller::Impl::NotifyInputMethodContext()
340 if( mEventData && mEventData->mInputMethodContext )
342 CharacterIndex cursorPosition = GetLogicalCursorPosition();
344 const Length numberOfWhiteSpaces = GetNumberOfWhiteSpaces( 0u );
346 // Update the cursor position by removing the initial white spaces.
347 if( cursorPosition < numberOfWhiteSpaces )
353 cursorPosition -= numberOfWhiteSpaces;
356 mEventData->mInputMethodContext.SetCursorPosition( cursorPosition );
357 mEventData->mInputMethodContext.NotifyCursorPosition();
361 void Controller::Impl::NotifyInputMethodContextMultiLineStatus()
363 if ( mEventData && mEventData->mInputMethodContext )
365 Text::Layout::Engine::Type layout = mLayoutEngine.GetLayout();
366 mEventData->mInputMethodContext.NotifyTextInputMultiLine( layout == Text::Layout::Engine::MULTI_LINE_BOX );
370 CharacterIndex Controller::Impl::GetLogicalCursorPosition() const
372 CharacterIndex cursorPosition = 0u;
376 if( ( EventData::SELECTING == mEventData->mState ) ||
377 ( EventData::SELECTION_HANDLE_PANNING == mEventData->mState ) )
379 cursorPosition = std::min( mEventData->mRightSelectionPosition, mEventData->mLeftSelectionPosition );
383 cursorPosition = mEventData->mPrimaryCursorPosition;
387 return cursorPosition;
390 Length Controller::Impl::GetNumberOfWhiteSpaces( CharacterIndex index ) const
392 Length numberOfWhiteSpaces = 0u;
394 // Get the buffer to the text.
395 Character* utf32CharacterBuffer = mModel->mLogicalModel->mText.Begin();
397 const Length totalNumberOfCharacters = mModel->mLogicalModel->mText.Count();
398 for( ; index < totalNumberOfCharacters; ++index, ++numberOfWhiteSpaces )
400 if( !TextAbstraction::IsWhiteSpace( *( utf32CharacterBuffer + index ) ) )
406 return numberOfWhiteSpaces;
409 void Controller::Impl::GetText( CharacterIndex index, std::string& text ) const
411 // Get the total number of characters.
412 Length numberOfCharacters = mModel->mLogicalModel->mText.Count();
414 // Retrieve the text.
415 if( 0u != numberOfCharacters )
417 Utf32ToUtf8( mModel->mLogicalModel->mText.Begin() + index, numberOfCharacters - index, text );
421 void Controller::Impl::CalculateTextUpdateIndices( Length& numberOfCharacters )
423 mTextUpdateInfo.mParagraphCharacterIndex = 0u;
424 mTextUpdateInfo.mStartGlyphIndex = 0u;
425 mTextUpdateInfo.mStartLineIndex = 0u;
426 numberOfCharacters = 0u;
428 const Length numberOfParagraphs = mModel->mLogicalModel->mParagraphInfo.Count();
429 if( 0u == numberOfParagraphs )
431 mTextUpdateInfo.mParagraphCharacterIndex = 0u;
432 numberOfCharacters = 0u;
434 mTextUpdateInfo.mRequestedNumberOfCharacters = mTextUpdateInfo.mNumberOfCharactersToAdd - mTextUpdateInfo.mNumberOfCharactersToRemove;
436 // Nothing else to do if there are no paragraphs.
440 // Find the paragraphs to be updated.
441 Vector<ParagraphRunIndex> paragraphsToBeUpdated;
442 if( mTextUpdateInfo.mCharacterIndex >= mTextUpdateInfo.mPreviousNumberOfCharacters )
444 // Text is being added at the end of the current text.
445 if( mTextUpdateInfo.mIsLastCharacterNewParagraph )
447 // Text is being added in a new paragraph after the last character of the text.
448 mTextUpdateInfo.mParagraphCharacterIndex = mTextUpdateInfo.mPreviousNumberOfCharacters;
449 numberOfCharacters = 0u;
450 mTextUpdateInfo.mRequestedNumberOfCharacters = mTextUpdateInfo.mNumberOfCharactersToAdd - mTextUpdateInfo.mNumberOfCharactersToRemove;
452 mTextUpdateInfo.mStartGlyphIndex = mModel->mVisualModel->mGlyphs.Count();
453 mTextUpdateInfo.mStartLineIndex = mModel->mVisualModel->mLines.Count() - 1u;
455 // Nothing else to do;
459 paragraphsToBeUpdated.PushBack( numberOfParagraphs - 1u );
463 Length numberOfCharactersToUpdate = 0u;
464 if( mTextUpdateInfo.mFullRelayoutNeeded )
466 numberOfCharactersToUpdate = mTextUpdateInfo.mPreviousNumberOfCharacters;
470 numberOfCharactersToUpdate = ( mTextUpdateInfo.mNumberOfCharactersToRemove > 0u ) ? mTextUpdateInfo.mNumberOfCharactersToRemove : 1u;
472 mModel->mLogicalModel->FindParagraphs( mTextUpdateInfo.mCharacterIndex,
473 numberOfCharactersToUpdate,
474 paragraphsToBeUpdated );
477 if( 0u != paragraphsToBeUpdated.Count() )
479 const ParagraphRunIndex firstParagraphIndex = *( paragraphsToBeUpdated.Begin() );
480 const ParagraphRun& firstParagraph = *( mModel->mLogicalModel->mParagraphInfo.Begin() + firstParagraphIndex );
481 mTextUpdateInfo.mParagraphCharacterIndex = firstParagraph.characterRun.characterIndex;
483 ParagraphRunIndex lastParagraphIndex = *( paragraphsToBeUpdated.End() - 1u );
484 const ParagraphRun& lastParagraph = *( mModel->mLogicalModel->mParagraphInfo.Begin() + lastParagraphIndex );
486 if( ( mTextUpdateInfo.mNumberOfCharactersToRemove > 0u ) && // Some character are removed.
487 ( lastParagraphIndex < numberOfParagraphs - 1u ) && // There is a next paragraph.
488 ( ( lastParagraph.characterRun.characterIndex + lastParagraph.characterRun.numberOfCharacters ) == // The last removed character is the new paragraph character.
489 ( mTextUpdateInfo.mCharacterIndex + mTextUpdateInfo.mNumberOfCharactersToRemove ) ) )
491 // The new paragraph character of the last updated paragraph has been removed so is going to be merged with the next one.
492 const ParagraphRun& lastParagraph = *( mModel->mLogicalModel->mParagraphInfo.Begin() + lastParagraphIndex + 1u );
494 numberOfCharacters = lastParagraph.characterRun.characterIndex + lastParagraph.characterRun.numberOfCharacters - mTextUpdateInfo.mParagraphCharacterIndex;
498 numberOfCharacters = lastParagraph.characterRun.characterIndex + lastParagraph.characterRun.numberOfCharacters - mTextUpdateInfo.mParagraphCharacterIndex;
502 mTextUpdateInfo.mRequestedNumberOfCharacters = numberOfCharacters + mTextUpdateInfo.mNumberOfCharactersToAdd - mTextUpdateInfo.mNumberOfCharactersToRemove;
503 mTextUpdateInfo.mStartGlyphIndex = *( mModel->mVisualModel->mCharactersToGlyph.Begin() + mTextUpdateInfo.mParagraphCharacterIndex );
506 void Controller::Impl::ClearFullModelData( OperationsMask operations )
508 if( NO_OPERATION != ( GET_LINE_BREAKS & operations ) )
510 mModel->mLogicalModel->mLineBreakInfo.Clear();
511 mModel->mLogicalModel->mParagraphInfo.Clear();
514 if( NO_OPERATION != ( GET_WORD_BREAKS & operations ) )
516 mModel->mLogicalModel->mLineBreakInfo.Clear();
519 if( NO_OPERATION != ( GET_SCRIPTS & operations ) )
521 mModel->mLogicalModel->mScriptRuns.Clear();
524 if( NO_OPERATION != ( VALIDATE_FONTS & operations ) )
526 mModel->mLogicalModel->mFontRuns.Clear();
529 if( 0u != mModel->mLogicalModel->mBidirectionalParagraphInfo.Count() )
531 if( NO_OPERATION != ( BIDI_INFO & operations ) )
533 mModel->mLogicalModel->mBidirectionalParagraphInfo.Clear();
534 mModel->mLogicalModel->mCharacterDirections.Clear();
537 if( NO_OPERATION != ( REORDER & operations ) )
539 // Free the allocated memory used to store the conversion table in the bidirectional line info run.
540 for( Vector<BidirectionalLineInfoRun>::Iterator it = mModel->mLogicalModel->mBidirectionalLineInfo.Begin(),
541 endIt = mModel->mLogicalModel->mBidirectionalLineInfo.End();
545 BidirectionalLineInfoRun& bidiLineInfo = *it;
547 free( bidiLineInfo.visualToLogicalMap );
548 bidiLineInfo.visualToLogicalMap = NULL;
550 mModel->mLogicalModel->mBidirectionalLineInfo.Clear();
554 if( NO_OPERATION != ( SHAPE_TEXT & operations ) )
556 mModel->mVisualModel->mGlyphs.Clear();
557 mModel->mVisualModel->mGlyphsToCharacters.Clear();
558 mModel->mVisualModel->mCharactersToGlyph.Clear();
559 mModel->mVisualModel->mCharactersPerGlyph.Clear();
560 mModel->mVisualModel->mGlyphsPerCharacter.Clear();
561 mModel->mVisualModel->mGlyphPositions.Clear();
564 if( NO_OPERATION != ( LAYOUT & operations ) )
566 mModel->mVisualModel->mLines.Clear();
569 if( NO_OPERATION != ( COLOR & operations ) )
571 mModel->mVisualModel->mColorIndices.Clear();
575 void Controller::Impl::ClearCharacterModelData( CharacterIndex startIndex, CharacterIndex endIndex, OperationsMask operations )
577 const CharacterIndex endIndexPlusOne = endIndex + 1u;
579 if( NO_OPERATION != ( GET_LINE_BREAKS & operations ) )
581 // Clear the line break info.
582 LineBreakInfo* lineBreakInfoBuffer = mModel->mLogicalModel->mLineBreakInfo.Begin();
584 mModel->mLogicalModel->mLineBreakInfo.Erase( lineBreakInfoBuffer + startIndex,
585 lineBreakInfoBuffer + endIndexPlusOne );
587 // Clear the paragraphs.
588 ClearCharacterRuns( startIndex,
590 mModel->mLogicalModel->mParagraphInfo );
593 if( NO_OPERATION != ( GET_WORD_BREAKS & operations ) )
595 // Clear the word break info.
596 WordBreakInfo* wordBreakInfoBuffer = mModel->mLogicalModel->mWordBreakInfo.Begin();
598 mModel->mLogicalModel->mWordBreakInfo.Erase( wordBreakInfoBuffer + startIndex,
599 wordBreakInfoBuffer + endIndexPlusOne );
602 if( NO_OPERATION != ( GET_SCRIPTS & operations ) )
604 // Clear the scripts.
605 ClearCharacterRuns( startIndex,
607 mModel->mLogicalModel->mScriptRuns );
610 if( NO_OPERATION != ( VALIDATE_FONTS & operations ) )
613 ClearCharacterRuns( startIndex,
615 mModel->mLogicalModel->mFontRuns );
618 if( 0u != mModel->mLogicalModel->mBidirectionalParagraphInfo.Count() )
620 if( NO_OPERATION != ( BIDI_INFO & operations ) )
622 // Clear the bidirectional paragraph info.
623 ClearCharacterRuns( startIndex,
625 mModel->mLogicalModel->mBidirectionalParagraphInfo );
627 // Clear the character's directions.
628 CharacterDirection* characterDirectionsBuffer = mModel->mLogicalModel->mCharacterDirections.Begin();
630 mModel->mLogicalModel->mCharacterDirections.Erase( characterDirectionsBuffer + startIndex,
631 characterDirectionsBuffer + endIndexPlusOne );
634 if( NO_OPERATION != ( REORDER & operations ) )
636 uint32_t startRemoveIndex = mModel->mLogicalModel->mBidirectionalLineInfo.Count();
637 uint32_t endRemoveIndex = startRemoveIndex;
638 ClearCharacterRuns( startIndex,
640 mModel->mLogicalModel->mBidirectionalLineInfo,
644 BidirectionalLineInfoRun* bidirectionalLineInfoBuffer = mModel->mLogicalModel->mBidirectionalLineInfo.Begin();
646 // Free the allocated memory used to store the conversion table in the bidirectional line info run.
647 for( Vector<BidirectionalLineInfoRun>::Iterator it = bidirectionalLineInfoBuffer + startRemoveIndex,
648 endIt = bidirectionalLineInfoBuffer + endRemoveIndex;
652 BidirectionalLineInfoRun& bidiLineInfo = *it;
654 free( bidiLineInfo.visualToLogicalMap );
655 bidiLineInfo.visualToLogicalMap = NULL;
658 mModel->mLogicalModel->mBidirectionalLineInfo.Erase( bidirectionalLineInfoBuffer + startRemoveIndex,
659 bidirectionalLineInfoBuffer + endRemoveIndex );
664 void Controller::Impl::ClearGlyphModelData( CharacterIndex startIndex, CharacterIndex endIndex, OperationsMask operations )
666 const CharacterIndex endIndexPlusOne = endIndex + 1u;
667 const Length numberOfCharactersRemoved = endIndexPlusOne - startIndex;
669 // Convert the character index to glyph index before deleting the character to glyph and the glyphs per character buffers.
670 GlyphIndex* charactersToGlyphBuffer = mModel->mVisualModel->mCharactersToGlyph.Begin();
671 Length* glyphsPerCharacterBuffer = mModel->mVisualModel->mGlyphsPerCharacter.Begin();
673 const GlyphIndex endGlyphIndexPlusOne = *( charactersToGlyphBuffer + endIndex ) + *( glyphsPerCharacterBuffer + endIndex );
674 const Length numberOfGlyphsRemoved = endGlyphIndexPlusOne - mTextUpdateInfo.mStartGlyphIndex;
676 if( NO_OPERATION != ( SHAPE_TEXT & operations ) )
678 // Update the character to glyph indices.
679 for( Vector<GlyphIndex>::Iterator it = charactersToGlyphBuffer + endIndexPlusOne,
680 endIt = charactersToGlyphBuffer + mModel->mVisualModel->mCharactersToGlyph.Count();
684 CharacterIndex& index = *it;
685 index -= numberOfGlyphsRemoved;
688 // Clear the character to glyph conversion table.
689 mModel->mVisualModel->mCharactersToGlyph.Erase( charactersToGlyphBuffer + startIndex,
690 charactersToGlyphBuffer + endIndexPlusOne );
692 // Clear the glyphs per character table.
693 mModel->mVisualModel->mGlyphsPerCharacter.Erase( glyphsPerCharacterBuffer + startIndex,
694 glyphsPerCharacterBuffer + endIndexPlusOne );
696 // Clear the glyphs buffer.
697 GlyphInfo* glyphsBuffer = mModel->mVisualModel->mGlyphs.Begin();
698 mModel->mVisualModel->mGlyphs.Erase( glyphsBuffer + mTextUpdateInfo.mStartGlyphIndex,
699 glyphsBuffer + endGlyphIndexPlusOne );
701 CharacterIndex* glyphsToCharactersBuffer = mModel->mVisualModel->mGlyphsToCharacters.Begin();
703 // Update the glyph to character indices.
704 for( Vector<CharacterIndex>::Iterator it = glyphsToCharactersBuffer + endGlyphIndexPlusOne,
705 endIt = glyphsToCharactersBuffer + mModel->mVisualModel->mGlyphsToCharacters.Count();
709 CharacterIndex& index = *it;
710 index -= numberOfCharactersRemoved;
713 // Clear the glyphs to characters buffer.
714 mModel->mVisualModel->mGlyphsToCharacters.Erase( glyphsToCharactersBuffer + mTextUpdateInfo.mStartGlyphIndex,
715 glyphsToCharactersBuffer + endGlyphIndexPlusOne );
717 // Clear the characters per glyph buffer.
718 Length* charactersPerGlyphBuffer = mModel->mVisualModel->mCharactersPerGlyph.Begin();
719 mModel->mVisualModel->mCharactersPerGlyph.Erase( charactersPerGlyphBuffer + mTextUpdateInfo.mStartGlyphIndex,
720 charactersPerGlyphBuffer + endGlyphIndexPlusOne );
722 // Clear the positions buffer.
723 Vector2* positionsBuffer = mModel->mVisualModel->mGlyphPositions.Begin();
724 mModel->mVisualModel->mGlyphPositions.Erase( positionsBuffer + mTextUpdateInfo.mStartGlyphIndex,
725 positionsBuffer + endGlyphIndexPlusOne );
728 if( NO_OPERATION != ( LAYOUT & operations ) )
731 uint32_t startRemoveIndex = mModel->mVisualModel->mLines.Count();
732 uint32_t endRemoveIndex = startRemoveIndex;
733 ClearCharacterRuns( startIndex,
735 mModel->mVisualModel->mLines,
739 // Will update the glyph runs.
740 startRemoveIndex = mModel->mVisualModel->mLines.Count();
741 endRemoveIndex = startRemoveIndex;
742 ClearGlyphRuns( mTextUpdateInfo.mStartGlyphIndex,
743 endGlyphIndexPlusOne - 1u,
744 mModel->mVisualModel->mLines,
748 // Set the line index from where to insert the new laid-out lines.
749 mTextUpdateInfo.mStartLineIndex = startRemoveIndex;
751 LineRun* linesBuffer = mModel->mVisualModel->mLines.Begin();
752 mModel->mVisualModel->mLines.Erase( linesBuffer + startRemoveIndex,
753 linesBuffer + endRemoveIndex );
756 if( NO_OPERATION != ( COLOR & operations ) )
758 if( 0u != mModel->mVisualModel->mColorIndices.Count() )
760 ColorIndex* colorIndexBuffer = mModel->mVisualModel->mColorIndices.Begin();
761 mModel->mVisualModel->mColorIndices.Erase( colorIndexBuffer + mTextUpdateInfo.mStartGlyphIndex,
762 colorIndexBuffer + endGlyphIndexPlusOne );
767 void Controller::Impl::ClearModelData( CharacterIndex startIndex, CharacterIndex endIndex, OperationsMask operations )
769 if( mTextUpdateInfo.mClearAll ||
770 ( ( 0u == startIndex ) &&
771 ( mTextUpdateInfo.mPreviousNumberOfCharacters == endIndex + 1u ) ) )
773 ClearFullModelData( operations );
777 // Clear the model data related with characters.
778 ClearCharacterModelData( startIndex, endIndex, operations );
780 // Clear the model data related with glyphs.
781 ClearGlyphModelData( startIndex, endIndex, operations );
784 // The estimated number of lines. Used to avoid reallocations when layouting.
785 mTextUpdateInfo.mEstimatedNumberOfLines = std::max( mModel->mVisualModel->mLines.Count(), mModel->mLogicalModel->mParagraphInfo.Count() );
787 mModel->mVisualModel->ClearCaches();
790 bool Controller::Impl::UpdateModel( OperationsMask operationsRequired )
792 DALI_LOG_INFO( gLogFilter, Debug::General, "Controller::UpdateModel\n" );
794 // Calculate the operations to be done.
795 const OperationsMask operations = static_cast<OperationsMask>( mOperationsPending & operationsRequired );
797 if( NO_OPERATION == operations )
799 // Nothing to do if no operations are pending and required.
803 Vector<Character>& srcCharacters = mModel->mLogicalModel->mText;
804 Vector<Character> displayCharacters;
805 bool useHiddenText = false;
806 if ( mHiddenInput && mEventData != NULL && !mEventData->mIsShowingPlaceholderText)
808 mHiddenInput->Substitute( srcCharacters,displayCharacters );
809 useHiddenText = true;
812 Vector<Character>& utf32Characters = useHiddenText ? displayCharacters : srcCharacters;
813 const Length numberOfCharacters = utf32Characters.Count();
815 // Index to the first character of the first paragraph to be updated.
816 CharacterIndex startIndex = 0u;
817 // Number of characters of the paragraphs to be removed.
818 Length paragraphCharacters = 0u;
820 CalculateTextUpdateIndices( paragraphCharacters );
822 // Check whether the indices for updating the text is valid
823 if ( numberOfCharacters > 0u &&
824 ( mTextUpdateInfo.mParagraphCharacterIndex >= numberOfCharacters ||
825 mTextUpdateInfo.mRequestedNumberOfCharacters > numberOfCharacters ) )
827 std::string currentText;
828 Utf32ToUtf8( mModel->mLogicalModel->mText.Begin(), numberOfCharacters, currentText );
830 DALI_LOG_ERROR( "Controller::Impl::UpdateModel: mTextUpdateInfo has invalid indices\n" );
831 DALI_LOG_ERROR( "Number of characters: %d, current text is: %s\n", numberOfCharacters, currentText.c_str() );
833 // Dump mTextUpdateInfo
834 DALI_LOG_ERROR( "Dump mTextUpdateInfo:\n" );
835 DALI_LOG_ERROR( " mTextUpdateInfo.mCharacterIndex = %u\n", mTextUpdateInfo.mCharacterIndex );
836 DALI_LOG_ERROR( " mTextUpdateInfo.mNumberOfCharactersToRemove = %u\n", mTextUpdateInfo.mNumberOfCharactersToRemove );
837 DALI_LOG_ERROR( " mTextUpdateInfo.mNumberOfCharactersToAdd = %u\n", mTextUpdateInfo.mNumberOfCharactersToAdd );
838 DALI_LOG_ERROR( " mTextUpdateInfo.mPreviousNumberOfCharacters = %u\n", mTextUpdateInfo.mPreviousNumberOfCharacters );
839 DALI_LOG_ERROR( " mTextUpdateInfo.mParagraphCharacterIndex = %u\n", mTextUpdateInfo.mParagraphCharacterIndex );
840 DALI_LOG_ERROR( " mTextUpdateInfo.mRequestedNumberOfCharacters = %u\n", mTextUpdateInfo.mRequestedNumberOfCharacters );
841 DALI_LOG_ERROR( " mTextUpdateInfo.mStartGlyphIndex = %u\n", mTextUpdateInfo.mStartGlyphIndex );
842 DALI_LOG_ERROR( " mTextUpdateInfo.mStartLineIndex = %u\n", mTextUpdateInfo.mStartLineIndex );
843 DALI_LOG_ERROR( " mTextUpdateInfo.mEstimatedNumberOfLines = %u\n", mTextUpdateInfo.mEstimatedNumberOfLines );
844 DALI_LOG_ERROR( " mTextUpdateInfo.mClearAll = %d\n", mTextUpdateInfo.mClearAll );
845 DALI_LOG_ERROR( " mTextUpdateInfo.mFullRelayoutNeeded = %d\n", mTextUpdateInfo.mFullRelayoutNeeded );
846 DALI_LOG_ERROR( " mTextUpdateInfo.mIsLastCharacterNewParagraph = %d\n", mTextUpdateInfo.mIsLastCharacterNewParagraph );
851 startIndex = mTextUpdateInfo.mParagraphCharacterIndex;
853 if( mTextUpdateInfo.mClearAll ||
854 ( 0u != paragraphCharacters ) )
856 ClearModelData( startIndex, startIndex + ( ( paragraphCharacters > 0u ) ? paragraphCharacters - 1u : 0u ), operations );
859 mTextUpdateInfo.mClearAll = false;
861 // Whether the model is updated.
862 bool updated = false;
864 Vector<LineBreakInfo>& lineBreakInfo = mModel->mLogicalModel->mLineBreakInfo;
865 const Length requestedNumberOfCharacters = mTextUpdateInfo.mRequestedNumberOfCharacters;
867 if( NO_OPERATION != ( GET_LINE_BREAKS & operations ) )
869 // Retrieves the line break info. The line break info is used to split the text in 'paragraphs' to
870 // calculate the bidirectional info for each 'paragraph'.
871 // It's also used to layout the text (where it should be a new line) or to shape the text (text in different lines
872 // is not shaped together).
873 lineBreakInfo.Resize( numberOfCharacters, TextAbstraction::LINE_NO_BREAK );
875 SetLineBreakInfo( utf32Characters,
877 requestedNumberOfCharacters,
880 // Create the paragraph info.
881 mModel->mLogicalModel->CreateParagraphInfo( startIndex,
882 requestedNumberOfCharacters );
886 Vector<WordBreakInfo>& wordBreakInfo = mModel->mLogicalModel->mWordBreakInfo;
887 if( NO_OPERATION != ( GET_WORD_BREAKS & operations ) )
889 // Retrieves the word break info. The word break info is used to layout the text (where to wrap the text in lines).
890 wordBreakInfo.Resize( numberOfCharacters, TextAbstraction::WORD_NO_BREAK );
892 SetWordBreakInfo( utf32Characters,
894 requestedNumberOfCharacters,
899 const bool getScripts = NO_OPERATION != ( GET_SCRIPTS & operations );
900 const bool validateFonts = NO_OPERATION != ( VALIDATE_FONTS & operations );
902 Vector<ScriptRun>& scripts = mModel->mLogicalModel->mScriptRuns;
903 Vector<FontRun>& validFonts = mModel->mLogicalModel->mFontRuns;
905 if( getScripts || validateFonts )
907 // Validates the fonts assigned by the application or assigns default ones.
908 // It makes sure all the characters are going to be rendered by the correct font.
909 MultilanguageSupport multilanguageSupport = MultilanguageSupport::Get();
913 // Retrieves the scripts used in the text.
914 multilanguageSupport.SetScripts( utf32Characters,
916 requestedNumberOfCharacters,
922 // Validate the fonts set through the mark-up string.
923 Vector<FontDescriptionRun>& fontDescriptionRuns = mModel->mLogicalModel->mFontDescriptionRuns;
925 // Get the default font's description.
926 TextAbstraction::FontDescription defaultFontDescription;
927 TextAbstraction::PointSize26Dot6 defaultPointSize = TextAbstraction::FontClient::DEFAULT_POINT_SIZE;
929 if( IsShowingPlaceholderText() && mEventData && ( NULL != mEventData->mPlaceholderFont ) )
931 // If the placeholder font is set specifically, only placeholder font is changed.
932 defaultFontDescription = mEventData->mPlaceholderFont->mFontDescription;
933 if( mEventData->mPlaceholderFont->sizeDefined )
935 defaultPointSize = mEventData->mPlaceholderFont->mDefaultPointSize * 64u;
938 else if( NULL != mFontDefaults )
940 // Set the normal font and the placeholder font.
941 defaultFontDescription = mFontDefaults->mFontDescription;
943 if( mTextFitEnabled )
945 defaultPointSize = mFontDefaults->mFitPointSize * 64u;
949 defaultPointSize = mFontDefaults->mDefaultPointSize * 64u;
953 // Validates the fonts. If there is a character with no assigned font it sets a default one.
954 // After this call, fonts are validated.
955 multilanguageSupport.ValidateFonts( utf32Characters,
958 defaultFontDescription,
961 requestedNumberOfCharacters,
967 Vector<Character> mirroredUtf32Characters;
968 bool textMirrored = false;
969 const Length numberOfParagraphs = mModel->mLogicalModel->mParagraphInfo.Count();
970 if( NO_OPERATION != ( BIDI_INFO & operations ) )
972 Vector<BidirectionalParagraphInfoRun>& bidirectionalInfo = mModel->mLogicalModel->mBidirectionalParagraphInfo;
973 bidirectionalInfo.Reserve( numberOfParagraphs );
975 // Calculates the bidirectional info for the whole paragraph if it contains right to left scripts.
976 SetBidirectionalInfo( utf32Characters,
980 requestedNumberOfCharacters,
982 mModel->mMatchSystemLanguageDirection,
985 if( 0u != bidirectionalInfo.Count() )
987 // Only set the character directions if there is right to left characters.
988 Vector<CharacterDirection>& directions = mModel->mLogicalModel->mCharacterDirections;
989 GetCharactersDirection( bidirectionalInfo,
992 requestedNumberOfCharacters,
995 // This paragraph has right to left text. Some characters may need to be mirrored.
996 // TODO: consider if the mirrored string can be stored as well.
998 textMirrored = GetMirroredText( utf32Characters,
1002 requestedNumberOfCharacters,
1003 mirroredUtf32Characters );
1007 // There is no right to left characters. Clear the directions vector.
1008 mModel->mLogicalModel->mCharacterDirections.Clear();
1013 Vector<GlyphInfo>& glyphs = mModel->mVisualModel->mGlyphs;
1014 Vector<CharacterIndex>& glyphsToCharactersMap = mModel->mVisualModel->mGlyphsToCharacters;
1015 Vector<Length>& charactersPerGlyph = mModel->mVisualModel->mCharactersPerGlyph;
1016 Vector<GlyphIndex> newParagraphGlyphs;
1017 newParagraphGlyphs.Reserve( numberOfParagraphs );
1019 const Length currentNumberOfGlyphs = glyphs.Count();
1020 if( NO_OPERATION != ( SHAPE_TEXT & operations ) )
1022 const Vector<Character>& textToShape = textMirrored ? mirroredUtf32Characters : utf32Characters;
1024 ShapeText( textToShape,
1029 mTextUpdateInfo.mStartGlyphIndex,
1030 requestedNumberOfCharacters,
1032 glyphsToCharactersMap,
1034 newParagraphGlyphs );
1036 // Create the 'number of glyphs' per character and the glyph to character conversion tables.
1037 mModel->mVisualModel->CreateGlyphsPerCharacterTable( startIndex, mTextUpdateInfo.mStartGlyphIndex, requestedNumberOfCharacters );
1038 mModel->mVisualModel->CreateCharacterToGlyphTable( startIndex, mTextUpdateInfo.mStartGlyphIndex, requestedNumberOfCharacters );
1042 const Length numberOfGlyphs = glyphs.Count() - currentNumberOfGlyphs;
1044 if( NO_OPERATION != ( GET_GLYPH_METRICS & operations ) )
1046 GlyphInfo* glyphsBuffer = glyphs.Begin();
1047 mMetrics->GetGlyphMetrics( glyphsBuffer + mTextUpdateInfo.mStartGlyphIndex, numberOfGlyphs );
1049 // Update the width and advance of all new paragraph characters.
1050 for( Vector<GlyphIndex>::ConstIterator it = newParagraphGlyphs.Begin(), endIt = newParagraphGlyphs.End(); it != endIt; ++it )
1052 const GlyphIndex index = *it;
1053 GlyphInfo& glyph = *( glyphsBuffer + index );
1055 glyph.xBearing = 0.f;
1057 glyph.advance = 0.f;
1062 if( NO_OPERATION != ( COLOR & operations ) )
1064 // Set the color runs in glyphs.
1065 SetColorSegmentationInfo( mModel->mLogicalModel->mColorRuns,
1066 mModel->mVisualModel->mCharactersToGlyph,
1067 mModel->mVisualModel->mGlyphsPerCharacter,
1069 mTextUpdateInfo.mStartGlyphIndex,
1070 requestedNumberOfCharacters,
1071 mModel->mVisualModel->mColors,
1072 mModel->mVisualModel->mColorIndices );
1077 if( ( NULL != mEventData ) &&
1078 mEventData->mPreEditFlag &&
1079 ( 0u != mModel->mVisualModel->mCharactersToGlyph.Count() ) )
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 // TODO: At the moment the underline runs are only for pre-edit.
1095 mModel->mVisualModel->mUnderlineRuns.PushBack( underlineRun );
1098 // The estimated number of lines. Used to avoid reallocations when layouting.
1099 mTextUpdateInfo.mEstimatedNumberOfLines = std::max( mModel->mVisualModel->mLines.Count(), mModel->mLogicalModel->mParagraphInfo.Count() );
1101 // Set the previous number of characters for the next time the text is updated.
1102 mTextUpdateInfo.mPreviousNumberOfCharacters = numberOfCharacters;
1107 void Controller::Impl::RetrieveDefaultInputStyle( InputStyle& inputStyle )
1109 // Sets the default text's color.
1110 inputStyle.textColor = mTextColor;
1111 inputStyle.isDefaultColor = true;
1113 inputStyle.familyName.clear();
1114 inputStyle.weight = TextAbstraction::FontWeight::NORMAL;
1115 inputStyle.width = TextAbstraction::FontWidth::NORMAL;
1116 inputStyle.slant = TextAbstraction::FontSlant::NORMAL;
1117 inputStyle.size = 0.f;
1119 inputStyle.lineSpacing = 0.f;
1121 inputStyle.underlineProperties.clear();
1122 inputStyle.shadowProperties.clear();
1123 inputStyle.embossProperties.clear();
1124 inputStyle.outlineProperties.clear();
1126 inputStyle.isFamilyDefined = false;
1127 inputStyle.isWeightDefined = false;
1128 inputStyle.isWidthDefined = false;
1129 inputStyle.isSlantDefined = false;
1130 inputStyle.isSizeDefined = false;
1132 inputStyle.isLineSpacingDefined = false;
1134 inputStyle.isUnderlineDefined = false;
1135 inputStyle.isShadowDefined = false;
1136 inputStyle.isEmbossDefined = false;
1137 inputStyle.isOutlineDefined = false;
1139 // Sets the default font's family name, weight, width, slant and size.
1142 if( mFontDefaults->familyDefined )
1144 inputStyle.familyName = mFontDefaults->mFontDescription.family;
1145 inputStyle.isFamilyDefined = true;
1148 if( mFontDefaults->weightDefined )
1150 inputStyle.weight = mFontDefaults->mFontDescription.weight;
1151 inputStyle.isWeightDefined = true;
1154 if( mFontDefaults->widthDefined )
1156 inputStyle.width = mFontDefaults->mFontDescription.width;
1157 inputStyle.isWidthDefined = true;
1160 if( mFontDefaults->slantDefined )
1162 inputStyle.slant = mFontDefaults->mFontDescription.slant;
1163 inputStyle.isSlantDefined = true;
1166 if( mFontDefaults->sizeDefined )
1168 inputStyle.size = mFontDefaults->mDefaultPointSize;
1169 inputStyle.isSizeDefined = true;
1174 float Controller::Impl::GetDefaultFontLineHeight()
1176 FontId defaultFontId = 0u;
1177 if( NULL == mFontDefaults )
1179 TextAbstraction::FontDescription fontDescription;
1180 defaultFontId = mFontClient.GetFontId( fontDescription );
1184 defaultFontId = mFontDefaults->GetFontId( mFontClient );
1187 Text::FontMetrics fontMetrics;
1188 mMetrics->GetFontMetrics( defaultFontId, fontMetrics );
1190 return( fontMetrics.ascender - fontMetrics.descender );
1193 void Controller::Impl::OnCursorKeyEvent( const Event& event )
1195 if( NULL == mEventData || !IsShowingRealText() )
1197 // Nothing to do if there is no text input.
1201 int keyCode = event.p1.mInt;
1202 bool isShiftModifier = event.p2.mBool;
1204 CharacterIndex previousPrimaryCursorPosition = mEventData->mPrimaryCursorPosition;
1206 if( Dali::DALI_KEY_CURSOR_LEFT == keyCode )
1208 if( mEventData->mPrimaryCursorPosition > 0u )
1210 if ( !isShiftModifier && mEventData->mDecorator->IsHighlightVisible() )
1212 mEventData->mPrimaryCursorPosition = std::min(mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition);
1216 mEventData->mPrimaryCursorPosition = CalculateNewCursorIndex( mEventData->mPrimaryCursorPosition - 1u );
1220 else if( Dali::DALI_KEY_CURSOR_RIGHT == keyCode )
1222 if( mModel->mLogicalModel->mText.Count() > mEventData->mPrimaryCursorPosition )
1224 if ( !isShiftModifier && mEventData->mDecorator->IsHighlightVisible() )
1226 mEventData->mPrimaryCursorPosition = std::max(mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition);
1230 mEventData->mPrimaryCursorPosition = CalculateNewCursorIndex( mEventData->mPrimaryCursorPosition );
1234 else if( Dali::DALI_KEY_CURSOR_UP == keyCode && !isShiftModifier )
1236 // Ignore Shift-Up for text selection for now.
1238 // Get first the line index of the current cursor position index.
1239 CharacterIndex characterIndex = 0u;
1241 if( mEventData->mPrimaryCursorPosition > 0u )
1243 characterIndex = mEventData->mPrimaryCursorPosition - 1u;
1246 const LineIndex lineIndex = mModel->mVisualModel->GetLineOfCharacter( characterIndex );
1247 const LineIndex previousLineIndex = ( lineIndex > 0 ? lineIndex - 1u : lineIndex );
1249 // Retrieve the cursor position info.
1250 CursorInfo cursorInfo;
1251 GetCursorPosition( mEventData->mPrimaryCursorPosition,
1254 // Get the line above.
1255 const LineRun& line = *( mModel->mVisualModel->mLines.Begin() + previousLineIndex );
1257 // Get the next hit 'y' point.
1258 const float hitPointY = cursorInfo.lineOffset - 0.5f * ( line.ascender - line.descender );
1260 // Use the cursor hook position 'x' and the next hit 'y' position to calculate the new cursor index.
1261 bool matchedCharacter = false;
1262 mEventData->mPrimaryCursorPosition = Text::GetClosestCursorIndex( mModel->mVisualModel,
1263 mModel->mLogicalModel,
1265 mEventData->mCursorHookPositionX,
1267 CharacterHitTest::TAP,
1270 else if( Dali::DALI_KEY_CURSOR_DOWN == keyCode && !isShiftModifier )
1272 // Ignore Shift-Down for text selection for now.
1274 // Get first the line index of the current cursor position index.
1275 CharacterIndex characterIndex = 0u;
1277 if( mEventData->mPrimaryCursorPosition > 0u )
1279 characterIndex = mEventData->mPrimaryCursorPosition - 1u;
1282 const LineIndex lineIndex = mModel->mVisualModel->GetLineOfCharacter( characterIndex );
1284 if( lineIndex + 1u < mModel->mVisualModel->mLines.Count() )
1286 // Retrieve the cursor position info.
1287 CursorInfo cursorInfo;
1288 GetCursorPosition( mEventData->mPrimaryCursorPosition,
1291 // Get the line below.
1292 const LineRun& line = *( mModel->mVisualModel->mLines.Begin() + lineIndex + 1u );
1294 // Get the next hit 'y' point.
1295 const float hitPointY = cursorInfo.lineOffset + cursorInfo.lineHeight + 0.5f * ( line.ascender - line.descender );
1297 // Use the cursor hook position 'x' and the next hit 'y' position to calculate the new cursor index.
1298 bool matchedCharacter = false;
1299 mEventData->mPrimaryCursorPosition = Text::GetClosestCursorIndex( mModel->mVisualModel,
1300 mModel->mLogicalModel,
1302 mEventData->mCursorHookPositionX,
1304 CharacterHitTest::TAP,
1309 if ( !isShiftModifier && mEventData->mState != EventData::SELECTING )
1311 // Update selection position after moving the cursor
1312 mEventData->mLeftSelectionPosition = mEventData->mPrimaryCursorPosition;
1313 mEventData->mRightSelectionPosition = mEventData->mPrimaryCursorPosition;
1316 if ( isShiftModifier && IsShowingRealText() && mEventData->mShiftSelectionFlag )
1318 // Handle text selection
1319 bool selecting = false;
1321 if ( Dali::DALI_KEY_CURSOR_LEFT == keyCode || Dali::DALI_KEY_CURSOR_RIGHT == keyCode )
1323 // Shift-Left/Right to select the text
1324 int cursorPositionDelta = mEventData->mPrimaryCursorPosition - previousPrimaryCursorPosition;
1325 if ( cursorPositionDelta > 0 || mEventData->mRightSelectionPosition > 0u ) // Check the boundary
1327 mEventData->mRightSelectionPosition += cursorPositionDelta;
1331 else if ( mEventData->mLeftSelectionPosition != mEventData->mRightSelectionPosition )
1333 // Show no grab handles and text highlight if Shift-Up/Down pressed but no selected text
1339 // Notify the cursor position to the InputMethodContext.
1340 if( mEventData->mInputMethodContext )
1342 mEventData->mInputMethodContext.SetCursorPosition( mEventData->mPrimaryCursorPosition );
1343 mEventData->mInputMethodContext.NotifyCursorPosition();
1346 ChangeState( EventData::SELECTING );
1348 mEventData->mUpdateLeftSelectionPosition = true;
1349 mEventData->mUpdateRightSelectionPosition = true;
1350 mEventData->mUpdateGrabHandlePosition = true;
1351 mEventData->mUpdateHighlightBox = true;
1353 // Hide the text selection popup if select the text using keyboard instead of moving grab handles
1354 if( mEventData->mGrabHandlePopupEnabled )
1356 mEventData->mDecorator->SetPopupActive( false );
1362 // Handle normal cursor move
1363 ChangeState( EventData::EDITING );
1364 mEventData->mUpdateCursorPosition = true;
1367 mEventData->mUpdateInputStyle = true;
1368 mEventData->mScrollAfterUpdatePosition = true;
1371 void Controller::Impl::OnTapEvent( const Event& event )
1373 if( NULL != mEventData )
1375 const unsigned int tapCount = event.p1.mUint;
1377 if( 1u == tapCount )
1379 if( IsShowingRealText() )
1381 // Convert from control's coords to text's coords.
1382 const float xPosition = event.p2.mFloat - mModel->mScrollPosition.x;
1383 const float yPosition = event.p3.mFloat - mModel->mScrollPosition.y;
1385 // Keep the tap 'x' position. Used to move the cursor.
1386 mEventData->mCursorHookPositionX = xPosition;
1388 // Whether to touch point hits on a glyph.
1389 bool matchedCharacter = false;
1390 mEventData->mPrimaryCursorPosition = Text::GetClosestCursorIndex( mModel->mVisualModel,
1391 mModel->mLogicalModel,
1395 CharacterHitTest::TAP,
1398 // When the cursor position is changing, delay cursor blinking
1399 mEventData->mDecorator->DelayCursorBlink();
1403 mEventData->mPrimaryCursorPosition = 0u;
1406 // Update selection position after tapping
1407 mEventData->mLeftSelectionPosition = mEventData->mPrimaryCursorPosition;
1408 mEventData->mRightSelectionPosition = mEventData->mPrimaryCursorPosition;
1410 mEventData->mUpdateCursorPosition = true;
1411 mEventData->mUpdateGrabHandlePosition = true;
1412 mEventData->mScrollAfterUpdatePosition = true;
1413 mEventData->mUpdateInputStyle = true;
1415 // Notify the cursor position to the InputMethodContext.
1416 if( mEventData->mInputMethodContext )
1418 mEventData->mInputMethodContext.SetCursorPosition( mEventData->mPrimaryCursorPosition );
1419 mEventData->mInputMethodContext.NotifyCursorPosition();
1422 else if( 2u == tapCount )
1424 if( mEventData->mSelectionEnabled )
1426 // Convert from control's coords to text's coords.
1427 const float xPosition = event.p2.mFloat - mModel->mScrollPosition.x;
1428 const float yPosition = event.p3.mFloat - mModel->mScrollPosition.y;
1430 // Calculates the logical position from the x,y coords.
1431 RepositionSelectionHandles( xPosition,
1433 mEventData->mDoubleTapAction );
1439 void Controller::Impl::OnPanEvent( const Event& event )
1441 if( NULL == mEventData )
1443 // Nothing to do if there is no text input.
1447 const bool isHorizontalScrollEnabled = mEventData->mDecorator->IsHorizontalScrollEnabled();
1448 const bool isVerticalScrollEnabled = mEventData->mDecorator->IsVerticalScrollEnabled();
1450 if( !isHorizontalScrollEnabled && !isVerticalScrollEnabled )
1452 // Nothing to do if scrolling is not enabled.
1456 const int state = event.p1.mInt;
1460 case Gesture::Started:
1462 // Will remove the cursor, handles or text's popup, ...
1463 ChangeState( EventData::TEXT_PANNING );
1466 case Gesture::Continuing:
1468 const Vector2& layoutSize = mModel->mVisualModel->GetLayoutSize();
1469 const Vector2 currentScroll = mModel->mScrollPosition;
1471 if( isHorizontalScrollEnabled )
1473 const float displacementX = event.p2.mFloat;
1474 mModel->mScrollPosition.x += displacementX;
1476 ClampHorizontalScroll( layoutSize );
1479 if( isVerticalScrollEnabled )
1481 const float displacementY = event.p3.mFloat;
1482 mModel->mScrollPosition.y += displacementY;
1484 ClampVerticalScroll( layoutSize );
1487 mEventData->mDecorator->UpdatePositions( mModel->mScrollPosition - currentScroll );
1490 case Gesture::Finished:
1491 case Gesture::Cancelled: // FALLTHROUGH
1493 // Will go back to the previous state to show the cursor, handles, the text's popup, ...
1494 ChangeState( mEventData->mPreviousState );
1502 void Controller::Impl::OnLongPressEvent( const Event& event )
1504 DALI_LOG_INFO( gLogFilter, Debug::General, "Controller::OnLongPressEvent\n" );
1506 if( !IsShowingRealText() && ( EventData::EDITING == mEventData->mState ) )
1508 ChangeState( EventData::EDITING_WITH_POPUP );
1509 mEventData->mDecoratorUpdated = true;
1510 mEventData->mUpdateInputStyle = true;
1514 if( mEventData->mSelectionEnabled )
1516 // Convert from control's coords to text's coords.
1517 const float xPosition = event.p2.mFloat - mModel->mScrollPosition.x;
1518 const float yPosition = event.p3.mFloat - mModel->mScrollPosition.y;
1520 // Calculates the logical position from the x,y coords.
1521 RepositionSelectionHandles( xPosition,
1523 mEventData->mLongPressAction );
1528 void Controller::Impl::OnHandleEvent( const Event& event )
1530 if( NULL == mEventData )
1532 // Nothing to do if there is no text input.
1536 const unsigned int state = event.p1.mUint;
1537 const bool handleStopScrolling = ( HANDLE_STOP_SCROLLING == state );
1538 const bool isSmoothHandlePanEnabled = mEventData->mDecorator->IsSmoothHandlePanEnabled();
1540 if( HANDLE_PRESSED == state )
1542 // Convert from decorator's coords to text's coords.
1543 const float xPosition = event.p2.mFloat - mModel->mScrollPosition.x;
1544 const float yPosition = event.p3.mFloat - mModel->mScrollPosition.y;
1546 // Need to calculate the handle's new position.
1547 bool matchedCharacter = false;
1548 const CharacterIndex handleNewPosition = Text::GetClosestCursorIndex( mModel->mVisualModel,
1549 mModel->mLogicalModel,
1553 CharacterHitTest::SCROLL,
1556 if( Event::GRAB_HANDLE_EVENT == event.type )
1558 ChangeState ( EventData::GRAB_HANDLE_PANNING );
1560 if( handleNewPosition != mEventData->mPrimaryCursorPosition )
1562 // Updates the cursor position if the handle's new position is different than the current one.
1563 mEventData->mUpdateCursorPosition = true;
1564 // Does not update the grab handle position if the smooth panning is enabled. (The decorator does it smooth).
1565 mEventData->mUpdateGrabHandlePosition = !isSmoothHandlePanEnabled;
1566 mEventData->mPrimaryCursorPosition = handleNewPosition;
1569 // 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.
1570 mEventData->mDecoratorUpdated = isSmoothHandlePanEnabled;
1572 else if( Event::LEFT_SELECTION_HANDLE_EVENT == event.type )
1574 ChangeState ( EventData::SELECTION_HANDLE_PANNING );
1576 if( ( handleNewPosition != mEventData->mLeftSelectionPosition ) &&
1577 ( handleNewPosition != mEventData->mRightSelectionPosition ) )
1579 // Updates the highlight box if the handle's new position is different than the current one.
1580 mEventData->mUpdateHighlightBox = true;
1581 // Does not update the selection handle position if the smooth panning is enabled. (The decorator does it smooth).
1582 mEventData->mUpdateLeftSelectionPosition = !isSmoothHandlePanEnabled;
1583 mEventData->mLeftSelectionPosition = handleNewPosition;
1586 // 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.
1587 mEventData->mDecoratorUpdated = isSmoothHandlePanEnabled;
1589 // Will define the order to scroll the text to match the handle position.
1590 mEventData->mIsLeftHandleSelected = true;
1591 mEventData->mIsRightHandleSelected = false;
1593 else if( Event::RIGHT_SELECTION_HANDLE_EVENT == event.type )
1595 ChangeState ( EventData::SELECTION_HANDLE_PANNING );
1597 if( ( handleNewPosition != mEventData->mRightSelectionPosition ) &&
1598 ( handleNewPosition != mEventData->mLeftSelectionPosition ) )
1600 // Updates the highlight box if the handle's new position is different than the current one.
1601 mEventData->mUpdateHighlightBox = true;
1602 // Does not update the selection handle position if the smooth panning is enabled. (The decorator does it smooth).
1603 mEventData->mUpdateRightSelectionPosition = !isSmoothHandlePanEnabled;
1604 mEventData->mRightSelectionPosition = handleNewPosition;
1607 // 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.
1608 mEventData->mDecoratorUpdated = isSmoothHandlePanEnabled;
1610 // Will define the order to scroll the text to match the handle position.
1611 mEventData->mIsLeftHandleSelected = false;
1612 mEventData->mIsRightHandleSelected = true;
1614 } // end ( HANDLE_PRESSED == state )
1615 else if( ( HANDLE_RELEASED == state ) ||
1616 handleStopScrolling )
1618 CharacterIndex handlePosition = 0u;
1619 if( handleStopScrolling || isSmoothHandlePanEnabled )
1621 // Convert from decorator's coords to text's coords.
1622 const float xPosition = event.p2.mFloat - mModel->mScrollPosition.x;
1623 const float yPosition = event.p3.mFloat - mModel->mScrollPosition.y;
1625 bool matchedCharacter = false;
1626 handlePosition = Text::GetClosestCursorIndex( mModel->mVisualModel,
1627 mModel->mLogicalModel,
1631 CharacterHitTest::SCROLL,
1635 if( Event::GRAB_HANDLE_EVENT == event.type )
1637 mEventData->mUpdateCursorPosition = true;
1638 mEventData->mUpdateGrabHandlePosition = true;
1639 mEventData->mUpdateInputStyle = true;
1641 if( !IsClipboardEmpty() )
1643 ChangeState( EventData::EDITING_WITH_PASTE_POPUP ); // Moving grabhandle will show Paste Popup
1646 if( handleStopScrolling || isSmoothHandlePanEnabled )
1648 mEventData->mScrollAfterUpdatePosition = true;
1649 mEventData->mPrimaryCursorPosition = handlePosition;
1652 else if( Event::LEFT_SELECTION_HANDLE_EVENT == event.type )
1654 ChangeState( EventData::SELECTING );
1656 mEventData->mUpdateHighlightBox = true;
1657 mEventData->mUpdateLeftSelectionPosition = true;
1658 mEventData->mUpdateRightSelectionPosition = true;
1660 if( handleStopScrolling || isSmoothHandlePanEnabled )
1662 mEventData->mScrollAfterUpdatePosition = true;
1664 if( ( handlePosition != mEventData->mRightSelectionPosition ) &&
1665 ( handlePosition != mEventData->mLeftSelectionPosition ) )
1667 mEventData->mLeftSelectionPosition = handlePosition;
1671 else if( Event::RIGHT_SELECTION_HANDLE_EVENT == event.type )
1673 ChangeState( EventData::SELECTING );
1675 mEventData->mUpdateHighlightBox = true;
1676 mEventData->mUpdateRightSelectionPosition = true;
1677 mEventData->mUpdateLeftSelectionPosition = true;
1679 if( handleStopScrolling || isSmoothHandlePanEnabled )
1681 mEventData->mScrollAfterUpdatePosition = true;
1682 if( ( handlePosition != mEventData->mRightSelectionPosition ) &&
1683 ( handlePosition != mEventData->mLeftSelectionPosition ) )
1685 mEventData->mRightSelectionPosition = handlePosition;
1690 mEventData->mDecoratorUpdated = true;
1691 } // end ( ( HANDLE_RELEASED == state ) || ( HANDLE_STOP_SCROLLING == state ) )
1692 else if( HANDLE_SCROLLING == state )
1694 const float xSpeed = event.p2.mFloat;
1695 const float ySpeed = event.p3.mFloat;
1696 const Vector2& layoutSize = mModel->mVisualModel->GetLayoutSize();
1697 const Vector2 currentScrollPosition = mModel->mScrollPosition;
1699 mModel->mScrollPosition.x += xSpeed;
1700 mModel->mScrollPosition.y += ySpeed;
1702 ClampHorizontalScroll( layoutSize );
1703 ClampVerticalScroll( layoutSize );
1705 bool endOfScroll = false;
1706 if( Vector2::ZERO == ( currentScrollPosition - mModel->mScrollPosition ) )
1708 // Notify the decorator there is no more text to scroll.
1709 // The decorator won't send more scroll events.
1710 mEventData->mDecorator->NotifyEndOfScroll();
1711 // Still need to set the position of the handle.
1715 // Set the position of the handle.
1716 const bool scrollRightDirection = xSpeed > 0.f;
1717 const bool scrollBottomDirection = ySpeed > 0.f;
1718 const bool leftSelectionHandleEvent = Event::LEFT_SELECTION_HANDLE_EVENT == event.type;
1719 const bool rightSelectionHandleEvent = Event::RIGHT_SELECTION_HANDLE_EVENT == event.type;
1721 if( Event::GRAB_HANDLE_EVENT == event.type )
1723 ChangeState( EventData::GRAB_HANDLE_PANNING );
1725 // Get the grab handle position in decorator coords.
1726 Vector2 position = mEventData->mDecorator->GetPosition( GRAB_HANDLE );
1728 if( mEventData->mDecorator->IsHorizontalScrollEnabled() )
1730 // Position the grag handle close to either the left or right edge.
1731 position.x = scrollRightDirection ? 0.f : mModel->mVisualModel->mControlSize.width;
1734 if( mEventData->mDecorator->IsVerticalScrollEnabled() )
1736 position.x = mEventData->mCursorHookPositionX;
1738 // Position the grag handle close to either the top or bottom edge.
1739 position.y = scrollBottomDirection ? 0.f : mModel->mVisualModel->mControlSize.height;
1742 // Get the new handle position.
1743 // The grab handle's position is in decorator's coords. Need to transforms to text's coords.
1744 bool matchedCharacter = false;
1745 const CharacterIndex handlePosition = Text::GetClosestCursorIndex( mModel->mVisualModel,
1746 mModel->mLogicalModel,
1748 position.x - mModel->mScrollPosition.x,
1749 position.y - mModel->mScrollPosition.y,
1750 CharacterHitTest::SCROLL,
1753 if( mEventData->mPrimaryCursorPosition != handlePosition )
1755 mEventData->mUpdateCursorPosition = true;
1756 mEventData->mUpdateGrabHandlePosition = !isSmoothHandlePanEnabled;
1757 mEventData->mScrollAfterUpdatePosition = true;
1758 mEventData->mPrimaryCursorPosition = handlePosition;
1760 mEventData->mUpdateInputStyle = mEventData->mUpdateCursorPosition;
1762 // Updates the decorator if the soft handle panning is enabled.
1763 mEventData->mDecoratorUpdated = isSmoothHandlePanEnabled;
1765 else if( leftSelectionHandleEvent || rightSelectionHandleEvent )
1767 ChangeState( EventData::SELECTION_HANDLE_PANNING );
1769 // Get the selection handle position in decorator coords.
1770 Vector2 position = mEventData->mDecorator->GetPosition( leftSelectionHandleEvent ? Text::LEFT_SELECTION_HANDLE : Text::RIGHT_SELECTION_HANDLE );
1772 if( mEventData->mDecorator->IsHorizontalScrollEnabled() )
1774 // Position the selection handle close to either the left or right edge.
1775 position.x = scrollRightDirection ? 0.f : mModel->mVisualModel->mControlSize.width;
1778 if( mEventData->mDecorator->IsVerticalScrollEnabled() )
1780 position.x = mEventData->mCursorHookPositionX;
1782 // Position the grag handle close to either the top or bottom edge.
1783 position.y = scrollBottomDirection ? 0.f : mModel->mVisualModel->mControlSize.height;
1786 // Get the new handle position.
1787 // The selection handle's position is in decorator's coords. Need to transform to text's coords.
1788 bool matchedCharacter = false;
1789 const CharacterIndex handlePosition = Text::GetClosestCursorIndex( mModel->mVisualModel,
1790 mModel->mLogicalModel,
1792 position.x - mModel->mScrollPosition.x,
1793 position.y - mModel->mScrollPosition.y,
1794 CharacterHitTest::SCROLL,
1797 if( leftSelectionHandleEvent )
1799 const bool differentHandles = ( mEventData->mLeftSelectionPosition != handlePosition ) && ( mEventData->mRightSelectionPosition != handlePosition );
1801 if( differentHandles || endOfScroll )
1803 mEventData->mUpdateHighlightBox = true;
1804 mEventData->mUpdateLeftSelectionPosition = !isSmoothHandlePanEnabled;
1805 mEventData->mUpdateRightSelectionPosition = isSmoothHandlePanEnabled;
1806 mEventData->mLeftSelectionPosition = handlePosition;
1811 const bool differentHandles = ( mEventData->mRightSelectionPosition != handlePosition ) && ( mEventData->mLeftSelectionPosition != handlePosition );
1812 if( differentHandles || endOfScroll )
1814 mEventData->mUpdateHighlightBox = true;
1815 mEventData->mUpdateRightSelectionPosition = !isSmoothHandlePanEnabled;
1816 mEventData->mUpdateLeftSelectionPosition = isSmoothHandlePanEnabled;
1817 mEventData->mRightSelectionPosition = handlePosition;
1821 if( mEventData->mUpdateLeftSelectionPosition || mEventData->mUpdateRightSelectionPosition )
1823 RepositionSelectionHandles();
1825 mEventData->mScrollAfterUpdatePosition = !isSmoothHandlePanEnabled;
1828 mEventData->mDecoratorUpdated = true;
1829 } // end ( HANDLE_SCROLLING == state )
1832 void Controller::Impl::OnSelectEvent( const Event& event )
1834 if( NULL == mEventData )
1836 // Nothing to do if there is no text.
1840 if( mEventData->mSelectionEnabled )
1842 // Convert from control's coords to text's coords.
1843 const float xPosition = event.p2.mFloat - mModel->mScrollPosition.x;
1844 const float yPosition = event.p3.mFloat - mModel->mScrollPosition.y;
1846 // Calculates the logical position from the x,y coords.
1847 RepositionSelectionHandles( xPosition,
1849 Controller::NoTextTap::HIGHLIGHT );
1853 void Controller::Impl::OnSelectAllEvent()
1855 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "OnSelectAllEvent mEventData->mSelectionEnabled%s \n", mEventData->mSelectionEnabled?"true":"false");
1857 if( NULL == mEventData )
1859 // Nothing to do if there is no text.
1863 if( mEventData->mSelectionEnabled )
1865 // Calculates the logical position from the start.
1866 RepositionSelectionHandles( 0.f - mModel->mScrollPosition.x,
1867 0.f - mModel->mScrollPosition.y,
1868 Controller::NoTextTap::HIGHLIGHT );
1870 mEventData->mLeftSelectionPosition = 0u;
1871 mEventData->mRightSelectionPosition = mModel->mLogicalModel->mText.Count();
1875 void Controller::Impl::RetrieveSelection( std::string& selectedText, bool deleteAfterRetrieval )
1877 if( mEventData->mLeftSelectionPosition == mEventData->mRightSelectionPosition )
1879 // Nothing to select if handles are in the same place.
1880 selectedText.clear();
1884 const bool handlesCrossed = mEventData->mLeftSelectionPosition > mEventData->mRightSelectionPosition;
1886 //Get start and end position of selection
1887 const CharacterIndex startOfSelectedText = handlesCrossed ? mEventData->mRightSelectionPosition : mEventData->mLeftSelectionPosition;
1888 const Length lengthOfSelectedText = ( handlesCrossed ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition ) - startOfSelectedText;
1890 Vector<Character>& utf32Characters = mModel->mLogicalModel->mText;
1891 const Length numberOfCharacters = utf32Characters.Count();
1893 // Validate the start and end selection points
1894 if( ( startOfSelectedText + lengthOfSelectedText ) <= numberOfCharacters )
1896 //Get text as a UTF8 string
1897 Utf32ToUtf8( &utf32Characters[startOfSelectedText], lengthOfSelectedText, selectedText );
1899 if( deleteAfterRetrieval ) // Only delete text if copied successfully
1901 // Keep a copy of the current input style.
1902 InputStyle currentInputStyle;
1903 currentInputStyle.Copy( mEventData->mInputStyle );
1905 // Set as input style the style of the first deleted character.
1906 mModel->mLogicalModel->RetrieveStyle( startOfSelectedText, mEventData->mInputStyle );
1908 // Compare if the input style has changed.
1909 const bool hasInputStyleChanged = !currentInputStyle.Equal( mEventData->mInputStyle );
1911 if( hasInputStyleChanged )
1913 const InputStyle::Mask styleChangedMask = currentInputStyle.GetInputStyleChangeMask( mEventData->mInputStyle );
1914 // Queue the input style changed signal.
1915 mEventData->mInputStyleChangedQueue.PushBack( styleChangedMask );
1918 mModel->mLogicalModel->UpdateTextStyleRuns( startOfSelectedText, -static_cast<int>( lengthOfSelectedText ) );
1920 // Mark the paragraphs to be updated.
1921 if( Layout::Engine::SINGLE_LINE_BOX == mLayoutEngine.GetLayout() )
1923 mTextUpdateInfo.mCharacterIndex = 0;
1924 mTextUpdateInfo.mNumberOfCharactersToRemove = mTextUpdateInfo.mPreviousNumberOfCharacters;
1925 mTextUpdateInfo.mNumberOfCharactersToAdd = mTextUpdateInfo.mPreviousNumberOfCharacters - lengthOfSelectedText;
1926 mTextUpdateInfo.mClearAll = true;
1930 mTextUpdateInfo.mCharacterIndex = startOfSelectedText;
1931 mTextUpdateInfo.mNumberOfCharactersToRemove = lengthOfSelectedText;
1934 // Delete text between handles
1935 Vector<Character>::Iterator first = utf32Characters.Begin() + startOfSelectedText;
1936 Vector<Character>::Iterator last = first + lengthOfSelectedText;
1937 utf32Characters.Erase( first, last );
1939 // Will show the cursor at the first character of the selection.
1940 mEventData->mPrimaryCursorPosition = handlesCrossed ? mEventData->mRightSelectionPosition : mEventData->mLeftSelectionPosition;
1944 // Will show the cursor at the last character of the selection.
1945 mEventData->mPrimaryCursorPosition = handlesCrossed ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition;
1948 mEventData->mDecoratorUpdated = true;
1952 void Controller::Impl::SetSelection( int start, int end )
1954 mEventData->mLeftSelectionPosition = start;
1955 mEventData->mRightSelectionPosition = end;
1956 mEventData->mUpdateCursorPosition = true;
1959 std::pair< int, int > Controller::Impl::GetSelectionIndexes() const
1961 return { mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition };
1964 void Controller::Impl::ShowClipboard()
1968 mClipboard.ShowClipboard();
1972 void Controller::Impl::HideClipboard()
1974 if( mClipboard && mClipboardHideEnabled )
1976 mClipboard.HideClipboard();
1980 void Controller::Impl::SetClipboardHideEnable(bool enable)
1982 mClipboardHideEnabled = enable;
1985 bool Controller::Impl::CopyStringToClipboard( const std::string& source )
1987 //Send string to clipboard
1988 return ( mClipboard && mClipboard.SetItem( source ) );
1991 void Controller::Impl::SendSelectionToClipboard( bool deleteAfterSending )
1993 std::string selectedText;
1994 RetrieveSelection( selectedText, deleteAfterSending );
1995 CopyStringToClipboard( selectedText );
1996 ChangeState( EventData::EDITING );
1999 void Controller::Impl::RequestGetTextFromClipboard()
2003 mClipboard.RequestItem();
2007 void Controller::Impl::RepositionSelectionHandles()
2009 CharacterIndex selectionStart = mEventData->mLeftSelectionPosition;
2010 CharacterIndex selectionEnd = mEventData->mRightSelectionPosition;
2012 if( selectionStart == selectionEnd )
2014 // Nothing to select if handles are in the same place.
2015 // So, deactive Highlight box.
2016 mEventData->mDecorator->SetHighlightActive( false );
2020 mEventData->mDecorator->ClearHighlights();
2022 const GlyphIndex* const charactersToGlyphBuffer = mModel->mVisualModel->mCharactersToGlyph.Begin();
2023 const Length* const glyphsPerCharacterBuffer = mModel->mVisualModel->mGlyphsPerCharacter.Begin();
2024 const GlyphInfo* const glyphsBuffer = mModel->mVisualModel->mGlyphs.Begin();
2025 const Vector2* const positionsBuffer = mModel->mVisualModel->mGlyphPositions.Begin();
2026 const Length* const charactersPerGlyphBuffer = mModel->mVisualModel->mCharactersPerGlyph.Begin();
2027 const CharacterIndex* const glyphToCharacterBuffer = mModel->mVisualModel->mGlyphsToCharacters.Begin();
2028 const CharacterDirection* const modelCharacterDirectionsBuffer = ( 0u != mModel->mLogicalModel->mCharacterDirections.Count() ) ? mModel->mLogicalModel->mCharacterDirections.Begin() : NULL;
2030 const bool isLastCharacter = selectionEnd >= mModel->mLogicalModel->mText.Count();
2031 const CharacterDirection startDirection = ( ( NULL == modelCharacterDirectionsBuffer ) ? false : *( modelCharacterDirectionsBuffer + selectionStart ) );
2032 const CharacterDirection endDirection = ( ( NULL == modelCharacterDirectionsBuffer ) ? false : *( modelCharacterDirectionsBuffer + ( selectionEnd - ( isLastCharacter ? 1u : 0u ) ) ) );
2034 // Swap the indices if the start is greater than the end.
2035 const bool indicesSwapped = selectionStart > selectionEnd;
2037 // Tell the decorator to flip the selection handles if needed.
2038 mEventData->mDecorator->SetSelectionHandleFlipState( indicesSwapped, startDirection, endDirection );
2040 if( indicesSwapped )
2042 std::swap( selectionStart, selectionEnd );
2045 // Get the indices to the first and last selected glyphs.
2046 const CharacterIndex selectionEndMinusOne = selectionEnd - 1u;
2047 const GlyphIndex glyphStart = *( charactersToGlyphBuffer + selectionStart );
2048 const Length numberOfGlyphs = *( glyphsPerCharacterBuffer + selectionEndMinusOne );
2049 const GlyphIndex glyphEnd = *( charactersToGlyphBuffer + selectionEndMinusOne ) + ( ( numberOfGlyphs > 0 ) ? numberOfGlyphs - 1u : 0u );
2051 // Get the lines where the glyphs are laid-out.
2052 const LineRun* lineRun = mModel->mVisualModel->mLines.Begin();
2054 LineIndex lineIndex = 0u;
2055 Length numberOfLines = 0u;
2056 mModel->mVisualModel->GetNumberOfLines( glyphStart,
2057 1u + glyphEnd - glyphStart,
2060 const LineIndex firstLineIndex = lineIndex;
2062 // Create the structure to store some selection box info.
2063 Vector<SelectionBoxInfo> selectionBoxLinesInfo;
2064 selectionBoxLinesInfo.Resize( numberOfLines );
2066 SelectionBoxInfo* selectionBoxInfo = selectionBoxLinesInfo.Begin();
2067 selectionBoxInfo->minX = MAX_FLOAT;
2068 selectionBoxInfo->maxX = MIN_FLOAT;
2070 // Keep the min and max 'x' position to calculate the size and position of the highlighed text.
2071 float minHighlightX = std::numeric_limits<float>::max();
2072 float maxHighlightX = std::numeric_limits<float>::min();
2074 Vector2 highLightPosition; // The highlight position in decorator's coords.
2076 // Retrieve the first line and get the line's vertical offset, the line's height and the index to the last glyph.
2078 // The line's vertical offset of all the lines before the line where the first glyph is laid-out.
2079 selectionBoxInfo->lineOffset = CalculateLineOffset( mModel->mVisualModel->mLines,
2082 // Transform to decorator's (control) coords.
2083 selectionBoxInfo->lineOffset += mModel->mScrollPosition.y;
2085 lineRun += firstLineIndex;
2087 // The line height is the addition of the line ascender and the line descender.
2088 // However, the line descender has a negative value, hence the subtraction.
2089 selectionBoxInfo->lineHeight = lineRun->ascender - lineRun->descender;
2091 GlyphIndex lastGlyphOfLine = lineRun->glyphRun.glyphIndex + lineRun->glyphRun.numberOfGlyphs - 1u;
2093 // Check if the first glyph is a ligature that must be broken like Latin ff, fi, or Arabic ï»», etc which needs special code.
2094 const Length numberOfCharactersStart = *( charactersPerGlyphBuffer + glyphStart );
2095 bool splitStartGlyph = ( numberOfCharactersStart > 1u ) && HasLigatureMustBreak( mModel->mLogicalModel->GetScript( selectionStart ) );
2097 // Check if the last glyph is a ligature that must be broken like Latin ff, fi, or Arabic ï»», etc which needs special code.
2098 const Length numberOfCharactersEnd = *( charactersPerGlyphBuffer + glyphEnd );
2099 bool splitEndGlyph = ( glyphStart != glyphEnd ) && ( numberOfCharactersEnd > 1u ) && HasLigatureMustBreak( mModel->mLogicalModel->GetScript( selectionEndMinusOne ) );
2101 // The number of quads of the selection box.
2102 const unsigned int numberOfQuads = 1u + ( glyphEnd - glyphStart ) + ( ( numberOfLines > 1u ) ? 2u * numberOfLines : 0u );
2103 mEventData->mDecorator->ResizeHighlightQuads( numberOfQuads );
2105 // Count the actual number of quads.
2106 unsigned int actualNumberOfQuads = 0u;
2109 // Traverse the glyphs.
2110 for( GlyphIndex index = glyphStart; index <= glyphEnd; ++index )
2112 const GlyphInfo& glyph = *( glyphsBuffer + index );
2113 const Vector2& position = *( positionsBuffer + index );
2115 if( splitStartGlyph )
2117 // 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.
2119 const float glyphAdvance = glyph.advance / static_cast<float>( numberOfCharactersStart );
2120 const CharacterIndex interGlyphIndex = selectionStart - *( glyphToCharacterBuffer + glyphStart );
2121 // Get the direction of the character.
2122 CharacterDirection isCurrentRightToLeft = false;
2123 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
2125 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + selectionStart );
2128 // The end point could be in the middle of the ligature.
2129 // Calculate the number of characters selected.
2130 const Length numberOfCharacters = ( glyphStart == glyphEnd ) ? ( selectionEnd - selectionStart ) : ( numberOfCharactersStart - interGlyphIndex );
2132 quad.x = lineRun->alignmentOffset + position.x - glyph.xBearing + mModel->mScrollPosition.x + glyphAdvance * static_cast<float>( isCurrentRightToLeft ? ( numberOfCharactersStart - interGlyphIndex - numberOfCharacters ) : interGlyphIndex );
2133 quad.y = selectionBoxInfo->lineOffset;
2134 quad.z = quad.x + static_cast<float>( numberOfCharacters ) * glyphAdvance;
2135 quad.w = selectionBoxInfo->lineOffset + selectionBoxInfo->lineHeight;
2137 // Store the min and max 'x' for each line.
2138 selectionBoxInfo->minX = std::min( selectionBoxInfo->minX, quad.x );
2139 selectionBoxInfo->maxX = std::max( selectionBoxInfo->maxX, quad.z );
2141 mEventData->mDecorator->AddHighlight( actualNumberOfQuads, quad );
2142 ++actualNumberOfQuads;
2144 splitStartGlyph = false;
2148 if( splitEndGlyph && ( index == glyphEnd ) )
2150 // 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.
2152 const float glyphAdvance = glyph.advance / static_cast<float>( numberOfCharactersEnd );
2153 const CharacterIndex interGlyphIndex = selectionEnd - *( glyphToCharacterBuffer + glyphEnd );
2154 // Get the direction of the character.
2155 CharacterDirection isCurrentRightToLeft = false;
2156 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
2158 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + selectionEnd );
2161 const Length numberOfCharacters = numberOfCharactersEnd - interGlyphIndex;
2163 quad.x = lineRun->alignmentOffset + position.x - glyph.xBearing + mModel->mScrollPosition.x + ( isCurrentRightToLeft ? ( glyphAdvance * static_cast<float>( numberOfCharacters ) ) : 0.f );
2164 quad.y = selectionBoxInfo->lineOffset;
2165 quad.z = quad.x + static_cast<float>( interGlyphIndex ) * glyphAdvance;
2166 quad.w = quad.y + selectionBoxInfo->lineHeight;
2168 // Store the min and max 'x' for each line.
2169 selectionBoxInfo->minX = std::min( selectionBoxInfo->minX, quad.x );
2170 selectionBoxInfo->maxX = std::max( selectionBoxInfo->maxX, quad.z );
2172 mEventData->mDecorator->AddHighlight( actualNumberOfQuads,
2174 ++actualNumberOfQuads;
2176 splitEndGlyph = false;
2180 quad.x = lineRun->alignmentOffset + position.x - glyph.xBearing + mModel->mScrollPosition.x;
2181 quad.y = selectionBoxInfo->lineOffset;
2182 quad.z = quad.x + glyph.advance;
2183 quad.w = quad.y + selectionBoxInfo->lineHeight;
2185 // Store the min and max 'x' for each line.
2186 selectionBoxInfo->minX = std::min( selectionBoxInfo->minX, quad.x );
2187 selectionBoxInfo->maxX = std::max( selectionBoxInfo->maxX, quad.z );
2189 mEventData->mDecorator->AddHighlight( actualNumberOfQuads,
2191 ++actualNumberOfQuads;
2193 // Whether to retrieve the next line.
2194 if( index == lastGlyphOfLine )
2197 if( lineIndex < firstLineIndex + numberOfLines )
2199 // Retrieve the next line.
2202 // Get the last glyph of the new line.
2203 lastGlyphOfLine = lineRun->glyphRun.glyphIndex + lineRun->glyphRun.numberOfGlyphs - 1u;
2205 // Keep the offset and height of the current selection box.
2206 const float currentLineOffset = selectionBoxInfo->lineOffset;
2207 const float currentLineHeight = selectionBoxInfo->lineHeight;
2209 // Get the selection box info for the next line.
2212 selectionBoxInfo->minX = MAX_FLOAT;
2213 selectionBoxInfo->maxX = MIN_FLOAT;
2215 // Update the line's vertical offset.
2216 selectionBoxInfo->lineOffset = currentLineOffset + currentLineHeight;
2218 // The line height is the addition of the line ascender and the line descender.
2219 // However, the line descender has a negative value, hence the subtraction.
2220 selectionBoxInfo->lineHeight = lineRun->ascender - lineRun->descender;
2225 // Traverses all the lines and updates the min and max 'x' positions and the total height.
2226 // The final width is calculated after 'boxifying' the selection.
2227 for( Vector<SelectionBoxInfo>::ConstIterator it = selectionBoxLinesInfo.Begin(),
2228 endIt = selectionBoxLinesInfo.End();
2232 const SelectionBoxInfo& info = *it;
2234 // Update the size of the highlighted text.
2235 highLightSize.height += info.lineHeight;
2236 minHighlightX = std::min( minHighlightX, info.minX );
2237 maxHighlightX = std::max( maxHighlightX, info.maxX );
2240 // Add extra geometry to 'boxify' the selection.
2242 if( 1u < numberOfLines )
2244 // Boxify the first line.
2245 lineRun = mModel->mVisualModel->mLines.Begin() + firstLineIndex;
2246 const SelectionBoxInfo& firstSelectionBoxLineInfo = *( selectionBoxLinesInfo.Begin() );
2248 bool boxifyBegin = ( LTR != lineRun->direction ) && ( LTR != startDirection );
2249 bool boxifyEnd = ( LTR == lineRun->direction ) && ( LTR == startDirection );
2254 quad.y = firstSelectionBoxLineInfo.lineOffset;
2255 quad.z = firstSelectionBoxLineInfo.minX;
2256 quad.w = firstSelectionBoxLineInfo.lineOffset + firstSelectionBoxLineInfo.lineHeight;
2258 // Boxify at the beginning of the line.
2259 mEventData->mDecorator->AddHighlight( actualNumberOfQuads,
2261 ++actualNumberOfQuads;
2263 // Update the size of the highlighted text.
2264 minHighlightX = 0.f;
2269 quad.x = firstSelectionBoxLineInfo.maxX;
2270 quad.y = firstSelectionBoxLineInfo.lineOffset;
2271 quad.z = mModel->mVisualModel->mControlSize.width;
2272 quad.w = firstSelectionBoxLineInfo.lineOffset + firstSelectionBoxLineInfo.lineHeight;
2274 // Boxify at the end of the line.
2275 mEventData->mDecorator->AddHighlight( actualNumberOfQuads,
2277 ++actualNumberOfQuads;
2279 // Update the size of the highlighted text.
2280 maxHighlightX = mModel->mVisualModel->mControlSize.width;
2283 // Boxify the central lines.
2284 if( 2u < numberOfLines )
2286 for( Vector<SelectionBoxInfo>::ConstIterator it = selectionBoxLinesInfo.Begin() + 1u,
2287 endIt = selectionBoxLinesInfo.End() - 1u;
2291 const SelectionBoxInfo& info = *it;
2294 quad.y = info.lineOffset;
2296 quad.w = info.lineOffset + info.lineHeight;
2298 mEventData->mDecorator->AddHighlight( actualNumberOfQuads,
2300 ++actualNumberOfQuads;
2303 quad.y = info.lineOffset;
2304 quad.z = mModel->mVisualModel->mControlSize.width;
2305 quad.w = info.lineOffset + info.lineHeight;
2307 mEventData->mDecorator->AddHighlight( actualNumberOfQuads,
2309 ++actualNumberOfQuads;
2312 // Update the size of the highlighted text.
2313 minHighlightX = 0.f;
2314 maxHighlightX = mModel->mVisualModel->mControlSize.width;
2317 // Boxify the last line.
2318 lineRun = mModel->mVisualModel->mLines.Begin() + firstLineIndex + numberOfLines - 1u;
2319 const SelectionBoxInfo& lastSelectionBoxLineInfo = *( selectionBoxLinesInfo.End() - 1u );
2321 boxifyBegin = ( LTR == lineRun->direction ) && ( LTR == endDirection );
2322 boxifyEnd = ( LTR != lineRun->direction ) && ( LTR != endDirection );
2327 quad.y = lastSelectionBoxLineInfo.lineOffset;
2328 quad.z = lastSelectionBoxLineInfo.minX;
2329 quad.w = lastSelectionBoxLineInfo.lineOffset + lastSelectionBoxLineInfo.lineHeight;
2331 // Boxify at the beginning of the line.
2332 mEventData->mDecorator->AddHighlight( actualNumberOfQuads,
2334 ++actualNumberOfQuads;
2336 // Update the size of the highlighted text.
2337 minHighlightX = 0.f;
2342 quad.x = lastSelectionBoxLineInfo.maxX;
2343 quad.y = lastSelectionBoxLineInfo.lineOffset;
2344 quad.z = mModel->mVisualModel->mControlSize.width;
2345 quad.w = lastSelectionBoxLineInfo.lineOffset + lastSelectionBoxLineInfo.lineHeight;
2347 // Boxify at the end of the line.
2348 mEventData->mDecorator->AddHighlight( actualNumberOfQuads,
2350 ++actualNumberOfQuads;
2352 // Update the size of the highlighted text.
2353 maxHighlightX = mModel->mVisualModel->mControlSize.width;
2357 // Set the actual number of quads.
2358 mEventData->mDecorator->ResizeHighlightQuads( actualNumberOfQuads );
2360 // Sets the highlight's size and position. In decorator's coords.
2361 // The highlight's height has been calculated above (before 'boxifying' the highlight).
2362 highLightSize.width = maxHighlightX - minHighlightX;
2364 highLightPosition.x = minHighlightX;
2365 const SelectionBoxInfo& firstSelectionBoxLineInfo = *( selectionBoxLinesInfo.Begin() );
2366 highLightPosition.y = firstSelectionBoxLineInfo.lineOffset;
2368 mEventData->mDecorator->SetHighLightBox( highLightPosition, highLightSize, static_cast<float>( mModel->GetOutlineWidth() ) );
2370 if( !mEventData->mDecorator->IsSmoothHandlePanEnabled() )
2372 CursorInfo primaryCursorInfo;
2373 GetCursorPosition( mEventData->mLeftSelectionPosition,
2374 primaryCursorInfo );
2376 const Vector2 primaryPosition = primaryCursorInfo.primaryPosition + mModel->mScrollPosition;
2378 mEventData->mDecorator->SetPosition( LEFT_SELECTION_HANDLE,
2380 primaryCursorInfo.lineOffset + mModel->mScrollPosition.y,
2381 primaryCursorInfo.lineHeight );
2383 CursorInfo secondaryCursorInfo;
2384 GetCursorPosition( mEventData->mRightSelectionPosition,
2385 secondaryCursorInfo );
2387 const Vector2 secondaryPosition = secondaryCursorInfo.primaryPosition + mModel->mScrollPosition;
2389 mEventData->mDecorator->SetPosition( RIGHT_SELECTION_HANDLE,
2390 secondaryPosition.x,
2391 secondaryCursorInfo.lineOffset + mModel->mScrollPosition.y,
2392 secondaryCursorInfo.lineHeight );
2395 // Set the flag to update the decorator.
2396 mEventData->mDecoratorUpdated = true;
2399 void Controller::Impl::RepositionSelectionHandles( float visualX, float visualY, Controller::NoTextTap::Action action )
2401 if( NULL == mEventData )
2403 // Nothing to do if there is no text input.
2407 if( IsShowingPlaceholderText() )
2409 // Nothing to do if there is the place-holder text.
2413 const Length numberOfGlyphs = mModel->mVisualModel->mGlyphs.Count();
2414 const Length numberOfLines = mModel->mVisualModel->mLines.Count();
2415 if( ( 0 == numberOfGlyphs ) ||
2416 ( 0 == numberOfLines ) )
2418 // Nothing to do if there is no text.
2422 // Find which word was selected
2423 CharacterIndex selectionStart( 0 );
2424 CharacterIndex selectionEnd( 0 );
2425 CharacterIndex noTextHitIndex( 0 );
2426 const bool characterHit = FindSelectionIndices( mModel->mVisualModel,
2427 mModel->mLogicalModel,
2434 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "%p selectionStart %d selectionEnd %d\n", this, selectionStart, selectionEnd );
2436 if( characterHit || ( Controller::NoTextTap::HIGHLIGHT == action ) )
2438 ChangeState( EventData::SELECTING );
2440 mEventData->mLeftSelectionPosition = selectionStart;
2441 mEventData->mRightSelectionPosition = selectionEnd;
2443 mEventData->mUpdateLeftSelectionPosition = true;
2444 mEventData->mUpdateRightSelectionPosition = true;
2445 mEventData->mUpdateHighlightBox = true;
2447 // It may happen an InputMethodContext commit event arrives before the selection event
2448 // if the InputMethodContext is in pre-edit state. The commit event will set the
2449 // mEventData->mUpdateCursorPosition flag to true. If it's not set back
2450 // to false, the highlight box won't be updated.
2451 mEventData->mUpdateCursorPosition = false;
2453 mEventData->mScrollAfterUpdatePosition = ( mEventData->mLeftSelectionPosition != mEventData->mRightSelectionPosition );
2455 // Cursor to be positioned at end of selection so if selection interrupted and edit mode restarted the cursor will be at end of selection
2456 mEventData->mPrimaryCursorPosition = std::max( mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition );
2458 else if( Controller::NoTextTap::SHOW_SELECTION_POPUP == action )
2460 // Nothing to select. i.e. a white space, out of bounds
2461 ChangeState( EventData::EDITING_WITH_POPUP );
2463 mEventData->mPrimaryCursorPosition = noTextHitIndex;
2465 mEventData->mUpdateCursorPosition = true;
2466 mEventData->mUpdateGrabHandlePosition = true;
2467 mEventData->mScrollAfterUpdatePosition = true;
2468 mEventData->mUpdateInputStyle = true;
2470 else if( Controller::NoTextTap::NO_ACTION == action )
2472 // Nothing to select. i.e. a white space, out of bounds
2473 mEventData->mPrimaryCursorPosition = noTextHitIndex;
2475 mEventData->mUpdateCursorPosition = true;
2476 mEventData->mUpdateGrabHandlePosition = true;
2477 mEventData->mScrollAfterUpdatePosition = true;
2478 mEventData->mUpdateInputStyle = true;
2482 void Controller::Impl::SetPopupButtons()
2485 * Sets the Popup buttons to be shown depending on State.
2487 * If SELECTING : CUT & COPY + ( PASTE & CLIPBOARD if content available to paste )
2489 * If EDITING_WITH_POPUP : SELECT & SELECT_ALL
2492 TextSelectionPopup::Buttons buttonsToShow = TextSelectionPopup::NONE;
2494 if( EventData::SELECTING == mEventData->mState )
2496 buttonsToShow = TextSelectionPopup::Buttons( TextSelectionPopup::CUT | TextSelectionPopup::COPY );
2498 if( !IsClipboardEmpty() )
2500 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
2501 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::CLIPBOARD ) );
2504 if( !mEventData->mAllTextSelected )
2506 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::SELECT_ALL ) );
2509 else if( EventData::EDITING_WITH_POPUP == mEventData->mState )
2511 if( mModel->mLogicalModel->mText.Count() && !IsShowingPlaceholderText() )
2513 buttonsToShow = TextSelectionPopup::Buttons( TextSelectionPopup::SELECT | TextSelectionPopup::SELECT_ALL );
2516 if( !IsClipboardEmpty() )
2518 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
2519 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::CLIPBOARD ) );
2522 else if( EventData::EDITING_WITH_PASTE_POPUP == mEventData->mState )
2524 if ( !IsClipboardEmpty() )
2526 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
2527 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::CLIPBOARD ) );
2531 mEventData->mDecorator->SetEnabledPopupButtons( buttonsToShow );
2534 void Controller::Impl::ChangeState( EventData::State newState )
2536 if( NULL == mEventData )
2538 // Nothing to do if there is no text input.
2542 DALI_LOG_INFO( gLogFilter, Debug::General, "ChangeState state:%d newstate:%d\n", mEventData->mState, newState );
2544 if( mEventData->mState != newState )
2546 mEventData->mPreviousState = mEventData->mState;
2547 mEventData->mState = newState;
2549 switch( mEventData->mState )
2551 case EventData::INACTIVE:
2553 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
2554 mEventData->mDecorator->StopCursorBlink();
2555 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
2556 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
2557 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
2558 mEventData->mDecorator->SetHighlightActive( false );
2559 mEventData->mDecorator->SetPopupActive( false );
2560 mEventData->mDecoratorUpdated = true;
2563 case EventData::INTERRUPTED:
2565 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
2566 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
2567 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
2568 mEventData->mDecorator->SetHighlightActive( false );
2569 mEventData->mDecorator->SetPopupActive( false );
2570 mEventData->mDecoratorUpdated = true;
2573 case EventData::SELECTING:
2575 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
2576 mEventData->mDecorator->StopCursorBlink();
2577 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
2578 if ( mEventData->mGrabHandleEnabled )
2580 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true );
2581 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true );
2583 mEventData->mDecorator->SetHighlightActive( true );
2584 if( mEventData->mGrabHandlePopupEnabled )
2587 mEventData->mDecorator->SetPopupActive( true );
2589 mEventData->mDecoratorUpdated = true;
2592 case EventData::EDITING:
2594 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
2595 if( mEventData->mCursorBlinkEnabled )
2597 mEventData->mDecorator->StartCursorBlink();
2599 // Grab handle is not shown until a tap is received whilst EDITING
2600 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
2601 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
2602 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
2603 mEventData->mDecorator->SetHighlightActive( false );
2604 if( mEventData->mGrabHandlePopupEnabled )
2606 mEventData->mDecorator->SetPopupActive( false );
2608 mEventData->mDecoratorUpdated = true;
2611 case EventData::EDITING_WITH_POPUP:
2613 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "EDITING_WITH_POPUP \n", newState );
2615 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
2616 if( mEventData->mCursorBlinkEnabled )
2618 mEventData->mDecorator->StartCursorBlink();
2620 if( mEventData->mSelectionEnabled )
2622 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
2623 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
2624 mEventData->mDecorator->SetHighlightActive( false );
2626 else if ( mEventData->mGrabHandleEnabled )
2628 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
2630 if( mEventData->mGrabHandlePopupEnabled )
2633 mEventData->mDecorator->SetPopupActive( true );
2635 mEventData->mDecoratorUpdated = true;
2638 case EventData::EDITING_WITH_GRAB_HANDLE:
2640 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "EDITING_WITH_GRAB_HANDLE \n", newState );
2642 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
2643 if( mEventData->mCursorBlinkEnabled )
2645 mEventData->mDecorator->StartCursorBlink();
2647 // Grab handle is not shown until a tap is received whilst EDITING
2648 if ( mEventData->mGrabHandleEnabled )
2650 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
2652 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
2653 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
2654 mEventData->mDecorator->SetHighlightActive( false );
2655 if( mEventData->mGrabHandlePopupEnabled )
2657 mEventData->mDecorator->SetPopupActive( false );
2659 mEventData->mDecoratorUpdated = true;
2662 case EventData::SELECTION_HANDLE_PANNING:
2664 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
2665 mEventData->mDecorator->StopCursorBlink();
2666 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
2667 if ( mEventData->mGrabHandleEnabled )
2669 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true );
2670 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true );
2672 mEventData->mDecorator->SetHighlightActive( true );
2673 if( mEventData->mGrabHandlePopupEnabled )
2675 mEventData->mDecorator->SetPopupActive( false );
2677 mEventData->mDecoratorUpdated = true;
2680 case EventData::GRAB_HANDLE_PANNING:
2682 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "GRAB_HANDLE_PANNING \n", newState );
2684 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
2685 if( mEventData->mCursorBlinkEnabled )
2687 mEventData->mDecorator->StartCursorBlink();
2689 if ( mEventData->mGrabHandleEnabled )
2691 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
2693 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
2694 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
2695 mEventData->mDecorator->SetHighlightActive( false );
2696 if( mEventData->mGrabHandlePopupEnabled )
2698 mEventData->mDecorator->SetPopupActive( false );
2700 mEventData->mDecoratorUpdated = true;
2703 case EventData::EDITING_WITH_PASTE_POPUP:
2705 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "EDITING_WITH_PASTE_POPUP \n", newState );
2707 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
2708 if( mEventData->mCursorBlinkEnabled )
2710 mEventData->mDecorator->StartCursorBlink();
2713 if ( mEventData->mGrabHandleEnabled )
2715 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
2717 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
2718 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
2719 mEventData->mDecorator->SetHighlightActive( false );
2721 if( mEventData->mGrabHandlePopupEnabled )
2724 mEventData->mDecorator->SetPopupActive( true );
2726 mEventData->mDecoratorUpdated = true;
2729 case EventData::TEXT_PANNING:
2731 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
2732 mEventData->mDecorator->StopCursorBlink();
2733 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
2734 if( mEventData->mDecorator->IsHandleActive( LEFT_SELECTION_HANDLE ) ||
2735 mEventData->mDecorator->IsHandleActive( RIGHT_SELECTION_HANDLE ) )
2737 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
2738 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
2739 mEventData->mDecorator->SetHighlightActive( true );
2742 if( mEventData->mGrabHandlePopupEnabled )
2744 mEventData->mDecorator->SetPopupActive( false );
2747 mEventData->mDecoratorUpdated = true;
2754 void Controller::Impl::GetCursorPosition( CharacterIndex logical,
2755 CursorInfo& cursorInfo )
2757 if( !IsShowingRealText() )
2759 // Do not want to use the place-holder text to set the cursor position.
2761 // Use the line's height of the font's family set to set the cursor's size.
2762 // If there is no font's family set, use the default font.
2763 // Use the current alignment to place the cursor at the beginning, center or end of the box.
2765 cursorInfo.lineOffset = 0.f;
2766 cursorInfo.lineHeight = GetDefaultFontLineHeight();
2767 cursorInfo.primaryCursorHeight = cursorInfo.lineHeight;
2770 if( mModel->mMatchSystemLanguageDirection )
2772 isRTL = mLayoutDirection == LayoutDirection::RIGHT_TO_LEFT;
2775 switch( mModel->mHorizontalAlignment )
2777 case Text::HorizontalAlignment::BEGIN :
2781 cursorInfo.primaryPosition.x = mModel->mVisualModel->mControlSize.width - mEventData->mDecorator->GetCursorWidth();
2785 cursorInfo.primaryPosition.x = 0.f;
2789 case Text::HorizontalAlignment::CENTER:
2791 cursorInfo.primaryPosition.x = floorf( 0.5f * mModel->mVisualModel->mControlSize.width );
2794 case Text::HorizontalAlignment::END:
2798 cursorInfo.primaryPosition.x = 0.f;
2802 cursorInfo.primaryPosition.x = mModel->mVisualModel->mControlSize.width - mEventData->mDecorator->GetCursorWidth();
2808 // Nothing else to do.
2812 const bool isMultiLine = ( Layout::Engine::MULTI_LINE_BOX == mLayoutEngine.GetLayout() );
2813 GetCursorPositionParameters parameters;
2814 parameters.visualModel = mModel->mVisualModel;
2815 parameters.logicalModel = mModel->mLogicalModel;
2816 parameters.metrics = mMetrics;
2817 parameters.logical = logical;
2818 parameters.isMultiline = isMultiLine;
2820 Text::GetCursorPosition( parameters,
2823 // Adds Outline offset.
2824 const float outlineWidth = static_cast<float>( mModel->GetOutlineWidth() );
2825 cursorInfo.primaryPosition.x += outlineWidth;
2826 cursorInfo.primaryPosition.y += outlineWidth;
2827 cursorInfo.secondaryPosition.x += outlineWidth;
2828 cursorInfo.secondaryPosition.y += outlineWidth;
2832 // If the text is editable and multi-line, the cursor position after a white space shouldn't exceed the boundaries of the text control.
2834 // Note the white spaces laid-out at the end of the line might exceed the boundaries of the control.
2835 // The reason is a wrapped line must not start with a white space so they are laid-out at the end of the line.
2837 if( 0.f > cursorInfo.primaryPosition.x )
2839 cursorInfo.primaryPosition.x = 0.f;
2842 const float edgeWidth = mModel->mVisualModel->mControlSize.width - static_cast<float>( mEventData->mDecorator->GetCursorWidth() );
2843 if( cursorInfo.primaryPosition.x > edgeWidth )
2845 cursorInfo.primaryPosition.x = edgeWidth;
2850 CharacterIndex Controller::Impl::CalculateNewCursorIndex( CharacterIndex index ) const
2852 if( NULL == mEventData )
2854 // Nothing to do if there is no text input.
2858 CharacterIndex cursorIndex = mEventData->mPrimaryCursorPosition;
2860 const GlyphIndex* const charactersToGlyphBuffer = mModel->mVisualModel->mCharactersToGlyph.Begin();
2861 const Length* const charactersPerGlyphBuffer = mModel->mVisualModel->mCharactersPerGlyph.Begin();
2863 GlyphIndex glyphIndex = *( charactersToGlyphBuffer + index );
2864 Length numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
2866 if( numberOfCharacters > 1u )
2868 const Script script = mModel->mLogicalModel->GetScript( index );
2869 if( HasLigatureMustBreak( script ) )
2871 // Prevents to jump the whole Latin ligatures like fi, ff, or Arabic ï»», ...
2872 numberOfCharacters = 1u;
2877 while( 0u == numberOfCharacters )
2880 numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
2884 if( index < mEventData->mPrimaryCursorPosition )
2886 cursorIndex -= numberOfCharacters;
2890 cursorIndex += numberOfCharacters;
2893 // Will update the cursor hook position.
2894 mEventData->mUpdateCursorHookPosition = true;
2899 void Controller::Impl::UpdateCursorPosition( const CursorInfo& cursorInfo )
2901 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "-->Controller::UpdateCursorPosition %p\n", this );
2902 if( NULL == mEventData )
2904 // Nothing to do if there is no text input.
2905 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition no event data\n" );
2909 const Vector2 cursorPosition = cursorInfo.primaryPosition + mModel->mScrollPosition;
2911 mEventData->mDecorator->SetGlyphOffset( PRIMARY_CURSOR, cursorInfo.glyphOffset );
2913 // Sets the cursor position.
2914 mEventData->mDecorator->SetPosition( PRIMARY_CURSOR,
2917 cursorInfo.primaryCursorHeight,
2918 cursorInfo.lineHeight );
2919 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Primary cursor position: %f,%f\n", cursorPosition.x, cursorPosition.y );
2921 if( mEventData->mUpdateGrabHandlePosition )
2923 // Sets the grab handle position.
2924 mEventData->mDecorator->SetPosition( GRAB_HANDLE,
2926 cursorInfo.lineOffset + mModel->mScrollPosition.y,
2927 cursorInfo.lineHeight );
2930 if( cursorInfo.isSecondaryCursor )
2932 mEventData->mDecorator->SetPosition( SECONDARY_CURSOR,
2933 cursorInfo.secondaryPosition.x + mModel->mScrollPosition.x,
2934 cursorInfo.secondaryPosition.y + mModel->mScrollPosition.y,
2935 cursorInfo.secondaryCursorHeight,
2936 cursorInfo.lineHeight );
2937 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Secondary cursor position: %f,%f\n", cursorInfo.secondaryPosition.x + mModel->mScrollPosition.x, cursorInfo.secondaryPosition.y + mModel->mScrollPosition.y );
2940 // Set which cursors are active according the state.
2941 if( EventData::IsEditingState( mEventData->mState ) || ( EventData::GRAB_HANDLE_PANNING == mEventData->mState ) )
2943 if( cursorInfo.isSecondaryCursor )
2945 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_BOTH );
2949 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
2954 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
2957 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition\n" );
2960 void Controller::Impl::UpdateSelectionHandle( HandleType handleType,
2961 const CursorInfo& cursorInfo )
2963 if( ( LEFT_SELECTION_HANDLE != handleType ) &&
2964 ( RIGHT_SELECTION_HANDLE != handleType ) )
2969 const Vector2 cursorPosition = cursorInfo.primaryPosition + mModel->mScrollPosition;
2971 // Sets the handle's position.
2972 mEventData->mDecorator->SetPosition( handleType,
2974 cursorInfo.lineOffset + mModel->mScrollPosition.y,
2975 cursorInfo.lineHeight );
2977 // If selection handle at start of the text and other at end of the text then all text is selected.
2978 const CharacterIndex startOfSelection = std::min( mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition );
2979 const CharacterIndex endOfSelection = std::max ( mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition );
2980 mEventData->mAllTextSelected = ( startOfSelection == 0 ) && ( endOfSelection == mModel->mLogicalModel->mText.Count() );
2983 void Controller::Impl::ClampHorizontalScroll( const Vector2& layoutSize )
2985 // Clamp between -space & -alignment offset.
2987 if( layoutSize.width > mModel->mVisualModel->mControlSize.width )
2989 const float space = ( layoutSize.width - mModel->mVisualModel->mControlSize.width ) + mModel->mAlignmentOffset;
2990 mModel->mScrollPosition.x = ( mModel->mScrollPosition.x < -space ) ? -space : mModel->mScrollPosition.x;
2991 mModel->mScrollPosition.x = ( mModel->mScrollPosition.x > -mModel->mAlignmentOffset ) ? -mModel->mAlignmentOffset : mModel->mScrollPosition.x;
2993 mEventData->mDecoratorUpdated = true;
2997 mModel->mScrollPosition.x = 0.f;
3001 void Controller::Impl::ClampVerticalScroll( const Vector2& layoutSize )
3003 if( Layout::Engine::SINGLE_LINE_BOX == mLayoutEngine.GetLayout() )
3005 // Nothing to do if the text is single line.
3009 // Clamp between -space & 0.
3010 if( layoutSize.height > mModel->mVisualModel->mControlSize.height )
3012 const float space = ( layoutSize.height - mModel->mVisualModel->mControlSize.height );
3013 mModel->mScrollPosition.y = ( mModel->mScrollPosition.y < -space ) ? -space : mModel->mScrollPosition.y;
3014 mModel->mScrollPosition.y = ( mModel->mScrollPosition.y > 0.f ) ? 0.f : mModel->mScrollPosition.y;
3016 mEventData->mDecoratorUpdated = true;
3020 mModel->mScrollPosition.y = 0.f;
3024 void Controller::Impl::ScrollToMakePositionVisible( const Vector2& position, float lineHeight )
3026 const float cursorWidth = mEventData->mDecorator ? static_cast<float>( mEventData->mDecorator->GetCursorWidth() ) : 0.f;
3028 // position is in actor's coords.
3029 const float positionEndX = position.x + cursorWidth;
3030 const float positionEndY = position.y + lineHeight;
3032 // Transform the position to decorator coords.
3033 const float decoratorPositionBeginX = position.x + mModel->mScrollPosition.x;
3034 const float decoratorPositionEndX = positionEndX + mModel->mScrollPosition.x;
3036 const float decoratorPositionBeginY = position.y + mModel->mScrollPosition.y;
3037 const float decoratorPositionEndY = positionEndY + mModel->mScrollPosition.y;
3039 if( decoratorPositionBeginX < 0.f )
3041 mModel->mScrollPosition.x = -position.x;
3043 else if( decoratorPositionEndX > mModel->mVisualModel->mControlSize.width )
3045 mModel->mScrollPosition.x = mModel->mVisualModel->mControlSize.width - positionEndX;
3048 if( Layout::Engine::MULTI_LINE_BOX == mLayoutEngine.GetLayout() )
3050 if( decoratorPositionBeginY < 0.f )
3052 mModel->mScrollPosition.y = -position.y;
3054 else if( decoratorPositionEndY > mModel->mVisualModel->mControlSize.height )
3056 mModel->mScrollPosition.y = mModel->mVisualModel->mControlSize.height - positionEndY;
3061 void Controller::Impl::ScrollTextToMatchCursor( const CursorInfo& cursorInfo )
3063 // Get the current cursor position in decorator coords.
3064 const Vector2& currentCursorPosition = mEventData->mDecorator->GetPosition( PRIMARY_CURSOR );
3066 const LineIndex lineIndex = mModel->mVisualModel->GetLineOfCharacter( mEventData->mPrimaryCursorPosition );
3070 // Calculate the offset to match the cursor position before the character was deleted.
3071 mModel->mScrollPosition.x = currentCursorPosition.x - cursorInfo.primaryPosition.x;
3073 //If text control has more than two lines and current line index is not last, calculate scrollpositionY
3074 if( mModel->mVisualModel->mLines.Count() > 1u && lineIndex != mModel->mVisualModel->mLines.Count() -1u )
3076 const float currentCursorGlyphOffset = mEventData->mDecorator->GetGlyphOffset( PRIMARY_CURSOR );
3077 mModel->mScrollPosition.y = currentCursorPosition.y - cursorInfo.lineOffset - currentCursorGlyphOffset;
3081 ClampHorizontalScroll( mModel->mVisualModel->GetLayoutSize() );
3082 ClampVerticalScroll( mModel->mVisualModel->GetLayoutSize() );
3084 // Makes the new cursor position visible if needed.
3085 ScrollToMakePositionVisible( cursorInfo.primaryPosition, cursorInfo.lineHeight );
3088 void Controller::Impl::RequestRelayout()
3090 if( NULL != mControlInterface )
3092 mControlInterface->RequestTextRelayout();
3098 } // namespace Toolkit