2 * Copyright (c) 2017 Samsung Electronics Co., Ltd.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
19 #include <dali-toolkit/internal/text/text-controller-impl.h>
22 #include <dali/public-api/adaptor-framework/key.h>
23 #include <dali/integration-api/debug.h>
27 #include <dali-toolkit/internal/text/bidirectional-support.h>
28 #include <dali-toolkit/internal/text/character-set-conversion.h>
29 #include <dali-toolkit/internal/text/color-segmentation.h>
30 #include <dali-toolkit/internal/text/cursor-helper-functions.h>
31 #include <dali-toolkit/internal/text/multi-language-support.h>
32 #include <dali-toolkit/internal/text/segmentation.h>
33 #include <dali-toolkit/internal/text/shaper.h>
34 #include <dali-toolkit/internal/text/text-control-interface.h>
35 #include <dali-toolkit/internal/text/text-run-container.h>
41 * @brief Struct used to calculate the selection box.
43 struct SelectionBoxInfo
51 #if defined(DEBUG_ENABLED)
52 Debug::Filter* gLogFilter = Debug::Filter::New(Debug::NoLogging, true, "LOG_TEXT_CONTROLS");
55 const float MAX_FLOAT = std::numeric_limits<float>::max();
56 const float MIN_FLOAT = std::numeric_limits<float>::min();
57 const Dali::Toolkit::Text::CharacterDirection LTR = false; ///< Left To Right direction
70 EventData::EventData( DecoratorPtr decorator )
71 : mDecorator( decorator ),
73 mPlaceholderFont( NULL ),
74 mPlaceholderTextActive(),
75 mPlaceholderTextInactive(),
76 mPlaceholderTextColor( 0.8f, 0.8f, 0.8f, 0.8f ), // This color has been published in the Public API (placeholder-properties.h).
78 mInputStyleChangedQueue(),
79 mPreviousState( INACTIVE ),
81 mPrimaryCursorPosition( 0u ),
82 mLeftSelectionPosition( 0u ),
83 mRightSelectionPosition( 0u ),
84 mPreEditStartPosition( 0u ),
86 mCursorHookPositionX( 0.f ),
87 mDoubleTapAction( Controller::NoTextTap::NO_ACTION ),
88 mLongPressAction( Controller::NoTextTap::SHOW_SELECTION_POPUP ),
89 mIsShowingPlaceholderText( false ),
90 mPreEditFlag( false ),
91 mDecoratorUpdated( false ),
92 mCursorBlinkEnabled( true ),
93 mGrabHandleEnabled( true ),
94 mGrabHandlePopupEnabled( true ),
95 mSelectionEnabled( true ),
96 mUpdateCursorHookPosition( false ),
97 mUpdateCursorPosition( false ),
98 mUpdateGrabHandlePosition( false ),
99 mUpdateLeftSelectionPosition( false ),
100 mUpdateRightSelectionPosition( false ),
101 mIsLeftHandleSelected( false ),
102 mIsRightHandleSelected( false ),
103 mUpdateHighlightBox( false ),
104 mScrollAfterUpdatePosition( false ),
105 mScrollAfterDelete( false ),
106 mAllTextSelected( false ),
107 mUpdateInputStyle( false ),
108 mPasswordInput( false ),
109 mIsPlaceholderPixelSize( false ),
110 mIsPlaceholderElideEnabled( false ),
111 mPlaceholderEllipsisFlag( false ),
112 mShiftSelectionFlag( true )
114 mImfManager = ImfManager::Get();
117 EventData::~EventData()
120 bool Controller::Impl::ProcessInputEvents()
122 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "-->Controller::ProcessInputEvents\n" );
123 if( NULL == mEventData )
125 // Nothing to do if there is no text input.
126 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::ProcessInputEvents no event data\n" );
130 if( mEventData->mDecorator )
132 for( std::vector<Event>::iterator iter = mEventData->mEventQueue.begin();
133 iter != mEventData->mEventQueue.end();
138 case Event::CURSOR_KEY_EVENT:
140 OnCursorKeyEvent( *iter );
143 case Event::TAP_EVENT:
148 case Event::LONG_PRESS_EVENT:
150 OnLongPressEvent( *iter );
153 case Event::PAN_EVENT:
158 case Event::GRAB_HANDLE_EVENT:
159 case Event::LEFT_SELECTION_HANDLE_EVENT:
160 case Event::RIGHT_SELECTION_HANDLE_EVENT: // Fall through
162 OnHandleEvent( *iter );
167 OnSelectEvent( *iter );
170 case Event::SELECT_ALL:
179 if( mEventData->mUpdateCursorPosition ||
180 mEventData->mUpdateHighlightBox )
185 // The cursor must also be repositioned after inserts into the model
186 if( mEventData->mUpdateCursorPosition )
188 // Updates the cursor position and scrolls the text to make it visible.
189 CursorInfo cursorInfo;
190 // Calculate the cursor position from the new cursor index.
191 GetCursorPosition( mEventData->mPrimaryCursorPosition,
194 if( mEventData->mUpdateCursorHookPosition )
196 // Update the cursor hook position. Used to move the cursor with the keys 'up' and 'down'.
197 mEventData->mCursorHookPositionX = cursorInfo.primaryPosition.x;
198 mEventData->mUpdateCursorHookPosition = false;
201 // Scroll first the text after delete ...
202 if( mEventData->mScrollAfterDelete )
204 ScrollTextToMatchCursor( cursorInfo );
207 // ... then, text can be scrolled to make the cursor visible.
208 if( mEventData->mScrollAfterUpdatePosition )
210 const Vector2 currentCursorPosition( cursorInfo.primaryPosition.x, cursorInfo.lineOffset );
211 ScrollToMakePositionVisible( currentCursorPosition, cursorInfo.lineHeight );
213 mEventData->mScrollAfterUpdatePosition = false;
214 mEventData->mScrollAfterDelete = false;
216 UpdateCursorPosition( cursorInfo );
218 mEventData->mDecoratorUpdated = true;
219 mEventData->mUpdateCursorPosition = false;
220 mEventData->mUpdateGrabHandlePosition = false;
224 CursorInfo leftHandleInfo;
225 CursorInfo rightHandleInfo;
227 if( mEventData->mUpdateHighlightBox )
229 GetCursorPosition( mEventData->mLeftSelectionPosition,
232 GetCursorPosition( mEventData->mRightSelectionPosition,
235 if( mEventData->mScrollAfterUpdatePosition && ( mEventData->mIsLeftHandleSelected ? mEventData->mUpdateLeftSelectionPosition : mEventData->mUpdateRightSelectionPosition ) )
237 if( mEventData->mIsLeftHandleSelected && mEventData->mIsRightHandleSelected )
239 CursorInfo& infoLeft = leftHandleInfo;
241 const Vector2 currentCursorPositionLeft( infoLeft.primaryPosition.x, infoLeft.lineOffset );
242 ScrollToMakePositionVisible( currentCursorPositionLeft, infoLeft.lineHeight );
244 CursorInfo& infoRight = rightHandleInfo;
246 const Vector2 currentCursorPositionRight( infoRight.primaryPosition.x, infoRight.lineOffset );
247 ScrollToMakePositionVisible( currentCursorPositionRight, infoRight.lineHeight );
251 CursorInfo& info = mEventData->mIsLeftHandleSelected ? leftHandleInfo : rightHandleInfo;
253 const Vector2 currentCursorPosition( info.primaryPosition.x, info.lineOffset );
254 ScrollToMakePositionVisible( currentCursorPosition, info.lineHeight );
259 if( mEventData->mUpdateLeftSelectionPosition )
261 UpdateSelectionHandle( LEFT_SELECTION_HANDLE,
265 mEventData->mDecoratorUpdated = true;
266 mEventData->mUpdateLeftSelectionPosition = false;
269 if( mEventData->mUpdateRightSelectionPosition )
271 UpdateSelectionHandle( RIGHT_SELECTION_HANDLE,
275 mEventData->mDecoratorUpdated = true;
276 mEventData->mUpdateRightSelectionPosition = false;
279 if( mEventData->mUpdateHighlightBox )
281 RepositionSelectionHandles();
283 mEventData->mUpdateLeftSelectionPosition = false;
284 mEventData->mUpdateRightSelectionPosition = false;
285 mEventData->mUpdateHighlightBox = false;
286 mEventData->mIsLeftHandleSelected = false;
287 mEventData->mIsRightHandleSelected = false;
290 mEventData->mScrollAfterUpdatePosition = false;
293 if( mEventData->mUpdateInputStyle )
295 // Keep a copy of the current input style.
296 InputStyle currentInputStyle;
297 currentInputStyle.Copy( mEventData->mInputStyle );
299 // Set the default style first.
300 RetrieveDefaultInputStyle( mEventData->mInputStyle );
302 // Get the character index from the cursor index.
303 const CharacterIndex styleIndex = ( mEventData->mPrimaryCursorPosition > 0u ) ? mEventData->mPrimaryCursorPosition - 1u : 0u;
305 // Retrieve the style from the style runs stored in the logical model.
306 mModel->mLogicalModel->RetrieveStyle( styleIndex, mEventData->mInputStyle );
308 // Compare if the input style has changed.
309 const bool hasInputStyleChanged = !currentInputStyle.Equal( mEventData->mInputStyle );
311 if( hasInputStyleChanged )
313 const InputStyle::Mask styleChangedMask = currentInputStyle.GetInputStyleChangeMask( mEventData->mInputStyle );
314 // Queue the input style changed signal.
315 mEventData->mInputStyleChangedQueue.PushBack( styleChangedMask );
318 mEventData->mUpdateInputStyle = false;
321 mEventData->mEventQueue.clear();
323 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::ProcessInputEvents\n" );
325 const bool decoratorUpdated = mEventData->mDecoratorUpdated;
326 mEventData->mDecoratorUpdated = false;
328 return decoratorUpdated;
331 void Controller::Impl::NotifyImfManager()
333 if( mEventData && mEventData->mImfManager )
335 CharacterIndex cursorPosition = GetLogicalCursorPosition();
337 const Length numberOfWhiteSpaces = GetNumberOfWhiteSpaces( 0u );
339 // Update the cursor position by removing the initial white spaces.
340 if( cursorPosition < numberOfWhiteSpaces )
346 cursorPosition -= numberOfWhiteSpaces;
349 mEventData->mImfManager.SetCursorPosition( cursorPosition );
350 mEventData->mImfManager.NotifyCursorPosition();
354 void Controller::Impl::NotifyImfMultiLineStatus()
358 Text::Layout::Engine::Type layout = mLayoutEngine.GetLayout();
359 mEventData->mImfManager.NotifyTextInputMultiLine( layout == Text::Layout::Engine::MULTI_LINE_BOX );
363 CharacterIndex Controller::Impl::GetLogicalCursorPosition() const
365 CharacterIndex cursorPosition = 0u;
369 if( ( EventData::SELECTING == mEventData->mState ) ||
370 ( EventData::SELECTION_HANDLE_PANNING == mEventData->mState ) )
372 cursorPosition = std::min( mEventData->mRightSelectionPosition, mEventData->mLeftSelectionPosition );
376 cursorPosition = mEventData->mPrimaryCursorPosition;
380 return cursorPosition;
383 Length Controller::Impl::GetNumberOfWhiteSpaces( CharacterIndex index ) const
385 Length numberOfWhiteSpaces = 0u;
387 // Get the buffer to the text.
388 Character* utf32CharacterBuffer = mModel->mLogicalModel->mText.Begin();
390 const Length totalNumberOfCharacters = mModel->mLogicalModel->mText.Count();
391 for( ; index < totalNumberOfCharacters; ++index, ++numberOfWhiteSpaces )
393 if( !TextAbstraction::IsWhiteSpace( *( utf32CharacterBuffer + index ) ) )
399 return numberOfWhiteSpaces;
402 void Controller::Impl::GetText( CharacterIndex index, std::string& text ) const
404 // Get the total number of characters.
405 Length numberOfCharacters = mModel->mLogicalModel->mText.Count();
407 // Retrieve the text.
408 if( 0u != numberOfCharacters )
410 Utf32ToUtf8( mModel->mLogicalModel->mText.Begin() + index, numberOfCharacters - index, text );
414 void Controller::Impl::CalculateTextUpdateIndices( Length& numberOfCharacters )
416 mTextUpdateInfo.mParagraphCharacterIndex = 0u;
417 mTextUpdateInfo.mStartGlyphIndex = 0u;
418 mTextUpdateInfo.mStartLineIndex = 0u;
419 numberOfCharacters = 0u;
421 const Length numberOfParagraphs = mModel->mLogicalModel->mParagraphInfo.Count();
422 if( 0u == numberOfParagraphs )
424 mTextUpdateInfo.mParagraphCharacterIndex = 0u;
425 numberOfCharacters = 0u;
427 // prevent mTextUpdateInfo.mRequestedNumberOfCharacters value underflow
428 mTextUpdateInfo.mRequestedNumberOfCharacters = ( mTextUpdateInfo.mNumberOfCharactersToAdd < mTextUpdateInfo.mNumberOfCharactersToRemove ? 0u : mTextUpdateInfo.mNumberOfCharactersToAdd - mTextUpdateInfo.mNumberOfCharactersToRemove );
430 // Nothing else to do if there are no paragraphs.
434 // Find the paragraphs to be updated.
435 Vector<ParagraphRunIndex> paragraphsToBeUpdated;
436 if( mTextUpdateInfo.mCharacterIndex >= mTextUpdateInfo.mPreviousNumberOfCharacters )
438 // Text is being added at the end of the current text.
439 if( mTextUpdateInfo.mIsLastCharacterNewParagraph )
441 // Text is being added in a new paragraph after the last character of the text.
442 mTextUpdateInfo.mParagraphCharacterIndex = mTextUpdateInfo.mPreviousNumberOfCharacters;
443 numberOfCharacters = 0u;
445 // prevent mTextUpdateInfo.mRequestedNumberOfCharacters value underflow
446 mTextUpdateInfo.mRequestedNumberOfCharacters = ( mTextUpdateInfo.mNumberOfCharactersToAdd < mTextUpdateInfo.mNumberOfCharactersToRemove ? 0u : mTextUpdateInfo.mNumberOfCharactersToAdd - mTextUpdateInfo.mNumberOfCharactersToRemove );
448 mTextUpdateInfo.mStartGlyphIndex = mModel->mVisualModel->mGlyphs.Count();
449 mTextUpdateInfo.mStartLineIndex = mModel->mVisualModel->mLines.Count() - 1u;
451 // Nothing else to do;
455 paragraphsToBeUpdated.PushBack( numberOfParagraphs - 1u );
459 Length numberOfCharactersToUpdate = 0u;
460 if( mTextUpdateInfo.mFullRelayoutNeeded )
462 numberOfCharactersToUpdate = mTextUpdateInfo.mPreviousNumberOfCharacters;
466 numberOfCharactersToUpdate = ( mTextUpdateInfo.mNumberOfCharactersToRemove > 0u ) ? mTextUpdateInfo.mNumberOfCharactersToRemove : 1u;
468 mModel->mLogicalModel->FindParagraphs( mTextUpdateInfo.mCharacterIndex,
469 numberOfCharactersToUpdate,
470 paragraphsToBeUpdated );
473 if( 0u != paragraphsToBeUpdated.Count() )
475 const ParagraphRunIndex firstParagraphIndex = *( paragraphsToBeUpdated.Begin() );
476 const ParagraphRun& firstParagraph = *( mModel->mLogicalModel->mParagraphInfo.Begin() + firstParagraphIndex );
477 mTextUpdateInfo.mParagraphCharacterIndex = firstParagraph.characterRun.characterIndex;
479 ParagraphRunIndex lastParagraphIndex = *( paragraphsToBeUpdated.End() - 1u );
480 const ParagraphRun& lastParagraph = *( mModel->mLogicalModel->mParagraphInfo.Begin() + lastParagraphIndex );
482 if( ( mTextUpdateInfo.mNumberOfCharactersToRemove > 0u ) && // Some character are removed.
483 ( lastParagraphIndex < numberOfParagraphs - 1u ) && // There is a next paragraph.
484 ( ( lastParagraph.characterRun.characterIndex + lastParagraph.characterRun.numberOfCharacters ) == // The last removed character is the new paragraph character.
485 ( mTextUpdateInfo.mCharacterIndex + mTextUpdateInfo.mNumberOfCharactersToRemove ) ) )
487 // The new paragraph character of the last updated paragraph has been removed so is going to be merged with the next one.
488 const ParagraphRun& lastParagraph = *( mModel->mLogicalModel->mParagraphInfo.Begin() + lastParagraphIndex + 1u );
490 numberOfCharacters = lastParagraph.characterRun.characterIndex + lastParagraph.characterRun.numberOfCharacters - mTextUpdateInfo.mParagraphCharacterIndex;
494 numberOfCharacters = lastParagraph.characterRun.characterIndex + lastParagraph.characterRun.numberOfCharacters - mTextUpdateInfo.mParagraphCharacterIndex;
498 // prevent mTextUpdateInfo.mRequestedNumberOfCharacters value underflow
499 if( numberOfCharacters + mTextUpdateInfo.mNumberOfCharactersToAdd < mTextUpdateInfo.mNumberOfCharactersToRemove )
501 mTextUpdateInfo.mRequestedNumberOfCharacters = 0u;
505 mTextUpdateInfo.mRequestedNumberOfCharacters = numberOfCharacters + mTextUpdateInfo.mNumberOfCharactersToAdd - mTextUpdateInfo.mNumberOfCharactersToRemove;
507 mTextUpdateInfo.mStartGlyphIndex = *( mModel->mVisualModel->mCharactersToGlyph.Begin() + mTextUpdateInfo.mParagraphCharacterIndex );
510 void Controller::Impl::ClearFullModelData( OperationsMask operations )
512 if( NO_OPERATION != ( GET_LINE_BREAKS & operations ) )
514 mModel->mLogicalModel->mLineBreakInfo.Clear();
515 mModel->mLogicalModel->mParagraphInfo.Clear();
518 if( NO_OPERATION != ( GET_WORD_BREAKS & operations ) )
520 mModel->mLogicalModel->mLineBreakInfo.Clear();
523 if( NO_OPERATION != ( GET_SCRIPTS & operations ) )
525 mModel->mLogicalModel->mScriptRuns.Clear();
528 if( NO_OPERATION != ( VALIDATE_FONTS & operations ) )
530 mModel->mLogicalModel->mFontRuns.Clear();
533 if( 0u != mModel->mLogicalModel->mBidirectionalParagraphInfo.Count() )
535 if( NO_OPERATION != ( BIDI_INFO & operations ) )
537 mModel->mLogicalModel->mBidirectionalParagraphInfo.Clear();
538 mModel->mLogicalModel->mCharacterDirections.Clear();
541 if( NO_OPERATION != ( REORDER & operations ) )
543 // Free the allocated memory used to store the conversion table in the bidirectional line info run.
544 for( Vector<BidirectionalLineInfoRun>::Iterator it = mModel->mLogicalModel->mBidirectionalLineInfo.Begin(),
545 endIt = mModel->mLogicalModel->mBidirectionalLineInfo.End();
549 BidirectionalLineInfoRun& bidiLineInfo = *it;
551 free( bidiLineInfo.visualToLogicalMap );
552 bidiLineInfo.visualToLogicalMap = NULL;
554 mModel->mLogicalModel->mBidirectionalLineInfo.Clear();
558 if( NO_OPERATION != ( SHAPE_TEXT & operations ) )
560 mModel->mVisualModel->mGlyphs.Clear();
561 mModel->mVisualModel->mGlyphsToCharacters.Clear();
562 mModel->mVisualModel->mCharactersToGlyph.Clear();
563 mModel->mVisualModel->mCharactersPerGlyph.Clear();
564 mModel->mVisualModel->mGlyphsPerCharacter.Clear();
565 mModel->mVisualModel->mGlyphPositions.Clear();
568 if( NO_OPERATION != ( LAYOUT & operations ) )
570 mModel->mVisualModel->mLines.Clear();
573 if( NO_OPERATION != ( COLOR & operations ) )
575 mModel->mVisualModel->mColorIndices.Clear();
579 void Controller::Impl::ClearCharacterModelData( CharacterIndex startIndex, CharacterIndex endIndex, OperationsMask operations )
581 const CharacterIndex endIndexPlusOne = endIndex + 1u;
583 if( NO_OPERATION != ( GET_LINE_BREAKS & operations ) )
585 // Clear the line break info.
586 LineBreakInfo* lineBreakInfoBuffer = mModel->mLogicalModel->mLineBreakInfo.Begin();
588 mModel->mLogicalModel->mLineBreakInfo.Erase( lineBreakInfoBuffer + startIndex,
589 lineBreakInfoBuffer + endIndexPlusOne );
591 // Clear the paragraphs.
592 ClearCharacterRuns( startIndex,
594 mModel->mLogicalModel->mParagraphInfo );
597 if( NO_OPERATION != ( GET_WORD_BREAKS & operations ) )
599 // Clear the word break info.
600 WordBreakInfo* wordBreakInfoBuffer = mModel->mLogicalModel->mWordBreakInfo.Begin();
602 mModel->mLogicalModel->mWordBreakInfo.Erase( wordBreakInfoBuffer + startIndex,
603 wordBreakInfoBuffer + endIndexPlusOne );
606 if( NO_OPERATION != ( GET_SCRIPTS & operations ) )
608 // Clear the scripts.
609 ClearCharacterRuns( startIndex,
611 mModel->mLogicalModel->mScriptRuns );
614 if( NO_OPERATION != ( VALIDATE_FONTS & operations ) )
617 ClearCharacterRuns( startIndex,
619 mModel->mLogicalModel->mFontRuns );
622 if( 0u != mModel->mLogicalModel->mBidirectionalParagraphInfo.Count() )
624 if( NO_OPERATION != ( BIDI_INFO & operations ) )
626 // Clear the bidirectional paragraph info.
627 ClearCharacterRuns( startIndex,
629 mModel->mLogicalModel->mBidirectionalParagraphInfo );
631 // Clear the character's directions.
632 CharacterDirection* characterDirectionsBuffer = mModel->mLogicalModel->mCharacterDirections.Begin();
634 mModel->mLogicalModel->mCharacterDirections.Erase( characterDirectionsBuffer + startIndex,
635 characterDirectionsBuffer + endIndexPlusOne );
638 if( NO_OPERATION != ( REORDER & operations ) )
640 uint32_t startRemoveIndex = mModel->mLogicalModel->mBidirectionalLineInfo.Count();
641 uint32_t endRemoveIndex = startRemoveIndex;
642 ClearCharacterRuns( startIndex,
644 mModel->mLogicalModel->mBidirectionalLineInfo,
648 BidirectionalLineInfoRun* bidirectionalLineInfoBuffer = mModel->mLogicalModel->mBidirectionalLineInfo.Begin();
650 // Free the allocated memory used to store the conversion table in the bidirectional line info run.
651 for( Vector<BidirectionalLineInfoRun>::Iterator it = bidirectionalLineInfoBuffer + startRemoveIndex,
652 endIt = bidirectionalLineInfoBuffer + endRemoveIndex;
656 BidirectionalLineInfoRun& bidiLineInfo = *it;
658 free( bidiLineInfo.visualToLogicalMap );
659 bidiLineInfo.visualToLogicalMap = NULL;
662 mModel->mLogicalModel->mBidirectionalLineInfo.Erase( bidirectionalLineInfoBuffer + startRemoveIndex,
663 bidirectionalLineInfoBuffer + endRemoveIndex );
668 void Controller::Impl::ClearGlyphModelData( CharacterIndex startIndex, CharacterIndex endIndex, OperationsMask operations )
670 const CharacterIndex endIndexPlusOne = endIndex + 1u;
671 const Length numberOfCharactersRemoved = endIndexPlusOne - startIndex;
673 // Convert the character index to glyph index before deleting the character to glyph and the glyphs per character buffers.
674 GlyphIndex* charactersToGlyphBuffer = mModel->mVisualModel->mCharactersToGlyph.Begin();
675 Length* glyphsPerCharacterBuffer = mModel->mVisualModel->mGlyphsPerCharacter.Begin();
677 const GlyphIndex endGlyphIndexPlusOne = *( charactersToGlyphBuffer + endIndex ) + *( glyphsPerCharacterBuffer + endIndex );
678 const Length numberOfGlyphsRemoved = endGlyphIndexPlusOne - mTextUpdateInfo.mStartGlyphIndex;
680 if( NO_OPERATION != ( SHAPE_TEXT & operations ) )
682 // Update the character to glyph indices.
683 for( Vector<GlyphIndex>::Iterator it = charactersToGlyphBuffer + endIndexPlusOne,
684 endIt = charactersToGlyphBuffer + mModel->mVisualModel->mCharactersToGlyph.Count();
688 CharacterIndex& index = *it;
689 index -= numberOfGlyphsRemoved;
692 // Clear the character to glyph conversion table.
693 mModel->mVisualModel->mCharactersToGlyph.Erase( charactersToGlyphBuffer + startIndex,
694 charactersToGlyphBuffer + endIndexPlusOne );
696 // Clear the glyphs per character table.
697 mModel->mVisualModel->mGlyphsPerCharacter.Erase( glyphsPerCharacterBuffer + startIndex,
698 glyphsPerCharacterBuffer + endIndexPlusOne );
700 // Clear the glyphs buffer.
701 GlyphInfo* glyphsBuffer = mModel->mVisualModel->mGlyphs.Begin();
702 mModel->mVisualModel->mGlyphs.Erase( glyphsBuffer + mTextUpdateInfo.mStartGlyphIndex,
703 glyphsBuffer + endGlyphIndexPlusOne );
705 CharacterIndex* glyphsToCharactersBuffer = mModel->mVisualModel->mGlyphsToCharacters.Begin();
707 // Update the glyph to character indices.
708 for( Vector<CharacterIndex>::Iterator it = glyphsToCharactersBuffer + endGlyphIndexPlusOne,
709 endIt = glyphsToCharactersBuffer + mModel->mVisualModel->mGlyphsToCharacters.Count();
713 CharacterIndex& index = *it;
714 index -= numberOfCharactersRemoved;
717 // Clear the glyphs to characters buffer.
718 mModel->mVisualModel->mGlyphsToCharacters.Erase( glyphsToCharactersBuffer + mTextUpdateInfo.mStartGlyphIndex,
719 glyphsToCharactersBuffer + endGlyphIndexPlusOne );
721 // Clear the characters per glyph buffer.
722 Length* charactersPerGlyphBuffer = mModel->mVisualModel->mCharactersPerGlyph.Begin();
723 mModel->mVisualModel->mCharactersPerGlyph.Erase( charactersPerGlyphBuffer + mTextUpdateInfo.mStartGlyphIndex,
724 charactersPerGlyphBuffer + endGlyphIndexPlusOne );
726 // Clear the positions buffer.
727 Vector2* positionsBuffer = mModel->mVisualModel->mGlyphPositions.Begin();
728 mModel->mVisualModel->mGlyphPositions.Erase( positionsBuffer + mTextUpdateInfo.mStartGlyphIndex,
729 positionsBuffer + endGlyphIndexPlusOne );
732 if( NO_OPERATION != ( LAYOUT & operations ) )
735 uint32_t startRemoveIndex = mModel->mVisualModel->mLines.Count();
736 uint32_t endRemoveIndex = startRemoveIndex;
737 ClearCharacterRuns( startIndex,
739 mModel->mVisualModel->mLines,
743 // Will update the glyph runs.
744 startRemoveIndex = mModel->mVisualModel->mLines.Count();
745 endRemoveIndex = startRemoveIndex;
746 ClearGlyphRuns( mTextUpdateInfo.mStartGlyphIndex,
747 endGlyphIndexPlusOne - 1u,
748 mModel->mVisualModel->mLines,
752 // Set the line index from where to insert the new laid-out lines.
753 mTextUpdateInfo.mStartLineIndex = startRemoveIndex;
755 LineRun* linesBuffer = mModel->mVisualModel->mLines.Begin();
756 mModel->mVisualModel->mLines.Erase( linesBuffer + startRemoveIndex,
757 linesBuffer + endRemoveIndex );
760 if( NO_OPERATION != ( COLOR & operations ) )
762 if( 0u != mModel->mVisualModel->mColorIndices.Count() )
764 ColorIndex* colorIndexBuffer = mModel->mVisualModel->mColorIndices.Begin();
765 mModel->mVisualModel->mColorIndices.Erase( colorIndexBuffer + mTextUpdateInfo.mStartGlyphIndex,
766 colorIndexBuffer + endGlyphIndexPlusOne );
771 void Controller::Impl::ClearModelData( CharacterIndex startIndex, CharacterIndex endIndex, OperationsMask operations )
773 if( mTextUpdateInfo.mClearAll ||
774 ( ( 0u == startIndex ) &&
775 ( mTextUpdateInfo.mPreviousNumberOfCharacters == endIndex + 1u ) ) )
777 ClearFullModelData( operations );
781 // Clear the model data related with characters.
782 ClearCharacterModelData( startIndex, endIndex, operations );
784 // Clear the model data related with glyphs.
785 ClearGlyphModelData( startIndex, endIndex, operations );
788 // The estimated number of lines. Used to avoid reallocations when layouting.
789 mTextUpdateInfo.mEstimatedNumberOfLines = std::max( mModel->mVisualModel->mLines.Count(), mModel->mLogicalModel->mParagraphInfo.Count() );
791 mModel->mVisualModel->ClearCaches();
794 bool Controller::Impl::UpdateModel( OperationsMask operationsRequired )
796 DALI_LOG_INFO( gLogFilter, Debug::General, "Controller::UpdateModel\n" );
798 // Calculate the operations to be done.
799 const OperationsMask operations = static_cast<OperationsMask>( mOperationsPending & operationsRequired );
801 if( NO_OPERATION == operations )
803 // Nothing to do if no operations are pending and required.
807 Vector<Character>& srcCharacters = mModel->mLogicalModel->mText;
808 Vector<Character> displayCharacters;
809 bool useHiddenText = false;
810 if ( mHiddenInput && mEventData != NULL && !mEventData->mIsShowingPlaceholderText)
812 mHiddenInput->Substitute( srcCharacters,displayCharacters );
813 useHiddenText = true;
816 Vector<Character>& utf32Characters = useHiddenText ? displayCharacters : srcCharacters;
817 const Length numberOfCharacters = utf32Characters.Count();
819 // Index to the first character of the first paragraph to be updated.
820 CharacterIndex startIndex = 0u;
821 // Number of characters of the paragraphs to be removed.
822 Length paragraphCharacters = 0u;
824 CalculateTextUpdateIndices( paragraphCharacters );
826 // Check whether the indices for updating the text is valid
827 if ( numberOfCharacters > 0u &&
828 ( mTextUpdateInfo.mParagraphCharacterIndex >= numberOfCharacters ||
829 mTextUpdateInfo.mRequestedNumberOfCharacters > numberOfCharacters ) )
831 std::string currentText;
832 Utf32ToUtf8( mModel->mLogicalModel->mText.Begin(), numberOfCharacters, currentText );
834 DALI_LOG_ERROR( "Controller::Impl::UpdateModel: mTextUpdateInfo has invalid indices\n" );
835 DALI_LOG_ERROR( "Number of characters: %d, current text is: %s\n", numberOfCharacters, currentText.c_str() );
837 // Dump mTextUpdateInfo
838 DALI_LOG_ERROR( "Dump mTextUpdateInfo:\n" );
839 DALI_LOG_ERROR( " mTextUpdateInfo.mCharacterIndex = %u\n", mTextUpdateInfo.mCharacterIndex );
840 DALI_LOG_ERROR( " mTextUpdateInfo.mNumberOfCharactersToRemove = %u\n", mTextUpdateInfo.mNumberOfCharactersToRemove );
841 DALI_LOG_ERROR( " mTextUpdateInfo.mNumberOfCharactersToAdd = %u\n", mTextUpdateInfo.mNumberOfCharactersToAdd );
842 DALI_LOG_ERROR( " mTextUpdateInfo.mPreviousNumberOfCharacters = %u\n", mTextUpdateInfo.mPreviousNumberOfCharacters );
843 DALI_LOG_ERROR( " mTextUpdateInfo.mParagraphCharacterIndex = %u\n", mTextUpdateInfo.mParagraphCharacterIndex );
844 DALI_LOG_ERROR( " mTextUpdateInfo.mRequestedNumberOfCharacters = %u\n", mTextUpdateInfo.mRequestedNumberOfCharacters );
845 DALI_LOG_ERROR( " mTextUpdateInfo.mStartGlyphIndex = %u\n", mTextUpdateInfo.mStartGlyphIndex );
846 DALI_LOG_ERROR( " mTextUpdateInfo.mStartLineIndex = %u\n", mTextUpdateInfo.mStartLineIndex );
847 DALI_LOG_ERROR( " mTextUpdateInfo.mEstimatedNumberOfLines = %u\n", mTextUpdateInfo.mEstimatedNumberOfLines );
848 DALI_LOG_ERROR( " mTextUpdateInfo.mClearAll = %d\n", mTextUpdateInfo.mClearAll );
849 DALI_LOG_ERROR( " mTextUpdateInfo.mFullRelayoutNeeded = %d\n", mTextUpdateInfo.mFullRelayoutNeeded );
850 DALI_LOG_ERROR( " mTextUpdateInfo.mIsLastCharacterNewParagraph = %d\n", mTextUpdateInfo.mIsLastCharacterNewParagraph );
855 startIndex = mTextUpdateInfo.mParagraphCharacterIndex;
857 if( mTextUpdateInfo.mClearAll ||
858 ( 0u != paragraphCharacters ) )
860 ClearModelData( startIndex, startIndex + ( ( paragraphCharacters > 0u ) ? paragraphCharacters - 1u : 0u ), operations );
863 mTextUpdateInfo.mClearAll = false;
865 // Whether the model is updated.
866 bool updated = false;
868 Vector<LineBreakInfo>& lineBreakInfo = mModel->mLogicalModel->mLineBreakInfo;
869 const Length requestedNumberOfCharacters = mTextUpdateInfo.mRequestedNumberOfCharacters;
871 if( NO_OPERATION != ( GET_LINE_BREAKS & operations ) )
873 // Retrieves the line break info. The line break info is used to split the text in 'paragraphs' to
874 // calculate the bidirectional info for each 'paragraph'.
875 // It's also used to layout the text (where it should be a new line) or to shape the text (text in different lines
876 // is not shaped together).
877 lineBreakInfo.Resize( numberOfCharacters, TextAbstraction::LINE_NO_BREAK );
879 SetLineBreakInfo( utf32Characters,
881 requestedNumberOfCharacters,
884 // Create the paragraph info.
885 mModel->mLogicalModel->CreateParagraphInfo( startIndex,
886 requestedNumberOfCharacters );
890 Vector<WordBreakInfo>& wordBreakInfo = mModel->mLogicalModel->mWordBreakInfo;
891 if( NO_OPERATION != ( GET_WORD_BREAKS & operations ) )
893 // Retrieves the word break info. The word break info is used to layout the text (where to wrap the text in lines).
894 wordBreakInfo.Resize( numberOfCharacters, TextAbstraction::WORD_NO_BREAK );
896 SetWordBreakInfo( utf32Characters,
898 requestedNumberOfCharacters,
903 const bool getScripts = NO_OPERATION != ( GET_SCRIPTS & operations );
904 const bool validateFonts = NO_OPERATION != ( VALIDATE_FONTS & operations );
906 Vector<ScriptRun>& scripts = mModel->mLogicalModel->mScriptRuns;
907 Vector<FontRun>& validFonts = mModel->mLogicalModel->mFontRuns;
909 if( getScripts || validateFonts )
911 // Validates the fonts assigned by the application or assigns default ones.
912 // It makes sure all the characters are going to be rendered by the correct font.
913 MultilanguageSupport multilanguageSupport = MultilanguageSupport::Get();
917 // Retrieves the scripts used in the text.
918 multilanguageSupport.SetScripts( utf32Characters,
920 requestedNumberOfCharacters,
926 // Validate the fonts set through the mark-up string.
927 Vector<FontDescriptionRun>& fontDescriptionRuns = mModel->mLogicalModel->mFontDescriptionRuns;
929 // Get the default font's description.
930 TextAbstraction::FontDescription defaultFontDescription;
931 TextAbstraction::PointSize26Dot6 defaultPointSize = TextAbstraction::FontClient::DEFAULT_POINT_SIZE;
933 if( IsShowingPlaceholderText() && mEventData && ( NULL != mEventData->mPlaceholderFont ) )
935 // If the placeholder font is set specifically, only placeholder font is changed.
936 defaultFontDescription = mEventData->mPlaceholderFont->mFontDescription;
937 if( mEventData->mPlaceholderFont->sizeDefined )
939 defaultPointSize = mEventData->mPlaceholderFont->mDefaultPointSize * 64u;
942 else if( NULL != mFontDefaults )
944 // Set the normal font and the placeholder font.
945 defaultFontDescription = mFontDefaults->mFontDescription;
946 defaultPointSize = mFontDefaults->mDefaultPointSize * 64u;
949 // Validates the fonts. If there is a character with no assigned font it sets a default one.
950 // After this call, fonts are validated.
951 multilanguageSupport.ValidateFonts( utf32Characters,
954 defaultFontDescription,
957 requestedNumberOfCharacters,
963 Vector<Character> mirroredUtf32Characters;
964 bool textMirrored = false;
965 const Length numberOfParagraphs = mModel->mLogicalModel->mParagraphInfo.Count();
966 if( NO_OPERATION != ( BIDI_INFO & operations ) )
968 Vector<BidirectionalParagraphInfoRun>& bidirectionalInfo = mModel->mLogicalModel->mBidirectionalParagraphInfo;
969 bidirectionalInfo.Reserve( numberOfParagraphs );
971 // Calculates the bidirectional info for the whole paragraph if it contains right to left scripts.
972 SetBidirectionalInfo( utf32Characters,
976 requestedNumberOfCharacters,
979 if( 0u != bidirectionalInfo.Count() )
981 // Only set the character directions if there is right to left characters.
982 Vector<CharacterDirection>& directions = mModel->mLogicalModel->mCharacterDirections;
983 GetCharactersDirection( bidirectionalInfo,
986 requestedNumberOfCharacters,
989 // This paragraph has right to left text. Some characters may need to be mirrored.
990 // TODO: consider if the mirrored string can be stored as well.
992 textMirrored = GetMirroredText( utf32Characters,
996 requestedNumberOfCharacters,
997 mirroredUtf32Characters );
1001 // There is no right to left characters. Clear the directions vector.
1002 mModel->mLogicalModel->mCharacterDirections.Clear();
1007 Vector<GlyphInfo>& glyphs = mModel->mVisualModel->mGlyphs;
1008 Vector<CharacterIndex>& glyphsToCharactersMap = mModel->mVisualModel->mGlyphsToCharacters;
1009 Vector<Length>& charactersPerGlyph = mModel->mVisualModel->mCharactersPerGlyph;
1010 Vector<GlyphIndex> newParagraphGlyphs;
1011 newParagraphGlyphs.Reserve( numberOfParagraphs );
1013 const Length currentNumberOfGlyphs = glyphs.Count();
1014 if( NO_OPERATION != ( SHAPE_TEXT & operations ) )
1016 const Vector<Character>& textToShape = textMirrored ? mirroredUtf32Characters : utf32Characters;
1018 ShapeText( textToShape,
1023 mTextUpdateInfo.mStartGlyphIndex,
1024 requestedNumberOfCharacters,
1026 glyphsToCharactersMap,
1028 newParagraphGlyphs );
1030 // Create the 'number of glyphs' per character and the glyph to character conversion tables.
1031 mModel->mVisualModel->CreateGlyphsPerCharacterTable( startIndex, mTextUpdateInfo.mStartGlyphIndex, requestedNumberOfCharacters );
1032 mModel->mVisualModel->CreateCharacterToGlyphTable( startIndex, mTextUpdateInfo.mStartGlyphIndex, requestedNumberOfCharacters );
1036 const Length numberOfGlyphs = glyphs.Count() - currentNumberOfGlyphs;
1038 if( NO_OPERATION != ( GET_GLYPH_METRICS & operations ) )
1040 GlyphInfo* glyphsBuffer = glyphs.Begin();
1041 mMetrics->GetGlyphMetrics( glyphsBuffer + mTextUpdateInfo.mStartGlyphIndex, numberOfGlyphs );
1043 // Update the width and advance of all new paragraph characters.
1044 for( Vector<GlyphIndex>::ConstIterator it = newParagraphGlyphs.Begin(), endIt = newParagraphGlyphs.End(); it != endIt; ++it )
1046 const GlyphIndex index = *it;
1047 GlyphInfo& glyph = *( glyphsBuffer + index );
1049 glyph.xBearing = 0.f;
1051 glyph.advance = 0.f;
1056 if( NO_OPERATION != ( COLOR & operations ) )
1058 // Set the color runs in glyphs.
1059 SetColorSegmentationInfo( mModel->mLogicalModel->mColorRuns,
1060 mModel->mVisualModel->mCharactersToGlyph,
1061 mModel->mVisualModel->mGlyphsPerCharacter,
1063 mTextUpdateInfo.mStartGlyphIndex,
1064 requestedNumberOfCharacters,
1065 mModel->mVisualModel->mColors,
1066 mModel->mVisualModel->mColorIndices );
1071 if( ( NULL != mEventData ) &&
1072 mEventData->mPreEditFlag &&
1073 ( 0u != mModel->mVisualModel->mCharactersToGlyph.Count() ) )
1075 // Add the underline for the pre-edit text.
1076 const GlyphIndex* const charactersToGlyphBuffer = mModel->mVisualModel->mCharactersToGlyph.Begin();
1077 const Length* const glyphsPerCharacterBuffer = mModel->mVisualModel->mGlyphsPerCharacter.Begin();
1079 const GlyphIndex glyphStart = *( charactersToGlyphBuffer + mEventData->mPreEditStartPosition );
1080 const CharacterIndex lastPreEditCharacter = mEventData->mPreEditStartPosition + ( ( mEventData->mPreEditLength > 0u ) ? mEventData->mPreEditLength - 1u : 0u );
1081 const Length numberOfGlyphsLastCharacter = *( glyphsPerCharacterBuffer + lastPreEditCharacter );
1082 const GlyphIndex glyphEnd = *( charactersToGlyphBuffer + lastPreEditCharacter ) + ( numberOfGlyphsLastCharacter > 1u ? numberOfGlyphsLastCharacter - 1u : 0u );
1084 GlyphRun underlineRun;
1085 underlineRun.glyphIndex = glyphStart;
1086 underlineRun.numberOfGlyphs = 1u + glyphEnd - glyphStart;
1088 // TODO: At the moment the underline runs are only for pre-edit.
1089 mModel->mVisualModel->mUnderlineRuns.PushBack( underlineRun );
1092 // The estimated number of lines. Used to avoid reallocations when layouting.
1093 mTextUpdateInfo.mEstimatedNumberOfLines = std::max( mModel->mVisualModel->mLines.Count(), mModel->mLogicalModel->mParagraphInfo.Count() );
1095 // Set the previous number of characters for the next time the text is updated.
1096 mTextUpdateInfo.mPreviousNumberOfCharacters = numberOfCharacters;
1101 void Controller::Impl::RetrieveDefaultInputStyle( InputStyle& inputStyle )
1103 // Sets the default text's color.
1104 inputStyle.textColor = mTextColor;
1105 inputStyle.isDefaultColor = true;
1107 inputStyle.familyName.clear();
1108 inputStyle.weight = TextAbstraction::FontWeight::NORMAL;
1109 inputStyle.width = TextAbstraction::FontWidth::NORMAL;
1110 inputStyle.slant = TextAbstraction::FontSlant::NORMAL;
1111 inputStyle.size = 0.f;
1113 inputStyle.lineSpacing = 0.f;
1115 inputStyle.underlineProperties.clear();
1116 inputStyle.shadowProperties.clear();
1117 inputStyle.embossProperties.clear();
1118 inputStyle.outlineProperties.clear();
1120 inputStyle.isFamilyDefined = false;
1121 inputStyle.isWeightDefined = false;
1122 inputStyle.isWidthDefined = false;
1123 inputStyle.isSlantDefined = false;
1124 inputStyle.isSizeDefined = false;
1126 inputStyle.isLineSpacingDefined = false;
1128 inputStyle.isUnderlineDefined = false;
1129 inputStyle.isShadowDefined = false;
1130 inputStyle.isEmbossDefined = false;
1131 inputStyle.isOutlineDefined = false;
1133 // Sets the default font's family name, weight, width, slant and size.
1136 if( mFontDefaults->familyDefined )
1138 inputStyle.familyName = mFontDefaults->mFontDescription.family;
1139 inputStyle.isFamilyDefined = true;
1142 if( mFontDefaults->weightDefined )
1144 inputStyle.weight = mFontDefaults->mFontDescription.weight;
1145 inputStyle.isWeightDefined = true;
1148 if( mFontDefaults->widthDefined )
1150 inputStyle.width = mFontDefaults->mFontDescription.width;
1151 inputStyle.isWidthDefined = true;
1154 if( mFontDefaults->slantDefined )
1156 inputStyle.slant = mFontDefaults->mFontDescription.slant;
1157 inputStyle.isSlantDefined = true;
1160 if( mFontDefaults->sizeDefined )
1162 inputStyle.size = mFontDefaults->mDefaultPointSize;
1163 inputStyle.isSizeDefined = true;
1168 float Controller::Impl::GetDefaultFontLineHeight()
1170 FontId defaultFontId = 0u;
1171 if( NULL == mFontDefaults )
1173 TextAbstraction::FontDescription fontDescription;
1174 defaultFontId = mFontClient.GetFontId( fontDescription );
1178 defaultFontId = mFontDefaults->GetFontId( mFontClient );
1181 Text::FontMetrics fontMetrics;
1182 mMetrics->GetFontMetrics( defaultFontId, fontMetrics );
1184 return( fontMetrics.ascender - fontMetrics.descender );
1187 void Controller::Impl::OnCursorKeyEvent( const Event& event )
1189 if( NULL == mEventData || !IsShowingRealText() )
1191 // Nothing to do if there is no text input.
1195 int keyCode = event.p1.mInt;
1196 bool isShiftModifier = event.p2.mBool;
1198 CharacterIndex previousPrimaryCursorPosition = mEventData->mPrimaryCursorPosition;
1200 if( Dali::DALI_KEY_CURSOR_LEFT == keyCode )
1202 if( mEventData->mPrimaryCursorPosition > 0u )
1204 if ( !isShiftModifier && mEventData->mDecorator->IsHighlightVisible() )
1206 mEventData->mPrimaryCursorPosition = std::min(mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition);
1210 mEventData->mPrimaryCursorPosition = CalculateNewCursorIndex( mEventData->mPrimaryCursorPosition - 1u );
1214 else if( Dali::DALI_KEY_CURSOR_RIGHT == keyCode )
1216 if( mModel->mLogicalModel->mText.Count() > mEventData->mPrimaryCursorPosition )
1218 if ( !isShiftModifier && mEventData->mDecorator->IsHighlightVisible() )
1220 mEventData->mPrimaryCursorPosition = std::max(mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition);
1224 mEventData->mPrimaryCursorPosition = CalculateNewCursorIndex( mEventData->mPrimaryCursorPosition );
1228 else if( Dali::DALI_KEY_CURSOR_UP == keyCode && !isShiftModifier )
1230 // Ignore Shift-Up for text selection for now.
1232 // Get first the line index of the current cursor position index.
1233 CharacterIndex characterIndex = 0u;
1235 if( mEventData->mPrimaryCursorPosition > 0u )
1237 characterIndex = mEventData->mPrimaryCursorPosition - 1u;
1240 const LineIndex lineIndex = mModel->mVisualModel->GetLineOfCharacter( characterIndex );
1241 const LineIndex previousLineIndex = ( lineIndex > 0 ? lineIndex - 1u : lineIndex );
1243 // Retrieve the cursor position info.
1244 CursorInfo cursorInfo;
1245 GetCursorPosition( mEventData->mPrimaryCursorPosition,
1248 // Get the line above.
1249 const LineRun& line = *( mModel->mVisualModel->mLines.Begin() + previousLineIndex );
1251 // Get the next hit 'y' point.
1252 const float hitPointY = cursorInfo.lineOffset - 0.5f * ( line.ascender - line.descender );
1254 // Use the cursor hook position 'x' and the next hit 'y' position to calculate the new cursor index.
1255 bool matchedCharacter = false;
1256 mEventData->mPrimaryCursorPosition = Text::GetClosestCursorIndex( mModel->mVisualModel,
1257 mModel->mLogicalModel,
1259 mEventData->mCursorHookPositionX,
1261 CharacterHitTest::TAP,
1264 else if( Dali::DALI_KEY_CURSOR_DOWN == keyCode && !isShiftModifier )
1266 // Ignore Shift-Down for text selection for now.
1268 // Get first the line index of the current cursor position index.
1269 CharacterIndex characterIndex = 0u;
1271 if( mEventData->mPrimaryCursorPosition > 0u )
1273 characterIndex = mEventData->mPrimaryCursorPosition - 1u;
1276 const LineIndex lineIndex = mModel->mVisualModel->GetLineOfCharacter( characterIndex );
1278 if( lineIndex + 1u < mModel->mVisualModel->mLines.Count() )
1280 // Retrieve the cursor position info.
1281 CursorInfo cursorInfo;
1282 GetCursorPosition( mEventData->mPrimaryCursorPosition,
1285 // Get the line below.
1286 const LineRun& line = *( mModel->mVisualModel->mLines.Begin() + lineIndex + 1u );
1288 // Get the next hit 'y' point.
1289 const float hitPointY = cursorInfo.lineOffset + cursorInfo.lineHeight + 0.5f * ( line.ascender - line.descender );
1291 // Use the cursor hook position 'x' and the next hit 'y' position to calculate the new cursor index.
1292 bool matchedCharacter = false;
1293 mEventData->mPrimaryCursorPosition = Text::GetClosestCursorIndex( mModel->mVisualModel,
1294 mModel->mLogicalModel,
1296 mEventData->mCursorHookPositionX,
1298 CharacterHitTest::TAP,
1303 if ( !isShiftModifier && mEventData->mState != EventData::SELECTING )
1305 // Update selection position after moving the cursor
1306 mEventData->mLeftSelectionPosition = mEventData->mPrimaryCursorPosition;
1307 mEventData->mRightSelectionPosition = mEventData->mPrimaryCursorPosition;
1310 if ( isShiftModifier && IsShowingRealText() && mEventData->mShiftSelectionFlag )
1312 // Handle text selection
1313 bool selecting = false;
1315 if ( Dali::DALI_KEY_CURSOR_LEFT == keyCode || Dali::DALI_KEY_CURSOR_RIGHT == keyCode )
1317 // Shift-Left/Right to select the text
1318 int cursorPositionDelta = mEventData->mPrimaryCursorPosition - previousPrimaryCursorPosition;
1319 if ( cursorPositionDelta > 0 || mEventData->mRightSelectionPosition > 0u ) // Check the boundary
1321 mEventData->mRightSelectionPosition += cursorPositionDelta;
1325 else if ( mEventData->mLeftSelectionPosition != mEventData->mRightSelectionPosition )
1327 // Show no grab handles and text highlight if Shift-Up/Down pressed but no selected text
1333 // Notify the cursor position to the imf manager.
1334 if( mEventData->mImfManager )
1336 mEventData->mImfManager.SetCursorPosition( mEventData->mPrimaryCursorPosition );
1337 mEventData->mImfManager.NotifyCursorPosition();
1340 ChangeState( EventData::SELECTING );
1342 mEventData->mUpdateLeftSelectionPosition = true;
1343 mEventData->mUpdateRightSelectionPosition = true;
1344 mEventData->mUpdateGrabHandlePosition = true;
1345 mEventData->mUpdateHighlightBox = true;
1347 // Hide the text selection popup if select the text using keyboard instead of moving grab handles
1348 if( mEventData->mGrabHandlePopupEnabled )
1350 mEventData->mDecorator->SetPopupActive( false );
1356 // Handle normal cursor move
1357 ChangeState( EventData::EDITING );
1358 mEventData->mUpdateCursorPosition = true;
1361 mEventData->mUpdateInputStyle = true;
1362 mEventData->mScrollAfterUpdatePosition = true;
1365 void Controller::Impl::OnTapEvent( const Event& event )
1367 if( NULL != mEventData )
1369 const unsigned int tapCount = event.p1.mUint;
1371 if( 1u == tapCount )
1373 if( IsShowingRealText() )
1375 // Convert from control's coords to text's coords.
1376 const float xPosition = event.p2.mFloat - mModel->mScrollPosition.x;
1377 const float yPosition = event.p3.mFloat - mModel->mScrollPosition.y;
1379 // Keep the tap 'x' position. Used to move the cursor.
1380 mEventData->mCursorHookPositionX = xPosition;
1382 // Whether to touch point hits on a glyph.
1383 bool matchedCharacter = false;
1384 mEventData->mPrimaryCursorPosition = Text::GetClosestCursorIndex( mModel->mVisualModel,
1385 mModel->mLogicalModel,
1389 CharacterHitTest::TAP,
1392 // When the cursor position is changing, delay cursor blinking
1393 mEventData->mDecorator->DelayCursorBlink();
1397 mEventData->mPrimaryCursorPosition = 0u;
1400 // Update selection position after tapping
1401 mEventData->mLeftSelectionPosition = mEventData->mPrimaryCursorPosition;
1402 mEventData->mRightSelectionPosition = mEventData->mPrimaryCursorPosition;
1404 mEventData->mUpdateCursorPosition = true;
1405 mEventData->mUpdateGrabHandlePosition = true;
1406 mEventData->mScrollAfterUpdatePosition = true;
1407 mEventData->mUpdateInputStyle = true;
1409 // Notify the cursor position to the imf manager.
1410 if( mEventData->mImfManager )
1412 mEventData->mImfManager.SetCursorPosition( mEventData->mPrimaryCursorPosition );
1413 mEventData->mImfManager.NotifyCursorPosition();
1416 else if( 2u == tapCount )
1418 if( mEventData->mSelectionEnabled )
1420 // Convert from control's coords to text's coords.
1421 const float xPosition = event.p2.mFloat - mModel->mScrollPosition.x;
1422 const float yPosition = event.p3.mFloat - mModel->mScrollPosition.y;
1424 // Calculates the logical position from the x,y coords.
1425 RepositionSelectionHandles( xPosition,
1427 mEventData->mDoubleTapAction );
1433 void Controller::Impl::OnPanEvent( const Event& event )
1435 if( NULL == mEventData )
1437 // Nothing to do if there is no text input.
1441 const bool isHorizontalScrollEnabled = mEventData->mDecorator->IsHorizontalScrollEnabled();
1442 const bool isVerticalScrollEnabled = mEventData->mDecorator->IsVerticalScrollEnabled();
1444 if( !isHorizontalScrollEnabled && !isVerticalScrollEnabled )
1446 // Nothing to do if scrolling is not enabled.
1450 const int state = event.p1.mInt;
1454 case Gesture::Started:
1456 // Will remove the cursor, handles or text's popup, ...
1457 ChangeState( EventData::TEXT_PANNING );
1460 case Gesture::Continuing:
1462 const Vector2& layoutSize = mModel->mVisualModel->GetLayoutSize();
1463 const Vector2 currentScroll = mModel->mScrollPosition;
1465 if( isHorizontalScrollEnabled )
1467 const float displacementX = event.p2.mFloat;
1468 mModel->mScrollPosition.x += displacementX;
1470 ClampHorizontalScroll( layoutSize );
1473 if( isVerticalScrollEnabled )
1475 const float displacementY = event.p3.mFloat;
1476 mModel->mScrollPosition.y += displacementY;
1478 ClampVerticalScroll( layoutSize );
1481 mEventData->mDecorator->UpdatePositions( mModel->mScrollPosition - currentScroll );
1484 case Gesture::Finished:
1485 case Gesture::Cancelled: // FALLTHROUGH
1487 // Will go back to the previous state to show the cursor, handles, the text's popup, ...
1488 ChangeState( mEventData->mPreviousState );
1496 void Controller::Impl::OnLongPressEvent( const Event& event )
1498 DALI_LOG_INFO( gLogFilter, Debug::General, "Controller::OnLongPressEvent\n" );
1500 if( !IsShowingRealText() && ( EventData::EDITING == mEventData->mState ) )
1502 ChangeState( EventData::EDITING_WITH_POPUP );
1503 mEventData->mDecoratorUpdated = true;
1504 mEventData->mUpdateInputStyle = true;
1508 if( mEventData->mSelectionEnabled )
1510 // Convert from control's coords to text's coords.
1511 const float xPosition = event.p2.mFloat - mModel->mScrollPosition.x;
1512 const float yPosition = event.p3.mFloat - mModel->mScrollPosition.y;
1514 // Calculates the logical position from the x,y coords.
1515 RepositionSelectionHandles( xPosition,
1517 mEventData->mLongPressAction );
1522 void Controller::Impl::OnHandleEvent( const Event& event )
1524 if( NULL == mEventData )
1526 // Nothing to do if there is no text input.
1530 const unsigned int state = event.p1.mUint;
1531 const bool handleStopScrolling = ( HANDLE_STOP_SCROLLING == state );
1532 const bool isSmoothHandlePanEnabled = mEventData->mDecorator->IsSmoothHandlePanEnabled();
1534 if( HANDLE_PRESSED == state )
1536 // Convert from decorator's coords to text's coords.
1537 const float xPosition = event.p2.mFloat - mModel->mScrollPosition.x;
1538 const float yPosition = event.p3.mFloat - mModel->mScrollPosition.y;
1540 // Need to calculate the handle's new position.
1541 bool matchedCharacter = false;
1542 const CharacterIndex handleNewPosition = Text::GetClosestCursorIndex( mModel->mVisualModel,
1543 mModel->mLogicalModel,
1547 CharacterHitTest::SCROLL,
1550 if( Event::GRAB_HANDLE_EVENT == event.type )
1552 ChangeState ( EventData::GRAB_HANDLE_PANNING );
1554 if( handleNewPosition != mEventData->mPrimaryCursorPosition )
1556 // Updates the cursor position if the handle's new position is different than the current one.
1557 mEventData->mUpdateCursorPosition = true;
1558 // Does not update the grab handle position if the smooth panning is enabled. (The decorator does it smooth).
1559 mEventData->mUpdateGrabHandlePosition = !isSmoothHandlePanEnabled;
1560 mEventData->mPrimaryCursorPosition = handleNewPosition;
1563 // 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.
1564 mEventData->mDecoratorUpdated = isSmoothHandlePanEnabled;
1566 else if( Event::LEFT_SELECTION_HANDLE_EVENT == event.type )
1568 ChangeState ( EventData::SELECTION_HANDLE_PANNING );
1570 if( ( handleNewPosition != mEventData->mLeftSelectionPosition ) &&
1571 ( handleNewPosition != mEventData->mRightSelectionPosition ) )
1573 // Updates the highlight box if the handle's new position is different than the current one.
1574 mEventData->mUpdateHighlightBox = true;
1575 // Does not update the selection handle position if the smooth panning is enabled. (The decorator does it smooth).
1576 mEventData->mUpdateLeftSelectionPosition = !isSmoothHandlePanEnabled;
1577 mEventData->mLeftSelectionPosition = handleNewPosition;
1580 // 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.
1581 mEventData->mDecoratorUpdated = isSmoothHandlePanEnabled;
1583 // Will define the order to scroll the text to match the handle position.
1584 mEventData->mIsLeftHandleSelected = true;
1585 mEventData->mIsRightHandleSelected = false;
1587 else if( Event::RIGHT_SELECTION_HANDLE_EVENT == event.type )
1589 ChangeState ( EventData::SELECTION_HANDLE_PANNING );
1591 if( ( handleNewPosition != mEventData->mRightSelectionPosition ) &&
1592 ( handleNewPosition != mEventData->mLeftSelectionPosition ) )
1594 // Updates the highlight box if the handle's new position is different than the current one.
1595 mEventData->mUpdateHighlightBox = true;
1596 // Does not update the selection handle position if the smooth panning is enabled. (The decorator does it smooth).
1597 mEventData->mUpdateRightSelectionPosition = !isSmoothHandlePanEnabled;
1598 mEventData->mRightSelectionPosition = handleNewPosition;
1601 // 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.
1602 mEventData->mDecoratorUpdated = isSmoothHandlePanEnabled;
1604 // Will define the order to scroll the text to match the handle position.
1605 mEventData->mIsLeftHandleSelected = false;
1606 mEventData->mIsRightHandleSelected = true;
1608 } // end ( HANDLE_PRESSED == state )
1609 else if( ( HANDLE_RELEASED == state ) ||
1610 handleStopScrolling )
1612 CharacterIndex handlePosition = 0u;
1613 if( handleStopScrolling || isSmoothHandlePanEnabled )
1615 // Convert from decorator's coords to text's coords.
1616 const float xPosition = event.p2.mFloat - mModel->mScrollPosition.x;
1617 const float yPosition = event.p3.mFloat - mModel->mScrollPosition.y;
1619 bool matchedCharacter = false;
1620 handlePosition = Text::GetClosestCursorIndex( mModel->mVisualModel,
1621 mModel->mLogicalModel,
1625 CharacterHitTest::SCROLL,
1629 if( Event::GRAB_HANDLE_EVENT == event.type )
1631 mEventData->mUpdateCursorPosition = true;
1632 mEventData->mUpdateGrabHandlePosition = true;
1633 mEventData->mUpdateInputStyle = true;
1635 if( !IsClipboardEmpty() )
1637 ChangeState( EventData::EDITING_WITH_PASTE_POPUP ); // Moving grabhandle will show Paste Popup
1640 if( handleStopScrolling || isSmoothHandlePanEnabled )
1642 mEventData->mScrollAfterUpdatePosition = true;
1643 mEventData->mPrimaryCursorPosition = handlePosition;
1646 else if( Event::LEFT_SELECTION_HANDLE_EVENT == event.type )
1648 ChangeState( EventData::SELECTING );
1650 mEventData->mUpdateHighlightBox = true;
1651 mEventData->mUpdateLeftSelectionPosition = true;
1652 mEventData->mUpdateRightSelectionPosition = true;
1654 if( handleStopScrolling || isSmoothHandlePanEnabled )
1656 mEventData->mScrollAfterUpdatePosition = true;
1658 if( ( handlePosition != mEventData->mRightSelectionPosition ) &&
1659 ( handlePosition != mEventData->mLeftSelectionPosition ) )
1661 mEventData->mLeftSelectionPosition = handlePosition;
1665 else if( Event::RIGHT_SELECTION_HANDLE_EVENT == event.type )
1667 ChangeState( EventData::SELECTING );
1669 mEventData->mUpdateHighlightBox = true;
1670 mEventData->mUpdateRightSelectionPosition = true;
1671 mEventData->mUpdateLeftSelectionPosition = true;
1673 if( handleStopScrolling || isSmoothHandlePanEnabled )
1675 mEventData->mScrollAfterUpdatePosition = true;
1676 if( ( handlePosition != mEventData->mRightSelectionPosition ) &&
1677 ( handlePosition != mEventData->mLeftSelectionPosition ) )
1679 mEventData->mRightSelectionPosition = handlePosition;
1684 mEventData->mDecoratorUpdated = true;
1685 } // end ( ( HANDLE_RELEASED == state ) || ( HANDLE_STOP_SCROLLING == state ) )
1686 else if( HANDLE_SCROLLING == state )
1688 const float xSpeed = event.p2.mFloat;
1689 const float ySpeed = event.p3.mFloat;
1690 const Vector2& layoutSize = mModel->mVisualModel->GetLayoutSize();
1691 const Vector2 currentScrollPosition = mModel->mScrollPosition;
1693 mModel->mScrollPosition.x += xSpeed;
1694 mModel->mScrollPosition.y += ySpeed;
1696 ClampHorizontalScroll( layoutSize );
1697 ClampVerticalScroll( layoutSize );
1699 bool endOfScroll = false;
1700 if( Vector2::ZERO == ( currentScrollPosition - mModel->mScrollPosition ) )
1702 // Notify the decorator there is no more text to scroll.
1703 // The decorator won't send more scroll events.
1704 mEventData->mDecorator->NotifyEndOfScroll();
1705 // Still need to set the position of the handle.
1709 // Set the position of the handle.
1710 const bool scrollRightDirection = xSpeed > 0.f;
1711 const bool scrollBottomDirection = ySpeed > 0.f;
1712 const bool leftSelectionHandleEvent = Event::LEFT_SELECTION_HANDLE_EVENT == event.type;
1713 const bool rightSelectionHandleEvent = Event::RIGHT_SELECTION_HANDLE_EVENT == event.type;
1715 if( Event::GRAB_HANDLE_EVENT == event.type )
1717 ChangeState( EventData::GRAB_HANDLE_PANNING );
1719 // Get the grab handle position in decorator coords.
1720 Vector2 position = mEventData->mDecorator->GetPosition( GRAB_HANDLE );
1722 if( mEventData->mDecorator->IsHorizontalScrollEnabled() )
1724 // Position the grag handle close to either the left or right edge.
1725 position.x = scrollRightDirection ? 0.f : mModel->mVisualModel->mControlSize.width;
1728 if( mEventData->mDecorator->IsVerticalScrollEnabled() )
1730 position.x = mEventData->mCursorHookPositionX;
1732 // Position the grag handle close to either the top or bottom edge.
1733 position.y = scrollBottomDirection ? 0.f : mModel->mVisualModel->mControlSize.height;
1736 // Get the new handle position.
1737 // The grab handle's position is in decorator's coords. Need to transforms to text's coords.
1738 bool matchedCharacter = false;
1739 const CharacterIndex handlePosition = Text::GetClosestCursorIndex( mModel->mVisualModel,
1740 mModel->mLogicalModel,
1742 position.x - mModel->mScrollPosition.x,
1743 position.y - mModel->mScrollPosition.y,
1744 CharacterHitTest::SCROLL,
1747 if( mEventData->mPrimaryCursorPosition != handlePosition )
1749 mEventData->mUpdateCursorPosition = true;
1750 mEventData->mUpdateGrabHandlePosition = !isSmoothHandlePanEnabled;
1751 mEventData->mScrollAfterUpdatePosition = true;
1752 mEventData->mPrimaryCursorPosition = handlePosition;
1754 mEventData->mUpdateInputStyle = mEventData->mUpdateCursorPosition;
1756 // Updates the decorator if the soft handle panning is enabled.
1757 mEventData->mDecoratorUpdated = isSmoothHandlePanEnabled;
1759 else if( leftSelectionHandleEvent || rightSelectionHandleEvent )
1761 ChangeState( EventData::SELECTION_HANDLE_PANNING );
1763 // Get the selection handle position in decorator coords.
1764 Vector2 position = mEventData->mDecorator->GetPosition( leftSelectionHandleEvent ? Text::LEFT_SELECTION_HANDLE : Text::RIGHT_SELECTION_HANDLE );
1766 if( mEventData->mDecorator->IsHorizontalScrollEnabled() )
1768 // Position the selection handle close to either the left or right edge.
1769 position.x = scrollRightDirection ? 0.f : mModel->mVisualModel->mControlSize.width;
1772 if( mEventData->mDecorator->IsVerticalScrollEnabled() )
1774 position.x = mEventData->mCursorHookPositionX;
1776 // Position the grag handle close to either the top or bottom edge.
1777 position.y = scrollBottomDirection ? 0.f : mModel->mVisualModel->mControlSize.height;
1780 // Get the new handle position.
1781 // The selection handle's position is in decorator's coords. Need to transform to text's coords.
1782 bool matchedCharacter = false;
1783 const CharacterIndex handlePosition = Text::GetClosestCursorIndex( mModel->mVisualModel,
1784 mModel->mLogicalModel,
1786 position.x - mModel->mScrollPosition.x,
1787 position.y - mModel->mScrollPosition.y,
1788 CharacterHitTest::SCROLL,
1791 if( leftSelectionHandleEvent )
1793 const bool differentHandles = ( mEventData->mLeftSelectionPosition != handlePosition ) && ( mEventData->mRightSelectionPosition != handlePosition );
1795 if( differentHandles || endOfScroll )
1797 mEventData->mUpdateHighlightBox = true;
1798 mEventData->mUpdateLeftSelectionPosition = !isSmoothHandlePanEnabled;
1799 mEventData->mUpdateRightSelectionPosition = isSmoothHandlePanEnabled;
1800 mEventData->mLeftSelectionPosition = handlePosition;
1805 const bool differentHandles = ( mEventData->mRightSelectionPosition != handlePosition ) && ( mEventData->mLeftSelectionPosition != handlePosition );
1806 if( differentHandles || endOfScroll )
1808 mEventData->mUpdateHighlightBox = true;
1809 mEventData->mUpdateRightSelectionPosition = !isSmoothHandlePanEnabled;
1810 mEventData->mUpdateLeftSelectionPosition = isSmoothHandlePanEnabled;
1811 mEventData->mRightSelectionPosition = handlePosition;
1815 if( mEventData->mUpdateLeftSelectionPosition || mEventData->mUpdateRightSelectionPosition )
1817 RepositionSelectionHandles();
1819 mEventData->mScrollAfterUpdatePosition = !isSmoothHandlePanEnabled;
1822 mEventData->mDecoratorUpdated = true;
1823 } // end ( HANDLE_SCROLLING == state )
1826 void Controller::Impl::OnSelectEvent( const Event& event )
1828 if( NULL == mEventData )
1830 // Nothing to do if there is no text.
1834 if( mEventData->mSelectionEnabled )
1836 // Convert from control's coords to text's coords.
1837 const float xPosition = event.p2.mFloat - mModel->mScrollPosition.x;
1838 const float yPosition = event.p3.mFloat - mModel->mScrollPosition.y;
1840 // Calculates the logical position from the x,y coords.
1841 RepositionSelectionHandles( xPosition,
1843 Controller::NoTextTap::HIGHLIGHT );
1847 void Controller::Impl::OnSelectAllEvent()
1849 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "OnSelectAllEvent mEventData->mSelectionEnabled%s \n", mEventData->mSelectionEnabled?"true":"false");
1851 if( NULL == mEventData )
1853 // Nothing to do if there is no text.
1857 if( mEventData->mSelectionEnabled )
1859 ChangeState( EventData::SELECTING );
1861 mEventData->mLeftSelectionPosition = 0u;
1862 mEventData->mRightSelectionPosition = mModel->mLogicalModel->mText.Count();
1864 mEventData->mScrollAfterUpdatePosition = true;
1865 mEventData->mUpdateLeftSelectionPosition = true;
1866 mEventData->mUpdateRightSelectionPosition = true;
1867 mEventData->mUpdateHighlightBox = true;
1871 void Controller::Impl::RetrieveSelection( std::string& selectedText, bool deleteAfterRetrieval )
1873 if( mEventData->mLeftSelectionPosition == mEventData->mRightSelectionPosition )
1875 // Nothing to select if handles are in the same place.
1876 selectedText.clear();
1880 const bool handlesCrossed = mEventData->mLeftSelectionPosition > mEventData->mRightSelectionPosition;
1882 //Get start and end position of selection
1883 const CharacterIndex startOfSelectedText = handlesCrossed ? mEventData->mRightSelectionPosition : mEventData->mLeftSelectionPosition;
1884 const Length lengthOfSelectedText = ( handlesCrossed ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition ) - startOfSelectedText;
1886 Vector<Character>& utf32Characters = mModel->mLogicalModel->mText;
1887 const Length numberOfCharacters = utf32Characters.Count();
1889 // Validate the start and end selection points
1890 if( ( startOfSelectedText + lengthOfSelectedText ) <= numberOfCharacters )
1892 //Get text as a UTF8 string
1893 Utf32ToUtf8( &utf32Characters[startOfSelectedText], lengthOfSelectedText, selectedText );
1895 if( deleteAfterRetrieval ) // Only delete text if copied successfully
1897 // Keep a copy of the current input style.
1898 InputStyle currentInputStyle;
1899 currentInputStyle.Copy( mEventData->mInputStyle );
1901 // Set as input style the style of the first deleted character.
1902 mModel->mLogicalModel->RetrieveStyle( startOfSelectedText, mEventData->mInputStyle );
1904 // Compare if the input style has changed.
1905 const bool hasInputStyleChanged = !currentInputStyle.Equal( mEventData->mInputStyle );
1907 if( hasInputStyleChanged )
1909 const InputStyle::Mask styleChangedMask = currentInputStyle.GetInputStyleChangeMask( mEventData->mInputStyle );
1910 // Queue the input style changed signal.
1911 mEventData->mInputStyleChangedQueue.PushBack( styleChangedMask );
1914 mModel->mLogicalModel->UpdateTextStyleRuns( startOfSelectedText, -static_cast<int>( lengthOfSelectedText ) );
1916 // Mark the paragraphs to be updated.
1917 if( Layout::Engine::SINGLE_LINE_BOX == mLayoutEngine.GetLayout() )
1919 mTextUpdateInfo.mCharacterIndex = 0;
1920 mTextUpdateInfo.mNumberOfCharactersToRemove = mTextUpdateInfo.mPreviousNumberOfCharacters;
1921 mTextUpdateInfo.mNumberOfCharactersToAdd = mTextUpdateInfo.mPreviousNumberOfCharacters - lengthOfSelectedText;
1922 mTextUpdateInfo.mClearAll = true;
1926 mTextUpdateInfo.mCharacterIndex = startOfSelectedText;
1927 mTextUpdateInfo.mNumberOfCharactersToRemove = lengthOfSelectedText;
1930 // Delete text between handles
1931 Vector<Character>::Iterator first = utf32Characters.Begin() + startOfSelectedText;
1932 Vector<Character>::Iterator last = first + lengthOfSelectedText;
1933 utf32Characters.Erase( first, last );
1935 // Will show the cursor at the first character of the selection.
1936 mEventData->mPrimaryCursorPosition = handlesCrossed ? mEventData->mRightSelectionPosition : mEventData->mLeftSelectionPosition;
1940 // Will show the cursor at the last character of the selection.
1941 mEventData->mPrimaryCursorPosition = handlesCrossed ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition;
1944 mEventData->mDecoratorUpdated = true;
1948 void Controller::Impl::ShowClipboard()
1952 mClipboard.ShowClipboard();
1956 void Controller::Impl::HideClipboard()
1958 if( mClipboard && mClipboardHideEnabled )
1960 mClipboard.HideClipboard();
1964 void Controller::Impl::SetClipboardHideEnable(bool enable)
1966 mClipboardHideEnabled = enable;
1969 bool Controller::Impl::CopyStringToClipboard( std::string& source )
1971 //Send string to clipboard
1972 return ( mClipboard && mClipboard.SetItem( source ) );
1975 void Controller::Impl::SendSelectionToClipboard( bool deleteAfterSending )
1977 std::string selectedText;
1978 RetrieveSelection( selectedText, deleteAfterSending );
1979 CopyStringToClipboard( selectedText );
1980 ChangeState( EventData::EDITING );
1983 void Controller::Impl::RequestGetTextFromClipboard()
1987 mClipboard.RequestItem();
1991 void Controller::Impl::RepositionSelectionHandles()
1993 CharacterIndex selectionStart = mEventData->mLeftSelectionPosition;
1994 CharacterIndex selectionEnd = mEventData->mRightSelectionPosition;
1996 if( selectionStart == selectionEnd )
1998 // Nothing to select if handles are in the same place.
1999 // So, deactive Highlight box.
2000 mEventData->mDecorator->SetHighlightActive( false );
2004 mEventData->mDecorator->ClearHighlights();
2006 const GlyphIndex* const charactersToGlyphBuffer = mModel->mVisualModel->mCharactersToGlyph.Begin();
2007 const Length* const glyphsPerCharacterBuffer = mModel->mVisualModel->mGlyphsPerCharacter.Begin();
2008 const GlyphInfo* const glyphsBuffer = mModel->mVisualModel->mGlyphs.Begin();
2009 const Vector2* const positionsBuffer = mModel->mVisualModel->mGlyphPositions.Begin();
2010 const Length* const charactersPerGlyphBuffer = mModel->mVisualModel->mCharactersPerGlyph.Begin();
2011 const CharacterIndex* const glyphToCharacterBuffer = mModel->mVisualModel->mGlyphsToCharacters.Begin();
2012 const CharacterDirection* const modelCharacterDirectionsBuffer = ( 0u != mModel->mLogicalModel->mCharacterDirections.Count() ) ? mModel->mLogicalModel->mCharacterDirections.Begin() : NULL;
2014 const bool isLastCharacter = selectionEnd >= mModel->mLogicalModel->mText.Count();
2015 const CharacterDirection startDirection = ( ( NULL == modelCharacterDirectionsBuffer ) ? false : *( modelCharacterDirectionsBuffer + selectionStart ) );
2016 const CharacterDirection endDirection = ( ( NULL == modelCharacterDirectionsBuffer ) ? false : *( modelCharacterDirectionsBuffer + ( selectionEnd - ( isLastCharacter ? 1u : 0u ) ) ) );
2018 // Swap the indices if the start is greater than the end.
2019 const bool indicesSwapped = selectionStart > selectionEnd;
2021 // Tell the decorator to flip the selection handles if needed.
2022 mEventData->mDecorator->SetSelectionHandleFlipState( indicesSwapped, startDirection, endDirection );
2024 if( indicesSwapped )
2026 std::swap( selectionStart, selectionEnd );
2029 // Get the indices to the first and last selected glyphs.
2030 const CharacterIndex selectionEndMinusOne = selectionEnd - 1u;
2031 const GlyphIndex glyphStart = *( charactersToGlyphBuffer + selectionStart );
2032 const Length numberOfGlyphs = *( glyphsPerCharacterBuffer + selectionEndMinusOne );
2033 const GlyphIndex glyphEnd = *( charactersToGlyphBuffer + selectionEndMinusOne ) + ( ( numberOfGlyphs > 0 ) ? numberOfGlyphs - 1u : 0u );
2035 // Get the lines where the glyphs are laid-out.
2036 const LineRun* lineRun = mModel->mVisualModel->mLines.Begin();
2038 LineIndex lineIndex = 0u;
2039 Length numberOfLines = 0u;
2040 mModel->mVisualModel->GetNumberOfLines( glyphStart,
2041 1u + glyphEnd - glyphStart,
2044 const LineIndex firstLineIndex = lineIndex;
2046 // Create the structure to store some selection box info.
2047 Vector<SelectionBoxInfo> selectionBoxLinesInfo;
2048 selectionBoxLinesInfo.Resize( numberOfLines );
2050 SelectionBoxInfo* selectionBoxInfo = selectionBoxLinesInfo.Begin();
2051 selectionBoxInfo->minX = MAX_FLOAT;
2052 selectionBoxInfo->maxX = MIN_FLOAT;
2054 // Keep the min and max 'x' position to calculate the size and position of the highlighed text.
2055 float minHighlightX = std::numeric_limits<float>::max();
2056 float maxHighlightX = std::numeric_limits<float>::min();
2058 Vector2 highLightPosition; // The highlight position in decorator's coords.
2060 // Retrieve the first line and get the line's vertical offset, the line's height and the index to the last glyph.
2062 // The line's vertical offset of all the lines before the line where the first glyph is laid-out.
2063 selectionBoxInfo->lineOffset = CalculateLineOffset( mModel->mVisualModel->mLines,
2066 // Transform to decorator's (control) coords.
2067 selectionBoxInfo->lineOffset += mModel->mScrollPosition.y;
2069 lineRun += firstLineIndex;
2071 // The line height is the addition of the line ascender and the line descender.
2072 // However, the line descender has a negative value, hence the subtraction.
2073 selectionBoxInfo->lineHeight = lineRun->ascender - lineRun->descender;
2075 GlyphIndex lastGlyphOfLine = lineRun->glyphRun.glyphIndex + lineRun->glyphRun.numberOfGlyphs - 1u;
2077 // Check if the first glyph is a ligature that must be broken like Latin ff, fi, or Arabic ï»», etc which needs special code.
2078 const Length numberOfCharactersStart = *( charactersPerGlyphBuffer + glyphStart );
2079 bool splitStartGlyph = ( numberOfCharactersStart > 1u ) && HasLigatureMustBreak( mModel->mLogicalModel->GetScript( selectionStart ) );
2081 // Check if the last glyph is a ligature that must be broken like Latin ff, fi, or Arabic ï»», etc which needs special code.
2082 const Length numberOfCharactersEnd = *( charactersPerGlyphBuffer + glyphEnd );
2083 bool splitEndGlyph = ( glyphStart != glyphEnd ) && ( numberOfCharactersEnd > 1u ) && HasLigatureMustBreak( mModel->mLogicalModel->GetScript( selectionEndMinusOne ) );
2085 // The number of quads of the selection box.
2086 const unsigned int numberOfQuads = 1u + ( glyphEnd - glyphStart ) + ( ( numberOfLines > 1u ) ? 2u * numberOfLines : 0u );
2087 mEventData->mDecorator->ResizeHighlightQuads( numberOfQuads );
2089 // Count the actual number of quads.
2090 unsigned int actualNumberOfQuads = 0u;
2093 // Traverse the glyphs.
2094 for( GlyphIndex index = glyphStart; index <= glyphEnd; ++index )
2096 const GlyphInfo& glyph = *( glyphsBuffer + index );
2097 const Vector2& position = *( positionsBuffer + index );
2099 if( splitStartGlyph )
2101 // 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.
2103 const float glyphAdvance = glyph.advance / static_cast<float>( numberOfCharactersStart );
2104 const CharacterIndex interGlyphIndex = selectionStart - *( glyphToCharacterBuffer + glyphStart );
2105 // Get the direction of the character.
2106 CharacterDirection isCurrentRightToLeft = false;
2107 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
2109 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + selectionStart );
2112 // The end point could be in the middle of the ligature.
2113 // Calculate the number of characters selected.
2114 const Length numberOfCharacters = ( glyphStart == glyphEnd ) ? ( selectionEnd - selectionStart ) : ( numberOfCharactersStart - interGlyphIndex );
2116 quad.x = lineRun->alignmentOffset + position.x - glyph.xBearing + mModel->mScrollPosition.x + glyphAdvance * static_cast<float>( isCurrentRightToLeft ? ( numberOfCharactersStart - interGlyphIndex - numberOfCharacters ) : interGlyphIndex );
2117 quad.y = selectionBoxInfo->lineOffset;
2118 quad.z = quad.x + static_cast<float>( numberOfCharacters ) * glyphAdvance;
2119 quad.w = selectionBoxInfo->lineOffset + selectionBoxInfo->lineHeight;
2121 // Store the min and max 'x' for each line.
2122 selectionBoxInfo->minX = std::min( selectionBoxInfo->minX, quad.x );
2123 selectionBoxInfo->maxX = std::max( selectionBoxInfo->maxX, quad.z );
2125 mEventData->mDecorator->AddHighlight( actualNumberOfQuads, quad );
2126 ++actualNumberOfQuads;
2128 splitStartGlyph = false;
2132 if( splitEndGlyph && ( index == glyphEnd ) )
2134 // 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.
2136 const float glyphAdvance = glyph.advance / static_cast<float>( numberOfCharactersEnd );
2137 const CharacterIndex interGlyphIndex = selectionEnd - *( glyphToCharacterBuffer + glyphEnd );
2138 // Get the direction of the character.
2139 CharacterDirection isCurrentRightToLeft = false;
2140 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
2142 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + selectionEnd );
2145 const Length numberOfCharacters = numberOfCharactersEnd - interGlyphIndex;
2147 quad.x = lineRun->alignmentOffset + position.x - glyph.xBearing + mModel->mScrollPosition.x + ( isCurrentRightToLeft ? ( glyphAdvance * static_cast<float>( numberOfCharacters ) ) : 0.f );
2148 quad.y = selectionBoxInfo->lineOffset;
2149 quad.z = quad.x + static_cast<float>( interGlyphIndex ) * glyphAdvance;
2150 quad.w = quad.y + selectionBoxInfo->lineHeight;
2152 // Store the min and max 'x' for each line.
2153 selectionBoxInfo->minX = std::min( selectionBoxInfo->minX, quad.x );
2154 selectionBoxInfo->maxX = std::max( selectionBoxInfo->maxX, quad.z );
2156 mEventData->mDecorator->AddHighlight( actualNumberOfQuads,
2158 ++actualNumberOfQuads;
2160 splitEndGlyph = false;
2164 quad.x = lineRun->alignmentOffset + position.x - glyph.xBearing + mModel->mScrollPosition.x;
2165 quad.y = selectionBoxInfo->lineOffset;
2166 quad.z = quad.x + glyph.advance;
2167 quad.w = quad.y + selectionBoxInfo->lineHeight;
2169 // Store the min and max 'x' for each line.
2170 selectionBoxInfo->minX = std::min( selectionBoxInfo->minX, quad.x );
2171 selectionBoxInfo->maxX = std::max( selectionBoxInfo->maxX, quad.z );
2173 mEventData->mDecorator->AddHighlight( actualNumberOfQuads,
2175 ++actualNumberOfQuads;
2177 // Whether to retrieve the next line.
2178 if( index == lastGlyphOfLine )
2181 if( lineIndex < firstLineIndex + numberOfLines )
2183 // Retrieve the next line.
2186 // Get the last glyph of the new line.
2187 lastGlyphOfLine = lineRun->glyphRun.glyphIndex + lineRun->glyphRun.numberOfGlyphs - 1u;
2189 // Keep the offset and height of the current selection box.
2190 const float currentLineOffset = selectionBoxInfo->lineOffset;
2191 const float currentLineHeight = selectionBoxInfo->lineHeight;
2193 // Get the selection box info for the next line.
2196 selectionBoxInfo->minX = MAX_FLOAT;
2197 selectionBoxInfo->maxX = MIN_FLOAT;
2199 // Update the line's vertical offset.
2200 selectionBoxInfo->lineOffset = currentLineOffset + currentLineHeight;
2202 // The line height is the addition of the line ascender and the line descender.
2203 // However, the line descender has a negative value, hence the subtraction.
2204 selectionBoxInfo->lineHeight = lineRun->ascender - lineRun->descender;
2209 // Traverses all the lines and updates the min and max 'x' positions and the total height.
2210 // The final width is calculated after 'boxifying' the selection.
2211 for( Vector<SelectionBoxInfo>::ConstIterator it = selectionBoxLinesInfo.Begin(),
2212 endIt = selectionBoxLinesInfo.End();
2216 const SelectionBoxInfo& info = *it;
2218 // Update the size of the highlighted text.
2219 highLightSize.height += info.lineHeight;
2220 minHighlightX = std::min( minHighlightX, info.minX );
2221 maxHighlightX = std::max( maxHighlightX, info.maxX );
2224 // Add extra geometry to 'boxify' the selection.
2226 if( 1u < numberOfLines )
2228 // Boxify the first line.
2229 lineRun = mModel->mVisualModel->mLines.Begin() + firstLineIndex;
2230 const SelectionBoxInfo& firstSelectionBoxLineInfo = *( selectionBoxLinesInfo.Begin() );
2232 bool boxifyBegin = ( LTR != lineRun->direction ) && ( LTR != startDirection );
2233 bool boxifyEnd = ( LTR == lineRun->direction ) && ( LTR == startDirection );
2238 quad.y = firstSelectionBoxLineInfo.lineOffset;
2239 quad.z = firstSelectionBoxLineInfo.minX;
2240 quad.w = firstSelectionBoxLineInfo.lineOffset + firstSelectionBoxLineInfo.lineHeight;
2242 // Boxify at the beginning of the line.
2243 mEventData->mDecorator->AddHighlight( actualNumberOfQuads,
2245 ++actualNumberOfQuads;
2247 // Update the size of the highlighted text.
2248 minHighlightX = 0.f;
2253 quad.x = firstSelectionBoxLineInfo.maxX;
2254 quad.y = firstSelectionBoxLineInfo.lineOffset;
2255 quad.z = mModel->mVisualModel->mControlSize.width;
2256 quad.w = firstSelectionBoxLineInfo.lineOffset + firstSelectionBoxLineInfo.lineHeight;
2258 // Boxify at the end of the line.
2259 mEventData->mDecorator->AddHighlight( actualNumberOfQuads,
2261 ++actualNumberOfQuads;
2263 // Update the size of the highlighted text.
2264 maxHighlightX = mModel->mVisualModel->mControlSize.width;
2267 // Boxify the central lines.
2268 if( 2u < numberOfLines )
2270 for( Vector<SelectionBoxInfo>::ConstIterator it = selectionBoxLinesInfo.Begin() + 1u,
2271 endIt = selectionBoxLinesInfo.End() - 1u;
2275 const SelectionBoxInfo& info = *it;
2278 quad.y = info.lineOffset;
2280 quad.w = info.lineOffset + info.lineHeight;
2282 mEventData->mDecorator->AddHighlight( actualNumberOfQuads,
2284 ++actualNumberOfQuads;
2287 quad.y = info.lineOffset;
2288 quad.z = mModel->mVisualModel->mControlSize.width;
2289 quad.w = info.lineOffset + info.lineHeight;
2291 mEventData->mDecorator->AddHighlight( actualNumberOfQuads,
2293 ++actualNumberOfQuads;
2296 // Update the size of the highlighted text.
2297 minHighlightX = 0.f;
2298 maxHighlightX = mModel->mVisualModel->mControlSize.width;
2301 // Boxify the last line.
2302 lineRun = mModel->mVisualModel->mLines.Begin() + firstLineIndex + numberOfLines - 1u;
2303 const SelectionBoxInfo& lastSelectionBoxLineInfo = *( selectionBoxLinesInfo.End() - 1u );
2305 boxifyBegin = ( LTR == lineRun->direction ) && ( LTR == endDirection );
2306 boxifyEnd = ( LTR != lineRun->direction ) && ( LTR != endDirection );
2311 quad.y = lastSelectionBoxLineInfo.lineOffset;
2312 quad.z = lastSelectionBoxLineInfo.minX;
2313 quad.w = lastSelectionBoxLineInfo.lineOffset + lastSelectionBoxLineInfo.lineHeight;
2315 // Boxify at the beginning of the line.
2316 mEventData->mDecorator->AddHighlight( actualNumberOfQuads,
2318 ++actualNumberOfQuads;
2320 // Update the size of the highlighted text.
2321 minHighlightX = 0.f;
2326 quad.x = lastSelectionBoxLineInfo.maxX;
2327 quad.y = lastSelectionBoxLineInfo.lineOffset;
2328 quad.z = mModel->mVisualModel->mControlSize.width;
2329 quad.w = lastSelectionBoxLineInfo.lineOffset + lastSelectionBoxLineInfo.lineHeight;
2331 // Boxify at the end of the line.
2332 mEventData->mDecorator->AddHighlight( actualNumberOfQuads,
2334 ++actualNumberOfQuads;
2336 // Update the size of the highlighted text.
2337 maxHighlightX = mModel->mVisualModel->mControlSize.width;
2341 // Set the actual number of quads.
2342 mEventData->mDecorator->ResizeHighlightQuads( actualNumberOfQuads );
2344 // Sets the highlight's size and position. In decorator's coords.
2345 // The highlight's height has been calculated above (before 'boxifying' the highlight).
2346 highLightSize.width = maxHighlightX - minHighlightX;
2348 highLightPosition.x = minHighlightX;
2349 const SelectionBoxInfo& firstSelectionBoxLineInfo = *( selectionBoxLinesInfo.Begin() );
2350 highLightPosition.y = firstSelectionBoxLineInfo.lineOffset;
2352 mEventData->mDecorator->SetHighLightBox( highLightPosition, highLightSize, static_cast<float>( mModel->GetOutlineWidth() ) );
2354 if( !mEventData->mDecorator->IsSmoothHandlePanEnabled() )
2356 CursorInfo primaryCursorInfo;
2357 GetCursorPosition( mEventData->mLeftSelectionPosition,
2358 primaryCursorInfo );
2360 const Vector2 primaryPosition = primaryCursorInfo.primaryPosition + mModel->mScrollPosition;
2362 mEventData->mDecorator->SetPosition( LEFT_SELECTION_HANDLE,
2364 primaryCursorInfo.lineOffset + mModel->mScrollPosition.y,
2365 primaryCursorInfo.lineHeight );
2367 CursorInfo secondaryCursorInfo;
2368 GetCursorPosition( mEventData->mRightSelectionPosition,
2369 secondaryCursorInfo );
2371 const Vector2 secondaryPosition = secondaryCursorInfo.primaryPosition + mModel->mScrollPosition;
2373 mEventData->mDecorator->SetPosition( RIGHT_SELECTION_HANDLE,
2374 secondaryPosition.x,
2375 secondaryCursorInfo.lineOffset + mModel->mScrollPosition.y,
2376 secondaryCursorInfo.lineHeight );
2379 // Set the flag to update the decorator.
2380 mEventData->mDecoratorUpdated = true;
2383 void Controller::Impl::RepositionSelectionHandles( float visualX, float visualY, Controller::NoTextTap::Action action )
2385 if( NULL == mEventData )
2387 // Nothing to do if there is no text input.
2391 if( IsShowingPlaceholderText() )
2393 // Nothing to do if there is the place-holder text.
2397 const Length numberOfGlyphs = mModel->mVisualModel->mGlyphs.Count();
2398 const Length numberOfLines = mModel->mVisualModel->mLines.Count();
2399 if( ( 0 == numberOfGlyphs ) ||
2400 ( 0 == numberOfLines ) )
2402 // Nothing to do if there is no text.
2406 // Find which word was selected
2407 CharacterIndex selectionStart( 0 );
2408 CharacterIndex selectionEnd( 0 );
2409 CharacterIndex noTextHitIndex( 0 );
2410 const bool characterHit = FindSelectionIndices( mModel->mVisualModel,
2411 mModel->mLogicalModel,
2418 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "%p selectionStart %d selectionEnd %d\n", this, selectionStart, selectionEnd );
2420 if( characterHit || ( Controller::NoTextTap::HIGHLIGHT == action ) )
2422 ChangeState( EventData::SELECTING );
2424 mEventData->mLeftSelectionPosition = selectionStart;
2425 mEventData->mRightSelectionPosition = selectionEnd;
2427 mEventData->mUpdateLeftSelectionPosition = true;
2428 mEventData->mUpdateRightSelectionPosition = true;
2429 mEventData->mUpdateHighlightBox = true;
2431 // It may happen an IMF commit event arrives before the selection event
2432 // if the IMF manager is in pre-edit state. The commit event will set the
2433 // mEventData->mUpdateCursorPosition flag to true. If it's not set back
2434 // to false, the highlight box won't be updated.
2435 mEventData->mUpdateCursorPosition = false;
2437 mEventData->mScrollAfterUpdatePosition = ( mEventData->mLeftSelectionPosition != mEventData->mRightSelectionPosition );
2439 // Cursor to be positioned at end of selection so if selection interrupted and edit mode restarted the cursor will be at end of selection
2440 mEventData->mPrimaryCursorPosition = std::max( mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition );
2442 else if( Controller::NoTextTap::SHOW_SELECTION_POPUP == action )
2444 // Nothing to select. i.e. a white space, out of bounds
2445 ChangeState( EventData::EDITING_WITH_POPUP );
2447 mEventData->mPrimaryCursorPosition = noTextHitIndex;
2449 mEventData->mUpdateCursorPosition = true;
2450 mEventData->mUpdateGrabHandlePosition = true;
2451 mEventData->mScrollAfterUpdatePosition = true;
2452 mEventData->mUpdateInputStyle = true;
2454 else if( Controller::NoTextTap::NO_ACTION == action )
2456 // Nothing to select. i.e. a white space, out of bounds
2457 mEventData->mPrimaryCursorPosition = noTextHitIndex;
2459 mEventData->mUpdateCursorPosition = true;
2460 mEventData->mUpdateGrabHandlePosition = true;
2461 mEventData->mScrollAfterUpdatePosition = true;
2462 mEventData->mUpdateInputStyle = true;
2466 void Controller::Impl::SetPopupButtons()
2469 * Sets the Popup buttons to be shown depending on State.
2471 * If SELECTING : CUT & COPY + ( PASTE & CLIPBOARD if content available to paste )
2473 * If EDITING_WITH_POPUP : SELECT & SELECT_ALL
2476 TextSelectionPopup::Buttons buttonsToShow = TextSelectionPopup::NONE;
2478 if( EventData::SELECTING == mEventData->mState )
2480 buttonsToShow = TextSelectionPopup::Buttons( TextSelectionPopup::CUT | TextSelectionPopup::COPY );
2482 if( !IsClipboardEmpty() )
2484 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
2485 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::CLIPBOARD ) );
2488 if( !mEventData->mAllTextSelected )
2490 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::SELECT_ALL ) );
2493 else if( EventData::EDITING_WITH_POPUP == mEventData->mState )
2495 if( mModel->mLogicalModel->mText.Count() && !IsShowingPlaceholderText() )
2497 buttonsToShow = TextSelectionPopup::Buttons( TextSelectionPopup::SELECT | TextSelectionPopup::SELECT_ALL );
2500 if( !IsClipboardEmpty() )
2502 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
2503 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::CLIPBOARD ) );
2506 else if( EventData::EDITING_WITH_PASTE_POPUP == mEventData->mState )
2508 if ( !IsClipboardEmpty() )
2510 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
2511 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::CLIPBOARD ) );
2515 mEventData->mDecorator->SetEnabledPopupButtons( buttonsToShow );
2518 void Controller::Impl::ChangeState( EventData::State newState )
2520 if( NULL == mEventData )
2522 // Nothing to do if there is no text input.
2526 DALI_LOG_INFO( gLogFilter, Debug::General, "ChangeState state:%d newstate:%d\n", mEventData->mState, newState );
2528 if( mEventData->mState != newState )
2530 mEventData->mPreviousState = mEventData->mState;
2531 mEventData->mState = newState;
2533 switch( mEventData->mState )
2535 case EventData::INACTIVE:
2537 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
2538 mEventData->mDecorator->StopCursorBlink();
2539 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
2540 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
2541 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
2542 mEventData->mDecorator->SetHighlightActive( false );
2543 mEventData->mDecorator->SetPopupActive( false );
2544 mEventData->mDecoratorUpdated = true;
2547 case EventData::INTERRUPTED:
2549 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
2550 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
2551 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
2552 mEventData->mDecorator->SetHighlightActive( false );
2553 mEventData->mDecorator->SetPopupActive( false );
2554 mEventData->mDecoratorUpdated = true;
2557 case EventData::SELECTING:
2559 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
2560 mEventData->mDecorator->StopCursorBlink();
2561 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
2562 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true );
2563 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true );
2564 mEventData->mDecorator->SetHighlightActive( true );
2565 if( mEventData->mGrabHandlePopupEnabled )
2568 mEventData->mDecorator->SetPopupActive( true );
2570 mEventData->mDecoratorUpdated = true;
2573 case EventData::EDITING:
2575 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
2576 if( mEventData->mCursorBlinkEnabled )
2578 mEventData->mDecorator->StartCursorBlink();
2580 // Grab handle is not shown until a tap is received whilst EDITING
2581 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
2582 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
2583 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
2584 mEventData->mDecorator->SetHighlightActive( false );
2585 if( mEventData->mGrabHandlePopupEnabled )
2587 mEventData->mDecorator->SetPopupActive( false );
2589 mEventData->mDecoratorUpdated = true;
2592 case EventData::EDITING_WITH_POPUP:
2594 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "EDITING_WITH_POPUP \n", newState );
2596 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
2597 if( mEventData->mCursorBlinkEnabled )
2599 mEventData->mDecorator->StartCursorBlink();
2601 if( mEventData->mSelectionEnabled )
2603 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
2604 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
2605 mEventData->mDecorator->SetHighlightActive( false );
2609 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
2611 if( mEventData->mGrabHandlePopupEnabled )
2614 mEventData->mDecorator->SetPopupActive( true );
2616 mEventData->mDecoratorUpdated = true;
2619 case EventData::EDITING_WITH_GRAB_HANDLE:
2621 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "EDITING_WITH_GRAB_HANDLE \n", newState );
2623 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
2624 if( mEventData->mCursorBlinkEnabled )
2626 mEventData->mDecorator->StartCursorBlink();
2628 // Grab handle is not shown until a tap is received whilst EDITING
2629 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
2630 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
2631 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
2632 mEventData->mDecorator->SetHighlightActive( false );
2633 if( mEventData->mGrabHandlePopupEnabled )
2635 mEventData->mDecorator->SetPopupActive( false );
2637 mEventData->mDecoratorUpdated = true;
2640 case EventData::SELECTION_HANDLE_PANNING:
2642 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
2643 mEventData->mDecorator->StopCursorBlink();
2644 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
2645 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true );
2646 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true );
2647 mEventData->mDecorator->SetHighlightActive( true );
2648 if( mEventData->mGrabHandlePopupEnabled )
2650 mEventData->mDecorator->SetPopupActive( false );
2652 mEventData->mDecoratorUpdated = true;
2655 case EventData::GRAB_HANDLE_PANNING:
2657 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "GRAB_HANDLE_PANNING \n", newState );
2659 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
2660 if( mEventData->mCursorBlinkEnabled )
2662 mEventData->mDecorator->StartCursorBlink();
2664 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
2665 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
2666 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
2667 mEventData->mDecorator->SetHighlightActive( false );
2668 if( mEventData->mGrabHandlePopupEnabled )
2670 mEventData->mDecorator->SetPopupActive( false );
2672 mEventData->mDecoratorUpdated = true;
2675 case EventData::EDITING_WITH_PASTE_POPUP:
2677 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "EDITING_WITH_PASTE_POPUP \n", newState );
2679 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
2680 if( mEventData->mCursorBlinkEnabled )
2682 mEventData->mDecorator->StartCursorBlink();
2685 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
2686 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
2687 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
2688 mEventData->mDecorator->SetHighlightActive( false );
2690 if( mEventData->mGrabHandlePopupEnabled )
2693 mEventData->mDecorator->SetPopupActive( true );
2695 mEventData->mDecoratorUpdated = true;
2698 case EventData::TEXT_PANNING:
2700 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
2701 mEventData->mDecorator->StopCursorBlink();
2702 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
2703 if( mEventData->mDecorator->IsHandleActive( LEFT_SELECTION_HANDLE ) ||
2704 mEventData->mDecorator->IsHandleActive( RIGHT_SELECTION_HANDLE ) )
2706 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
2707 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
2708 mEventData->mDecorator->SetHighlightActive( true );
2711 if( mEventData->mGrabHandlePopupEnabled )
2713 mEventData->mDecorator->SetPopupActive( false );
2716 mEventData->mDecoratorUpdated = true;
2723 void Controller::Impl::GetCursorPosition( CharacterIndex logical,
2724 CursorInfo& cursorInfo )
2726 if( !IsShowingRealText() )
2728 // Do not want to use the place-holder text to set the cursor position.
2730 // Use the line's height of the font's family set to set the cursor's size.
2731 // If there is no font's family set, use the default font.
2732 // Use the current alignment to place the cursor at the beginning, center or end of the box.
2734 cursorInfo.lineOffset = 0.f;
2735 cursorInfo.lineHeight = GetDefaultFontLineHeight();
2736 cursorInfo.primaryCursorHeight = cursorInfo.lineHeight;
2738 switch( mModel->mHorizontalAlignment )
2740 case Text::HorizontalAlignment::BEGIN :
2742 cursorInfo.primaryPosition.x = 0.f;
2745 case Text::HorizontalAlignment::CENTER:
2747 cursorInfo.primaryPosition.x = floorf( 0.5f * mModel->mVisualModel->mControlSize.width );
2750 case Text::HorizontalAlignment::END:
2752 cursorInfo.primaryPosition.x = mModel->mVisualModel->mControlSize.width - mEventData->mDecorator->GetCursorWidth();
2757 // Nothing else to do.
2761 const bool isMultiLine = ( Layout::Engine::MULTI_LINE_BOX == mLayoutEngine.GetLayout() );
2762 GetCursorPositionParameters parameters;
2763 parameters.visualModel = mModel->mVisualModel;
2764 parameters.logicalModel = mModel->mLogicalModel;
2765 parameters.metrics = mMetrics;
2766 parameters.logical = logical;
2767 parameters.isMultiline = isMultiLine;
2769 Text::GetCursorPosition( parameters,
2772 // Adds Outline offset.
2773 const float outlineWidth = static_cast<float>( mModel->GetOutlineWidth() );
2774 cursorInfo.primaryPosition.x += outlineWidth;
2775 cursorInfo.primaryPosition.y += outlineWidth;
2776 cursorInfo.secondaryPosition.x += outlineWidth;
2777 cursorInfo.secondaryPosition.y += outlineWidth;
2781 // If the text is editable and multi-line, the cursor position after a white space shouldn't exceed the boundaries of the text control.
2783 // Note the white spaces laid-out at the end of the line might exceed the boundaries of the control.
2784 // The reason is a wrapped line must not start with a white space so they are laid-out at the end of the line.
2786 if( 0.f > cursorInfo.primaryPosition.x )
2788 cursorInfo.primaryPosition.x = 0.f;
2791 const float edgeWidth = mModel->mVisualModel->mControlSize.width - static_cast<float>( mEventData->mDecorator->GetCursorWidth() );
2792 if( cursorInfo.primaryPosition.x > edgeWidth )
2794 cursorInfo.primaryPosition.x = edgeWidth;
2799 CharacterIndex Controller::Impl::CalculateNewCursorIndex( CharacterIndex index ) const
2801 if( NULL == mEventData )
2803 // Nothing to do if there is no text input.
2807 CharacterIndex cursorIndex = mEventData->mPrimaryCursorPosition;
2809 const GlyphIndex* const charactersToGlyphBuffer = mModel->mVisualModel->mCharactersToGlyph.Begin();
2810 const Length* const charactersPerGlyphBuffer = mModel->mVisualModel->mCharactersPerGlyph.Begin();
2812 GlyphIndex glyphIndex = *( charactersToGlyphBuffer + index );
2813 Length numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
2815 if( numberOfCharacters > 1u )
2817 const Script script = mModel->mLogicalModel->GetScript( index );
2818 if( HasLigatureMustBreak( script ) )
2820 // Prevents to jump the whole Latin ligatures like fi, ff, or Arabic ï»», ...
2821 numberOfCharacters = 1u;
2826 while( 0u == numberOfCharacters )
2829 numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
2833 if( index < mEventData->mPrimaryCursorPosition )
2835 cursorIndex -= numberOfCharacters;
2839 cursorIndex += numberOfCharacters;
2842 // Will update the cursor hook position.
2843 mEventData->mUpdateCursorHookPosition = true;
2848 void Controller::Impl::UpdateCursorPosition( const CursorInfo& cursorInfo )
2850 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "-->Controller::UpdateCursorPosition %p\n", this );
2851 if( NULL == mEventData )
2853 // Nothing to do if there is no text input.
2854 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition no event data\n" );
2858 const Vector2 cursorPosition = cursorInfo.primaryPosition + mModel->mScrollPosition;
2860 mEventData->mDecorator->SetGlyphOffset( PRIMARY_CURSOR, cursorInfo.glyphOffset );
2862 // Sets the cursor position.
2863 mEventData->mDecorator->SetPosition( PRIMARY_CURSOR,
2866 cursorInfo.primaryCursorHeight,
2867 cursorInfo.lineHeight );
2868 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Primary cursor position: %f,%f\n", cursorPosition.x, cursorPosition.y );
2870 if( mEventData->mUpdateGrabHandlePosition )
2872 // Sets the grab handle position.
2873 mEventData->mDecorator->SetPosition( GRAB_HANDLE,
2875 cursorInfo.lineOffset + mModel->mScrollPosition.y,
2876 cursorInfo.lineHeight );
2879 if( cursorInfo.isSecondaryCursor )
2881 mEventData->mDecorator->SetPosition( SECONDARY_CURSOR,
2882 cursorInfo.secondaryPosition.x + mModel->mScrollPosition.x,
2883 cursorInfo.secondaryPosition.y + mModel->mScrollPosition.y,
2884 cursorInfo.secondaryCursorHeight,
2885 cursorInfo.lineHeight );
2886 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Secondary cursor position: %f,%f\n", cursorInfo.secondaryPosition.x + mModel->mScrollPosition.x, cursorInfo.secondaryPosition.y + mModel->mScrollPosition.y );
2889 // Set which cursors are active according the state.
2890 if( EventData::IsEditingState( mEventData->mState ) || ( EventData::GRAB_HANDLE_PANNING == mEventData->mState ) )
2892 if( cursorInfo.isSecondaryCursor )
2894 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_BOTH );
2898 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
2903 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
2906 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition\n" );
2909 void Controller::Impl::UpdateSelectionHandle( HandleType handleType,
2910 const CursorInfo& cursorInfo )
2912 if( ( LEFT_SELECTION_HANDLE != handleType ) &&
2913 ( RIGHT_SELECTION_HANDLE != handleType ) )
2918 const Vector2 cursorPosition = cursorInfo.primaryPosition + mModel->mScrollPosition;
2920 // Sets the handle's position.
2921 mEventData->mDecorator->SetPosition( handleType,
2923 cursorInfo.lineOffset + mModel->mScrollPosition.y,
2924 cursorInfo.lineHeight );
2926 // If selection handle at start of the text and other at end of the text then all text is selected.
2927 const CharacterIndex startOfSelection = std::min( mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition );
2928 const CharacterIndex endOfSelection = std::max ( mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition );
2929 mEventData->mAllTextSelected = ( startOfSelection == 0 ) && ( endOfSelection == mModel->mLogicalModel->mText.Count() );
2932 void Controller::Impl::ClampHorizontalScroll( const Vector2& layoutSize )
2934 // Clamp between -space & -alignment offset.
2936 if( layoutSize.width > mModel->mVisualModel->mControlSize.width )
2938 const float space = ( layoutSize.width - mModel->mVisualModel->mControlSize.width ) + mModel->mAlignmentOffset;
2939 mModel->mScrollPosition.x = ( mModel->mScrollPosition.x < -space ) ? -space : mModel->mScrollPosition.x;
2940 mModel->mScrollPosition.x = ( mModel->mScrollPosition.x > -mModel->mAlignmentOffset ) ? -mModel->mAlignmentOffset : mModel->mScrollPosition.x;
2942 mEventData->mDecoratorUpdated = true;
2946 mModel->mScrollPosition.x = 0.f;
2950 void Controller::Impl::ClampVerticalScroll( const Vector2& layoutSize )
2952 if( Layout::Engine::SINGLE_LINE_BOX == mLayoutEngine.GetLayout() )
2954 // Nothing to do if the text is single line.
2958 // Clamp between -space & 0.
2959 if( layoutSize.height > mModel->mVisualModel->mControlSize.height )
2961 const float space = ( layoutSize.height - mModel->mVisualModel->mControlSize.height );
2962 mModel->mScrollPosition.y = ( mModel->mScrollPosition.y < -space ) ? -space : mModel->mScrollPosition.y;
2963 mModel->mScrollPosition.y = ( mModel->mScrollPosition.y > 0.f ) ? 0.f : mModel->mScrollPosition.y;
2965 mEventData->mDecoratorUpdated = true;
2969 mModel->mScrollPosition.y = 0.f;
2973 void Controller::Impl::ScrollToMakePositionVisible( const Vector2& position, float lineHeight )
2975 const float cursorWidth = mEventData->mDecorator ? static_cast<float>( mEventData->mDecorator->GetCursorWidth() ) : 0.f;
2977 // position is in actor's coords.
2978 const float positionEndX = position.x + cursorWidth;
2979 const float positionEndY = position.y + lineHeight;
2981 // Transform the position to decorator coords.
2982 const float decoratorPositionBeginX = position.x + mModel->mScrollPosition.x;
2983 const float decoratorPositionEndX = positionEndX + mModel->mScrollPosition.x;
2985 const float decoratorPositionBeginY = position.y + mModel->mScrollPosition.y;
2986 const float decoratorPositionEndY = positionEndY + mModel->mScrollPosition.y;
2988 if( decoratorPositionBeginX < 0.f )
2990 mModel->mScrollPosition.x = -position.x;
2992 else if( decoratorPositionEndX > mModel->mVisualModel->mControlSize.width )
2994 mModel->mScrollPosition.x = mModel->mVisualModel->mControlSize.width - positionEndX;
2997 if( Layout::Engine::MULTI_LINE_BOX == mLayoutEngine.GetLayout() )
2999 if( decoratorPositionBeginY < 0.f )
3001 mModel->mScrollPosition.y = -position.y;
3003 else if( decoratorPositionEndY > mModel->mVisualModel->mControlSize.height )
3005 mModel->mScrollPosition.y = mModel->mVisualModel->mControlSize.height - positionEndY;
3010 void Controller::Impl::ScrollTextToMatchCursor( const CursorInfo& cursorInfo )
3012 // Get the current cursor position in decorator coords.
3013 const Vector2& currentCursorPosition = mEventData->mDecorator->GetPosition( PRIMARY_CURSOR );
3015 const LineIndex lineIndex = mModel->mVisualModel->GetLineOfCharacter( mEventData->mPrimaryCursorPosition );
3019 // Calculate the offset to match the cursor position before the character was deleted.
3020 mModel->mScrollPosition.x = currentCursorPosition.x - cursorInfo.primaryPosition.x;
3022 //If text control has more than two lines and current line index is not last, calculate scrollpositionY
3023 if( mModel->mVisualModel->mLines.Count() > 1u && lineIndex != mModel->mVisualModel->mLines.Count() -1u )
3025 const float currentCursorGlyphOffset = mEventData->mDecorator->GetGlyphOffset( PRIMARY_CURSOR );
3026 mModel->mScrollPosition.y = currentCursorPosition.y - cursorInfo.lineOffset - currentCursorGlyphOffset;
3030 ClampHorizontalScroll( mModel->mVisualModel->GetLayoutSize() );
3031 ClampVerticalScroll( mModel->mVisualModel->GetLayoutSize() );
3033 // Makes the new cursor position visible if needed.
3034 ScrollToMakePositionVisible( cursorInfo.primaryPosition, cursorInfo.lineHeight );
3037 void Controller::Impl::RequestRelayout()
3039 if( NULL != mControlInterface )
3041 mControlInterface->RequestTextRelayout();
3047 } // namespace Toolkit