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 )
113 mImfManager = ImfManager::Get();
116 EventData::~EventData()
119 bool Controller::Impl::ProcessInputEvents()
121 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "-->Controller::ProcessInputEvents\n" );
122 if( NULL == mEventData )
124 // Nothing to do if there is no text input.
125 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::ProcessInputEvents no event data\n" );
129 if( mEventData->mDecorator )
131 for( std::vector<Event>::iterator iter = mEventData->mEventQueue.begin();
132 iter != mEventData->mEventQueue.end();
137 case Event::CURSOR_KEY_EVENT:
139 OnCursorKeyEvent( *iter );
142 case Event::TAP_EVENT:
147 case Event::LONG_PRESS_EVENT:
149 OnLongPressEvent( *iter );
152 case Event::PAN_EVENT:
157 case Event::GRAB_HANDLE_EVENT:
158 case Event::LEFT_SELECTION_HANDLE_EVENT:
159 case Event::RIGHT_SELECTION_HANDLE_EVENT: // Fall through
161 OnHandleEvent( *iter );
166 OnSelectEvent( *iter );
169 case Event::SELECT_ALL:
178 if( mEventData->mUpdateCursorPosition ||
179 mEventData->mUpdateHighlightBox )
184 // The cursor must also be repositioned after inserts into the model
185 if( mEventData->mUpdateCursorPosition )
187 // Updates the cursor position and scrolls the text to make it visible.
188 CursorInfo cursorInfo;
189 // Calculate the cursor position from the new cursor index.
190 GetCursorPosition( mEventData->mPrimaryCursorPosition,
193 if( mEventData->mUpdateCursorHookPosition )
195 // Update the cursor hook position. Used to move the cursor with the keys 'up' and 'down'.
196 mEventData->mCursorHookPositionX = cursorInfo.primaryPosition.x;
197 mEventData->mUpdateCursorHookPosition = false;
200 // Scroll first the text after delete ...
201 if( mEventData->mScrollAfterDelete )
203 ScrollTextToMatchCursor( cursorInfo );
206 // ... then, text can be scrolled to make the cursor visible.
207 if( mEventData->mScrollAfterUpdatePosition )
209 const Vector2 currentCursorPosition( cursorInfo.primaryPosition.x, cursorInfo.lineOffset );
210 ScrollToMakePositionVisible( currentCursorPosition, cursorInfo.lineHeight );
212 mEventData->mScrollAfterUpdatePosition = false;
213 mEventData->mScrollAfterDelete = false;
215 UpdateCursorPosition( cursorInfo );
217 mEventData->mDecoratorUpdated = true;
218 mEventData->mUpdateCursorPosition = false;
219 mEventData->mUpdateGrabHandlePosition = false;
223 CursorInfo leftHandleInfo;
224 CursorInfo rightHandleInfo;
226 if( mEventData->mUpdateHighlightBox )
228 GetCursorPosition( mEventData->mLeftSelectionPosition,
231 GetCursorPosition( mEventData->mRightSelectionPosition,
234 if( mEventData->mScrollAfterUpdatePosition && ( mEventData->mIsLeftHandleSelected ? mEventData->mUpdateLeftSelectionPosition : mEventData->mUpdateRightSelectionPosition ) )
236 if( mEventData->mIsLeftHandleSelected && mEventData->mIsRightHandleSelected )
238 CursorInfo& infoLeft = leftHandleInfo;
240 const Vector2 currentCursorPositionLeft( infoLeft.primaryPosition.x, infoLeft.lineOffset );
241 ScrollToMakePositionVisible( currentCursorPositionLeft, infoLeft.lineHeight );
243 CursorInfo& infoRight = rightHandleInfo;
245 const Vector2 currentCursorPositionRight( infoRight.primaryPosition.x, infoRight.lineOffset );
246 ScrollToMakePositionVisible( currentCursorPositionRight, infoRight.lineHeight );
250 CursorInfo& info = mEventData->mIsLeftHandleSelected ? leftHandleInfo : rightHandleInfo;
252 const Vector2 currentCursorPosition( info.primaryPosition.x, info.lineOffset );
253 ScrollToMakePositionVisible( currentCursorPosition, info.lineHeight );
258 if( mEventData->mUpdateLeftSelectionPosition )
260 UpdateSelectionHandle( LEFT_SELECTION_HANDLE,
264 mEventData->mDecoratorUpdated = true;
265 mEventData->mUpdateLeftSelectionPosition = false;
268 if( mEventData->mUpdateRightSelectionPosition )
270 UpdateSelectionHandle( RIGHT_SELECTION_HANDLE,
274 mEventData->mDecoratorUpdated = true;
275 mEventData->mUpdateRightSelectionPosition = false;
278 if( mEventData->mUpdateHighlightBox )
280 RepositionSelectionHandles();
282 mEventData->mUpdateLeftSelectionPosition = false;
283 mEventData->mUpdateRightSelectionPosition = false;
284 mEventData->mUpdateHighlightBox = false;
285 mEventData->mIsLeftHandleSelected = false;
286 mEventData->mIsRightHandleSelected = false;
289 mEventData->mScrollAfterUpdatePosition = false;
292 if( mEventData->mUpdateInputStyle )
294 // Keep a copy of the current input style.
295 InputStyle currentInputStyle;
296 currentInputStyle.Copy( mEventData->mInputStyle );
298 // Set the default style first.
299 RetrieveDefaultInputStyle( mEventData->mInputStyle );
301 // Get the character index from the cursor index.
302 const CharacterIndex styleIndex = ( mEventData->mPrimaryCursorPosition > 0u ) ? mEventData->mPrimaryCursorPosition - 1u : 0u;
304 // Retrieve the style from the style runs stored in the logical model.
305 mModel->mLogicalModel->RetrieveStyle( styleIndex, mEventData->mInputStyle );
307 // Compare if the input style has changed.
308 const bool hasInputStyleChanged = !currentInputStyle.Equal( mEventData->mInputStyle );
310 if( hasInputStyleChanged )
312 const InputStyle::Mask styleChangedMask = currentInputStyle.GetInputStyleChangeMask( mEventData->mInputStyle );
313 // Queue the input style changed signal.
314 mEventData->mInputStyleChangedQueue.PushBack( styleChangedMask );
317 mEventData->mUpdateInputStyle = false;
320 mEventData->mEventQueue.clear();
322 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::ProcessInputEvents\n" );
324 const bool decoratorUpdated = mEventData->mDecoratorUpdated;
325 mEventData->mDecoratorUpdated = false;
327 return decoratorUpdated;
330 void Controller::Impl::NotifyImfManager()
332 if( mEventData && mEventData->mImfManager )
334 CharacterIndex cursorPosition = GetLogicalCursorPosition();
336 const Length numberOfWhiteSpaces = GetNumberOfWhiteSpaces( 0u );
338 // Update the cursor position by removing the initial white spaces.
339 if( cursorPosition < numberOfWhiteSpaces )
345 cursorPosition -= numberOfWhiteSpaces;
348 mEventData->mImfManager.SetCursorPosition( cursorPosition );
349 mEventData->mImfManager.NotifyCursorPosition();
353 void Controller::Impl::NotifyImfMultiLineStatus()
357 Text::Layout::Engine::Type layout = mLayoutEngine.GetLayout();
358 mEventData->mImfManager.NotifyTextInputMultiLine( layout == Text::Layout::Engine::MULTI_LINE_BOX );
362 CharacterIndex Controller::Impl::GetLogicalCursorPosition() const
364 CharacterIndex cursorPosition = 0u;
368 if( ( EventData::SELECTING == mEventData->mState ) ||
369 ( EventData::SELECTION_HANDLE_PANNING == mEventData->mState ) )
371 cursorPosition = std::min( mEventData->mRightSelectionPosition, mEventData->mLeftSelectionPosition );
375 cursorPosition = mEventData->mPrimaryCursorPosition;
379 return cursorPosition;
382 Length Controller::Impl::GetNumberOfWhiteSpaces( CharacterIndex index ) const
384 Length numberOfWhiteSpaces = 0u;
386 // Get the buffer to the text.
387 Character* utf32CharacterBuffer = mModel->mLogicalModel->mText.Begin();
389 const Length totalNumberOfCharacters = mModel->mLogicalModel->mText.Count();
390 for( ; index < totalNumberOfCharacters; ++index, ++numberOfWhiteSpaces )
392 if( !TextAbstraction::IsWhiteSpace( *( utf32CharacterBuffer + index ) ) )
398 return numberOfWhiteSpaces;
401 void Controller::Impl::GetText( CharacterIndex index, std::string& text ) const
403 // Get the total number of characters.
404 Length numberOfCharacters = mModel->mLogicalModel->mText.Count();
406 // Retrieve the text.
407 if( 0u != numberOfCharacters )
409 Utf32ToUtf8( mModel->mLogicalModel->mText.Begin() + index, numberOfCharacters - index, text );
413 void Controller::Impl::CalculateTextUpdateIndices( Length& numberOfCharacters )
415 mTextUpdateInfo.mParagraphCharacterIndex = 0u;
416 mTextUpdateInfo.mStartGlyphIndex = 0u;
417 mTextUpdateInfo.mStartLineIndex = 0u;
418 numberOfCharacters = 0u;
420 const Length numberOfParagraphs = mModel->mLogicalModel->mParagraphInfo.Count();
421 if( 0u == numberOfParagraphs )
423 mTextUpdateInfo.mParagraphCharacterIndex = 0u;
424 numberOfCharacters = 0u;
426 mTextUpdateInfo.mRequestedNumberOfCharacters = mTextUpdateInfo.mNumberOfCharactersToAdd - mTextUpdateInfo.mNumberOfCharactersToRemove;
428 // Nothing else to do if there are no paragraphs.
432 // Find the paragraphs to be updated.
433 Vector<ParagraphRunIndex> paragraphsToBeUpdated;
434 if( mTextUpdateInfo.mCharacterIndex >= mTextUpdateInfo.mPreviousNumberOfCharacters )
436 // Text is being added at the end of the current text.
437 if( mTextUpdateInfo.mIsLastCharacterNewParagraph )
439 // Text is being added in a new paragraph after the last character of the text.
440 mTextUpdateInfo.mParagraphCharacterIndex = mTextUpdateInfo.mPreviousNumberOfCharacters;
441 numberOfCharacters = 0u;
442 mTextUpdateInfo.mRequestedNumberOfCharacters = mTextUpdateInfo.mNumberOfCharactersToAdd - mTextUpdateInfo.mNumberOfCharactersToRemove;
444 mTextUpdateInfo.mStartGlyphIndex = mModel->mVisualModel->mGlyphs.Count();
445 mTextUpdateInfo.mStartLineIndex = mModel->mVisualModel->mLines.Count() - 1u;
447 // Nothing else to do;
451 paragraphsToBeUpdated.PushBack( numberOfParagraphs - 1u );
455 Length numberOfCharactersToUpdate = 0u;
456 if( mTextUpdateInfo.mFullRelayoutNeeded )
458 numberOfCharactersToUpdate = mTextUpdateInfo.mPreviousNumberOfCharacters;
462 numberOfCharactersToUpdate = ( mTextUpdateInfo.mNumberOfCharactersToRemove > 0u ) ? mTextUpdateInfo.mNumberOfCharactersToRemove : 1u;
464 mModel->mLogicalModel->FindParagraphs( mTextUpdateInfo.mCharacterIndex,
465 numberOfCharactersToUpdate,
466 paragraphsToBeUpdated );
469 if( 0u != paragraphsToBeUpdated.Count() )
471 const ParagraphRunIndex firstParagraphIndex = *( paragraphsToBeUpdated.Begin() );
472 const ParagraphRun& firstParagraph = *( mModel->mLogicalModel->mParagraphInfo.Begin() + firstParagraphIndex );
473 mTextUpdateInfo.mParagraphCharacterIndex = firstParagraph.characterRun.characterIndex;
475 ParagraphRunIndex lastParagraphIndex = *( paragraphsToBeUpdated.End() - 1u );
476 const ParagraphRun& lastParagraph = *( mModel->mLogicalModel->mParagraphInfo.Begin() + lastParagraphIndex );
478 if( ( mTextUpdateInfo.mNumberOfCharactersToRemove > 0u ) && // Some character are removed.
479 ( lastParagraphIndex < numberOfParagraphs - 1u ) && // There is a next paragraph.
480 ( ( lastParagraph.characterRun.characterIndex + lastParagraph.characterRun.numberOfCharacters ) == // The last removed character is the new paragraph character.
481 ( mTextUpdateInfo.mCharacterIndex + mTextUpdateInfo.mNumberOfCharactersToRemove ) ) )
483 // The new paragraph character of the last updated paragraph has been removed so is going to be merged with the next one.
484 const ParagraphRun& lastParagraph = *( mModel->mLogicalModel->mParagraphInfo.Begin() + lastParagraphIndex + 1u );
486 numberOfCharacters = lastParagraph.characterRun.characterIndex + lastParagraph.characterRun.numberOfCharacters - mTextUpdateInfo.mParagraphCharacterIndex;
490 numberOfCharacters = lastParagraph.characterRun.characterIndex + lastParagraph.characterRun.numberOfCharacters - mTextUpdateInfo.mParagraphCharacterIndex;
494 mTextUpdateInfo.mRequestedNumberOfCharacters = numberOfCharacters + mTextUpdateInfo.mNumberOfCharactersToAdd - mTextUpdateInfo.mNumberOfCharactersToRemove;
495 mTextUpdateInfo.mStartGlyphIndex = *( mModel->mVisualModel->mCharactersToGlyph.Begin() + mTextUpdateInfo.mParagraphCharacterIndex );
498 void Controller::Impl::ClearFullModelData( OperationsMask operations )
500 if( NO_OPERATION != ( GET_LINE_BREAKS & operations ) )
502 mModel->mLogicalModel->mLineBreakInfo.Clear();
503 mModel->mLogicalModel->mParagraphInfo.Clear();
506 if( NO_OPERATION != ( GET_WORD_BREAKS & operations ) )
508 mModel->mLogicalModel->mLineBreakInfo.Clear();
511 if( NO_OPERATION != ( GET_SCRIPTS & operations ) )
513 mModel->mLogicalModel->mScriptRuns.Clear();
516 if( NO_OPERATION != ( VALIDATE_FONTS & operations ) )
518 mModel->mLogicalModel->mFontRuns.Clear();
521 if( 0u != mModel->mLogicalModel->mBidirectionalParagraphInfo.Count() )
523 if( NO_OPERATION != ( BIDI_INFO & operations ) )
525 mModel->mLogicalModel->mBidirectionalParagraphInfo.Clear();
526 mModel->mLogicalModel->mCharacterDirections.Clear();
529 if( NO_OPERATION != ( REORDER & operations ) )
531 // Free the allocated memory used to store the conversion table in the bidirectional line info run.
532 for( Vector<BidirectionalLineInfoRun>::Iterator it = mModel->mLogicalModel->mBidirectionalLineInfo.Begin(),
533 endIt = mModel->mLogicalModel->mBidirectionalLineInfo.End();
537 BidirectionalLineInfoRun& bidiLineInfo = *it;
539 free( bidiLineInfo.visualToLogicalMap );
540 bidiLineInfo.visualToLogicalMap = NULL;
542 mModel->mLogicalModel->mBidirectionalLineInfo.Clear();
546 if( NO_OPERATION != ( SHAPE_TEXT & operations ) )
548 mModel->mVisualModel->mGlyphs.Clear();
549 mModel->mVisualModel->mGlyphsToCharacters.Clear();
550 mModel->mVisualModel->mCharactersToGlyph.Clear();
551 mModel->mVisualModel->mCharactersPerGlyph.Clear();
552 mModel->mVisualModel->mGlyphsPerCharacter.Clear();
553 mModel->mVisualModel->mGlyphPositions.Clear();
556 if( NO_OPERATION != ( LAYOUT & operations ) )
558 mModel->mVisualModel->mLines.Clear();
561 if( NO_OPERATION != ( COLOR & operations ) )
563 mModel->mVisualModel->mColorIndices.Clear();
567 void Controller::Impl::ClearCharacterModelData( CharacterIndex startIndex, CharacterIndex endIndex, OperationsMask operations )
569 const CharacterIndex endIndexPlusOne = endIndex + 1u;
571 if( NO_OPERATION != ( GET_LINE_BREAKS & operations ) )
573 // Clear the line break info.
574 LineBreakInfo* lineBreakInfoBuffer = mModel->mLogicalModel->mLineBreakInfo.Begin();
576 mModel->mLogicalModel->mLineBreakInfo.Erase( lineBreakInfoBuffer + startIndex,
577 lineBreakInfoBuffer + endIndexPlusOne );
579 // Clear the paragraphs.
580 ClearCharacterRuns( startIndex,
582 mModel->mLogicalModel->mParagraphInfo );
585 if( NO_OPERATION != ( GET_WORD_BREAKS & operations ) )
587 // Clear the word break info.
588 WordBreakInfo* wordBreakInfoBuffer = mModel->mLogicalModel->mWordBreakInfo.Begin();
590 mModel->mLogicalModel->mWordBreakInfo.Erase( wordBreakInfoBuffer + startIndex,
591 wordBreakInfoBuffer + endIndexPlusOne );
594 if( NO_OPERATION != ( GET_SCRIPTS & operations ) )
596 // Clear the scripts.
597 ClearCharacterRuns( startIndex,
599 mModel->mLogicalModel->mScriptRuns );
602 if( NO_OPERATION != ( VALIDATE_FONTS & operations ) )
605 ClearCharacterRuns( startIndex,
607 mModel->mLogicalModel->mFontRuns );
610 if( 0u != mModel->mLogicalModel->mBidirectionalParagraphInfo.Count() )
612 if( NO_OPERATION != ( BIDI_INFO & operations ) )
614 // Clear the bidirectional paragraph info.
615 ClearCharacterRuns( startIndex,
617 mModel->mLogicalModel->mBidirectionalParagraphInfo );
619 // Clear the character's directions.
620 CharacterDirection* characterDirectionsBuffer = mModel->mLogicalModel->mCharacterDirections.Begin();
622 mModel->mLogicalModel->mCharacterDirections.Erase( characterDirectionsBuffer + startIndex,
623 characterDirectionsBuffer + endIndexPlusOne );
626 if( NO_OPERATION != ( REORDER & operations ) )
628 uint32_t startRemoveIndex = mModel->mLogicalModel->mBidirectionalLineInfo.Count();
629 uint32_t endRemoveIndex = startRemoveIndex;
630 ClearCharacterRuns( startIndex,
632 mModel->mLogicalModel->mBidirectionalLineInfo,
636 BidirectionalLineInfoRun* bidirectionalLineInfoBuffer = mModel->mLogicalModel->mBidirectionalLineInfo.Begin();
638 // Free the allocated memory used to store the conversion table in the bidirectional line info run.
639 for( Vector<BidirectionalLineInfoRun>::Iterator it = bidirectionalLineInfoBuffer + startRemoveIndex,
640 endIt = bidirectionalLineInfoBuffer + endRemoveIndex;
644 BidirectionalLineInfoRun& bidiLineInfo = *it;
646 free( bidiLineInfo.visualToLogicalMap );
647 bidiLineInfo.visualToLogicalMap = NULL;
650 mModel->mLogicalModel->mBidirectionalLineInfo.Erase( bidirectionalLineInfoBuffer + startRemoveIndex,
651 bidirectionalLineInfoBuffer + endRemoveIndex );
656 void Controller::Impl::ClearGlyphModelData( CharacterIndex startIndex, CharacterIndex endIndex, OperationsMask operations )
658 const CharacterIndex endIndexPlusOne = endIndex + 1u;
659 const Length numberOfCharactersRemoved = endIndexPlusOne - startIndex;
661 // Convert the character index to glyph index before deleting the character to glyph and the glyphs per character buffers.
662 GlyphIndex* charactersToGlyphBuffer = mModel->mVisualModel->mCharactersToGlyph.Begin();
663 Length* glyphsPerCharacterBuffer = mModel->mVisualModel->mGlyphsPerCharacter.Begin();
665 const GlyphIndex endGlyphIndexPlusOne = *( charactersToGlyphBuffer + endIndex ) + *( glyphsPerCharacterBuffer + endIndex );
666 const Length numberOfGlyphsRemoved = endGlyphIndexPlusOne - mTextUpdateInfo.mStartGlyphIndex;
668 if( NO_OPERATION != ( SHAPE_TEXT & operations ) )
670 // Update the character to glyph indices.
671 for( Vector<GlyphIndex>::Iterator it = charactersToGlyphBuffer + endIndexPlusOne,
672 endIt = charactersToGlyphBuffer + mModel->mVisualModel->mCharactersToGlyph.Count();
676 CharacterIndex& index = *it;
677 index -= numberOfGlyphsRemoved;
680 // Clear the character to glyph conversion table.
681 mModel->mVisualModel->mCharactersToGlyph.Erase( charactersToGlyphBuffer + startIndex,
682 charactersToGlyphBuffer + endIndexPlusOne );
684 // Clear the glyphs per character table.
685 mModel->mVisualModel->mGlyphsPerCharacter.Erase( glyphsPerCharacterBuffer + startIndex,
686 glyphsPerCharacterBuffer + endIndexPlusOne );
688 // Clear the glyphs buffer.
689 GlyphInfo* glyphsBuffer = mModel->mVisualModel->mGlyphs.Begin();
690 mModel->mVisualModel->mGlyphs.Erase( glyphsBuffer + mTextUpdateInfo.mStartGlyphIndex,
691 glyphsBuffer + endGlyphIndexPlusOne );
693 CharacterIndex* glyphsToCharactersBuffer = mModel->mVisualModel->mGlyphsToCharacters.Begin();
695 // Update the glyph to character indices.
696 for( Vector<CharacterIndex>::Iterator it = glyphsToCharactersBuffer + endGlyphIndexPlusOne,
697 endIt = glyphsToCharactersBuffer + mModel->mVisualModel->mGlyphsToCharacters.Count();
701 CharacterIndex& index = *it;
702 index -= numberOfCharactersRemoved;
705 // Clear the glyphs to characters buffer.
706 mModel->mVisualModel->mGlyphsToCharacters.Erase( glyphsToCharactersBuffer + mTextUpdateInfo.mStartGlyphIndex,
707 glyphsToCharactersBuffer + endGlyphIndexPlusOne );
709 // Clear the characters per glyph buffer.
710 Length* charactersPerGlyphBuffer = mModel->mVisualModel->mCharactersPerGlyph.Begin();
711 mModel->mVisualModel->mCharactersPerGlyph.Erase( charactersPerGlyphBuffer + mTextUpdateInfo.mStartGlyphIndex,
712 charactersPerGlyphBuffer + endGlyphIndexPlusOne );
714 // Clear the positions buffer.
715 Vector2* positionsBuffer = mModel->mVisualModel->mGlyphPositions.Begin();
716 mModel->mVisualModel->mGlyphPositions.Erase( positionsBuffer + mTextUpdateInfo.mStartGlyphIndex,
717 positionsBuffer + endGlyphIndexPlusOne );
720 if( NO_OPERATION != ( LAYOUT & operations ) )
723 uint32_t startRemoveIndex = mModel->mVisualModel->mLines.Count();
724 uint32_t endRemoveIndex = startRemoveIndex;
725 ClearCharacterRuns( startIndex,
727 mModel->mVisualModel->mLines,
731 // Will update the glyph runs.
732 startRemoveIndex = mModel->mVisualModel->mLines.Count();
733 endRemoveIndex = startRemoveIndex;
734 ClearGlyphRuns( mTextUpdateInfo.mStartGlyphIndex,
735 endGlyphIndexPlusOne - 1u,
736 mModel->mVisualModel->mLines,
740 // Set the line index from where to insert the new laid-out lines.
741 mTextUpdateInfo.mStartLineIndex = startRemoveIndex;
743 LineRun* linesBuffer = mModel->mVisualModel->mLines.Begin();
744 mModel->mVisualModel->mLines.Erase( linesBuffer + startRemoveIndex,
745 linesBuffer + endRemoveIndex );
748 if( NO_OPERATION != ( COLOR & operations ) )
750 if( 0u != mModel->mVisualModel->mColorIndices.Count() )
752 ColorIndex* colorIndexBuffer = mModel->mVisualModel->mColorIndices.Begin();
753 mModel->mVisualModel->mColorIndices.Erase( colorIndexBuffer + mTextUpdateInfo.mStartGlyphIndex,
754 colorIndexBuffer + endGlyphIndexPlusOne );
759 void Controller::Impl::ClearModelData( CharacterIndex startIndex, CharacterIndex endIndex, OperationsMask operations )
761 if( mTextUpdateInfo.mClearAll ||
762 ( ( 0u == startIndex ) &&
763 ( mTextUpdateInfo.mPreviousNumberOfCharacters == endIndex + 1u ) ) )
765 ClearFullModelData( operations );
769 // Clear the model data related with characters.
770 ClearCharacterModelData( startIndex, endIndex, operations );
772 // Clear the model data related with glyphs.
773 ClearGlyphModelData( startIndex, endIndex, operations );
776 // The estimated number of lines. Used to avoid reallocations when layouting.
777 mTextUpdateInfo.mEstimatedNumberOfLines = std::max( mModel->mVisualModel->mLines.Count(), mModel->mLogicalModel->mParagraphInfo.Count() );
779 mModel->mVisualModel->ClearCaches();
782 bool Controller::Impl::UpdateModel( OperationsMask operationsRequired )
784 DALI_LOG_INFO( gLogFilter, Debug::General, "Controller::UpdateModel\n" );
786 // Calculate the operations to be done.
787 const OperationsMask operations = static_cast<OperationsMask>( mOperationsPending & operationsRequired );
789 if( NO_OPERATION == operations )
791 // Nothing to do if no operations are pending and required.
795 Vector<Character>& srcCharacters = mModel->mLogicalModel->mText;
796 Vector<Character> displayCharacters;
797 bool useHiddenText = false;
798 if ( mHiddenInput && mEventData != NULL && !mEventData->mIsShowingPlaceholderText)
800 mHiddenInput->Substitute( srcCharacters,displayCharacters );
801 useHiddenText = true;
804 Vector<Character>& utf32Characters = useHiddenText ? displayCharacters : srcCharacters;
805 const Length numberOfCharacters = utf32Characters.Count();
807 // Index to the first character of the first paragraph to be updated.
808 CharacterIndex startIndex = 0u;
809 // Number of characters of the paragraphs to be removed.
810 Length paragraphCharacters = 0u;
812 CalculateTextUpdateIndices( paragraphCharacters );
813 startIndex = mTextUpdateInfo.mParagraphCharacterIndex;
815 if( mTextUpdateInfo.mClearAll ||
816 ( 0u != paragraphCharacters ) )
818 ClearModelData( startIndex, startIndex + ( ( paragraphCharacters > 0u ) ? paragraphCharacters - 1u : 0u ), operations );
821 mTextUpdateInfo.mClearAll = false;
823 // Whether the model is updated.
824 bool updated = false;
826 Vector<LineBreakInfo>& lineBreakInfo = mModel->mLogicalModel->mLineBreakInfo;
827 const Length requestedNumberOfCharacters = mTextUpdateInfo.mRequestedNumberOfCharacters;
829 if( NO_OPERATION != ( GET_LINE_BREAKS & operations ) )
831 // Retrieves the line break info. The line break info is used to split the text in 'paragraphs' to
832 // calculate the bidirectional info for each 'paragraph'.
833 // It's also used to layout the text (where it should be a new line) or to shape the text (text in different lines
834 // is not shaped together).
835 lineBreakInfo.Resize( numberOfCharacters, TextAbstraction::LINE_NO_BREAK );
837 SetLineBreakInfo( utf32Characters,
839 requestedNumberOfCharacters,
842 // Create the paragraph info.
843 mModel->mLogicalModel->CreateParagraphInfo( startIndex,
844 requestedNumberOfCharacters );
848 Vector<WordBreakInfo>& wordBreakInfo = mModel->mLogicalModel->mWordBreakInfo;
849 if( NO_OPERATION != ( GET_WORD_BREAKS & operations ) )
851 // Retrieves the word break info. The word break info is used to layout the text (where to wrap the text in lines).
852 wordBreakInfo.Resize( numberOfCharacters, TextAbstraction::WORD_NO_BREAK );
854 SetWordBreakInfo( utf32Characters,
856 requestedNumberOfCharacters,
861 const bool getScripts = NO_OPERATION != ( GET_SCRIPTS & operations );
862 const bool validateFonts = NO_OPERATION != ( VALIDATE_FONTS & operations );
864 Vector<ScriptRun>& scripts = mModel->mLogicalModel->mScriptRuns;
865 Vector<FontRun>& validFonts = mModel->mLogicalModel->mFontRuns;
867 if( getScripts || validateFonts )
869 // Validates the fonts assigned by the application or assigns default ones.
870 // It makes sure all the characters are going to be rendered by the correct font.
871 MultilanguageSupport multilanguageSupport = MultilanguageSupport::Get();
875 // Retrieves the scripts used in the text.
876 multilanguageSupport.SetScripts( utf32Characters,
878 requestedNumberOfCharacters,
884 // Validate the fonts set through the mark-up string.
885 Vector<FontDescriptionRun>& fontDescriptionRuns = mModel->mLogicalModel->mFontDescriptionRuns;
887 // Get the default font's description.
888 TextAbstraction::FontDescription defaultFontDescription;
889 TextAbstraction::PointSize26Dot6 defaultPointSize = TextAbstraction::FontClient::DEFAULT_POINT_SIZE;
891 if( IsShowingPlaceholderText() && ( NULL != mEventData->mPlaceholderFont ) )
893 // If the placeholder font is set specifically, only placeholder font is changed.
894 defaultFontDescription = mEventData->mPlaceholderFont->mFontDescription;
895 if( mEventData->mPlaceholderFont->sizeDefined )
897 defaultPointSize = mEventData->mPlaceholderFont->mDefaultPointSize * 64u;
900 else if( NULL != mFontDefaults )
902 // Set the normal font and the placeholder font.
903 defaultFontDescription = mFontDefaults->mFontDescription;
904 defaultPointSize = mFontDefaults->mDefaultPointSize * 64u;
907 // Validates the fonts. If there is a character with no assigned font it sets a default one.
908 // After this call, fonts are validated.
909 multilanguageSupport.ValidateFonts( utf32Characters,
912 defaultFontDescription,
915 requestedNumberOfCharacters,
921 Vector<Character> mirroredUtf32Characters;
922 bool textMirrored = false;
923 const Length numberOfParagraphs = mModel->mLogicalModel->mParagraphInfo.Count();
924 if( NO_OPERATION != ( BIDI_INFO & operations ) )
926 Vector<BidirectionalParagraphInfoRun>& bidirectionalInfo = mModel->mLogicalModel->mBidirectionalParagraphInfo;
927 bidirectionalInfo.Reserve( numberOfParagraphs );
929 // Calculates the bidirectional info for the whole paragraph if it contains right to left scripts.
930 SetBidirectionalInfo( utf32Characters,
934 requestedNumberOfCharacters,
937 if( 0u != bidirectionalInfo.Count() )
939 // Only set the character directions if there is right to left characters.
940 Vector<CharacterDirection>& directions = mModel->mLogicalModel->mCharacterDirections;
941 GetCharactersDirection( bidirectionalInfo,
944 requestedNumberOfCharacters,
947 // This paragraph has right to left text. Some characters may need to be mirrored.
948 // TODO: consider if the mirrored string can be stored as well.
950 textMirrored = GetMirroredText( utf32Characters,
954 requestedNumberOfCharacters,
955 mirroredUtf32Characters );
959 // There is no right to left characters. Clear the directions vector.
960 mModel->mLogicalModel->mCharacterDirections.Clear();
965 Vector<GlyphInfo>& glyphs = mModel->mVisualModel->mGlyphs;
966 Vector<CharacterIndex>& glyphsToCharactersMap = mModel->mVisualModel->mGlyphsToCharacters;
967 Vector<Length>& charactersPerGlyph = mModel->mVisualModel->mCharactersPerGlyph;
968 Vector<GlyphIndex> newParagraphGlyphs;
969 newParagraphGlyphs.Reserve( numberOfParagraphs );
971 const Length currentNumberOfGlyphs = glyphs.Count();
972 if( NO_OPERATION != ( SHAPE_TEXT & operations ) )
974 const Vector<Character>& textToShape = textMirrored ? mirroredUtf32Characters : utf32Characters;
976 ShapeText( textToShape,
981 mTextUpdateInfo.mStartGlyphIndex,
982 requestedNumberOfCharacters,
984 glyphsToCharactersMap,
986 newParagraphGlyphs );
988 // Create the 'number of glyphs' per character and the glyph to character conversion tables.
989 mModel->mVisualModel->CreateGlyphsPerCharacterTable( startIndex, mTextUpdateInfo.mStartGlyphIndex, requestedNumberOfCharacters );
990 mModel->mVisualModel->CreateCharacterToGlyphTable( startIndex, mTextUpdateInfo.mStartGlyphIndex, requestedNumberOfCharacters );
994 const Length numberOfGlyphs = glyphs.Count() - currentNumberOfGlyphs;
996 if( NO_OPERATION != ( GET_GLYPH_METRICS & operations ) )
998 GlyphInfo* glyphsBuffer = glyphs.Begin();
999 mMetrics->GetGlyphMetrics( glyphsBuffer + mTextUpdateInfo.mStartGlyphIndex, numberOfGlyphs );
1001 // Update the width and advance of all new paragraph characters.
1002 for( Vector<GlyphIndex>::ConstIterator it = newParagraphGlyphs.Begin(), endIt = newParagraphGlyphs.End(); it != endIt; ++it )
1004 const GlyphIndex index = *it;
1005 GlyphInfo& glyph = *( glyphsBuffer + index );
1007 glyph.xBearing = 0.f;
1009 glyph.advance = 0.f;
1014 if( NO_OPERATION != ( COLOR & operations ) )
1016 // Set the color runs in glyphs.
1017 SetColorSegmentationInfo( mModel->mLogicalModel->mColorRuns,
1018 mModel->mVisualModel->mCharactersToGlyph,
1019 mModel->mVisualModel->mGlyphsPerCharacter,
1021 mTextUpdateInfo.mStartGlyphIndex,
1022 requestedNumberOfCharacters,
1023 mModel->mVisualModel->mColors,
1024 mModel->mVisualModel->mColorIndices );
1029 if( ( NULL != mEventData ) &&
1030 mEventData->mPreEditFlag &&
1031 ( 0u != mModel->mVisualModel->mCharactersToGlyph.Count() ) )
1033 // Add the underline for the pre-edit text.
1034 const GlyphIndex* const charactersToGlyphBuffer = mModel->mVisualModel->mCharactersToGlyph.Begin();
1035 const Length* const glyphsPerCharacterBuffer = mModel->mVisualModel->mGlyphsPerCharacter.Begin();
1037 const GlyphIndex glyphStart = *( charactersToGlyphBuffer + mEventData->mPreEditStartPosition );
1038 const CharacterIndex lastPreEditCharacter = mEventData->mPreEditStartPosition + ( ( mEventData->mPreEditLength > 0u ) ? mEventData->mPreEditLength - 1u : 0u );
1039 const Length numberOfGlyphsLastCharacter = *( glyphsPerCharacterBuffer + lastPreEditCharacter );
1040 const GlyphIndex glyphEnd = *( charactersToGlyphBuffer + lastPreEditCharacter ) + ( numberOfGlyphsLastCharacter > 1u ? numberOfGlyphsLastCharacter - 1u : 0u );
1042 GlyphRun underlineRun;
1043 underlineRun.glyphIndex = glyphStart;
1044 underlineRun.numberOfGlyphs = 1u + glyphEnd - glyphStart;
1046 // TODO: At the moment the underline runs are only for pre-edit.
1047 mModel->mVisualModel->mUnderlineRuns.PushBack( underlineRun );
1050 // The estimated number of lines. Used to avoid reallocations when layouting.
1051 mTextUpdateInfo.mEstimatedNumberOfLines = std::max( mModel->mVisualModel->mLines.Count(), mModel->mLogicalModel->mParagraphInfo.Count() );
1053 // Set the previous number of characters for the next time the text is updated.
1054 mTextUpdateInfo.mPreviousNumberOfCharacters = numberOfCharacters;
1059 void Controller::Impl::RetrieveDefaultInputStyle( InputStyle& inputStyle )
1061 // Sets the default text's color.
1062 inputStyle.textColor = mTextColor;
1063 inputStyle.isDefaultColor = true;
1065 inputStyle.familyName.clear();
1066 inputStyle.weight = TextAbstraction::FontWeight::NORMAL;
1067 inputStyle.width = TextAbstraction::FontWidth::NORMAL;
1068 inputStyle.slant = TextAbstraction::FontSlant::NORMAL;
1069 inputStyle.size = 0.f;
1071 inputStyle.lineSpacing = 0.f;
1073 inputStyle.underlineProperties.clear();
1074 inputStyle.shadowProperties.clear();
1075 inputStyle.embossProperties.clear();
1076 inputStyle.outlineProperties.clear();
1078 inputStyle.isFamilyDefined = false;
1079 inputStyle.isWeightDefined = false;
1080 inputStyle.isWidthDefined = false;
1081 inputStyle.isSlantDefined = false;
1082 inputStyle.isSizeDefined = false;
1084 inputStyle.isLineSpacingDefined = false;
1086 inputStyle.isUnderlineDefined = false;
1087 inputStyle.isShadowDefined = false;
1088 inputStyle.isEmbossDefined = false;
1089 inputStyle.isOutlineDefined = false;
1091 // Sets the default font's family name, weight, width, slant and size.
1094 if( mFontDefaults->familyDefined )
1096 inputStyle.familyName = mFontDefaults->mFontDescription.family;
1097 inputStyle.isFamilyDefined = true;
1100 if( mFontDefaults->weightDefined )
1102 inputStyle.weight = mFontDefaults->mFontDescription.weight;
1103 inputStyle.isWeightDefined = true;
1106 if( mFontDefaults->widthDefined )
1108 inputStyle.width = mFontDefaults->mFontDescription.width;
1109 inputStyle.isWidthDefined = true;
1112 if( mFontDefaults->slantDefined )
1114 inputStyle.slant = mFontDefaults->mFontDescription.slant;
1115 inputStyle.isSlantDefined = true;
1118 if( mFontDefaults->sizeDefined )
1120 inputStyle.size = mFontDefaults->mDefaultPointSize;
1121 inputStyle.isSizeDefined = true;
1126 float Controller::Impl::GetDefaultFontLineHeight()
1128 FontId defaultFontId = 0u;
1129 if( NULL == mFontDefaults )
1131 TextAbstraction::FontDescription fontDescription;
1132 defaultFontId = mFontClient.GetFontId( fontDescription );
1136 defaultFontId = mFontDefaults->GetFontId( mFontClient );
1139 Text::FontMetrics fontMetrics;
1140 mMetrics->GetFontMetrics( defaultFontId, fontMetrics );
1142 return( fontMetrics.ascender - fontMetrics.descender );
1145 void Controller::Impl::OnCursorKeyEvent( const Event& event )
1147 if( NULL == mEventData )
1149 // Nothing to do if there is no text input.
1153 int keyCode = event.p1.mInt;
1154 bool isShiftModifier = event.p2.mBool;
1156 CharacterIndex previousPrimaryCursorPosition = mEventData->mPrimaryCursorPosition;
1158 if( Dali::DALI_KEY_CURSOR_LEFT == keyCode )
1160 if( mEventData->mPrimaryCursorPosition > 0u )
1162 mEventData->mPrimaryCursorPosition = CalculateNewCursorIndex( mEventData->mPrimaryCursorPosition - 1u );
1165 else if( Dali::DALI_KEY_CURSOR_RIGHT == keyCode )
1167 if( mModel->mLogicalModel->mText.Count() > mEventData->mPrimaryCursorPosition )
1169 mEventData->mPrimaryCursorPosition = CalculateNewCursorIndex( mEventData->mPrimaryCursorPosition );
1172 else if( Dali::DALI_KEY_CURSOR_UP == keyCode && !isShiftModifier )
1174 // Ignore Shift-Up for text selection for now.
1176 // Get first the line index of the current cursor position index.
1177 CharacterIndex characterIndex = 0u;
1179 if( mEventData->mPrimaryCursorPosition > 0u )
1181 characterIndex = mEventData->mPrimaryCursorPosition - 1u;
1184 const LineIndex lineIndex = mModel->mVisualModel->GetLineOfCharacter( characterIndex );
1185 const LineIndex previousLineIndex = ( lineIndex > 0 ? lineIndex - 1u : lineIndex );
1187 // Retrieve the cursor position info.
1188 CursorInfo cursorInfo;
1189 GetCursorPosition( mEventData->mPrimaryCursorPosition,
1192 // Get the line above.
1193 const LineRun& line = *( mModel->mVisualModel->mLines.Begin() + previousLineIndex );
1195 // Get the next hit 'y' point.
1196 const float hitPointY = cursorInfo.lineOffset - 0.5f * ( line.ascender - line.descender );
1198 // Use the cursor hook position 'x' and the next hit 'y' position to calculate the new cursor index.
1199 bool matchedCharacter = false;
1200 mEventData->mPrimaryCursorPosition = Text::GetClosestCursorIndex( mModel->mVisualModel,
1201 mModel->mLogicalModel,
1203 mEventData->mCursorHookPositionX,
1205 CharacterHitTest::TAP,
1208 else if( Dali::DALI_KEY_CURSOR_DOWN == keyCode && !isShiftModifier )
1210 // Ignore Shift-Down for text selection for now.
1212 // Get first the line index of the current cursor position index.
1213 CharacterIndex characterIndex = 0u;
1215 if( mEventData->mPrimaryCursorPosition > 0u )
1217 characterIndex = mEventData->mPrimaryCursorPosition - 1u;
1220 const LineIndex lineIndex = mModel->mVisualModel->GetLineOfCharacter( characterIndex );
1222 if( lineIndex + 1u < mModel->mVisualModel->mLines.Count() )
1224 // Retrieve the cursor position info.
1225 CursorInfo cursorInfo;
1226 GetCursorPosition( mEventData->mPrimaryCursorPosition,
1229 // Get the line below.
1230 const LineRun& line = *( mModel->mVisualModel->mLines.Begin() + lineIndex + 1u );
1232 // Get the next hit 'y' point.
1233 const float hitPointY = cursorInfo.lineOffset + cursorInfo.lineHeight + 0.5f * ( line.ascender - line.descender );
1235 // Use the cursor hook position 'x' and the next hit 'y' position to calculate the new cursor index.
1236 bool matchedCharacter = false;
1237 mEventData->mPrimaryCursorPosition = Text::GetClosestCursorIndex( mModel->mVisualModel,
1238 mModel->mLogicalModel,
1240 mEventData->mCursorHookPositionX,
1242 CharacterHitTest::TAP,
1247 if ( !isShiftModifier && mEventData->mState != EventData::SELECTING )
1249 // Update selection position after moving the cursor
1250 mEventData->mLeftSelectionPosition = mEventData->mPrimaryCursorPosition;
1251 mEventData->mRightSelectionPosition = mEventData->mPrimaryCursorPosition;
1254 if ( isShiftModifier && IsShowingRealText() )
1256 // Handle text selection
1257 bool selecting = false;
1259 if ( Dali::DALI_KEY_CURSOR_LEFT == keyCode || Dali::DALI_KEY_CURSOR_RIGHT == keyCode )
1261 // Shift-Left/Right to select the text
1262 int cursorPositionDelta = mEventData->mPrimaryCursorPosition - previousPrimaryCursorPosition;
1263 if ( cursorPositionDelta > 0 || mEventData->mRightSelectionPosition > 0u ) // Check the boundary
1265 mEventData->mRightSelectionPosition += cursorPositionDelta;
1269 else if ( mEventData->mLeftSelectionPosition != mEventData->mRightSelectionPosition )
1271 // Show no grab handles and text highlight if Shift-Up/Down pressed but no selected text
1277 // Notify the cursor position to the imf manager.
1278 if( mEventData->mImfManager )
1280 mEventData->mImfManager.SetCursorPosition( mEventData->mPrimaryCursorPosition );
1281 mEventData->mImfManager.NotifyCursorPosition();
1284 ChangeState( EventData::SELECTING );
1286 mEventData->mUpdateLeftSelectionPosition = true;
1287 mEventData->mUpdateRightSelectionPosition = true;
1288 mEventData->mUpdateGrabHandlePosition = true;
1289 mEventData->mUpdateHighlightBox = true;
1291 // Hide the text selection popup if select the text using keyboard instead of moving grab handles
1292 if( mEventData->mGrabHandlePopupEnabled )
1294 mEventData->mDecorator->SetPopupActive( false );
1300 // Handle normal cursor move
1301 ChangeState( EventData::EDITING );
1302 mEventData->mUpdateCursorPosition = true;
1305 mEventData->mUpdateInputStyle = true;
1306 mEventData->mScrollAfterUpdatePosition = true;
1309 void Controller::Impl::OnTapEvent( const Event& event )
1311 if( NULL != mEventData )
1313 const unsigned int tapCount = event.p1.mUint;
1315 if( 1u == tapCount )
1317 if( IsShowingRealText() )
1319 // Convert from control's coords to text's coords.
1320 const float xPosition = event.p2.mFloat - mModel->mScrollPosition.x;
1321 const float yPosition = event.p3.mFloat - mModel->mScrollPosition.y;
1323 // Keep the tap 'x' position. Used to move the cursor.
1324 mEventData->mCursorHookPositionX = xPosition;
1326 // Whether to touch point hits on a glyph.
1327 bool matchedCharacter = false;
1328 mEventData->mPrimaryCursorPosition = Text::GetClosestCursorIndex( mModel->mVisualModel,
1329 mModel->mLogicalModel,
1333 CharacterHitTest::TAP,
1336 // When the cursor position is changing, delay cursor blinking
1337 mEventData->mDecorator->DelayCursorBlink();
1341 mEventData->mPrimaryCursorPosition = 0u;
1344 // Update selection position after tapping
1345 mEventData->mLeftSelectionPosition = mEventData->mPrimaryCursorPosition;
1346 mEventData->mRightSelectionPosition = mEventData->mPrimaryCursorPosition;
1348 mEventData->mUpdateCursorPosition = true;
1349 mEventData->mUpdateGrabHandlePosition = true;
1350 mEventData->mScrollAfterUpdatePosition = true;
1351 mEventData->mUpdateInputStyle = true;
1353 // Notify the cursor position to the imf manager.
1354 if( mEventData->mImfManager )
1356 mEventData->mImfManager.SetCursorPosition( mEventData->mPrimaryCursorPosition );
1357 mEventData->mImfManager.NotifyCursorPosition();
1360 else if( 2u == tapCount )
1362 if( mEventData->mSelectionEnabled )
1364 // Convert from control's coords to text's coords.
1365 const float xPosition = event.p2.mFloat - mModel->mScrollPosition.x;
1366 const float yPosition = event.p3.mFloat - mModel->mScrollPosition.y;
1368 // Calculates the logical position from the x,y coords.
1369 RepositionSelectionHandles( xPosition,
1371 mEventData->mDoubleTapAction );
1377 void Controller::Impl::OnPanEvent( const Event& event )
1379 if( NULL == mEventData )
1381 // Nothing to do if there is no text input.
1385 const bool isHorizontalScrollEnabled = mEventData->mDecorator->IsHorizontalScrollEnabled();
1386 const bool isVerticalScrollEnabled = mEventData->mDecorator->IsVerticalScrollEnabled();
1388 if( !isHorizontalScrollEnabled && !isVerticalScrollEnabled )
1390 // Nothing to do if scrolling is not enabled.
1394 const int state = event.p1.mInt;
1398 case Gesture::Started:
1400 // Will remove the cursor, handles or text's popup, ...
1401 ChangeState( EventData::TEXT_PANNING );
1404 case Gesture::Continuing:
1406 const Vector2& layoutSize = mModel->mVisualModel->GetLayoutSize();
1407 const Vector2 currentScroll = mModel->mScrollPosition;
1409 if( isHorizontalScrollEnabled )
1411 const float displacementX = event.p2.mFloat;
1412 mModel->mScrollPosition.x += displacementX;
1414 ClampHorizontalScroll( layoutSize );
1417 if( isVerticalScrollEnabled )
1419 const float displacementY = event.p3.mFloat;
1420 mModel->mScrollPosition.y += displacementY;
1422 ClampVerticalScroll( layoutSize );
1425 mEventData->mDecorator->UpdatePositions( mModel->mScrollPosition - currentScroll );
1428 case Gesture::Finished:
1429 case Gesture::Cancelled: // FALLTHROUGH
1431 // Will go back to the previous state to show the cursor, handles, the text's popup, ...
1432 ChangeState( mEventData->mPreviousState );
1440 void Controller::Impl::OnLongPressEvent( const Event& event )
1442 DALI_LOG_INFO( gLogFilter, Debug::General, "Controller::OnLongPressEvent\n" );
1444 if( !IsShowingRealText() && ( EventData::EDITING == mEventData->mState ) )
1446 ChangeState( EventData::EDITING_WITH_POPUP );
1447 mEventData->mDecoratorUpdated = true;
1448 mEventData->mUpdateInputStyle = true;
1452 if( mEventData->mSelectionEnabled )
1454 // Convert from control's coords to text's coords.
1455 const float xPosition = event.p2.mFloat - mModel->mScrollPosition.x;
1456 const float yPosition = event.p3.mFloat - mModel->mScrollPosition.y;
1458 // Calculates the logical position from the x,y coords.
1459 RepositionSelectionHandles( xPosition,
1461 mEventData->mLongPressAction );
1466 void Controller::Impl::OnHandleEvent( const Event& event )
1468 if( NULL == mEventData )
1470 // Nothing to do if there is no text input.
1474 const unsigned int state = event.p1.mUint;
1475 const bool handleStopScrolling = ( HANDLE_STOP_SCROLLING == state );
1476 const bool isSmoothHandlePanEnabled = mEventData->mDecorator->IsSmoothHandlePanEnabled();
1478 if( HANDLE_PRESSED == state )
1480 // Convert from decorator's coords to text's coords.
1481 const float xPosition = event.p2.mFloat - mModel->mScrollPosition.x;
1482 const float yPosition = event.p3.mFloat - mModel->mScrollPosition.y;
1484 // Need to calculate the handle's new position.
1485 bool matchedCharacter = false;
1486 const CharacterIndex handleNewPosition = Text::GetClosestCursorIndex( mModel->mVisualModel,
1487 mModel->mLogicalModel,
1491 CharacterHitTest::SCROLL,
1494 if( Event::GRAB_HANDLE_EVENT == event.type )
1496 ChangeState ( EventData::GRAB_HANDLE_PANNING );
1498 if( handleNewPosition != mEventData->mPrimaryCursorPosition )
1500 // Updates the cursor position if the handle's new position is different than the current one.
1501 mEventData->mUpdateCursorPosition = true;
1502 // Does not update the grab handle position if the smooth panning is enabled. (The decorator does it smooth).
1503 mEventData->mUpdateGrabHandlePosition = !isSmoothHandlePanEnabled;
1504 mEventData->mPrimaryCursorPosition = handleNewPosition;
1507 // 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.
1508 mEventData->mDecoratorUpdated = isSmoothHandlePanEnabled;
1510 else if( Event::LEFT_SELECTION_HANDLE_EVENT == event.type )
1512 ChangeState ( EventData::SELECTION_HANDLE_PANNING );
1514 if( ( handleNewPosition != mEventData->mLeftSelectionPosition ) &&
1515 ( handleNewPosition != mEventData->mRightSelectionPosition ) )
1517 // Updates the highlight box if the handle's new position is different than the current one.
1518 mEventData->mUpdateHighlightBox = true;
1519 // Does not update the selection handle position if the smooth panning is enabled. (The decorator does it smooth).
1520 mEventData->mUpdateLeftSelectionPosition = !isSmoothHandlePanEnabled;
1521 mEventData->mLeftSelectionPosition = handleNewPosition;
1524 // 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.
1525 mEventData->mDecoratorUpdated = isSmoothHandlePanEnabled;
1527 // Will define the order to scroll the text to match the handle position.
1528 mEventData->mIsLeftHandleSelected = true;
1529 mEventData->mIsRightHandleSelected = false;
1531 else if( Event::RIGHT_SELECTION_HANDLE_EVENT == event.type )
1533 ChangeState ( EventData::SELECTION_HANDLE_PANNING );
1535 if( ( handleNewPosition != mEventData->mRightSelectionPosition ) &&
1536 ( handleNewPosition != mEventData->mLeftSelectionPosition ) )
1538 // Updates the highlight box if the handle's new position is different than the current one.
1539 mEventData->mUpdateHighlightBox = true;
1540 // Does not update the selection handle position if the smooth panning is enabled. (The decorator does it smooth).
1541 mEventData->mUpdateRightSelectionPosition = !isSmoothHandlePanEnabled;
1542 mEventData->mRightSelectionPosition = handleNewPosition;
1545 // 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.
1546 mEventData->mDecoratorUpdated = isSmoothHandlePanEnabled;
1548 // Will define the order to scroll the text to match the handle position.
1549 mEventData->mIsLeftHandleSelected = false;
1550 mEventData->mIsRightHandleSelected = true;
1552 } // end ( HANDLE_PRESSED == state )
1553 else if( ( HANDLE_RELEASED == state ) ||
1554 handleStopScrolling )
1556 CharacterIndex handlePosition = 0u;
1557 if( handleStopScrolling || isSmoothHandlePanEnabled )
1559 // Convert from decorator's coords to text's coords.
1560 const float xPosition = event.p2.mFloat - mModel->mScrollPosition.x;
1561 const float yPosition = event.p3.mFloat - mModel->mScrollPosition.y;
1563 bool matchedCharacter = false;
1564 handlePosition = Text::GetClosestCursorIndex( mModel->mVisualModel,
1565 mModel->mLogicalModel,
1569 CharacterHitTest::SCROLL,
1573 if( Event::GRAB_HANDLE_EVENT == event.type )
1575 mEventData->mUpdateCursorPosition = true;
1576 mEventData->mUpdateGrabHandlePosition = true;
1577 mEventData->mUpdateInputStyle = true;
1579 if( !IsClipboardEmpty() )
1581 ChangeState( EventData::EDITING_WITH_PASTE_POPUP ); // Moving grabhandle will show Paste Popup
1584 if( handleStopScrolling || isSmoothHandlePanEnabled )
1586 mEventData->mScrollAfterUpdatePosition = true;
1587 mEventData->mPrimaryCursorPosition = handlePosition;
1590 else if( Event::LEFT_SELECTION_HANDLE_EVENT == event.type )
1592 ChangeState( EventData::SELECTING );
1594 mEventData->mUpdateHighlightBox = true;
1595 mEventData->mUpdateLeftSelectionPosition = true;
1596 mEventData->mUpdateRightSelectionPosition = true;
1598 if( handleStopScrolling || isSmoothHandlePanEnabled )
1600 mEventData->mScrollAfterUpdatePosition = true;
1602 if( ( handlePosition != mEventData->mRightSelectionPosition ) &&
1603 ( handlePosition != mEventData->mLeftSelectionPosition ) )
1605 mEventData->mLeftSelectionPosition = handlePosition;
1609 else if( Event::RIGHT_SELECTION_HANDLE_EVENT == event.type )
1611 ChangeState( EventData::SELECTING );
1613 mEventData->mUpdateHighlightBox = true;
1614 mEventData->mUpdateRightSelectionPosition = true;
1615 mEventData->mUpdateLeftSelectionPosition = true;
1617 if( handleStopScrolling || isSmoothHandlePanEnabled )
1619 mEventData->mScrollAfterUpdatePosition = true;
1620 if( ( handlePosition != mEventData->mRightSelectionPosition ) &&
1621 ( handlePosition != mEventData->mLeftSelectionPosition ) )
1623 mEventData->mRightSelectionPosition = handlePosition;
1628 mEventData->mDecoratorUpdated = true;
1629 } // end ( ( HANDLE_RELEASED == state ) || ( HANDLE_STOP_SCROLLING == state ) )
1630 else if( HANDLE_SCROLLING == state )
1632 const float xSpeed = event.p2.mFloat;
1633 const float ySpeed = event.p3.mFloat;
1634 const Vector2& layoutSize = mModel->mVisualModel->GetLayoutSize();
1635 const Vector2 currentScrollPosition = mModel->mScrollPosition;
1637 mModel->mScrollPosition.x += xSpeed;
1638 mModel->mScrollPosition.y += ySpeed;
1640 ClampHorizontalScroll( layoutSize );
1641 ClampVerticalScroll( layoutSize );
1643 bool endOfScroll = false;
1644 if( Vector2::ZERO == ( currentScrollPosition - mModel->mScrollPosition ) )
1646 // Notify the decorator there is no more text to scroll.
1647 // The decorator won't send more scroll events.
1648 mEventData->mDecorator->NotifyEndOfScroll();
1649 // Still need to set the position of the handle.
1653 // Set the position of the handle.
1654 const bool scrollRightDirection = xSpeed > 0.f;
1655 const bool scrollBottomDirection = ySpeed > 0.f;
1656 const bool leftSelectionHandleEvent = Event::LEFT_SELECTION_HANDLE_EVENT == event.type;
1657 const bool rightSelectionHandleEvent = Event::RIGHT_SELECTION_HANDLE_EVENT == event.type;
1659 if( Event::GRAB_HANDLE_EVENT == event.type )
1661 ChangeState( EventData::GRAB_HANDLE_PANNING );
1663 // Get the grab handle position in decorator coords.
1664 Vector2 position = mEventData->mDecorator->GetPosition( GRAB_HANDLE );
1666 if( mEventData->mDecorator->IsHorizontalScrollEnabled() )
1668 // Position the grag handle close to either the left or right edge.
1669 position.x = scrollRightDirection ? 0.f : mModel->mVisualModel->mControlSize.width;
1672 if( mEventData->mDecorator->IsVerticalScrollEnabled() )
1674 position.x = mEventData->mCursorHookPositionX;
1676 // Position the grag handle close to either the top or bottom edge.
1677 position.y = scrollBottomDirection ? 0.f : mModel->mVisualModel->mControlSize.height;
1680 // Get the new handle position.
1681 // The grab handle's position is in decorator's coords. Need to transforms to text's coords.
1682 bool matchedCharacter = false;
1683 const CharacterIndex handlePosition = Text::GetClosestCursorIndex( mModel->mVisualModel,
1684 mModel->mLogicalModel,
1686 position.x - mModel->mScrollPosition.x,
1687 position.y - mModel->mScrollPosition.y,
1688 CharacterHitTest::SCROLL,
1691 if( mEventData->mPrimaryCursorPosition != handlePosition )
1693 mEventData->mUpdateCursorPosition = true;
1694 mEventData->mUpdateGrabHandlePosition = !isSmoothHandlePanEnabled;
1695 mEventData->mScrollAfterUpdatePosition = true;
1696 mEventData->mPrimaryCursorPosition = handlePosition;
1698 mEventData->mUpdateInputStyle = mEventData->mUpdateCursorPosition;
1700 // Updates the decorator if the soft handle panning is enabled.
1701 mEventData->mDecoratorUpdated = isSmoothHandlePanEnabled;
1703 else if( leftSelectionHandleEvent || rightSelectionHandleEvent )
1705 ChangeState( EventData::SELECTION_HANDLE_PANNING );
1707 // Get the selection handle position in decorator coords.
1708 Vector2 position = mEventData->mDecorator->GetPosition( leftSelectionHandleEvent ? Text::LEFT_SELECTION_HANDLE : Text::RIGHT_SELECTION_HANDLE );
1710 if( mEventData->mDecorator->IsHorizontalScrollEnabled() )
1712 // Position the selection handle close to either the left or right edge.
1713 position.x = scrollRightDirection ? 0.f : mModel->mVisualModel->mControlSize.width;
1716 if( mEventData->mDecorator->IsVerticalScrollEnabled() )
1718 position.x = mEventData->mCursorHookPositionX;
1720 // Position the grag handle close to either the top or bottom edge.
1721 position.y = scrollBottomDirection ? 0.f : mModel->mVisualModel->mControlSize.height;
1724 // Get the new handle position.
1725 // The selection handle's position is in decorator's coords. Need to transform to text's coords.
1726 bool matchedCharacter = false;
1727 const CharacterIndex handlePosition = Text::GetClosestCursorIndex( mModel->mVisualModel,
1728 mModel->mLogicalModel,
1730 position.x - mModel->mScrollPosition.x,
1731 position.y - mModel->mScrollPosition.y,
1732 CharacterHitTest::SCROLL,
1735 if( leftSelectionHandleEvent )
1737 const bool differentHandles = ( mEventData->mLeftSelectionPosition != handlePosition ) && ( mEventData->mRightSelectionPosition != handlePosition );
1739 if( differentHandles || endOfScroll )
1741 mEventData->mUpdateHighlightBox = true;
1742 mEventData->mUpdateLeftSelectionPosition = !isSmoothHandlePanEnabled;
1743 mEventData->mUpdateRightSelectionPosition = isSmoothHandlePanEnabled;
1744 mEventData->mLeftSelectionPosition = handlePosition;
1749 const bool differentHandles = ( mEventData->mRightSelectionPosition != handlePosition ) && ( mEventData->mLeftSelectionPosition != handlePosition );
1750 if( differentHandles || endOfScroll )
1752 mEventData->mUpdateHighlightBox = true;
1753 mEventData->mUpdateRightSelectionPosition = !isSmoothHandlePanEnabled;
1754 mEventData->mUpdateLeftSelectionPosition = isSmoothHandlePanEnabled;
1755 mEventData->mRightSelectionPosition = handlePosition;
1759 if( mEventData->mUpdateLeftSelectionPosition || mEventData->mUpdateRightSelectionPosition )
1761 RepositionSelectionHandles();
1763 mEventData->mScrollAfterUpdatePosition = !isSmoothHandlePanEnabled;
1766 mEventData->mDecoratorUpdated = true;
1767 } // end ( HANDLE_SCROLLING == state )
1770 void Controller::Impl::OnSelectEvent( const Event& event )
1772 if( NULL == mEventData )
1774 // Nothing to do if there is no text.
1778 if( mEventData->mSelectionEnabled )
1780 // Convert from control's coords to text's coords.
1781 const float xPosition = event.p2.mFloat - mModel->mScrollPosition.x;
1782 const float yPosition = event.p3.mFloat - mModel->mScrollPosition.y;
1784 // Calculates the logical position from the x,y coords.
1785 RepositionSelectionHandles( xPosition,
1787 Controller::NoTextTap::HIGHLIGHT );
1791 void Controller::Impl::OnSelectAllEvent()
1793 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "OnSelectAllEvent mEventData->mSelectionEnabled%s \n", mEventData->mSelectionEnabled?"true":"false");
1795 if( NULL == mEventData )
1797 // Nothing to do if there is no text.
1801 if( mEventData->mSelectionEnabled )
1803 ChangeState( EventData::SELECTING );
1805 mEventData->mLeftSelectionPosition = 0u;
1806 mEventData->mRightSelectionPosition = mModel->mLogicalModel->mText.Count();
1808 mEventData->mScrollAfterUpdatePosition = true;
1809 mEventData->mUpdateLeftSelectionPosition = true;
1810 mEventData->mUpdateRightSelectionPosition = true;
1811 mEventData->mUpdateHighlightBox = true;
1815 void Controller::Impl::RetrieveSelection( std::string& selectedText, bool deleteAfterRetrieval )
1817 if( mEventData->mLeftSelectionPosition == mEventData->mRightSelectionPosition )
1819 // Nothing to select if handles are in the same place.
1820 selectedText.clear();
1824 const bool handlesCrossed = mEventData->mLeftSelectionPosition > mEventData->mRightSelectionPosition;
1826 //Get start and end position of selection
1827 const CharacterIndex startOfSelectedText = handlesCrossed ? mEventData->mRightSelectionPosition : mEventData->mLeftSelectionPosition;
1828 const Length lengthOfSelectedText = ( handlesCrossed ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition ) - startOfSelectedText;
1830 Vector<Character>& utf32Characters = mModel->mLogicalModel->mText;
1831 const Length numberOfCharacters = utf32Characters.Count();
1833 // Validate the start and end selection points
1834 if( ( startOfSelectedText + lengthOfSelectedText ) <= numberOfCharacters )
1836 //Get text as a UTF8 string
1837 Utf32ToUtf8( &utf32Characters[startOfSelectedText], lengthOfSelectedText, selectedText );
1839 if( deleteAfterRetrieval ) // Only delete text if copied successfully
1841 // Keep a copy of the current input style.
1842 InputStyle currentInputStyle;
1843 currentInputStyle.Copy( mEventData->mInputStyle );
1845 // Set as input style the style of the first deleted character.
1846 mModel->mLogicalModel->RetrieveStyle( startOfSelectedText, mEventData->mInputStyle );
1848 // Compare if the input style has changed.
1849 const bool hasInputStyleChanged = !currentInputStyle.Equal( mEventData->mInputStyle );
1851 if( hasInputStyleChanged )
1853 const InputStyle::Mask styleChangedMask = currentInputStyle.GetInputStyleChangeMask( mEventData->mInputStyle );
1854 // Queue the input style changed signal.
1855 mEventData->mInputStyleChangedQueue.PushBack( styleChangedMask );
1858 mModel->mLogicalModel->UpdateTextStyleRuns( startOfSelectedText, -static_cast<int>( lengthOfSelectedText ) );
1860 // Mark the paragraphs to be updated.
1861 if( Layout::Engine::SINGLE_LINE_BOX == mLayoutEngine.GetLayout() )
1863 mTextUpdateInfo.mCharacterIndex = 0;
1864 mTextUpdateInfo.mNumberOfCharactersToRemove = mTextUpdateInfo.mPreviousNumberOfCharacters;
1865 mTextUpdateInfo.mNumberOfCharactersToAdd = mTextUpdateInfo.mPreviousNumberOfCharacters - lengthOfSelectedText;
1866 mTextUpdateInfo.mClearAll = true;
1870 mTextUpdateInfo.mCharacterIndex = startOfSelectedText;
1871 mTextUpdateInfo.mNumberOfCharactersToRemove = lengthOfSelectedText;
1874 // Delete text between handles
1875 Vector<Character>::Iterator first = utf32Characters.Begin() + startOfSelectedText;
1876 Vector<Character>::Iterator last = first + lengthOfSelectedText;
1877 utf32Characters.Erase( first, last );
1879 // Will show the cursor at the first character of the selection.
1880 mEventData->mPrimaryCursorPosition = handlesCrossed ? mEventData->mRightSelectionPosition : mEventData->mLeftSelectionPosition;
1884 // Will show the cursor at the last character of the selection.
1885 mEventData->mPrimaryCursorPosition = handlesCrossed ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition;
1888 mEventData->mDecoratorUpdated = true;
1892 void Controller::Impl::ShowClipboard()
1896 mClipboard.ShowClipboard();
1900 void Controller::Impl::HideClipboard()
1902 if( mClipboard && mClipboardHideEnabled )
1904 mClipboard.HideClipboard();
1908 void Controller::Impl::SetClipboardHideEnable(bool enable)
1910 mClipboardHideEnabled = enable;
1913 bool Controller::Impl::CopyStringToClipboard( std::string& source )
1915 //Send string to clipboard
1916 return ( mClipboard && mClipboard.SetItem( source ) );
1919 void Controller::Impl::SendSelectionToClipboard( bool deleteAfterSending )
1921 std::string selectedText;
1922 RetrieveSelection( selectedText, deleteAfterSending );
1923 CopyStringToClipboard( selectedText );
1924 ChangeState( EventData::EDITING );
1927 void Controller::Impl::RequestGetTextFromClipboard()
1931 mClipboard.RequestItem();
1935 void Controller::Impl::RepositionSelectionHandles()
1937 CharacterIndex selectionStart = mEventData->mLeftSelectionPosition;
1938 CharacterIndex selectionEnd = mEventData->mRightSelectionPosition;
1940 if( selectionStart == selectionEnd )
1942 // Nothing to select if handles are in the same place.
1946 mEventData->mDecorator->ClearHighlights();
1948 const GlyphIndex* const charactersToGlyphBuffer = mModel->mVisualModel->mCharactersToGlyph.Begin();
1949 const Length* const glyphsPerCharacterBuffer = mModel->mVisualModel->mGlyphsPerCharacter.Begin();
1950 const GlyphInfo* const glyphsBuffer = mModel->mVisualModel->mGlyphs.Begin();
1951 const Vector2* const positionsBuffer = mModel->mVisualModel->mGlyphPositions.Begin();
1952 const Length* const charactersPerGlyphBuffer = mModel->mVisualModel->mCharactersPerGlyph.Begin();
1953 const CharacterIndex* const glyphToCharacterBuffer = mModel->mVisualModel->mGlyphsToCharacters.Begin();
1954 const CharacterDirection* const modelCharacterDirectionsBuffer = ( 0u != mModel->mLogicalModel->mCharacterDirections.Count() ) ? mModel->mLogicalModel->mCharacterDirections.Begin() : NULL;
1956 const bool isLastCharacter = selectionEnd >= mModel->mLogicalModel->mText.Count();
1957 const CharacterDirection startDirection = ( ( NULL == modelCharacterDirectionsBuffer ) ? false : *( modelCharacterDirectionsBuffer + selectionStart ) );
1958 const CharacterDirection endDirection = ( ( NULL == modelCharacterDirectionsBuffer ) ? false : *( modelCharacterDirectionsBuffer + ( selectionEnd - ( isLastCharacter ? 1u : 0u ) ) ) );
1960 // Swap the indices if the start is greater than the end.
1961 const bool indicesSwapped = selectionStart > selectionEnd;
1963 // Tell the decorator to flip the selection handles if needed.
1964 mEventData->mDecorator->SetSelectionHandleFlipState( indicesSwapped, startDirection, endDirection );
1966 if( indicesSwapped )
1968 std::swap( selectionStart, selectionEnd );
1971 // Get the indices to the first and last selected glyphs.
1972 const CharacterIndex selectionEndMinusOne = selectionEnd - 1u;
1973 const GlyphIndex glyphStart = *( charactersToGlyphBuffer + selectionStart );
1974 const Length numberOfGlyphs = *( glyphsPerCharacterBuffer + selectionEndMinusOne );
1975 const GlyphIndex glyphEnd = *( charactersToGlyphBuffer + selectionEndMinusOne ) + ( ( numberOfGlyphs > 0 ) ? numberOfGlyphs - 1u : 0u );
1977 // Get the lines where the glyphs are laid-out.
1978 const LineRun* lineRun = mModel->mVisualModel->mLines.Begin();
1980 LineIndex lineIndex = 0u;
1981 Length numberOfLines = 0u;
1982 mModel->mVisualModel->GetNumberOfLines( glyphStart,
1983 1u + glyphEnd - glyphStart,
1986 const LineIndex firstLineIndex = lineIndex;
1988 // Create the structure to store some selection box info.
1989 Vector<SelectionBoxInfo> selectionBoxLinesInfo;
1990 selectionBoxLinesInfo.Resize( numberOfLines );
1992 SelectionBoxInfo* selectionBoxInfo = selectionBoxLinesInfo.Begin();
1993 selectionBoxInfo->minX = MAX_FLOAT;
1994 selectionBoxInfo->maxX = MIN_FLOAT;
1996 // Keep the min and max 'x' position to calculate the size and position of the highlighed text.
1997 float minHighlightX = std::numeric_limits<float>::max();
1998 float maxHighlightX = std::numeric_limits<float>::min();
2000 Vector2 highLightPosition; // The highlight position in decorator's coords.
2002 // Retrieve the first line and get the line's vertical offset, the line's height and the index to the last glyph.
2004 // The line's vertical offset of all the lines before the line where the first glyph is laid-out.
2005 selectionBoxInfo->lineOffset = CalculateLineOffset( mModel->mVisualModel->mLines,
2008 // Transform to decorator's (control) coords.
2009 selectionBoxInfo->lineOffset += mModel->mScrollPosition.y;
2011 lineRun += firstLineIndex;
2013 // The line height is the addition of the line ascender and the line descender.
2014 // However, the line descender has a negative value, hence the subtraction.
2015 selectionBoxInfo->lineHeight = lineRun->ascender - lineRun->descender;
2017 GlyphIndex lastGlyphOfLine = lineRun->glyphRun.glyphIndex + lineRun->glyphRun.numberOfGlyphs - 1u;
2019 // Check if the first glyph is a ligature that must be broken like Latin ff, fi, or Arabic ï»», etc which needs special code.
2020 const Length numberOfCharactersStart = *( charactersPerGlyphBuffer + glyphStart );
2021 bool splitStartGlyph = ( numberOfCharactersStart > 1u ) && HasLigatureMustBreak( mModel->mLogicalModel->GetScript( selectionStart ) );
2023 // Check if the last glyph is a ligature that must be broken like Latin ff, fi, or Arabic ï»», etc which needs special code.
2024 const Length numberOfCharactersEnd = *( charactersPerGlyphBuffer + glyphEnd );
2025 bool splitEndGlyph = ( glyphStart != glyphEnd ) && ( numberOfCharactersEnd > 1u ) && HasLigatureMustBreak( mModel->mLogicalModel->GetScript( selectionEndMinusOne ) );
2027 // The number of quads of the selection box.
2028 const unsigned int numberOfQuads = 1u + ( glyphEnd - glyphStart ) + ( ( numberOfLines > 1u ) ? 2u * numberOfLines : 0u );
2029 mEventData->mDecorator->ResizeHighlightQuads( numberOfQuads );
2031 // Count the actual number of quads.
2032 unsigned int actualNumberOfQuads = 0u;
2035 // Traverse the glyphs.
2036 for( GlyphIndex index = glyphStart; index <= glyphEnd; ++index )
2038 const GlyphInfo& glyph = *( glyphsBuffer + index );
2039 const Vector2& position = *( positionsBuffer + index );
2041 if( splitStartGlyph )
2043 // 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.
2045 const float glyphAdvance = glyph.advance / static_cast<float>( numberOfCharactersStart );
2046 const CharacterIndex interGlyphIndex = selectionStart - *( glyphToCharacterBuffer + glyphStart );
2047 // Get the direction of the character.
2048 CharacterDirection isCurrentRightToLeft = false;
2049 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
2051 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + selectionStart );
2054 // The end point could be in the middle of the ligature.
2055 // Calculate the number of characters selected.
2056 const Length numberOfCharacters = ( glyphStart == glyphEnd ) ? ( selectionEnd - selectionStart ) : ( numberOfCharactersStart - interGlyphIndex );
2058 quad.x = lineRun->alignmentOffset + position.x - glyph.xBearing + mModel->mScrollPosition.x + glyphAdvance * static_cast<float>( isCurrentRightToLeft ? ( numberOfCharactersStart - interGlyphIndex - numberOfCharacters ) : interGlyphIndex );
2059 quad.y = selectionBoxInfo->lineOffset;
2060 quad.z = quad.x + static_cast<float>( numberOfCharacters ) * glyphAdvance;
2061 quad.w = selectionBoxInfo->lineOffset + selectionBoxInfo->lineHeight;
2063 // Store the min and max 'x' for each line.
2064 selectionBoxInfo->minX = std::min( selectionBoxInfo->minX, quad.x );
2065 selectionBoxInfo->maxX = std::max( selectionBoxInfo->maxX, quad.z );
2067 mEventData->mDecorator->AddHighlight( actualNumberOfQuads, quad );
2068 ++actualNumberOfQuads;
2070 splitStartGlyph = false;
2074 if( splitEndGlyph && ( index == glyphEnd ) )
2076 // 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.
2078 const float glyphAdvance = glyph.advance / static_cast<float>( numberOfCharactersEnd );
2079 const CharacterIndex interGlyphIndex = selectionEnd - *( glyphToCharacterBuffer + glyphEnd );
2080 // Get the direction of the character.
2081 CharacterDirection isCurrentRightToLeft = false;
2082 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
2084 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + selectionEnd );
2087 const Length numberOfCharacters = numberOfCharactersEnd - interGlyphIndex;
2089 quad.x = lineRun->alignmentOffset + position.x - glyph.xBearing + mModel->mScrollPosition.x + ( isCurrentRightToLeft ? ( glyphAdvance * static_cast<float>( numberOfCharacters ) ) : 0.f );
2090 quad.y = selectionBoxInfo->lineOffset;
2091 quad.z = quad.x + static_cast<float>( interGlyphIndex ) * glyphAdvance;
2092 quad.w = quad.y + selectionBoxInfo->lineHeight;
2094 // Store the min and max 'x' for each line.
2095 selectionBoxInfo->minX = std::min( selectionBoxInfo->minX, quad.x );
2096 selectionBoxInfo->maxX = std::max( selectionBoxInfo->maxX, quad.z );
2098 mEventData->mDecorator->AddHighlight( actualNumberOfQuads,
2100 ++actualNumberOfQuads;
2102 splitEndGlyph = false;
2106 quad.x = lineRun->alignmentOffset + position.x - glyph.xBearing + mModel->mScrollPosition.x;
2107 quad.y = selectionBoxInfo->lineOffset;
2108 quad.z = quad.x + glyph.advance;
2109 quad.w = quad.y + selectionBoxInfo->lineHeight;
2111 // Store the min and max 'x' for each line.
2112 selectionBoxInfo->minX = std::min( selectionBoxInfo->minX, quad.x );
2113 selectionBoxInfo->maxX = std::max( selectionBoxInfo->maxX, quad.z );
2115 mEventData->mDecorator->AddHighlight( actualNumberOfQuads,
2117 ++actualNumberOfQuads;
2119 // Whether to retrieve the next line.
2120 if( index == lastGlyphOfLine )
2123 if( lineIndex < firstLineIndex + numberOfLines )
2125 // Retrieve the next line.
2128 // Get the last glyph of the new line.
2129 lastGlyphOfLine = lineRun->glyphRun.glyphIndex + lineRun->glyphRun.numberOfGlyphs - 1u;
2131 // Keep the offset and height of the current selection box.
2132 const float currentLineOffset = selectionBoxInfo->lineOffset;
2133 const float currentLineHeight = selectionBoxInfo->lineHeight;
2135 // Get the selection box info for the next line.
2138 selectionBoxInfo->minX = MAX_FLOAT;
2139 selectionBoxInfo->maxX = MIN_FLOAT;
2141 // Update the line's vertical offset.
2142 selectionBoxInfo->lineOffset = currentLineOffset + currentLineHeight;
2144 // The line height is the addition of the line ascender and the line descender.
2145 // However, the line descender has a negative value, hence the subtraction.
2146 selectionBoxInfo->lineHeight = lineRun->ascender - lineRun->descender;
2151 // Traverses all the lines and updates the min and max 'x' positions and the total height.
2152 // The final width is calculated after 'boxifying' the selection.
2153 for( Vector<SelectionBoxInfo>::ConstIterator it = selectionBoxLinesInfo.Begin(),
2154 endIt = selectionBoxLinesInfo.End();
2158 const SelectionBoxInfo& info = *it;
2160 // Update the size of the highlighted text.
2161 highLightSize.height += info.lineHeight;
2162 minHighlightX = std::min( minHighlightX, info.minX );
2163 maxHighlightX = std::max( maxHighlightX, info.maxX );
2166 // Add extra geometry to 'boxify' the selection.
2168 if( 1u < numberOfLines )
2170 // Boxify the first line.
2171 lineRun = mModel->mVisualModel->mLines.Begin() + firstLineIndex;
2172 const SelectionBoxInfo& firstSelectionBoxLineInfo = *( selectionBoxLinesInfo.Begin() );
2174 bool boxifyBegin = ( LTR != lineRun->direction ) && ( LTR != startDirection );
2175 bool boxifyEnd = ( LTR == lineRun->direction ) && ( LTR == startDirection );
2180 quad.y = firstSelectionBoxLineInfo.lineOffset;
2181 quad.z = firstSelectionBoxLineInfo.minX;
2182 quad.w = firstSelectionBoxLineInfo.lineOffset + firstSelectionBoxLineInfo.lineHeight;
2184 // Boxify at the beginning of the line.
2185 mEventData->mDecorator->AddHighlight( actualNumberOfQuads,
2187 ++actualNumberOfQuads;
2189 // Update the size of the highlighted text.
2190 minHighlightX = 0.f;
2195 quad.x = firstSelectionBoxLineInfo.maxX;
2196 quad.y = firstSelectionBoxLineInfo.lineOffset;
2197 quad.z = mModel->mVisualModel->mControlSize.width;
2198 quad.w = firstSelectionBoxLineInfo.lineOffset + firstSelectionBoxLineInfo.lineHeight;
2200 // Boxify at the end of the line.
2201 mEventData->mDecorator->AddHighlight( actualNumberOfQuads,
2203 ++actualNumberOfQuads;
2205 // Update the size of the highlighted text.
2206 maxHighlightX = mModel->mVisualModel->mControlSize.width;
2209 // Boxify the central lines.
2210 if( 2u < numberOfLines )
2212 for( Vector<SelectionBoxInfo>::ConstIterator it = selectionBoxLinesInfo.Begin() + 1u,
2213 endIt = selectionBoxLinesInfo.End() - 1u;
2217 const SelectionBoxInfo& info = *it;
2220 quad.y = info.lineOffset;
2222 quad.w = info.lineOffset + info.lineHeight;
2224 mEventData->mDecorator->AddHighlight( actualNumberOfQuads,
2226 ++actualNumberOfQuads;
2229 quad.y = info.lineOffset;
2230 quad.z = mModel->mVisualModel->mControlSize.width;
2231 quad.w = info.lineOffset + info.lineHeight;
2233 mEventData->mDecorator->AddHighlight( actualNumberOfQuads,
2235 ++actualNumberOfQuads;
2238 // Update the size of the highlighted text.
2239 minHighlightX = 0.f;
2240 maxHighlightX = mModel->mVisualModel->mControlSize.width;
2243 // Boxify the last line.
2244 lineRun = mModel->mVisualModel->mLines.Begin() + firstLineIndex + numberOfLines - 1u;
2245 const SelectionBoxInfo& lastSelectionBoxLineInfo = *( selectionBoxLinesInfo.End() - 1u );
2247 boxifyBegin = ( LTR == lineRun->direction ) && ( LTR == endDirection );
2248 boxifyEnd = ( LTR != lineRun->direction ) && ( LTR != endDirection );
2253 quad.y = lastSelectionBoxLineInfo.lineOffset;
2254 quad.z = lastSelectionBoxLineInfo.minX;
2255 quad.w = lastSelectionBoxLineInfo.lineOffset + lastSelectionBoxLineInfo.lineHeight;
2257 // Boxify at the beginning of the line.
2258 mEventData->mDecorator->AddHighlight( actualNumberOfQuads,
2260 ++actualNumberOfQuads;
2262 // Update the size of the highlighted text.
2263 minHighlightX = 0.f;
2268 quad.x = lastSelectionBoxLineInfo.maxX;
2269 quad.y = lastSelectionBoxLineInfo.lineOffset;
2270 quad.z = mModel->mVisualModel->mControlSize.width;
2271 quad.w = lastSelectionBoxLineInfo.lineOffset + lastSelectionBoxLineInfo.lineHeight;
2273 // Boxify at the end of the line.
2274 mEventData->mDecorator->AddHighlight( actualNumberOfQuads,
2276 ++actualNumberOfQuads;
2278 // Update the size of the highlighted text.
2279 maxHighlightX = mModel->mVisualModel->mControlSize.width;
2283 // Set the actual number of quads.
2284 mEventData->mDecorator->ResizeHighlightQuads( actualNumberOfQuads );
2286 // Sets the highlight's size and position. In decorator's coords.
2287 // The highlight's height has been calculated above (before 'boxifying' the highlight).
2288 highLightSize.width = maxHighlightX - minHighlightX;
2290 highLightPosition.x = minHighlightX;
2291 const SelectionBoxInfo& firstSelectionBoxLineInfo = *( selectionBoxLinesInfo.Begin() );
2292 highLightPosition.y = firstSelectionBoxLineInfo.lineOffset;
2294 mEventData->mDecorator->SetHighLightBox( highLightPosition, highLightSize );
2296 if( !mEventData->mDecorator->IsSmoothHandlePanEnabled() )
2298 CursorInfo primaryCursorInfo;
2299 GetCursorPosition( mEventData->mLeftSelectionPosition,
2300 primaryCursorInfo );
2302 const Vector2 primaryPosition = primaryCursorInfo.primaryPosition + mModel->mScrollPosition;
2304 mEventData->mDecorator->SetPosition( LEFT_SELECTION_HANDLE,
2306 primaryCursorInfo.lineOffset + mModel->mScrollPosition.y,
2307 primaryCursorInfo.lineHeight );
2309 CursorInfo secondaryCursorInfo;
2310 GetCursorPosition( mEventData->mRightSelectionPosition,
2311 secondaryCursorInfo );
2313 const Vector2 secondaryPosition = secondaryCursorInfo.primaryPosition + mModel->mScrollPosition;
2315 mEventData->mDecorator->SetPosition( RIGHT_SELECTION_HANDLE,
2316 secondaryPosition.x,
2317 secondaryCursorInfo.lineOffset + mModel->mScrollPosition.y,
2318 secondaryCursorInfo.lineHeight );
2321 // Cursor to be positioned at end of selection so if selection interrupted and edit mode restarted the cursor will be at end of selection
2322 mEventData->mPrimaryCursorPosition = ( indicesSwapped ) ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition;
2324 // Set the flag to update the decorator.
2325 mEventData->mDecoratorUpdated = true;
2328 void Controller::Impl::RepositionSelectionHandles( float visualX, float visualY, Controller::NoTextTap::Action action )
2330 if( NULL == mEventData )
2332 // Nothing to do if there is no text input.
2336 if( IsShowingPlaceholderText() )
2338 // Nothing to do if there is the place-holder text.
2342 const Length numberOfGlyphs = mModel->mVisualModel->mGlyphs.Count();
2343 const Length numberOfLines = mModel->mVisualModel->mLines.Count();
2344 if( ( 0 == numberOfGlyphs ) ||
2345 ( 0 == numberOfLines ) )
2347 // Nothing to do if there is no text.
2351 // Find which word was selected
2352 CharacterIndex selectionStart( 0 );
2353 CharacterIndex selectionEnd( 0 );
2354 CharacterIndex noTextHitIndex( 0 );
2355 const bool characterHit = FindSelectionIndices( mModel->mVisualModel,
2356 mModel->mLogicalModel,
2363 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "%p selectionStart %d selectionEnd %d\n", this, selectionStart, selectionEnd );
2365 if( characterHit || ( Controller::NoTextTap::HIGHLIGHT == action ) )
2367 ChangeState( EventData::SELECTING );
2369 mEventData->mLeftSelectionPosition = selectionStart;
2370 mEventData->mRightSelectionPosition = selectionEnd;
2372 mEventData->mUpdateLeftSelectionPosition = true;
2373 mEventData->mUpdateRightSelectionPosition = true;
2374 mEventData->mUpdateHighlightBox = true;
2376 // It may happen an IMF commit event arrives before the selection event
2377 // if the IMF manager is in pre-edit state. The commit event will set the
2378 // mEventData->mUpdateCursorPosition flag to true. If it's not set back
2379 // to false, the highlight box won't be updated.
2380 mEventData->mUpdateCursorPosition = false;
2382 mEventData->mScrollAfterUpdatePosition = ( mEventData->mLeftSelectionPosition != mEventData->mRightSelectionPosition );
2384 else if( Controller::NoTextTap::SHOW_SELECTION_POPUP == action )
2386 // Nothing to select. i.e. a white space, out of bounds
2387 ChangeState( EventData::EDITING_WITH_POPUP );
2389 mEventData->mPrimaryCursorPosition = noTextHitIndex;
2391 mEventData->mUpdateCursorPosition = true;
2392 mEventData->mUpdateGrabHandlePosition = true;
2393 mEventData->mScrollAfterUpdatePosition = true;
2394 mEventData->mUpdateInputStyle = true;
2396 else if( Controller::NoTextTap::NO_ACTION == action )
2398 // Nothing to select. i.e. a white space, out of bounds
2399 mEventData->mPrimaryCursorPosition = noTextHitIndex;
2401 mEventData->mUpdateCursorPosition = true;
2402 mEventData->mUpdateGrabHandlePosition = true;
2403 mEventData->mScrollAfterUpdatePosition = true;
2404 mEventData->mUpdateInputStyle = true;
2408 void Controller::Impl::SetPopupButtons()
2411 * Sets the Popup buttons to be shown depending on State.
2413 * If SELECTING : CUT & COPY + ( PASTE & CLIPBOARD if content available to paste )
2415 * If EDITING_WITH_POPUP : SELECT & SELECT_ALL
2418 TextSelectionPopup::Buttons buttonsToShow = TextSelectionPopup::NONE;
2420 if( EventData::SELECTING == mEventData->mState )
2422 buttonsToShow = TextSelectionPopup::Buttons( TextSelectionPopup::CUT | TextSelectionPopup::COPY );
2424 if( !IsClipboardEmpty() )
2426 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
2427 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::CLIPBOARD ) );
2430 if( !mEventData->mAllTextSelected )
2432 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::SELECT_ALL ) );
2435 else if( EventData::EDITING_WITH_POPUP == mEventData->mState )
2437 if( mModel->mLogicalModel->mText.Count() && !IsShowingPlaceholderText() )
2439 buttonsToShow = TextSelectionPopup::Buttons( TextSelectionPopup::SELECT | TextSelectionPopup::SELECT_ALL );
2442 if( !IsClipboardEmpty() )
2444 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
2445 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::CLIPBOARD ) );
2448 else if( EventData::EDITING_WITH_PASTE_POPUP == mEventData->mState )
2450 if ( !IsClipboardEmpty() )
2452 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
2453 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::CLIPBOARD ) );
2457 mEventData->mDecorator->SetEnabledPopupButtons( buttonsToShow );
2460 void Controller::Impl::ChangeState( EventData::State newState )
2462 if( NULL == mEventData )
2464 // Nothing to do if there is no text input.
2468 DALI_LOG_INFO( gLogFilter, Debug::General, "ChangeState state:%d newstate:%d\n", mEventData->mState, newState );
2470 if( mEventData->mState != newState )
2472 mEventData->mPreviousState = mEventData->mState;
2473 mEventData->mState = newState;
2475 switch( mEventData->mState )
2477 case EventData::INACTIVE:
2479 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
2480 mEventData->mDecorator->StopCursorBlink();
2481 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
2482 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
2483 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
2484 mEventData->mDecorator->SetHighlightActive( false );
2485 mEventData->mDecorator->SetPopupActive( false );
2486 mEventData->mDecoratorUpdated = true;
2489 case EventData::INTERRUPTED:
2491 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
2492 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
2493 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
2494 mEventData->mDecorator->SetHighlightActive( false );
2495 mEventData->mDecorator->SetPopupActive( false );
2496 mEventData->mDecoratorUpdated = true;
2499 case EventData::SELECTING:
2501 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
2502 mEventData->mDecorator->StopCursorBlink();
2503 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
2504 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true );
2505 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true );
2506 mEventData->mDecorator->SetHighlightActive( true );
2507 if( mEventData->mGrabHandlePopupEnabled )
2510 mEventData->mDecorator->SetPopupActive( true );
2512 mEventData->mDecoratorUpdated = true;
2515 case EventData::EDITING:
2517 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
2518 if( mEventData->mCursorBlinkEnabled )
2520 mEventData->mDecorator->StartCursorBlink();
2522 // Grab handle is not shown until a tap is received whilst EDITING
2523 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
2524 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
2525 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
2526 mEventData->mDecorator->SetHighlightActive( false );
2527 if( mEventData->mGrabHandlePopupEnabled )
2529 mEventData->mDecorator->SetPopupActive( false );
2531 mEventData->mDecoratorUpdated = true;
2534 case EventData::EDITING_WITH_POPUP:
2536 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "EDITING_WITH_POPUP \n", newState );
2538 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
2539 if( mEventData->mCursorBlinkEnabled )
2541 mEventData->mDecorator->StartCursorBlink();
2543 if( mEventData->mSelectionEnabled )
2545 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
2546 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
2547 mEventData->mDecorator->SetHighlightActive( false );
2551 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
2553 if( mEventData->mGrabHandlePopupEnabled )
2556 mEventData->mDecorator->SetPopupActive( true );
2558 mEventData->mDecoratorUpdated = true;
2561 case EventData::EDITING_WITH_GRAB_HANDLE:
2563 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "EDITING_WITH_GRAB_HANDLE \n", newState );
2565 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
2566 if( mEventData->mCursorBlinkEnabled )
2568 mEventData->mDecorator->StartCursorBlink();
2570 // Grab handle is not shown until a tap is received whilst EDITING
2571 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
2572 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
2573 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
2574 mEventData->mDecorator->SetHighlightActive( false );
2575 if( mEventData->mGrabHandlePopupEnabled )
2577 mEventData->mDecorator->SetPopupActive( false );
2579 mEventData->mDecoratorUpdated = true;
2582 case EventData::SELECTION_HANDLE_PANNING:
2584 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
2585 mEventData->mDecorator->StopCursorBlink();
2586 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
2587 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true );
2588 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true );
2589 mEventData->mDecorator->SetHighlightActive( true );
2590 if( mEventData->mGrabHandlePopupEnabled )
2592 mEventData->mDecorator->SetPopupActive( false );
2594 mEventData->mDecoratorUpdated = true;
2597 case EventData::GRAB_HANDLE_PANNING:
2599 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "GRAB_HANDLE_PANNING \n", newState );
2601 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
2602 if( mEventData->mCursorBlinkEnabled )
2604 mEventData->mDecorator->StartCursorBlink();
2606 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
2607 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
2608 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
2609 mEventData->mDecorator->SetHighlightActive( false );
2610 if( mEventData->mGrabHandlePopupEnabled )
2612 mEventData->mDecorator->SetPopupActive( false );
2614 mEventData->mDecoratorUpdated = true;
2617 case EventData::EDITING_WITH_PASTE_POPUP:
2619 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "EDITING_WITH_PASTE_POPUP \n", newState );
2621 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
2622 if( mEventData->mCursorBlinkEnabled )
2624 mEventData->mDecorator->StartCursorBlink();
2627 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
2628 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
2629 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
2630 mEventData->mDecorator->SetHighlightActive( false );
2632 if( mEventData->mGrabHandlePopupEnabled )
2635 mEventData->mDecorator->SetPopupActive( true );
2637 mEventData->mDecoratorUpdated = true;
2640 case EventData::TEXT_PANNING:
2642 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
2643 mEventData->mDecorator->StopCursorBlink();
2644 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
2645 if( mEventData->mDecorator->IsHandleActive( LEFT_SELECTION_HANDLE ) ||
2646 mEventData->mDecorator->IsHandleActive( RIGHT_SELECTION_HANDLE ) )
2648 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
2649 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
2650 mEventData->mDecorator->SetHighlightActive( true );
2653 if( mEventData->mGrabHandlePopupEnabled )
2655 mEventData->mDecorator->SetPopupActive( false );
2658 mEventData->mDecoratorUpdated = true;
2665 void Controller::Impl::GetCursorPosition( CharacterIndex logical,
2666 CursorInfo& cursorInfo )
2668 if( !IsShowingRealText() )
2670 // Do not want to use the place-holder text to set the cursor position.
2672 // Use the line's height of the font's family set to set the cursor's size.
2673 // If there is no font's family set, use the default font.
2674 // Use the current alignment to place the cursor at the beginning, center or end of the box.
2676 cursorInfo.lineOffset = 0.f;
2677 cursorInfo.lineHeight = GetDefaultFontLineHeight();
2678 cursorInfo.primaryCursorHeight = cursorInfo.lineHeight;
2680 switch( mModel->mHorizontalAlignment )
2682 case Text::HorizontalAlignment::BEGIN :
2684 cursorInfo.primaryPosition.x = 0.f;
2687 case Text::HorizontalAlignment::CENTER:
2689 cursorInfo.primaryPosition.x = floorf( 0.5f * mModel->mVisualModel->mControlSize.width );
2692 case Text::HorizontalAlignment::END:
2694 cursorInfo.primaryPosition.x = mModel->mVisualModel->mControlSize.width - mEventData->mDecorator->GetCursorWidth();
2699 // Nothing else to do.
2703 const bool isMultiLine = ( Layout::Engine::MULTI_LINE_BOX == mLayoutEngine.GetLayout() );
2704 GetCursorPositionParameters parameters;
2705 parameters.visualModel = mModel->mVisualModel;
2706 parameters.logicalModel = mModel->mLogicalModel;
2707 parameters.metrics = mMetrics;
2708 parameters.logical = logical;
2709 parameters.isMultiline = isMultiLine;
2711 Text::GetCursorPosition( parameters,
2716 // If the text is editable and multi-line, the cursor position after a white space shouldn't exceed the boundaries of the text control.
2718 // Note the white spaces laid-out at the end of the line might exceed the boundaries of the control.
2719 // The reason is a wrapped line must not start with a white space so they are laid-out at the end of the line.
2721 if( 0.f > cursorInfo.primaryPosition.x )
2723 cursorInfo.primaryPosition.x = 0.f;
2726 const float edgeWidth = mModel->mVisualModel->mControlSize.width - static_cast<float>( mEventData->mDecorator->GetCursorWidth() );
2727 if( cursorInfo.primaryPosition.x > edgeWidth )
2729 cursorInfo.primaryPosition.x = edgeWidth;
2734 CharacterIndex Controller::Impl::CalculateNewCursorIndex( CharacterIndex index ) const
2736 if( NULL == mEventData )
2738 // Nothing to do if there is no text input.
2742 CharacterIndex cursorIndex = mEventData->mPrimaryCursorPosition;
2744 const GlyphIndex* const charactersToGlyphBuffer = mModel->mVisualModel->mCharactersToGlyph.Begin();
2745 const Length* const charactersPerGlyphBuffer = mModel->mVisualModel->mCharactersPerGlyph.Begin();
2747 GlyphIndex glyphIndex = *( charactersToGlyphBuffer + index );
2748 Length numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
2750 if( numberOfCharacters > 1u )
2752 const Script script = mModel->mLogicalModel->GetScript( index );
2753 if( HasLigatureMustBreak( script ) )
2755 // Prevents to jump the whole Latin ligatures like fi, ff, or Arabic ï»», ...
2756 numberOfCharacters = 1u;
2761 while( 0u == numberOfCharacters )
2764 numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
2768 if( index < mEventData->mPrimaryCursorPosition )
2770 cursorIndex -= numberOfCharacters;
2774 cursorIndex += numberOfCharacters;
2777 // Will update the cursor hook position.
2778 mEventData->mUpdateCursorHookPosition = true;
2783 void Controller::Impl::UpdateCursorPosition( const CursorInfo& cursorInfo )
2785 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "-->Controller::UpdateCursorPosition %p\n", this );
2786 if( NULL == mEventData )
2788 // Nothing to do if there is no text input.
2789 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition no event data\n" );
2793 const Vector2 cursorPosition = cursorInfo.primaryPosition + mModel->mScrollPosition;
2795 mEventData->mDecorator->SetGlyphOffset( PRIMARY_CURSOR, cursorInfo.glyphOffset );
2797 // Sets the cursor position.
2798 mEventData->mDecorator->SetPosition( PRIMARY_CURSOR,
2801 cursorInfo.primaryCursorHeight,
2802 cursorInfo.lineHeight );
2803 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Primary cursor position: %f,%f\n", cursorPosition.x, cursorPosition.y );
2805 if( mEventData->mUpdateGrabHandlePosition )
2807 // Sets the grab handle position.
2808 mEventData->mDecorator->SetPosition( GRAB_HANDLE,
2810 cursorInfo.lineOffset + mModel->mScrollPosition.y,
2811 cursorInfo.lineHeight );
2814 if( cursorInfo.isSecondaryCursor )
2816 mEventData->mDecorator->SetPosition( SECONDARY_CURSOR,
2817 cursorInfo.secondaryPosition.x + mModel->mScrollPosition.x,
2818 cursorInfo.secondaryPosition.y + mModel->mScrollPosition.y,
2819 cursorInfo.secondaryCursorHeight,
2820 cursorInfo.lineHeight );
2821 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Secondary cursor position: %f,%f\n", cursorInfo.secondaryPosition.x + mModel->mScrollPosition.x, cursorInfo.secondaryPosition.y + mModel->mScrollPosition.y );
2824 // Set which cursors are active according the state.
2825 if( EventData::IsEditingState( mEventData->mState ) || ( EventData::GRAB_HANDLE_PANNING == mEventData->mState ) )
2827 if( cursorInfo.isSecondaryCursor )
2829 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_BOTH );
2833 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
2838 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
2841 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition\n" );
2844 void Controller::Impl::UpdateSelectionHandle( HandleType handleType,
2845 const CursorInfo& cursorInfo )
2847 if( ( LEFT_SELECTION_HANDLE != handleType ) &&
2848 ( RIGHT_SELECTION_HANDLE != handleType ) )
2853 const Vector2 cursorPosition = cursorInfo.primaryPosition + mModel->mScrollPosition;
2855 // Sets the handle's position.
2856 mEventData->mDecorator->SetPosition( handleType,
2858 cursorInfo.lineOffset + mModel->mScrollPosition.y,
2859 cursorInfo.lineHeight );
2861 // If selection handle at start of the text and other at end of the text then all text is selected.
2862 const CharacterIndex startOfSelection = std::min( mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition );
2863 const CharacterIndex endOfSelection = std::max ( mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition );
2864 mEventData->mAllTextSelected = ( startOfSelection == 0 ) && ( endOfSelection == mModel->mLogicalModel->mText.Count() );
2867 void Controller::Impl::ClampHorizontalScroll( const Vector2& layoutSize )
2869 // Clamp between -space & -alignment offset.
2871 if( layoutSize.width > mModel->mVisualModel->mControlSize.width )
2873 const float space = ( layoutSize.width - mModel->mVisualModel->mControlSize.width ) + mModel->mAlignmentOffset;
2874 mModel->mScrollPosition.x = ( mModel->mScrollPosition.x < -space ) ? -space : mModel->mScrollPosition.x;
2875 mModel->mScrollPosition.x = ( mModel->mScrollPosition.x > -mModel->mAlignmentOffset ) ? -mModel->mAlignmentOffset : mModel->mScrollPosition.x;
2877 mEventData->mDecoratorUpdated = true;
2881 mModel->mScrollPosition.x = 0.f;
2885 void Controller::Impl::ClampVerticalScroll( const Vector2& layoutSize )
2887 if( Layout::Engine::SINGLE_LINE_BOX == mLayoutEngine.GetLayout() )
2889 // Nothing to do if the text is single line.
2893 // Clamp between -space & 0.
2894 if( layoutSize.height > mModel->mVisualModel->mControlSize.height )
2896 const float space = ( layoutSize.height - mModel->mVisualModel->mControlSize.height );
2897 mModel->mScrollPosition.y = ( mModel->mScrollPosition.y < -space ) ? -space : mModel->mScrollPosition.y;
2898 mModel->mScrollPosition.y = ( mModel->mScrollPosition.y > 0.f ) ? 0.f : mModel->mScrollPosition.y;
2900 mEventData->mDecoratorUpdated = true;
2904 mModel->mScrollPosition.y = 0.f;
2908 void Controller::Impl::ScrollToMakePositionVisible( const Vector2& position, float lineHeight )
2910 const float cursorWidth = mEventData->mDecorator ? static_cast<float>( mEventData->mDecorator->GetCursorWidth() ) : 0.f;
2912 // position is in actor's coords.
2913 const float positionEndX = position.x + cursorWidth;
2914 const float positionEndY = position.y + lineHeight;
2916 // Transform the position to decorator coords.
2917 const float decoratorPositionBeginX = position.x + mModel->mScrollPosition.x;
2918 const float decoratorPositionEndX = positionEndX + mModel->mScrollPosition.x;
2920 const float decoratorPositionBeginY = position.y + mModel->mScrollPosition.y;
2921 const float decoratorPositionEndY = positionEndY + mModel->mScrollPosition.y;
2923 if( decoratorPositionBeginX < 0.f )
2925 mModel->mScrollPosition.x = -position.x;
2927 else if( decoratorPositionEndX > mModel->mVisualModel->mControlSize.width )
2929 mModel->mScrollPosition.x = mModel->mVisualModel->mControlSize.width - positionEndX;
2932 if( Layout::Engine::MULTI_LINE_BOX == mLayoutEngine.GetLayout() )
2934 if( decoratorPositionBeginY < 0.f )
2936 mModel->mScrollPosition.y = -position.y;
2938 else if( decoratorPositionEndY > mModel->mVisualModel->mControlSize.height )
2940 mModel->mScrollPosition.y = mModel->mVisualModel->mControlSize.height - positionEndY;
2945 void Controller::Impl::ScrollTextToMatchCursor( const CursorInfo& cursorInfo )
2947 // Get the current cursor position in decorator coords.
2948 const Vector2& currentCursorPosition = mEventData->mDecorator->GetPosition( PRIMARY_CURSOR );
2950 const LineIndex lineIndex = mModel->mVisualModel->GetLineOfCharacter( mEventData->mPrimaryCursorPosition );
2954 // Calculate the offset to match the cursor position before the character was deleted.
2955 mModel->mScrollPosition.x = currentCursorPosition.x - cursorInfo.primaryPosition.x;
2957 //If text control has more than two lines and current line index is not last, calculate scrollpositionY
2958 if( mModel->mVisualModel->mLines.Count() > 1u && lineIndex != mModel->mVisualModel->mLines.Count() -1u )
2960 const float currentCursorGlyphOffset = mEventData->mDecorator->GetGlyphOffset( PRIMARY_CURSOR );
2961 mModel->mScrollPosition.y = currentCursorPosition.y - cursorInfo.lineOffset - currentCursorGlyphOffset;
2965 ClampHorizontalScroll( mModel->mVisualModel->GetLayoutSize() );
2966 ClampVerticalScroll( mModel->mVisualModel->GetLayoutSize() );
2968 // Makes the new cursor position visible if needed.
2969 ScrollToMakePositionVisible( cursorInfo.primaryPosition, cursorInfo.lineHeight );
2972 void Controller::Impl::RequestRelayout()
2974 if( NULL != mControlInterface )
2976 mControlInterface->RequestTextRelayout();
2982 } // namespace Toolkit