2 * Copyright (c) 2017 Samsung Electronics Co., Ltd.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
19 #include <dali-toolkit/internal/text/text-controller-impl.h>
22 #include <dali/public-api/adaptor-framework/key.h>
23 #include <dali/integration-api/debug.h>
27 #include <dali-toolkit/internal/text/bidirectional-support.h>
28 #include <dali-toolkit/internal/text/character-set-conversion.h>
29 #include <dali-toolkit/internal/text/color-segmentation.h>
30 #include <dali-toolkit/internal/text/cursor-helper-functions.h>
31 #include <dali-toolkit/internal/text/multi-language-support.h>
32 #include <dali-toolkit/internal/text/segmentation.h>
33 #include <dali-toolkit/internal/text/shaper.h>
34 #include <dali-toolkit/internal/text/text-control-interface.h>
35 #include <dali-toolkit/internal/text/text-run-container.h>
41 * @brief Struct used to calculate the selection box.
43 struct SelectionBoxInfo
51 #if defined(DEBUG_ENABLED)
52 Debug::Filter* gLogFilter = Debug::Filter::New(Debug::NoLogging, true, "LOG_TEXT_CONTROLS");
55 const float MAX_FLOAT = std::numeric_limits<float>::max();
56 const float MIN_FLOAT = std::numeric_limits<float>::min();
57 const Dali::Toolkit::Text::CharacterDirection LTR = false; ///< Left To Right direction
70 EventData::EventData( DecoratorPtr decorator )
71 : mDecorator( decorator ),
73 mPlaceholderFont( NULL ),
74 mPlaceholderTextActive(),
75 mPlaceholderTextInactive(),
76 mPlaceholderTextColor( 0.8f, 0.8f, 0.8f, 0.8f ), // This color has been published in the Public API (placeholder-properties.h).
78 mInputStyleChangedQueue(),
79 mPreviousState( INACTIVE ),
81 mPrimaryCursorPosition( 0u ),
82 mLeftSelectionPosition( 0u ),
83 mRightSelectionPosition( 0u ),
84 mPreEditStartPosition( 0u ),
86 mCursorHookPositionX( 0.f ),
87 mDoubleTapAction( Controller::NoTextTap::NO_ACTION ),
88 mLongPressAction( Controller::NoTextTap::SHOW_SELECTION_POPUP ),
89 mIsShowingPlaceholderText( false ),
90 mPreEditFlag( false ),
91 mDecoratorUpdated( false ),
92 mCursorBlinkEnabled( true ),
93 mGrabHandleEnabled( true ),
94 mGrabHandlePopupEnabled( true ),
95 mSelectionEnabled( true ),
96 mUpdateCursorHookPosition( false ),
97 mUpdateCursorPosition( false ),
98 mUpdateGrabHandlePosition( false ),
99 mUpdateLeftSelectionPosition( false ),
100 mUpdateRightSelectionPosition( false ),
101 mIsLeftHandleSelected( false ),
102 mIsRightHandleSelected( false ),
103 mUpdateHighlightBox( false ),
104 mScrollAfterUpdatePosition( false ),
105 mScrollAfterDelete( false ),
106 mAllTextSelected( false ),
107 mUpdateInputStyle( false ),
108 mPasswordInput( false ),
109 mIsPlaceholderPixelSize( false ),
110 mIsPlaceholderElideEnabled( false ),
111 mPlaceholderEllipsisFlag( false ),
112 mShiftSelectionFlag( true )
114 mImfManager = ImfManager::Get();
117 EventData::~EventData()
120 bool Controller::Impl::ProcessInputEvents()
122 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "-->Controller::ProcessInputEvents\n" );
123 if( NULL == mEventData )
125 // Nothing to do if there is no text input.
126 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::ProcessInputEvents no event data\n" );
130 if( mEventData->mDecorator )
132 for( std::vector<Event>::iterator iter = mEventData->mEventQueue.begin();
133 iter != mEventData->mEventQueue.end();
138 case Event::CURSOR_KEY_EVENT:
140 OnCursorKeyEvent( *iter );
143 case Event::TAP_EVENT:
148 case Event::LONG_PRESS_EVENT:
150 OnLongPressEvent( *iter );
153 case Event::PAN_EVENT:
158 case Event::GRAB_HANDLE_EVENT:
159 case Event::LEFT_SELECTION_HANDLE_EVENT:
160 case Event::RIGHT_SELECTION_HANDLE_EVENT: // Fall through
162 OnHandleEvent( *iter );
167 OnSelectEvent( *iter );
170 case Event::SELECT_ALL:
179 if( mEventData->mUpdateCursorPosition ||
180 mEventData->mUpdateHighlightBox )
185 // The cursor must also be repositioned after inserts into the model
186 if( mEventData->mUpdateCursorPosition )
188 // Updates the cursor position and scrolls the text to make it visible.
189 CursorInfo cursorInfo;
190 // Calculate the cursor position from the new cursor index.
191 GetCursorPosition( mEventData->mPrimaryCursorPosition,
194 if( mEventData->mUpdateCursorHookPosition )
196 // Update the cursor hook position. Used to move the cursor with the keys 'up' and 'down'.
197 mEventData->mCursorHookPositionX = cursorInfo.primaryPosition.x;
198 mEventData->mUpdateCursorHookPosition = false;
201 // Scroll first the text after delete ...
202 if( mEventData->mScrollAfterDelete )
204 ScrollTextToMatchCursor( cursorInfo );
207 // ... then, text can be scrolled to make the cursor visible.
208 if( mEventData->mScrollAfterUpdatePosition )
210 const Vector2 currentCursorPosition( cursorInfo.primaryPosition.x, cursorInfo.lineOffset );
211 ScrollToMakePositionVisible( currentCursorPosition, cursorInfo.lineHeight );
213 mEventData->mScrollAfterUpdatePosition = false;
214 mEventData->mScrollAfterDelete = false;
216 UpdateCursorPosition( cursorInfo );
218 mEventData->mDecoratorUpdated = true;
219 mEventData->mUpdateCursorPosition = false;
220 mEventData->mUpdateGrabHandlePosition = false;
224 CursorInfo leftHandleInfo;
225 CursorInfo rightHandleInfo;
227 if( mEventData->mUpdateHighlightBox )
229 GetCursorPosition( mEventData->mLeftSelectionPosition,
232 GetCursorPosition( mEventData->mRightSelectionPosition,
235 if( mEventData->mScrollAfterUpdatePosition && ( mEventData->mIsLeftHandleSelected ? mEventData->mUpdateLeftSelectionPosition : mEventData->mUpdateRightSelectionPosition ) )
237 if( mEventData->mIsLeftHandleSelected && mEventData->mIsRightHandleSelected )
239 CursorInfo& infoLeft = leftHandleInfo;
241 const Vector2 currentCursorPositionLeft( infoLeft.primaryPosition.x, infoLeft.lineOffset );
242 ScrollToMakePositionVisible( currentCursorPositionLeft, infoLeft.lineHeight );
244 CursorInfo& infoRight = rightHandleInfo;
246 const Vector2 currentCursorPositionRight( infoRight.primaryPosition.x, infoRight.lineOffset );
247 ScrollToMakePositionVisible( currentCursorPositionRight, infoRight.lineHeight );
251 CursorInfo& info = mEventData->mIsLeftHandleSelected ? leftHandleInfo : rightHandleInfo;
253 const Vector2 currentCursorPosition( info.primaryPosition.x, info.lineOffset );
254 ScrollToMakePositionVisible( currentCursorPosition, info.lineHeight );
259 if( mEventData->mUpdateLeftSelectionPosition )
261 UpdateSelectionHandle( LEFT_SELECTION_HANDLE,
265 mEventData->mDecoratorUpdated = true;
266 mEventData->mUpdateLeftSelectionPosition = false;
269 if( mEventData->mUpdateRightSelectionPosition )
271 UpdateSelectionHandle( RIGHT_SELECTION_HANDLE,
275 mEventData->mDecoratorUpdated = true;
276 mEventData->mUpdateRightSelectionPosition = false;
279 if( mEventData->mUpdateHighlightBox )
281 RepositionSelectionHandles();
283 mEventData->mUpdateLeftSelectionPosition = false;
284 mEventData->mUpdateRightSelectionPosition = false;
285 mEventData->mUpdateHighlightBox = false;
286 mEventData->mIsLeftHandleSelected = false;
287 mEventData->mIsRightHandleSelected = false;
290 mEventData->mScrollAfterUpdatePosition = false;
293 if( mEventData->mUpdateInputStyle )
295 // Keep a copy of the current input style.
296 InputStyle currentInputStyle;
297 currentInputStyle.Copy( mEventData->mInputStyle );
299 // Set the default style first.
300 RetrieveDefaultInputStyle( mEventData->mInputStyle );
302 // Get the character index from the cursor index.
303 const CharacterIndex styleIndex = ( mEventData->mPrimaryCursorPosition > 0u ) ? mEventData->mPrimaryCursorPosition - 1u : 0u;
305 // Retrieve the style from the style runs stored in the logical model.
306 mModel->mLogicalModel->RetrieveStyle( styleIndex, mEventData->mInputStyle );
308 // Compare if the input style has changed.
309 const bool hasInputStyleChanged = !currentInputStyle.Equal( mEventData->mInputStyle );
311 if( hasInputStyleChanged )
313 const InputStyle::Mask styleChangedMask = currentInputStyle.GetInputStyleChangeMask( mEventData->mInputStyle );
314 // Queue the input style changed signal.
315 mEventData->mInputStyleChangedQueue.PushBack( styleChangedMask );
318 mEventData->mUpdateInputStyle = false;
321 mEventData->mEventQueue.clear();
323 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::ProcessInputEvents\n" );
325 const bool decoratorUpdated = mEventData->mDecoratorUpdated;
326 mEventData->mDecoratorUpdated = false;
328 return decoratorUpdated;
331 void Controller::Impl::NotifyImfManager()
333 if( mEventData && mEventData->mImfManager )
335 CharacterIndex cursorPosition = GetLogicalCursorPosition();
337 const Length numberOfWhiteSpaces = GetNumberOfWhiteSpaces( 0u );
339 // Update the cursor position by removing the initial white spaces.
340 if( cursorPosition < numberOfWhiteSpaces )
346 cursorPosition -= numberOfWhiteSpaces;
349 mEventData->mImfManager.SetCursorPosition( cursorPosition );
350 mEventData->mImfManager.NotifyCursorPosition();
354 void Controller::Impl::NotifyImfMultiLineStatus()
358 Text::Layout::Engine::Type layout = mLayoutEngine.GetLayout();
359 mEventData->mImfManager.NotifyTextInputMultiLine( layout == Text::Layout::Engine::MULTI_LINE_BOX );
363 CharacterIndex Controller::Impl::GetLogicalCursorPosition() const
365 CharacterIndex cursorPosition = 0u;
369 if( ( EventData::SELECTING == mEventData->mState ) ||
370 ( EventData::SELECTION_HANDLE_PANNING == mEventData->mState ) )
372 cursorPosition = std::min( mEventData->mRightSelectionPosition, mEventData->mLeftSelectionPosition );
376 cursorPosition = mEventData->mPrimaryCursorPosition;
380 return cursorPosition;
383 Length Controller::Impl::GetNumberOfWhiteSpaces( CharacterIndex index ) const
385 Length numberOfWhiteSpaces = 0u;
387 // Get the buffer to the text.
388 Character* utf32CharacterBuffer = mModel->mLogicalModel->mText.Begin();
390 const Length totalNumberOfCharacters = mModel->mLogicalModel->mText.Count();
391 for( ; index < totalNumberOfCharacters; ++index, ++numberOfWhiteSpaces )
393 if( !TextAbstraction::IsWhiteSpace( *( utf32CharacterBuffer + index ) ) )
399 return numberOfWhiteSpaces;
402 void Controller::Impl::GetText( CharacterIndex index, std::string& text ) const
404 // Get the total number of characters.
405 Length numberOfCharacters = mModel->mLogicalModel->mText.Count();
407 // Retrieve the text.
408 if( 0u != numberOfCharacters )
410 Utf32ToUtf8( mModel->mLogicalModel->mText.Begin() + index, numberOfCharacters - index, text );
414 void Controller::Impl::CalculateTextUpdateIndices( Length& numberOfCharacters )
416 mTextUpdateInfo.mParagraphCharacterIndex = 0u;
417 mTextUpdateInfo.mStartGlyphIndex = 0u;
418 mTextUpdateInfo.mStartLineIndex = 0u;
419 numberOfCharacters = 0u;
421 const Length numberOfParagraphs = mModel->mLogicalModel->mParagraphInfo.Count();
422 if( 0u == numberOfParagraphs )
424 mTextUpdateInfo.mParagraphCharacterIndex = 0u;
425 numberOfCharacters = 0u;
427 // prevent mTextUpdateInfo.mRequestedNumberOfCharacters value underflow
428 mTextUpdateInfo.mRequestedNumberOfCharacters = ( mTextUpdateInfo.mNumberOfCharactersToAdd < mTextUpdateInfo.mNumberOfCharactersToRemove ? 0u : mTextUpdateInfo.mNumberOfCharactersToAdd - mTextUpdateInfo.mNumberOfCharactersToRemove );
430 // Nothing else to do if there are no paragraphs.
434 // Find the paragraphs to be updated.
435 Vector<ParagraphRunIndex> paragraphsToBeUpdated;
436 if( mTextUpdateInfo.mCharacterIndex >= mTextUpdateInfo.mPreviousNumberOfCharacters )
438 // Text is being added at the end of the current text.
439 if( mTextUpdateInfo.mIsLastCharacterNewParagraph )
441 // Text is being added in a new paragraph after the last character of the text.
442 mTextUpdateInfo.mParagraphCharacterIndex = mTextUpdateInfo.mPreviousNumberOfCharacters;
443 numberOfCharacters = 0u;
445 // prevent mTextUpdateInfo.mRequestedNumberOfCharacters value underflow
446 mTextUpdateInfo.mRequestedNumberOfCharacters = ( mTextUpdateInfo.mNumberOfCharactersToAdd < mTextUpdateInfo.mNumberOfCharactersToRemove ? 0u : mTextUpdateInfo.mNumberOfCharactersToAdd - mTextUpdateInfo.mNumberOfCharactersToRemove );
448 mTextUpdateInfo.mStartGlyphIndex = mModel->mVisualModel->mGlyphs.Count();
449 mTextUpdateInfo.mStartLineIndex = mModel->mVisualModel->mLines.Count() - 1u;
451 // Nothing else to do;
455 paragraphsToBeUpdated.PushBack( numberOfParagraphs - 1u );
459 Length numberOfCharactersToUpdate = 0u;
460 if( mTextUpdateInfo.mFullRelayoutNeeded )
462 numberOfCharactersToUpdate = mTextUpdateInfo.mPreviousNumberOfCharacters;
466 numberOfCharactersToUpdate = ( mTextUpdateInfo.mNumberOfCharactersToRemove > 0u ) ? mTextUpdateInfo.mNumberOfCharactersToRemove : 1u;
468 mModel->mLogicalModel->FindParagraphs( mTextUpdateInfo.mCharacterIndex,
469 numberOfCharactersToUpdate,
470 paragraphsToBeUpdated );
473 if( 0u != paragraphsToBeUpdated.Count() )
475 const ParagraphRunIndex firstParagraphIndex = *( paragraphsToBeUpdated.Begin() );
476 const ParagraphRun& firstParagraph = *( mModel->mLogicalModel->mParagraphInfo.Begin() + firstParagraphIndex );
477 mTextUpdateInfo.mParagraphCharacterIndex = firstParagraph.characterRun.characterIndex;
479 ParagraphRunIndex lastParagraphIndex = *( paragraphsToBeUpdated.End() - 1u );
480 const ParagraphRun& lastParagraph = *( mModel->mLogicalModel->mParagraphInfo.Begin() + lastParagraphIndex );
482 if( ( mTextUpdateInfo.mNumberOfCharactersToRemove > 0u ) && // Some character are removed.
483 ( lastParagraphIndex < numberOfParagraphs - 1u ) && // There is a next paragraph.
484 ( ( lastParagraph.characterRun.characterIndex + lastParagraph.characterRun.numberOfCharacters ) == // The last removed character is the new paragraph character.
485 ( mTextUpdateInfo.mCharacterIndex + mTextUpdateInfo.mNumberOfCharactersToRemove ) ) )
487 // The new paragraph character of the last updated paragraph has been removed so is going to be merged with the next one.
488 const ParagraphRun& lastParagraph = *( mModel->mLogicalModel->mParagraphInfo.Begin() + lastParagraphIndex + 1u );
490 numberOfCharacters = lastParagraph.characterRun.characterIndex + lastParagraph.characterRun.numberOfCharacters - mTextUpdateInfo.mParagraphCharacterIndex;
494 numberOfCharacters = lastParagraph.characterRun.characterIndex + lastParagraph.characterRun.numberOfCharacters - mTextUpdateInfo.mParagraphCharacterIndex;
498 // prevent mTextUpdateInfo.mRequestedNumberOfCharacters value underflow
499 if( numberOfCharacters + mTextUpdateInfo.mNumberOfCharactersToAdd < mTextUpdateInfo.mNumberOfCharactersToRemove )
501 mTextUpdateInfo.mRequestedNumberOfCharacters = 0u;
505 mTextUpdateInfo.mRequestedNumberOfCharacters = numberOfCharacters + mTextUpdateInfo.mNumberOfCharactersToAdd - mTextUpdateInfo.mNumberOfCharactersToRemove;
507 mTextUpdateInfo.mStartGlyphIndex = *( mModel->mVisualModel->mCharactersToGlyph.Begin() + mTextUpdateInfo.mParagraphCharacterIndex );
510 void Controller::Impl::ClearFullModelData( OperationsMask operations )
512 if( NO_OPERATION != ( GET_LINE_BREAKS & operations ) )
514 mModel->mLogicalModel->mLineBreakInfo.Clear();
515 mModel->mLogicalModel->mParagraphInfo.Clear();
518 if( NO_OPERATION != ( GET_WORD_BREAKS & operations ) )
520 mModel->mLogicalModel->mLineBreakInfo.Clear();
523 if( NO_OPERATION != ( GET_SCRIPTS & operations ) )
525 mModel->mLogicalModel->mScriptRuns.Clear();
528 if( NO_OPERATION != ( VALIDATE_FONTS & operations ) )
530 mModel->mLogicalModel->mFontRuns.Clear();
533 if( 0u != mModel->mLogicalModel->mBidirectionalParagraphInfo.Count() )
535 if( NO_OPERATION != ( BIDI_INFO & operations ) )
537 mModel->mLogicalModel->mBidirectionalParagraphInfo.Clear();
538 mModel->mLogicalModel->mCharacterDirections.Clear();
541 if( NO_OPERATION != ( REORDER & operations ) )
543 // Free the allocated memory used to store the conversion table in the bidirectional line info run.
544 for( Vector<BidirectionalLineInfoRun>::Iterator it = mModel->mLogicalModel->mBidirectionalLineInfo.Begin(),
545 endIt = mModel->mLogicalModel->mBidirectionalLineInfo.End();
549 BidirectionalLineInfoRun& bidiLineInfo = *it;
551 free( bidiLineInfo.visualToLogicalMap );
552 bidiLineInfo.visualToLogicalMap = NULL;
554 mModel->mLogicalModel->mBidirectionalLineInfo.Clear();
558 if( NO_OPERATION != ( SHAPE_TEXT & operations ) )
560 mModel->mVisualModel->mGlyphs.Clear();
561 mModel->mVisualModel->mGlyphsToCharacters.Clear();
562 mModel->mVisualModel->mCharactersToGlyph.Clear();
563 mModel->mVisualModel->mCharactersPerGlyph.Clear();
564 mModel->mVisualModel->mGlyphsPerCharacter.Clear();
565 mModel->mVisualModel->mGlyphPositions.Clear();
568 if( NO_OPERATION != ( LAYOUT & operations ) )
570 mModel->mVisualModel->mLines.Clear();
573 if( NO_OPERATION != ( COLOR & operations ) )
575 mModel->mVisualModel->mColorIndices.Clear();
579 void Controller::Impl::ClearCharacterModelData( CharacterIndex startIndex, CharacterIndex endIndex, OperationsMask operations )
581 const CharacterIndex endIndexPlusOne = endIndex + 1u;
583 if( NO_OPERATION != ( GET_LINE_BREAKS & operations ) )
585 // Clear the line break info.
586 LineBreakInfo* lineBreakInfoBuffer = mModel->mLogicalModel->mLineBreakInfo.Begin();
588 mModel->mLogicalModel->mLineBreakInfo.Erase( lineBreakInfoBuffer + startIndex,
589 lineBreakInfoBuffer + endIndexPlusOne );
591 // Clear the paragraphs.
592 ClearCharacterRuns( startIndex,
594 mModel->mLogicalModel->mParagraphInfo );
597 if( NO_OPERATION != ( GET_WORD_BREAKS & operations ) )
599 // Clear the word break info.
600 WordBreakInfo* wordBreakInfoBuffer = mModel->mLogicalModel->mWordBreakInfo.Begin();
602 mModel->mLogicalModel->mWordBreakInfo.Erase( wordBreakInfoBuffer + startIndex,
603 wordBreakInfoBuffer + endIndexPlusOne );
606 if( NO_OPERATION != ( GET_SCRIPTS & operations ) )
608 // Clear the scripts.
609 ClearCharacterRuns( startIndex,
611 mModel->mLogicalModel->mScriptRuns );
614 if( NO_OPERATION != ( VALIDATE_FONTS & operations ) )
617 ClearCharacterRuns( startIndex,
619 mModel->mLogicalModel->mFontRuns );
622 if( 0u != mModel->mLogicalModel->mBidirectionalParagraphInfo.Count() )
624 if( NO_OPERATION != ( BIDI_INFO & operations ) )
626 // Clear the bidirectional paragraph info.
627 ClearCharacterRuns( startIndex,
629 mModel->mLogicalModel->mBidirectionalParagraphInfo );
631 // Clear the character's directions.
632 CharacterDirection* characterDirectionsBuffer = mModel->mLogicalModel->mCharacterDirections.Begin();
634 mModel->mLogicalModel->mCharacterDirections.Erase( characterDirectionsBuffer + startIndex,
635 characterDirectionsBuffer + endIndexPlusOne );
638 if( NO_OPERATION != ( REORDER & operations ) )
640 uint32_t startRemoveIndex = mModel->mLogicalModel->mBidirectionalLineInfo.Count();
641 uint32_t endRemoveIndex = startRemoveIndex;
642 ClearCharacterRuns( startIndex,
644 mModel->mLogicalModel->mBidirectionalLineInfo,
648 BidirectionalLineInfoRun* bidirectionalLineInfoBuffer = mModel->mLogicalModel->mBidirectionalLineInfo.Begin();
650 // Free the allocated memory used to store the conversion table in the bidirectional line info run.
651 for( Vector<BidirectionalLineInfoRun>::Iterator it = bidirectionalLineInfoBuffer + startRemoveIndex,
652 endIt = bidirectionalLineInfoBuffer + endRemoveIndex;
656 BidirectionalLineInfoRun& bidiLineInfo = *it;
658 free( bidiLineInfo.visualToLogicalMap );
659 bidiLineInfo.visualToLogicalMap = NULL;
662 mModel->mLogicalModel->mBidirectionalLineInfo.Erase( bidirectionalLineInfoBuffer + startRemoveIndex,
663 bidirectionalLineInfoBuffer + endRemoveIndex );
668 void Controller::Impl::ClearGlyphModelData( CharacterIndex startIndex, CharacterIndex endIndex, OperationsMask operations )
670 const CharacterIndex endIndexPlusOne = endIndex + 1u;
671 const Length numberOfCharactersRemoved = endIndexPlusOne - startIndex;
673 // Convert the character index to glyph index before deleting the character to glyph and the glyphs per character buffers.
674 GlyphIndex* charactersToGlyphBuffer = mModel->mVisualModel->mCharactersToGlyph.Begin();
675 Length* glyphsPerCharacterBuffer = mModel->mVisualModel->mGlyphsPerCharacter.Begin();
677 const GlyphIndex endGlyphIndexPlusOne = *( charactersToGlyphBuffer + endIndex ) + *( glyphsPerCharacterBuffer + endIndex );
678 const Length numberOfGlyphsRemoved = endGlyphIndexPlusOne - mTextUpdateInfo.mStartGlyphIndex;
680 if( NO_OPERATION != ( SHAPE_TEXT & operations ) )
682 // Update the character to glyph indices.
683 for( Vector<GlyphIndex>::Iterator it = charactersToGlyphBuffer + endIndexPlusOne,
684 endIt = charactersToGlyphBuffer + mModel->mVisualModel->mCharactersToGlyph.Count();
688 CharacterIndex& index = *it;
689 index -= numberOfGlyphsRemoved;
692 // Clear the character to glyph conversion table.
693 mModel->mVisualModel->mCharactersToGlyph.Erase( charactersToGlyphBuffer + startIndex,
694 charactersToGlyphBuffer + endIndexPlusOne );
696 // Clear the glyphs per character table.
697 mModel->mVisualModel->mGlyphsPerCharacter.Erase( glyphsPerCharacterBuffer + startIndex,
698 glyphsPerCharacterBuffer + endIndexPlusOne );
700 // Clear the glyphs buffer.
701 GlyphInfo* glyphsBuffer = mModel->mVisualModel->mGlyphs.Begin();
702 mModel->mVisualModel->mGlyphs.Erase( glyphsBuffer + mTextUpdateInfo.mStartGlyphIndex,
703 glyphsBuffer + endGlyphIndexPlusOne );
705 CharacterIndex* glyphsToCharactersBuffer = mModel->mVisualModel->mGlyphsToCharacters.Begin();
707 // Update the glyph to character indices.
708 for( Vector<CharacterIndex>::Iterator it = glyphsToCharactersBuffer + endGlyphIndexPlusOne,
709 endIt = glyphsToCharactersBuffer + mModel->mVisualModel->mGlyphsToCharacters.Count();
713 CharacterIndex& index = *it;
714 index -= numberOfCharactersRemoved;
717 // Clear the glyphs to characters buffer.
718 mModel->mVisualModel->mGlyphsToCharacters.Erase( glyphsToCharactersBuffer + mTextUpdateInfo.mStartGlyphIndex,
719 glyphsToCharactersBuffer + endGlyphIndexPlusOne );
721 // Clear the characters per glyph buffer.
722 Length* charactersPerGlyphBuffer = mModel->mVisualModel->mCharactersPerGlyph.Begin();
723 mModel->mVisualModel->mCharactersPerGlyph.Erase( charactersPerGlyphBuffer + mTextUpdateInfo.mStartGlyphIndex,
724 charactersPerGlyphBuffer + endGlyphIndexPlusOne );
726 // Clear the positions buffer.
727 Vector2* positionsBuffer = mModel->mVisualModel->mGlyphPositions.Begin();
728 mModel->mVisualModel->mGlyphPositions.Erase( positionsBuffer + mTextUpdateInfo.mStartGlyphIndex,
729 positionsBuffer + endGlyphIndexPlusOne );
732 if( NO_OPERATION != ( LAYOUT & operations ) )
735 uint32_t startRemoveIndex = mModel->mVisualModel->mLines.Count();
736 uint32_t endRemoveIndex = startRemoveIndex;
737 ClearCharacterRuns( startIndex,
739 mModel->mVisualModel->mLines,
743 // Will update the glyph runs.
744 startRemoveIndex = mModel->mVisualModel->mLines.Count();
745 endRemoveIndex = startRemoveIndex;
746 ClearGlyphRuns( mTextUpdateInfo.mStartGlyphIndex,
747 endGlyphIndexPlusOne - 1u,
748 mModel->mVisualModel->mLines,
752 // Set the line index from where to insert the new laid-out lines.
753 mTextUpdateInfo.mStartLineIndex = startRemoveIndex;
755 LineRun* linesBuffer = mModel->mVisualModel->mLines.Begin();
756 mModel->mVisualModel->mLines.Erase( linesBuffer + startRemoveIndex,
757 linesBuffer + endRemoveIndex );
760 if( NO_OPERATION != ( COLOR & operations ) )
762 if( 0u != mModel->mVisualModel->mColorIndices.Count() )
764 ColorIndex* colorIndexBuffer = mModel->mVisualModel->mColorIndices.Begin();
765 mModel->mVisualModel->mColorIndices.Erase( colorIndexBuffer + mTextUpdateInfo.mStartGlyphIndex,
766 colorIndexBuffer + endGlyphIndexPlusOne );
771 void Controller::Impl::ClearModelData( CharacterIndex startIndex, CharacterIndex endIndex, OperationsMask operations )
773 if( mTextUpdateInfo.mClearAll ||
774 ( ( 0u == startIndex ) &&
775 ( mTextUpdateInfo.mPreviousNumberOfCharacters == endIndex + 1u ) ) )
777 ClearFullModelData( operations );
781 // Clear the model data related with characters.
782 ClearCharacterModelData( startIndex, endIndex, operations );
784 // Clear the model data related with glyphs.
785 ClearGlyphModelData( startIndex, endIndex, operations );
788 // The estimated number of lines. Used to avoid reallocations when layouting.
789 mTextUpdateInfo.mEstimatedNumberOfLines = std::max( mModel->mVisualModel->mLines.Count(), mModel->mLogicalModel->mParagraphInfo.Count() );
791 mModel->mVisualModel->ClearCaches();
794 bool Controller::Impl::UpdateModel( OperationsMask operationsRequired )
796 DALI_LOG_INFO( gLogFilter, Debug::General, "Controller::UpdateModel\n" );
798 // Calculate the operations to be done.
799 const OperationsMask operations = static_cast<OperationsMask>( mOperationsPending & operationsRequired );
801 if( NO_OPERATION == operations )
803 // Nothing to do if no operations are pending and required.
807 Vector<Character>& srcCharacters = mModel->mLogicalModel->mText;
808 Vector<Character> displayCharacters;
809 bool useHiddenText = false;
810 if ( mHiddenInput && mEventData != NULL && !mEventData->mIsShowingPlaceholderText)
812 mHiddenInput->Substitute( srcCharacters,displayCharacters );
813 useHiddenText = true;
816 Vector<Character>& utf32Characters = useHiddenText ? displayCharacters : srcCharacters;
817 const Length numberOfCharacters = utf32Characters.Count();
819 // Index to the first character of the first paragraph to be updated.
820 CharacterIndex startIndex = 0u;
821 // Number of characters of the paragraphs to be removed.
822 Length paragraphCharacters = 0u;
824 CalculateTextUpdateIndices( paragraphCharacters );
825 startIndex = mTextUpdateInfo.mParagraphCharacterIndex;
827 if( mTextUpdateInfo.mClearAll ||
828 ( 0u != paragraphCharacters ) )
830 ClearModelData( startIndex, startIndex + ( ( paragraphCharacters > 0u ) ? paragraphCharacters - 1u : 0u ), operations );
833 mTextUpdateInfo.mClearAll = false;
835 // Whether the model is updated.
836 bool updated = false;
838 Vector<LineBreakInfo>& lineBreakInfo = mModel->mLogicalModel->mLineBreakInfo;
839 const Length requestedNumberOfCharacters = mTextUpdateInfo.mRequestedNumberOfCharacters;
841 if( NO_OPERATION != ( GET_LINE_BREAKS & operations ) )
843 // Retrieves the line break info. The line break info is used to split the text in 'paragraphs' to
844 // calculate the bidirectional info for each 'paragraph'.
845 // It's also used to layout the text (where it should be a new line) or to shape the text (text in different lines
846 // is not shaped together).
847 lineBreakInfo.Resize( numberOfCharacters, TextAbstraction::LINE_NO_BREAK );
849 SetLineBreakInfo( utf32Characters,
851 requestedNumberOfCharacters,
854 // Create the paragraph info.
855 mModel->mLogicalModel->CreateParagraphInfo( startIndex,
856 requestedNumberOfCharacters );
860 Vector<WordBreakInfo>& wordBreakInfo = mModel->mLogicalModel->mWordBreakInfo;
861 if( NO_OPERATION != ( GET_WORD_BREAKS & operations ) )
863 // Retrieves the word break info. The word break info is used to layout the text (where to wrap the text in lines).
864 wordBreakInfo.Resize( numberOfCharacters, TextAbstraction::WORD_NO_BREAK );
866 SetWordBreakInfo( utf32Characters,
868 requestedNumberOfCharacters,
873 const bool getScripts = NO_OPERATION != ( GET_SCRIPTS & operations );
874 const bool validateFonts = NO_OPERATION != ( VALIDATE_FONTS & operations );
876 Vector<ScriptRun>& scripts = mModel->mLogicalModel->mScriptRuns;
877 Vector<FontRun>& validFonts = mModel->mLogicalModel->mFontRuns;
879 if( getScripts || validateFonts )
881 // Validates the fonts assigned by the application or assigns default ones.
882 // It makes sure all the characters are going to be rendered by the correct font.
883 MultilanguageSupport multilanguageSupport = MultilanguageSupport::Get();
887 // Retrieves the scripts used in the text.
888 multilanguageSupport.SetScripts( utf32Characters,
890 requestedNumberOfCharacters,
896 // Validate the fonts set through the mark-up string.
897 Vector<FontDescriptionRun>& fontDescriptionRuns = mModel->mLogicalModel->mFontDescriptionRuns;
899 // Get the default font's description.
900 TextAbstraction::FontDescription defaultFontDescription;
901 TextAbstraction::PointSize26Dot6 defaultPointSize = TextAbstraction::FontClient::DEFAULT_POINT_SIZE;
903 if( IsShowingPlaceholderText() && mEventData && ( NULL != mEventData->mPlaceholderFont ) )
905 // If the placeholder font is set specifically, only placeholder font is changed.
906 defaultFontDescription = mEventData->mPlaceholderFont->mFontDescription;
907 if( mEventData->mPlaceholderFont->sizeDefined )
909 defaultPointSize = mEventData->mPlaceholderFont->mDefaultPointSize * 64u;
912 else if( NULL != mFontDefaults )
914 // Set the normal font and the placeholder font.
915 defaultFontDescription = mFontDefaults->mFontDescription;
916 defaultPointSize = mFontDefaults->mDefaultPointSize * 64u;
919 // Validates the fonts. If there is a character with no assigned font it sets a default one.
920 // After this call, fonts are validated.
921 multilanguageSupport.ValidateFonts( utf32Characters,
924 defaultFontDescription,
927 requestedNumberOfCharacters,
933 Vector<Character> mirroredUtf32Characters;
934 bool textMirrored = false;
935 const Length numberOfParagraphs = mModel->mLogicalModel->mParagraphInfo.Count();
936 if( NO_OPERATION != ( BIDI_INFO & operations ) )
938 Vector<BidirectionalParagraphInfoRun>& bidirectionalInfo = mModel->mLogicalModel->mBidirectionalParagraphInfo;
939 bidirectionalInfo.Reserve( numberOfParagraphs );
941 // Calculates the bidirectional info for the whole paragraph if it contains right to left scripts.
942 SetBidirectionalInfo( utf32Characters,
946 requestedNumberOfCharacters,
949 if( 0u != bidirectionalInfo.Count() )
951 // Only set the character directions if there is right to left characters.
952 Vector<CharacterDirection>& directions = mModel->mLogicalModel->mCharacterDirections;
953 GetCharactersDirection( bidirectionalInfo,
956 requestedNumberOfCharacters,
959 // This paragraph has right to left text. Some characters may need to be mirrored.
960 // TODO: consider if the mirrored string can be stored as well.
962 textMirrored = GetMirroredText( utf32Characters,
966 requestedNumberOfCharacters,
967 mirroredUtf32Characters );
971 // There is no right to left characters. Clear the directions vector.
972 mModel->mLogicalModel->mCharacterDirections.Clear();
977 Vector<GlyphInfo>& glyphs = mModel->mVisualModel->mGlyphs;
978 Vector<CharacterIndex>& glyphsToCharactersMap = mModel->mVisualModel->mGlyphsToCharacters;
979 Vector<Length>& charactersPerGlyph = mModel->mVisualModel->mCharactersPerGlyph;
980 Vector<GlyphIndex> newParagraphGlyphs;
981 newParagraphGlyphs.Reserve( numberOfParagraphs );
983 const Length currentNumberOfGlyphs = glyphs.Count();
984 if( NO_OPERATION != ( SHAPE_TEXT & operations ) )
986 const Vector<Character>& textToShape = textMirrored ? mirroredUtf32Characters : utf32Characters;
988 ShapeText( textToShape,
993 mTextUpdateInfo.mStartGlyphIndex,
994 requestedNumberOfCharacters,
996 glyphsToCharactersMap,
998 newParagraphGlyphs );
1000 // Create the 'number of glyphs' per character and the glyph to character conversion tables.
1001 mModel->mVisualModel->CreateGlyphsPerCharacterTable( startIndex, mTextUpdateInfo.mStartGlyphIndex, requestedNumberOfCharacters );
1002 mModel->mVisualModel->CreateCharacterToGlyphTable( startIndex, mTextUpdateInfo.mStartGlyphIndex, requestedNumberOfCharacters );
1006 const Length numberOfGlyphs = glyphs.Count() - currentNumberOfGlyphs;
1008 if( NO_OPERATION != ( GET_GLYPH_METRICS & operations ) )
1010 GlyphInfo* glyphsBuffer = glyphs.Begin();
1011 mMetrics->GetGlyphMetrics( glyphsBuffer + mTextUpdateInfo.mStartGlyphIndex, numberOfGlyphs );
1013 // Update the width and advance of all new paragraph characters.
1014 for( Vector<GlyphIndex>::ConstIterator it = newParagraphGlyphs.Begin(), endIt = newParagraphGlyphs.End(); it != endIt; ++it )
1016 const GlyphIndex index = *it;
1017 GlyphInfo& glyph = *( glyphsBuffer + index );
1019 glyph.xBearing = 0.f;
1021 glyph.advance = 0.f;
1026 if( NO_OPERATION != ( COLOR & operations ) )
1028 // Set the color runs in glyphs.
1029 SetColorSegmentationInfo( mModel->mLogicalModel->mColorRuns,
1030 mModel->mVisualModel->mCharactersToGlyph,
1031 mModel->mVisualModel->mGlyphsPerCharacter,
1033 mTextUpdateInfo.mStartGlyphIndex,
1034 requestedNumberOfCharacters,
1035 mModel->mVisualModel->mColors,
1036 mModel->mVisualModel->mColorIndices );
1041 if( ( NULL != mEventData ) &&
1042 mEventData->mPreEditFlag &&
1043 ( 0u != mModel->mVisualModel->mCharactersToGlyph.Count() ) )
1045 // Add the underline for the pre-edit text.
1046 const GlyphIndex* const charactersToGlyphBuffer = mModel->mVisualModel->mCharactersToGlyph.Begin();
1047 const Length* const glyphsPerCharacterBuffer = mModel->mVisualModel->mGlyphsPerCharacter.Begin();
1049 const GlyphIndex glyphStart = *( charactersToGlyphBuffer + mEventData->mPreEditStartPosition );
1050 const CharacterIndex lastPreEditCharacter = mEventData->mPreEditStartPosition + ( ( mEventData->mPreEditLength > 0u ) ? mEventData->mPreEditLength - 1u : 0u );
1051 const Length numberOfGlyphsLastCharacter = *( glyphsPerCharacterBuffer + lastPreEditCharacter );
1052 const GlyphIndex glyphEnd = *( charactersToGlyphBuffer + lastPreEditCharacter ) + ( numberOfGlyphsLastCharacter > 1u ? numberOfGlyphsLastCharacter - 1u : 0u );
1054 GlyphRun underlineRun;
1055 underlineRun.glyphIndex = glyphStart;
1056 underlineRun.numberOfGlyphs = 1u + glyphEnd - glyphStart;
1058 // TODO: At the moment the underline runs are only for pre-edit.
1059 mModel->mVisualModel->mUnderlineRuns.PushBack( underlineRun );
1062 // The estimated number of lines. Used to avoid reallocations when layouting.
1063 mTextUpdateInfo.mEstimatedNumberOfLines = std::max( mModel->mVisualModel->mLines.Count(), mModel->mLogicalModel->mParagraphInfo.Count() );
1065 // Set the previous number of characters for the next time the text is updated.
1066 mTextUpdateInfo.mPreviousNumberOfCharacters = numberOfCharacters;
1071 void Controller::Impl::RetrieveDefaultInputStyle( InputStyle& inputStyle )
1073 // Sets the default text's color.
1074 inputStyle.textColor = mTextColor;
1075 inputStyle.isDefaultColor = true;
1077 inputStyle.familyName.clear();
1078 inputStyle.weight = TextAbstraction::FontWeight::NORMAL;
1079 inputStyle.width = TextAbstraction::FontWidth::NORMAL;
1080 inputStyle.slant = TextAbstraction::FontSlant::NORMAL;
1081 inputStyle.size = 0.f;
1083 inputStyle.lineSpacing = 0.f;
1085 inputStyle.underlineProperties.clear();
1086 inputStyle.shadowProperties.clear();
1087 inputStyle.embossProperties.clear();
1088 inputStyle.outlineProperties.clear();
1090 inputStyle.isFamilyDefined = false;
1091 inputStyle.isWeightDefined = false;
1092 inputStyle.isWidthDefined = false;
1093 inputStyle.isSlantDefined = false;
1094 inputStyle.isSizeDefined = false;
1096 inputStyle.isLineSpacingDefined = false;
1098 inputStyle.isUnderlineDefined = false;
1099 inputStyle.isShadowDefined = false;
1100 inputStyle.isEmbossDefined = false;
1101 inputStyle.isOutlineDefined = false;
1103 // Sets the default font's family name, weight, width, slant and size.
1106 if( mFontDefaults->familyDefined )
1108 inputStyle.familyName = mFontDefaults->mFontDescription.family;
1109 inputStyle.isFamilyDefined = true;
1112 if( mFontDefaults->weightDefined )
1114 inputStyle.weight = mFontDefaults->mFontDescription.weight;
1115 inputStyle.isWeightDefined = true;
1118 if( mFontDefaults->widthDefined )
1120 inputStyle.width = mFontDefaults->mFontDescription.width;
1121 inputStyle.isWidthDefined = true;
1124 if( mFontDefaults->slantDefined )
1126 inputStyle.slant = mFontDefaults->mFontDescription.slant;
1127 inputStyle.isSlantDefined = true;
1130 if( mFontDefaults->sizeDefined )
1132 inputStyle.size = mFontDefaults->mDefaultPointSize;
1133 inputStyle.isSizeDefined = true;
1138 float Controller::Impl::GetDefaultFontLineHeight()
1140 FontId defaultFontId = 0u;
1141 if( NULL == mFontDefaults )
1143 TextAbstraction::FontDescription fontDescription;
1144 defaultFontId = mFontClient.GetFontId( fontDescription );
1148 defaultFontId = mFontDefaults->GetFontId( mFontClient );
1151 Text::FontMetrics fontMetrics;
1152 mMetrics->GetFontMetrics( defaultFontId, fontMetrics );
1154 return( fontMetrics.ascender - fontMetrics.descender );
1157 void Controller::Impl::OnCursorKeyEvent( const Event& event )
1159 if( NULL == mEventData )
1161 // Nothing to do if there is no text input.
1165 int keyCode = event.p1.mInt;
1166 bool isShiftModifier = event.p2.mBool;
1168 CharacterIndex previousPrimaryCursorPosition = mEventData->mPrimaryCursorPosition;
1170 if( Dali::DALI_KEY_CURSOR_LEFT == keyCode )
1172 if( mEventData->mPrimaryCursorPosition > 0u )
1174 if ( !isShiftModifier && mEventData->mDecorator->IsHighlightVisible() )
1176 mEventData->mPrimaryCursorPosition = std::min(mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition);
1180 mEventData->mPrimaryCursorPosition = CalculateNewCursorIndex( mEventData->mPrimaryCursorPosition - 1u );
1184 else if( Dali::DALI_KEY_CURSOR_RIGHT == keyCode )
1186 if( mModel->mLogicalModel->mText.Count() > mEventData->mPrimaryCursorPosition )
1188 if ( !isShiftModifier && mEventData->mDecorator->IsHighlightVisible() )
1190 mEventData->mPrimaryCursorPosition = std::max(mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition);
1194 mEventData->mPrimaryCursorPosition = CalculateNewCursorIndex( mEventData->mPrimaryCursorPosition );
1198 else if( Dali::DALI_KEY_CURSOR_UP == keyCode && !isShiftModifier )
1200 // Ignore Shift-Up for text selection for now.
1202 // Get first the line index of the current cursor position index.
1203 CharacterIndex characterIndex = 0u;
1205 if( mEventData->mPrimaryCursorPosition > 0u )
1207 characterIndex = mEventData->mPrimaryCursorPosition - 1u;
1210 const LineIndex lineIndex = mModel->mVisualModel->GetLineOfCharacter( characterIndex );
1211 const LineIndex previousLineIndex = ( lineIndex > 0 ? lineIndex - 1u : lineIndex );
1213 // Retrieve the cursor position info.
1214 CursorInfo cursorInfo;
1215 GetCursorPosition( mEventData->mPrimaryCursorPosition,
1218 // Get the line above.
1219 const LineRun& line = *( mModel->mVisualModel->mLines.Begin() + previousLineIndex );
1221 // Get the next hit 'y' point.
1222 const float hitPointY = cursorInfo.lineOffset - 0.5f * ( line.ascender - line.descender );
1224 // Use the cursor hook position 'x' and the next hit 'y' position to calculate the new cursor index.
1225 bool matchedCharacter = false;
1226 mEventData->mPrimaryCursorPosition = Text::GetClosestCursorIndex( mModel->mVisualModel,
1227 mModel->mLogicalModel,
1229 mEventData->mCursorHookPositionX,
1231 CharacterHitTest::TAP,
1234 else if( Dali::DALI_KEY_CURSOR_DOWN == keyCode && !isShiftModifier )
1236 // Ignore Shift-Down for text selection for now.
1238 // Get first the line index of the current cursor position index.
1239 CharacterIndex characterIndex = 0u;
1241 if( mEventData->mPrimaryCursorPosition > 0u )
1243 characterIndex = mEventData->mPrimaryCursorPosition - 1u;
1246 const LineIndex lineIndex = mModel->mVisualModel->GetLineOfCharacter( characterIndex );
1248 if( lineIndex + 1u < mModel->mVisualModel->mLines.Count() )
1250 // Retrieve the cursor position info.
1251 CursorInfo cursorInfo;
1252 GetCursorPosition( mEventData->mPrimaryCursorPosition,
1255 // Get the line below.
1256 const LineRun& line = *( mModel->mVisualModel->mLines.Begin() + lineIndex + 1u );
1258 // Get the next hit 'y' point.
1259 const float hitPointY = cursorInfo.lineOffset + cursorInfo.lineHeight + 0.5f * ( line.ascender - line.descender );
1261 // Use the cursor hook position 'x' and the next hit 'y' position to calculate the new cursor index.
1262 bool matchedCharacter = false;
1263 mEventData->mPrimaryCursorPosition = Text::GetClosestCursorIndex( mModel->mVisualModel,
1264 mModel->mLogicalModel,
1266 mEventData->mCursorHookPositionX,
1268 CharacterHitTest::TAP,
1273 if ( !isShiftModifier && mEventData->mState != EventData::SELECTING )
1275 // Update selection position after moving the cursor
1276 mEventData->mLeftSelectionPosition = mEventData->mPrimaryCursorPosition;
1277 mEventData->mRightSelectionPosition = mEventData->mPrimaryCursorPosition;
1280 if ( isShiftModifier && IsShowingRealText() && mEventData->mShiftSelectionFlag )
1282 // Handle text selection
1283 bool selecting = false;
1285 if ( Dali::DALI_KEY_CURSOR_LEFT == keyCode || Dali::DALI_KEY_CURSOR_RIGHT == keyCode )
1287 // Shift-Left/Right to select the text
1288 int cursorPositionDelta = mEventData->mPrimaryCursorPosition - previousPrimaryCursorPosition;
1289 if ( cursorPositionDelta > 0 || mEventData->mRightSelectionPosition > 0u ) // Check the boundary
1291 mEventData->mRightSelectionPosition += cursorPositionDelta;
1295 else if ( mEventData->mLeftSelectionPosition != mEventData->mRightSelectionPosition )
1297 // Show no grab handles and text highlight if Shift-Up/Down pressed but no selected text
1303 // Notify the cursor position to the imf manager.
1304 if( mEventData->mImfManager )
1306 mEventData->mImfManager.SetCursorPosition( mEventData->mPrimaryCursorPosition );
1307 mEventData->mImfManager.NotifyCursorPosition();
1310 ChangeState( EventData::SELECTING );
1312 mEventData->mUpdateLeftSelectionPosition = true;
1313 mEventData->mUpdateRightSelectionPosition = true;
1314 mEventData->mUpdateGrabHandlePosition = true;
1315 mEventData->mUpdateHighlightBox = true;
1317 // Hide the text selection popup if select the text using keyboard instead of moving grab handles
1318 if( mEventData->mGrabHandlePopupEnabled )
1320 mEventData->mDecorator->SetPopupActive( false );
1326 // Handle normal cursor move
1327 ChangeState( EventData::EDITING );
1328 mEventData->mUpdateCursorPosition = true;
1331 mEventData->mUpdateInputStyle = true;
1332 mEventData->mScrollAfterUpdatePosition = true;
1335 void Controller::Impl::OnTapEvent( const Event& event )
1337 if( NULL != mEventData )
1339 const unsigned int tapCount = event.p1.mUint;
1341 if( 1u == tapCount )
1343 if( IsShowingRealText() )
1345 // Convert from control's coords to text's coords.
1346 const float xPosition = event.p2.mFloat - mModel->mScrollPosition.x;
1347 const float yPosition = event.p3.mFloat - mModel->mScrollPosition.y;
1349 // Keep the tap 'x' position. Used to move the cursor.
1350 mEventData->mCursorHookPositionX = xPosition;
1352 // Whether to touch point hits on a glyph.
1353 bool matchedCharacter = false;
1354 mEventData->mPrimaryCursorPosition = Text::GetClosestCursorIndex( mModel->mVisualModel,
1355 mModel->mLogicalModel,
1359 CharacterHitTest::TAP,
1362 // When the cursor position is changing, delay cursor blinking
1363 mEventData->mDecorator->DelayCursorBlink();
1367 mEventData->mPrimaryCursorPosition = 0u;
1370 // Update selection position after tapping
1371 mEventData->mLeftSelectionPosition = mEventData->mPrimaryCursorPosition;
1372 mEventData->mRightSelectionPosition = mEventData->mPrimaryCursorPosition;
1374 mEventData->mUpdateCursorPosition = true;
1375 mEventData->mUpdateGrabHandlePosition = true;
1376 mEventData->mScrollAfterUpdatePosition = true;
1377 mEventData->mUpdateInputStyle = true;
1379 // Notify the cursor position to the imf manager.
1380 if( mEventData->mImfManager )
1382 mEventData->mImfManager.SetCursorPosition( mEventData->mPrimaryCursorPosition );
1383 mEventData->mImfManager.NotifyCursorPosition();
1386 else if( 2u == tapCount )
1388 if( mEventData->mSelectionEnabled )
1390 // Convert from control's coords to text's coords.
1391 const float xPosition = event.p2.mFloat - mModel->mScrollPosition.x;
1392 const float yPosition = event.p3.mFloat - mModel->mScrollPosition.y;
1394 // Calculates the logical position from the x,y coords.
1395 RepositionSelectionHandles( xPosition,
1397 mEventData->mDoubleTapAction );
1403 void Controller::Impl::OnPanEvent( const Event& event )
1405 if( NULL == mEventData )
1407 // Nothing to do if there is no text input.
1411 const bool isHorizontalScrollEnabled = mEventData->mDecorator->IsHorizontalScrollEnabled();
1412 const bool isVerticalScrollEnabled = mEventData->mDecorator->IsVerticalScrollEnabled();
1414 if( !isHorizontalScrollEnabled && !isVerticalScrollEnabled )
1416 // Nothing to do if scrolling is not enabled.
1420 const int state = event.p1.mInt;
1424 case Gesture::Started:
1426 // Will remove the cursor, handles or text's popup, ...
1427 ChangeState( EventData::TEXT_PANNING );
1430 case Gesture::Continuing:
1432 const Vector2& layoutSize = mModel->mVisualModel->GetLayoutSize();
1433 const Vector2 currentScroll = mModel->mScrollPosition;
1435 if( isHorizontalScrollEnabled )
1437 const float displacementX = event.p2.mFloat;
1438 mModel->mScrollPosition.x += displacementX;
1440 ClampHorizontalScroll( layoutSize );
1443 if( isVerticalScrollEnabled )
1445 const float displacementY = event.p3.mFloat;
1446 mModel->mScrollPosition.y += displacementY;
1448 ClampVerticalScroll( layoutSize );
1451 mEventData->mDecorator->UpdatePositions( mModel->mScrollPosition - currentScroll );
1454 case Gesture::Finished:
1455 case Gesture::Cancelled: // FALLTHROUGH
1457 // Will go back to the previous state to show the cursor, handles, the text's popup, ...
1458 ChangeState( mEventData->mPreviousState );
1466 void Controller::Impl::OnLongPressEvent( const Event& event )
1468 DALI_LOG_INFO( gLogFilter, Debug::General, "Controller::OnLongPressEvent\n" );
1470 if( !IsShowingRealText() && ( EventData::EDITING == mEventData->mState ) )
1472 ChangeState( EventData::EDITING_WITH_POPUP );
1473 mEventData->mDecoratorUpdated = true;
1474 mEventData->mUpdateInputStyle = true;
1478 if( mEventData->mSelectionEnabled )
1480 // Convert from control's coords to text's coords.
1481 const float xPosition = event.p2.mFloat - mModel->mScrollPosition.x;
1482 const float yPosition = event.p3.mFloat - mModel->mScrollPosition.y;
1484 // Calculates the logical position from the x,y coords.
1485 RepositionSelectionHandles( xPosition,
1487 mEventData->mLongPressAction );
1492 void Controller::Impl::OnHandleEvent( const Event& event )
1494 if( NULL == mEventData )
1496 // Nothing to do if there is no text input.
1500 const unsigned int state = event.p1.mUint;
1501 const bool handleStopScrolling = ( HANDLE_STOP_SCROLLING == state );
1502 const bool isSmoothHandlePanEnabled = mEventData->mDecorator->IsSmoothHandlePanEnabled();
1504 if( HANDLE_PRESSED == state )
1506 // Convert from decorator's coords to text's coords.
1507 const float xPosition = event.p2.mFloat - mModel->mScrollPosition.x;
1508 const float yPosition = event.p3.mFloat - mModel->mScrollPosition.y;
1510 // Need to calculate the handle's new position.
1511 bool matchedCharacter = false;
1512 const CharacterIndex handleNewPosition = Text::GetClosestCursorIndex( mModel->mVisualModel,
1513 mModel->mLogicalModel,
1517 CharacterHitTest::SCROLL,
1520 if( Event::GRAB_HANDLE_EVENT == event.type )
1522 ChangeState ( EventData::GRAB_HANDLE_PANNING );
1524 if( handleNewPosition != mEventData->mPrimaryCursorPosition )
1526 // Updates the cursor position if the handle's new position is different than the current one.
1527 mEventData->mUpdateCursorPosition = true;
1528 // Does not update the grab handle position if the smooth panning is enabled. (The decorator does it smooth).
1529 mEventData->mUpdateGrabHandlePosition = !isSmoothHandlePanEnabled;
1530 mEventData->mPrimaryCursorPosition = handleNewPosition;
1533 // 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.
1534 mEventData->mDecoratorUpdated = isSmoothHandlePanEnabled;
1536 else if( Event::LEFT_SELECTION_HANDLE_EVENT == event.type )
1538 ChangeState ( EventData::SELECTION_HANDLE_PANNING );
1540 if( ( handleNewPosition != mEventData->mLeftSelectionPosition ) &&
1541 ( handleNewPosition != mEventData->mRightSelectionPosition ) )
1543 // Updates the highlight box if the handle's new position is different than the current one.
1544 mEventData->mUpdateHighlightBox = true;
1545 // Does not update the selection handle position if the smooth panning is enabled. (The decorator does it smooth).
1546 mEventData->mUpdateLeftSelectionPosition = !isSmoothHandlePanEnabled;
1547 mEventData->mLeftSelectionPosition = handleNewPosition;
1550 // 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.
1551 mEventData->mDecoratorUpdated = isSmoothHandlePanEnabled;
1553 // Will define the order to scroll the text to match the handle position.
1554 mEventData->mIsLeftHandleSelected = true;
1555 mEventData->mIsRightHandleSelected = false;
1557 else if( Event::RIGHT_SELECTION_HANDLE_EVENT == event.type )
1559 ChangeState ( EventData::SELECTION_HANDLE_PANNING );
1561 if( ( handleNewPosition != mEventData->mRightSelectionPosition ) &&
1562 ( handleNewPosition != mEventData->mLeftSelectionPosition ) )
1564 // Updates the highlight box if the handle's new position is different than the current one.
1565 mEventData->mUpdateHighlightBox = true;
1566 // Does not update the selection handle position if the smooth panning is enabled. (The decorator does it smooth).
1567 mEventData->mUpdateRightSelectionPosition = !isSmoothHandlePanEnabled;
1568 mEventData->mRightSelectionPosition = handleNewPosition;
1571 // 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.
1572 mEventData->mDecoratorUpdated = isSmoothHandlePanEnabled;
1574 // Will define the order to scroll the text to match the handle position.
1575 mEventData->mIsLeftHandleSelected = false;
1576 mEventData->mIsRightHandleSelected = true;
1578 } // end ( HANDLE_PRESSED == state )
1579 else if( ( HANDLE_RELEASED == state ) ||
1580 handleStopScrolling )
1582 CharacterIndex handlePosition = 0u;
1583 if( handleStopScrolling || isSmoothHandlePanEnabled )
1585 // Convert from decorator's coords to text's coords.
1586 const float xPosition = event.p2.mFloat - mModel->mScrollPosition.x;
1587 const float yPosition = event.p3.mFloat - mModel->mScrollPosition.y;
1589 bool matchedCharacter = false;
1590 handlePosition = Text::GetClosestCursorIndex( mModel->mVisualModel,
1591 mModel->mLogicalModel,
1595 CharacterHitTest::SCROLL,
1599 if( Event::GRAB_HANDLE_EVENT == event.type )
1601 mEventData->mUpdateCursorPosition = true;
1602 mEventData->mUpdateGrabHandlePosition = true;
1603 mEventData->mUpdateInputStyle = true;
1605 if( !IsClipboardEmpty() )
1607 ChangeState( EventData::EDITING_WITH_PASTE_POPUP ); // Moving grabhandle will show Paste Popup
1610 if( handleStopScrolling || isSmoothHandlePanEnabled )
1612 mEventData->mScrollAfterUpdatePosition = true;
1613 mEventData->mPrimaryCursorPosition = handlePosition;
1616 else if( Event::LEFT_SELECTION_HANDLE_EVENT == event.type )
1618 ChangeState( EventData::SELECTING );
1620 mEventData->mUpdateHighlightBox = true;
1621 mEventData->mUpdateLeftSelectionPosition = true;
1622 mEventData->mUpdateRightSelectionPosition = true;
1624 if( handleStopScrolling || isSmoothHandlePanEnabled )
1626 mEventData->mScrollAfterUpdatePosition = true;
1628 if( ( handlePosition != mEventData->mRightSelectionPosition ) &&
1629 ( handlePosition != mEventData->mLeftSelectionPosition ) )
1631 mEventData->mLeftSelectionPosition = handlePosition;
1635 else if( Event::RIGHT_SELECTION_HANDLE_EVENT == event.type )
1637 ChangeState( EventData::SELECTING );
1639 mEventData->mUpdateHighlightBox = true;
1640 mEventData->mUpdateRightSelectionPosition = true;
1641 mEventData->mUpdateLeftSelectionPosition = true;
1643 if( handleStopScrolling || isSmoothHandlePanEnabled )
1645 mEventData->mScrollAfterUpdatePosition = true;
1646 if( ( handlePosition != mEventData->mRightSelectionPosition ) &&
1647 ( handlePosition != mEventData->mLeftSelectionPosition ) )
1649 mEventData->mRightSelectionPosition = handlePosition;
1654 mEventData->mDecoratorUpdated = true;
1655 } // end ( ( HANDLE_RELEASED == state ) || ( HANDLE_STOP_SCROLLING == state ) )
1656 else if( HANDLE_SCROLLING == state )
1658 const float xSpeed = event.p2.mFloat;
1659 const float ySpeed = event.p3.mFloat;
1660 const Vector2& layoutSize = mModel->mVisualModel->GetLayoutSize();
1661 const Vector2 currentScrollPosition = mModel->mScrollPosition;
1663 mModel->mScrollPosition.x += xSpeed;
1664 mModel->mScrollPosition.y += ySpeed;
1666 ClampHorizontalScroll( layoutSize );
1667 ClampVerticalScroll( layoutSize );
1669 bool endOfScroll = false;
1670 if( Vector2::ZERO == ( currentScrollPosition - mModel->mScrollPosition ) )
1672 // Notify the decorator there is no more text to scroll.
1673 // The decorator won't send more scroll events.
1674 mEventData->mDecorator->NotifyEndOfScroll();
1675 // Still need to set the position of the handle.
1679 // Set the position of the handle.
1680 const bool scrollRightDirection = xSpeed > 0.f;
1681 const bool scrollBottomDirection = ySpeed > 0.f;
1682 const bool leftSelectionHandleEvent = Event::LEFT_SELECTION_HANDLE_EVENT == event.type;
1683 const bool rightSelectionHandleEvent = Event::RIGHT_SELECTION_HANDLE_EVENT == event.type;
1685 if( Event::GRAB_HANDLE_EVENT == event.type )
1687 ChangeState( EventData::GRAB_HANDLE_PANNING );
1689 // Get the grab handle position in decorator coords.
1690 Vector2 position = mEventData->mDecorator->GetPosition( GRAB_HANDLE );
1692 if( mEventData->mDecorator->IsHorizontalScrollEnabled() )
1694 // Position the grag handle close to either the left or right edge.
1695 position.x = scrollRightDirection ? 0.f : mModel->mVisualModel->mControlSize.width;
1698 if( mEventData->mDecorator->IsVerticalScrollEnabled() )
1700 position.x = mEventData->mCursorHookPositionX;
1702 // Position the grag handle close to either the top or bottom edge.
1703 position.y = scrollBottomDirection ? 0.f : mModel->mVisualModel->mControlSize.height;
1706 // Get the new handle position.
1707 // The grab handle's position is in decorator's coords. Need to transforms to text's coords.
1708 bool matchedCharacter = false;
1709 const CharacterIndex handlePosition = Text::GetClosestCursorIndex( mModel->mVisualModel,
1710 mModel->mLogicalModel,
1712 position.x - mModel->mScrollPosition.x,
1713 position.y - mModel->mScrollPosition.y,
1714 CharacterHitTest::SCROLL,
1717 if( mEventData->mPrimaryCursorPosition != handlePosition )
1719 mEventData->mUpdateCursorPosition = true;
1720 mEventData->mUpdateGrabHandlePosition = !isSmoothHandlePanEnabled;
1721 mEventData->mScrollAfterUpdatePosition = true;
1722 mEventData->mPrimaryCursorPosition = handlePosition;
1724 mEventData->mUpdateInputStyle = mEventData->mUpdateCursorPosition;
1726 // Updates the decorator if the soft handle panning is enabled.
1727 mEventData->mDecoratorUpdated = isSmoothHandlePanEnabled;
1729 else if( leftSelectionHandleEvent || rightSelectionHandleEvent )
1731 ChangeState( EventData::SELECTION_HANDLE_PANNING );
1733 // Get the selection handle position in decorator coords.
1734 Vector2 position = mEventData->mDecorator->GetPosition( leftSelectionHandleEvent ? Text::LEFT_SELECTION_HANDLE : Text::RIGHT_SELECTION_HANDLE );
1736 if( mEventData->mDecorator->IsHorizontalScrollEnabled() )
1738 // Position the selection handle close to either the left or right edge.
1739 position.x = scrollRightDirection ? 0.f : mModel->mVisualModel->mControlSize.width;
1742 if( mEventData->mDecorator->IsVerticalScrollEnabled() )
1744 position.x = mEventData->mCursorHookPositionX;
1746 // Position the grag handle close to either the top or bottom edge.
1747 position.y = scrollBottomDirection ? 0.f : mModel->mVisualModel->mControlSize.height;
1750 // Get the new handle position.
1751 // The selection handle's position is in decorator's coords. Need to transform to text's coords.
1752 bool matchedCharacter = false;
1753 const CharacterIndex handlePosition = Text::GetClosestCursorIndex( mModel->mVisualModel,
1754 mModel->mLogicalModel,
1756 position.x - mModel->mScrollPosition.x,
1757 position.y - mModel->mScrollPosition.y,
1758 CharacterHitTest::SCROLL,
1761 if( leftSelectionHandleEvent )
1763 const bool differentHandles = ( mEventData->mLeftSelectionPosition != handlePosition ) && ( mEventData->mRightSelectionPosition != handlePosition );
1765 if( differentHandles || endOfScroll )
1767 mEventData->mUpdateHighlightBox = true;
1768 mEventData->mUpdateLeftSelectionPosition = !isSmoothHandlePanEnabled;
1769 mEventData->mUpdateRightSelectionPosition = isSmoothHandlePanEnabled;
1770 mEventData->mLeftSelectionPosition = handlePosition;
1775 const bool differentHandles = ( mEventData->mRightSelectionPosition != handlePosition ) && ( mEventData->mLeftSelectionPosition != handlePosition );
1776 if( differentHandles || endOfScroll )
1778 mEventData->mUpdateHighlightBox = true;
1779 mEventData->mUpdateRightSelectionPosition = !isSmoothHandlePanEnabled;
1780 mEventData->mUpdateLeftSelectionPosition = isSmoothHandlePanEnabled;
1781 mEventData->mRightSelectionPosition = handlePosition;
1785 if( mEventData->mUpdateLeftSelectionPosition || mEventData->mUpdateRightSelectionPosition )
1787 RepositionSelectionHandles();
1789 mEventData->mScrollAfterUpdatePosition = !isSmoothHandlePanEnabled;
1792 mEventData->mDecoratorUpdated = true;
1793 } // end ( HANDLE_SCROLLING == state )
1796 void Controller::Impl::OnSelectEvent( const Event& event )
1798 if( NULL == mEventData )
1800 // Nothing to do if there is no text.
1804 if( mEventData->mSelectionEnabled )
1806 // Convert from control's coords to text's coords.
1807 const float xPosition = event.p2.mFloat - mModel->mScrollPosition.x;
1808 const float yPosition = event.p3.mFloat - mModel->mScrollPosition.y;
1810 // Calculates the logical position from the x,y coords.
1811 RepositionSelectionHandles( xPosition,
1813 Controller::NoTextTap::HIGHLIGHT );
1817 void Controller::Impl::OnSelectAllEvent()
1819 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "OnSelectAllEvent mEventData->mSelectionEnabled%s \n", mEventData->mSelectionEnabled?"true":"false");
1821 if( NULL == mEventData )
1823 // Nothing to do if there is no text.
1827 if( mEventData->mSelectionEnabled )
1829 ChangeState( EventData::SELECTING );
1831 mEventData->mLeftSelectionPosition = 0u;
1832 mEventData->mRightSelectionPosition = mModel->mLogicalModel->mText.Count();
1834 mEventData->mScrollAfterUpdatePosition = true;
1835 mEventData->mUpdateLeftSelectionPosition = true;
1836 mEventData->mUpdateRightSelectionPosition = true;
1837 mEventData->mUpdateHighlightBox = true;
1841 void Controller::Impl::RetrieveSelection( std::string& selectedText, bool deleteAfterRetrieval )
1843 if( mEventData->mLeftSelectionPosition == mEventData->mRightSelectionPosition )
1845 // Nothing to select if handles are in the same place.
1846 selectedText.clear();
1850 const bool handlesCrossed = mEventData->mLeftSelectionPosition > mEventData->mRightSelectionPosition;
1852 //Get start and end position of selection
1853 const CharacterIndex startOfSelectedText = handlesCrossed ? mEventData->mRightSelectionPosition : mEventData->mLeftSelectionPosition;
1854 const Length lengthOfSelectedText = ( handlesCrossed ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition ) - startOfSelectedText;
1856 Vector<Character>& utf32Characters = mModel->mLogicalModel->mText;
1857 const Length numberOfCharacters = utf32Characters.Count();
1859 // Validate the start and end selection points
1860 if( ( startOfSelectedText + lengthOfSelectedText ) <= numberOfCharacters )
1862 //Get text as a UTF8 string
1863 Utf32ToUtf8( &utf32Characters[startOfSelectedText], lengthOfSelectedText, selectedText );
1865 if( deleteAfterRetrieval ) // Only delete text if copied successfully
1867 // Keep a copy of the current input style.
1868 InputStyle currentInputStyle;
1869 currentInputStyle.Copy( mEventData->mInputStyle );
1871 // Set as input style the style of the first deleted character.
1872 mModel->mLogicalModel->RetrieveStyle( startOfSelectedText, mEventData->mInputStyle );
1874 // Compare if the input style has changed.
1875 const bool hasInputStyleChanged = !currentInputStyle.Equal( mEventData->mInputStyle );
1877 if( hasInputStyleChanged )
1879 const InputStyle::Mask styleChangedMask = currentInputStyle.GetInputStyleChangeMask( mEventData->mInputStyle );
1880 // Queue the input style changed signal.
1881 mEventData->mInputStyleChangedQueue.PushBack( styleChangedMask );
1884 mModel->mLogicalModel->UpdateTextStyleRuns( startOfSelectedText, -static_cast<int>( lengthOfSelectedText ) );
1886 // Mark the paragraphs to be updated.
1887 if( Layout::Engine::SINGLE_LINE_BOX == mLayoutEngine.GetLayout() )
1889 mTextUpdateInfo.mCharacterIndex = 0;
1890 mTextUpdateInfo.mNumberOfCharactersToRemove = mTextUpdateInfo.mPreviousNumberOfCharacters;
1891 mTextUpdateInfo.mNumberOfCharactersToAdd = mTextUpdateInfo.mPreviousNumberOfCharacters - lengthOfSelectedText;
1892 mTextUpdateInfo.mClearAll = true;
1896 mTextUpdateInfo.mCharacterIndex = startOfSelectedText;
1897 mTextUpdateInfo.mNumberOfCharactersToRemove = lengthOfSelectedText;
1900 // Delete text between handles
1901 Vector<Character>::Iterator first = utf32Characters.Begin() + startOfSelectedText;
1902 Vector<Character>::Iterator last = first + lengthOfSelectedText;
1903 utf32Characters.Erase( first, last );
1905 // Will show the cursor at the first character of the selection.
1906 mEventData->mPrimaryCursorPosition = handlesCrossed ? mEventData->mRightSelectionPosition : mEventData->mLeftSelectionPosition;
1910 // Will show the cursor at the last character of the selection.
1911 mEventData->mPrimaryCursorPosition = handlesCrossed ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition;
1914 mEventData->mDecoratorUpdated = true;
1918 void Controller::Impl::ShowClipboard()
1922 mClipboard.ShowClipboard();
1926 void Controller::Impl::HideClipboard()
1928 if( mClipboard && mClipboardHideEnabled )
1930 mClipboard.HideClipboard();
1934 void Controller::Impl::SetClipboardHideEnable(bool enable)
1936 mClipboardHideEnabled = enable;
1939 bool Controller::Impl::CopyStringToClipboard( std::string& source )
1941 //Send string to clipboard
1942 return ( mClipboard && mClipboard.SetItem( source ) );
1945 void Controller::Impl::SendSelectionToClipboard( bool deleteAfterSending )
1947 std::string selectedText;
1948 RetrieveSelection( selectedText, deleteAfterSending );
1949 CopyStringToClipboard( selectedText );
1950 ChangeState( EventData::EDITING );
1953 void Controller::Impl::RequestGetTextFromClipboard()
1957 mClipboard.RequestItem();
1961 void Controller::Impl::RepositionSelectionHandles()
1963 CharacterIndex selectionStart = mEventData->mLeftSelectionPosition;
1964 CharacterIndex selectionEnd = mEventData->mRightSelectionPosition;
1966 if( selectionStart == selectionEnd )
1968 // Nothing to select if handles are in the same place.
1969 // So, deactive Highlight box.
1970 mEventData->mDecorator->SetHighlightActive( false );
1974 mEventData->mDecorator->ClearHighlights();
1976 const GlyphIndex* const charactersToGlyphBuffer = mModel->mVisualModel->mCharactersToGlyph.Begin();
1977 const Length* const glyphsPerCharacterBuffer = mModel->mVisualModel->mGlyphsPerCharacter.Begin();
1978 const GlyphInfo* const glyphsBuffer = mModel->mVisualModel->mGlyphs.Begin();
1979 const Vector2* const positionsBuffer = mModel->mVisualModel->mGlyphPositions.Begin();
1980 const Length* const charactersPerGlyphBuffer = mModel->mVisualModel->mCharactersPerGlyph.Begin();
1981 const CharacterIndex* const glyphToCharacterBuffer = mModel->mVisualModel->mGlyphsToCharacters.Begin();
1982 const CharacterDirection* const modelCharacterDirectionsBuffer = ( 0u != mModel->mLogicalModel->mCharacterDirections.Count() ) ? mModel->mLogicalModel->mCharacterDirections.Begin() : NULL;
1984 const bool isLastCharacter = selectionEnd >= mModel->mLogicalModel->mText.Count();
1985 const CharacterDirection startDirection = ( ( NULL == modelCharacterDirectionsBuffer ) ? false : *( modelCharacterDirectionsBuffer + selectionStart ) );
1986 const CharacterDirection endDirection = ( ( NULL == modelCharacterDirectionsBuffer ) ? false : *( modelCharacterDirectionsBuffer + ( selectionEnd - ( isLastCharacter ? 1u : 0u ) ) ) );
1988 // Swap the indices if the start is greater than the end.
1989 const bool indicesSwapped = selectionStart > selectionEnd;
1991 // Tell the decorator to flip the selection handles if needed.
1992 mEventData->mDecorator->SetSelectionHandleFlipState( indicesSwapped, startDirection, endDirection );
1994 if( indicesSwapped )
1996 std::swap( selectionStart, selectionEnd );
1999 // Get the indices to the first and last selected glyphs.
2000 const CharacterIndex selectionEndMinusOne = selectionEnd - 1u;
2001 const GlyphIndex glyphStart = *( charactersToGlyphBuffer + selectionStart );
2002 const Length numberOfGlyphs = *( glyphsPerCharacterBuffer + selectionEndMinusOne );
2003 const GlyphIndex glyphEnd = *( charactersToGlyphBuffer + selectionEndMinusOne ) + ( ( numberOfGlyphs > 0 ) ? numberOfGlyphs - 1u : 0u );
2005 // Get the lines where the glyphs are laid-out.
2006 const LineRun* lineRun = mModel->mVisualModel->mLines.Begin();
2008 LineIndex lineIndex = 0u;
2009 Length numberOfLines = 0u;
2010 mModel->mVisualModel->GetNumberOfLines( glyphStart,
2011 1u + glyphEnd - glyphStart,
2014 const LineIndex firstLineIndex = lineIndex;
2016 // Create the structure to store some selection box info.
2017 Vector<SelectionBoxInfo> selectionBoxLinesInfo;
2018 selectionBoxLinesInfo.Resize( numberOfLines );
2020 SelectionBoxInfo* selectionBoxInfo = selectionBoxLinesInfo.Begin();
2021 selectionBoxInfo->minX = MAX_FLOAT;
2022 selectionBoxInfo->maxX = MIN_FLOAT;
2024 // Keep the min and max 'x' position to calculate the size and position of the highlighed text.
2025 float minHighlightX = std::numeric_limits<float>::max();
2026 float maxHighlightX = std::numeric_limits<float>::min();
2028 Vector2 highLightPosition; // The highlight position in decorator's coords.
2030 // Retrieve the first line and get the line's vertical offset, the line's height and the index to the last glyph.
2032 // The line's vertical offset of all the lines before the line where the first glyph is laid-out.
2033 selectionBoxInfo->lineOffset = CalculateLineOffset( mModel->mVisualModel->mLines,
2036 // Transform to decorator's (control) coords.
2037 selectionBoxInfo->lineOffset += mModel->mScrollPosition.y;
2039 lineRun += firstLineIndex;
2041 // The line height is the addition of the line ascender and the line descender.
2042 // However, the line descender has a negative value, hence the subtraction.
2043 selectionBoxInfo->lineHeight = lineRun->ascender - lineRun->descender;
2045 GlyphIndex lastGlyphOfLine = lineRun->glyphRun.glyphIndex + lineRun->glyphRun.numberOfGlyphs - 1u;
2047 // Check if the first glyph is a ligature that must be broken like Latin ff, fi, or Arabic ï»», etc which needs special code.
2048 const Length numberOfCharactersStart = *( charactersPerGlyphBuffer + glyphStart );
2049 bool splitStartGlyph = ( numberOfCharactersStart > 1u ) && HasLigatureMustBreak( mModel->mLogicalModel->GetScript( selectionStart ) );
2051 // Check if the last glyph is a ligature that must be broken like Latin ff, fi, or Arabic ï»», etc which needs special code.
2052 const Length numberOfCharactersEnd = *( charactersPerGlyphBuffer + glyphEnd );
2053 bool splitEndGlyph = ( glyphStart != glyphEnd ) && ( numberOfCharactersEnd > 1u ) && HasLigatureMustBreak( mModel->mLogicalModel->GetScript( selectionEndMinusOne ) );
2055 // The number of quads of the selection box.
2056 const unsigned int numberOfQuads = 1u + ( glyphEnd - glyphStart ) + ( ( numberOfLines > 1u ) ? 2u * numberOfLines : 0u );
2057 mEventData->mDecorator->ResizeHighlightQuads( numberOfQuads );
2059 // Count the actual number of quads.
2060 unsigned int actualNumberOfQuads = 0u;
2063 // Traverse the glyphs.
2064 for( GlyphIndex index = glyphStart; index <= glyphEnd; ++index )
2066 const GlyphInfo& glyph = *( glyphsBuffer + index );
2067 const Vector2& position = *( positionsBuffer + index );
2069 if( splitStartGlyph )
2071 // 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.
2073 const float glyphAdvance = glyph.advance / static_cast<float>( numberOfCharactersStart );
2074 const CharacterIndex interGlyphIndex = selectionStart - *( glyphToCharacterBuffer + glyphStart );
2075 // Get the direction of the character.
2076 CharacterDirection isCurrentRightToLeft = false;
2077 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
2079 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + selectionStart );
2082 // The end point could be in the middle of the ligature.
2083 // Calculate the number of characters selected.
2084 const Length numberOfCharacters = ( glyphStart == glyphEnd ) ? ( selectionEnd - selectionStart ) : ( numberOfCharactersStart - interGlyphIndex );
2086 quad.x = lineRun->alignmentOffset + position.x - glyph.xBearing + mModel->mScrollPosition.x + glyphAdvance * static_cast<float>( isCurrentRightToLeft ? ( numberOfCharactersStart - interGlyphIndex - numberOfCharacters ) : interGlyphIndex );
2087 quad.y = selectionBoxInfo->lineOffset;
2088 quad.z = quad.x + static_cast<float>( numberOfCharacters ) * glyphAdvance;
2089 quad.w = selectionBoxInfo->lineOffset + selectionBoxInfo->lineHeight;
2091 // Store the min and max 'x' for each line.
2092 selectionBoxInfo->minX = std::min( selectionBoxInfo->minX, quad.x );
2093 selectionBoxInfo->maxX = std::max( selectionBoxInfo->maxX, quad.z );
2095 mEventData->mDecorator->AddHighlight( actualNumberOfQuads, quad );
2096 ++actualNumberOfQuads;
2098 splitStartGlyph = false;
2102 if( splitEndGlyph && ( index == glyphEnd ) )
2104 // 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.
2106 const float glyphAdvance = glyph.advance / static_cast<float>( numberOfCharactersEnd );
2107 const CharacterIndex interGlyphIndex = selectionEnd - *( glyphToCharacterBuffer + glyphEnd );
2108 // Get the direction of the character.
2109 CharacterDirection isCurrentRightToLeft = false;
2110 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
2112 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + selectionEnd );
2115 const Length numberOfCharacters = numberOfCharactersEnd - interGlyphIndex;
2117 quad.x = lineRun->alignmentOffset + position.x - glyph.xBearing + mModel->mScrollPosition.x + ( isCurrentRightToLeft ? ( glyphAdvance * static_cast<float>( numberOfCharacters ) ) : 0.f );
2118 quad.y = selectionBoxInfo->lineOffset;
2119 quad.z = quad.x + static_cast<float>( interGlyphIndex ) * glyphAdvance;
2120 quad.w = quad.y + selectionBoxInfo->lineHeight;
2122 // Store the min and max 'x' for each line.
2123 selectionBoxInfo->minX = std::min( selectionBoxInfo->minX, quad.x );
2124 selectionBoxInfo->maxX = std::max( selectionBoxInfo->maxX, quad.z );
2126 mEventData->mDecorator->AddHighlight( actualNumberOfQuads,
2128 ++actualNumberOfQuads;
2130 splitEndGlyph = false;
2134 quad.x = lineRun->alignmentOffset + position.x - glyph.xBearing + mModel->mScrollPosition.x;
2135 quad.y = selectionBoxInfo->lineOffset;
2136 quad.z = quad.x + glyph.advance;
2137 quad.w = quad.y + selectionBoxInfo->lineHeight;
2139 // Store the min and max 'x' for each line.
2140 selectionBoxInfo->minX = std::min( selectionBoxInfo->minX, quad.x );
2141 selectionBoxInfo->maxX = std::max( selectionBoxInfo->maxX, quad.z );
2143 mEventData->mDecorator->AddHighlight( actualNumberOfQuads,
2145 ++actualNumberOfQuads;
2147 // Whether to retrieve the next line.
2148 if( index == lastGlyphOfLine )
2151 if( lineIndex < firstLineIndex + numberOfLines )
2153 // Retrieve the next line.
2156 // Get the last glyph of the new line.
2157 lastGlyphOfLine = lineRun->glyphRun.glyphIndex + lineRun->glyphRun.numberOfGlyphs - 1u;
2159 // Keep the offset and height of the current selection box.
2160 const float currentLineOffset = selectionBoxInfo->lineOffset;
2161 const float currentLineHeight = selectionBoxInfo->lineHeight;
2163 // Get the selection box info for the next line.
2166 selectionBoxInfo->minX = MAX_FLOAT;
2167 selectionBoxInfo->maxX = MIN_FLOAT;
2169 // Update the line's vertical offset.
2170 selectionBoxInfo->lineOffset = currentLineOffset + currentLineHeight;
2172 // The line height is the addition of the line ascender and the line descender.
2173 // However, the line descender has a negative value, hence the subtraction.
2174 selectionBoxInfo->lineHeight = lineRun->ascender - lineRun->descender;
2179 // Traverses all the lines and updates the min and max 'x' positions and the total height.
2180 // The final width is calculated after 'boxifying' the selection.
2181 for( Vector<SelectionBoxInfo>::ConstIterator it = selectionBoxLinesInfo.Begin(),
2182 endIt = selectionBoxLinesInfo.End();
2186 const SelectionBoxInfo& info = *it;
2188 // Update the size of the highlighted text.
2189 highLightSize.height += info.lineHeight;
2190 minHighlightX = std::min( minHighlightX, info.minX );
2191 maxHighlightX = std::max( maxHighlightX, info.maxX );
2194 // Add extra geometry to 'boxify' the selection.
2196 if( 1u < numberOfLines )
2198 // Boxify the first line.
2199 lineRun = mModel->mVisualModel->mLines.Begin() + firstLineIndex;
2200 const SelectionBoxInfo& firstSelectionBoxLineInfo = *( selectionBoxLinesInfo.Begin() );
2202 bool boxifyBegin = ( LTR != lineRun->direction ) && ( LTR != startDirection );
2203 bool boxifyEnd = ( LTR == lineRun->direction ) && ( LTR == startDirection );
2208 quad.y = firstSelectionBoxLineInfo.lineOffset;
2209 quad.z = firstSelectionBoxLineInfo.minX;
2210 quad.w = firstSelectionBoxLineInfo.lineOffset + firstSelectionBoxLineInfo.lineHeight;
2212 // Boxify at the beginning of the line.
2213 mEventData->mDecorator->AddHighlight( actualNumberOfQuads,
2215 ++actualNumberOfQuads;
2217 // Update the size of the highlighted text.
2218 minHighlightX = 0.f;
2223 quad.x = firstSelectionBoxLineInfo.maxX;
2224 quad.y = firstSelectionBoxLineInfo.lineOffset;
2225 quad.z = mModel->mVisualModel->mControlSize.width;
2226 quad.w = firstSelectionBoxLineInfo.lineOffset + firstSelectionBoxLineInfo.lineHeight;
2228 // Boxify at the end of the line.
2229 mEventData->mDecorator->AddHighlight( actualNumberOfQuads,
2231 ++actualNumberOfQuads;
2233 // Update the size of the highlighted text.
2234 maxHighlightX = mModel->mVisualModel->mControlSize.width;
2237 // Boxify the central lines.
2238 if( 2u < numberOfLines )
2240 for( Vector<SelectionBoxInfo>::ConstIterator it = selectionBoxLinesInfo.Begin() + 1u,
2241 endIt = selectionBoxLinesInfo.End() - 1u;
2245 const SelectionBoxInfo& info = *it;
2248 quad.y = info.lineOffset;
2250 quad.w = info.lineOffset + info.lineHeight;
2252 mEventData->mDecorator->AddHighlight( actualNumberOfQuads,
2254 ++actualNumberOfQuads;
2257 quad.y = info.lineOffset;
2258 quad.z = mModel->mVisualModel->mControlSize.width;
2259 quad.w = info.lineOffset + info.lineHeight;
2261 mEventData->mDecorator->AddHighlight( actualNumberOfQuads,
2263 ++actualNumberOfQuads;
2266 // Update the size of the highlighted text.
2267 minHighlightX = 0.f;
2268 maxHighlightX = mModel->mVisualModel->mControlSize.width;
2271 // Boxify the last line.
2272 lineRun = mModel->mVisualModel->mLines.Begin() + firstLineIndex + numberOfLines - 1u;
2273 const SelectionBoxInfo& lastSelectionBoxLineInfo = *( selectionBoxLinesInfo.End() - 1u );
2275 boxifyBegin = ( LTR == lineRun->direction ) && ( LTR == endDirection );
2276 boxifyEnd = ( LTR != lineRun->direction ) && ( LTR != endDirection );
2281 quad.y = lastSelectionBoxLineInfo.lineOffset;
2282 quad.z = lastSelectionBoxLineInfo.minX;
2283 quad.w = lastSelectionBoxLineInfo.lineOffset + lastSelectionBoxLineInfo.lineHeight;
2285 // Boxify at the beginning of the line.
2286 mEventData->mDecorator->AddHighlight( actualNumberOfQuads,
2288 ++actualNumberOfQuads;
2290 // Update the size of the highlighted text.
2291 minHighlightX = 0.f;
2296 quad.x = lastSelectionBoxLineInfo.maxX;
2297 quad.y = lastSelectionBoxLineInfo.lineOffset;
2298 quad.z = mModel->mVisualModel->mControlSize.width;
2299 quad.w = lastSelectionBoxLineInfo.lineOffset + lastSelectionBoxLineInfo.lineHeight;
2301 // Boxify at the end of the line.
2302 mEventData->mDecorator->AddHighlight( actualNumberOfQuads,
2304 ++actualNumberOfQuads;
2306 // Update the size of the highlighted text.
2307 maxHighlightX = mModel->mVisualModel->mControlSize.width;
2311 // Set the actual number of quads.
2312 mEventData->mDecorator->ResizeHighlightQuads( actualNumberOfQuads );
2314 // Sets the highlight's size and position. In decorator's coords.
2315 // The highlight's height has been calculated above (before 'boxifying' the highlight).
2316 highLightSize.width = maxHighlightX - minHighlightX;
2318 highLightPosition.x = minHighlightX;
2319 const SelectionBoxInfo& firstSelectionBoxLineInfo = *( selectionBoxLinesInfo.Begin() );
2320 highLightPosition.y = firstSelectionBoxLineInfo.lineOffset;
2322 mEventData->mDecorator->SetHighLightBox( highLightPosition, highLightSize, static_cast<float>( mModel->GetOutlineWidth() ) );
2324 if( !mEventData->mDecorator->IsSmoothHandlePanEnabled() )
2326 CursorInfo primaryCursorInfo;
2327 GetCursorPosition( mEventData->mLeftSelectionPosition,
2328 primaryCursorInfo );
2330 const Vector2 primaryPosition = primaryCursorInfo.primaryPosition + mModel->mScrollPosition;
2332 mEventData->mDecorator->SetPosition( LEFT_SELECTION_HANDLE,
2334 primaryCursorInfo.lineOffset + mModel->mScrollPosition.y,
2335 primaryCursorInfo.lineHeight );
2337 CursorInfo secondaryCursorInfo;
2338 GetCursorPosition( mEventData->mRightSelectionPosition,
2339 secondaryCursorInfo );
2341 const Vector2 secondaryPosition = secondaryCursorInfo.primaryPosition + mModel->mScrollPosition;
2343 mEventData->mDecorator->SetPosition( RIGHT_SELECTION_HANDLE,
2344 secondaryPosition.x,
2345 secondaryCursorInfo.lineOffset + mModel->mScrollPosition.y,
2346 secondaryCursorInfo.lineHeight );
2349 // Set the flag to update the decorator.
2350 mEventData->mDecoratorUpdated = true;
2353 void Controller::Impl::RepositionSelectionHandles( float visualX, float visualY, Controller::NoTextTap::Action action )
2355 if( NULL == mEventData )
2357 // Nothing to do if there is no text input.
2361 if( IsShowingPlaceholderText() )
2363 // Nothing to do if there is the place-holder text.
2367 const Length numberOfGlyphs = mModel->mVisualModel->mGlyphs.Count();
2368 const Length numberOfLines = mModel->mVisualModel->mLines.Count();
2369 if( ( 0 == numberOfGlyphs ) ||
2370 ( 0 == numberOfLines ) )
2372 // Nothing to do if there is no text.
2376 // Find which word was selected
2377 CharacterIndex selectionStart( 0 );
2378 CharacterIndex selectionEnd( 0 );
2379 CharacterIndex noTextHitIndex( 0 );
2380 const bool characterHit = FindSelectionIndices( mModel->mVisualModel,
2381 mModel->mLogicalModel,
2388 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "%p selectionStart %d selectionEnd %d\n", this, selectionStart, selectionEnd );
2390 if( characterHit || ( Controller::NoTextTap::HIGHLIGHT == action ) )
2392 ChangeState( EventData::SELECTING );
2394 mEventData->mLeftSelectionPosition = selectionStart;
2395 mEventData->mRightSelectionPosition = selectionEnd;
2397 mEventData->mUpdateLeftSelectionPosition = true;
2398 mEventData->mUpdateRightSelectionPosition = true;
2399 mEventData->mUpdateHighlightBox = true;
2401 // It may happen an IMF commit event arrives before the selection event
2402 // if the IMF manager is in pre-edit state. The commit event will set the
2403 // mEventData->mUpdateCursorPosition flag to true. If it's not set back
2404 // to false, the highlight box won't be updated.
2405 mEventData->mUpdateCursorPosition = false;
2407 mEventData->mScrollAfterUpdatePosition = ( mEventData->mLeftSelectionPosition != mEventData->mRightSelectionPosition );
2409 // Cursor to be positioned at end of selection so if selection interrupted and edit mode restarted the cursor will be at end of selection
2410 mEventData->mPrimaryCursorPosition = std::max( mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition );
2412 else if( Controller::NoTextTap::SHOW_SELECTION_POPUP == action )
2414 // Nothing to select. i.e. a white space, out of bounds
2415 ChangeState( EventData::EDITING_WITH_POPUP );
2417 mEventData->mPrimaryCursorPosition = noTextHitIndex;
2419 mEventData->mUpdateCursorPosition = true;
2420 mEventData->mUpdateGrabHandlePosition = true;
2421 mEventData->mScrollAfterUpdatePosition = true;
2422 mEventData->mUpdateInputStyle = true;
2424 else if( Controller::NoTextTap::NO_ACTION == action )
2426 // Nothing to select. i.e. a white space, out of bounds
2427 mEventData->mPrimaryCursorPosition = noTextHitIndex;
2429 mEventData->mUpdateCursorPosition = true;
2430 mEventData->mUpdateGrabHandlePosition = true;
2431 mEventData->mScrollAfterUpdatePosition = true;
2432 mEventData->mUpdateInputStyle = true;
2436 void Controller::Impl::SetPopupButtons()
2439 * Sets the Popup buttons to be shown depending on State.
2441 * If SELECTING : CUT & COPY + ( PASTE & CLIPBOARD if content available to paste )
2443 * If EDITING_WITH_POPUP : SELECT & SELECT_ALL
2446 TextSelectionPopup::Buttons buttonsToShow = TextSelectionPopup::NONE;
2448 if( EventData::SELECTING == mEventData->mState )
2450 buttonsToShow = TextSelectionPopup::Buttons( TextSelectionPopup::CUT | TextSelectionPopup::COPY );
2452 if( !IsClipboardEmpty() )
2454 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
2455 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::CLIPBOARD ) );
2458 if( !mEventData->mAllTextSelected )
2460 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::SELECT_ALL ) );
2463 else if( EventData::EDITING_WITH_POPUP == mEventData->mState )
2465 if( mModel->mLogicalModel->mText.Count() && !IsShowingPlaceholderText() )
2467 buttonsToShow = TextSelectionPopup::Buttons( TextSelectionPopup::SELECT | TextSelectionPopup::SELECT_ALL );
2470 if( !IsClipboardEmpty() )
2472 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
2473 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::CLIPBOARD ) );
2476 else if( EventData::EDITING_WITH_PASTE_POPUP == mEventData->mState )
2478 if ( !IsClipboardEmpty() )
2480 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
2481 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::CLIPBOARD ) );
2485 mEventData->mDecorator->SetEnabledPopupButtons( buttonsToShow );
2488 void Controller::Impl::ChangeState( EventData::State newState )
2490 if( NULL == mEventData )
2492 // Nothing to do if there is no text input.
2496 DALI_LOG_INFO( gLogFilter, Debug::General, "ChangeState state:%d newstate:%d\n", mEventData->mState, newState );
2498 if( mEventData->mState != newState )
2500 mEventData->mPreviousState = mEventData->mState;
2501 mEventData->mState = newState;
2503 switch( mEventData->mState )
2505 case EventData::INACTIVE:
2507 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
2508 mEventData->mDecorator->StopCursorBlink();
2509 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
2510 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
2511 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
2512 mEventData->mDecorator->SetHighlightActive( false );
2513 mEventData->mDecorator->SetPopupActive( false );
2514 mEventData->mDecoratorUpdated = true;
2517 case EventData::INTERRUPTED:
2519 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
2520 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
2521 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
2522 mEventData->mDecorator->SetHighlightActive( false );
2523 mEventData->mDecorator->SetPopupActive( false );
2524 mEventData->mDecoratorUpdated = true;
2527 case EventData::SELECTING:
2529 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
2530 mEventData->mDecorator->StopCursorBlink();
2531 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
2532 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true );
2533 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true );
2534 mEventData->mDecorator->SetHighlightActive( true );
2535 if( mEventData->mGrabHandlePopupEnabled )
2538 mEventData->mDecorator->SetPopupActive( true );
2540 mEventData->mDecoratorUpdated = true;
2543 case EventData::EDITING:
2545 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
2546 if( mEventData->mCursorBlinkEnabled )
2548 mEventData->mDecorator->StartCursorBlink();
2550 // Grab handle is not shown until a tap is received whilst EDITING
2551 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
2552 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
2553 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
2554 mEventData->mDecorator->SetHighlightActive( false );
2555 if( mEventData->mGrabHandlePopupEnabled )
2557 mEventData->mDecorator->SetPopupActive( false );
2559 mEventData->mDecoratorUpdated = true;
2562 case EventData::EDITING_WITH_POPUP:
2564 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "EDITING_WITH_POPUP \n", newState );
2566 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
2567 if( mEventData->mCursorBlinkEnabled )
2569 mEventData->mDecorator->StartCursorBlink();
2571 if( mEventData->mSelectionEnabled )
2573 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
2574 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
2575 mEventData->mDecorator->SetHighlightActive( false );
2579 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
2581 if( mEventData->mGrabHandlePopupEnabled )
2584 mEventData->mDecorator->SetPopupActive( true );
2586 mEventData->mDecoratorUpdated = true;
2589 case EventData::EDITING_WITH_GRAB_HANDLE:
2591 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "EDITING_WITH_GRAB_HANDLE \n", newState );
2593 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
2594 if( mEventData->mCursorBlinkEnabled )
2596 mEventData->mDecorator->StartCursorBlink();
2598 // Grab handle is not shown until a tap is received whilst EDITING
2599 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
2600 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
2601 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
2602 mEventData->mDecorator->SetHighlightActive( false );
2603 if( mEventData->mGrabHandlePopupEnabled )
2605 mEventData->mDecorator->SetPopupActive( false );
2607 mEventData->mDecoratorUpdated = true;
2610 case EventData::SELECTION_HANDLE_PANNING:
2612 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
2613 mEventData->mDecorator->StopCursorBlink();
2614 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
2615 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true );
2616 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true );
2617 mEventData->mDecorator->SetHighlightActive( true );
2618 if( mEventData->mGrabHandlePopupEnabled )
2620 mEventData->mDecorator->SetPopupActive( false );
2622 mEventData->mDecoratorUpdated = true;
2625 case EventData::GRAB_HANDLE_PANNING:
2627 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "GRAB_HANDLE_PANNING \n", newState );
2629 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
2630 if( mEventData->mCursorBlinkEnabled )
2632 mEventData->mDecorator->StartCursorBlink();
2634 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
2635 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
2636 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
2637 mEventData->mDecorator->SetHighlightActive( false );
2638 if( mEventData->mGrabHandlePopupEnabled )
2640 mEventData->mDecorator->SetPopupActive( false );
2642 mEventData->mDecoratorUpdated = true;
2645 case EventData::EDITING_WITH_PASTE_POPUP:
2647 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "EDITING_WITH_PASTE_POPUP \n", newState );
2649 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
2650 if( mEventData->mCursorBlinkEnabled )
2652 mEventData->mDecorator->StartCursorBlink();
2655 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
2656 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
2657 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
2658 mEventData->mDecorator->SetHighlightActive( false );
2660 if( mEventData->mGrabHandlePopupEnabled )
2663 mEventData->mDecorator->SetPopupActive( true );
2665 mEventData->mDecoratorUpdated = true;
2668 case EventData::TEXT_PANNING:
2670 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
2671 mEventData->mDecorator->StopCursorBlink();
2672 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
2673 if( mEventData->mDecorator->IsHandleActive( LEFT_SELECTION_HANDLE ) ||
2674 mEventData->mDecorator->IsHandleActive( RIGHT_SELECTION_HANDLE ) )
2676 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
2677 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
2678 mEventData->mDecorator->SetHighlightActive( true );
2681 if( mEventData->mGrabHandlePopupEnabled )
2683 mEventData->mDecorator->SetPopupActive( false );
2686 mEventData->mDecoratorUpdated = true;
2693 void Controller::Impl::GetCursorPosition( CharacterIndex logical,
2694 CursorInfo& cursorInfo )
2696 if( !IsShowingRealText() )
2698 // Do not want to use the place-holder text to set the cursor position.
2700 // Use the line's height of the font's family set to set the cursor's size.
2701 // If there is no font's family set, use the default font.
2702 // Use the current alignment to place the cursor at the beginning, center or end of the box.
2704 cursorInfo.lineOffset = 0.f;
2705 cursorInfo.lineHeight = GetDefaultFontLineHeight();
2706 cursorInfo.primaryCursorHeight = cursorInfo.lineHeight;
2708 switch( mModel->mHorizontalAlignment )
2710 case Text::HorizontalAlignment::BEGIN :
2712 cursorInfo.primaryPosition.x = 0.f;
2715 case Text::HorizontalAlignment::CENTER:
2717 cursorInfo.primaryPosition.x = floorf( 0.5f * mModel->mVisualModel->mControlSize.width );
2720 case Text::HorizontalAlignment::END:
2722 cursorInfo.primaryPosition.x = mModel->mVisualModel->mControlSize.width - mEventData->mDecorator->GetCursorWidth();
2727 // Nothing else to do.
2731 const bool isMultiLine = ( Layout::Engine::MULTI_LINE_BOX == mLayoutEngine.GetLayout() );
2732 GetCursorPositionParameters parameters;
2733 parameters.visualModel = mModel->mVisualModel;
2734 parameters.logicalModel = mModel->mLogicalModel;
2735 parameters.metrics = mMetrics;
2736 parameters.logical = logical;
2737 parameters.isMultiline = isMultiLine;
2739 Text::GetCursorPosition( parameters,
2742 // Adds Outline offset.
2743 const float outlineWidth = static_cast<float>( mModel->GetOutlineWidth() );
2744 cursorInfo.primaryPosition.x += outlineWidth;
2745 cursorInfo.primaryPosition.y += outlineWidth;
2746 cursorInfo.secondaryPosition.x += outlineWidth;
2747 cursorInfo.secondaryPosition.y += outlineWidth;
2751 // If the text is editable and multi-line, the cursor position after a white space shouldn't exceed the boundaries of the text control.
2753 // Note the white spaces laid-out at the end of the line might exceed the boundaries of the control.
2754 // The reason is a wrapped line must not start with a white space so they are laid-out at the end of the line.
2756 if( 0.f > cursorInfo.primaryPosition.x )
2758 cursorInfo.primaryPosition.x = 0.f;
2761 const float edgeWidth = mModel->mVisualModel->mControlSize.width - static_cast<float>( mEventData->mDecorator->GetCursorWidth() );
2762 if( cursorInfo.primaryPosition.x > edgeWidth )
2764 cursorInfo.primaryPosition.x = edgeWidth;
2769 CharacterIndex Controller::Impl::CalculateNewCursorIndex( CharacterIndex index ) const
2771 if( NULL == mEventData )
2773 // Nothing to do if there is no text input.
2777 CharacterIndex cursorIndex = mEventData->mPrimaryCursorPosition;
2779 const GlyphIndex* const charactersToGlyphBuffer = mModel->mVisualModel->mCharactersToGlyph.Begin();
2780 const Length* const charactersPerGlyphBuffer = mModel->mVisualModel->mCharactersPerGlyph.Begin();
2782 GlyphIndex glyphIndex = *( charactersToGlyphBuffer + index );
2783 Length numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
2785 if( numberOfCharacters > 1u )
2787 const Script script = mModel->mLogicalModel->GetScript( index );
2788 if( HasLigatureMustBreak( script ) )
2790 // Prevents to jump the whole Latin ligatures like fi, ff, or Arabic ï»», ...
2791 numberOfCharacters = 1u;
2796 while( 0u == numberOfCharacters )
2799 numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
2803 if( index < mEventData->mPrimaryCursorPosition )
2805 cursorIndex -= numberOfCharacters;
2809 cursorIndex += numberOfCharacters;
2812 // Will update the cursor hook position.
2813 mEventData->mUpdateCursorHookPosition = true;
2818 void Controller::Impl::UpdateCursorPosition( const CursorInfo& cursorInfo )
2820 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "-->Controller::UpdateCursorPosition %p\n", this );
2821 if( NULL == mEventData )
2823 // Nothing to do if there is no text input.
2824 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition no event data\n" );
2828 const Vector2 cursorPosition = cursorInfo.primaryPosition + mModel->mScrollPosition;
2830 mEventData->mDecorator->SetGlyphOffset( PRIMARY_CURSOR, cursorInfo.glyphOffset );
2832 // Sets the cursor position.
2833 mEventData->mDecorator->SetPosition( PRIMARY_CURSOR,
2836 cursorInfo.primaryCursorHeight,
2837 cursorInfo.lineHeight );
2838 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Primary cursor position: %f,%f\n", cursorPosition.x, cursorPosition.y );
2840 if( mEventData->mUpdateGrabHandlePosition )
2842 // Sets the grab handle position.
2843 mEventData->mDecorator->SetPosition( GRAB_HANDLE,
2845 cursorInfo.lineOffset + mModel->mScrollPosition.y,
2846 cursorInfo.lineHeight );
2849 if( cursorInfo.isSecondaryCursor )
2851 mEventData->mDecorator->SetPosition( SECONDARY_CURSOR,
2852 cursorInfo.secondaryPosition.x + mModel->mScrollPosition.x,
2853 cursorInfo.secondaryPosition.y + mModel->mScrollPosition.y,
2854 cursorInfo.secondaryCursorHeight,
2855 cursorInfo.lineHeight );
2856 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Secondary cursor position: %f,%f\n", cursorInfo.secondaryPosition.x + mModel->mScrollPosition.x, cursorInfo.secondaryPosition.y + mModel->mScrollPosition.y );
2859 // Set which cursors are active according the state.
2860 if( EventData::IsEditingState( mEventData->mState ) || ( EventData::GRAB_HANDLE_PANNING == mEventData->mState ) )
2862 if( cursorInfo.isSecondaryCursor )
2864 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_BOTH );
2868 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
2873 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
2876 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition\n" );
2879 void Controller::Impl::UpdateSelectionHandle( HandleType handleType,
2880 const CursorInfo& cursorInfo )
2882 if( ( LEFT_SELECTION_HANDLE != handleType ) &&
2883 ( RIGHT_SELECTION_HANDLE != handleType ) )
2888 const Vector2 cursorPosition = cursorInfo.primaryPosition + mModel->mScrollPosition;
2890 // Sets the handle's position.
2891 mEventData->mDecorator->SetPosition( handleType,
2893 cursorInfo.lineOffset + mModel->mScrollPosition.y,
2894 cursorInfo.lineHeight );
2896 // If selection handle at start of the text and other at end of the text then all text is selected.
2897 const CharacterIndex startOfSelection = std::min( mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition );
2898 const CharacterIndex endOfSelection = std::max ( mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition );
2899 mEventData->mAllTextSelected = ( startOfSelection == 0 ) && ( endOfSelection == mModel->mLogicalModel->mText.Count() );
2902 void Controller::Impl::ClampHorizontalScroll( const Vector2& layoutSize )
2904 // Clamp between -space & -alignment offset.
2906 if( layoutSize.width > mModel->mVisualModel->mControlSize.width )
2908 const float space = ( layoutSize.width - mModel->mVisualModel->mControlSize.width ) + mModel->mAlignmentOffset;
2909 mModel->mScrollPosition.x = ( mModel->mScrollPosition.x < -space ) ? -space : mModel->mScrollPosition.x;
2910 mModel->mScrollPosition.x = ( mModel->mScrollPosition.x > -mModel->mAlignmentOffset ) ? -mModel->mAlignmentOffset : mModel->mScrollPosition.x;
2912 mEventData->mDecoratorUpdated = true;
2916 mModel->mScrollPosition.x = 0.f;
2920 void Controller::Impl::ClampVerticalScroll( const Vector2& layoutSize )
2922 if( Layout::Engine::SINGLE_LINE_BOX == mLayoutEngine.GetLayout() )
2924 // Nothing to do if the text is single line.
2928 // Clamp between -space & 0.
2929 if( layoutSize.height > mModel->mVisualModel->mControlSize.height )
2931 const float space = ( layoutSize.height - mModel->mVisualModel->mControlSize.height );
2932 mModel->mScrollPosition.y = ( mModel->mScrollPosition.y < -space ) ? -space : mModel->mScrollPosition.y;
2933 mModel->mScrollPosition.y = ( mModel->mScrollPosition.y > 0.f ) ? 0.f : mModel->mScrollPosition.y;
2935 mEventData->mDecoratorUpdated = true;
2939 mModel->mScrollPosition.y = 0.f;
2943 void Controller::Impl::ScrollToMakePositionVisible( const Vector2& position, float lineHeight )
2945 const float cursorWidth = mEventData->mDecorator ? static_cast<float>( mEventData->mDecorator->GetCursorWidth() ) : 0.f;
2947 // position is in actor's coords.
2948 const float positionEndX = position.x + cursorWidth;
2949 const float positionEndY = position.y + lineHeight;
2951 // Transform the position to decorator coords.
2952 const float decoratorPositionBeginX = position.x + mModel->mScrollPosition.x;
2953 const float decoratorPositionEndX = positionEndX + mModel->mScrollPosition.x;
2955 const float decoratorPositionBeginY = position.y + mModel->mScrollPosition.y;
2956 const float decoratorPositionEndY = positionEndY + mModel->mScrollPosition.y;
2958 if( decoratorPositionBeginX < 0.f )
2960 mModel->mScrollPosition.x = -position.x;
2962 else if( decoratorPositionEndX > mModel->mVisualModel->mControlSize.width )
2964 mModel->mScrollPosition.x = mModel->mVisualModel->mControlSize.width - positionEndX;
2967 if( Layout::Engine::MULTI_LINE_BOX == mLayoutEngine.GetLayout() )
2969 if( decoratorPositionBeginY < 0.f )
2971 mModel->mScrollPosition.y = -position.y;
2973 else if( decoratorPositionEndY > mModel->mVisualModel->mControlSize.height )
2975 mModel->mScrollPosition.y = mModel->mVisualModel->mControlSize.height - positionEndY;
2980 void Controller::Impl::ScrollTextToMatchCursor( const CursorInfo& cursorInfo )
2982 // Get the current cursor position in decorator coords.
2983 const Vector2& currentCursorPosition = mEventData->mDecorator->GetPosition( PRIMARY_CURSOR );
2985 const LineIndex lineIndex = mModel->mVisualModel->GetLineOfCharacter( mEventData->mPrimaryCursorPosition );
2989 // Calculate the offset to match the cursor position before the character was deleted.
2990 mModel->mScrollPosition.x = currentCursorPosition.x - cursorInfo.primaryPosition.x;
2992 //If text control has more than two lines and current line index is not last, calculate scrollpositionY
2993 if( mModel->mVisualModel->mLines.Count() > 1u && lineIndex != mModel->mVisualModel->mLines.Count() -1u )
2995 const float currentCursorGlyphOffset = mEventData->mDecorator->GetGlyphOffset( PRIMARY_CURSOR );
2996 mModel->mScrollPosition.y = currentCursorPosition.y - cursorInfo.lineOffset - currentCursorGlyphOffset;
3000 ClampHorizontalScroll( mModel->mVisualModel->GetLayoutSize() );
3001 ClampVerticalScroll( mModel->mVisualModel->GetLayoutSize() );
3003 // Makes the new cursor position visible if needed.
3004 ScrollToMakePositionVisible( cursorInfo.primaryPosition, cursorInfo.lineHeight );
3007 void Controller::Impl::RequestRelayout()
3009 if( NULL != mControlInterface )
3011 mControlInterface->RequestTextRelayout();
3017 } // namespace Toolkit