2 * Copyright (c) 2017 Samsung Electronics Co., Ltd.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
19 #include <dali-toolkit/internal/text/text-controller-impl.h>
22 #include <dali/public-api/adaptor-framework/key.h>
23 #include <dali/integration-api/debug.h>
27 #include <dali-toolkit/internal/text/bidirectional-support.h>
28 #include <dali-toolkit/internal/text/character-set-conversion.h>
29 #include <dali-toolkit/internal/text/color-segmentation.h>
30 #include <dali-toolkit/internal/text/cursor-helper-functions.h>
31 #include <dali-toolkit/internal/text/multi-language-support.h>
32 #include <dali-toolkit/internal/text/segmentation.h>
33 #include <dali-toolkit/internal/text/shaper.h>
34 #include <dali-toolkit/internal/text/text-control-interface.h>
35 #include <dali-toolkit/internal/text/text-run-container.h>
41 * @brief Struct used to calculate the selection box.
43 struct SelectionBoxInfo
51 #if defined(DEBUG_ENABLED)
52 Debug::Filter* gLogFilter = Debug::Filter::New(Debug::NoLogging, true, "LOG_TEXT_CONTROLS");
55 const float MAX_FLOAT = std::numeric_limits<float>::max();
56 const float MIN_FLOAT = std::numeric_limits<float>::min();
57 const Dali::Toolkit::Text::CharacterDirection LTR = false; ///< Left To Right direction
70 EventData::EventData( DecoratorPtr decorator )
71 : mDecorator( decorator ),
73 mPlaceholderFont( NULL ),
74 mPlaceholderTextActive(),
75 mPlaceholderTextInactive(),
76 mPlaceholderTextColor( 0.8f, 0.8f, 0.8f, 0.8f ),
78 mInputStyleChangedQueue(),
79 mPreviousState( INACTIVE ),
81 mPrimaryCursorPosition( 0u ),
82 mLeftSelectionPosition( 0u ),
83 mRightSelectionPosition( 0u ),
84 mPreEditStartPosition( 0u ),
86 mCursorHookPositionX( 0.f ),
87 mDoubleTapAction( Controller::NoTextTap::NO_ACTION ),
88 mLongPressAction( Controller::NoTextTap::SHOW_SELECTION_POPUP ),
89 mIsShowingPlaceholderText( false ),
90 mPreEditFlag( false ),
91 mDecoratorUpdated( false ),
92 mCursorBlinkEnabled( true ),
93 mGrabHandleEnabled( true ),
94 mGrabHandlePopupEnabled( true ),
95 mSelectionEnabled( true ),
96 mUpdateCursorHookPosition( false ),
97 mUpdateCursorPosition( false ),
98 mUpdateGrabHandlePosition( false ),
99 mUpdateLeftSelectionPosition( false ),
100 mUpdateRightSelectionPosition( false ),
101 mIsLeftHandleSelected( false ),
102 mIsRightHandleSelected( false ),
103 mUpdateHighlightBox( false ),
104 mScrollAfterUpdatePosition( false ),
105 mScrollAfterDelete( false ),
106 mAllTextSelected( false ),
107 mUpdateInputStyle( false ),
108 mPasswordInput( false ),
109 mIsPlaceholderPixelSize( false )
111 mImfManager = ImfManager::Get();
114 EventData::~EventData()
117 bool Controller::Impl::ProcessInputEvents()
119 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "-->Controller::ProcessInputEvents\n" );
120 if( NULL == mEventData )
122 // Nothing to do if there is no text input.
123 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::ProcessInputEvents no event data\n" );
127 if( mEventData->mDecorator )
129 for( std::vector<Event>::iterator iter = mEventData->mEventQueue.begin();
130 iter != mEventData->mEventQueue.end();
135 case Event::CURSOR_KEY_EVENT:
137 OnCursorKeyEvent( *iter );
140 case Event::TAP_EVENT:
145 case Event::LONG_PRESS_EVENT:
147 OnLongPressEvent( *iter );
150 case Event::PAN_EVENT:
155 case Event::GRAB_HANDLE_EVENT:
156 case Event::LEFT_SELECTION_HANDLE_EVENT:
157 case Event::RIGHT_SELECTION_HANDLE_EVENT: // Fall through
159 OnHandleEvent( *iter );
164 OnSelectEvent( *iter );
167 case Event::SELECT_ALL:
176 if( mEventData->mUpdateCursorPosition ||
177 mEventData->mUpdateHighlightBox )
182 // The cursor must also be repositioned after inserts into the model
183 if( mEventData->mUpdateCursorPosition )
185 // Updates the cursor position and scrolls the text to make it visible.
186 CursorInfo cursorInfo;
187 // Calculate the cursor position from the new cursor index.
188 GetCursorPosition( mEventData->mPrimaryCursorPosition,
191 if( mEventData->mUpdateCursorHookPosition )
193 // Update the cursor hook position. Used to move the cursor with the keys 'up' and 'down'.
194 mEventData->mCursorHookPositionX = cursorInfo.primaryPosition.x;
195 mEventData->mUpdateCursorHookPosition = false;
198 // Scroll first the text after delete ...
199 if( mEventData->mScrollAfterDelete )
201 ScrollTextToMatchCursor( cursorInfo );
204 // ... then, text can be scrolled to make the cursor visible.
205 if( mEventData->mScrollAfterUpdatePosition )
207 const Vector2 currentCursorPosition( cursorInfo.primaryPosition.x, cursorInfo.lineOffset );
208 ScrollToMakePositionVisible( currentCursorPosition, cursorInfo.lineHeight );
210 mEventData->mScrollAfterUpdatePosition = false;
211 mEventData->mScrollAfterDelete = false;
213 UpdateCursorPosition( cursorInfo );
215 mEventData->mDecoratorUpdated = true;
216 mEventData->mUpdateCursorPosition = false;
217 mEventData->mUpdateGrabHandlePosition = false;
221 CursorInfo leftHandleInfo;
222 CursorInfo rightHandleInfo;
224 if( mEventData->mUpdateHighlightBox )
226 GetCursorPosition( mEventData->mLeftSelectionPosition,
229 GetCursorPosition( mEventData->mRightSelectionPosition,
232 if( mEventData->mScrollAfterUpdatePosition && ( mEventData->mIsLeftHandleSelected ? mEventData->mUpdateLeftSelectionPosition : mEventData->mUpdateRightSelectionPosition ) )
234 if( mEventData->mIsLeftHandleSelected && mEventData->mIsRightHandleSelected )
236 CursorInfo& infoLeft = leftHandleInfo;
238 const Vector2 currentCursorPositionLeft( infoLeft.primaryPosition.x, infoLeft.lineOffset );
239 ScrollToMakePositionVisible( currentCursorPositionLeft, infoLeft.lineHeight );
241 CursorInfo& infoRight = rightHandleInfo;
243 const Vector2 currentCursorPositionRight( infoRight.primaryPosition.x, infoRight.lineOffset );
244 ScrollToMakePositionVisible( currentCursorPositionRight, infoRight.lineHeight );
248 CursorInfo& info = mEventData->mIsLeftHandleSelected ? leftHandleInfo : rightHandleInfo;
250 const Vector2 currentCursorPosition( info.primaryPosition.x, info.lineOffset );
251 ScrollToMakePositionVisible( currentCursorPosition, info.lineHeight );
256 if( mEventData->mUpdateLeftSelectionPosition )
258 UpdateSelectionHandle( LEFT_SELECTION_HANDLE,
262 mEventData->mDecoratorUpdated = true;
263 mEventData->mUpdateLeftSelectionPosition = false;
266 if( mEventData->mUpdateRightSelectionPosition )
268 UpdateSelectionHandle( RIGHT_SELECTION_HANDLE,
272 mEventData->mDecoratorUpdated = true;
273 mEventData->mUpdateRightSelectionPosition = false;
276 if( mEventData->mUpdateHighlightBox )
278 RepositionSelectionHandles();
280 mEventData->mUpdateLeftSelectionPosition = false;
281 mEventData->mUpdateRightSelectionPosition = false;
282 mEventData->mUpdateHighlightBox = false;
283 mEventData->mIsLeftHandleSelected = false;
284 mEventData->mIsRightHandleSelected = false;
287 mEventData->mScrollAfterUpdatePosition = false;
290 if( mEventData->mUpdateInputStyle )
292 // Keep a copy of the current input style.
293 InputStyle currentInputStyle;
294 currentInputStyle.Copy( mEventData->mInputStyle );
296 // Set the default style first.
297 RetrieveDefaultInputStyle( mEventData->mInputStyle );
299 // Get the character index from the cursor index.
300 const CharacterIndex styleIndex = ( mEventData->mPrimaryCursorPosition > 0u ) ? mEventData->mPrimaryCursorPosition - 1u : 0u;
302 // Retrieve the style from the style runs stored in the logical model.
303 mModel->mLogicalModel->RetrieveStyle( styleIndex, mEventData->mInputStyle );
305 // Compare if the input style has changed.
306 const bool hasInputStyleChanged = !currentInputStyle.Equal( mEventData->mInputStyle );
308 if( hasInputStyleChanged )
310 const InputStyle::Mask styleChangedMask = currentInputStyle.GetInputStyleChangeMask( mEventData->mInputStyle );
311 // Queue the input style changed signal.
312 mEventData->mInputStyleChangedQueue.PushBack( styleChangedMask );
315 mEventData->mUpdateInputStyle = false;
318 mEventData->mEventQueue.clear();
320 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::ProcessInputEvents\n" );
322 const bool decoratorUpdated = mEventData->mDecoratorUpdated;
323 mEventData->mDecoratorUpdated = false;
325 return decoratorUpdated;
328 void Controller::Impl::NotifyImfManager()
330 if( mEventData && mEventData->mImfManager )
332 CharacterIndex cursorPosition = GetLogicalCursorPosition();
334 const Length numberOfWhiteSpaces = GetNumberOfWhiteSpaces( 0u );
336 // Update the cursor position by removing the initial white spaces.
337 if( cursorPosition < numberOfWhiteSpaces )
343 cursorPosition -= numberOfWhiteSpaces;
346 mEventData->mImfManager.SetCursorPosition( cursorPosition );
347 mEventData->mImfManager.NotifyCursorPosition();
351 void Controller::Impl::NotifyImfMultiLineStatus()
355 Text::Layout::Engine::Type layout = mLayoutEngine.GetLayout();
356 mEventData->mImfManager.NotifyTextInputMultiLine( layout == Text::Layout::Engine::MULTI_LINE_BOX );
360 CharacterIndex Controller::Impl::GetLogicalCursorPosition() const
362 CharacterIndex cursorPosition = 0u;
366 if( ( EventData::SELECTING == mEventData->mState ) ||
367 ( EventData::SELECTION_HANDLE_PANNING == mEventData->mState ) )
369 cursorPosition = std::min( mEventData->mRightSelectionPosition, mEventData->mLeftSelectionPosition );
373 cursorPosition = mEventData->mPrimaryCursorPosition;
377 return cursorPosition;
380 Length Controller::Impl::GetNumberOfWhiteSpaces( CharacterIndex index ) const
382 Length numberOfWhiteSpaces = 0u;
384 // Get the buffer to the text.
385 Character* utf32CharacterBuffer = mModel->mLogicalModel->mText.Begin();
387 const Length totalNumberOfCharacters = mModel->mLogicalModel->mText.Count();
388 for( ; index < totalNumberOfCharacters; ++index, ++numberOfWhiteSpaces )
390 if( !TextAbstraction::IsWhiteSpace( *( utf32CharacterBuffer + index ) ) )
396 return numberOfWhiteSpaces;
399 void Controller::Impl::GetText( CharacterIndex index, std::string& text ) const
401 // Get the total number of characters.
402 Length numberOfCharacters = mModel->mLogicalModel->mText.Count();
404 // Retrieve the text.
405 if( 0u != numberOfCharacters )
407 Utf32ToUtf8( mModel->mLogicalModel->mText.Begin() + index, numberOfCharacters - index, text );
411 void Controller::Impl::CalculateTextUpdateIndices( Length& numberOfCharacters )
413 mTextUpdateInfo.mParagraphCharacterIndex = 0u;
414 mTextUpdateInfo.mStartGlyphIndex = 0u;
415 mTextUpdateInfo.mStartLineIndex = 0u;
416 numberOfCharacters = 0u;
418 const Length numberOfParagraphs = mModel->mLogicalModel->mParagraphInfo.Count();
419 if( 0u == numberOfParagraphs )
421 mTextUpdateInfo.mParagraphCharacterIndex = 0u;
422 numberOfCharacters = 0u;
424 mTextUpdateInfo.mRequestedNumberOfCharacters = mTextUpdateInfo.mNumberOfCharactersToAdd - mTextUpdateInfo.mNumberOfCharactersToRemove;
426 // Nothing else to do if there are no paragraphs.
430 // Find the paragraphs to be updated.
431 Vector<ParagraphRunIndex> paragraphsToBeUpdated;
432 if( mTextUpdateInfo.mCharacterIndex >= mTextUpdateInfo.mPreviousNumberOfCharacters )
434 // Text is being added at the end of the current text.
435 if( mTextUpdateInfo.mIsLastCharacterNewParagraph )
437 // Text is being added in a new paragraph after the last character of the text.
438 mTextUpdateInfo.mParagraphCharacterIndex = mTextUpdateInfo.mPreviousNumberOfCharacters;
439 numberOfCharacters = 0u;
440 mTextUpdateInfo.mRequestedNumberOfCharacters = mTextUpdateInfo.mNumberOfCharactersToAdd - mTextUpdateInfo.mNumberOfCharactersToRemove;
442 mTextUpdateInfo.mStartGlyphIndex = mModel->mVisualModel->mGlyphs.Count();
443 mTextUpdateInfo.mStartLineIndex = mModel->mVisualModel->mLines.Count() - 1u;
445 // Nothing else to do;
449 paragraphsToBeUpdated.PushBack( numberOfParagraphs - 1u );
453 Length numberOfCharactersToUpdate = 0u;
454 if( mTextUpdateInfo.mFullRelayoutNeeded )
456 numberOfCharactersToUpdate = mTextUpdateInfo.mPreviousNumberOfCharacters;
460 numberOfCharactersToUpdate = ( mTextUpdateInfo.mNumberOfCharactersToRemove > 0u ) ? mTextUpdateInfo.mNumberOfCharactersToRemove : 1u;
462 mModel->mLogicalModel->FindParagraphs( mTextUpdateInfo.mCharacterIndex,
463 numberOfCharactersToUpdate,
464 paragraphsToBeUpdated );
467 if( 0u != paragraphsToBeUpdated.Count() )
469 const ParagraphRunIndex firstParagraphIndex = *( paragraphsToBeUpdated.Begin() );
470 const ParagraphRun& firstParagraph = *( mModel->mLogicalModel->mParagraphInfo.Begin() + firstParagraphIndex );
471 mTextUpdateInfo.mParagraphCharacterIndex = firstParagraph.characterRun.characterIndex;
473 ParagraphRunIndex lastParagraphIndex = *( paragraphsToBeUpdated.End() - 1u );
474 const ParagraphRun& lastParagraph = *( mModel->mLogicalModel->mParagraphInfo.Begin() + lastParagraphIndex );
476 if( ( mTextUpdateInfo.mNumberOfCharactersToRemove > 0u ) && // Some character are removed.
477 ( lastParagraphIndex < numberOfParagraphs - 1u ) && // There is a next paragraph.
478 ( ( lastParagraph.characterRun.characterIndex + lastParagraph.characterRun.numberOfCharacters ) == // The last removed character is the new paragraph character.
479 ( mTextUpdateInfo.mCharacterIndex + mTextUpdateInfo.mNumberOfCharactersToRemove ) ) )
481 // The new paragraph character of the last updated paragraph has been removed so is going to be merged with the next one.
482 const ParagraphRun& lastParagraph = *( mModel->mLogicalModel->mParagraphInfo.Begin() + lastParagraphIndex + 1u );
484 numberOfCharacters = lastParagraph.characterRun.characterIndex + lastParagraph.characterRun.numberOfCharacters - mTextUpdateInfo.mParagraphCharacterIndex;
488 numberOfCharacters = lastParagraph.characterRun.characterIndex + lastParagraph.characterRun.numberOfCharacters - mTextUpdateInfo.mParagraphCharacterIndex;
492 mTextUpdateInfo.mRequestedNumberOfCharacters = numberOfCharacters + mTextUpdateInfo.mNumberOfCharactersToAdd - mTextUpdateInfo.mNumberOfCharactersToRemove;
493 mTextUpdateInfo.mStartGlyphIndex = *( mModel->mVisualModel->mCharactersToGlyph.Begin() + mTextUpdateInfo.mParagraphCharacterIndex );
496 void Controller::Impl::ClearFullModelData( OperationsMask operations )
498 if( NO_OPERATION != ( GET_LINE_BREAKS & operations ) )
500 mModel->mLogicalModel->mLineBreakInfo.Clear();
501 mModel->mLogicalModel->mParagraphInfo.Clear();
504 if( NO_OPERATION != ( GET_WORD_BREAKS & operations ) )
506 mModel->mLogicalModel->mLineBreakInfo.Clear();
509 if( NO_OPERATION != ( GET_SCRIPTS & operations ) )
511 mModel->mLogicalModel->mScriptRuns.Clear();
514 if( NO_OPERATION != ( VALIDATE_FONTS & operations ) )
516 mModel->mLogicalModel->mFontRuns.Clear();
519 if( 0u != mModel->mLogicalModel->mBidirectionalParagraphInfo.Count() )
521 if( NO_OPERATION != ( BIDI_INFO & operations ) )
523 mModel->mLogicalModel->mBidirectionalParagraphInfo.Clear();
524 mModel->mLogicalModel->mCharacterDirections.Clear();
527 if( NO_OPERATION != ( REORDER & operations ) )
529 // Free the allocated memory used to store the conversion table in the bidirectional line info run.
530 for( Vector<BidirectionalLineInfoRun>::Iterator it = mModel->mLogicalModel->mBidirectionalLineInfo.Begin(),
531 endIt = mModel->mLogicalModel->mBidirectionalLineInfo.End();
535 BidirectionalLineInfoRun& bidiLineInfo = *it;
537 free( bidiLineInfo.visualToLogicalMap );
538 bidiLineInfo.visualToLogicalMap = NULL;
540 mModel->mLogicalModel->mBidirectionalLineInfo.Clear();
544 if( NO_OPERATION != ( SHAPE_TEXT & operations ) )
546 mModel->mVisualModel->mGlyphs.Clear();
547 mModel->mVisualModel->mGlyphsToCharacters.Clear();
548 mModel->mVisualModel->mCharactersToGlyph.Clear();
549 mModel->mVisualModel->mCharactersPerGlyph.Clear();
550 mModel->mVisualModel->mGlyphsPerCharacter.Clear();
551 mModel->mVisualModel->mGlyphPositions.Clear();
554 if( NO_OPERATION != ( LAYOUT & operations ) )
556 mModel->mVisualModel->mLines.Clear();
559 if( NO_OPERATION != ( COLOR & operations ) )
561 mModel->mVisualModel->mColorIndices.Clear();
565 void Controller::Impl::ClearCharacterModelData( CharacterIndex startIndex, CharacterIndex endIndex, OperationsMask operations )
567 const CharacterIndex endIndexPlusOne = endIndex + 1u;
569 if( NO_OPERATION != ( GET_LINE_BREAKS & operations ) )
571 // Clear the line break info.
572 LineBreakInfo* lineBreakInfoBuffer = mModel->mLogicalModel->mLineBreakInfo.Begin();
574 mModel->mLogicalModel->mLineBreakInfo.Erase( lineBreakInfoBuffer + startIndex,
575 lineBreakInfoBuffer + endIndexPlusOne );
577 // Clear the paragraphs.
578 ClearCharacterRuns( startIndex,
580 mModel->mLogicalModel->mParagraphInfo );
583 if( NO_OPERATION != ( GET_WORD_BREAKS & operations ) )
585 // Clear the word break info.
586 WordBreakInfo* wordBreakInfoBuffer = mModel->mLogicalModel->mWordBreakInfo.Begin();
588 mModel->mLogicalModel->mWordBreakInfo.Erase( wordBreakInfoBuffer + startIndex,
589 wordBreakInfoBuffer + endIndexPlusOne );
592 if( NO_OPERATION != ( GET_SCRIPTS & operations ) )
594 // Clear the scripts.
595 ClearCharacterRuns( startIndex,
597 mModel->mLogicalModel->mScriptRuns );
600 if( NO_OPERATION != ( VALIDATE_FONTS & operations ) )
603 ClearCharacterRuns( startIndex,
605 mModel->mLogicalModel->mFontRuns );
608 if( 0u != mModel->mLogicalModel->mBidirectionalParagraphInfo.Count() )
610 if( NO_OPERATION != ( BIDI_INFO & operations ) )
612 // Clear the bidirectional paragraph info.
613 ClearCharacterRuns( startIndex,
615 mModel->mLogicalModel->mBidirectionalParagraphInfo );
617 // Clear the character's directions.
618 CharacterDirection* characterDirectionsBuffer = mModel->mLogicalModel->mCharacterDirections.Begin();
620 mModel->mLogicalModel->mCharacterDirections.Erase( characterDirectionsBuffer + startIndex,
621 characterDirectionsBuffer + endIndexPlusOne );
624 if( NO_OPERATION != ( REORDER & operations ) )
626 uint32_t startRemoveIndex = mModel->mLogicalModel->mBidirectionalLineInfo.Count();
627 uint32_t endRemoveIndex = startRemoveIndex;
628 ClearCharacterRuns( startIndex,
630 mModel->mLogicalModel->mBidirectionalLineInfo,
634 BidirectionalLineInfoRun* bidirectionalLineInfoBuffer = mModel->mLogicalModel->mBidirectionalLineInfo.Begin();
636 // Free the allocated memory used to store the conversion table in the bidirectional line info run.
637 for( Vector<BidirectionalLineInfoRun>::Iterator it = bidirectionalLineInfoBuffer + startRemoveIndex,
638 endIt = bidirectionalLineInfoBuffer + endRemoveIndex;
642 BidirectionalLineInfoRun& bidiLineInfo = *it;
644 free( bidiLineInfo.visualToLogicalMap );
645 bidiLineInfo.visualToLogicalMap = NULL;
648 mModel->mLogicalModel->mBidirectionalLineInfo.Erase( bidirectionalLineInfoBuffer + startRemoveIndex,
649 bidirectionalLineInfoBuffer + endRemoveIndex );
654 void Controller::Impl::ClearGlyphModelData( CharacterIndex startIndex, CharacterIndex endIndex, OperationsMask operations )
656 const CharacterIndex endIndexPlusOne = endIndex + 1u;
657 const Length numberOfCharactersRemoved = endIndexPlusOne - startIndex;
659 // Convert the character index to glyph index before deleting the character to glyph and the glyphs per character buffers.
660 GlyphIndex* charactersToGlyphBuffer = mModel->mVisualModel->mCharactersToGlyph.Begin();
661 Length* glyphsPerCharacterBuffer = mModel->mVisualModel->mGlyphsPerCharacter.Begin();
663 const GlyphIndex endGlyphIndexPlusOne = *( charactersToGlyphBuffer + endIndex ) + *( glyphsPerCharacterBuffer + endIndex );
664 const Length numberOfGlyphsRemoved = endGlyphIndexPlusOne - mTextUpdateInfo.mStartGlyphIndex;
666 if( NO_OPERATION != ( SHAPE_TEXT & operations ) )
668 // Update the character to glyph indices.
669 for( Vector<GlyphIndex>::Iterator it = charactersToGlyphBuffer + endIndexPlusOne,
670 endIt = charactersToGlyphBuffer + mModel->mVisualModel->mCharactersToGlyph.Count();
674 CharacterIndex& index = *it;
675 index -= numberOfGlyphsRemoved;
678 // Clear the character to glyph conversion table.
679 mModel->mVisualModel->mCharactersToGlyph.Erase( charactersToGlyphBuffer + startIndex,
680 charactersToGlyphBuffer + endIndexPlusOne );
682 // Clear the glyphs per character table.
683 mModel->mVisualModel->mGlyphsPerCharacter.Erase( glyphsPerCharacterBuffer + startIndex,
684 glyphsPerCharacterBuffer + endIndexPlusOne );
686 // Clear the glyphs buffer.
687 GlyphInfo* glyphsBuffer = mModel->mVisualModel->mGlyphs.Begin();
688 mModel->mVisualModel->mGlyphs.Erase( glyphsBuffer + mTextUpdateInfo.mStartGlyphIndex,
689 glyphsBuffer + endGlyphIndexPlusOne );
691 CharacterIndex* glyphsToCharactersBuffer = mModel->mVisualModel->mGlyphsToCharacters.Begin();
693 // Update the glyph to character indices.
694 for( Vector<CharacterIndex>::Iterator it = glyphsToCharactersBuffer + endGlyphIndexPlusOne,
695 endIt = glyphsToCharactersBuffer + mModel->mVisualModel->mGlyphsToCharacters.Count();
699 CharacterIndex& index = *it;
700 index -= numberOfCharactersRemoved;
703 // Clear the glyphs to characters buffer.
704 mModel->mVisualModel->mGlyphsToCharacters.Erase( glyphsToCharactersBuffer + mTextUpdateInfo.mStartGlyphIndex,
705 glyphsToCharactersBuffer + endGlyphIndexPlusOne );
707 // Clear the characters per glyph buffer.
708 Length* charactersPerGlyphBuffer = mModel->mVisualModel->mCharactersPerGlyph.Begin();
709 mModel->mVisualModel->mCharactersPerGlyph.Erase( charactersPerGlyphBuffer + mTextUpdateInfo.mStartGlyphIndex,
710 charactersPerGlyphBuffer + endGlyphIndexPlusOne );
712 // Clear the positions buffer.
713 Vector2* positionsBuffer = mModel->mVisualModel->mGlyphPositions.Begin();
714 mModel->mVisualModel->mGlyphPositions.Erase( positionsBuffer + mTextUpdateInfo.mStartGlyphIndex,
715 positionsBuffer + endGlyphIndexPlusOne );
718 if( NO_OPERATION != ( LAYOUT & operations ) )
721 uint32_t startRemoveIndex = mModel->mVisualModel->mLines.Count();
722 uint32_t endRemoveIndex = startRemoveIndex;
723 ClearCharacterRuns( startIndex,
725 mModel->mVisualModel->mLines,
729 // Will update the glyph runs.
730 startRemoveIndex = mModel->mVisualModel->mLines.Count();
731 endRemoveIndex = startRemoveIndex;
732 ClearGlyphRuns( mTextUpdateInfo.mStartGlyphIndex,
733 endGlyphIndexPlusOne - 1u,
734 mModel->mVisualModel->mLines,
738 // Set the line index from where to insert the new laid-out lines.
739 mTextUpdateInfo.mStartLineIndex = startRemoveIndex;
741 LineRun* linesBuffer = mModel->mVisualModel->mLines.Begin();
742 mModel->mVisualModel->mLines.Erase( linesBuffer + startRemoveIndex,
743 linesBuffer + endRemoveIndex );
746 if( NO_OPERATION != ( COLOR & operations ) )
748 if( 0u != mModel->mVisualModel->mColorIndices.Count() )
750 ColorIndex* colorIndexBuffer = mModel->mVisualModel->mColorIndices.Begin();
751 mModel->mVisualModel->mColorIndices.Erase( colorIndexBuffer + mTextUpdateInfo.mStartGlyphIndex,
752 colorIndexBuffer + endGlyphIndexPlusOne );
757 void Controller::Impl::ClearModelData( CharacterIndex startIndex, CharacterIndex endIndex, OperationsMask operations )
759 if( mTextUpdateInfo.mClearAll ||
760 ( ( 0u == startIndex ) &&
761 ( mTextUpdateInfo.mPreviousNumberOfCharacters == endIndex + 1u ) ) )
763 ClearFullModelData( operations );
767 // Clear the model data related with characters.
768 ClearCharacterModelData( startIndex, endIndex, operations );
770 // Clear the model data related with glyphs.
771 ClearGlyphModelData( startIndex, endIndex, operations );
774 // The estimated number of lines. Used to avoid reallocations when layouting.
775 mTextUpdateInfo.mEstimatedNumberOfLines = std::max( mModel->mVisualModel->mLines.Count(), mModel->mLogicalModel->mParagraphInfo.Count() );
777 mModel->mVisualModel->ClearCaches();
780 bool Controller::Impl::UpdateModel( OperationsMask operationsRequired )
782 DALI_LOG_INFO( gLogFilter, Debug::General, "Controller::UpdateModel\n" );
784 // Calculate the operations to be done.
785 const OperationsMask operations = static_cast<OperationsMask>( mOperationsPending & operationsRequired );
787 if( NO_OPERATION == operations )
789 // Nothing to do if no operations are pending and required.
793 Vector<Character>& srcCharacters = mModel->mLogicalModel->mText;
794 Vector<Character> displayCharacters;
795 bool useHiddenText = false;
796 if ( mHiddenInput && mEventData != NULL && !mEventData->mIsShowingPlaceholderText)
798 mHiddenInput->Substitute( srcCharacters,displayCharacters );
799 useHiddenText = true;
802 Vector<Character>& utf32Characters = useHiddenText ? displayCharacters : srcCharacters;
803 const Length numberOfCharacters = utf32Characters.Count();
805 // Index to the first character of the first paragraph to be updated.
806 CharacterIndex startIndex = 0u;
807 // Number of characters of the paragraphs to be removed.
808 Length paragraphCharacters = 0u;
810 CalculateTextUpdateIndices( paragraphCharacters );
811 startIndex = mTextUpdateInfo.mParagraphCharacterIndex;
813 if( mTextUpdateInfo.mClearAll ||
814 ( 0u != paragraphCharacters ) )
816 ClearModelData( startIndex, startIndex + ( ( paragraphCharacters > 0u ) ? paragraphCharacters - 1u : 0u ), operations );
819 mTextUpdateInfo.mClearAll = false;
821 // Whether the model is updated.
822 bool updated = false;
824 Vector<LineBreakInfo>& lineBreakInfo = mModel->mLogicalModel->mLineBreakInfo;
825 const Length requestedNumberOfCharacters = mTextUpdateInfo.mRequestedNumberOfCharacters;
827 if( NO_OPERATION != ( GET_LINE_BREAKS & operations ) )
829 // Retrieves the line break info. The line break info is used to split the text in 'paragraphs' to
830 // calculate the bidirectional info for each 'paragraph'.
831 // It's also used to layout the text (where it should be a new line) or to shape the text (text in different lines
832 // is not shaped together).
833 lineBreakInfo.Resize( numberOfCharacters, TextAbstraction::LINE_NO_BREAK );
835 SetLineBreakInfo( utf32Characters,
837 requestedNumberOfCharacters,
840 // Create the paragraph info.
841 mModel->mLogicalModel->CreateParagraphInfo( startIndex,
842 requestedNumberOfCharacters );
846 Vector<WordBreakInfo>& wordBreakInfo = mModel->mLogicalModel->mWordBreakInfo;
847 if( NO_OPERATION != ( GET_WORD_BREAKS & operations ) )
849 // Retrieves the word break info. The word break info is used to layout the text (where to wrap the text in lines).
850 wordBreakInfo.Resize( numberOfCharacters, TextAbstraction::WORD_NO_BREAK );
852 SetWordBreakInfo( utf32Characters,
854 requestedNumberOfCharacters,
859 const bool getScripts = NO_OPERATION != ( GET_SCRIPTS & operations );
860 const bool validateFonts = NO_OPERATION != ( VALIDATE_FONTS & operations );
862 Vector<ScriptRun>& scripts = mModel->mLogicalModel->mScriptRuns;
863 Vector<FontRun>& validFonts = mModel->mLogicalModel->mFontRuns;
865 if( getScripts || validateFonts )
867 // Validates the fonts assigned by the application or assigns default ones.
868 // It makes sure all the characters are going to be rendered by the correct font.
869 MultilanguageSupport multilanguageSupport = MultilanguageSupport::Get();
873 // Retrieves the scripts used in the text.
874 multilanguageSupport.SetScripts( utf32Characters,
876 requestedNumberOfCharacters,
882 // Validate the fonts set through the mark-up string.
883 Vector<FontDescriptionRun>& fontDescriptionRuns = mModel->mLogicalModel->mFontDescriptionRuns;
885 // Get the default font's description.
886 TextAbstraction::FontDescription defaultFontDescription;
887 TextAbstraction::PointSize26Dot6 defaultPointSize = TextAbstraction::FontClient::DEFAULT_POINT_SIZE;
889 if( IsShowingPlaceholderText() && ( NULL != mEventData->mPlaceholderFont ) )
891 // If the placeholder font is set specifically, only placeholder font is changed.
892 defaultFontDescription = mEventData->mPlaceholderFont->mFontDescription;
893 if( mEventData->mPlaceholderFont->sizeDefined )
895 defaultPointSize = mEventData->mPlaceholderFont->mDefaultPointSize * 64u;
898 else if( NULL != mFontDefaults )
900 // Set the normal font and the placeholder font.
901 defaultFontDescription = mFontDefaults->mFontDescription;
902 defaultPointSize = mFontDefaults->mDefaultPointSize * 64u;
905 // Validates the fonts. If there is a character with no assigned font it sets a default one.
906 // After this call, fonts are validated.
907 multilanguageSupport.ValidateFonts( utf32Characters,
910 defaultFontDescription,
913 requestedNumberOfCharacters,
919 Vector<Character> mirroredUtf32Characters;
920 bool textMirrored = false;
921 const Length numberOfParagraphs = mModel->mLogicalModel->mParagraphInfo.Count();
922 if( NO_OPERATION != ( BIDI_INFO & operations ) )
924 Vector<BidirectionalParagraphInfoRun>& bidirectionalInfo = mModel->mLogicalModel->mBidirectionalParagraphInfo;
925 bidirectionalInfo.Reserve( numberOfParagraphs );
927 // Calculates the bidirectional info for the whole paragraph if it contains right to left scripts.
928 SetBidirectionalInfo( utf32Characters,
932 requestedNumberOfCharacters,
935 if( 0u != bidirectionalInfo.Count() )
937 // Only set the character directions if there is right to left characters.
938 Vector<CharacterDirection>& directions = mModel->mLogicalModel->mCharacterDirections;
939 GetCharactersDirection( bidirectionalInfo,
942 requestedNumberOfCharacters,
945 // This paragraph has right to left text. Some characters may need to be mirrored.
946 // TODO: consider if the mirrored string can be stored as well.
948 textMirrored = GetMirroredText( utf32Characters,
952 requestedNumberOfCharacters,
953 mirroredUtf32Characters );
957 // There is no right to left characters. Clear the directions vector.
958 mModel->mLogicalModel->mCharacterDirections.Clear();
963 Vector<GlyphInfo>& glyphs = mModel->mVisualModel->mGlyphs;
964 Vector<CharacterIndex>& glyphsToCharactersMap = mModel->mVisualModel->mGlyphsToCharacters;
965 Vector<Length>& charactersPerGlyph = mModel->mVisualModel->mCharactersPerGlyph;
966 Vector<GlyphIndex> newParagraphGlyphs;
967 newParagraphGlyphs.Reserve( numberOfParagraphs );
969 const Length currentNumberOfGlyphs = glyphs.Count();
970 if( NO_OPERATION != ( SHAPE_TEXT & operations ) )
972 const Vector<Character>& textToShape = textMirrored ? mirroredUtf32Characters : utf32Characters;
974 ShapeText( textToShape,
979 mTextUpdateInfo.mStartGlyphIndex,
980 requestedNumberOfCharacters,
982 glyphsToCharactersMap,
984 newParagraphGlyphs );
986 // Create the 'number of glyphs' per character and the glyph to character conversion tables.
987 mModel->mVisualModel->CreateGlyphsPerCharacterTable( startIndex, mTextUpdateInfo.mStartGlyphIndex, requestedNumberOfCharacters );
988 mModel->mVisualModel->CreateCharacterToGlyphTable( startIndex, mTextUpdateInfo.mStartGlyphIndex, requestedNumberOfCharacters );
992 const Length numberOfGlyphs = glyphs.Count() - currentNumberOfGlyphs;
994 if( NO_OPERATION != ( GET_GLYPH_METRICS & operations ) )
996 GlyphInfo* glyphsBuffer = glyphs.Begin();
997 mMetrics->GetGlyphMetrics( glyphsBuffer + mTextUpdateInfo.mStartGlyphIndex, numberOfGlyphs );
999 // Update the width and advance of all new paragraph characters.
1000 for( Vector<GlyphIndex>::ConstIterator it = newParagraphGlyphs.Begin(), endIt = newParagraphGlyphs.End(); it != endIt; ++it )
1002 const GlyphIndex index = *it;
1003 GlyphInfo& glyph = *( glyphsBuffer + index );
1005 glyph.xBearing = 0.f;
1007 glyph.advance = 0.f;
1012 if( NO_OPERATION != ( COLOR & operations ) )
1014 // Set the color runs in glyphs.
1015 SetColorSegmentationInfo( mModel->mLogicalModel->mColorRuns,
1016 mModel->mVisualModel->mCharactersToGlyph,
1017 mModel->mVisualModel->mGlyphsPerCharacter,
1019 mTextUpdateInfo.mStartGlyphIndex,
1020 requestedNumberOfCharacters,
1021 mModel->mVisualModel->mColors,
1022 mModel->mVisualModel->mColorIndices );
1027 if( ( NULL != mEventData ) &&
1028 mEventData->mPreEditFlag &&
1029 ( 0u != mModel->mVisualModel->mCharactersToGlyph.Count() ) )
1031 // Add the underline for the pre-edit text.
1032 const GlyphIndex* const charactersToGlyphBuffer = mModel->mVisualModel->mCharactersToGlyph.Begin();
1033 const Length* const glyphsPerCharacterBuffer = mModel->mVisualModel->mGlyphsPerCharacter.Begin();
1035 const GlyphIndex glyphStart = *( charactersToGlyphBuffer + mEventData->mPreEditStartPosition );
1036 const CharacterIndex lastPreEditCharacter = mEventData->mPreEditStartPosition + ( ( mEventData->mPreEditLength > 0u ) ? mEventData->mPreEditLength - 1u : 0u );
1037 const Length numberOfGlyphsLastCharacter = *( glyphsPerCharacterBuffer + lastPreEditCharacter );
1038 const GlyphIndex glyphEnd = *( charactersToGlyphBuffer + lastPreEditCharacter ) + ( numberOfGlyphsLastCharacter > 1u ? numberOfGlyphsLastCharacter - 1u : 0u );
1040 GlyphRun underlineRun;
1041 underlineRun.glyphIndex = glyphStart;
1042 underlineRun.numberOfGlyphs = 1u + glyphEnd - glyphStart;
1044 // TODO: At the moment the underline runs are only for pre-edit.
1045 mModel->mVisualModel->mUnderlineRuns.PushBack( underlineRun );
1048 // The estimated number of lines. Used to avoid reallocations when layouting.
1049 mTextUpdateInfo.mEstimatedNumberOfLines = std::max( mModel->mVisualModel->mLines.Count(), mModel->mLogicalModel->mParagraphInfo.Count() );
1051 // Set the previous number of characters for the next time the text is updated.
1052 mTextUpdateInfo.mPreviousNumberOfCharacters = numberOfCharacters;
1057 void Controller::Impl::RetrieveDefaultInputStyle( InputStyle& inputStyle )
1059 // Sets the default text's color.
1060 inputStyle.textColor = mTextColor;
1061 inputStyle.isDefaultColor = true;
1063 inputStyle.familyName.clear();
1064 inputStyle.weight = TextAbstraction::FontWeight::NORMAL;
1065 inputStyle.width = TextAbstraction::FontWidth::NORMAL;
1066 inputStyle.slant = TextAbstraction::FontSlant::NORMAL;
1067 inputStyle.size = 0.f;
1069 inputStyle.lineSpacing = 0.f;
1071 inputStyle.underlineProperties.clear();
1072 inputStyle.shadowProperties.clear();
1073 inputStyle.embossProperties.clear();
1074 inputStyle.outlineProperties.clear();
1076 inputStyle.isFamilyDefined = false;
1077 inputStyle.isWeightDefined = false;
1078 inputStyle.isWidthDefined = false;
1079 inputStyle.isSlantDefined = false;
1080 inputStyle.isSizeDefined = false;
1082 inputStyle.isLineSpacingDefined = false;
1084 inputStyle.isUnderlineDefined = false;
1085 inputStyle.isShadowDefined = false;
1086 inputStyle.isEmbossDefined = false;
1087 inputStyle.isOutlineDefined = false;
1089 // Sets the default font's family name, weight, width, slant and size.
1092 if( mFontDefaults->familyDefined )
1094 inputStyle.familyName = mFontDefaults->mFontDescription.family;
1095 inputStyle.isFamilyDefined = true;
1098 if( mFontDefaults->weightDefined )
1100 inputStyle.weight = mFontDefaults->mFontDescription.weight;
1101 inputStyle.isWeightDefined = true;
1104 if( mFontDefaults->widthDefined )
1106 inputStyle.width = mFontDefaults->mFontDescription.width;
1107 inputStyle.isWidthDefined = true;
1110 if( mFontDefaults->slantDefined )
1112 inputStyle.slant = mFontDefaults->mFontDescription.slant;
1113 inputStyle.isSlantDefined = true;
1116 if( mFontDefaults->sizeDefined )
1118 inputStyle.size = mFontDefaults->mDefaultPointSize;
1119 inputStyle.isSizeDefined = true;
1124 float Controller::Impl::GetDefaultFontLineHeight()
1126 FontId defaultFontId = 0u;
1127 if( NULL == mFontDefaults )
1129 TextAbstraction::FontDescription fontDescription;
1130 defaultFontId = mFontClient.GetFontId( fontDescription );
1134 defaultFontId = mFontDefaults->GetFontId( mFontClient );
1137 Text::FontMetrics fontMetrics;
1138 mMetrics->GetFontMetrics( defaultFontId, fontMetrics );
1140 return( fontMetrics.ascender - fontMetrics.descender );
1143 void Controller::Impl::OnCursorKeyEvent( const Event& event )
1145 if( NULL == mEventData )
1147 // Nothing to do if there is no text input.
1151 int keyCode = event.p1.mInt;
1153 if( Dali::DALI_KEY_CURSOR_LEFT == keyCode )
1155 if( mEventData->mPrimaryCursorPosition > 0u )
1157 mEventData->mPrimaryCursorPosition = CalculateNewCursorIndex( mEventData->mPrimaryCursorPosition - 1u );
1160 else if( Dali::DALI_KEY_CURSOR_RIGHT == keyCode )
1162 if( mModel->mLogicalModel->mText.Count() > mEventData->mPrimaryCursorPosition )
1164 mEventData->mPrimaryCursorPosition = CalculateNewCursorIndex( mEventData->mPrimaryCursorPosition );
1167 else if( Dali::DALI_KEY_CURSOR_UP == keyCode )
1169 // Get first the line index of the current cursor position index.
1170 CharacterIndex characterIndex = 0u;
1172 if( mEventData->mPrimaryCursorPosition > 0u )
1174 characterIndex = mEventData->mPrimaryCursorPosition - 1u;
1177 const LineIndex lineIndex = mModel->mVisualModel->GetLineOfCharacter( characterIndex );
1178 const LineIndex previousLineIndex = ( lineIndex > 0 ? lineIndex - 1u : lineIndex );
1180 // Retrieve the cursor position info.
1181 CursorInfo cursorInfo;
1182 GetCursorPosition( mEventData->mPrimaryCursorPosition,
1185 // Get the line above.
1186 const LineRun& line = *( mModel->mVisualModel->mLines.Begin() + previousLineIndex );
1188 // Get the next hit 'y' point.
1189 const float hitPointY = cursorInfo.lineOffset - 0.5f * ( line.ascender - line.descender );
1191 // Use the cursor hook position 'x' and the next hit 'y' position to calculate the new cursor index.
1192 bool matchedCharacter = false;
1193 mEventData->mPrimaryCursorPosition = Text::GetClosestCursorIndex( mModel->mVisualModel,
1194 mModel->mLogicalModel,
1196 mEventData->mCursorHookPositionX,
1198 CharacterHitTest::TAP,
1201 else if( Dali::DALI_KEY_CURSOR_DOWN == keyCode )
1203 // Get first the line index of the current cursor position index.
1204 CharacterIndex characterIndex = 0u;
1206 if( mEventData->mPrimaryCursorPosition > 0u )
1208 characterIndex = mEventData->mPrimaryCursorPosition - 1u;
1211 const LineIndex lineIndex = mModel->mVisualModel->GetLineOfCharacter( characterIndex );
1213 if( lineIndex + 1u < mModel->mVisualModel->mLines.Count() )
1215 // Retrieve the cursor position info.
1216 CursorInfo cursorInfo;
1217 GetCursorPosition( mEventData->mPrimaryCursorPosition,
1220 // Get the line below.
1221 const LineRun& line = *( mModel->mVisualModel->mLines.Begin() + lineIndex + 1u );
1223 // Get the next hit 'y' point.
1224 const float hitPointY = cursorInfo.lineOffset + cursorInfo.lineHeight + 0.5f * ( line.ascender - line.descender );
1226 // Use the cursor hook position 'x' and the next hit 'y' position to calculate the new cursor index.
1227 bool matchedCharacter = false;
1228 mEventData->mPrimaryCursorPosition = Text::GetClosestCursorIndex( mModel->mVisualModel,
1229 mModel->mLogicalModel,
1231 mEventData->mCursorHookPositionX,
1233 CharacterHitTest::TAP,
1238 mEventData->mUpdateCursorPosition = true;
1239 mEventData->mUpdateInputStyle = true;
1240 mEventData->mScrollAfterUpdatePosition = true;
1243 void Controller::Impl::OnTapEvent( const Event& event )
1245 if( NULL != mEventData )
1247 const unsigned int tapCount = event.p1.mUint;
1249 if( 1u == tapCount )
1251 if( IsShowingRealText() )
1253 // Convert from control's coords to text's coords.
1254 const float xPosition = event.p2.mFloat - mModel->mScrollPosition.x;
1255 const float yPosition = event.p3.mFloat - mModel->mScrollPosition.y;
1257 // Keep the tap 'x' position. Used to move the cursor.
1258 mEventData->mCursorHookPositionX = xPosition;
1260 // Whether to touch point hits on a glyph.
1261 bool matchedCharacter = false;
1262 mEventData->mPrimaryCursorPosition = Text::GetClosestCursorIndex( mModel->mVisualModel,
1263 mModel->mLogicalModel,
1267 CharacterHitTest::TAP,
1270 // When the cursor position is changing, delay cursor blinking
1271 mEventData->mDecorator->DelayCursorBlink();
1275 mEventData->mPrimaryCursorPosition = 0u;
1278 mEventData->mUpdateCursorPosition = true;
1279 mEventData->mUpdateGrabHandlePosition = true;
1280 mEventData->mScrollAfterUpdatePosition = true;
1281 mEventData->mUpdateInputStyle = true;
1283 // Notify the cursor position to the imf manager.
1284 if( mEventData->mImfManager )
1286 mEventData->mImfManager.SetCursorPosition( mEventData->mPrimaryCursorPosition );
1287 mEventData->mImfManager.NotifyCursorPosition();
1290 else if( 2u == tapCount )
1292 if( mEventData->mSelectionEnabled )
1294 // Convert from control's coords to text's coords.
1295 const float xPosition = event.p2.mFloat - mModel->mScrollPosition.x;
1296 const float yPosition = event.p3.mFloat - mModel->mScrollPosition.y;
1298 // Calculates the logical position from the x,y coords.
1299 RepositionSelectionHandles( xPosition,
1301 mEventData->mDoubleTapAction );
1307 void Controller::Impl::OnPanEvent( const Event& event )
1309 if( NULL == mEventData )
1311 // Nothing to do if there is no text input.
1315 const bool isHorizontalScrollEnabled = mEventData->mDecorator->IsHorizontalScrollEnabled();
1316 const bool isVerticalScrollEnabled = mEventData->mDecorator->IsVerticalScrollEnabled();
1318 if( !isHorizontalScrollEnabled && !isVerticalScrollEnabled )
1320 // Nothing to do if scrolling is not enabled.
1324 const int state = event.p1.mInt;
1328 case Gesture::Started:
1330 // Will remove the cursor, handles or text's popup, ...
1331 ChangeState( EventData::TEXT_PANNING );
1334 case Gesture::Continuing:
1336 const Vector2& layoutSize = mModel->mVisualModel->GetLayoutSize();
1337 const Vector2 currentScroll = mModel->mScrollPosition;
1339 if( isHorizontalScrollEnabled )
1341 const float displacementX = event.p2.mFloat;
1342 mModel->mScrollPosition.x += displacementX;
1344 ClampHorizontalScroll( layoutSize );
1347 if( isVerticalScrollEnabled )
1349 const float displacementY = event.p3.mFloat;
1350 mModel->mScrollPosition.y += displacementY;
1352 ClampVerticalScroll( layoutSize );
1355 mEventData->mDecorator->UpdatePositions( mModel->mScrollPosition - currentScroll );
1358 case Gesture::Finished:
1359 case Gesture::Cancelled: // FALLTHROUGH
1361 // Will go back to the previous state to show the cursor, handles, the text's popup, ...
1362 ChangeState( mEventData->mPreviousState );
1370 void Controller::Impl::OnLongPressEvent( const Event& event )
1372 DALI_LOG_INFO( gLogFilter, Debug::General, "Controller::OnLongPressEvent\n" );
1374 if( !IsShowingRealText() && ( EventData::EDITING == mEventData->mState ) )
1376 ChangeState( EventData::EDITING_WITH_POPUP );
1377 mEventData->mDecoratorUpdated = true;
1378 mEventData->mUpdateInputStyle = true;
1382 if( mEventData->mSelectionEnabled )
1384 // Convert from control's coords to text's coords.
1385 const float xPosition = event.p2.mFloat - mModel->mScrollPosition.x;
1386 const float yPosition = event.p3.mFloat - mModel->mScrollPosition.y;
1388 // Calculates the logical position from the x,y coords.
1389 RepositionSelectionHandles( xPosition,
1391 mEventData->mLongPressAction );
1396 void Controller::Impl::OnHandleEvent( const Event& event )
1398 if( NULL == mEventData )
1400 // Nothing to do if there is no text input.
1404 const unsigned int state = event.p1.mUint;
1405 const bool handleStopScrolling = ( HANDLE_STOP_SCROLLING == state );
1406 const bool isSmoothHandlePanEnabled = mEventData->mDecorator->IsSmoothHandlePanEnabled();
1408 if( HANDLE_PRESSED == state )
1410 // Convert from decorator's coords to text's coords.
1411 const float xPosition = event.p2.mFloat - mModel->mScrollPosition.x;
1412 const float yPosition = event.p3.mFloat - mModel->mScrollPosition.y;
1414 // Need to calculate the handle's new position.
1415 bool matchedCharacter = false;
1416 const CharacterIndex handleNewPosition = Text::GetClosestCursorIndex( mModel->mVisualModel,
1417 mModel->mLogicalModel,
1421 CharacterHitTest::SCROLL,
1424 if( Event::GRAB_HANDLE_EVENT == event.type )
1426 ChangeState ( EventData::GRAB_HANDLE_PANNING );
1428 if( handleNewPosition != mEventData->mPrimaryCursorPosition )
1430 // Updates the cursor position if the handle's new position is different than the current one.
1431 mEventData->mUpdateCursorPosition = true;
1432 // Does not update the grab handle position if the smooth panning is enabled. (The decorator does it smooth).
1433 mEventData->mUpdateGrabHandlePosition = !isSmoothHandlePanEnabled;
1434 mEventData->mPrimaryCursorPosition = handleNewPosition;
1437 // 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.
1438 mEventData->mDecoratorUpdated = isSmoothHandlePanEnabled;
1440 else if( Event::LEFT_SELECTION_HANDLE_EVENT == event.type )
1442 ChangeState ( EventData::SELECTION_HANDLE_PANNING );
1444 if( ( handleNewPosition != mEventData->mLeftSelectionPosition ) &&
1445 ( handleNewPosition != mEventData->mRightSelectionPosition ) )
1447 // Updates the highlight box if the handle's new position is different than the current one.
1448 mEventData->mUpdateHighlightBox = true;
1449 // Does not update the selection handle position if the smooth panning is enabled. (The decorator does it smooth).
1450 mEventData->mUpdateLeftSelectionPosition = !isSmoothHandlePanEnabled;
1451 mEventData->mLeftSelectionPosition = handleNewPosition;
1454 // 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.
1455 mEventData->mDecoratorUpdated = isSmoothHandlePanEnabled;
1457 // Will define the order to scroll the text to match the handle position.
1458 mEventData->mIsLeftHandleSelected = true;
1459 mEventData->mIsRightHandleSelected = false;
1461 else if( Event::RIGHT_SELECTION_HANDLE_EVENT == event.type )
1463 ChangeState ( EventData::SELECTION_HANDLE_PANNING );
1465 if( ( handleNewPosition != mEventData->mRightSelectionPosition ) &&
1466 ( handleNewPosition != mEventData->mLeftSelectionPosition ) )
1468 // Updates the highlight box if the handle's new position is different than the current one.
1469 mEventData->mUpdateHighlightBox = true;
1470 // Does not update the selection handle position if the smooth panning is enabled. (The decorator does it smooth).
1471 mEventData->mUpdateRightSelectionPosition = !isSmoothHandlePanEnabled;
1472 mEventData->mRightSelectionPosition = handleNewPosition;
1475 // 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.
1476 mEventData->mDecoratorUpdated = isSmoothHandlePanEnabled;
1478 // Will define the order to scroll the text to match the handle position.
1479 mEventData->mIsLeftHandleSelected = false;
1480 mEventData->mIsRightHandleSelected = true;
1482 } // end ( HANDLE_PRESSED == state )
1483 else if( ( HANDLE_RELEASED == state ) ||
1484 handleStopScrolling )
1486 CharacterIndex handlePosition = 0u;
1487 if( handleStopScrolling || isSmoothHandlePanEnabled )
1489 // Convert from decorator's coords to text's coords.
1490 const float xPosition = event.p2.mFloat - mModel->mScrollPosition.x;
1491 const float yPosition = event.p3.mFloat - mModel->mScrollPosition.y;
1493 bool matchedCharacter = false;
1494 handlePosition = Text::GetClosestCursorIndex( mModel->mVisualModel,
1495 mModel->mLogicalModel,
1499 CharacterHitTest::SCROLL,
1503 if( Event::GRAB_HANDLE_EVENT == event.type )
1505 mEventData->mUpdateCursorPosition = true;
1506 mEventData->mUpdateGrabHandlePosition = true;
1507 mEventData->mUpdateInputStyle = true;
1509 if( !IsClipboardEmpty() )
1511 ChangeState( EventData::EDITING_WITH_PASTE_POPUP ); // Moving grabhandle will show Paste Popup
1514 if( handleStopScrolling || isSmoothHandlePanEnabled )
1516 mEventData->mScrollAfterUpdatePosition = true;
1517 mEventData->mPrimaryCursorPosition = handlePosition;
1520 else if( Event::LEFT_SELECTION_HANDLE_EVENT == event.type )
1522 ChangeState( EventData::SELECTING );
1524 mEventData->mUpdateHighlightBox = true;
1525 mEventData->mUpdateLeftSelectionPosition = true;
1526 mEventData->mUpdateRightSelectionPosition = true;
1528 if( handleStopScrolling || isSmoothHandlePanEnabled )
1530 mEventData->mScrollAfterUpdatePosition = true;
1532 if( ( handlePosition != mEventData->mRightSelectionPosition ) &&
1533 ( handlePosition != mEventData->mLeftSelectionPosition ) )
1535 mEventData->mLeftSelectionPosition = handlePosition;
1539 else if( Event::RIGHT_SELECTION_HANDLE_EVENT == event.type )
1541 ChangeState( EventData::SELECTING );
1543 mEventData->mUpdateHighlightBox = true;
1544 mEventData->mUpdateRightSelectionPosition = true;
1545 mEventData->mUpdateLeftSelectionPosition = true;
1547 if( handleStopScrolling || isSmoothHandlePanEnabled )
1549 mEventData->mScrollAfterUpdatePosition = true;
1550 if( ( handlePosition != mEventData->mRightSelectionPosition ) &&
1551 ( handlePosition != mEventData->mLeftSelectionPosition ) )
1553 mEventData->mRightSelectionPosition = handlePosition;
1558 mEventData->mDecoratorUpdated = true;
1559 } // end ( ( HANDLE_RELEASED == state ) || ( HANDLE_STOP_SCROLLING == state ) )
1560 else if( HANDLE_SCROLLING == state )
1562 const float xSpeed = event.p2.mFloat;
1563 const float ySpeed = event.p3.mFloat;
1564 const Vector2& layoutSize = mModel->mVisualModel->GetLayoutSize();
1565 const Vector2 currentScrollPosition = mModel->mScrollPosition;
1567 mModel->mScrollPosition.x += xSpeed;
1568 mModel->mScrollPosition.y += ySpeed;
1570 ClampHorizontalScroll( layoutSize );
1571 ClampVerticalScroll( layoutSize );
1573 bool endOfScroll = false;
1574 if( Vector2::ZERO == ( currentScrollPosition - mModel->mScrollPosition ) )
1576 // Notify the decorator there is no more text to scroll.
1577 // The decorator won't send more scroll events.
1578 mEventData->mDecorator->NotifyEndOfScroll();
1579 // Still need to set the position of the handle.
1583 // Set the position of the handle.
1584 const bool scrollRightDirection = xSpeed > 0.f;
1585 const bool scrollBottomDirection = ySpeed > 0.f;
1586 const bool leftSelectionHandleEvent = Event::LEFT_SELECTION_HANDLE_EVENT == event.type;
1587 const bool rightSelectionHandleEvent = Event::RIGHT_SELECTION_HANDLE_EVENT == event.type;
1589 if( Event::GRAB_HANDLE_EVENT == event.type )
1591 ChangeState( EventData::GRAB_HANDLE_PANNING );
1593 // Get the grab handle position in decorator coords.
1594 Vector2 position = mEventData->mDecorator->GetPosition( GRAB_HANDLE );
1596 if( mEventData->mDecorator->IsHorizontalScrollEnabled() )
1598 // Position the grag handle close to either the left or right edge.
1599 position.x = scrollRightDirection ? 0.f : mModel->mVisualModel->mControlSize.width;
1602 if( mEventData->mDecorator->IsVerticalScrollEnabled() )
1604 position.x = mEventData->mCursorHookPositionX;
1606 // Position the grag handle close to either the top or bottom edge.
1607 position.y = scrollBottomDirection ? 0.f : mModel->mVisualModel->mControlSize.height;
1610 // Get the new handle position.
1611 // The grab handle's position is in decorator's coords. Need to transforms to text's coords.
1612 bool matchedCharacter = false;
1613 const CharacterIndex handlePosition = Text::GetClosestCursorIndex( mModel->mVisualModel,
1614 mModel->mLogicalModel,
1616 position.x - mModel->mScrollPosition.x,
1617 position.y - mModel->mScrollPosition.y,
1618 CharacterHitTest::SCROLL,
1621 if( mEventData->mPrimaryCursorPosition != handlePosition )
1623 mEventData->mUpdateCursorPosition = true;
1624 mEventData->mUpdateGrabHandlePosition = !isSmoothHandlePanEnabled;
1625 mEventData->mScrollAfterUpdatePosition = true;
1626 mEventData->mPrimaryCursorPosition = handlePosition;
1628 mEventData->mUpdateInputStyle = mEventData->mUpdateCursorPosition;
1630 // Updates the decorator if the soft handle panning is enabled.
1631 mEventData->mDecoratorUpdated = isSmoothHandlePanEnabled;
1633 else if( leftSelectionHandleEvent || rightSelectionHandleEvent )
1635 ChangeState( EventData::SELECTION_HANDLE_PANNING );
1637 // Get the selection handle position in decorator coords.
1638 Vector2 position = mEventData->mDecorator->GetPosition( leftSelectionHandleEvent ? Text::LEFT_SELECTION_HANDLE : Text::RIGHT_SELECTION_HANDLE );
1640 if( mEventData->mDecorator->IsHorizontalScrollEnabled() )
1642 // Position the selection handle close to either the left or right edge.
1643 position.x = scrollRightDirection ? 0.f : mModel->mVisualModel->mControlSize.width;
1646 if( mEventData->mDecorator->IsVerticalScrollEnabled() )
1648 position.x = mEventData->mCursorHookPositionX;
1650 // Position the grag handle close to either the top or bottom edge.
1651 position.y = scrollBottomDirection ? 0.f : mModel->mVisualModel->mControlSize.height;
1654 // Get the new handle position.
1655 // The selection handle's position is in decorator's coords. Need to transform to text's coords.
1656 bool matchedCharacter = false;
1657 const CharacterIndex handlePosition = Text::GetClosestCursorIndex( mModel->mVisualModel,
1658 mModel->mLogicalModel,
1660 position.x - mModel->mScrollPosition.x,
1661 position.y - mModel->mScrollPosition.y,
1662 CharacterHitTest::SCROLL,
1665 if( leftSelectionHandleEvent )
1667 const bool differentHandles = ( mEventData->mLeftSelectionPosition != handlePosition ) && ( mEventData->mRightSelectionPosition != handlePosition );
1669 if( differentHandles || endOfScroll )
1671 mEventData->mUpdateHighlightBox = true;
1672 mEventData->mUpdateLeftSelectionPosition = !isSmoothHandlePanEnabled;
1673 mEventData->mUpdateRightSelectionPosition = isSmoothHandlePanEnabled;
1674 mEventData->mLeftSelectionPosition = handlePosition;
1679 const bool differentHandles = ( mEventData->mRightSelectionPosition != handlePosition ) && ( mEventData->mLeftSelectionPosition != handlePosition );
1680 if( differentHandles || endOfScroll )
1682 mEventData->mUpdateHighlightBox = true;
1683 mEventData->mUpdateRightSelectionPosition = !isSmoothHandlePanEnabled;
1684 mEventData->mUpdateLeftSelectionPosition = isSmoothHandlePanEnabled;
1685 mEventData->mRightSelectionPosition = handlePosition;
1689 if( mEventData->mUpdateLeftSelectionPosition || mEventData->mUpdateRightSelectionPosition )
1691 RepositionSelectionHandles();
1693 mEventData->mScrollAfterUpdatePosition = !isSmoothHandlePanEnabled;
1696 mEventData->mDecoratorUpdated = true;
1697 } // end ( HANDLE_SCROLLING == state )
1700 void Controller::Impl::OnSelectEvent( const Event& event )
1702 if( NULL == mEventData )
1704 // Nothing to do if there is no text.
1708 if( mEventData->mSelectionEnabled )
1710 // Convert from control's coords to text's coords.
1711 const float xPosition = event.p2.mFloat - mModel->mScrollPosition.x;
1712 const float yPosition = event.p3.mFloat - mModel->mScrollPosition.y;
1714 // Calculates the logical position from the x,y coords.
1715 RepositionSelectionHandles( xPosition,
1717 Controller::NoTextTap::HIGHLIGHT );
1721 void Controller::Impl::OnSelectAllEvent()
1723 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "OnSelectAllEvent mEventData->mSelectionEnabled%s \n", mEventData->mSelectionEnabled?"true":"false");
1725 if( NULL == mEventData )
1727 // Nothing to do if there is no text.
1731 if( mEventData->mSelectionEnabled )
1733 ChangeState( EventData::SELECTING );
1735 mEventData->mLeftSelectionPosition = 0u;
1736 mEventData->mRightSelectionPosition = mModel->mLogicalModel->mText.Count();
1738 mEventData->mScrollAfterUpdatePosition = true;
1739 mEventData->mUpdateLeftSelectionPosition = true;
1740 mEventData->mUpdateRightSelectionPosition = true;
1741 mEventData->mUpdateHighlightBox = true;
1745 void Controller::Impl::RetrieveSelection( std::string& selectedText, bool deleteAfterRetrieval )
1747 if( mEventData->mLeftSelectionPosition == mEventData->mRightSelectionPosition )
1749 // Nothing to select if handles are in the same place.
1750 selectedText.clear();
1754 const bool handlesCrossed = mEventData->mLeftSelectionPosition > mEventData->mRightSelectionPosition;
1756 //Get start and end position of selection
1757 const CharacterIndex startOfSelectedText = handlesCrossed ? mEventData->mRightSelectionPosition : mEventData->mLeftSelectionPosition;
1758 const Length lengthOfSelectedText = ( handlesCrossed ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition ) - startOfSelectedText;
1760 Vector<Character>& utf32Characters = mModel->mLogicalModel->mText;
1761 const Length numberOfCharacters = utf32Characters.Count();
1763 // Validate the start and end selection points
1764 if( ( startOfSelectedText + lengthOfSelectedText ) <= numberOfCharacters )
1766 //Get text as a UTF8 string
1767 Utf32ToUtf8( &utf32Characters[startOfSelectedText], lengthOfSelectedText, selectedText );
1769 if( deleteAfterRetrieval ) // Only delete text if copied successfully
1771 // Keep a copy of the current input style.
1772 InputStyle currentInputStyle;
1773 currentInputStyle.Copy( mEventData->mInputStyle );
1775 // Set as input style the style of the first deleted character.
1776 mModel->mLogicalModel->RetrieveStyle( startOfSelectedText, mEventData->mInputStyle );
1778 // Compare if the input style has changed.
1779 const bool hasInputStyleChanged = !currentInputStyle.Equal( mEventData->mInputStyle );
1781 if( hasInputStyleChanged )
1783 const InputStyle::Mask styleChangedMask = currentInputStyle.GetInputStyleChangeMask( mEventData->mInputStyle );
1784 // Queue the input style changed signal.
1785 mEventData->mInputStyleChangedQueue.PushBack( styleChangedMask );
1788 mModel->mLogicalModel->UpdateTextStyleRuns( startOfSelectedText, -static_cast<int>( lengthOfSelectedText ) );
1790 // Mark the paragraphs to be updated.
1791 if( Layout::Engine::SINGLE_LINE_BOX == mLayoutEngine.GetLayout() )
1793 mTextUpdateInfo.mCharacterIndex = 0;
1794 mTextUpdateInfo.mNumberOfCharactersToRemove = mTextUpdateInfo.mPreviousNumberOfCharacters;
1795 mTextUpdateInfo.mNumberOfCharactersToAdd = mTextUpdateInfo.mPreviousNumberOfCharacters - lengthOfSelectedText;
1796 mTextUpdateInfo.mClearAll = true;
1800 mTextUpdateInfo.mCharacterIndex = startOfSelectedText;
1801 mTextUpdateInfo.mNumberOfCharactersToRemove = lengthOfSelectedText;
1804 // Delete text between handles
1805 Vector<Character>::Iterator first = utf32Characters.Begin() + startOfSelectedText;
1806 Vector<Character>::Iterator last = first + lengthOfSelectedText;
1807 utf32Characters.Erase( first, last );
1809 // Will show the cursor at the first character of the selection.
1810 mEventData->mPrimaryCursorPosition = handlesCrossed ? mEventData->mRightSelectionPosition : mEventData->mLeftSelectionPosition;
1814 // Will show the cursor at the last character of the selection.
1815 mEventData->mPrimaryCursorPosition = handlesCrossed ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition;
1818 mEventData->mDecoratorUpdated = true;
1822 void Controller::Impl::ShowClipboard()
1826 mClipboard.ShowClipboard();
1830 void Controller::Impl::HideClipboard()
1832 if( mClipboard && mClipboardHideEnabled )
1834 mClipboard.HideClipboard();
1838 void Controller::Impl::SetClipboardHideEnable(bool enable)
1840 mClipboardHideEnabled = enable;
1843 bool Controller::Impl::CopyStringToClipboard( std::string& source )
1845 //Send string to clipboard
1846 return ( mClipboard && mClipboard.SetItem( source ) );
1849 void Controller::Impl::SendSelectionToClipboard( bool deleteAfterSending )
1851 std::string selectedText;
1852 RetrieveSelection( selectedText, deleteAfterSending );
1853 CopyStringToClipboard( selectedText );
1854 ChangeState( EventData::EDITING );
1857 void Controller::Impl::RequestGetTextFromClipboard()
1861 mClipboard.RequestItem();
1865 void Controller::Impl::RepositionSelectionHandles()
1867 CharacterIndex selectionStart = mEventData->mLeftSelectionPosition;
1868 CharacterIndex selectionEnd = mEventData->mRightSelectionPosition;
1870 if( selectionStart == selectionEnd )
1872 // Nothing to select if handles are in the same place.
1876 mEventData->mDecorator->ClearHighlights();
1878 const GlyphIndex* const charactersToGlyphBuffer = mModel->mVisualModel->mCharactersToGlyph.Begin();
1879 const Length* const glyphsPerCharacterBuffer = mModel->mVisualModel->mGlyphsPerCharacter.Begin();
1880 const GlyphInfo* const glyphsBuffer = mModel->mVisualModel->mGlyphs.Begin();
1881 const Vector2* const positionsBuffer = mModel->mVisualModel->mGlyphPositions.Begin();
1882 const Length* const charactersPerGlyphBuffer = mModel->mVisualModel->mCharactersPerGlyph.Begin();
1883 const CharacterIndex* const glyphToCharacterBuffer = mModel->mVisualModel->mGlyphsToCharacters.Begin();
1884 const CharacterDirection* const modelCharacterDirectionsBuffer = ( 0u != mModel->mLogicalModel->mCharacterDirections.Count() ) ? mModel->mLogicalModel->mCharacterDirections.Begin() : NULL;
1886 const bool isLastCharacter = selectionEnd >= mModel->mLogicalModel->mText.Count();
1887 const CharacterDirection startDirection = ( ( NULL == modelCharacterDirectionsBuffer ) ? false : *( modelCharacterDirectionsBuffer + selectionStart ) );
1888 const CharacterDirection endDirection = ( ( NULL == modelCharacterDirectionsBuffer ) ? false : *( modelCharacterDirectionsBuffer + ( selectionEnd - ( isLastCharacter ? 1u : 0u ) ) ) );
1890 // Swap the indices if the start is greater than the end.
1891 const bool indicesSwapped = selectionStart > selectionEnd;
1893 // Tell the decorator to flip the selection handles if needed.
1894 mEventData->mDecorator->SetSelectionHandleFlipState( indicesSwapped, startDirection, endDirection );
1896 if( indicesSwapped )
1898 std::swap( selectionStart, selectionEnd );
1901 // Get the indices to the first and last selected glyphs.
1902 const CharacterIndex selectionEndMinusOne = selectionEnd - 1u;
1903 const GlyphIndex glyphStart = *( charactersToGlyphBuffer + selectionStart );
1904 const Length numberOfGlyphs = *( glyphsPerCharacterBuffer + selectionEndMinusOne );
1905 const GlyphIndex glyphEnd = *( charactersToGlyphBuffer + selectionEndMinusOne ) + ( ( numberOfGlyphs > 0 ) ? numberOfGlyphs - 1u : 0u );
1907 // Get the lines where the glyphs are laid-out.
1908 const LineRun* lineRun = mModel->mVisualModel->mLines.Begin();
1910 LineIndex lineIndex = 0u;
1911 Length numberOfLines = 0u;
1912 mModel->mVisualModel->GetNumberOfLines( glyphStart,
1913 1u + glyphEnd - glyphStart,
1916 const LineIndex firstLineIndex = lineIndex;
1918 // Create the structure to store some selection box info.
1919 Vector<SelectionBoxInfo> selectionBoxLinesInfo;
1920 selectionBoxLinesInfo.Resize( numberOfLines );
1922 SelectionBoxInfo* selectionBoxInfo = selectionBoxLinesInfo.Begin();
1923 selectionBoxInfo->minX = MAX_FLOAT;
1924 selectionBoxInfo->maxX = MIN_FLOAT;
1926 // Keep the min and max 'x' position to calculate the size and position of the highlighed text.
1927 float minHighlightX = std::numeric_limits<float>::max();
1928 float maxHighlightX = std::numeric_limits<float>::min();
1930 Vector2 highLightPosition; // The highlight position in decorator's coords.
1932 // Retrieve the first line and get the line's vertical offset, the line's height and the index to the last glyph.
1934 // The line's vertical offset of all the lines before the line where the first glyph is laid-out.
1935 selectionBoxInfo->lineOffset = CalculateLineOffset( mModel->mVisualModel->mLines,
1938 // Transform to decorator's (control) coords.
1939 selectionBoxInfo->lineOffset += mModel->mScrollPosition.y;
1941 lineRun += firstLineIndex;
1943 // The line height is the addition of the line ascender and the line descender.
1944 // However, the line descender has a negative value, hence the subtraction.
1945 selectionBoxInfo->lineHeight = lineRun->ascender - lineRun->descender;
1947 GlyphIndex lastGlyphOfLine = lineRun->glyphRun.glyphIndex + lineRun->glyphRun.numberOfGlyphs - 1u;
1949 // Check if the first glyph is a ligature that must be broken like Latin ff, fi, or Arabic ï»», etc which needs special code.
1950 const Length numberOfCharactersStart = *( charactersPerGlyphBuffer + glyphStart );
1951 bool splitStartGlyph = ( numberOfCharactersStart > 1u ) && HasLigatureMustBreak( mModel->mLogicalModel->GetScript( selectionStart ) );
1953 // Check if the last glyph is a ligature that must be broken like Latin ff, fi, or Arabic ï»», etc which needs special code.
1954 const Length numberOfCharactersEnd = *( charactersPerGlyphBuffer + glyphEnd );
1955 bool splitEndGlyph = ( glyphStart != glyphEnd ) && ( numberOfCharactersEnd > 1u ) && HasLigatureMustBreak( mModel->mLogicalModel->GetScript( selectionEndMinusOne ) );
1957 // The number of quads of the selection box.
1958 const unsigned int numberOfQuads = 1u + ( glyphEnd - glyphStart ) + ( ( numberOfLines > 1u ) ? 2u * numberOfLines : 0u );
1959 mEventData->mDecorator->ResizeHighlightQuads( numberOfQuads );
1961 // Count the actual number of quads.
1962 unsigned int actualNumberOfQuads = 0u;
1965 // Traverse the glyphs.
1966 for( GlyphIndex index = glyphStart; index <= glyphEnd; ++index )
1968 const GlyphInfo& glyph = *( glyphsBuffer + index );
1969 const Vector2& position = *( positionsBuffer + index );
1971 if( splitStartGlyph )
1973 // 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.
1975 const float glyphAdvance = glyph.advance / static_cast<float>( numberOfCharactersStart );
1976 const CharacterIndex interGlyphIndex = selectionStart - *( glyphToCharacterBuffer + glyphStart );
1977 // Get the direction of the character.
1978 CharacterDirection isCurrentRightToLeft = false;
1979 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
1981 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + selectionStart );
1984 // The end point could be in the middle of the ligature.
1985 // Calculate the number of characters selected.
1986 const Length numberOfCharacters = ( glyphStart == glyphEnd ) ? ( selectionEnd - selectionStart ) : ( numberOfCharactersStart - interGlyphIndex );
1988 quad.x = lineRun->alignmentOffset + position.x - glyph.xBearing + mModel->mScrollPosition.x + glyphAdvance * static_cast<float>( isCurrentRightToLeft ? ( numberOfCharactersStart - interGlyphIndex - numberOfCharacters ) : interGlyphIndex );
1989 quad.y = selectionBoxInfo->lineOffset;
1990 quad.z = quad.x + static_cast<float>( numberOfCharacters ) * glyphAdvance;
1991 quad.w = selectionBoxInfo->lineOffset + selectionBoxInfo->lineHeight;
1993 // Store the min and max 'x' for each line.
1994 selectionBoxInfo->minX = std::min( selectionBoxInfo->minX, quad.x );
1995 selectionBoxInfo->maxX = std::max( selectionBoxInfo->maxX, quad.z );
1997 mEventData->mDecorator->AddHighlight( actualNumberOfQuads, quad );
1998 ++actualNumberOfQuads;
2000 splitStartGlyph = false;
2004 if( splitEndGlyph && ( index == glyphEnd ) )
2006 // 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.
2008 const float glyphAdvance = glyph.advance / static_cast<float>( numberOfCharactersEnd );
2009 const CharacterIndex interGlyphIndex = selectionEnd - *( glyphToCharacterBuffer + glyphEnd );
2010 // Get the direction of the character.
2011 CharacterDirection isCurrentRightToLeft = false;
2012 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
2014 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + selectionEnd );
2017 const Length numberOfCharacters = numberOfCharactersEnd - interGlyphIndex;
2019 quad.x = lineRun->alignmentOffset + position.x - glyph.xBearing + mModel->mScrollPosition.x + ( isCurrentRightToLeft ? ( glyphAdvance * static_cast<float>( numberOfCharacters ) ) : 0.f );
2020 quad.y = selectionBoxInfo->lineOffset;
2021 quad.z = quad.x + static_cast<float>( interGlyphIndex ) * glyphAdvance;
2022 quad.w = quad.y + selectionBoxInfo->lineHeight;
2024 // Store the min and max 'x' for each line.
2025 selectionBoxInfo->minX = std::min( selectionBoxInfo->minX, quad.x );
2026 selectionBoxInfo->maxX = std::max( selectionBoxInfo->maxX, quad.z );
2028 mEventData->mDecorator->AddHighlight( actualNumberOfQuads,
2030 ++actualNumberOfQuads;
2032 splitEndGlyph = false;
2036 quad.x = lineRun->alignmentOffset + position.x - glyph.xBearing + mModel->mScrollPosition.x;
2037 quad.y = selectionBoxInfo->lineOffset;
2038 quad.z = quad.x + glyph.advance;
2039 quad.w = quad.y + selectionBoxInfo->lineHeight;
2041 // Store the min and max 'x' for each line.
2042 selectionBoxInfo->minX = std::min( selectionBoxInfo->minX, quad.x );
2043 selectionBoxInfo->maxX = std::max( selectionBoxInfo->maxX, quad.z );
2045 mEventData->mDecorator->AddHighlight( actualNumberOfQuads,
2047 ++actualNumberOfQuads;
2049 // Whether to retrieve the next line.
2050 if( index == lastGlyphOfLine )
2053 if( lineIndex < firstLineIndex + numberOfLines )
2055 // Retrieve the next line.
2058 // Get the last glyph of the new line.
2059 lastGlyphOfLine = lineRun->glyphRun.glyphIndex + lineRun->glyphRun.numberOfGlyphs - 1u;
2061 // Keep the offset and height of the current selection box.
2062 const float currentLineOffset = selectionBoxInfo->lineOffset;
2063 const float currentLineHeight = selectionBoxInfo->lineHeight;
2065 // Get the selection box info for the next line.
2068 selectionBoxInfo->minX = MAX_FLOAT;
2069 selectionBoxInfo->maxX = MIN_FLOAT;
2071 // Update the line's vertical offset.
2072 selectionBoxInfo->lineOffset = currentLineOffset + currentLineHeight;
2074 // The line height is the addition of the line ascender and the line descender.
2075 // However, the line descender has a negative value, hence the subtraction.
2076 selectionBoxInfo->lineHeight = lineRun->ascender - lineRun->descender;
2081 // Traverses all the lines and updates the min and max 'x' positions and the total height.
2082 // The final width is calculated after 'boxifying' the selection.
2083 for( Vector<SelectionBoxInfo>::ConstIterator it = selectionBoxLinesInfo.Begin(),
2084 endIt = selectionBoxLinesInfo.End();
2088 const SelectionBoxInfo& info = *it;
2090 // Update the size of the highlighted text.
2091 highLightSize.height += info.lineHeight;
2092 minHighlightX = std::min( minHighlightX, info.minX );
2093 maxHighlightX = std::max( maxHighlightX, info.maxX );
2096 // Add extra geometry to 'boxify' the selection.
2098 if( 1u < numberOfLines )
2100 // Boxify the first line.
2101 lineRun = mModel->mVisualModel->mLines.Begin() + firstLineIndex;
2102 const SelectionBoxInfo& firstSelectionBoxLineInfo = *( selectionBoxLinesInfo.Begin() );
2104 bool boxifyBegin = ( LTR != lineRun->direction ) && ( LTR != startDirection );
2105 bool boxifyEnd = ( LTR == lineRun->direction ) && ( LTR == startDirection );
2110 quad.y = firstSelectionBoxLineInfo.lineOffset;
2111 quad.z = firstSelectionBoxLineInfo.minX;
2112 quad.w = firstSelectionBoxLineInfo.lineOffset + firstSelectionBoxLineInfo.lineHeight;
2114 // Boxify at the beginning of the line.
2115 mEventData->mDecorator->AddHighlight( actualNumberOfQuads,
2117 ++actualNumberOfQuads;
2119 // Update the size of the highlighted text.
2120 minHighlightX = 0.f;
2125 quad.x = firstSelectionBoxLineInfo.maxX;
2126 quad.y = firstSelectionBoxLineInfo.lineOffset;
2127 quad.z = mModel->mVisualModel->mControlSize.width;
2128 quad.w = firstSelectionBoxLineInfo.lineOffset + firstSelectionBoxLineInfo.lineHeight;
2130 // Boxify at the end of the line.
2131 mEventData->mDecorator->AddHighlight( actualNumberOfQuads,
2133 ++actualNumberOfQuads;
2135 // Update the size of the highlighted text.
2136 maxHighlightX = mModel->mVisualModel->mControlSize.width;
2139 // Boxify the central lines.
2140 if( 2u < numberOfLines )
2142 for( Vector<SelectionBoxInfo>::ConstIterator it = selectionBoxLinesInfo.Begin() + 1u,
2143 endIt = selectionBoxLinesInfo.End() - 1u;
2147 const SelectionBoxInfo& info = *it;
2150 quad.y = info.lineOffset;
2152 quad.w = info.lineOffset + info.lineHeight;
2154 mEventData->mDecorator->AddHighlight( actualNumberOfQuads,
2156 ++actualNumberOfQuads;
2159 quad.y = info.lineOffset;
2160 quad.z = mModel->mVisualModel->mControlSize.width;
2161 quad.w = info.lineOffset + info.lineHeight;
2163 mEventData->mDecorator->AddHighlight( actualNumberOfQuads,
2165 ++actualNumberOfQuads;
2168 // Update the size of the highlighted text.
2169 minHighlightX = 0.f;
2170 maxHighlightX = mModel->mVisualModel->mControlSize.width;
2173 // Boxify the last line.
2174 lineRun = mModel->mVisualModel->mLines.Begin() + firstLineIndex + numberOfLines - 1u;
2175 const SelectionBoxInfo& lastSelectionBoxLineInfo = *( selectionBoxLinesInfo.End() - 1u );
2177 boxifyBegin = ( LTR == lineRun->direction ) && ( LTR == endDirection );
2178 boxifyEnd = ( LTR != lineRun->direction ) && ( LTR != endDirection );
2183 quad.y = lastSelectionBoxLineInfo.lineOffset;
2184 quad.z = lastSelectionBoxLineInfo.minX;
2185 quad.w = lastSelectionBoxLineInfo.lineOffset + lastSelectionBoxLineInfo.lineHeight;
2187 // Boxify at the beginning of the line.
2188 mEventData->mDecorator->AddHighlight( actualNumberOfQuads,
2190 ++actualNumberOfQuads;
2192 // Update the size of the highlighted text.
2193 minHighlightX = 0.f;
2198 quad.x = lastSelectionBoxLineInfo.maxX;
2199 quad.y = lastSelectionBoxLineInfo.lineOffset;
2200 quad.z = mModel->mVisualModel->mControlSize.width;
2201 quad.w = lastSelectionBoxLineInfo.lineOffset + lastSelectionBoxLineInfo.lineHeight;
2203 // Boxify at the end of the line.
2204 mEventData->mDecorator->AddHighlight( actualNumberOfQuads,
2206 ++actualNumberOfQuads;
2208 // Update the size of the highlighted text.
2209 maxHighlightX = mModel->mVisualModel->mControlSize.width;
2213 // Set the actual number of quads.
2214 mEventData->mDecorator->ResizeHighlightQuads( actualNumberOfQuads );
2216 // Sets the highlight's size and position. In decorator's coords.
2217 // The highlight's height has been calculated above (before 'boxifying' the highlight).
2218 highLightSize.width = maxHighlightX - minHighlightX;
2220 highLightPosition.x = minHighlightX;
2221 const SelectionBoxInfo& firstSelectionBoxLineInfo = *( selectionBoxLinesInfo.Begin() );
2222 highLightPosition.y = firstSelectionBoxLineInfo.lineOffset;
2224 mEventData->mDecorator->SetHighLightBox( highLightPosition, highLightSize );
2226 if( !mEventData->mDecorator->IsSmoothHandlePanEnabled() )
2228 CursorInfo primaryCursorInfo;
2229 GetCursorPosition( mEventData->mLeftSelectionPosition,
2230 primaryCursorInfo );
2232 const Vector2 primaryPosition = primaryCursorInfo.primaryPosition + mModel->mScrollPosition;
2234 mEventData->mDecorator->SetPosition( LEFT_SELECTION_HANDLE,
2236 primaryCursorInfo.lineOffset + mModel->mScrollPosition.y,
2237 primaryCursorInfo.lineHeight );
2239 CursorInfo secondaryCursorInfo;
2240 GetCursorPosition( mEventData->mRightSelectionPosition,
2241 secondaryCursorInfo );
2243 const Vector2 secondaryPosition = secondaryCursorInfo.primaryPosition + mModel->mScrollPosition;
2245 mEventData->mDecorator->SetPosition( RIGHT_SELECTION_HANDLE,
2246 secondaryPosition.x,
2247 secondaryCursorInfo.lineOffset + mModel->mScrollPosition.y,
2248 secondaryCursorInfo.lineHeight );
2251 // Cursor to be positioned at end of selection so if selection interrupted and edit mode restarted the cursor will be at end of selection
2252 mEventData->mPrimaryCursorPosition = ( indicesSwapped ) ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition;
2254 // Set the flag to update the decorator.
2255 mEventData->mDecoratorUpdated = true;
2258 void Controller::Impl::RepositionSelectionHandles( float visualX, float visualY, Controller::NoTextTap::Action action )
2260 if( NULL == mEventData )
2262 // Nothing to do if there is no text input.
2266 if( IsShowingPlaceholderText() )
2268 // Nothing to do if there is the place-holder text.
2272 const Length numberOfGlyphs = mModel->mVisualModel->mGlyphs.Count();
2273 const Length numberOfLines = mModel->mVisualModel->mLines.Count();
2274 if( ( 0 == numberOfGlyphs ) ||
2275 ( 0 == numberOfLines ) )
2277 // Nothing to do if there is no text.
2281 // Find which word was selected
2282 CharacterIndex selectionStart( 0 );
2283 CharacterIndex selectionEnd( 0 );
2284 CharacterIndex noTextHitIndex( 0 );
2285 const bool characterHit = FindSelectionIndices( mModel->mVisualModel,
2286 mModel->mLogicalModel,
2293 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "%p selectionStart %d selectionEnd %d\n", this, selectionStart, selectionEnd );
2295 if( characterHit || ( Controller::NoTextTap::HIGHLIGHT == action ) )
2297 ChangeState( EventData::SELECTING );
2299 mEventData->mLeftSelectionPosition = selectionStart;
2300 mEventData->mRightSelectionPosition = selectionEnd;
2302 mEventData->mUpdateLeftSelectionPosition = true;
2303 mEventData->mUpdateRightSelectionPosition = true;
2304 mEventData->mUpdateHighlightBox = true;
2306 // It may happen an IMF commit event arrives before the selection event
2307 // if the IMF manager is in pre-edit state. The commit event will set the
2308 // mEventData->mUpdateCursorPosition flag to true. If it's not set back
2309 // to false, the highlight box won't be updated.
2310 mEventData->mUpdateCursorPosition = false;
2312 mEventData->mScrollAfterUpdatePosition = ( mEventData->mLeftSelectionPosition != mEventData->mRightSelectionPosition );
2314 else if( Controller::NoTextTap::SHOW_SELECTION_POPUP == action )
2316 // Nothing to select. i.e. a white space, out of bounds
2317 ChangeState( EventData::EDITING_WITH_POPUP );
2319 mEventData->mPrimaryCursorPosition = noTextHitIndex;
2321 mEventData->mUpdateCursorPosition = true;
2322 mEventData->mUpdateGrabHandlePosition = true;
2323 mEventData->mScrollAfterUpdatePosition = true;
2324 mEventData->mUpdateInputStyle = true;
2326 else if( Controller::NoTextTap::NO_ACTION == action )
2328 // Nothing to select. i.e. a white space, out of bounds
2329 mEventData->mPrimaryCursorPosition = noTextHitIndex;
2331 mEventData->mUpdateCursorPosition = true;
2332 mEventData->mUpdateGrabHandlePosition = true;
2333 mEventData->mScrollAfterUpdatePosition = true;
2334 mEventData->mUpdateInputStyle = true;
2338 void Controller::Impl::SetPopupButtons()
2341 * Sets the Popup buttons to be shown depending on State.
2343 * If SELECTING : CUT & COPY + ( PASTE & CLIPBOARD if content available to paste )
2345 * If EDITING_WITH_POPUP : SELECT & SELECT_ALL
2348 TextSelectionPopup::Buttons buttonsToShow = TextSelectionPopup::NONE;
2350 if( EventData::SELECTING == mEventData->mState )
2352 buttonsToShow = TextSelectionPopup::Buttons( TextSelectionPopup::CUT | TextSelectionPopup::COPY );
2354 if( !IsClipboardEmpty() )
2356 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
2357 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::CLIPBOARD ) );
2360 if( !mEventData->mAllTextSelected )
2362 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::SELECT_ALL ) );
2365 else if( EventData::EDITING_WITH_POPUP == mEventData->mState )
2367 if( mModel->mLogicalModel->mText.Count() && !IsShowingPlaceholderText() )
2369 buttonsToShow = TextSelectionPopup::Buttons( TextSelectionPopup::SELECT | TextSelectionPopup::SELECT_ALL );
2372 if( !IsClipboardEmpty() )
2374 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
2375 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::CLIPBOARD ) );
2378 else if( EventData::EDITING_WITH_PASTE_POPUP == mEventData->mState )
2380 if ( !IsClipboardEmpty() )
2382 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
2383 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::CLIPBOARD ) );
2387 mEventData->mDecorator->SetEnabledPopupButtons( buttonsToShow );
2390 void Controller::Impl::ChangeState( EventData::State newState )
2392 if( NULL == mEventData )
2394 // Nothing to do if there is no text input.
2398 DALI_LOG_INFO( gLogFilter, Debug::General, "ChangeState state:%d newstate:%d\n", mEventData->mState, newState );
2400 if( mEventData->mState != newState )
2402 mEventData->mPreviousState = mEventData->mState;
2403 mEventData->mState = newState;
2405 switch( mEventData->mState )
2407 case EventData::INACTIVE:
2409 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
2410 mEventData->mDecorator->StopCursorBlink();
2411 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
2412 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
2413 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
2414 mEventData->mDecorator->SetHighlightActive( false );
2415 mEventData->mDecorator->SetPopupActive( false );
2416 mEventData->mDecoratorUpdated = true;
2419 case EventData::INTERRUPTED:
2421 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
2422 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
2423 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
2424 mEventData->mDecorator->SetHighlightActive( false );
2425 mEventData->mDecorator->SetPopupActive( false );
2426 mEventData->mDecoratorUpdated = true;
2429 case EventData::SELECTING:
2431 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
2432 mEventData->mDecorator->StopCursorBlink();
2433 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
2434 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true );
2435 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true );
2436 mEventData->mDecorator->SetHighlightActive( true );
2437 if( mEventData->mGrabHandlePopupEnabled )
2440 mEventData->mDecorator->SetPopupActive( true );
2442 mEventData->mDecoratorUpdated = true;
2445 case EventData::EDITING:
2447 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
2448 if( mEventData->mCursorBlinkEnabled )
2450 mEventData->mDecorator->StartCursorBlink();
2452 // Grab handle is not shown until a tap is received whilst EDITING
2453 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
2454 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
2455 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
2456 mEventData->mDecorator->SetHighlightActive( false );
2457 if( mEventData->mGrabHandlePopupEnabled )
2459 mEventData->mDecorator->SetPopupActive( false );
2461 mEventData->mDecoratorUpdated = true;
2464 case EventData::EDITING_WITH_POPUP:
2466 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "EDITING_WITH_POPUP \n", newState );
2468 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
2469 if( mEventData->mCursorBlinkEnabled )
2471 mEventData->mDecorator->StartCursorBlink();
2473 if( mEventData->mSelectionEnabled )
2475 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
2476 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
2477 mEventData->mDecorator->SetHighlightActive( false );
2481 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
2483 if( mEventData->mGrabHandlePopupEnabled )
2486 mEventData->mDecorator->SetPopupActive( true );
2488 mEventData->mDecoratorUpdated = true;
2491 case EventData::EDITING_WITH_GRAB_HANDLE:
2493 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "EDITING_WITH_GRAB_HANDLE \n", newState );
2495 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
2496 if( mEventData->mCursorBlinkEnabled )
2498 mEventData->mDecorator->StartCursorBlink();
2500 // Grab handle is not shown until a tap is received whilst EDITING
2501 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
2502 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
2503 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
2504 mEventData->mDecorator->SetHighlightActive( false );
2505 if( mEventData->mGrabHandlePopupEnabled )
2507 mEventData->mDecorator->SetPopupActive( false );
2509 mEventData->mDecoratorUpdated = true;
2512 case EventData::SELECTION_HANDLE_PANNING:
2514 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
2515 mEventData->mDecorator->StopCursorBlink();
2516 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
2517 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true );
2518 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true );
2519 mEventData->mDecorator->SetHighlightActive( true );
2520 if( mEventData->mGrabHandlePopupEnabled )
2522 mEventData->mDecorator->SetPopupActive( false );
2524 mEventData->mDecoratorUpdated = true;
2527 case EventData::GRAB_HANDLE_PANNING:
2529 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "GRAB_HANDLE_PANNING \n", newState );
2531 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
2532 if( mEventData->mCursorBlinkEnabled )
2534 mEventData->mDecorator->StartCursorBlink();
2536 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
2537 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
2538 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
2539 mEventData->mDecorator->SetHighlightActive( false );
2540 if( mEventData->mGrabHandlePopupEnabled )
2542 mEventData->mDecorator->SetPopupActive( false );
2544 mEventData->mDecoratorUpdated = true;
2547 case EventData::EDITING_WITH_PASTE_POPUP:
2549 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "EDITING_WITH_PASTE_POPUP \n", newState );
2551 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
2552 if( mEventData->mCursorBlinkEnabled )
2554 mEventData->mDecorator->StartCursorBlink();
2557 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
2558 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
2559 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
2560 mEventData->mDecorator->SetHighlightActive( false );
2562 if( mEventData->mGrabHandlePopupEnabled )
2565 mEventData->mDecorator->SetPopupActive( true );
2567 mEventData->mDecoratorUpdated = true;
2570 case EventData::TEXT_PANNING:
2572 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
2573 mEventData->mDecorator->StopCursorBlink();
2574 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
2575 if( mEventData->mDecorator->IsHandleActive( LEFT_SELECTION_HANDLE ) ||
2576 mEventData->mDecorator->IsHandleActive( RIGHT_SELECTION_HANDLE ) )
2578 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
2579 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
2580 mEventData->mDecorator->SetHighlightActive( true );
2583 if( mEventData->mGrabHandlePopupEnabled )
2585 mEventData->mDecorator->SetPopupActive( false );
2588 mEventData->mDecoratorUpdated = true;
2595 void Controller::Impl::GetCursorPosition( CharacterIndex logical,
2596 CursorInfo& cursorInfo )
2598 if( !IsShowingRealText() )
2600 // Do not want to use the place-holder text to set the cursor position.
2602 // Use the line's height of the font's family set to set the cursor's size.
2603 // If there is no font's family set, use the default font.
2604 // Use the current alignment to place the cursor at the beginning, center or end of the box.
2606 cursorInfo.lineOffset = 0.f;
2607 cursorInfo.lineHeight = GetDefaultFontLineHeight();
2608 cursorInfo.primaryCursorHeight = cursorInfo.lineHeight;
2610 switch( mModel->mHorizontalAlignment )
2612 case Layout::HORIZONTAL_ALIGN_BEGIN:
2614 cursorInfo.primaryPosition.x = 0.f;
2617 case Layout::HORIZONTAL_ALIGN_CENTER:
2619 cursorInfo.primaryPosition.x = floorf( 0.5f * mModel->mVisualModel->mControlSize.width );
2622 case Layout::HORIZONTAL_ALIGN_END:
2624 cursorInfo.primaryPosition.x = mModel->mVisualModel->mControlSize.width - mEventData->mDecorator->GetCursorWidth();
2629 // Nothing else to do.
2633 const bool isMultiLine = ( Layout::Engine::MULTI_LINE_BOX == mLayoutEngine.GetLayout() );
2634 GetCursorPositionParameters parameters;
2635 parameters.visualModel = mModel->mVisualModel;
2636 parameters.logicalModel = mModel->mLogicalModel;
2637 parameters.metrics = mMetrics;
2638 parameters.logical = logical;
2639 parameters.isMultiline = isMultiLine;
2641 Text::GetCursorPosition( parameters,
2646 // If the text is editable and multi-line, the cursor position after a white space shouldn't exceed the boundaries of the text control.
2648 // Note the white spaces laid-out at the end of the line might exceed the boundaries of the control.
2649 // The reason is a wrapped line must not start with a white space so they are laid-out at the end of the line.
2651 if( 0.f > cursorInfo.primaryPosition.x )
2653 cursorInfo.primaryPosition.x = 0.f;
2656 const float edgeWidth = mModel->mVisualModel->mControlSize.width - static_cast<float>( mEventData->mDecorator->GetCursorWidth() );
2657 if( cursorInfo.primaryPosition.x > edgeWidth )
2659 cursorInfo.primaryPosition.x = edgeWidth;
2664 CharacterIndex Controller::Impl::CalculateNewCursorIndex( CharacterIndex index ) const
2666 if( NULL == mEventData )
2668 // Nothing to do if there is no text input.
2672 CharacterIndex cursorIndex = mEventData->mPrimaryCursorPosition;
2674 const GlyphIndex* const charactersToGlyphBuffer = mModel->mVisualModel->mCharactersToGlyph.Begin();
2675 const Length* const charactersPerGlyphBuffer = mModel->mVisualModel->mCharactersPerGlyph.Begin();
2677 GlyphIndex glyphIndex = *( charactersToGlyphBuffer + index );
2678 Length numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
2680 if( numberOfCharacters > 1u )
2682 const Script script = mModel->mLogicalModel->GetScript( index );
2683 if( HasLigatureMustBreak( script ) )
2685 // Prevents to jump the whole Latin ligatures like fi, ff, or Arabic ï»», ...
2686 numberOfCharacters = 1u;
2691 while( 0u == numberOfCharacters )
2694 numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
2698 if( index < mEventData->mPrimaryCursorPosition )
2700 cursorIndex -= numberOfCharacters;
2704 cursorIndex += numberOfCharacters;
2707 // Will update the cursor hook position.
2708 mEventData->mUpdateCursorHookPosition = true;
2713 void Controller::Impl::UpdateCursorPosition( const CursorInfo& cursorInfo )
2715 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "-->Controller::UpdateCursorPosition %p\n", this );
2716 if( NULL == mEventData )
2718 // Nothing to do if there is no text input.
2719 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition no event data\n" );
2723 const Vector2 cursorPosition = cursorInfo.primaryPosition + mModel->mScrollPosition;
2725 mEventData->mDecorator->SetGlyphOffset( PRIMARY_CURSOR, cursorInfo.glyphOffset );
2727 // Sets the cursor position.
2728 mEventData->mDecorator->SetPosition( PRIMARY_CURSOR,
2731 cursorInfo.primaryCursorHeight,
2732 cursorInfo.lineHeight );
2733 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Primary cursor position: %f,%f\n", cursorPosition.x, cursorPosition.y );
2735 if( mEventData->mUpdateGrabHandlePosition )
2737 // Sets the grab handle position.
2738 mEventData->mDecorator->SetPosition( GRAB_HANDLE,
2740 cursorInfo.lineOffset + mModel->mScrollPosition.y,
2741 cursorInfo.lineHeight );
2744 if( cursorInfo.isSecondaryCursor )
2746 mEventData->mDecorator->SetPosition( SECONDARY_CURSOR,
2747 cursorInfo.secondaryPosition.x + mModel->mScrollPosition.x,
2748 cursorInfo.secondaryPosition.y + mModel->mScrollPosition.y,
2749 cursorInfo.secondaryCursorHeight,
2750 cursorInfo.lineHeight );
2751 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Secondary cursor position: %f,%f\n", cursorInfo.secondaryPosition.x + mModel->mScrollPosition.x, cursorInfo.secondaryPosition.y + mModel->mScrollPosition.y );
2754 // Set which cursors are active according the state.
2755 if( EventData::IsEditingState( mEventData->mState ) || ( EventData::GRAB_HANDLE_PANNING == mEventData->mState ) )
2757 if( cursorInfo.isSecondaryCursor )
2759 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_BOTH );
2763 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
2768 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
2771 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition\n" );
2774 void Controller::Impl::UpdateSelectionHandle( HandleType handleType,
2775 const CursorInfo& cursorInfo )
2777 if( ( LEFT_SELECTION_HANDLE != handleType ) &&
2778 ( RIGHT_SELECTION_HANDLE != handleType ) )
2783 const Vector2 cursorPosition = cursorInfo.primaryPosition + mModel->mScrollPosition;
2785 // Sets the handle's position.
2786 mEventData->mDecorator->SetPosition( handleType,
2788 cursorInfo.lineOffset + mModel->mScrollPosition.y,
2789 cursorInfo.lineHeight );
2791 // If selection handle at start of the text and other at end of the text then all text is selected.
2792 const CharacterIndex startOfSelection = std::min( mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition );
2793 const CharacterIndex endOfSelection = std::max ( mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition );
2794 mEventData->mAllTextSelected = ( startOfSelection == 0 ) && ( endOfSelection == mModel->mLogicalModel->mText.Count() );
2797 void Controller::Impl::ClampHorizontalScroll( const Vector2& layoutSize )
2799 // Clamp between -space & -alignment offset.
2801 if( layoutSize.width > mModel->mVisualModel->mControlSize.width )
2803 const float space = ( layoutSize.width - mModel->mVisualModel->mControlSize.width ) + mModel->mAlignmentOffset;
2804 mModel->mScrollPosition.x = ( mModel->mScrollPosition.x < -space ) ? -space : mModel->mScrollPosition.x;
2805 mModel->mScrollPosition.x = ( mModel->mScrollPosition.x > -mModel->mAlignmentOffset ) ? -mModel->mAlignmentOffset : mModel->mScrollPosition.x;
2807 mEventData->mDecoratorUpdated = true;
2811 mModel->mScrollPosition.x = 0.f;
2815 void Controller::Impl::ClampVerticalScroll( const Vector2& layoutSize )
2817 if( Layout::Engine::SINGLE_LINE_BOX == mLayoutEngine.GetLayout() )
2819 // Nothing to do if the text is single line.
2823 // Clamp between -space & 0.
2824 if( layoutSize.height > mModel->mVisualModel->mControlSize.height )
2826 const float space = ( layoutSize.height - mModel->mVisualModel->mControlSize.height );
2827 mModel->mScrollPosition.y = ( mModel->mScrollPosition.y < -space ) ? -space : mModel->mScrollPosition.y;
2828 mModel->mScrollPosition.y = ( mModel->mScrollPosition.y > 0.f ) ? 0.f : mModel->mScrollPosition.y;
2830 mEventData->mDecoratorUpdated = true;
2834 mModel->mScrollPosition.y = 0.f;
2838 void Controller::Impl::ScrollToMakePositionVisible( const Vector2& position, float lineHeight )
2840 const float cursorWidth = mEventData->mDecorator ? static_cast<float>( mEventData->mDecorator->GetCursorWidth() ) : 0.f;
2842 // position is in actor's coords.
2843 const float positionEndX = position.x + cursorWidth;
2844 const float positionEndY = position.y + lineHeight;
2846 // Transform the position to decorator coords.
2847 const float decoratorPositionBeginX = position.x + mModel->mScrollPosition.x;
2848 const float decoratorPositionEndX = positionEndX + mModel->mScrollPosition.x;
2850 const float decoratorPositionBeginY = position.y + mModel->mScrollPosition.y;
2851 const float decoratorPositionEndY = positionEndY + mModel->mScrollPosition.y;
2853 if( decoratorPositionBeginX < 0.f )
2855 mModel->mScrollPosition.x = -position.x;
2857 else if( decoratorPositionEndX > mModel->mVisualModel->mControlSize.width )
2859 mModel->mScrollPosition.x = mModel->mVisualModel->mControlSize.width - positionEndX;
2862 if( Layout::Engine::MULTI_LINE_BOX == mLayoutEngine.GetLayout() )
2864 if( decoratorPositionBeginY < 0.f )
2866 mModel->mScrollPosition.y = -position.y;
2868 else if( decoratorPositionEndY > mModel->mVisualModel->mControlSize.height )
2870 mModel->mScrollPosition.y = mModel->mVisualModel->mControlSize.height - positionEndY;
2875 void Controller::Impl::ScrollTextToMatchCursor( const CursorInfo& cursorInfo )
2877 // Get the current cursor position in decorator coords.
2878 const Vector2& currentCursorPosition = mEventData->mDecorator->GetPosition( PRIMARY_CURSOR );
2880 const LineIndex lineIndex = mModel->mVisualModel->GetLineOfCharacter( mEventData->mPrimaryCursorPosition );
2884 // Calculate the offset to match the cursor position before the character was deleted.
2885 mModel->mScrollPosition.x = currentCursorPosition.x - cursorInfo.primaryPosition.x;
2887 //If text control has more than two lines and current line index is not last, calculate scrollpositionY
2888 if( mModel->mVisualModel->mLines.Count() > 1u && lineIndex != mModel->mVisualModel->mLines.Count() -1u )
2890 const float currentCursorGlyphOffset = mEventData->mDecorator->GetGlyphOffset( PRIMARY_CURSOR );
2891 mModel->mScrollPosition.y = currentCursorPosition.y - cursorInfo.lineOffset - currentCursorGlyphOffset;
2895 ClampHorizontalScroll( mModel->mVisualModel->GetLayoutSize() );
2896 ClampVerticalScroll( mModel->mVisualModel->GetLayoutSize() );
2898 // Makes the new cursor position visible if needed.
2899 ScrollToMakePositionVisible( cursorInfo.primaryPosition, cursorInfo.lineHeight );
2902 void Controller::Impl::RequestRelayout()
2904 if( NULL != mControlInterface )
2906 mControlInterface->RequestTextRelayout();
2912 } // namespace Toolkit