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 mPlaceholderTextActive(),
74 mPlaceholderTextInactive(),
75 mPlaceholderTextColor( 0.8f, 0.8f, 0.8f, 0.8f ),
77 mInputStyleChangedQueue(),
78 mPreviousState( INACTIVE ),
80 mPrimaryCursorPosition( 0u ),
81 mLeftSelectionPosition( 0u ),
82 mRightSelectionPosition( 0u ),
83 mPreEditStartPosition( 0u ),
85 mCursorHookPositionX( 0.f ),
86 mDoubleTapAction( Controller::NoTextTap::NO_ACTION ),
87 mLongPressAction( Controller::NoTextTap::SHOW_SELECTION_POPUP ),
88 mIsShowingPlaceholderText( false ),
89 mPreEditFlag( false ),
90 mDecoratorUpdated( false ),
91 mCursorBlinkEnabled( true ),
92 mGrabHandleEnabled( true ),
93 mGrabHandlePopupEnabled( true ),
94 mSelectionEnabled( true ),
95 mUpdateCursorHookPosition( false ),
96 mUpdateCursorPosition( false ),
97 mUpdateGrabHandlePosition( false ),
98 mUpdateLeftSelectionPosition( false ),
99 mUpdateRightSelectionPosition( false ),
100 mIsLeftHandleSelected( false ),
101 mIsRightHandleSelected( false ),
102 mUpdateHighlightBox( false ),
103 mScrollAfterUpdatePosition( false ),
104 mScrollAfterDelete( false ),
105 mAllTextSelected( false ),
106 mUpdateInputStyle( false )
108 mImfManager = ImfManager::Get();
111 EventData::~EventData()
114 bool Controller::Impl::ProcessInputEvents()
116 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "-->Controller::ProcessInputEvents\n" );
117 if( NULL == mEventData )
119 // Nothing to do if there is no text input.
120 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::ProcessInputEvents no event data\n" );
124 if( mEventData->mDecorator )
126 for( std::vector<Event>::iterator iter = mEventData->mEventQueue.begin();
127 iter != mEventData->mEventQueue.end();
132 case Event::CURSOR_KEY_EVENT:
134 OnCursorKeyEvent( *iter );
137 case Event::TAP_EVENT:
142 case Event::LONG_PRESS_EVENT:
144 OnLongPressEvent( *iter );
147 case Event::PAN_EVENT:
152 case Event::GRAB_HANDLE_EVENT:
153 case Event::LEFT_SELECTION_HANDLE_EVENT:
154 case Event::RIGHT_SELECTION_HANDLE_EVENT: // Fall through
156 OnHandleEvent( *iter );
161 OnSelectEvent( *iter );
164 case Event::SELECT_ALL:
173 if( mEventData->mUpdateCursorPosition ||
174 mEventData->mUpdateHighlightBox )
179 // The cursor must also be repositioned after inserts into the model
180 if( mEventData->mUpdateCursorPosition )
182 // Updates the cursor position and scrolls the text to make it visible.
183 CursorInfo cursorInfo;
184 // Calculate the cursor position from the new cursor index.
185 GetCursorPosition( mEventData->mPrimaryCursorPosition,
188 if( mEventData->mUpdateCursorHookPosition )
190 // Update the cursor hook position. Used to move the cursor with the keys 'up' and 'down'.
191 mEventData->mCursorHookPositionX = cursorInfo.primaryPosition.x;
192 mEventData->mUpdateCursorHookPosition = false;
195 // Scroll first the text after delete ...
196 if( mEventData->mScrollAfterDelete )
198 ScrollTextToMatchCursor( cursorInfo );
201 // ... then, text can be scrolled to make the cursor visible.
202 if( mEventData->mScrollAfterUpdatePosition )
204 const Vector2 currentCursorPosition( cursorInfo.primaryPosition.x, cursorInfo.lineOffset );
205 ScrollToMakePositionVisible( currentCursorPosition, cursorInfo.lineHeight );
207 mEventData->mScrollAfterUpdatePosition = false;
208 mEventData->mScrollAfterDelete = false;
210 UpdateCursorPosition( cursorInfo );
212 mEventData->mDecoratorUpdated = true;
213 mEventData->mUpdateCursorPosition = false;
214 mEventData->mUpdateGrabHandlePosition = false;
218 CursorInfo leftHandleInfo;
219 CursorInfo rightHandleInfo;
221 if( mEventData->mUpdateHighlightBox )
223 GetCursorPosition( mEventData->mLeftSelectionPosition,
226 GetCursorPosition( mEventData->mRightSelectionPosition,
229 if( mEventData->mScrollAfterUpdatePosition && ( mEventData->mIsLeftHandleSelected ? mEventData->mUpdateLeftSelectionPosition : mEventData->mUpdateRightSelectionPosition ) )
231 if( mEventData->mIsLeftHandleSelected && mEventData->mIsRightHandleSelected )
233 CursorInfo& infoLeft = leftHandleInfo;
235 const Vector2 currentCursorPositionLeft( infoLeft.primaryPosition.x, infoLeft.lineOffset );
236 ScrollToMakePositionVisible( currentCursorPositionLeft, infoLeft.lineHeight );
238 CursorInfo& infoRight = rightHandleInfo;
240 const Vector2 currentCursorPositionRight( infoRight.primaryPosition.x, infoRight.lineOffset );
241 ScrollToMakePositionVisible( currentCursorPositionRight, infoRight.lineHeight );
245 CursorInfo& info = mEventData->mIsLeftHandleSelected ? leftHandleInfo : rightHandleInfo;
247 const Vector2 currentCursorPosition( info.primaryPosition.x, info.lineOffset );
248 ScrollToMakePositionVisible( currentCursorPosition, info.lineHeight );
253 if( mEventData->mUpdateLeftSelectionPosition )
255 UpdateSelectionHandle( LEFT_SELECTION_HANDLE,
259 mEventData->mDecoratorUpdated = true;
260 mEventData->mUpdateLeftSelectionPosition = false;
263 if( mEventData->mUpdateRightSelectionPosition )
265 UpdateSelectionHandle( RIGHT_SELECTION_HANDLE,
269 mEventData->mDecoratorUpdated = true;
270 mEventData->mUpdateRightSelectionPosition = false;
273 if( mEventData->mUpdateHighlightBox )
275 RepositionSelectionHandles();
277 mEventData->mUpdateLeftSelectionPosition = false;
278 mEventData->mUpdateRightSelectionPosition = false;
279 mEventData->mUpdateHighlightBox = false;
280 mEventData->mIsLeftHandleSelected = false;
281 mEventData->mIsRightHandleSelected = false;
284 mEventData->mScrollAfterUpdatePosition = false;
287 if( mEventData->mUpdateInputStyle )
289 // Keep a copy of the current input style.
290 InputStyle currentInputStyle;
291 currentInputStyle.Copy( mEventData->mInputStyle );
293 // Set the default style first.
294 RetrieveDefaultInputStyle( mEventData->mInputStyle );
296 // Get the character index from the cursor index.
297 const CharacterIndex styleIndex = ( mEventData->mPrimaryCursorPosition > 0u ) ? mEventData->mPrimaryCursorPosition - 1u : 0u;
299 // Retrieve the style from the style runs stored in the logical model.
300 mLogicalModel->RetrieveStyle( styleIndex, mEventData->mInputStyle );
302 // Compare if the input style has changed.
303 const bool hasInputStyleChanged = !currentInputStyle.Equal( mEventData->mInputStyle );
305 if( hasInputStyleChanged )
307 const InputStyle::Mask styleChangedMask = currentInputStyle.GetInputStyleChangeMask( mEventData->mInputStyle );
308 // Queue the input style changed signal.
309 mEventData->mInputStyleChangedQueue.PushBack( styleChangedMask );
312 mEventData->mUpdateInputStyle = false;
315 mEventData->mEventQueue.clear();
317 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::ProcessInputEvents\n" );
319 const bool decoratorUpdated = mEventData->mDecoratorUpdated;
320 mEventData->mDecoratorUpdated = false;
322 return decoratorUpdated;
325 void Controller::Impl::NotifyImfManager()
327 if( mEventData && mEventData->mImfManager )
329 CharacterIndex cursorPosition = GetLogicalCursorPosition();
331 const Length numberOfWhiteSpaces = GetNumberOfWhiteSpaces( 0u );
333 // Update the cursor position by removing the initial white spaces.
334 if( cursorPosition < numberOfWhiteSpaces )
340 cursorPosition -= numberOfWhiteSpaces;
343 mEventData->mImfManager.SetCursorPosition( cursorPosition );
344 mEventData->mImfManager.NotifyCursorPosition();
348 void Controller::Impl::NotifyImfMultiLineStatus()
352 LayoutEngine::Layout layout = mLayoutEngine.GetLayout();
353 mEventData->mImfManager.NotifyTextInputMultiLine( layout == LayoutEngine::MULTI_LINE_BOX );
357 CharacterIndex Controller::Impl::GetLogicalCursorPosition() const
359 CharacterIndex cursorPosition = 0u;
363 if( ( EventData::SELECTING == mEventData->mState ) ||
364 ( EventData::SELECTION_HANDLE_PANNING == mEventData->mState ) )
366 cursorPosition = std::min( mEventData->mRightSelectionPosition, mEventData->mLeftSelectionPosition );
370 cursorPosition = mEventData->mPrimaryCursorPosition;
374 return cursorPosition;
377 Length Controller::Impl::GetNumberOfWhiteSpaces( CharacterIndex index ) const
379 Length numberOfWhiteSpaces = 0u;
381 // Get the buffer to the text.
382 Character* utf32CharacterBuffer = mLogicalModel->mText.Begin();
384 const Length totalNumberOfCharacters = mLogicalModel->mText.Count();
385 for( ; index < totalNumberOfCharacters; ++index, ++numberOfWhiteSpaces )
387 if( !TextAbstraction::IsWhiteSpace( *( utf32CharacterBuffer + index ) ) )
393 return numberOfWhiteSpaces;
396 void Controller::Impl::GetText( CharacterIndex index, std::string& text ) const
398 // Get the total number of characters.
399 Length numberOfCharacters = mLogicalModel->mText.Count();
401 // Retrieve the text.
402 if( 0u != numberOfCharacters )
404 Utf32ToUtf8( mLogicalModel->mText.Begin() + index, numberOfCharacters - index, text );
408 void Controller::Impl::CalculateTextUpdateIndices( Length& numberOfCharacters )
410 mTextUpdateInfo.mParagraphCharacterIndex = 0u;
411 mTextUpdateInfo.mStartGlyphIndex = 0u;
412 mTextUpdateInfo.mStartLineIndex = 0u;
413 numberOfCharacters = 0u;
415 const Length numberOfParagraphs = mLogicalModel->mParagraphInfo.Count();
416 if( 0u == numberOfParagraphs )
418 mTextUpdateInfo.mParagraphCharacterIndex = 0u;
419 numberOfCharacters = 0u;
421 mTextUpdateInfo.mRequestedNumberOfCharacters = mTextUpdateInfo.mNumberOfCharactersToAdd - mTextUpdateInfo.mNumberOfCharactersToRemove;
423 // Nothing else to do if there are no paragraphs.
427 // Find the paragraphs to be updated.
428 Vector<ParagraphRunIndex> paragraphsToBeUpdated;
429 if( mTextUpdateInfo.mCharacterIndex >= mTextUpdateInfo.mPreviousNumberOfCharacters )
431 // Text is being added at the end of the current text.
432 if( mTextUpdateInfo.mIsLastCharacterNewParagraph )
434 // Text is being added in a new paragraph after the last character of the text.
435 mTextUpdateInfo.mParagraphCharacterIndex = mTextUpdateInfo.mPreviousNumberOfCharacters;
436 numberOfCharacters = 0u;
437 mTextUpdateInfo.mRequestedNumberOfCharacters = mTextUpdateInfo.mNumberOfCharactersToAdd - mTextUpdateInfo.mNumberOfCharactersToRemove;
439 mTextUpdateInfo.mStartGlyphIndex = mVisualModel->mGlyphs.Count();
440 mTextUpdateInfo.mStartLineIndex = mVisualModel->mLines.Count() - 1u;
442 // Nothing else to do;
446 paragraphsToBeUpdated.PushBack( numberOfParagraphs - 1u );
450 Length numberOfCharactersToUpdate = 0u;
451 if( mTextUpdateInfo.mFullRelayoutNeeded )
453 numberOfCharactersToUpdate = mTextUpdateInfo.mPreviousNumberOfCharacters;
457 numberOfCharactersToUpdate = ( mTextUpdateInfo.mNumberOfCharactersToRemove > 0u ) ? mTextUpdateInfo.mNumberOfCharactersToRemove : 1u;
459 mLogicalModel->FindParagraphs( mTextUpdateInfo.mCharacterIndex,
460 numberOfCharactersToUpdate,
461 paragraphsToBeUpdated );
464 if( 0u != paragraphsToBeUpdated.Count() )
466 const ParagraphRunIndex firstParagraphIndex = *( paragraphsToBeUpdated.Begin() );
467 const ParagraphRun& firstParagraph = *( mLogicalModel->mParagraphInfo.Begin() + firstParagraphIndex );
468 mTextUpdateInfo.mParagraphCharacterIndex = firstParagraph.characterRun.characterIndex;
470 ParagraphRunIndex lastParagraphIndex = *( paragraphsToBeUpdated.End() - 1u );
471 const ParagraphRun& lastParagraph = *( mLogicalModel->mParagraphInfo.Begin() + lastParagraphIndex );
473 if( ( mTextUpdateInfo.mNumberOfCharactersToRemove > 0u ) && // Some character are removed.
474 ( lastParagraphIndex < numberOfParagraphs - 1u ) && // There is a next paragraph.
475 ( ( lastParagraph.characterRun.characterIndex + lastParagraph.characterRun.numberOfCharacters ) == // The last removed character is the new paragraph character.
476 ( mTextUpdateInfo.mCharacterIndex + mTextUpdateInfo.mNumberOfCharactersToRemove ) ) )
478 // The new paragraph character of the last updated paragraph has been removed so is going to be merged with the next one.
479 const ParagraphRun& lastParagraph = *( mLogicalModel->mParagraphInfo.Begin() + lastParagraphIndex + 1u );
481 numberOfCharacters = lastParagraph.characterRun.characterIndex + lastParagraph.characterRun.numberOfCharacters - mTextUpdateInfo.mParagraphCharacterIndex;
485 numberOfCharacters = lastParagraph.characterRun.characterIndex + lastParagraph.characterRun.numberOfCharacters - mTextUpdateInfo.mParagraphCharacterIndex;
489 mTextUpdateInfo.mRequestedNumberOfCharacters = numberOfCharacters + mTextUpdateInfo.mNumberOfCharactersToAdd - mTextUpdateInfo.mNumberOfCharactersToRemove;
490 mTextUpdateInfo.mStartGlyphIndex = *( mVisualModel->mCharactersToGlyph.Begin() + mTextUpdateInfo.mParagraphCharacterIndex );
493 void Controller::Impl::ClearFullModelData( OperationsMask operations )
495 if( NO_OPERATION != ( GET_LINE_BREAKS & operations ) )
497 mLogicalModel->mLineBreakInfo.Clear();
498 mLogicalModel->mParagraphInfo.Clear();
501 if( NO_OPERATION != ( GET_WORD_BREAKS & operations ) )
503 mLogicalModel->mLineBreakInfo.Clear();
506 if( NO_OPERATION != ( GET_SCRIPTS & operations ) )
508 mLogicalModel->mScriptRuns.Clear();
511 if( NO_OPERATION != ( VALIDATE_FONTS & operations ) )
513 mLogicalModel->mFontRuns.Clear();
516 if( 0u != mLogicalModel->mBidirectionalParagraphInfo.Count() )
518 if( NO_OPERATION != ( BIDI_INFO & operations ) )
520 mLogicalModel->mBidirectionalParagraphInfo.Clear();
521 mLogicalModel->mCharacterDirections.Clear();
524 if( NO_OPERATION != ( REORDER & operations ) )
526 // Free the allocated memory used to store the conversion table in the bidirectional line info run.
527 for( Vector<BidirectionalLineInfoRun>::Iterator it = mLogicalModel->mBidirectionalLineInfo.Begin(),
528 endIt = mLogicalModel->mBidirectionalLineInfo.End();
532 BidirectionalLineInfoRun& bidiLineInfo = *it;
534 free( bidiLineInfo.visualToLogicalMap );
535 bidiLineInfo.visualToLogicalMap = NULL;
537 mLogicalModel->mBidirectionalLineInfo.Clear();
541 if( NO_OPERATION != ( SHAPE_TEXT & operations ) )
543 mVisualModel->mGlyphs.Clear();
544 mVisualModel->mGlyphsToCharacters.Clear();
545 mVisualModel->mCharactersToGlyph.Clear();
546 mVisualModel->mCharactersPerGlyph.Clear();
547 mVisualModel->mGlyphsPerCharacter.Clear();
548 mVisualModel->mGlyphPositions.Clear();
551 if( NO_OPERATION != ( LAYOUT & operations ) )
553 mVisualModel->mLines.Clear();
556 if( NO_OPERATION != ( COLOR & operations ) )
558 mVisualModel->mColorIndices.Clear();
562 void Controller::Impl::ClearCharacterModelData( CharacterIndex startIndex, CharacterIndex endIndex, OperationsMask operations )
564 const CharacterIndex endIndexPlusOne = endIndex + 1u;
566 if( NO_OPERATION != ( GET_LINE_BREAKS & operations ) )
568 // Clear the line break info.
569 LineBreakInfo* lineBreakInfoBuffer = mLogicalModel->mLineBreakInfo.Begin();
571 mLogicalModel->mLineBreakInfo.Erase( lineBreakInfoBuffer + startIndex,
572 lineBreakInfoBuffer + endIndexPlusOne );
574 // Clear the paragraphs.
575 ClearCharacterRuns( startIndex,
577 mLogicalModel->mParagraphInfo );
580 if( NO_OPERATION != ( GET_WORD_BREAKS & operations ) )
582 // Clear the word break info.
583 WordBreakInfo* wordBreakInfoBuffer = mLogicalModel->mWordBreakInfo.Begin();
585 mLogicalModel->mWordBreakInfo.Erase( wordBreakInfoBuffer + startIndex,
586 wordBreakInfoBuffer + endIndexPlusOne );
589 if( NO_OPERATION != ( GET_SCRIPTS & operations ) )
591 // Clear the scripts.
592 ClearCharacterRuns( startIndex,
594 mLogicalModel->mScriptRuns );
597 if( NO_OPERATION != ( VALIDATE_FONTS & operations ) )
600 ClearCharacterRuns( startIndex,
602 mLogicalModel->mFontRuns );
605 if( 0u != mLogicalModel->mBidirectionalParagraphInfo.Count() )
607 if( NO_OPERATION != ( BIDI_INFO & operations ) )
609 // Clear the bidirectional paragraph info.
610 ClearCharacterRuns( startIndex,
612 mLogicalModel->mBidirectionalParagraphInfo );
614 // Clear the character's directions.
615 CharacterDirection* characterDirectionsBuffer = mLogicalModel->mCharacterDirections.Begin();
617 mLogicalModel->mCharacterDirections.Erase( characterDirectionsBuffer + startIndex,
618 characterDirectionsBuffer + endIndexPlusOne );
621 if( NO_OPERATION != ( REORDER & operations ) )
623 uint32_t startRemoveIndex = mLogicalModel->mBidirectionalLineInfo.Count();
624 uint32_t endRemoveIndex = startRemoveIndex;
625 ClearCharacterRuns( startIndex,
627 mLogicalModel->mBidirectionalLineInfo,
631 BidirectionalLineInfoRun* bidirectionalLineInfoBuffer = mLogicalModel->mBidirectionalLineInfo.Begin();
633 // Free the allocated memory used to store the conversion table in the bidirectional line info run.
634 for( Vector<BidirectionalLineInfoRun>::Iterator it = bidirectionalLineInfoBuffer + startRemoveIndex,
635 endIt = bidirectionalLineInfoBuffer + endRemoveIndex;
639 BidirectionalLineInfoRun& bidiLineInfo = *it;
641 free( bidiLineInfo.visualToLogicalMap );
642 bidiLineInfo.visualToLogicalMap = NULL;
645 mLogicalModel->mBidirectionalLineInfo.Erase( bidirectionalLineInfoBuffer + startRemoveIndex,
646 bidirectionalLineInfoBuffer + endRemoveIndex );
651 void Controller::Impl::ClearGlyphModelData( CharacterIndex startIndex, CharacterIndex endIndex, OperationsMask operations )
653 const CharacterIndex endIndexPlusOne = endIndex + 1u;
654 const Length numberOfCharactersRemoved = endIndexPlusOne - startIndex;
656 // Convert the character index to glyph index before deleting the character to glyph and the glyphs per character buffers.
657 GlyphIndex* charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
658 Length* glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
660 const GlyphIndex endGlyphIndexPlusOne = *( charactersToGlyphBuffer + endIndex ) + *( glyphsPerCharacterBuffer + endIndex );
661 const Length numberOfGlyphsRemoved = endGlyphIndexPlusOne - mTextUpdateInfo.mStartGlyphIndex;
663 if( NO_OPERATION != ( SHAPE_TEXT & operations ) )
665 // Update the character to glyph indices.
666 for( Vector<GlyphIndex>::Iterator it = charactersToGlyphBuffer + endIndexPlusOne,
667 endIt = charactersToGlyphBuffer + mVisualModel->mCharactersToGlyph.Count();
671 CharacterIndex& index = *it;
672 index -= numberOfGlyphsRemoved;
675 // Clear the character to glyph conversion table.
676 mVisualModel->mCharactersToGlyph.Erase( charactersToGlyphBuffer + startIndex,
677 charactersToGlyphBuffer + endIndexPlusOne );
679 // Clear the glyphs per character table.
680 mVisualModel->mGlyphsPerCharacter.Erase( glyphsPerCharacterBuffer + startIndex,
681 glyphsPerCharacterBuffer + endIndexPlusOne );
683 // Clear the glyphs buffer.
684 GlyphInfo* glyphsBuffer = mVisualModel->mGlyphs.Begin();
685 mVisualModel->mGlyphs.Erase( glyphsBuffer + mTextUpdateInfo.mStartGlyphIndex,
686 glyphsBuffer + endGlyphIndexPlusOne );
688 CharacterIndex* glyphsToCharactersBuffer = mVisualModel->mGlyphsToCharacters.Begin();
690 // Update the glyph to character indices.
691 for( Vector<CharacterIndex>::Iterator it = glyphsToCharactersBuffer + endGlyphIndexPlusOne,
692 endIt = glyphsToCharactersBuffer + mVisualModel->mGlyphsToCharacters.Count();
696 CharacterIndex& index = *it;
697 index -= numberOfCharactersRemoved;
700 // Clear the glyphs to characters buffer.
701 mVisualModel->mGlyphsToCharacters.Erase( glyphsToCharactersBuffer + mTextUpdateInfo.mStartGlyphIndex,
702 glyphsToCharactersBuffer + endGlyphIndexPlusOne );
704 // Clear the characters per glyph buffer.
705 Length* charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
706 mVisualModel->mCharactersPerGlyph.Erase( charactersPerGlyphBuffer + mTextUpdateInfo.mStartGlyphIndex,
707 charactersPerGlyphBuffer + endGlyphIndexPlusOne );
709 // Clear the positions buffer.
710 Vector2* positionsBuffer = mVisualModel->mGlyphPositions.Begin();
711 mVisualModel->mGlyphPositions.Erase( positionsBuffer + mTextUpdateInfo.mStartGlyphIndex,
712 positionsBuffer + endGlyphIndexPlusOne );
715 if( NO_OPERATION != ( LAYOUT & operations ) )
718 uint32_t startRemoveIndex = mVisualModel->mLines.Count();
719 uint32_t endRemoveIndex = startRemoveIndex;
720 ClearCharacterRuns( startIndex,
722 mVisualModel->mLines,
726 // Will update the glyph runs.
727 startRemoveIndex = mVisualModel->mLines.Count();
728 endRemoveIndex = startRemoveIndex;
729 ClearGlyphRuns( mTextUpdateInfo.mStartGlyphIndex,
730 endGlyphIndexPlusOne - 1u,
731 mVisualModel->mLines,
735 // Set the line index from where to insert the new laid-out lines.
736 mTextUpdateInfo.mStartLineIndex = startRemoveIndex;
738 LineRun* linesBuffer = mVisualModel->mLines.Begin();
739 mVisualModel->mLines.Erase( linesBuffer + startRemoveIndex,
740 linesBuffer + endRemoveIndex );
743 if( NO_OPERATION != ( COLOR & operations ) )
745 if( 0u != mVisualModel->mColorIndices.Count() )
747 ColorIndex* colorIndexBuffer = mVisualModel->mColorIndices.Begin();
748 mVisualModel->mColorIndices.Erase( colorIndexBuffer + mTextUpdateInfo.mStartGlyphIndex,
749 colorIndexBuffer + endGlyphIndexPlusOne );
754 void Controller::Impl::ClearModelData( CharacterIndex startIndex, CharacterIndex endIndex, OperationsMask operations )
756 if( mTextUpdateInfo.mClearAll ||
757 ( ( 0u == startIndex ) &&
758 ( mTextUpdateInfo.mPreviousNumberOfCharacters == endIndex + 1u ) ) )
760 ClearFullModelData( operations );
764 // Clear the model data related with characters.
765 ClearCharacterModelData( startIndex, endIndex, operations );
767 // Clear the model data related with glyphs.
768 ClearGlyphModelData( startIndex, endIndex, operations );
771 // The estimated number of lines. Used to avoid reallocations when layouting.
772 mTextUpdateInfo.mEstimatedNumberOfLines = std::max( mVisualModel->mLines.Count(), mLogicalModel->mParagraphInfo.Count() );
774 mVisualModel->ClearCaches();
777 bool Controller::Impl::UpdateModel( OperationsMask operationsRequired )
779 DALI_LOG_INFO( gLogFilter, Debug::General, "Controller::UpdateModel\n" );
781 // Calculate the operations to be done.
782 const OperationsMask operations = static_cast<OperationsMask>( mOperationsPending & operationsRequired );
784 if( NO_OPERATION == operations )
786 // Nothing to do if no operations are pending and required.
790 Vector<Character>& utf32Characters = mLogicalModel->mText;
792 const Length numberOfCharacters = utf32Characters.Count();
794 // Index to the first character of the first paragraph to be updated.
795 CharacterIndex startIndex = 0u;
796 // Number of characters of the paragraphs to be removed.
797 Length paragraphCharacters = 0u;
799 CalculateTextUpdateIndices( paragraphCharacters );
800 startIndex = mTextUpdateInfo.mParagraphCharacterIndex;
802 if( mTextUpdateInfo.mClearAll ||
803 ( 0u != paragraphCharacters ) )
805 ClearModelData( startIndex, startIndex + ( ( paragraphCharacters > 0u ) ? paragraphCharacters - 1u : 0u ), operations );
808 mTextUpdateInfo.mClearAll = false;
810 // Whether the model is updated.
811 bool updated = false;
813 Vector<LineBreakInfo>& lineBreakInfo = mLogicalModel->mLineBreakInfo;
814 const Length requestedNumberOfCharacters = mTextUpdateInfo.mRequestedNumberOfCharacters;
816 if( NO_OPERATION != ( GET_LINE_BREAKS & operations ) )
818 // Retrieves the line break info. The line break info is used to split the text in 'paragraphs' to
819 // calculate the bidirectional info for each 'paragraph'.
820 // It's also used to layout the text (where it should be a new line) or to shape the text (text in different lines
821 // is not shaped together).
822 lineBreakInfo.Resize( numberOfCharacters, TextAbstraction::LINE_NO_BREAK );
824 SetLineBreakInfo( utf32Characters,
826 requestedNumberOfCharacters,
829 // Create the paragraph info.
830 mLogicalModel->CreateParagraphInfo( startIndex,
831 requestedNumberOfCharacters );
835 Vector<WordBreakInfo>& wordBreakInfo = mLogicalModel->mWordBreakInfo;
836 if( NO_OPERATION != ( GET_WORD_BREAKS & operations ) )
838 // Retrieves the word break info. The word break info is used to layout the text (where to wrap the text in lines).
839 wordBreakInfo.Resize( numberOfCharacters, TextAbstraction::WORD_NO_BREAK );
841 SetWordBreakInfo( utf32Characters,
843 requestedNumberOfCharacters,
848 const bool getScripts = NO_OPERATION != ( GET_SCRIPTS & operations );
849 const bool validateFonts = NO_OPERATION != ( VALIDATE_FONTS & operations );
851 Vector<ScriptRun>& scripts = mLogicalModel->mScriptRuns;
852 Vector<FontRun>& validFonts = mLogicalModel->mFontRuns;
854 if( getScripts || validateFonts )
856 // Validates the fonts assigned by the application or assigns default ones.
857 // It makes sure all the characters are going to be rendered by the correct font.
858 MultilanguageSupport multilanguageSupport = MultilanguageSupport::Get();
862 // Retrieves the scripts used in the text.
863 multilanguageSupport.SetScripts( utf32Characters,
865 requestedNumberOfCharacters,
871 // Validate the fonts set through the mark-up string.
872 Vector<FontDescriptionRun>& fontDescriptionRuns = mLogicalModel->mFontDescriptionRuns;
874 // Get the default font's description.
875 TextAbstraction::FontDescription defaultFontDescription;
876 TextAbstraction::PointSize26Dot6 defaultPointSize = TextAbstraction::FontClient::DEFAULT_POINT_SIZE;
877 if( NULL != mFontDefaults )
879 defaultFontDescription = mFontDefaults->mFontDescription;
880 defaultPointSize = mFontDefaults->mDefaultPointSize * 64u;
883 // Validates the fonts. If there is a character with no assigned font it sets a default one.
884 // After this call, fonts are validated.
885 multilanguageSupport.ValidateFonts( utf32Characters,
888 defaultFontDescription,
891 requestedNumberOfCharacters,
897 Vector<Character> mirroredUtf32Characters;
898 bool textMirrored = false;
899 const Length numberOfParagraphs = mLogicalModel->mParagraphInfo.Count();
900 if( NO_OPERATION != ( BIDI_INFO & operations ) )
902 Vector<BidirectionalParagraphInfoRun>& bidirectionalInfo = mLogicalModel->mBidirectionalParagraphInfo;
903 bidirectionalInfo.Reserve( numberOfParagraphs );
905 // Calculates the bidirectional info for the whole paragraph if it contains right to left scripts.
906 SetBidirectionalInfo( utf32Characters,
910 requestedNumberOfCharacters,
913 if( 0u != bidirectionalInfo.Count() )
915 // Only set the character directions if there is right to left characters.
916 Vector<CharacterDirection>& directions = mLogicalModel->mCharacterDirections;
917 GetCharactersDirection( bidirectionalInfo,
920 requestedNumberOfCharacters,
923 // This paragraph has right to left text. Some characters may need to be mirrored.
924 // TODO: consider if the mirrored string can be stored as well.
926 textMirrored = GetMirroredText( utf32Characters,
930 requestedNumberOfCharacters,
931 mirroredUtf32Characters );
935 // There is no right to left characters. Clear the directions vector.
936 mLogicalModel->mCharacterDirections.Clear();
941 Vector<GlyphInfo>& glyphs = mVisualModel->mGlyphs;
942 Vector<CharacterIndex>& glyphsToCharactersMap = mVisualModel->mGlyphsToCharacters;
943 Vector<Length>& charactersPerGlyph = mVisualModel->mCharactersPerGlyph;
944 Vector<GlyphIndex> newParagraphGlyphs;
945 newParagraphGlyphs.Reserve( numberOfParagraphs );
947 const Length currentNumberOfGlyphs = glyphs.Count();
948 if( NO_OPERATION != ( SHAPE_TEXT & operations ) )
950 const Vector<Character>& textToShape = textMirrored ? mirroredUtf32Characters : utf32Characters;
952 ShapeText( textToShape,
957 mTextUpdateInfo.mStartGlyphIndex,
958 requestedNumberOfCharacters,
960 glyphsToCharactersMap,
962 newParagraphGlyphs );
964 // Create the 'number of glyphs' per character and the glyph to character conversion tables.
965 mVisualModel->CreateGlyphsPerCharacterTable( startIndex, mTextUpdateInfo.mStartGlyphIndex, requestedNumberOfCharacters );
966 mVisualModel->CreateCharacterToGlyphTable( startIndex, mTextUpdateInfo.mStartGlyphIndex, requestedNumberOfCharacters );
970 const Length numberOfGlyphs = glyphs.Count() - currentNumberOfGlyphs;
972 if( NO_OPERATION != ( GET_GLYPH_METRICS & operations ) )
974 GlyphInfo* glyphsBuffer = glyphs.Begin();
975 mMetrics->GetGlyphMetrics( glyphsBuffer + mTextUpdateInfo.mStartGlyphIndex, numberOfGlyphs );
977 // Update the width and advance of all new paragraph characters.
978 for( Vector<GlyphIndex>::ConstIterator it = newParagraphGlyphs.Begin(), endIt = newParagraphGlyphs.End(); it != endIt; ++it )
980 const GlyphIndex index = *it;
981 GlyphInfo& glyph = *( glyphsBuffer + index );
983 glyph.xBearing = 0.f;
990 if( NO_OPERATION != ( COLOR & operations ) )
992 // Set the color runs in glyphs.
993 SetColorSegmentationInfo( mLogicalModel->mColorRuns,
994 mVisualModel->mCharactersToGlyph,
995 mVisualModel->mGlyphsPerCharacter,
997 mTextUpdateInfo.mStartGlyphIndex,
998 requestedNumberOfCharacters,
999 mVisualModel->mColors,
1000 mVisualModel->mColorIndices );
1005 if( ( NULL != mEventData ) &&
1006 mEventData->mPreEditFlag &&
1007 ( 0u != mVisualModel->mCharactersToGlyph.Count() ) )
1009 // Add the underline for the pre-edit text.
1010 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
1011 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
1013 const GlyphIndex glyphStart = *( charactersToGlyphBuffer + mEventData->mPreEditStartPosition );
1014 const CharacterIndex lastPreEditCharacter = mEventData->mPreEditStartPosition + ( ( mEventData->mPreEditLength > 0u ) ? mEventData->mPreEditLength - 1u : 0u );
1015 const Length numberOfGlyphsLastCharacter = *( glyphsPerCharacterBuffer + lastPreEditCharacter );
1016 const GlyphIndex glyphEnd = *( charactersToGlyphBuffer + lastPreEditCharacter ) + ( numberOfGlyphsLastCharacter > 1u ? numberOfGlyphsLastCharacter - 1u : 0u );
1018 GlyphRun underlineRun;
1019 underlineRun.glyphIndex = glyphStart;
1020 underlineRun.numberOfGlyphs = 1u + glyphEnd - glyphStart;
1022 // TODO: At the moment the underline runs are only for pre-edit.
1023 mVisualModel->mUnderlineRuns.PushBack( underlineRun );
1026 // The estimated number of lines. Used to avoid reallocations when layouting.
1027 mTextUpdateInfo.mEstimatedNumberOfLines = std::max( mVisualModel->mLines.Count(), mLogicalModel->mParagraphInfo.Count() );
1029 // Set the previous number of characters for the next time the text is updated.
1030 mTextUpdateInfo.mPreviousNumberOfCharacters = numberOfCharacters;
1035 void Controller::Impl::RetrieveDefaultInputStyle( InputStyle& inputStyle )
1037 // Sets the default text's color.
1038 inputStyle.textColor = mTextColor;
1039 inputStyle.isDefaultColor = true;
1041 inputStyle.familyName.clear();
1042 inputStyle.weight = TextAbstraction::FontWeight::NORMAL;
1043 inputStyle.width = TextAbstraction::FontWidth::NORMAL;
1044 inputStyle.slant = TextAbstraction::FontSlant::NORMAL;
1045 inputStyle.size = 0.f;
1047 inputStyle.lineSpacing = 0.f;
1049 inputStyle.underlineProperties.clear();
1050 inputStyle.shadowProperties.clear();
1051 inputStyle.embossProperties.clear();
1052 inputStyle.outlineProperties.clear();
1054 inputStyle.isFamilyDefined = false;
1055 inputStyle.isWeightDefined = false;
1056 inputStyle.isWidthDefined = false;
1057 inputStyle.isSlantDefined = false;
1058 inputStyle.isSizeDefined = false;
1060 inputStyle.isLineSpacingDefined = false;
1062 inputStyle.isUnderlineDefined = false;
1063 inputStyle.isShadowDefined = false;
1064 inputStyle.isEmbossDefined = false;
1065 inputStyle.isOutlineDefined = false;
1067 // Sets the default font's family name, weight, width, slant and size.
1070 if( mFontDefaults->familyDefined )
1072 inputStyle.familyName = mFontDefaults->mFontDescription.family;
1073 inputStyle.isFamilyDefined = true;
1076 if( mFontDefaults->weightDefined )
1078 inputStyle.weight = mFontDefaults->mFontDescription.weight;
1079 inputStyle.isWeightDefined = true;
1082 if( mFontDefaults->widthDefined )
1084 inputStyle.width = mFontDefaults->mFontDescription.width;
1085 inputStyle.isWidthDefined = true;
1088 if( mFontDefaults->slantDefined )
1090 inputStyle.slant = mFontDefaults->mFontDescription.slant;
1091 inputStyle.isSlantDefined = true;
1094 if( mFontDefaults->sizeDefined )
1096 inputStyle.size = mFontDefaults->mDefaultPointSize;
1097 inputStyle.isSizeDefined = true;
1102 float Controller::Impl::GetDefaultFontLineHeight()
1104 FontId defaultFontId = 0u;
1105 if( NULL == mFontDefaults )
1107 TextAbstraction::FontDescription fontDescription;
1108 defaultFontId = mFontClient.GetFontId( fontDescription );
1112 defaultFontId = mFontDefaults->GetFontId( mFontClient );
1115 Text::FontMetrics fontMetrics;
1116 mMetrics->GetFontMetrics( defaultFontId, fontMetrics );
1118 return( fontMetrics.ascender - fontMetrics.descender );
1121 void Controller::Impl::OnCursorKeyEvent( const Event& event )
1123 if( NULL == mEventData )
1125 // Nothing to do if there is no text input.
1129 int keyCode = event.p1.mInt;
1131 if( Dali::DALI_KEY_CURSOR_LEFT == keyCode )
1133 if( mEventData->mPrimaryCursorPosition > 0u )
1135 mEventData->mPrimaryCursorPosition = CalculateNewCursorIndex( mEventData->mPrimaryCursorPosition - 1u );
1138 else if( Dali::DALI_KEY_CURSOR_RIGHT == keyCode )
1140 if( mLogicalModel->mText.Count() > mEventData->mPrimaryCursorPosition )
1142 mEventData->mPrimaryCursorPosition = CalculateNewCursorIndex( mEventData->mPrimaryCursorPosition );
1145 else if( Dali::DALI_KEY_CURSOR_UP == keyCode )
1147 // Get first the line index of the current cursor position index.
1148 CharacterIndex characterIndex = 0u;
1150 if( mEventData->mPrimaryCursorPosition > 0u )
1152 characterIndex = mEventData->mPrimaryCursorPosition - 1u;
1155 const LineIndex lineIndex = mVisualModel->GetLineOfCharacter( characterIndex );
1157 if( lineIndex > 0u )
1159 // Retrieve the cursor position info.
1160 CursorInfo cursorInfo;
1161 GetCursorPosition( mEventData->mPrimaryCursorPosition,
1164 // Get the line above.
1165 const LineRun& line = *( mVisualModel->mLines.Begin() + ( lineIndex - 1u ) );
1167 // Get the next hit 'y' point.
1168 const float hitPointY = cursorInfo.lineOffset - 0.5f * ( line.ascender - line.descender );
1170 // Use the cursor hook position 'x' and the next hit 'y' position to calculate the new cursor index.
1171 bool matchedCharacter = false;
1172 mEventData->mPrimaryCursorPosition = Text::GetClosestCursorIndex( mVisualModel,
1175 mEventData->mCursorHookPositionX,
1177 CharacterHitTest::TAP,
1181 else if( Dali::DALI_KEY_CURSOR_DOWN == keyCode )
1183 // Get first the line index of the current cursor position index.
1184 CharacterIndex characterIndex = 0u;
1186 if( mEventData->mPrimaryCursorPosition > 0u )
1188 characterIndex = mEventData->mPrimaryCursorPosition - 1u;
1191 const LineIndex lineIndex = mVisualModel->GetLineOfCharacter( characterIndex );
1193 if( lineIndex + 1u < mVisualModel->mLines.Count() )
1195 // Retrieve the cursor position info.
1196 CursorInfo cursorInfo;
1197 GetCursorPosition( mEventData->mPrimaryCursorPosition,
1200 // Get the line below.
1201 const LineRun& line = *( mVisualModel->mLines.Begin() + lineIndex + 1u );
1203 // Get the next hit 'y' point.
1204 const float hitPointY = cursorInfo.lineOffset + cursorInfo.lineHeight + 0.5f * ( line.ascender - line.descender );
1206 // Use the cursor hook position 'x' and the next hit 'y' position to calculate the new cursor index.
1207 bool matchedCharacter = false;
1208 mEventData->mPrimaryCursorPosition = Text::GetClosestCursorIndex( mVisualModel,
1211 mEventData->mCursorHookPositionX,
1213 CharacterHitTest::TAP,
1218 mEventData->mUpdateCursorPosition = true;
1219 mEventData->mUpdateInputStyle = true;
1220 mEventData->mScrollAfterUpdatePosition = true;
1223 void Controller::Impl::OnTapEvent( const Event& event )
1225 if( NULL != mEventData )
1227 const unsigned int tapCount = event.p1.mUint;
1229 if( 1u == tapCount )
1231 if( IsShowingRealText() )
1233 // Convert from control's coords to text's coords.
1234 const float xPosition = event.p2.mFloat - mScrollPosition.x;
1235 const float yPosition = event.p3.mFloat - mScrollPosition.y;
1237 // Keep the tap 'x' position. Used to move the cursor.
1238 mEventData->mCursorHookPositionX = xPosition;
1240 // Whether to touch point hits on a glyph.
1241 bool matchedCharacter = false;
1242 mEventData->mPrimaryCursorPosition = Text::GetClosestCursorIndex( mVisualModel,
1247 CharacterHitTest::TAP,
1250 // When the cursor position is changing, delay cursor blinking
1251 mEventData->mDecorator->DelayCursorBlink();
1255 mEventData->mPrimaryCursorPosition = 0u;
1258 mEventData->mUpdateCursorPosition = true;
1259 mEventData->mUpdateGrabHandlePosition = true;
1260 mEventData->mScrollAfterUpdatePosition = true;
1261 mEventData->mUpdateInputStyle = true;
1263 // Notify the cursor position to the imf manager.
1264 if( mEventData->mImfManager )
1266 mEventData->mImfManager.SetCursorPosition( mEventData->mPrimaryCursorPosition );
1267 mEventData->mImfManager.NotifyCursorPosition();
1270 else if( 2u == tapCount )
1272 if( mEventData->mSelectionEnabled )
1274 // Convert from control's coords to text's coords.
1275 const float xPosition = event.p2.mFloat - mScrollPosition.x;
1276 const float yPosition = event.p3.mFloat - mScrollPosition.y;
1278 // Calculates the logical position from the x,y coords.
1279 RepositionSelectionHandles( xPosition,
1281 mEventData->mDoubleTapAction );
1287 void Controller::Impl::OnPanEvent( const Event& event )
1289 if( NULL == mEventData )
1291 // Nothing to do if there is no text input.
1295 const bool isHorizontalScrollEnabled = mEventData->mDecorator->IsHorizontalScrollEnabled();
1296 const bool isVerticalScrollEnabled = mEventData->mDecorator->IsVerticalScrollEnabled();
1298 if( !isHorizontalScrollEnabled && !isVerticalScrollEnabled )
1300 // Nothing to do if scrolling is not enabled.
1304 const int state = event.p1.mInt;
1308 case Gesture::Started:
1310 // Will remove the cursor, handles or text's popup, ...
1311 ChangeState( EventData::TEXT_PANNING );
1314 case Gesture::Continuing:
1316 const Vector2& layoutSize = mVisualModel->GetLayoutSize();
1317 const Vector2 currentScroll = mScrollPosition;
1319 if( isHorizontalScrollEnabled )
1321 const float displacementX = event.p2.mFloat;
1322 mScrollPosition.x += displacementX;
1324 ClampHorizontalScroll( layoutSize );
1327 if( isVerticalScrollEnabled )
1329 const float displacementY = event.p3.mFloat;
1330 mScrollPosition.y += displacementY;
1332 ClampVerticalScroll( layoutSize );
1335 mEventData->mDecorator->UpdatePositions( mScrollPosition - currentScroll );
1338 case Gesture::Finished:
1339 case Gesture::Cancelled: // FALLTHROUGH
1341 // Will go back to the previous state to show the cursor, handles, the text's popup, ...
1342 ChangeState( mEventData->mPreviousState );
1350 void Controller::Impl::OnLongPressEvent( const Event& event )
1352 DALI_LOG_INFO( gLogFilter, Debug::General, "Controller::OnLongPressEvent\n" );
1354 if( !IsShowingRealText() && ( EventData::EDITING == mEventData->mState ) )
1356 ChangeState( EventData::EDITING_WITH_POPUP );
1357 mEventData->mDecoratorUpdated = true;
1358 mEventData->mUpdateInputStyle = true;
1362 if( mEventData->mSelectionEnabled )
1364 // Convert from control's coords to text's coords.
1365 const float xPosition = event.p2.mFloat - mScrollPosition.x;
1366 const float yPosition = event.p3.mFloat - mScrollPosition.y;
1368 // Calculates the logical position from the x,y coords.
1369 RepositionSelectionHandles( xPosition,
1371 mEventData->mLongPressAction );
1376 void Controller::Impl::OnHandleEvent( const Event& event )
1378 if( NULL == mEventData )
1380 // Nothing to do if there is no text input.
1384 const unsigned int state = event.p1.mUint;
1385 const bool handleStopScrolling = ( HANDLE_STOP_SCROLLING == state );
1386 const bool isSmoothHandlePanEnabled = mEventData->mDecorator->IsSmoothHandlePanEnabled();
1388 if( HANDLE_PRESSED == state )
1390 // Convert from decorator's coords to text's coords.
1391 const float xPosition = event.p2.mFloat - mScrollPosition.x;
1392 const float yPosition = event.p3.mFloat - mScrollPosition.y;
1394 // Need to calculate the handle's new position.
1395 bool matchedCharacter = false;
1396 const CharacterIndex handleNewPosition = Text::GetClosestCursorIndex( mVisualModel,
1401 CharacterHitTest::SCROLL,
1404 if( Event::GRAB_HANDLE_EVENT == event.type )
1406 ChangeState ( EventData::GRAB_HANDLE_PANNING );
1408 if( handleNewPosition != mEventData->mPrimaryCursorPosition )
1410 // Updates the cursor position if the handle's new position is different than the current one.
1411 mEventData->mUpdateCursorPosition = true;
1412 // Does not update the grab handle position if the smooth panning is enabled. (The decorator does it smooth).
1413 mEventData->mUpdateGrabHandlePosition = !isSmoothHandlePanEnabled;
1414 mEventData->mPrimaryCursorPosition = handleNewPosition;
1417 // 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.
1418 mEventData->mDecoratorUpdated = isSmoothHandlePanEnabled;
1420 else if( Event::LEFT_SELECTION_HANDLE_EVENT == event.type )
1422 ChangeState ( EventData::SELECTION_HANDLE_PANNING );
1424 if( ( handleNewPosition != mEventData->mLeftSelectionPosition ) &&
1425 ( handleNewPosition != mEventData->mRightSelectionPosition ) )
1427 // Updates the highlight box if the handle's new position is different than the current one.
1428 mEventData->mUpdateHighlightBox = true;
1429 // Does not update the selection handle position if the smooth panning is enabled. (The decorator does it smooth).
1430 mEventData->mUpdateLeftSelectionPosition = !isSmoothHandlePanEnabled;
1431 mEventData->mLeftSelectionPosition = handleNewPosition;
1434 // 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.
1435 mEventData->mDecoratorUpdated = isSmoothHandlePanEnabled;
1437 // Will define the order to scroll the text to match the handle position.
1438 mEventData->mIsLeftHandleSelected = true;
1439 mEventData->mIsRightHandleSelected = false;
1441 else if( Event::RIGHT_SELECTION_HANDLE_EVENT == event.type )
1443 ChangeState ( EventData::SELECTION_HANDLE_PANNING );
1445 if( ( handleNewPosition != mEventData->mRightSelectionPosition ) &&
1446 ( handleNewPosition != mEventData->mLeftSelectionPosition ) )
1448 // Updates the highlight box if the handle's new position is different than the current one.
1449 mEventData->mUpdateHighlightBox = true;
1450 // Does not update the selection handle position if the smooth panning is enabled. (The decorator does it smooth).
1451 mEventData->mUpdateRightSelectionPosition = !isSmoothHandlePanEnabled;
1452 mEventData->mRightSelectionPosition = handleNewPosition;
1455 // 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.
1456 mEventData->mDecoratorUpdated = isSmoothHandlePanEnabled;
1458 // Will define the order to scroll the text to match the handle position.
1459 mEventData->mIsLeftHandleSelected = false;
1460 mEventData->mIsRightHandleSelected = true;
1462 } // end ( HANDLE_PRESSED == state )
1463 else if( ( HANDLE_RELEASED == state ) ||
1464 handleStopScrolling )
1466 CharacterIndex handlePosition = 0u;
1467 if( handleStopScrolling || isSmoothHandlePanEnabled )
1469 // Convert from decorator's coords to text's coords.
1470 const float xPosition = event.p2.mFloat - mScrollPosition.x;
1471 const float yPosition = event.p3.mFloat - mScrollPosition.y;
1473 bool matchedCharacter = false;
1474 handlePosition = Text::GetClosestCursorIndex( mVisualModel,
1479 CharacterHitTest::SCROLL,
1483 if( Event::GRAB_HANDLE_EVENT == event.type )
1485 mEventData->mUpdateCursorPosition = true;
1486 mEventData->mUpdateGrabHandlePosition = true;
1487 mEventData->mUpdateInputStyle = true;
1489 if( !IsClipboardEmpty() )
1491 ChangeState( EventData::EDITING_WITH_PASTE_POPUP ); // Moving grabhandle will show Paste Popup
1494 if( handleStopScrolling || isSmoothHandlePanEnabled )
1496 mEventData->mScrollAfterUpdatePosition = true;
1497 mEventData->mPrimaryCursorPosition = handlePosition;
1500 else if( Event::LEFT_SELECTION_HANDLE_EVENT == event.type )
1502 ChangeState( EventData::SELECTING );
1504 mEventData->mUpdateHighlightBox = true;
1505 mEventData->mUpdateLeftSelectionPosition = true;
1506 mEventData->mUpdateRightSelectionPosition = true;
1508 if( handleStopScrolling || isSmoothHandlePanEnabled )
1510 mEventData->mScrollAfterUpdatePosition = true;
1512 if( ( handlePosition != mEventData->mRightSelectionPosition ) &&
1513 ( handlePosition != mEventData->mLeftSelectionPosition ) )
1515 mEventData->mLeftSelectionPosition = handlePosition;
1519 else if( Event::RIGHT_SELECTION_HANDLE_EVENT == event.type )
1521 ChangeState( EventData::SELECTING );
1523 mEventData->mUpdateHighlightBox = true;
1524 mEventData->mUpdateRightSelectionPosition = true;
1525 mEventData->mUpdateLeftSelectionPosition = true;
1527 if( handleStopScrolling || isSmoothHandlePanEnabled )
1529 mEventData->mScrollAfterUpdatePosition = true;
1530 if( ( handlePosition != mEventData->mRightSelectionPosition ) &&
1531 ( handlePosition != mEventData->mLeftSelectionPosition ) )
1533 mEventData->mRightSelectionPosition = handlePosition;
1538 mEventData->mDecoratorUpdated = true;
1539 } // end ( ( HANDLE_RELEASED == state ) || ( HANDLE_STOP_SCROLLING == state ) )
1540 else if( HANDLE_SCROLLING == state )
1542 const float xSpeed = event.p2.mFloat;
1543 const float ySpeed = event.p3.mFloat;
1544 const Vector2& layoutSize = mVisualModel->GetLayoutSize();
1545 const Vector2 currentScrollPosition = mScrollPosition;
1547 mScrollPosition.x += xSpeed;
1548 mScrollPosition.y += ySpeed;
1550 ClampHorizontalScroll( layoutSize );
1551 ClampVerticalScroll( layoutSize );
1553 bool endOfScroll = false;
1554 if( Vector2::ZERO == ( currentScrollPosition - mScrollPosition ) )
1556 // Notify the decorator there is no more text to scroll.
1557 // The decorator won't send more scroll events.
1558 mEventData->mDecorator->NotifyEndOfScroll();
1559 // Still need to set the position of the handle.
1563 // Set the position of the handle.
1564 const bool scrollRightDirection = xSpeed > 0.f;
1565 const bool scrollBottomDirection = ySpeed > 0.f;
1566 const bool leftSelectionHandleEvent = Event::LEFT_SELECTION_HANDLE_EVENT == event.type;
1567 const bool rightSelectionHandleEvent = Event::RIGHT_SELECTION_HANDLE_EVENT == event.type;
1569 if( Event::GRAB_HANDLE_EVENT == event.type )
1571 ChangeState( EventData::GRAB_HANDLE_PANNING );
1573 // Get the grab handle position in decorator coords.
1574 Vector2 position = mEventData->mDecorator->GetPosition( GRAB_HANDLE );
1576 if( mEventData->mDecorator->IsHorizontalScrollEnabled() )
1578 // Position the grag handle close to either the left or right edge.
1579 position.x = scrollRightDirection ? 0.f : mVisualModel->mControlSize.width;
1582 if( mEventData->mDecorator->IsVerticalScrollEnabled() )
1584 position.x = mEventData->mCursorHookPositionX;
1586 // Position the grag handle close to either the top or bottom edge.
1587 position.y = scrollBottomDirection ? 0.f : mVisualModel->mControlSize.height;
1590 // Get the new handle position.
1591 // The grab handle's position is in decorator's coords. Need to transforms to text's coords.
1592 bool matchedCharacter = false;
1593 const CharacterIndex handlePosition = Text::GetClosestCursorIndex( mVisualModel,
1596 position.x - mScrollPosition.x,
1597 position.y - mScrollPosition.y,
1598 CharacterHitTest::SCROLL,
1601 if( mEventData->mPrimaryCursorPosition != handlePosition )
1603 mEventData->mUpdateCursorPosition = true;
1604 mEventData->mUpdateGrabHandlePosition = !isSmoothHandlePanEnabled;
1605 mEventData->mScrollAfterUpdatePosition = true;
1606 mEventData->mPrimaryCursorPosition = handlePosition;
1608 mEventData->mUpdateInputStyle = mEventData->mUpdateCursorPosition;
1610 // Updates the decorator if the soft handle panning is enabled.
1611 mEventData->mDecoratorUpdated = isSmoothHandlePanEnabled;
1613 else if( leftSelectionHandleEvent || rightSelectionHandleEvent )
1615 ChangeState( EventData::SELECTION_HANDLE_PANNING );
1617 // Get the selection handle position in decorator coords.
1618 Vector2 position = mEventData->mDecorator->GetPosition( leftSelectionHandleEvent ? Text::LEFT_SELECTION_HANDLE : Text::RIGHT_SELECTION_HANDLE );
1620 if( mEventData->mDecorator->IsHorizontalScrollEnabled() )
1622 // Position the selection handle close to either the left or right edge.
1623 position.x = scrollRightDirection ? 0.f : mVisualModel->mControlSize.width;
1626 if( mEventData->mDecorator->IsVerticalScrollEnabled() )
1628 position.x = mEventData->mCursorHookPositionX;
1630 // Position the grag handle close to either the top or bottom edge.
1631 position.y = scrollBottomDirection ? 0.f : mVisualModel->mControlSize.height;
1634 // Get the new handle position.
1635 // The selection handle's position is in decorator's coords. Need to transform to text's coords.
1636 bool matchedCharacter = false;
1637 const CharacterIndex handlePosition = Text::GetClosestCursorIndex( mVisualModel,
1640 position.x - mScrollPosition.x,
1641 position.y - mScrollPosition.y,
1642 CharacterHitTest::SCROLL,
1645 if( leftSelectionHandleEvent )
1647 const bool differentHandles = ( mEventData->mLeftSelectionPosition != handlePosition ) && ( mEventData->mRightSelectionPosition != handlePosition );
1649 if( differentHandles || endOfScroll )
1651 mEventData->mUpdateHighlightBox = true;
1652 mEventData->mUpdateLeftSelectionPosition = !isSmoothHandlePanEnabled;
1653 mEventData->mUpdateRightSelectionPosition = isSmoothHandlePanEnabled;
1654 mEventData->mLeftSelectionPosition = handlePosition;
1659 const bool differentHandles = ( mEventData->mRightSelectionPosition != handlePosition ) && ( mEventData->mLeftSelectionPosition != handlePosition );
1660 if( differentHandles || endOfScroll )
1662 mEventData->mUpdateHighlightBox = true;
1663 mEventData->mUpdateRightSelectionPosition = !isSmoothHandlePanEnabled;
1664 mEventData->mUpdateLeftSelectionPosition = isSmoothHandlePanEnabled;
1665 mEventData->mRightSelectionPosition = handlePosition;
1669 if( mEventData->mUpdateLeftSelectionPosition || mEventData->mUpdateRightSelectionPosition )
1671 RepositionSelectionHandles();
1673 mEventData->mScrollAfterUpdatePosition = !isSmoothHandlePanEnabled;
1676 mEventData->mDecoratorUpdated = true;
1677 } // end ( HANDLE_SCROLLING == state )
1680 void Controller::Impl::OnSelectEvent( const Event& event )
1682 if( NULL == mEventData )
1684 // Nothing to do if there is no text.
1688 if( mEventData->mSelectionEnabled )
1690 // Convert from control's coords to text's coords.
1691 const float xPosition = event.p2.mFloat - mScrollPosition.x;
1692 const float yPosition = event.p3.mFloat - mScrollPosition.y;
1694 // Calculates the logical position from the x,y coords.
1695 RepositionSelectionHandles( xPosition,
1697 Controller::NoTextTap::HIGHLIGHT );
1701 void Controller::Impl::OnSelectAllEvent()
1703 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "OnSelectAllEvent mEventData->mSelectionEnabled%s \n", mEventData->mSelectionEnabled?"true":"false");
1705 if( NULL == mEventData )
1707 // Nothing to do if there is no text.
1711 if( mEventData->mSelectionEnabled )
1713 ChangeState( EventData::SELECTING );
1715 mEventData->mLeftSelectionPosition = 0u;
1716 mEventData->mRightSelectionPosition = mLogicalModel->mText.Count();
1718 mEventData->mScrollAfterUpdatePosition = true;
1719 mEventData->mUpdateLeftSelectionPosition = true;
1720 mEventData->mUpdateRightSelectionPosition = true;
1721 mEventData->mUpdateHighlightBox = true;
1725 void Controller::Impl::RetrieveSelection( std::string& selectedText, bool deleteAfterRetrieval )
1727 if( mEventData->mLeftSelectionPosition == mEventData->mRightSelectionPosition )
1729 // Nothing to select if handles are in the same place.
1730 selectedText.clear();
1734 const bool handlesCrossed = mEventData->mLeftSelectionPosition > mEventData->mRightSelectionPosition;
1736 //Get start and end position of selection
1737 const CharacterIndex startOfSelectedText = handlesCrossed ? mEventData->mRightSelectionPosition : mEventData->mLeftSelectionPosition;
1738 const Length lengthOfSelectedText = ( handlesCrossed ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition ) - startOfSelectedText;
1740 Vector<Character>& utf32Characters = mLogicalModel->mText;
1741 const Length numberOfCharacters = utf32Characters.Count();
1743 // Validate the start and end selection points
1744 if( ( startOfSelectedText + lengthOfSelectedText ) <= numberOfCharacters )
1746 //Get text as a UTF8 string
1747 Utf32ToUtf8( &utf32Characters[startOfSelectedText], lengthOfSelectedText, selectedText );
1749 if( deleteAfterRetrieval ) // Only delete text if copied successfully
1751 // Keep a copy of the current input style.
1752 InputStyle currentInputStyle;
1753 currentInputStyle.Copy( mEventData->mInputStyle );
1755 // Set as input style the style of the first deleted character.
1756 mLogicalModel->RetrieveStyle( startOfSelectedText, mEventData->mInputStyle );
1758 // Compare if the input style has changed.
1759 const bool hasInputStyleChanged = !currentInputStyle.Equal( mEventData->mInputStyle );
1761 if( hasInputStyleChanged )
1763 const InputStyle::Mask styleChangedMask = currentInputStyle.GetInputStyleChangeMask( mEventData->mInputStyle );
1764 // Queue the input style changed signal.
1765 mEventData->mInputStyleChangedQueue.PushBack( styleChangedMask );
1768 mLogicalModel->UpdateTextStyleRuns( startOfSelectedText, -static_cast<int>( lengthOfSelectedText ) );
1770 // Mark the paragraphs to be updated.
1771 if( LayoutEngine::SINGLE_LINE_BOX == mLayoutEngine.GetLayout() )
1773 mTextUpdateInfo.mCharacterIndex = 0;
1774 mTextUpdateInfo.mNumberOfCharactersToRemove = mTextUpdateInfo.mPreviousNumberOfCharacters;
1775 mTextUpdateInfo.mNumberOfCharactersToAdd = mTextUpdateInfo.mPreviousNumberOfCharacters - lengthOfSelectedText;
1776 mTextUpdateInfo.mClearAll = true;
1780 mTextUpdateInfo.mCharacterIndex = startOfSelectedText;
1781 mTextUpdateInfo.mNumberOfCharactersToRemove = lengthOfSelectedText;
1784 // Delete text between handles
1785 Vector<Character>::Iterator first = utf32Characters.Begin() + startOfSelectedText;
1786 Vector<Character>::Iterator last = first + lengthOfSelectedText;
1787 utf32Characters.Erase( first, last );
1789 // Will show the cursor at the first character of the selection.
1790 mEventData->mPrimaryCursorPosition = handlesCrossed ? mEventData->mRightSelectionPosition : mEventData->mLeftSelectionPosition;
1794 // Will show the cursor at the last character of the selection.
1795 mEventData->mPrimaryCursorPosition = handlesCrossed ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition;
1798 mEventData->mDecoratorUpdated = true;
1802 void Controller::Impl::ShowClipboard()
1806 mClipboard.ShowClipboard();
1810 void Controller::Impl::HideClipboard()
1812 if( mClipboard && mClipboardHideEnabled )
1814 mClipboard.HideClipboard();
1818 void Controller::Impl::SetClipboardHideEnable(bool enable)
1820 mClipboardHideEnabled = enable;
1823 bool Controller::Impl::CopyStringToClipboard( std::string& source )
1825 //Send string to clipboard
1826 return ( mClipboard && mClipboard.SetItem( source ) );
1829 void Controller::Impl::SendSelectionToClipboard( bool deleteAfterSending )
1831 std::string selectedText;
1832 RetrieveSelection( selectedText, deleteAfterSending );
1833 CopyStringToClipboard( selectedText );
1834 ChangeState( EventData::EDITING );
1837 void Controller::Impl::RequestGetTextFromClipboard()
1841 mClipboard.RequestItem();
1845 void Controller::Impl::RepositionSelectionHandles()
1847 CharacterIndex selectionStart = mEventData->mLeftSelectionPosition;
1848 CharacterIndex selectionEnd = mEventData->mRightSelectionPosition;
1850 if( selectionStart == selectionEnd )
1852 // Nothing to select if handles are in the same place.
1856 mEventData->mDecorator->ClearHighlights();
1858 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
1859 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
1860 const GlyphInfo* const glyphsBuffer = mVisualModel->mGlyphs.Begin();
1861 const Vector2* const positionsBuffer = mVisualModel->mGlyphPositions.Begin();
1862 const Length* const charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
1863 const CharacterIndex* const glyphToCharacterBuffer = mVisualModel->mGlyphsToCharacters.Begin();
1864 const CharacterDirection* const modelCharacterDirectionsBuffer = ( 0u != mLogicalModel->mCharacterDirections.Count() ) ? mLogicalModel->mCharacterDirections.Begin() : NULL;
1866 const bool isLastCharacter = selectionEnd >= mLogicalModel->mText.Count();
1867 const CharacterDirection startDirection = ( ( NULL == modelCharacterDirectionsBuffer ) ? false : *( modelCharacterDirectionsBuffer + selectionStart ) );
1868 const CharacterDirection endDirection = ( ( NULL == modelCharacterDirectionsBuffer ) ? false : *( modelCharacterDirectionsBuffer + ( selectionEnd - ( isLastCharacter ? 1u : 0u ) ) ) );
1870 // Swap the indices if the start is greater than the end.
1871 const bool indicesSwapped = selectionStart > selectionEnd;
1873 // Tell the decorator to flip the selection handles if needed.
1874 mEventData->mDecorator->SetSelectionHandleFlipState( indicesSwapped, startDirection, endDirection );
1876 if( indicesSwapped )
1878 std::swap( selectionStart, selectionEnd );
1881 // Get the indices to the first and last selected glyphs.
1882 const CharacterIndex selectionEndMinusOne = selectionEnd - 1u;
1883 const GlyphIndex glyphStart = *( charactersToGlyphBuffer + selectionStart );
1884 const Length numberOfGlyphs = *( glyphsPerCharacterBuffer + selectionEndMinusOne );
1885 const GlyphIndex glyphEnd = *( charactersToGlyphBuffer + selectionEndMinusOne ) + ( ( numberOfGlyphs > 0 ) ? numberOfGlyphs - 1u : 0u );
1887 // Get the lines where the glyphs are laid-out.
1888 const LineRun* lineRun = mVisualModel->mLines.Begin();
1890 LineIndex lineIndex = 0u;
1891 Length numberOfLines = 0u;
1892 mVisualModel->GetNumberOfLines( glyphStart,
1893 1u + glyphEnd - glyphStart,
1896 const LineIndex firstLineIndex = lineIndex;
1898 // Create the structure to store some selection box info.
1899 Vector<SelectionBoxInfo> selectionBoxLinesInfo;
1900 selectionBoxLinesInfo.Resize( numberOfLines );
1902 SelectionBoxInfo* selectionBoxInfo = selectionBoxLinesInfo.Begin();
1903 selectionBoxInfo->minX = MAX_FLOAT;
1904 selectionBoxInfo->maxX = MIN_FLOAT;
1906 // Keep the min and max 'x' position to calculate the size and position of the highlighed text.
1907 float minHighlightX = std::numeric_limits<float>::max();
1908 float maxHighlightX = std::numeric_limits<float>::min();
1910 Vector2 highLightPosition; // The highlight position in decorator's coords.
1912 // Retrieve the first line and get the line's vertical offset, the line's height and the index to the last glyph.
1914 // The line's vertical offset of all the lines before the line where the first glyph is laid-out.
1915 selectionBoxInfo->lineOffset = CalculateLineOffset( mVisualModel->mLines,
1918 // Transform to decorator's (control) coords.
1919 selectionBoxInfo->lineOffset += mScrollPosition.y;
1921 lineRun += firstLineIndex;
1923 // The line height is the addition of the line ascender and the line descender.
1924 // However, the line descender has a negative value, hence the subtraction.
1925 selectionBoxInfo->lineHeight = lineRun->ascender - lineRun->descender;
1927 GlyphIndex lastGlyphOfLine = lineRun->glyphRun.glyphIndex + lineRun->glyphRun.numberOfGlyphs - 1u;
1929 // Check if the first glyph is a ligature that must be broken like Latin ff, fi, or Arabic ï»», etc which needs special code.
1930 const Length numberOfCharactersStart = *( charactersPerGlyphBuffer + glyphStart );
1931 bool splitStartGlyph = ( numberOfCharactersStart > 1u ) && HasLigatureMustBreak( mLogicalModel->GetScript( selectionStart ) );
1933 // Check if the last glyph is a ligature that must be broken like Latin ff, fi, or Arabic ï»», etc which needs special code.
1934 const Length numberOfCharactersEnd = *( charactersPerGlyphBuffer + glyphEnd );
1935 bool splitEndGlyph = ( glyphStart != glyphEnd ) && ( numberOfCharactersEnd > 1u ) && HasLigatureMustBreak( mLogicalModel->GetScript( selectionEndMinusOne ) );
1937 // The number of quads of the selection box.
1938 const unsigned int numberOfQuads = 1u + ( glyphEnd - glyphStart ) + ( ( numberOfLines > 1u ) ? 2u * numberOfLines : 0u );
1939 mEventData->mDecorator->ResizeHighlightQuads( numberOfQuads );
1941 // Count the actual number of quads.
1942 unsigned int actualNumberOfQuads = 0u;
1945 // Traverse the glyphs.
1946 for( GlyphIndex index = glyphStart; index <= glyphEnd; ++index )
1948 const GlyphInfo& glyph = *( glyphsBuffer + index );
1949 const Vector2& position = *( positionsBuffer + index );
1951 if( splitStartGlyph )
1953 // 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.
1955 const float glyphAdvance = glyph.advance / static_cast<float>( numberOfCharactersStart );
1956 const CharacterIndex interGlyphIndex = selectionStart - *( glyphToCharacterBuffer + glyphStart );
1957 // Get the direction of the character.
1958 CharacterDirection isCurrentRightToLeft = false;
1959 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
1961 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + selectionStart );
1964 // The end point could be in the middle of the ligature.
1965 // Calculate the number of characters selected.
1966 const Length numberOfCharacters = ( glyphStart == glyphEnd ) ? ( selectionEnd - selectionStart ) : ( numberOfCharactersStart - interGlyphIndex );
1968 quad.x = lineRun->alignmentOffset + position.x - glyph.xBearing + mScrollPosition.x + glyphAdvance * static_cast<float>( isCurrentRightToLeft ? ( numberOfCharactersStart - interGlyphIndex - numberOfCharacters ) : interGlyphIndex );
1969 quad.y = selectionBoxInfo->lineOffset;
1970 quad.z = quad.x + static_cast<float>( numberOfCharacters ) * glyphAdvance;
1971 quad.w = selectionBoxInfo->lineOffset + selectionBoxInfo->lineHeight;
1973 // Store the min and max 'x' for each line.
1974 selectionBoxInfo->minX = std::min( selectionBoxInfo->minX, quad.x );
1975 selectionBoxInfo->maxX = std::max( selectionBoxInfo->maxX, quad.z );
1977 mEventData->mDecorator->AddHighlight( actualNumberOfQuads, quad );
1978 ++actualNumberOfQuads;
1980 splitStartGlyph = false;
1984 if( splitEndGlyph && ( index == glyphEnd ) )
1986 // 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.
1988 const float glyphAdvance = glyph.advance / static_cast<float>( numberOfCharactersEnd );
1989 const CharacterIndex interGlyphIndex = selectionEnd - *( glyphToCharacterBuffer + glyphEnd );
1990 // Get the direction of the character.
1991 CharacterDirection isCurrentRightToLeft = false;
1992 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
1994 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + selectionEnd );
1997 const Length numberOfCharacters = numberOfCharactersEnd - interGlyphIndex;
1999 quad.x = lineRun->alignmentOffset + position.x - glyph.xBearing + mScrollPosition.x + ( isCurrentRightToLeft ? ( glyphAdvance * static_cast<float>( numberOfCharacters ) ) : 0.f );
2000 quad.y = selectionBoxInfo->lineOffset;
2001 quad.z = quad.x + static_cast<float>( interGlyphIndex ) * glyphAdvance;
2002 quad.w = quad.y + selectionBoxInfo->lineHeight;
2004 // Store the min and max 'x' for each line.
2005 selectionBoxInfo->minX = std::min( selectionBoxInfo->minX, quad.x );
2006 selectionBoxInfo->maxX = std::max( selectionBoxInfo->maxX, quad.z );
2008 mEventData->mDecorator->AddHighlight( actualNumberOfQuads,
2010 ++actualNumberOfQuads;
2012 splitEndGlyph = false;
2016 quad.x = lineRun->alignmentOffset + position.x - glyph.xBearing + mScrollPosition.x;
2017 quad.y = selectionBoxInfo->lineOffset;
2018 quad.z = quad.x + glyph.advance;
2019 quad.w = quad.y + selectionBoxInfo->lineHeight;
2021 // Store the min and max 'x' for each line.
2022 selectionBoxInfo->minX = std::min( selectionBoxInfo->minX, quad.x );
2023 selectionBoxInfo->maxX = std::max( selectionBoxInfo->maxX, quad.z );
2025 mEventData->mDecorator->AddHighlight( actualNumberOfQuads,
2027 ++actualNumberOfQuads;
2029 // Whether to retrieve the next line.
2030 if( index == lastGlyphOfLine )
2033 if( lineIndex < firstLineIndex + numberOfLines )
2035 // Retrieve the next line.
2038 // Get the last glyph of the new line.
2039 lastGlyphOfLine = lineRun->glyphRun.glyphIndex + lineRun->glyphRun.numberOfGlyphs - 1u;
2041 // Keep the offset and height of the current selection box.
2042 const float currentLineOffset = selectionBoxInfo->lineOffset;
2043 const float currentLineHeight = selectionBoxInfo->lineHeight;
2045 // Get the selection box info for the next line.
2048 selectionBoxInfo->minX = MAX_FLOAT;
2049 selectionBoxInfo->maxX = MIN_FLOAT;
2051 // Update the line's vertical offset.
2052 selectionBoxInfo->lineOffset = currentLineOffset + currentLineHeight;
2054 // The line height is the addition of the line ascender and the line descender.
2055 // However, the line descender has a negative value, hence the subtraction.
2056 selectionBoxInfo->lineHeight = lineRun->ascender - lineRun->descender;
2061 // Traverses all the lines and updates the min and max 'x' positions and the total height.
2062 // The final width is calculated after 'boxifying' the selection.
2063 for( Vector<SelectionBoxInfo>::ConstIterator it = selectionBoxLinesInfo.Begin(),
2064 endIt = selectionBoxLinesInfo.End();
2068 const SelectionBoxInfo& info = *it;
2070 // Update the size of the highlighted text.
2071 highLightSize.height += info.lineHeight;
2072 minHighlightX = std::min( minHighlightX, info.minX );
2073 maxHighlightX = std::max( maxHighlightX, info.maxX );
2076 // Add extra geometry to 'boxify' the selection.
2078 if( 1u < numberOfLines )
2080 // Boxify the first line.
2081 lineRun = mVisualModel->mLines.Begin() + firstLineIndex;
2082 const SelectionBoxInfo& firstSelectionBoxLineInfo = *( selectionBoxLinesInfo.Begin() );
2084 bool boxifyBegin = ( LTR != lineRun->direction ) && ( LTR != startDirection );
2085 bool boxifyEnd = ( LTR == lineRun->direction ) && ( LTR == startDirection );
2090 quad.y = firstSelectionBoxLineInfo.lineOffset;
2091 quad.z = firstSelectionBoxLineInfo.minX;
2092 quad.w = firstSelectionBoxLineInfo.lineOffset + firstSelectionBoxLineInfo.lineHeight;
2094 // Boxify at the beginning of the line.
2095 mEventData->mDecorator->AddHighlight( actualNumberOfQuads,
2097 ++actualNumberOfQuads;
2099 // Update the size of the highlighted text.
2100 minHighlightX = 0.f;
2105 quad.x = firstSelectionBoxLineInfo.maxX;
2106 quad.y = firstSelectionBoxLineInfo.lineOffset;
2107 quad.z = mVisualModel->mControlSize.width;
2108 quad.w = firstSelectionBoxLineInfo.lineOffset + firstSelectionBoxLineInfo.lineHeight;
2110 // Boxify at the end of the line.
2111 mEventData->mDecorator->AddHighlight( actualNumberOfQuads,
2113 ++actualNumberOfQuads;
2115 // Update the size of the highlighted text.
2116 maxHighlightX = mVisualModel->mControlSize.width;
2119 // Boxify the central lines.
2120 if( 2u < numberOfLines )
2122 for( Vector<SelectionBoxInfo>::ConstIterator it = selectionBoxLinesInfo.Begin() + 1u,
2123 endIt = selectionBoxLinesInfo.End() - 1u;
2127 const SelectionBoxInfo& info = *it;
2130 quad.y = info.lineOffset;
2132 quad.w = info.lineOffset + info.lineHeight;
2134 mEventData->mDecorator->AddHighlight( actualNumberOfQuads,
2136 ++actualNumberOfQuads;
2139 quad.y = info.lineOffset;
2140 quad.z = mVisualModel->mControlSize.width;
2141 quad.w = info.lineOffset + info.lineHeight;
2143 mEventData->mDecorator->AddHighlight( actualNumberOfQuads,
2145 ++actualNumberOfQuads;
2148 // Update the size of the highlighted text.
2149 minHighlightX = 0.f;
2150 maxHighlightX = mVisualModel->mControlSize.width;
2153 // Boxify the last line.
2154 lineRun = mVisualModel->mLines.Begin() + firstLineIndex + numberOfLines - 1u;
2155 const SelectionBoxInfo& lastSelectionBoxLineInfo = *( selectionBoxLinesInfo.End() - 1u );
2157 boxifyBegin = ( LTR == lineRun->direction ) && ( LTR == endDirection );
2158 boxifyEnd = ( LTR != lineRun->direction ) && ( LTR != endDirection );
2163 quad.y = lastSelectionBoxLineInfo.lineOffset;
2164 quad.z = lastSelectionBoxLineInfo.minX;
2165 quad.w = lastSelectionBoxLineInfo.lineOffset + lastSelectionBoxLineInfo.lineHeight;
2167 // Boxify at the beginning of the line.
2168 mEventData->mDecorator->AddHighlight( actualNumberOfQuads,
2170 ++actualNumberOfQuads;
2172 // Update the size of the highlighted text.
2173 minHighlightX = 0.f;
2178 quad.x = lastSelectionBoxLineInfo.maxX;
2179 quad.y = lastSelectionBoxLineInfo.lineOffset;
2180 quad.z = mVisualModel->mControlSize.width;
2181 quad.w = lastSelectionBoxLineInfo.lineOffset + lastSelectionBoxLineInfo.lineHeight;
2183 // Boxify at the end of the line.
2184 mEventData->mDecorator->AddHighlight( actualNumberOfQuads,
2186 ++actualNumberOfQuads;
2188 // Update the size of the highlighted text.
2189 maxHighlightX = mVisualModel->mControlSize.width;
2193 // Set the actual number of quads.
2194 mEventData->mDecorator->ResizeHighlightQuads( actualNumberOfQuads );
2196 // Sets the highlight's size and position. In decorator's coords.
2197 // The highlight's height has been calculated above (before 'boxifying' the highlight).
2198 highLightSize.width = maxHighlightX - minHighlightX;
2200 highLightPosition.x = minHighlightX;
2201 const SelectionBoxInfo& firstSelectionBoxLineInfo = *( selectionBoxLinesInfo.Begin() );
2202 highLightPosition.y = firstSelectionBoxLineInfo.lineOffset;
2204 mEventData->mDecorator->SetHighLightBox( highLightPosition, highLightSize );
2206 if( !mEventData->mDecorator->IsSmoothHandlePanEnabled() )
2208 CursorInfo primaryCursorInfo;
2209 GetCursorPosition( mEventData->mLeftSelectionPosition,
2210 primaryCursorInfo );
2212 const Vector2 primaryPosition = primaryCursorInfo.primaryPosition + mScrollPosition;
2214 mEventData->mDecorator->SetPosition( LEFT_SELECTION_HANDLE,
2216 primaryCursorInfo.lineOffset + mScrollPosition.y,
2217 primaryCursorInfo.lineHeight );
2219 CursorInfo secondaryCursorInfo;
2220 GetCursorPosition( mEventData->mRightSelectionPosition,
2221 secondaryCursorInfo );
2223 const Vector2 secondaryPosition = secondaryCursorInfo.primaryPosition + mScrollPosition;
2225 mEventData->mDecorator->SetPosition( RIGHT_SELECTION_HANDLE,
2226 secondaryPosition.x,
2227 secondaryCursorInfo.lineOffset + mScrollPosition.y,
2228 secondaryCursorInfo.lineHeight );
2231 // Cursor to be positioned at end of selection so if selection interrupted and edit mode restarted the cursor will be at end of selection
2232 mEventData->mPrimaryCursorPosition = ( indicesSwapped ) ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition;
2234 // Set the flag to update the decorator.
2235 mEventData->mDecoratorUpdated = true;
2238 void Controller::Impl::RepositionSelectionHandles( float visualX, float visualY, Controller::NoTextTap::Action action )
2240 if( NULL == mEventData )
2242 // Nothing to do if there is no text input.
2246 const Length numberOfGlyphs = mVisualModel->mGlyphs.Count();
2247 const Length numberOfLines = mVisualModel->mLines.Count();
2248 if( ( 0 == numberOfGlyphs ) ||
2249 ( 0 == numberOfLines ) )
2251 // Nothing to do if there is no text.
2255 // Find which word was selected
2256 CharacterIndex selectionStart( 0 );
2257 CharacterIndex selectionEnd( 0 );
2258 CharacterIndex noTextHitIndex( 0 );
2259 const bool characterHit = FindSelectionIndices( mVisualModel,
2267 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "%p selectionStart %d selectionEnd %d\n", this, selectionStart, selectionEnd );
2269 if( characterHit || ( Controller::NoTextTap::HIGHLIGHT == action ) )
2271 ChangeState( EventData::SELECTING );
2273 mEventData->mLeftSelectionPosition = selectionStart;
2274 mEventData->mRightSelectionPosition = selectionEnd;
2276 mEventData->mUpdateLeftSelectionPosition = true;
2277 mEventData->mUpdateRightSelectionPosition = true;
2278 mEventData->mUpdateHighlightBox = true;
2280 // It may happen an IMF commit event arrives before the selection event
2281 // if the IMF manager is in pre-edit state. The commit event will set the
2282 // mEventData->mUpdateCursorPosition flag to true. If it's not set back
2283 // to false, the highlight box won't be updated.
2284 mEventData->mUpdateCursorPosition = false;
2286 mEventData->mScrollAfterUpdatePosition = ( mEventData->mLeftSelectionPosition != mEventData->mRightSelectionPosition );
2288 else if( Controller::NoTextTap::SHOW_SELECTION_POPUP == action )
2290 // Nothing to select. i.e. a white space, out of bounds
2291 ChangeState( EventData::EDITING_WITH_POPUP );
2293 mEventData->mPrimaryCursorPosition = noTextHitIndex;
2295 mEventData->mUpdateCursorPosition = true;
2296 mEventData->mUpdateGrabHandlePosition = true;
2297 mEventData->mScrollAfterUpdatePosition = true;
2298 mEventData->mUpdateInputStyle = true;
2300 else if( Controller::NoTextTap::NO_ACTION == action )
2302 // Nothing to select. i.e. a white space, out of bounds
2303 mEventData->mPrimaryCursorPosition = noTextHitIndex;
2305 mEventData->mUpdateCursorPosition = true;
2306 mEventData->mUpdateGrabHandlePosition = true;
2307 mEventData->mScrollAfterUpdatePosition = true;
2308 mEventData->mUpdateInputStyle = true;
2312 void Controller::Impl::SetPopupButtons()
2315 * Sets the Popup buttons to be shown depending on State.
2317 * If SELECTING : CUT & COPY + ( PASTE & CLIPBOARD if content available to paste )
2319 * If EDITING_WITH_POPUP : SELECT & SELECT_ALL
2322 TextSelectionPopup::Buttons buttonsToShow = TextSelectionPopup::NONE;
2324 if( EventData::SELECTING == mEventData->mState )
2326 buttonsToShow = TextSelectionPopup::Buttons( TextSelectionPopup::CUT | TextSelectionPopup::COPY );
2328 if( !IsClipboardEmpty() )
2330 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
2331 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::CLIPBOARD ) );
2334 if( !mEventData->mAllTextSelected )
2336 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::SELECT_ALL ) );
2339 else if( EventData::EDITING_WITH_POPUP == mEventData->mState )
2341 if( mLogicalModel->mText.Count() && !IsShowingPlaceholderText() )
2343 buttonsToShow = TextSelectionPopup::Buttons( TextSelectionPopup::SELECT | TextSelectionPopup::SELECT_ALL );
2346 if( !IsClipboardEmpty() )
2348 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
2349 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::CLIPBOARD ) );
2352 else if( EventData::EDITING_WITH_PASTE_POPUP == mEventData->mState )
2354 if ( !IsClipboardEmpty() )
2356 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
2357 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::CLIPBOARD ) );
2361 mEventData->mDecorator->SetEnabledPopupButtons( buttonsToShow );
2364 void Controller::Impl::ChangeState( EventData::State newState )
2366 if( NULL == mEventData )
2368 // Nothing to do if there is no text input.
2372 DALI_LOG_INFO( gLogFilter, Debug::General, "ChangeState state:%d newstate:%d\n", mEventData->mState, newState );
2374 if( mEventData->mState != newState )
2376 mEventData->mPreviousState = mEventData->mState;
2377 mEventData->mState = newState;
2379 switch( mEventData->mState )
2381 case EventData::INACTIVE:
2383 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
2384 mEventData->mDecorator->StopCursorBlink();
2385 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
2386 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
2387 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
2388 mEventData->mDecorator->SetHighlightActive( false );
2389 mEventData->mDecorator->SetPopupActive( false );
2390 mEventData->mDecoratorUpdated = true;
2393 case EventData::INTERRUPTED:
2395 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
2396 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
2397 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
2398 mEventData->mDecorator->SetHighlightActive( false );
2399 mEventData->mDecorator->SetPopupActive( false );
2400 mEventData->mDecoratorUpdated = true;
2403 case EventData::SELECTING:
2405 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
2406 mEventData->mDecorator->StopCursorBlink();
2407 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
2408 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true );
2409 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true );
2410 mEventData->mDecorator->SetHighlightActive( true );
2411 if( mEventData->mGrabHandlePopupEnabled )
2414 mEventData->mDecorator->SetPopupActive( true );
2416 mEventData->mDecoratorUpdated = true;
2419 case EventData::EDITING:
2421 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
2422 if( mEventData->mCursorBlinkEnabled )
2424 mEventData->mDecorator->StartCursorBlink();
2426 // Grab handle is not shown until a tap is received whilst EDITING
2427 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
2428 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
2429 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
2430 mEventData->mDecorator->SetHighlightActive( false );
2431 if( mEventData->mGrabHandlePopupEnabled )
2433 mEventData->mDecorator->SetPopupActive( false );
2435 mEventData->mDecoratorUpdated = true;
2438 case EventData::EDITING_WITH_POPUP:
2440 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "EDITING_WITH_POPUP \n", newState );
2442 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
2443 if( mEventData->mCursorBlinkEnabled )
2445 mEventData->mDecorator->StartCursorBlink();
2447 if( mEventData->mSelectionEnabled )
2449 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
2450 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
2451 mEventData->mDecorator->SetHighlightActive( false );
2455 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
2457 if( mEventData->mGrabHandlePopupEnabled )
2460 mEventData->mDecorator->SetPopupActive( true );
2462 mEventData->mDecoratorUpdated = true;
2465 case EventData::EDITING_WITH_GRAB_HANDLE:
2467 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "EDITING_WITH_GRAB_HANDLE \n", newState );
2469 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
2470 if( mEventData->mCursorBlinkEnabled )
2472 mEventData->mDecorator->StartCursorBlink();
2474 // Grab handle is not shown until a tap is received whilst EDITING
2475 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
2476 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
2477 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
2478 mEventData->mDecorator->SetHighlightActive( false );
2479 if( mEventData->mGrabHandlePopupEnabled )
2481 mEventData->mDecorator->SetPopupActive( false );
2483 mEventData->mDecoratorUpdated = true;
2486 case EventData::SELECTION_HANDLE_PANNING:
2488 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
2489 mEventData->mDecorator->StopCursorBlink();
2490 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
2491 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true );
2492 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true );
2493 mEventData->mDecorator->SetHighlightActive( true );
2494 if( mEventData->mGrabHandlePopupEnabled )
2496 mEventData->mDecorator->SetPopupActive( false );
2498 mEventData->mDecoratorUpdated = true;
2501 case EventData::GRAB_HANDLE_PANNING:
2503 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "GRAB_HANDLE_PANNING \n", newState );
2505 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
2506 if( mEventData->mCursorBlinkEnabled )
2508 mEventData->mDecorator->StartCursorBlink();
2510 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
2511 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
2512 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
2513 mEventData->mDecorator->SetHighlightActive( false );
2514 if( mEventData->mGrabHandlePopupEnabled )
2516 mEventData->mDecorator->SetPopupActive( false );
2518 mEventData->mDecoratorUpdated = true;
2521 case EventData::EDITING_WITH_PASTE_POPUP:
2523 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "EDITING_WITH_PASTE_POPUP \n", newState );
2525 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
2526 if( mEventData->mCursorBlinkEnabled )
2528 mEventData->mDecorator->StartCursorBlink();
2531 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
2532 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
2533 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
2534 mEventData->mDecorator->SetHighlightActive( false );
2536 if( mEventData->mGrabHandlePopupEnabled )
2539 mEventData->mDecorator->SetPopupActive( true );
2541 mEventData->mDecoratorUpdated = true;
2544 case EventData::TEXT_PANNING:
2546 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
2547 mEventData->mDecorator->StopCursorBlink();
2548 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
2549 if( mEventData->mDecorator->IsHandleActive( LEFT_SELECTION_HANDLE ) ||
2550 mEventData->mDecorator->IsHandleActive( RIGHT_SELECTION_HANDLE ) )
2552 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
2553 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
2554 mEventData->mDecorator->SetHighlightActive( true );
2557 if( mEventData->mGrabHandlePopupEnabled )
2559 mEventData->mDecorator->SetPopupActive( false );
2562 mEventData->mDecoratorUpdated = true;
2569 void Controller::Impl::GetCursorPosition( CharacterIndex logical,
2570 CursorInfo& cursorInfo )
2572 if( !IsShowingRealText() )
2574 // Do not want to use the place-holder text to set the cursor position.
2576 // Use the line's height of the font's family set to set the cursor's size.
2577 // If there is no font's family set, use the default font.
2578 // Use the current alignment to place the cursor at the beginning, center or end of the box.
2580 cursorInfo.lineOffset = 0.f;
2581 cursorInfo.lineHeight = GetDefaultFontLineHeight();
2582 cursorInfo.primaryCursorHeight = cursorInfo.lineHeight;
2584 switch( mLayoutEngine.GetHorizontalAlignment() )
2586 case LayoutEngine::HORIZONTAL_ALIGN_BEGIN:
2588 cursorInfo.primaryPosition.x = 0.f;
2591 case LayoutEngine::HORIZONTAL_ALIGN_CENTER:
2593 cursorInfo.primaryPosition.x = floorf( 0.5f * mVisualModel->mControlSize.width );
2596 case LayoutEngine::HORIZONTAL_ALIGN_END:
2598 cursorInfo.primaryPosition.x = mVisualModel->mControlSize.width - mEventData->mDecorator->GetCursorWidth();
2603 // Nothing else to do.
2607 const bool isMultiLine = ( LayoutEngine::MULTI_LINE_BOX == mLayoutEngine.GetLayout() );
2608 GetCursorPositionParameters parameters;
2609 parameters.visualModel = mVisualModel;
2610 parameters.logicalModel = mLogicalModel;
2611 parameters.metrics = mMetrics;
2612 parameters.logical = logical;
2613 parameters.isMultiline = isMultiLine;
2615 Text::GetCursorPosition( parameters,
2620 // If the text is editable and multi-line, the cursor position after a white space shouldn't exceed the boundaries of the text control.
2622 // Note the white spaces laid-out at the end of the line might exceed the boundaries of the control.
2623 // The reason is a wrapped line must not start with a white space so they are laid-out at the end of the line.
2625 if( 0.f > cursorInfo.primaryPosition.x )
2627 cursorInfo.primaryPosition.x = 0.f;
2630 const float edgeWidth = mVisualModel->mControlSize.width - static_cast<float>( mEventData->mDecorator->GetCursorWidth() );
2631 if( cursorInfo.primaryPosition.x > edgeWidth )
2633 cursorInfo.primaryPosition.x = edgeWidth;
2638 CharacterIndex Controller::Impl::CalculateNewCursorIndex( CharacterIndex index ) const
2640 if( NULL == mEventData )
2642 // Nothing to do if there is no text input.
2646 CharacterIndex cursorIndex = mEventData->mPrimaryCursorPosition;
2648 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
2649 const Length* const charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
2651 GlyphIndex glyphIndex = *( charactersToGlyphBuffer + index );
2652 Length numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
2654 if( numberOfCharacters > 1u )
2656 const Script script = mLogicalModel->GetScript( index );
2657 if( HasLigatureMustBreak( script ) )
2659 // Prevents to jump the whole Latin ligatures like fi, ff, or Arabic ï»», ...
2660 numberOfCharacters = 1u;
2665 while( 0u == numberOfCharacters )
2668 numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
2672 if( index < mEventData->mPrimaryCursorPosition )
2674 cursorIndex -= numberOfCharacters;
2678 cursorIndex += numberOfCharacters;
2681 // Will update the cursor hook position.
2682 mEventData->mUpdateCursorHookPosition = true;
2687 void Controller::Impl::UpdateCursorPosition( const CursorInfo& cursorInfo )
2689 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "-->Controller::UpdateCursorPosition %p\n", this );
2690 if( NULL == mEventData )
2692 // Nothing to do if there is no text input.
2693 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition no event data\n" );
2697 const Vector2 cursorPosition = cursorInfo.primaryPosition + mScrollPosition;
2699 // Sets the cursor position.
2700 mEventData->mDecorator->SetPosition( PRIMARY_CURSOR,
2703 cursorInfo.primaryCursorHeight,
2704 cursorInfo.lineHeight );
2705 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Primary cursor position: %f,%f\n", cursorPosition.x, cursorPosition.y );
2707 if( mEventData->mUpdateGrabHandlePosition )
2709 // Sets the grab handle position.
2710 mEventData->mDecorator->SetPosition( GRAB_HANDLE,
2712 cursorInfo.lineOffset + mScrollPosition.y,
2713 cursorInfo.lineHeight );
2716 if( cursorInfo.isSecondaryCursor )
2718 mEventData->mDecorator->SetPosition( SECONDARY_CURSOR,
2719 cursorInfo.secondaryPosition.x + mScrollPosition.x,
2720 cursorInfo.secondaryPosition.y + mScrollPosition.y,
2721 cursorInfo.secondaryCursorHeight,
2722 cursorInfo.lineHeight );
2723 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Secondary cursor position: %f,%f\n", cursorInfo.secondaryPosition.x + mScrollPosition.x, cursorInfo.secondaryPosition.y + mScrollPosition.y );
2726 // Set which cursors are active according the state.
2727 if( EventData::IsEditingState( mEventData->mState ) || ( EventData::GRAB_HANDLE_PANNING == mEventData->mState ) )
2729 if( cursorInfo.isSecondaryCursor )
2731 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_BOTH );
2735 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
2740 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
2743 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition\n" );
2746 void Controller::Impl::UpdateSelectionHandle( HandleType handleType,
2747 const CursorInfo& cursorInfo )
2749 if( ( LEFT_SELECTION_HANDLE != handleType ) &&
2750 ( RIGHT_SELECTION_HANDLE != handleType ) )
2755 const Vector2 cursorPosition = cursorInfo.primaryPosition + mScrollPosition;
2757 // Sets the handle's position.
2758 mEventData->mDecorator->SetPosition( handleType,
2760 cursorInfo.lineOffset + mScrollPosition.y,
2761 cursorInfo.lineHeight );
2763 // If selection handle at start of the text and other at end of the text then all text is selected.
2764 const CharacterIndex startOfSelection = std::min( mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition );
2765 const CharacterIndex endOfSelection = std::max ( mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition );
2766 mEventData->mAllTextSelected = ( startOfSelection == 0 ) && ( endOfSelection == mLogicalModel->mText.Count() );
2769 void Controller::Impl::ClampHorizontalScroll( const Vector2& layoutSize )
2771 // Clamp between -space & -alignment offset.
2773 if( layoutSize.width > mVisualModel->mControlSize.width )
2775 const float space = ( layoutSize.width - mVisualModel->mControlSize.width ) + mAlignmentOffset;
2776 mScrollPosition.x = ( mScrollPosition.x < -space ) ? -space : mScrollPosition.x;
2777 mScrollPosition.x = ( mScrollPosition.x > -mAlignmentOffset ) ? -mAlignmentOffset : mScrollPosition.x;
2779 mEventData->mDecoratorUpdated = true;
2783 mScrollPosition.x = 0.f;
2787 void Controller::Impl::ClampVerticalScroll( const Vector2& layoutSize )
2789 if( LayoutEngine::SINGLE_LINE_BOX == mLayoutEngine.GetLayout() )
2791 // Nothing to do if the text is single line.
2795 // Clamp between -space & 0.
2796 if( layoutSize.height > mVisualModel->mControlSize.height )
2798 const float space = ( layoutSize.height - mVisualModel->mControlSize.height );
2799 mScrollPosition.y = ( mScrollPosition.y < -space ) ? -space : mScrollPosition.y;
2800 mScrollPosition.y = ( mScrollPosition.y > 0.f ) ? 0.f : mScrollPosition.y;
2802 mEventData->mDecoratorUpdated = true;
2806 mScrollPosition.y = 0.f;
2810 void Controller::Impl::ScrollToMakePositionVisible( const Vector2& position, float lineHeight )
2812 const float cursorWidth = mEventData->mDecorator ? static_cast<float>( mEventData->mDecorator->GetCursorWidth() ) : 0.f;
2814 // position is in actor's coords.
2815 const float positionEndX = position.x + cursorWidth;
2816 const float positionEndY = position.y + lineHeight;
2818 // Transform the position to decorator coords.
2819 const float decoratorPositionBeginX = position.x + mScrollPosition.x;
2820 const float decoratorPositionEndX = positionEndX + mScrollPosition.x;
2822 const float decoratorPositionBeginY = position.y + mScrollPosition.y;
2823 const float decoratorPositionEndY = positionEndY + mScrollPosition.y;
2825 if( decoratorPositionBeginX < 0.f )
2827 mScrollPosition.x = -position.x;
2829 else if( decoratorPositionEndX > mVisualModel->mControlSize.width )
2831 mScrollPosition.x = mVisualModel->mControlSize.width - positionEndX;
2834 if( LayoutEngine::MULTI_LINE_BOX == mLayoutEngine.GetLayout() )
2836 if( decoratorPositionBeginY < 0.f )
2838 mScrollPosition.y = -position.y;
2840 else if( decoratorPositionEndY > mVisualModel->mControlSize.height )
2842 mScrollPosition.y = mVisualModel->mControlSize.height - positionEndY;
2847 void Controller::Impl::ScrollTextToMatchCursor( const CursorInfo& cursorInfo )
2849 // Get the current cursor position in decorator coords.
2850 const Vector2& currentCursorPosition = mEventData->mDecorator->GetPosition( PRIMARY_CURSOR );
2852 // Calculate the offset to match the cursor position before the character was deleted.
2853 mScrollPosition.x = currentCursorPosition.x - cursorInfo.primaryPosition.x;
2854 ClampHorizontalScroll( mVisualModel->GetLayoutSize() );
2856 if( LayoutEngine::MULTI_LINE_BOX == mLayoutEngine.GetLayout() )
2858 mScrollPosition.y = currentCursorPosition.y - cursorInfo.lineOffset;
2859 ClampVerticalScroll( mVisualModel->GetLayoutSize() );
2862 // Makes the new cursor position visible if needed.
2863 ScrollToMakePositionVisible( cursorInfo.primaryPosition, cursorInfo.lineHeight );
2866 void Controller::Impl::RequestRelayout()
2868 if( NULL != mControlInterface )
2870 mControlInterface->RequestTextRelayout();
2876 } // namespace Toolkit