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 );
825 startIndex = mTextUpdateInfo.mParagraphCharacterIndex;
827 if( mTextUpdateInfo.mClearAll ||
828 ( 0u != paragraphCharacters ) )
830 ClearModelData( startIndex, startIndex + ( ( paragraphCharacters > 0u ) ? paragraphCharacters - 1u : 0u ), operations );
833 mTextUpdateInfo.mClearAll = false;
835 // Whether the model is updated.
836 bool updated = false;
838 Vector<LineBreakInfo>& lineBreakInfo = mModel->mLogicalModel->mLineBreakInfo;
839 const Length requestedNumberOfCharacters = mTextUpdateInfo.mRequestedNumberOfCharacters;
841 if( NO_OPERATION != ( GET_LINE_BREAKS & operations ) )
843 // Retrieves the line break info. The line break info is used to split the text in 'paragraphs' to
844 // calculate the bidirectional info for each 'paragraph'.
845 // It's also used to layout the text (where it should be a new line) or to shape the text (text in different lines
846 // is not shaped together).
847 lineBreakInfo.Resize( numberOfCharacters, TextAbstraction::LINE_NO_BREAK );
849 SetLineBreakInfo( utf32Characters,
851 requestedNumberOfCharacters,
854 // Create the paragraph info.
855 mModel->mLogicalModel->CreateParagraphInfo( startIndex,
856 requestedNumberOfCharacters );
860 Vector<WordBreakInfo>& wordBreakInfo = mModel->mLogicalModel->mWordBreakInfo;
861 if( NO_OPERATION != ( GET_WORD_BREAKS & operations ) )
863 // Retrieves the word break info. The word break info is used to layout the text (where to wrap the text in lines).
864 wordBreakInfo.Resize( numberOfCharacters, TextAbstraction::WORD_NO_BREAK );
866 SetWordBreakInfo( utf32Characters,
868 requestedNumberOfCharacters,
873 const bool getScripts = NO_OPERATION != ( GET_SCRIPTS & operations );
874 const bool validateFonts = NO_OPERATION != ( VALIDATE_FONTS & operations );
876 Vector<ScriptRun>& scripts = mModel->mLogicalModel->mScriptRuns;
877 Vector<FontRun>& validFonts = mModel->mLogicalModel->mFontRuns;
879 if( getScripts || validateFonts )
881 // Validates the fonts assigned by the application or assigns default ones.
882 // It makes sure all the characters are going to be rendered by the correct font.
883 MultilanguageSupport multilanguageSupport = MultilanguageSupport::Get();
887 // Retrieves the scripts used in the text.
888 multilanguageSupport.SetScripts( utf32Characters,
890 requestedNumberOfCharacters,
896 // Validate the fonts set through the mark-up string.
897 Vector<FontDescriptionRun>& fontDescriptionRuns = mModel->mLogicalModel->mFontDescriptionRuns;
899 // Get the default font's description.
900 TextAbstraction::FontDescription defaultFontDescription;
901 TextAbstraction::PointSize26Dot6 defaultPointSize = TextAbstraction::FontClient::DEFAULT_POINT_SIZE;
903 if( IsShowingPlaceholderText() && mEventData && ( NULL != mEventData->mPlaceholderFont ) )
905 // If the placeholder font is set specifically, only placeholder font is changed.
906 defaultFontDescription = mEventData->mPlaceholderFont->mFontDescription;
907 if( mEventData->mPlaceholderFont->sizeDefined )
909 defaultPointSize = mEventData->mPlaceholderFont->mDefaultPointSize * 64u;
912 else if( NULL != mFontDefaults )
914 // Set the normal font and the placeholder font.
915 defaultFontDescription = mFontDefaults->mFontDescription;
916 defaultPointSize = mFontDefaults->mDefaultPointSize * 64u;
919 // Validates the fonts. If there is a character with no assigned font it sets a default one.
920 // After this call, fonts are validated.
921 multilanguageSupport.ValidateFonts( utf32Characters,
924 defaultFontDescription,
927 requestedNumberOfCharacters,
933 Vector<Character> mirroredUtf32Characters;
934 bool textMirrored = false;
935 const Length numberOfParagraphs = mModel->mLogicalModel->mParagraphInfo.Count();
936 if( NO_OPERATION != ( BIDI_INFO & operations ) )
938 Vector<BidirectionalParagraphInfoRun>& bidirectionalInfo = mModel->mLogicalModel->mBidirectionalParagraphInfo;
939 bidirectionalInfo.Reserve( numberOfParagraphs );
941 // Calculates the bidirectional info for the whole paragraph if it contains right to left scripts.
942 SetBidirectionalInfo( utf32Characters,
946 requestedNumberOfCharacters,
949 if( 0u != bidirectionalInfo.Count() )
951 // Only set the character directions if there is right to left characters.
952 Vector<CharacterDirection>& directions = mModel->mLogicalModel->mCharacterDirections;
953 GetCharactersDirection( bidirectionalInfo,
956 requestedNumberOfCharacters,
959 // This paragraph has right to left text. Some characters may need to be mirrored.
960 // TODO: consider if the mirrored string can be stored as well.
962 textMirrored = GetMirroredText( utf32Characters,
966 requestedNumberOfCharacters,
967 mirroredUtf32Characters );
971 // There is no right to left characters. Clear the directions vector.
972 mModel->mLogicalModel->mCharacterDirections.Clear();
977 Vector<GlyphInfo>& glyphs = mModel->mVisualModel->mGlyphs;
978 Vector<CharacterIndex>& glyphsToCharactersMap = mModel->mVisualModel->mGlyphsToCharacters;
979 Vector<Length>& charactersPerGlyph = mModel->mVisualModel->mCharactersPerGlyph;
980 Vector<GlyphIndex> newParagraphGlyphs;
981 newParagraphGlyphs.Reserve( numberOfParagraphs );
983 const Length currentNumberOfGlyphs = glyphs.Count();
984 if( NO_OPERATION != ( SHAPE_TEXT & operations ) )
986 const Vector<Character>& textToShape = textMirrored ? mirroredUtf32Characters : utf32Characters;
988 ShapeText( textToShape,
993 mTextUpdateInfo.mStartGlyphIndex,
994 requestedNumberOfCharacters,
996 glyphsToCharactersMap,
998 newParagraphGlyphs );
1000 // Create the 'number of glyphs' per character and the glyph to character conversion tables.
1001 mModel->mVisualModel->CreateGlyphsPerCharacterTable( startIndex, mTextUpdateInfo.mStartGlyphIndex, requestedNumberOfCharacters );
1002 mModel->mVisualModel->CreateCharacterToGlyphTable( startIndex, mTextUpdateInfo.mStartGlyphIndex, requestedNumberOfCharacters );
1006 const Length numberOfGlyphs = glyphs.Count() - currentNumberOfGlyphs;
1008 if( NO_OPERATION != ( GET_GLYPH_METRICS & operations ) )
1010 GlyphInfo* glyphsBuffer = glyphs.Begin();
1011 mMetrics->GetGlyphMetrics( glyphsBuffer + mTextUpdateInfo.mStartGlyphIndex, numberOfGlyphs );
1013 // Update the width and advance of all new paragraph characters.
1014 for( Vector<GlyphIndex>::ConstIterator it = newParagraphGlyphs.Begin(), endIt = newParagraphGlyphs.End(); it != endIt; ++it )
1016 const GlyphIndex index = *it;
1017 GlyphInfo& glyph = *( glyphsBuffer + index );
1019 glyph.xBearing = 0.f;
1021 glyph.advance = 0.f;
1026 if( NO_OPERATION != ( COLOR & operations ) )
1028 // Set the color runs in glyphs.
1029 SetColorSegmentationInfo( mModel->mLogicalModel->mColorRuns,
1030 mModel->mVisualModel->mCharactersToGlyph,
1031 mModel->mVisualModel->mGlyphsPerCharacter,
1033 mTextUpdateInfo.mStartGlyphIndex,
1034 requestedNumberOfCharacters,
1035 mModel->mVisualModel->mColors,
1036 mModel->mVisualModel->mColorIndices );
1041 if( ( NULL != mEventData ) &&
1042 mEventData->mPreEditFlag &&
1043 ( 0u != mModel->mVisualModel->mCharactersToGlyph.Count() ) )
1045 // Add the underline for the pre-edit text.
1046 const GlyphIndex* const charactersToGlyphBuffer = mModel->mVisualModel->mCharactersToGlyph.Begin();
1047 const Length* const glyphsPerCharacterBuffer = mModel->mVisualModel->mGlyphsPerCharacter.Begin();
1049 const GlyphIndex glyphStart = *( charactersToGlyphBuffer + mEventData->mPreEditStartPosition );
1050 const CharacterIndex lastPreEditCharacter = mEventData->mPreEditStartPosition + ( ( mEventData->mPreEditLength > 0u ) ? mEventData->mPreEditLength - 1u : 0u );
1051 const Length numberOfGlyphsLastCharacter = *( glyphsPerCharacterBuffer + lastPreEditCharacter );
1052 const GlyphIndex glyphEnd = *( charactersToGlyphBuffer + lastPreEditCharacter ) + ( numberOfGlyphsLastCharacter > 1u ? numberOfGlyphsLastCharacter - 1u : 0u );
1054 GlyphRun underlineRun;
1055 underlineRun.glyphIndex = glyphStart;
1056 underlineRun.numberOfGlyphs = 1u + glyphEnd - glyphStart;
1058 // TODO: At the moment the underline runs are only for pre-edit.
1059 mModel->mVisualModel->mUnderlineRuns.PushBack( underlineRun );
1062 // The estimated number of lines. Used to avoid reallocations when layouting.
1063 mTextUpdateInfo.mEstimatedNumberOfLines = std::max( mModel->mVisualModel->mLines.Count(), mModel->mLogicalModel->mParagraphInfo.Count() );
1065 // Set the previous number of characters for the next time the text is updated.
1066 mTextUpdateInfo.mPreviousNumberOfCharacters = numberOfCharacters;
1071 void Controller::Impl::RetrieveDefaultInputStyle( InputStyle& inputStyle )
1073 // Sets the default text's color.
1074 inputStyle.textColor = mTextColor;
1075 inputStyle.isDefaultColor = true;
1077 inputStyle.familyName.clear();
1078 inputStyle.weight = TextAbstraction::FontWeight::NORMAL;
1079 inputStyle.width = TextAbstraction::FontWidth::NORMAL;
1080 inputStyle.slant = TextAbstraction::FontSlant::NORMAL;
1081 inputStyle.size = 0.f;
1083 inputStyle.lineSpacing = 0.f;
1085 inputStyle.underlineProperties.clear();
1086 inputStyle.shadowProperties.clear();
1087 inputStyle.embossProperties.clear();
1088 inputStyle.outlineProperties.clear();
1090 inputStyle.isFamilyDefined = false;
1091 inputStyle.isWeightDefined = false;
1092 inputStyle.isWidthDefined = false;
1093 inputStyle.isSlantDefined = false;
1094 inputStyle.isSizeDefined = false;
1096 inputStyle.isLineSpacingDefined = false;
1098 inputStyle.isUnderlineDefined = false;
1099 inputStyle.isShadowDefined = false;
1100 inputStyle.isEmbossDefined = false;
1101 inputStyle.isOutlineDefined = false;
1103 // Sets the default font's family name, weight, width, slant and size.
1106 if( mFontDefaults->familyDefined )
1108 inputStyle.familyName = mFontDefaults->mFontDescription.family;
1109 inputStyle.isFamilyDefined = true;
1112 if( mFontDefaults->weightDefined )
1114 inputStyle.weight = mFontDefaults->mFontDescription.weight;
1115 inputStyle.isWeightDefined = true;
1118 if( mFontDefaults->widthDefined )
1120 inputStyle.width = mFontDefaults->mFontDescription.width;
1121 inputStyle.isWidthDefined = true;
1124 if( mFontDefaults->slantDefined )
1126 inputStyle.slant = mFontDefaults->mFontDescription.slant;
1127 inputStyle.isSlantDefined = true;
1130 if( mFontDefaults->sizeDefined )
1132 inputStyle.size = mFontDefaults->mDefaultPointSize;
1133 inputStyle.isSizeDefined = true;
1138 float Controller::Impl::GetDefaultFontLineHeight()
1140 FontId defaultFontId = 0u;
1141 if( NULL == mFontDefaults )
1143 TextAbstraction::FontDescription fontDescription;
1144 defaultFontId = mFontClient.GetFontId( fontDescription );
1148 defaultFontId = mFontDefaults->GetFontId( mFontClient );
1151 Text::FontMetrics fontMetrics;
1152 mMetrics->GetFontMetrics( defaultFontId, fontMetrics );
1154 return( fontMetrics.ascender - fontMetrics.descender );
1157 void Controller::Impl::OnCursorKeyEvent( const Event& event )
1159 if( NULL == mEventData )
1161 // Nothing to do if there is no text input.
1165 int keyCode = event.p1.mInt;
1166 bool isShiftModifier = event.p2.mBool;
1168 CharacterIndex previousPrimaryCursorPosition = mEventData->mPrimaryCursorPosition;
1170 if( Dali::DALI_KEY_CURSOR_LEFT == keyCode )
1172 if( mEventData->mPrimaryCursorPosition > 0u )
1174 mEventData->mPrimaryCursorPosition = CalculateNewCursorIndex( mEventData->mPrimaryCursorPosition - 1u );
1177 else if( Dali::DALI_KEY_CURSOR_RIGHT == keyCode )
1179 if( mModel->mLogicalModel->mText.Count() > mEventData->mPrimaryCursorPosition )
1181 mEventData->mPrimaryCursorPosition = CalculateNewCursorIndex( mEventData->mPrimaryCursorPosition );
1184 else if( Dali::DALI_KEY_CURSOR_UP == keyCode && !isShiftModifier )
1186 // Ignore Shift-Up for text selection for now.
1188 // Get first the line index of the current cursor position index.
1189 CharacterIndex characterIndex = 0u;
1191 if( mEventData->mPrimaryCursorPosition > 0u )
1193 characterIndex = mEventData->mPrimaryCursorPosition - 1u;
1196 const LineIndex lineIndex = mModel->mVisualModel->GetLineOfCharacter( characterIndex );
1197 const LineIndex previousLineIndex = ( lineIndex > 0 ? lineIndex - 1u : lineIndex );
1199 // Retrieve the cursor position info.
1200 CursorInfo cursorInfo;
1201 GetCursorPosition( mEventData->mPrimaryCursorPosition,
1204 // Get the line above.
1205 const LineRun& line = *( mModel->mVisualModel->mLines.Begin() + previousLineIndex );
1207 // Get the next hit 'y' point.
1208 const float hitPointY = cursorInfo.lineOffset - 0.5f * ( line.ascender - line.descender );
1210 // Use the cursor hook position 'x' and the next hit 'y' position to calculate the new cursor index.
1211 bool matchedCharacter = false;
1212 mEventData->mPrimaryCursorPosition = Text::GetClosestCursorIndex( mModel->mVisualModel,
1213 mModel->mLogicalModel,
1215 mEventData->mCursorHookPositionX,
1217 CharacterHitTest::TAP,
1220 else if( Dali::DALI_KEY_CURSOR_DOWN == keyCode && !isShiftModifier )
1222 // Ignore Shift-Down for text selection for now.
1224 // Get first the line index of the current cursor position index.
1225 CharacterIndex characterIndex = 0u;
1227 if( mEventData->mPrimaryCursorPosition > 0u )
1229 characterIndex = mEventData->mPrimaryCursorPosition - 1u;
1232 const LineIndex lineIndex = mModel->mVisualModel->GetLineOfCharacter( characterIndex );
1234 if( lineIndex + 1u < mModel->mVisualModel->mLines.Count() )
1236 // Retrieve the cursor position info.
1237 CursorInfo cursorInfo;
1238 GetCursorPosition( mEventData->mPrimaryCursorPosition,
1241 // Get the line below.
1242 const LineRun& line = *( mModel->mVisualModel->mLines.Begin() + lineIndex + 1u );
1244 // Get the next hit 'y' point.
1245 const float hitPointY = cursorInfo.lineOffset + cursorInfo.lineHeight + 0.5f * ( line.ascender - line.descender );
1247 // Use the cursor hook position 'x' and the next hit 'y' position to calculate the new cursor index.
1248 bool matchedCharacter = false;
1249 mEventData->mPrimaryCursorPosition = Text::GetClosestCursorIndex( mModel->mVisualModel,
1250 mModel->mLogicalModel,
1252 mEventData->mCursorHookPositionX,
1254 CharacterHitTest::TAP,
1259 if ( !isShiftModifier && mEventData->mState != EventData::SELECTING )
1261 // Update selection position after moving the cursor
1262 mEventData->mLeftSelectionPosition = mEventData->mPrimaryCursorPosition;
1263 mEventData->mRightSelectionPosition = mEventData->mPrimaryCursorPosition;
1266 if ( isShiftModifier && IsShowingRealText() && mEventData->mShiftSelectionFlag )
1268 // Handle text selection
1269 bool selecting = false;
1271 if ( Dali::DALI_KEY_CURSOR_LEFT == keyCode || Dali::DALI_KEY_CURSOR_RIGHT == keyCode )
1273 // Shift-Left/Right to select the text
1274 int cursorPositionDelta = mEventData->mPrimaryCursorPosition - previousPrimaryCursorPosition;
1275 if ( cursorPositionDelta > 0 || mEventData->mRightSelectionPosition > 0u ) // Check the boundary
1277 mEventData->mRightSelectionPosition += cursorPositionDelta;
1281 else if ( mEventData->mLeftSelectionPosition != mEventData->mRightSelectionPosition )
1283 // Show no grab handles and text highlight if Shift-Up/Down pressed but no selected text
1289 // Notify the cursor position to the imf manager.
1290 if( mEventData->mImfManager )
1292 mEventData->mImfManager.SetCursorPosition( mEventData->mPrimaryCursorPosition );
1293 mEventData->mImfManager.NotifyCursorPosition();
1296 ChangeState( EventData::SELECTING );
1298 mEventData->mUpdateLeftSelectionPosition = true;
1299 mEventData->mUpdateRightSelectionPosition = true;
1300 mEventData->mUpdateGrabHandlePosition = true;
1301 mEventData->mUpdateHighlightBox = true;
1303 // Hide the text selection popup if select the text using keyboard instead of moving grab handles
1304 if( mEventData->mGrabHandlePopupEnabled )
1306 mEventData->mDecorator->SetPopupActive( false );
1312 // Handle normal cursor move
1313 ChangeState( EventData::EDITING );
1314 mEventData->mUpdateCursorPosition = true;
1317 mEventData->mUpdateInputStyle = true;
1318 mEventData->mScrollAfterUpdatePosition = true;
1321 void Controller::Impl::OnTapEvent( const Event& event )
1323 if( NULL != mEventData )
1325 const unsigned int tapCount = event.p1.mUint;
1327 if( 1u == tapCount )
1329 if( IsShowingRealText() )
1331 // Convert from control's coords to text's coords.
1332 const float xPosition = event.p2.mFloat - mModel->mScrollPosition.x;
1333 const float yPosition = event.p3.mFloat - mModel->mScrollPosition.y;
1335 // Keep the tap 'x' position. Used to move the cursor.
1336 mEventData->mCursorHookPositionX = xPosition;
1338 // Whether to touch point hits on a glyph.
1339 bool matchedCharacter = false;
1340 mEventData->mPrimaryCursorPosition = Text::GetClosestCursorIndex( mModel->mVisualModel,
1341 mModel->mLogicalModel,
1345 CharacterHitTest::TAP,
1348 // When the cursor position is changing, delay cursor blinking
1349 mEventData->mDecorator->DelayCursorBlink();
1353 mEventData->mPrimaryCursorPosition = 0u;
1356 // Update selection position after tapping
1357 mEventData->mLeftSelectionPosition = mEventData->mPrimaryCursorPosition;
1358 mEventData->mRightSelectionPosition = mEventData->mPrimaryCursorPosition;
1360 mEventData->mUpdateCursorPosition = true;
1361 mEventData->mUpdateGrabHandlePosition = true;
1362 mEventData->mScrollAfterUpdatePosition = true;
1363 mEventData->mUpdateInputStyle = true;
1365 // Notify the cursor position to the imf manager.
1366 if( mEventData->mImfManager )
1368 mEventData->mImfManager.SetCursorPosition( mEventData->mPrimaryCursorPosition );
1369 mEventData->mImfManager.NotifyCursorPosition();
1372 else if( 2u == tapCount )
1374 if( mEventData->mSelectionEnabled )
1376 // Convert from control's coords to text's coords.
1377 const float xPosition = event.p2.mFloat - mModel->mScrollPosition.x;
1378 const float yPosition = event.p3.mFloat - mModel->mScrollPosition.y;
1380 // Calculates the logical position from the x,y coords.
1381 RepositionSelectionHandles( xPosition,
1383 mEventData->mDoubleTapAction );
1389 void Controller::Impl::OnPanEvent( const Event& event )
1391 if( NULL == mEventData )
1393 // Nothing to do if there is no text input.
1397 const bool isHorizontalScrollEnabled = mEventData->mDecorator->IsHorizontalScrollEnabled();
1398 const bool isVerticalScrollEnabled = mEventData->mDecorator->IsVerticalScrollEnabled();
1400 if( !isHorizontalScrollEnabled && !isVerticalScrollEnabled )
1402 // Nothing to do if scrolling is not enabled.
1406 const int state = event.p1.mInt;
1410 case Gesture::Started:
1412 // Will remove the cursor, handles or text's popup, ...
1413 ChangeState( EventData::TEXT_PANNING );
1416 case Gesture::Continuing:
1418 const Vector2& layoutSize = mModel->mVisualModel->GetLayoutSize();
1419 const Vector2 currentScroll = mModel->mScrollPosition;
1421 if( isHorizontalScrollEnabled )
1423 const float displacementX = event.p2.mFloat;
1424 mModel->mScrollPosition.x += displacementX;
1426 ClampHorizontalScroll( layoutSize );
1429 if( isVerticalScrollEnabled )
1431 const float displacementY = event.p3.mFloat;
1432 mModel->mScrollPosition.y += displacementY;
1434 ClampVerticalScroll( layoutSize );
1437 mEventData->mDecorator->UpdatePositions( mModel->mScrollPosition - currentScroll );
1440 case Gesture::Finished:
1441 case Gesture::Cancelled: // FALLTHROUGH
1443 // Will go back to the previous state to show the cursor, handles, the text's popup, ...
1444 ChangeState( mEventData->mPreviousState );
1452 void Controller::Impl::OnLongPressEvent( const Event& event )
1454 DALI_LOG_INFO( gLogFilter, Debug::General, "Controller::OnLongPressEvent\n" );
1456 if( !IsShowingRealText() && ( EventData::EDITING == mEventData->mState ) )
1458 ChangeState( EventData::EDITING_WITH_POPUP );
1459 mEventData->mDecoratorUpdated = true;
1460 mEventData->mUpdateInputStyle = true;
1464 if( mEventData->mSelectionEnabled )
1466 // Convert from control's coords to text's coords.
1467 const float xPosition = event.p2.mFloat - mModel->mScrollPosition.x;
1468 const float yPosition = event.p3.mFloat - mModel->mScrollPosition.y;
1470 // Calculates the logical position from the x,y coords.
1471 RepositionSelectionHandles( xPosition,
1473 mEventData->mLongPressAction );
1478 void Controller::Impl::OnHandleEvent( const Event& event )
1480 if( NULL == mEventData )
1482 // Nothing to do if there is no text input.
1486 const unsigned int state = event.p1.mUint;
1487 const bool handleStopScrolling = ( HANDLE_STOP_SCROLLING == state );
1488 const bool isSmoothHandlePanEnabled = mEventData->mDecorator->IsSmoothHandlePanEnabled();
1490 if( HANDLE_PRESSED == state )
1492 // Convert from decorator's coords to text's coords.
1493 const float xPosition = event.p2.mFloat - mModel->mScrollPosition.x;
1494 const float yPosition = event.p3.mFloat - mModel->mScrollPosition.y;
1496 // Need to calculate the handle's new position.
1497 bool matchedCharacter = false;
1498 const CharacterIndex handleNewPosition = Text::GetClosestCursorIndex( mModel->mVisualModel,
1499 mModel->mLogicalModel,
1503 CharacterHitTest::SCROLL,
1506 if( Event::GRAB_HANDLE_EVENT == event.type )
1508 ChangeState ( EventData::GRAB_HANDLE_PANNING );
1510 if( handleNewPosition != mEventData->mPrimaryCursorPosition )
1512 // Updates the cursor position if the handle's new position is different than the current one.
1513 mEventData->mUpdateCursorPosition = true;
1514 // Does not update the grab handle position if the smooth panning is enabled. (The decorator does it smooth).
1515 mEventData->mUpdateGrabHandlePosition = !isSmoothHandlePanEnabled;
1516 mEventData->mPrimaryCursorPosition = handleNewPosition;
1519 // 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.
1520 mEventData->mDecoratorUpdated = isSmoothHandlePanEnabled;
1522 else if( Event::LEFT_SELECTION_HANDLE_EVENT == event.type )
1524 ChangeState ( EventData::SELECTION_HANDLE_PANNING );
1526 if( ( handleNewPosition != mEventData->mLeftSelectionPosition ) &&
1527 ( handleNewPosition != mEventData->mRightSelectionPosition ) )
1529 // Updates the highlight box if the handle's new position is different than the current one.
1530 mEventData->mUpdateHighlightBox = true;
1531 // Does not update the selection handle position if the smooth panning is enabled. (The decorator does it smooth).
1532 mEventData->mUpdateLeftSelectionPosition = !isSmoothHandlePanEnabled;
1533 mEventData->mLeftSelectionPosition = handleNewPosition;
1536 // 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.
1537 mEventData->mDecoratorUpdated = isSmoothHandlePanEnabled;
1539 // Will define the order to scroll the text to match the handle position.
1540 mEventData->mIsLeftHandleSelected = true;
1541 mEventData->mIsRightHandleSelected = false;
1543 else if( Event::RIGHT_SELECTION_HANDLE_EVENT == event.type )
1545 ChangeState ( EventData::SELECTION_HANDLE_PANNING );
1547 if( ( handleNewPosition != mEventData->mRightSelectionPosition ) &&
1548 ( handleNewPosition != mEventData->mLeftSelectionPosition ) )
1550 // Updates the highlight box if the handle's new position is different than the current one.
1551 mEventData->mUpdateHighlightBox = true;
1552 // Does not update the selection handle position if the smooth panning is enabled. (The decorator does it smooth).
1553 mEventData->mUpdateRightSelectionPosition = !isSmoothHandlePanEnabled;
1554 mEventData->mRightSelectionPosition = handleNewPosition;
1557 // 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.
1558 mEventData->mDecoratorUpdated = isSmoothHandlePanEnabled;
1560 // Will define the order to scroll the text to match the handle position.
1561 mEventData->mIsLeftHandleSelected = false;
1562 mEventData->mIsRightHandleSelected = true;
1564 } // end ( HANDLE_PRESSED == state )
1565 else if( ( HANDLE_RELEASED == state ) ||
1566 handleStopScrolling )
1568 CharacterIndex handlePosition = 0u;
1569 if( handleStopScrolling || isSmoothHandlePanEnabled )
1571 // Convert from decorator's coords to text's coords.
1572 const float xPosition = event.p2.mFloat - mModel->mScrollPosition.x;
1573 const float yPosition = event.p3.mFloat - mModel->mScrollPosition.y;
1575 bool matchedCharacter = false;
1576 handlePosition = Text::GetClosestCursorIndex( mModel->mVisualModel,
1577 mModel->mLogicalModel,
1581 CharacterHitTest::SCROLL,
1585 if( Event::GRAB_HANDLE_EVENT == event.type )
1587 mEventData->mUpdateCursorPosition = true;
1588 mEventData->mUpdateGrabHandlePosition = true;
1589 mEventData->mUpdateInputStyle = true;
1591 if( !IsClipboardEmpty() )
1593 ChangeState( EventData::EDITING_WITH_PASTE_POPUP ); // Moving grabhandle will show Paste Popup
1596 if( handleStopScrolling || isSmoothHandlePanEnabled )
1598 mEventData->mScrollAfterUpdatePosition = true;
1599 mEventData->mPrimaryCursorPosition = handlePosition;
1602 else if( Event::LEFT_SELECTION_HANDLE_EVENT == event.type )
1604 ChangeState( EventData::SELECTING );
1606 mEventData->mUpdateHighlightBox = true;
1607 mEventData->mUpdateLeftSelectionPosition = true;
1608 mEventData->mUpdateRightSelectionPosition = true;
1610 if( handleStopScrolling || isSmoothHandlePanEnabled )
1612 mEventData->mScrollAfterUpdatePosition = true;
1614 if( ( handlePosition != mEventData->mRightSelectionPosition ) &&
1615 ( handlePosition != mEventData->mLeftSelectionPosition ) )
1617 mEventData->mLeftSelectionPosition = handlePosition;
1621 else if( Event::RIGHT_SELECTION_HANDLE_EVENT == event.type )
1623 ChangeState( EventData::SELECTING );
1625 mEventData->mUpdateHighlightBox = true;
1626 mEventData->mUpdateRightSelectionPosition = true;
1627 mEventData->mUpdateLeftSelectionPosition = true;
1629 if( handleStopScrolling || isSmoothHandlePanEnabled )
1631 mEventData->mScrollAfterUpdatePosition = true;
1632 if( ( handlePosition != mEventData->mRightSelectionPosition ) &&
1633 ( handlePosition != mEventData->mLeftSelectionPosition ) )
1635 mEventData->mRightSelectionPosition = handlePosition;
1640 mEventData->mDecoratorUpdated = true;
1641 } // end ( ( HANDLE_RELEASED == state ) || ( HANDLE_STOP_SCROLLING == state ) )
1642 else if( HANDLE_SCROLLING == state )
1644 const float xSpeed = event.p2.mFloat;
1645 const float ySpeed = event.p3.mFloat;
1646 const Vector2& layoutSize = mModel->mVisualModel->GetLayoutSize();
1647 const Vector2 currentScrollPosition = mModel->mScrollPosition;
1649 mModel->mScrollPosition.x += xSpeed;
1650 mModel->mScrollPosition.y += ySpeed;
1652 ClampHorizontalScroll( layoutSize );
1653 ClampVerticalScroll( layoutSize );
1655 bool endOfScroll = false;
1656 if( Vector2::ZERO == ( currentScrollPosition - mModel->mScrollPosition ) )
1658 // Notify the decorator there is no more text to scroll.
1659 // The decorator won't send more scroll events.
1660 mEventData->mDecorator->NotifyEndOfScroll();
1661 // Still need to set the position of the handle.
1665 // Set the position of the handle.
1666 const bool scrollRightDirection = xSpeed > 0.f;
1667 const bool scrollBottomDirection = ySpeed > 0.f;
1668 const bool leftSelectionHandleEvent = Event::LEFT_SELECTION_HANDLE_EVENT == event.type;
1669 const bool rightSelectionHandleEvent = Event::RIGHT_SELECTION_HANDLE_EVENT == event.type;
1671 if( Event::GRAB_HANDLE_EVENT == event.type )
1673 ChangeState( EventData::GRAB_HANDLE_PANNING );
1675 // Get the grab handle position in decorator coords.
1676 Vector2 position = mEventData->mDecorator->GetPosition( GRAB_HANDLE );
1678 if( mEventData->mDecorator->IsHorizontalScrollEnabled() )
1680 // Position the grag handle close to either the left or right edge.
1681 position.x = scrollRightDirection ? 0.f : mModel->mVisualModel->mControlSize.width;
1684 if( mEventData->mDecorator->IsVerticalScrollEnabled() )
1686 position.x = mEventData->mCursorHookPositionX;
1688 // Position the grag handle close to either the top or bottom edge.
1689 position.y = scrollBottomDirection ? 0.f : mModel->mVisualModel->mControlSize.height;
1692 // Get the new handle position.
1693 // The grab handle's position is in decorator's coords. Need to transforms to text's coords.
1694 bool matchedCharacter = false;
1695 const CharacterIndex handlePosition = Text::GetClosestCursorIndex( mModel->mVisualModel,
1696 mModel->mLogicalModel,
1698 position.x - mModel->mScrollPosition.x,
1699 position.y - mModel->mScrollPosition.y,
1700 CharacterHitTest::SCROLL,
1703 if( mEventData->mPrimaryCursorPosition != handlePosition )
1705 mEventData->mUpdateCursorPosition = true;
1706 mEventData->mUpdateGrabHandlePosition = !isSmoothHandlePanEnabled;
1707 mEventData->mScrollAfterUpdatePosition = true;
1708 mEventData->mPrimaryCursorPosition = handlePosition;
1710 mEventData->mUpdateInputStyle = mEventData->mUpdateCursorPosition;
1712 // Updates the decorator if the soft handle panning is enabled.
1713 mEventData->mDecoratorUpdated = isSmoothHandlePanEnabled;
1715 else if( leftSelectionHandleEvent || rightSelectionHandleEvent )
1717 ChangeState( EventData::SELECTION_HANDLE_PANNING );
1719 // Get the selection handle position in decorator coords.
1720 Vector2 position = mEventData->mDecorator->GetPosition( leftSelectionHandleEvent ? Text::LEFT_SELECTION_HANDLE : Text::RIGHT_SELECTION_HANDLE );
1722 if( mEventData->mDecorator->IsHorizontalScrollEnabled() )
1724 // Position the selection 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 selection handle's position is in decorator's coords. Need to transform 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( leftSelectionHandleEvent )
1749 const bool differentHandles = ( mEventData->mLeftSelectionPosition != handlePosition ) && ( mEventData->mRightSelectionPosition != handlePosition );
1751 if( differentHandles || endOfScroll )
1753 mEventData->mUpdateHighlightBox = true;
1754 mEventData->mUpdateLeftSelectionPosition = !isSmoothHandlePanEnabled;
1755 mEventData->mUpdateRightSelectionPosition = isSmoothHandlePanEnabled;
1756 mEventData->mLeftSelectionPosition = handlePosition;
1761 const bool differentHandles = ( mEventData->mRightSelectionPosition != handlePosition ) && ( mEventData->mLeftSelectionPosition != handlePosition );
1762 if( differentHandles || endOfScroll )
1764 mEventData->mUpdateHighlightBox = true;
1765 mEventData->mUpdateRightSelectionPosition = !isSmoothHandlePanEnabled;
1766 mEventData->mUpdateLeftSelectionPosition = isSmoothHandlePanEnabled;
1767 mEventData->mRightSelectionPosition = handlePosition;
1771 if( mEventData->mUpdateLeftSelectionPosition || mEventData->mUpdateRightSelectionPosition )
1773 RepositionSelectionHandles();
1775 mEventData->mScrollAfterUpdatePosition = !isSmoothHandlePanEnabled;
1778 mEventData->mDecoratorUpdated = true;
1779 } // end ( HANDLE_SCROLLING == state )
1782 void Controller::Impl::OnSelectEvent( const Event& event )
1784 if( NULL == mEventData )
1786 // Nothing to do if there is no text.
1790 if( mEventData->mSelectionEnabled )
1792 // Convert from control's coords to text's coords.
1793 const float xPosition = event.p2.mFloat - mModel->mScrollPosition.x;
1794 const float yPosition = event.p3.mFloat - mModel->mScrollPosition.y;
1796 // Calculates the logical position from the x,y coords.
1797 RepositionSelectionHandles( xPosition,
1799 Controller::NoTextTap::HIGHLIGHT );
1803 void Controller::Impl::OnSelectAllEvent()
1805 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "OnSelectAllEvent mEventData->mSelectionEnabled%s \n", mEventData->mSelectionEnabled?"true":"false");
1807 if( NULL == mEventData )
1809 // Nothing to do if there is no text.
1813 if( mEventData->mSelectionEnabled )
1815 ChangeState( EventData::SELECTING );
1817 mEventData->mLeftSelectionPosition = 0u;
1818 mEventData->mRightSelectionPosition = mModel->mLogicalModel->mText.Count();
1820 mEventData->mScrollAfterUpdatePosition = true;
1821 mEventData->mUpdateLeftSelectionPosition = true;
1822 mEventData->mUpdateRightSelectionPosition = true;
1823 mEventData->mUpdateHighlightBox = true;
1827 void Controller::Impl::RetrieveSelection( std::string& selectedText, bool deleteAfterRetrieval )
1829 if( mEventData->mLeftSelectionPosition == mEventData->mRightSelectionPosition )
1831 // Nothing to select if handles are in the same place.
1832 selectedText.clear();
1836 const bool handlesCrossed = mEventData->mLeftSelectionPosition > mEventData->mRightSelectionPosition;
1838 //Get start and end position of selection
1839 const CharacterIndex startOfSelectedText = handlesCrossed ? mEventData->mRightSelectionPosition : mEventData->mLeftSelectionPosition;
1840 const Length lengthOfSelectedText = ( handlesCrossed ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition ) - startOfSelectedText;
1842 Vector<Character>& utf32Characters = mModel->mLogicalModel->mText;
1843 const Length numberOfCharacters = utf32Characters.Count();
1845 // Validate the start and end selection points
1846 if( ( startOfSelectedText + lengthOfSelectedText ) <= numberOfCharacters )
1848 //Get text as a UTF8 string
1849 Utf32ToUtf8( &utf32Characters[startOfSelectedText], lengthOfSelectedText, selectedText );
1851 if( deleteAfterRetrieval ) // Only delete text if copied successfully
1853 // Keep a copy of the current input style.
1854 InputStyle currentInputStyle;
1855 currentInputStyle.Copy( mEventData->mInputStyle );
1857 // Set as input style the style of the first deleted character.
1858 mModel->mLogicalModel->RetrieveStyle( startOfSelectedText, mEventData->mInputStyle );
1860 // Compare if the input style has changed.
1861 const bool hasInputStyleChanged = !currentInputStyle.Equal( mEventData->mInputStyle );
1863 if( hasInputStyleChanged )
1865 const InputStyle::Mask styleChangedMask = currentInputStyle.GetInputStyleChangeMask( mEventData->mInputStyle );
1866 // Queue the input style changed signal.
1867 mEventData->mInputStyleChangedQueue.PushBack( styleChangedMask );
1870 mModel->mLogicalModel->UpdateTextStyleRuns( startOfSelectedText, -static_cast<int>( lengthOfSelectedText ) );
1872 // Mark the paragraphs to be updated.
1873 if( Layout::Engine::SINGLE_LINE_BOX == mLayoutEngine.GetLayout() )
1875 mTextUpdateInfo.mCharacterIndex = 0;
1876 mTextUpdateInfo.mNumberOfCharactersToRemove = mTextUpdateInfo.mPreviousNumberOfCharacters;
1877 mTextUpdateInfo.mNumberOfCharactersToAdd = mTextUpdateInfo.mPreviousNumberOfCharacters - lengthOfSelectedText;
1878 mTextUpdateInfo.mClearAll = true;
1882 mTextUpdateInfo.mCharacterIndex = startOfSelectedText;
1883 mTextUpdateInfo.mNumberOfCharactersToRemove = lengthOfSelectedText;
1886 // Delete text between handles
1887 Vector<Character>::Iterator first = utf32Characters.Begin() + startOfSelectedText;
1888 Vector<Character>::Iterator last = first + lengthOfSelectedText;
1889 utf32Characters.Erase( first, last );
1891 // Will show the cursor at the first character of the selection.
1892 mEventData->mPrimaryCursorPosition = handlesCrossed ? mEventData->mRightSelectionPosition : mEventData->mLeftSelectionPosition;
1896 // Will show the cursor at the last character of the selection.
1897 mEventData->mPrimaryCursorPosition = handlesCrossed ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition;
1900 mEventData->mDecoratorUpdated = true;
1904 void Controller::Impl::ShowClipboard()
1908 mClipboard.ShowClipboard();
1912 void Controller::Impl::HideClipboard()
1914 if( mClipboard && mClipboardHideEnabled )
1916 mClipboard.HideClipboard();
1920 void Controller::Impl::SetClipboardHideEnable(bool enable)
1922 mClipboardHideEnabled = enable;
1925 bool Controller::Impl::CopyStringToClipboard( std::string& source )
1927 //Send string to clipboard
1928 return ( mClipboard && mClipboard.SetItem( source ) );
1931 void Controller::Impl::SendSelectionToClipboard( bool deleteAfterSending )
1933 std::string selectedText;
1934 RetrieveSelection( selectedText, deleteAfterSending );
1935 CopyStringToClipboard( selectedText );
1936 ChangeState( EventData::EDITING );
1939 void Controller::Impl::RequestGetTextFromClipboard()
1943 mClipboard.RequestItem();
1947 void Controller::Impl::RepositionSelectionHandles()
1949 CharacterIndex selectionStart = mEventData->mLeftSelectionPosition;
1950 CharacterIndex selectionEnd = mEventData->mRightSelectionPosition;
1952 if( selectionStart == selectionEnd )
1954 // Nothing to select if handles are in the same place.
1955 // So, deactive Highlight box.
1956 mEventData->mDecorator->SetHighlightActive( false );
1960 mEventData->mDecorator->ClearHighlights();
1962 const GlyphIndex* const charactersToGlyphBuffer = mModel->mVisualModel->mCharactersToGlyph.Begin();
1963 const Length* const glyphsPerCharacterBuffer = mModel->mVisualModel->mGlyphsPerCharacter.Begin();
1964 const GlyphInfo* const glyphsBuffer = mModel->mVisualModel->mGlyphs.Begin();
1965 const Vector2* const positionsBuffer = mModel->mVisualModel->mGlyphPositions.Begin();
1966 const Length* const charactersPerGlyphBuffer = mModel->mVisualModel->mCharactersPerGlyph.Begin();
1967 const CharacterIndex* const glyphToCharacterBuffer = mModel->mVisualModel->mGlyphsToCharacters.Begin();
1968 const CharacterDirection* const modelCharacterDirectionsBuffer = ( 0u != mModel->mLogicalModel->mCharacterDirections.Count() ) ? mModel->mLogicalModel->mCharacterDirections.Begin() : NULL;
1970 const bool isLastCharacter = selectionEnd >= mModel->mLogicalModel->mText.Count();
1971 const CharacterDirection startDirection = ( ( NULL == modelCharacterDirectionsBuffer ) ? false : *( modelCharacterDirectionsBuffer + selectionStart ) );
1972 const CharacterDirection endDirection = ( ( NULL == modelCharacterDirectionsBuffer ) ? false : *( modelCharacterDirectionsBuffer + ( selectionEnd - ( isLastCharacter ? 1u : 0u ) ) ) );
1974 // Swap the indices if the start is greater than the end.
1975 const bool indicesSwapped = selectionStart > selectionEnd;
1977 // Tell the decorator to flip the selection handles if needed.
1978 mEventData->mDecorator->SetSelectionHandleFlipState( indicesSwapped, startDirection, endDirection );
1980 if( indicesSwapped )
1982 std::swap( selectionStart, selectionEnd );
1985 // Get the indices to the first and last selected glyphs.
1986 const CharacterIndex selectionEndMinusOne = selectionEnd - 1u;
1987 const GlyphIndex glyphStart = *( charactersToGlyphBuffer + selectionStart );
1988 const Length numberOfGlyphs = *( glyphsPerCharacterBuffer + selectionEndMinusOne );
1989 const GlyphIndex glyphEnd = *( charactersToGlyphBuffer + selectionEndMinusOne ) + ( ( numberOfGlyphs > 0 ) ? numberOfGlyphs - 1u : 0u );
1991 // Get the lines where the glyphs are laid-out.
1992 const LineRun* lineRun = mModel->mVisualModel->mLines.Begin();
1994 LineIndex lineIndex = 0u;
1995 Length numberOfLines = 0u;
1996 mModel->mVisualModel->GetNumberOfLines( glyphStart,
1997 1u + glyphEnd - glyphStart,
2000 const LineIndex firstLineIndex = lineIndex;
2002 // Create the structure to store some selection box info.
2003 Vector<SelectionBoxInfo> selectionBoxLinesInfo;
2004 selectionBoxLinesInfo.Resize( numberOfLines );
2006 SelectionBoxInfo* selectionBoxInfo = selectionBoxLinesInfo.Begin();
2007 selectionBoxInfo->minX = MAX_FLOAT;
2008 selectionBoxInfo->maxX = MIN_FLOAT;
2010 // Keep the min and max 'x' position to calculate the size and position of the highlighed text.
2011 float minHighlightX = std::numeric_limits<float>::max();
2012 float maxHighlightX = std::numeric_limits<float>::min();
2014 Vector2 highLightPosition; // The highlight position in decorator's coords.
2016 // Retrieve the first line and get the line's vertical offset, the line's height and the index to the last glyph.
2018 // The line's vertical offset of all the lines before the line where the first glyph is laid-out.
2019 selectionBoxInfo->lineOffset = CalculateLineOffset( mModel->mVisualModel->mLines,
2022 // Transform to decorator's (control) coords.
2023 selectionBoxInfo->lineOffset += mModel->mScrollPosition.y;
2025 lineRun += firstLineIndex;
2027 // The line height is the addition of the line ascender and the line descender.
2028 // However, the line descender has a negative value, hence the subtraction.
2029 selectionBoxInfo->lineHeight = lineRun->ascender - lineRun->descender;
2031 GlyphIndex lastGlyphOfLine = lineRun->glyphRun.glyphIndex + lineRun->glyphRun.numberOfGlyphs - 1u;
2033 // Check if the first glyph is a ligature that must be broken like Latin ff, fi, or Arabic ï»», etc which needs special code.
2034 const Length numberOfCharactersStart = *( charactersPerGlyphBuffer + glyphStart );
2035 bool splitStartGlyph = ( numberOfCharactersStart > 1u ) && HasLigatureMustBreak( mModel->mLogicalModel->GetScript( selectionStart ) );
2037 // Check if the last glyph is a ligature that must be broken like Latin ff, fi, or Arabic ï»», etc which needs special code.
2038 const Length numberOfCharactersEnd = *( charactersPerGlyphBuffer + glyphEnd );
2039 bool splitEndGlyph = ( glyphStart != glyphEnd ) && ( numberOfCharactersEnd > 1u ) && HasLigatureMustBreak( mModel->mLogicalModel->GetScript( selectionEndMinusOne ) );
2041 // The number of quads of the selection box.
2042 const unsigned int numberOfQuads = 1u + ( glyphEnd - glyphStart ) + ( ( numberOfLines > 1u ) ? 2u * numberOfLines : 0u );
2043 mEventData->mDecorator->ResizeHighlightQuads( numberOfQuads );
2045 // Count the actual number of quads.
2046 unsigned int actualNumberOfQuads = 0u;
2049 // Traverse the glyphs.
2050 for( GlyphIndex index = glyphStart; index <= glyphEnd; ++index )
2052 const GlyphInfo& glyph = *( glyphsBuffer + index );
2053 const Vector2& position = *( positionsBuffer + index );
2055 if( splitStartGlyph )
2057 // 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.
2059 const float glyphAdvance = glyph.advance / static_cast<float>( numberOfCharactersStart );
2060 const CharacterIndex interGlyphIndex = selectionStart - *( glyphToCharacterBuffer + glyphStart );
2061 // Get the direction of the character.
2062 CharacterDirection isCurrentRightToLeft = false;
2063 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
2065 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + selectionStart );
2068 // The end point could be in the middle of the ligature.
2069 // Calculate the number of characters selected.
2070 const Length numberOfCharacters = ( glyphStart == glyphEnd ) ? ( selectionEnd - selectionStart ) : ( numberOfCharactersStart - interGlyphIndex );
2072 quad.x = lineRun->alignmentOffset + position.x - glyph.xBearing + mModel->mScrollPosition.x + glyphAdvance * static_cast<float>( isCurrentRightToLeft ? ( numberOfCharactersStart - interGlyphIndex - numberOfCharacters ) : interGlyphIndex );
2073 quad.y = selectionBoxInfo->lineOffset;
2074 quad.z = quad.x + static_cast<float>( numberOfCharacters ) * glyphAdvance;
2075 quad.w = selectionBoxInfo->lineOffset + selectionBoxInfo->lineHeight;
2077 // Store the min and max 'x' for each line.
2078 selectionBoxInfo->minX = std::min( selectionBoxInfo->minX, quad.x );
2079 selectionBoxInfo->maxX = std::max( selectionBoxInfo->maxX, quad.z );
2081 mEventData->mDecorator->AddHighlight( actualNumberOfQuads, quad );
2082 ++actualNumberOfQuads;
2084 splitStartGlyph = false;
2088 if( splitEndGlyph && ( index == glyphEnd ) )
2090 // 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.
2092 const float glyphAdvance = glyph.advance / static_cast<float>( numberOfCharactersEnd );
2093 const CharacterIndex interGlyphIndex = selectionEnd - *( glyphToCharacterBuffer + glyphEnd );
2094 // Get the direction of the character.
2095 CharacterDirection isCurrentRightToLeft = false;
2096 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
2098 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + selectionEnd );
2101 const Length numberOfCharacters = numberOfCharactersEnd - interGlyphIndex;
2103 quad.x = lineRun->alignmentOffset + position.x - glyph.xBearing + mModel->mScrollPosition.x + ( isCurrentRightToLeft ? ( glyphAdvance * static_cast<float>( numberOfCharacters ) ) : 0.f );
2104 quad.y = selectionBoxInfo->lineOffset;
2105 quad.z = quad.x + static_cast<float>( interGlyphIndex ) * glyphAdvance;
2106 quad.w = quad.y + selectionBoxInfo->lineHeight;
2108 // Store the min and max 'x' for each line.
2109 selectionBoxInfo->minX = std::min( selectionBoxInfo->minX, quad.x );
2110 selectionBoxInfo->maxX = std::max( selectionBoxInfo->maxX, quad.z );
2112 mEventData->mDecorator->AddHighlight( actualNumberOfQuads,
2114 ++actualNumberOfQuads;
2116 splitEndGlyph = false;
2120 quad.x = lineRun->alignmentOffset + position.x - glyph.xBearing + mModel->mScrollPosition.x;
2121 quad.y = selectionBoxInfo->lineOffset;
2122 quad.z = quad.x + glyph.advance;
2123 quad.w = quad.y + selectionBoxInfo->lineHeight;
2125 // Store the min and max 'x' for each line.
2126 selectionBoxInfo->minX = std::min( selectionBoxInfo->minX, quad.x );
2127 selectionBoxInfo->maxX = std::max( selectionBoxInfo->maxX, quad.z );
2129 mEventData->mDecorator->AddHighlight( actualNumberOfQuads,
2131 ++actualNumberOfQuads;
2133 // Whether to retrieve the next line.
2134 if( index == lastGlyphOfLine )
2137 if( lineIndex < firstLineIndex + numberOfLines )
2139 // Retrieve the next line.
2142 // Get the last glyph of the new line.
2143 lastGlyphOfLine = lineRun->glyphRun.glyphIndex + lineRun->glyphRun.numberOfGlyphs - 1u;
2145 // Keep the offset and height of the current selection box.
2146 const float currentLineOffset = selectionBoxInfo->lineOffset;
2147 const float currentLineHeight = selectionBoxInfo->lineHeight;
2149 // Get the selection box info for the next line.
2152 selectionBoxInfo->minX = MAX_FLOAT;
2153 selectionBoxInfo->maxX = MIN_FLOAT;
2155 // Update the line's vertical offset.
2156 selectionBoxInfo->lineOffset = currentLineOffset + currentLineHeight;
2158 // The line height is the addition of the line ascender and the line descender.
2159 // However, the line descender has a negative value, hence the subtraction.
2160 selectionBoxInfo->lineHeight = lineRun->ascender - lineRun->descender;
2165 // Traverses all the lines and updates the min and max 'x' positions and the total height.
2166 // The final width is calculated after 'boxifying' the selection.
2167 for( Vector<SelectionBoxInfo>::ConstIterator it = selectionBoxLinesInfo.Begin(),
2168 endIt = selectionBoxLinesInfo.End();
2172 const SelectionBoxInfo& info = *it;
2174 // Update the size of the highlighted text.
2175 highLightSize.height += info.lineHeight;
2176 minHighlightX = std::min( minHighlightX, info.minX );
2177 maxHighlightX = std::max( maxHighlightX, info.maxX );
2180 // Add extra geometry to 'boxify' the selection.
2182 if( 1u < numberOfLines )
2184 // Boxify the first line.
2185 lineRun = mModel->mVisualModel->mLines.Begin() + firstLineIndex;
2186 const SelectionBoxInfo& firstSelectionBoxLineInfo = *( selectionBoxLinesInfo.Begin() );
2188 bool boxifyBegin = ( LTR != lineRun->direction ) && ( LTR != startDirection );
2189 bool boxifyEnd = ( LTR == lineRun->direction ) && ( LTR == startDirection );
2194 quad.y = firstSelectionBoxLineInfo.lineOffset;
2195 quad.z = firstSelectionBoxLineInfo.minX;
2196 quad.w = firstSelectionBoxLineInfo.lineOffset + firstSelectionBoxLineInfo.lineHeight;
2198 // Boxify at the beginning of the line.
2199 mEventData->mDecorator->AddHighlight( actualNumberOfQuads,
2201 ++actualNumberOfQuads;
2203 // Update the size of the highlighted text.
2204 minHighlightX = 0.f;
2209 quad.x = firstSelectionBoxLineInfo.maxX;
2210 quad.y = firstSelectionBoxLineInfo.lineOffset;
2211 quad.z = mModel->mVisualModel->mControlSize.width;
2212 quad.w = firstSelectionBoxLineInfo.lineOffset + firstSelectionBoxLineInfo.lineHeight;
2214 // Boxify at the end of the line.
2215 mEventData->mDecorator->AddHighlight( actualNumberOfQuads,
2217 ++actualNumberOfQuads;
2219 // Update the size of the highlighted text.
2220 maxHighlightX = mModel->mVisualModel->mControlSize.width;
2223 // Boxify the central lines.
2224 if( 2u < numberOfLines )
2226 for( Vector<SelectionBoxInfo>::ConstIterator it = selectionBoxLinesInfo.Begin() + 1u,
2227 endIt = selectionBoxLinesInfo.End() - 1u;
2231 const SelectionBoxInfo& info = *it;
2234 quad.y = info.lineOffset;
2236 quad.w = info.lineOffset + info.lineHeight;
2238 mEventData->mDecorator->AddHighlight( actualNumberOfQuads,
2240 ++actualNumberOfQuads;
2243 quad.y = info.lineOffset;
2244 quad.z = mModel->mVisualModel->mControlSize.width;
2245 quad.w = info.lineOffset + info.lineHeight;
2247 mEventData->mDecorator->AddHighlight( actualNumberOfQuads,
2249 ++actualNumberOfQuads;
2252 // Update the size of the highlighted text.
2253 minHighlightX = 0.f;
2254 maxHighlightX = mModel->mVisualModel->mControlSize.width;
2257 // Boxify the last line.
2258 lineRun = mModel->mVisualModel->mLines.Begin() + firstLineIndex + numberOfLines - 1u;
2259 const SelectionBoxInfo& lastSelectionBoxLineInfo = *( selectionBoxLinesInfo.End() - 1u );
2261 boxifyBegin = ( LTR == lineRun->direction ) && ( LTR == endDirection );
2262 boxifyEnd = ( LTR != lineRun->direction ) && ( LTR != endDirection );
2267 quad.y = lastSelectionBoxLineInfo.lineOffset;
2268 quad.z = lastSelectionBoxLineInfo.minX;
2269 quad.w = lastSelectionBoxLineInfo.lineOffset + lastSelectionBoxLineInfo.lineHeight;
2271 // Boxify at the beginning of the line.
2272 mEventData->mDecorator->AddHighlight( actualNumberOfQuads,
2274 ++actualNumberOfQuads;
2276 // Update the size of the highlighted text.
2277 minHighlightX = 0.f;
2282 quad.x = lastSelectionBoxLineInfo.maxX;
2283 quad.y = lastSelectionBoxLineInfo.lineOffset;
2284 quad.z = mModel->mVisualModel->mControlSize.width;
2285 quad.w = lastSelectionBoxLineInfo.lineOffset + lastSelectionBoxLineInfo.lineHeight;
2287 // Boxify at the end of the line.
2288 mEventData->mDecorator->AddHighlight( actualNumberOfQuads,
2290 ++actualNumberOfQuads;
2292 // Update the size of the highlighted text.
2293 maxHighlightX = mModel->mVisualModel->mControlSize.width;
2297 // Set the actual number of quads.
2298 mEventData->mDecorator->ResizeHighlightQuads( actualNumberOfQuads );
2300 // Sets the highlight's size and position. In decorator's coords.
2301 // The highlight's height has been calculated above (before 'boxifying' the highlight).
2302 highLightSize.width = maxHighlightX - minHighlightX;
2304 highLightPosition.x = minHighlightX;
2305 const SelectionBoxInfo& firstSelectionBoxLineInfo = *( selectionBoxLinesInfo.Begin() );
2306 highLightPosition.y = firstSelectionBoxLineInfo.lineOffset;
2308 mEventData->mDecorator->SetHighLightBox( highLightPosition, highLightSize, static_cast<float>( mModel->GetOutlineWidth() ) );
2310 if( !mEventData->mDecorator->IsSmoothHandlePanEnabled() )
2312 CursorInfo primaryCursorInfo;
2313 GetCursorPosition( mEventData->mLeftSelectionPosition,
2314 primaryCursorInfo );
2316 const Vector2 primaryPosition = primaryCursorInfo.primaryPosition + mModel->mScrollPosition;
2318 mEventData->mDecorator->SetPosition( LEFT_SELECTION_HANDLE,
2320 primaryCursorInfo.lineOffset + mModel->mScrollPosition.y,
2321 primaryCursorInfo.lineHeight );
2323 CursorInfo secondaryCursorInfo;
2324 GetCursorPosition( mEventData->mRightSelectionPosition,
2325 secondaryCursorInfo );
2327 const Vector2 secondaryPosition = secondaryCursorInfo.primaryPosition + mModel->mScrollPosition;
2329 mEventData->mDecorator->SetPosition( RIGHT_SELECTION_HANDLE,
2330 secondaryPosition.x,
2331 secondaryCursorInfo.lineOffset + mModel->mScrollPosition.y,
2332 secondaryCursorInfo.lineHeight );
2335 // Set the flag to update the decorator.
2336 mEventData->mDecoratorUpdated = true;
2339 void Controller::Impl::RepositionSelectionHandles( float visualX, float visualY, Controller::NoTextTap::Action action )
2341 if( NULL == mEventData )
2343 // Nothing to do if there is no text input.
2347 if( IsShowingPlaceholderText() )
2349 // Nothing to do if there is the place-holder text.
2353 const Length numberOfGlyphs = mModel->mVisualModel->mGlyphs.Count();
2354 const Length numberOfLines = mModel->mVisualModel->mLines.Count();
2355 if( ( 0 == numberOfGlyphs ) ||
2356 ( 0 == numberOfLines ) )
2358 // Nothing to do if there is no text.
2362 // Find which word was selected
2363 CharacterIndex selectionStart( 0 );
2364 CharacterIndex selectionEnd( 0 );
2365 CharacterIndex noTextHitIndex( 0 );
2366 const bool characterHit = FindSelectionIndices( mModel->mVisualModel,
2367 mModel->mLogicalModel,
2374 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "%p selectionStart %d selectionEnd %d\n", this, selectionStart, selectionEnd );
2376 if( characterHit || ( Controller::NoTextTap::HIGHLIGHT == action ) )
2378 ChangeState( EventData::SELECTING );
2380 mEventData->mLeftSelectionPosition = selectionStart;
2381 mEventData->mRightSelectionPosition = selectionEnd;
2383 mEventData->mUpdateLeftSelectionPosition = true;
2384 mEventData->mUpdateRightSelectionPosition = true;
2385 mEventData->mUpdateHighlightBox = true;
2387 // It may happen an IMF commit event arrives before the selection event
2388 // if the IMF manager is in pre-edit state. The commit event will set the
2389 // mEventData->mUpdateCursorPosition flag to true. If it's not set back
2390 // to false, the highlight box won't be updated.
2391 mEventData->mUpdateCursorPosition = false;
2393 mEventData->mScrollAfterUpdatePosition = ( mEventData->mLeftSelectionPosition != mEventData->mRightSelectionPosition );
2395 else if( Controller::NoTextTap::SHOW_SELECTION_POPUP == action )
2397 // Nothing to select. i.e. a white space, out of bounds
2398 ChangeState( EventData::EDITING_WITH_POPUP );
2400 mEventData->mPrimaryCursorPosition = noTextHitIndex;
2402 mEventData->mUpdateCursorPosition = true;
2403 mEventData->mUpdateGrabHandlePosition = true;
2404 mEventData->mScrollAfterUpdatePosition = true;
2405 mEventData->mUpdateInputStyle = true;
2407 else if( Controller::NoTextTap::NO_ACTION == action )
2409 // Nothing to select. i.e. a white space, out of bounds
2410 mEventData->mPrimaryCursorPosition = noTextHitIndex;
2412 mEventData->mUpdateCursorPosition = true;
2413 mEventData->mUpdateGrabHandlePosition = true;
2414 mEventData->mScrollAfterUpdatePosition = true;
2415 mEventData->mUpdateInputStyle = true;
2419 void Controller::Impl::SetPopupButtons()
2422 * Sets the Popup buttons to be shown depending on State.
2424 * If SELECTING : CUT & COPY + ( PASTE & CLIPBOARD if content available to paste )
2426 * If EDITING_WITH_POPUP : SELECT & SELECT_ALL
2429 TextSelectionPopup::Buttons buttonsToShow = TextSelectionPopup::NONE;
2431 if( EventData::SELECTING == mEventData->mState )
2433 buttonsToShow = TextSelectionPopup::Buttons( TextSelectionPopup::CUT | TextSelectionPopup::COPY );
2435 if( !IsClipboardEmpty() )
2437 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
2438 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::CLIPBOARD ) );
2441 if( !mEventData->mAllTextSelected )
2443 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::SELECT_ALL ) );
2446 else if( EventData::EDITING_WITH_POPUP == mEventData->mState )
2448 if( mModel->mLogicalModel->mText.Count() && !IsShowingPlaceholderText() )
2450 buttonsToShow = TextSelectionPopup::Buttons( TextSelectionPopup::SELECT | TextSelectionPopup::SELECT_ALL );
2453 if( !IsClipboardEmpty() )
2455 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
2456 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::CLIPBOARD ) );
2459 else if( EventData::EDITING_WITH_PASTE_POPUP == mEventData->mState )
2461 if ( !IsClipboardEmpty() )
2463 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
2464 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::CLIPBOARD ) );
2468 mEventData->mDecorator->SetEnabledPopupButtons( buttonsToShow );
2471 void Controller::Impl::ChangeState( EventData::State newState )
2473 if( NULL == mEventData )
2475 // Nothing to do if there is no text input.
2479 DALI_LOG_INFO( gLogFilter, Debug::General, "ChangeState state:%d newstate:%d\n", mEventData->mState, newState );
2481 if( mEventData->mState != newState )
2483 mEventData->mPreviousState = mEventData->mState;
2484 mEventData->mState = newState;
2486 switch( mEventData->mState )
2488 case EventData::INACTIVE:
2490 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
2491 mEventData->mDecorator->StopCursorBlink();
2492 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
2493 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
2494 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
2495 mEventData->mDecorator->SetHighlightActive( false );
2496 mEventData->mDecorator->SetPopupActive( false );
2497 mEventData->mDecoratorUpdated = true;
2500 case EventData::INTERRUPTED:
2502 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
2503 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
2504 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
2505 mEventData->mDecorator->SetHighlightActive( false );
2506 mEventData->mDecorator->SetPopupActive( false );
2507 mEventData->mDecoratorUpdated = true;
2510 case EventData::SELECTING:
2512 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
2513 mEventData->mDecorator->StopCursorBlink();
2514 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
2515 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true );
2516 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true );
2517 mEventData->mDecorator->SetHighlightActive( true );
2518 if( mEventData->mGrabHandlePopupEnabled )
2521 mEventData->mDecorator->SetPopupActive( true );
2523 mEventData->mDecoratorUpdated = true;
2526 case EventData::EDITING:
2528 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
2529 if( mEventData->mCursorBlinkEnabled )
2531 mEventData->mDecorator->StartCursorBlink();
2533 // Grab handle is not shown until a tap is received whilst EDITING
2534 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
2535 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
2536 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
2537 mEventData->mDecorator->SetHighlightActive( false );
2538 if( mEventData->mGrabHandlePopupEnabled )
2540 mEventData->mDecorator->SetPopupActive( false );
2542 mEventData->mDecoratorUpdated = true;
2545 case EventData::EDITING_WITH_POPUP:
2547 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "EDITING_WITH_POPUP \n", newState );
2549 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
2550 if( mEventData->mCursorBlinkEnabled )
2552 mEventData->mDecorator->StartCursorBlink();
2554 if( mEventData->mSelectionEnabled )
2556 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
2557 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
2558 mEventData->mDecorator->SetHighlightActive( false );
2562 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
2564 if( mEventData->mGrabHandlePopupEnabled )
2567 mEventData->mDecorator->SetPopupActive( true );
2569 mEventData->mDecoratorUpdated = true;
2572 case EventData::EDITING_WITH_GRAB_HANDLE:
2574 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "EDITING_WITH_GRAB_HANDLE \n", newState );
2576 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
2577 if( mEventData->mCursorBlinkEnabled )
2579 mEventData->mDecorator->StartCursorBlink();
2581 // Grab handle is not shown until a tap is received whilst EDITING
2582 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
2583 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
2584 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
2585 mEventData->mDecorator->SetHighlightActive( false );
2586 if( mEventData->mGrabHandlePopupEnabled )
2588 mEventData->mDecorator->SetPopupActive( false );
2590 mEventData->mDecoratorUpdated = true;
2593 case EventData::SELECTION_HANDLE_PANNING:
2595 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
2596 mEventData->mDecorator->StopCursorBlink();
2597 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
2598 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true );
2599 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true );
2600 mEventData->mDecorator->SetHighlightActive( true );
2601 if( mEventData->mGrabHandlePopupEnabled )
2603 mEventData->mDecorator->SetPopupActive( false );
2605 mEventData->mDecoratorUpdated = true;
2608 case EventData::GRAB_HANDLE_PANNING:
2610 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "GRAB_HANDLE_PANNING \n", newState );
2612 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
2613 if( mEventData->mCursorBlinkEnabled )
2615 mEventData->mDecorator->StartCursorBlink();
2617 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
2618 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
2619 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
2620 mEventData->mDecorator->SetHighlightActive( false );
2621 if( mEventData->mGrabHandlePopupEnabled )
2623 mEventData->mDecorator->SetPopupActive( false );
2625 mEventData->mDecoratorUpdated = true;
2628 case EventData::EDITING_WITH_PASTE_POPUP:
2630 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "EDITING_WITH_PASTE_POPUP \n", newState );
2632 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
2633 if( mEventData->mCursorBlinkEnabled )
2635 mEventData->mDecorator->StartCursorBlink();
2638 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
2639 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
2640 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
2641 mEventData->mDecorator->SetHighlightActive( false );
2643 if( mEventData->mGrabHandlePopupEnabled )
2646 mEventData->mDecorator->SetPopupActive( true );
2648 mEventData->mDecoratorUpdated = true;
2651 case EventData::TEXT_PANNING:
2653 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
2654 mEventData->mDecorator->StopCursorBlink();
2655 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
2656 if( mEventData->mDecorator->IsHandleActive( LEFT_SELECTION_HANDLE ) ||
2657 mEventData->mDecorator->IsHandleActive( RIGHT_SELECTION_HANDLE ) )
2659 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
2660 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
2661 mEventData->mDecorator->SetHighlightActive( true );
2664 if( mEventData->mGrabHandlePopupEnabled )
2666 mEventData->mDecorator->SetPopupActive( false );
2669 mEventData->mDecoratorUpdated = true;
2676 void Controller::Impl::GetCursorPosition( CharacterIndex logical,
2677 CursorInfo& cursorInfo )
2679 if( !IsShowingRealText() )
2681 // Do not want to use the place-holder text to set the cursor position.
2683 // Use the line's height of the font's family set to set the cursor's size.
2684 // If there is no font's family set, use the default font.
2685 // Use the current alignment to place the cursor at the beginning, center or end of the box.
2687 cursorInfo.lineOffset = 0.f;
2688 cursorInfo.lineHeight = GetDefaultFontLineHeight();
2689 cursorInfo.primaryCursorHeight = cursorInfo.lineHeight;
2691 switch( mModel->mHorizontalAlignment )
2693 case Text::HorizontalAlignment::BEGIN :
2695 cursorInfo.primaryPosition.x = 0.f;
2698 case Text::HorizontalAlignment::CENTER:
2700 cursorInfo.primaryPosition.x = floorf( 0.5f * mModel->mVisualModel->mControlSize.width );
2703 case Text::HorizontalAlignment::END:
2705 cursorInfo.primaryPosition.x = mModel->mVisualModel->mControlSize.width - mEventData->mDecorator->GetCursorWidth();
2710 // Nothing else to do.
2714 const bool isMultiLine = ( Layout::Engine::MULTI_LINE_BOX == mLayoutEngine.GetLayout() );
2715 GetCursorPositionParameters parameters;
2716 parameters.visualModel = mModel->mVisualModel;
2717 parameters.logicalModel = mModel->mLogicalModel;
2718 parameters.metrics = mMetrics;
2719 parameters.logical = logical;
2720 parameters.isMultiline = isMultiLine;
2722 Text::GetCursorPosition( parameters,
2725 // Adds Outline offset.
2726 const float outlineWidth = static_cast<float>( mModel->GetOutlineWidth() );
2727 cursorInfo.primaryPosition.x += outlineWidth;
2728 cursorInfo.primaryPosition.y += outlineWidth;
2729 cursorInfo.secondaryPosition.x += outlineWidth;
2730 cursorInfo.secondaryPosition.y += outlineWidth;
2734 // If the text is editable and multi-line, the cursor position after a white space shouldn't exceed the boundaries of the text control.
2736 // Note the white spaces laid-out at the end of the line might exceed the boundaries of the control.
2737 // The reason is a wrapped line must not start with a white space so they are laid-out at the end of the line.
2739 if( 0.f > cursorInfo.primaryPosition.x )
2741 cursorInfo.primaryPosition.x = 0.f;
2744 const float edgeWidth = mModel->mVisualModel->mControlSize.width - static_cast<float>( mEventData->mDecorator->GetCursorWidth() );
2745 if( cursorInfo.primaryPosition.x > edgeWidth )
2747 cursorInfo.primaryPosition.x = edgeWidth;
2752 CharacterIndex Controller::Impl::CalculateNewCursorIndex( CharacterIndex index ) const
2754 if( NULL == mEventData )
2756 // Nothing to do if there is no text input.
2760 CharacterIndex cursorIndex = mEventData->mPrimaryCursorPosition;
2762 const GlyphIndex* const charactersToGlyphBuffer = mModel->mVisualModel->mCharactersToGlyph.Begin();
2763 const Length* const charactersPerGlyphBuffer = mModel->mVisualModel->mCharactersPerGlyph.Begin();
2765 GlyphIndex glyphIndex = *( charactersToGlyphBuffer + index );
2766 Length numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
2768 if( numberOfCharacters > 1u )
2770 const Script script = mModel->mLogicalModel->GetScript( index );
2771 if( HasLigatureMustBreak( script ) )
2773 // Prevents to jump the whole Latin ligatures like fi, ff, or Arabic ï»», ...
2774 numberOfCharacters = 1u;
2779 while( 0u == numberOfCharacters )
2782 numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
2786 if( index < mEventData->mPrimaryCursorPosition )
2788 cursorIndex -= numberOfCharacters;
2792 cursorIndex += numberOfCharacters;
2795 // Will update the cursor hook position.
2796 mEventData->mUpdateCursorHookPosition = true;
2801 void Controller::Impl::UpdateCursorPosition( const CursorInfo& cursorInfo )
2803 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "-->Controller::UpdateCursorPosition %p\n", this );
2804 if( NULL == mEventData )
2806 // Nothing to do if there is no text input.
2807 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition no event data\n" );
2811 const Vector2 cursorPosition = cursorInfo.primaryPosition + mModel->mScrollPosition;
2813 mEventData->mDecorator->SetGlyphOffset( PRIMARY_CURSOR, cursorInfo.glyphOffset );
2815 // Sets the cursor position.
2816 mEventData->mDecorator->SetPosition( PRIMARY_CURSOR,
2819 cursorInfo.primaryCursorHeight,
2820 cursorInfo.lineHeight );
2821 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Primary cursor position: %f,%f\n", cursorPosition.x, cursorPosition.y );
2823 if( mEventData->mUpdateGrabHandlePosition )
2825 // Sets the grab handle position.
2826 mEventData->mDecorator->SetPosition( GRAB_HANDLE,
2828 cursorInfo.lineOffset + mModel->mScrollPosition.y,
2829 cursorInfo.lineHeight );
2832 if( cursorInfo.isSecondaryCursor )
2834 mEventData->mDecorator->SetPosition( SECONDARY_CURSOR,
2835 cursorInfo.secondaryPosition.x + mModel->mScrollPosition.x,
2836 cursorInfo.secondaryPosition.y + mModel->mScrollPosition.y,
2837 cursorInfo.secondaryCursorHeight,
2838 cursorInfo.lineHeight );
2839 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Secondary cursor position: %f,%f\n", cursorInfo.secondaryPosition.x + mModel->mScrollPosition.x, cursorInfo.secondaryPosition.y + mModel->mScrollPosition.y );
2842 // Set which cursors are active according the state.
2843 if( EventData::IsEditingState( mEventData->mState ) || ( EventData::GRAB_HANDLE_PANNING == mEventData->mState ) )
2845 if( cursorInfo.isSecondaryCursor )
2847 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_BOTH );
2851 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
2856 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
2859 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition\n" );
2862 void Controller::Impl::UpdateSelectionHandle( HandleType handleType,
2863 const CursorInfo& cursorInfo )
2865 if( ( LEFT_SELECTION_HANDLE != handleType ) &&
2866 ( RIGHT_SELECTION_HANDLE != handleType ) )
2871 const Vector2 cursorPosition = cursorInfo.primaryPosition + mModel->mScrollPosition;
2873 // Sets the handle's position.
2874 mEventData->mDecorator->SetPosition( handleType,
2876 cursorInfo.lineOffset + mModel->mScrollPosition.y,
2877 cursorInfo.lineHeight );
2879 // If selection handle at start of the text and other at end of the text then all text is selected.
2880 const CharacterIndex startOfSelection = std::min( mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition );
2881 const CharacterIndex endOfSelection = std::max ( mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition );
2882 mEventData->mAllTextSelected = ( startOfSelection == 0 ) && ( endOfSelection == mModel->mLogicalModel->mText.Count() );
2885 void Controller::Impl::ClampHorizontalScroll( const Vector2& layoutSize )
2887 // Clamp between -space & -alignment offset.
2889 if( layoutSize.width > mModel->mVisualModel->mControlSize.width )
2891 const float space = ( layoutSize.width - mModel->mVisualModel->mControlSize.width ) + mModel->mAlignmentOffset;
2892 mModel->mScrollPosition.x = ( mModel->mScrollPosition.x < -space ) ? -space : mModel->mScrollPosition.x;
2893 mModel->mScrollPosition.x = ( mModel->mScrollPosition.x > -mModel->mAlignmentOffset ) ? -mModel->mAlignmentOffset : mModel->mScrollPosition.x;
2895 mEventData->mDecoratorUpdated = true;
2899 mModel->mScrollPosition.x = 0.f;
2903 void Controller::Impl::ClampVerticalScroll( const Vector2& layoutSize )
2905 if( Layout::Engine::SINGLE_LINE_BOX == mLayoutEngine.GetLayout() )
2907 // Nothing to do if the text is single line.
2911 // Clamp between -space & 0.
2912 if( layoutSize.height > mModel->mVisualModel->mControlSize.height )
2914 const float space = ( layoutSize.height - mModel->mVisualModel->mControlSize.height );
2915 mModel->mScrollPosition.y = ( mModel->mScrollPosition.y < -space ) ? -space : mModel->mScrollPosition.y;
2916 mModel->mScrollPosition.y = ( mModel->mScrollPosition.y > 0.f ) ? 0.f : mModel->mScrollPosition.y;
2918 mEventData->mDecoratorUpdated = true;
2922 mModel->mScrollPosition.y = 0.f;
2926 void Controller::Impl::ScrollToMakePositionVisible( const Vector2& position, float lineHeight )
2928 const float cursorWidth = mEventData->mDecorator ? static_cast<float>( mEventData->mDecorator->GetCursorWidth() ) : 0.f;
2930 // position is in actor's coords.
2931 const float positionEndX = position.x + cursorWidth;
2932 const float positionEndY = position.y + lineHeight;
2934 // Transform the position to decorator coords.
2935 const float decoratorPositionBeginX = position.x + mModel->mScrollPosition.x;
2936 const float decoratorPositionEndX = positionEndX + mModel->mScrollPosition.x;
2938 const float decoratorPositionBeginY = position.y + mModel->mScrollPosition.y;
2939 const float decoratorPositionEndY = positionEndY + mModel->mScrollPosition.y;
2941 if( decoratorPositionBeginX < 0.f )
2943 mModel->mScrollPosition.x = -position.x;
2945 else if( decoratorPositionEndX > mModel->mVisualModel->mControlSize.width )
2947 mModel->mScrollPosition.x = mModel->mVisualModel->mControlSize.width - positionEndX;
2950 if( Layout::Engine::MULTI_LINE_BOX == mLayoutEngine.GetLayout() )
2952 if( decoratorPositionBeginY < 0.f )
2954 mModel->mScrollPosition.y = -position.y;
2956 else if( decoratorPositionEndY > mModel->mVisualModel->mControlSize.height )
2958 mModel->mScrollPosition.y = mModel->mVisualModel->mControlSize.height - positionEndY;
2963 void Controller::Impl::ScrollTextToMatchCursor( const CursorInfo& cursorInfo )
2965 // Get the current cursor position in decorator coords.
2966 const Vector2& currentCursorPosition = mEventData->mDecorator->GetPosition( PRIMARY_CURSOR );
2968 const LineIndex lineIndex = mModel->mVisualModel->GetLineOfCharacter( mEventData->mPrimaryCursorPosition );
2972 // Calculate the offset to match the cursor position before the character was deleted.
2973 mModel->mScrollPosition.x = currentCursorPosition.x - cursorInfo.primaryPosition.x;
2975 //If text control has more than two lines and current line index is not last, calculate scrollpositionY
2976 if( mModel->mVisualModel->mLines.Count() > 1u && lineIndex != mModel->mVisualModel->mLines.Count() -1u )
2978 const float currentCursorGlyphOffset = mEventData->mDecorator->GetGlyphOffset( PRIMARY_CURSOR );
2979 mModel->mScrollPosition.y = currentCursorPosition.y - cursorInfo.lineOffset - currentCursorGlyphOffset;
2983 ClampHorizontalScroll( mModel->mVisualModel->GetLayoutSize() );
2984 ClampVerticalScroll( mModel->mVisualModel->GetLayoutSize() );
2986 // Makes the new cursor position visible if needed.
2987 ScrollToMakePositionVisible( cursorInfo.primaryPosition, cursorInfo.lineHeight );
2990 void Controller::Impl::RequestRelayout()
2992 if( NULL != mControlInterface )
2994 mControlInterface->RequestTextRelayout();
3000 } // namespace Toolkit