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 mTextUpdateInfo.mRequestedNumberOfCharacters = mTextUpdateInfo.mNumberOfCharactersToAdd - mTextUpdateInfo.mNumberOfCharactersToRemove;
429 // Nothing else to do if there are no paragraphs.
433 // Find the paragraphs to be updated.
434 Vector<ParagraphRunIndex> paragraphsToBeUpdated;
435 if( mTextUpdateInfo.mCharacterIndex >= mTextUpdateInfo.mPreviousNumberOfCharacters )
437 // Text is being added at the end of the current text.
438 if( mTextUpdateInfo.mIsLastCharacterNewParagraph )
440 // Text is being added in a new paragraph after the last character of the text.
441 mTextUpdateInfo.mParagraphCharacterIndex = mTextUpdateInfo.mPreviousNumberOfCharacters;
442 numberOfCharacters = 0u;
443 mTextUpdateInfo.mRequestedNumberOfCharacters = mTextUpdateInfo.mNumberOfCharactersToAdd - mTextUpdateInfo.mNumberOfCharactersToRemove;
445 mTextUpdateInfo.mStartGlyphIndex = mModel->mVisualModel->mGlyphs.Count();
446 mTextUpdateInfo.mStartLineIndex = mModel->mVisualModel->mLines.Count() - 1u;
448 // Nothing else to do;
452 paragraphsToBeUpdated.PushBack( numberOfParagraphs - 1u );
456 Length numberOfCharactersToUpdate = 0u;
457 if( mTextUpdateInfo.mFullRelayoutNeeded )
459 numberOfCharactersToUpdate = mTextUpdateInfo.mPreviousNumberOfCharacters;
463 numberOfCharactersToUpdate = ( mTextUpdateInfo.mNumberOfCharactersToRemove > 0u ) ? mTextUpdateInfo.mNumberOfCharactersToRemove : 1u;
465 mModel->mLogicalModel->FindParagraphs( mTextUpdateInfo.mCharacterIndex,
466 numberOfCharactersToUpdate,
467 paragraphsToBeUpdated );
470 if( 0u != paragraphsToBeUpdated.Count() )
472 const ParagraphRunIndex firstParagraphIndex = *( paragraphsToBeUpdated.Begin() );
473 const ParagraphRun& firstParagraph = *( mModel->mLogicalModel->mParagraphInfo.Begin() + firstParagraphIndex );
474 mTextUpdateInfo.mParagraphCharacterIndex = firstParagraph.characterRun.characterIndex;
476 ParagraphRunIndex lastParagraphIndex = *( paragraphsToBeUpdated.End() - 1u );
477 const ParagraphRun& lastParagraph = *( mModel->mLogicalModel->mParagraphInfo.Begin() + lastParagraphIndex );
479 if( ( mTextUpdateInfo.mNumberOfCharactersToRemove > 0u ) && // Some character are removed.
480 ( lastParagraphIndex < numberOfParagraphs - 1u ) && // There is a next paragraph.
481 ( ( lastParagraph.characterRun.characterIndex + lastParagraph.characterRun.numberOfCharacters ) == // The last removed character is the new paragraph character.
482 ( mTextUpdateInfo.mCharacterIndex + mTextUpdateInfo.mNumberOfCharactersToRemove ) ) )
484 // The new paragraph character of the last updated paragraph has been removed so is going to be merged with the next one.
485 const ParagraphRun& lastParagraph = *( mModel->mLogicalModel->mParagraphInfo.Begin() + lastParagraphIndex + 1u );
487 numberOfCharacters = lastParagraph.characterRun.characterIndex + lastParagraph.characterRun.numberOfCharacters - mTextUpdateInfo.mParagraphCharacterIndex;
491 numberOfCharacters = lastParagraph.characterRun.characterIndex + lastParagraph.characterRun.numberOfCharacters - mTextUpdateInfo.mParagraphCharacterIndex;
495 mTextUpdateInfo.mRequestedNumberOfCharacters = numberOfCharacters + mTextUpdateInfo.mNumberOfCharactersToAdd - mTextUpdateInfo.mNumberOfCharactersToRemove;
496 mTextUpdateInfo.mStartGlyphIndex = *( mModel->mVisualModel->mCharactersToGlyph.Begin() + mTextUpdateInfo.mParagraphCharacterIndex );
499 void Controller::Impl::ClearFullModelData( OperationsMask operations )
501 if( NO_OPERATION != ( GET_LINE_BREAKS & operations ) )
503 mModel->mLogicalModel->mLineBreakInfo.Clear();
504 mModel->mLogicalModel->mParagraphInfo.Clear();
507 if( NO_OPERATION != ( GET_WORD_BREAKS & operations ) )
509 mModel->mLogicalModel->mLineBreakInfo.Clear();
512 if( NO_OPERATION != ( GET_SCRIPTS & operations ) )
514 mModel->mLogicalModel->mScriptRuns.Clear();
517 if( NO_OPERATION != ( VALIDATE_FONTS & operations ) )
519 mModel->mLogicalModel->mFontRuns.Clear();
522 if( 0u != mModel->mLogicalModel->mBidirectionalParagraphInfo.Count() )
524 if( NO_OPERATION != ( BIDI_INFO & operations ) )
526 mModel->mLogicalModel->mBidirectionalParagraphInfo.Clear();
527 mModel->mLogicalModel->mCharacterDirections.Clear();
530 if( NO_OPERATION != ( REORDER & operations ) )
532 // Free the allocated memory used to store the conversion table in the bidirectional line info run.
533 for( Vector<BidirectionalLineInfoRun>::Iterator it = mModel->mLogicalModel->mBidirectionalLineInfo.Begin(),
534 endIt = mModel->mLogicalModel->mBidirectionalLineInfo.End();
538 BidirectionalLineInfoRun& bidiLineInfo = *it;
540 free( bidiLineInfo.visualToLogicalMap );
541 bidiLineInfo.visualToLogicalMap = NULL;
543 mModel->mLogicalModel->mBidirectionalLineInfo.Clear();
547 if( NO_OPERATION != ( SHAPE_TEXT & operations ) )
549 mModel->mVisualModel->mGlyphs.Clear();
550 mModel->mVisualModel->mGlyphsToCharacters.Clear();
551 mModel->mVisualModel->mCharactersToGlyph.Clear();
552 mModel->mVisualModel->mCharactersPerGlyph.Clear();
553 mModel->mVisualModel->mGlyphsPerCharacter.Clear();
554 mModel->mVisualModel->mGlyphPositions.Clear();
557 if( NO_OPERATION != ( LAYOUT & operations ) )
559 mModel->mVisualModel->mLines.Clear();
562 if( NO_OPERATION != ( COLOR & operations ) )
564 mModel->mVisualModel->mColorIndices.Clear();
568 void Controller::Impl::ClearCharacterModelData( CharacterIndex startIndex, CharacterIndex endIndex, OperationsMask operations )
570 const CharacterIndex endIndexPlusOne = endIndex + 1u;
572 if( NO_OPERATION != ( GET_LINE_BREAKS & operations ) )
574 // Clear the line break info.
575 LineBreakInfo* lineBreakInfoBuffer = mModel->mLogicalModel->mLineBreakInfo.Begin();
577 mModel->mLogicalModel->mLineBreakInfo.Erase( lineBreakInfoBuffer + startIndex,
578 lineBreakInfoBuffer + endIndexPlusOne );
580 // Clear the paragraphs.
581 ClearCharacterRuns( startIndex,
583 mModel->mLogicalModel->mParagraphInfo );
586 if( NO_OPERATION != ( GET_WORD_BREAKS & operations ) )
588 // Clear the word break info.
589 WordBreakInfo* wordBreakInfoBuffer = mModel->mLogicalModel->mWordBreakInfo.Begin();
591 mModel->mLogicalModel->mWordBreakInfo.Erase( wordBreakInfoBuffer + startIndex,
592 wordBreakInfoBuffer + endIndexPlusOne );
595 if( NO_OPERATION != ( GET_SCRIPTS & operations ) )
597 // Clear the scripts.
598 ClearCharacterRuns( startIndex,
600 mModel->mLogicalModel->mScriptRuns );
603 if( NO_OPERATION != ( VALIDATE_FONTS & operations ) )
606 ClearCharacterRuns( startIndex,
608 mModel->mLogicalModel->mFontRuns );
611 if( 0u != mModel->mLogicalModel->mBidirectionalParagraphInfo.Count() )
613 if( NO_OPERATION != ( BIDI_INFO & operations ) )
615 // Clear the bidirectional paragraph info.
616 ClearCharacterRuns( startIndex,
618 mModel->mLogicalModel->mBidirectionalParagraphInfo );
620 // Clear the character's directions.
621 CharacterDirection* characterDirectionsBuffer = mModel->mLogicalModel->mCharacterDirections.Begin();
623 mModel->mLogicalModel->mCharacterDirections.Erase( characterDirectionsBuffer + startIndex,
624 characterDirectionsBuffer + endIndexPlusOne );
627 if( NO_OPERATION != ( REORDER & operations ) )
629 uint32_t startRemoveIndex = mModel->mLogicalModel->mBidirectionalLineInfo.Count();
630 uint32_t endRemoveIndex = startRemoveIndex;
631 ClearCharacterRuns( startIndex,
633 mModel->mLogicalModel->mBidirectionalLineInfo,
637 BidirectionalLineInfoRun* bidirectionalLineInfoBuffer = mModel->mLogicalModel->mBidirectionalLineInfo.Begin();
639 // Free the allocated memory used to store the conversion table in the bidirectional line info run.
640 for( Vector<BidirectionalLineInfoRun>::Iterator it = bidirectionalLineInfoBuffer + startRemoveIndex,
641 endIt = bidirectionalLineInfoBuffer + endRemoveIndex;
645 BidirectionalLineInfoRun& bidiLineInfo = *it;
647 free( bidiLineInfo.visualToLogicalMap );
648 bidiLineInfo.visualToLogicalMap = NULL;
651 mModel->mLogicalModel->mBidirectionalLineInfo.Erase( bidirectionalLineInfoBuffer + startRemoveIndex,
652 bidirectionalLineInfoBuffer + endRemoveIndex );
657 void Controller::Impl::ClearGlyphModelData( CharacterIndex startIndex, CharacterIndex endIndex, OperationsMask operations )
659 const CharacterIndex endIndexPlusOne = endIndex + 1u;
660 const Length numberOfCharactersRemoved = endIndexPlusOne - startIndex;
662 // Convert the character index to glyph index before deleting the character to glyph and the glyphs per character buffers.
663 GlyphIndex* charactersToGlyphBuffer = mModel->mVisualModel->mCharactersToGlyph.Begin();
664 Length* glyphsPerCharacterBuffer = mModel->mVisualModel->mGlyphsPerCharacter.Begin();
666 const GlyphIndex endGlyphIndexPlusOne = *( charactersToGlyphBuffer + endIndex ) + *( glyphsPerCharacterBuffer + endIndex );
667 const Length numberOfGlyphsRemoved = endGlyphIndexPlusOne - mTextUpdateInfo.mStartGlyphIndex;
669 if( NO_OPERATION != ( SHAPE_TEXT & operations ) )
671 // Update the character to glyph indices.
672 for( Vector<GlyphIndex>::Iterator it = charactersToGlyphBuffer + endIndexPlusOne,
673 endIt = charactersToGlyphBuffer + mModel->mVisualModel->mCharactersToGlyph.Count();
677 CharacterIndex& index = *it;
678 index -= numberOfGlyphsRemoved;
681 // Clear the character to glyph conversion table.
682 mModel->mVisualModel->mCharactersToGlyph.Erase( charactersToGlyphBuffer + startIndex,
683 charactersToGlyphBuffer + endIndexPlusOne );
685 // Clear the glyphs per character table.
686 mModel->mVisualModel->mGlyphsPerCharacter.Erase( glyphsPerCharacterBuffer + startIndex,
687 glyphsPerCharacterBuffer + endIndexPlusOne );
689 // Clear the glyphs buffer.
690 GlyphInfo* glyphsBuffer = mModel->mVisualModel->mGlyphs.Begin();
691 mModel->mVisualModel->mGlyphs.Erase( glyphsBuffer + mTextUpdateInfo.mStartGlyphIndex,
692 glyphsBuffer + endGlyphIndexPlusOne );
694 CharacterIndex* glyphsToCharactersBuffer = mModel->mVisualModel->mGlyphsToCharacters.Begin();
696 // Update the glyph to character indices.
697 for( Vector<CharacterIndex>::Iterator it = glyphsToCharactersBuffer + endGlyphIndexPlusOne,
698 endIt = glyphsToCharactersBuffer + mModel->mVisualModel->mGlyphsToCharacters.Count();
702 CharacterIndex& index = *it;
703 index -= numberOfCharactersRemoved;
706 // Clear the glyphs to characters buffer.
707 mModel->mVisualModel->mGlyphsToCharacters.Erase( glyphsToCharactersBuffer + mTextUpdateInfo.mStartGlyphIndex,
708 glyphsToCharactersBuffer + endGlyphIndexPlusOne );
710 // Clear the characters per glyph buffer.
711 Length* charactersPerGlyphBuffer = mModel->mVisualModel->mCharactersPerGlyph.Begin();
712 mModel->mVisualModel->mCharactersPerGlyph.Erase( charactersPerGlyphBuffer + mTextUpdateInfo.mStartGlyphIndex,
713 charactersPerGlyphBuffer + endGlyphIndexPlusOne );
715 // Clear the positions buffer.
716 Vector2* positionsBuffer = mModel->mVisualModel->mGlyphPositions.Begin();
717 mModel->mVisualModel->mGlyphPositions.Erase( positionsBuffer + mTextUpdateInfo.mStartGlyphIndex,
718 positionsBuffer + endGlyphIndexPlusOne );
721 if( NO_OPERATION != ( LAYOUT & operations ) )
724 uint32_t startRemoveIndex = mModel->mVisualModel->mLines.Count();
725 uint32_t endRemoveIndex = startRemoveIndex;
726 ClearCharacterRuns( startIndex,
728 mModel->mVisualModel->mLines,
732 // Will update the glyph runs.
733 startRemoveIndex = mModel->mVisualModel->mLines.Count();
734 endRemoveIndex = startRemoveIndex;
735 ClearGlyphRuns( mTextUpdateInfo.mStartGlyphIndex,
736 endGlyphIndexPlusOne - 1u,
737 mModel->mVisualModel->mLines,
741 // Set the line index from where to insert the new laid-out lines.
742 mTextUpdateInfo.mStartLineIndex = startRemoveIndex;
744 LineRun* linesBuffer = mModel->mVisualModel->mLines.Begin();
745 mModel->mVisualModel->mLines.Erase( linesBuffer + startRemoveIndex,
746 linesBuffer + endRemoveIndex );
749 if( NO_OPERATION != ( COLOR & operations ) )
751 if( 0u != mModel->mVisualModel->mColorIndices.Count() )
753 ColorIndex* colorIndexBuffer = mModel->mVisualModel->mColorIndices.Begin();
754 mModel->mVisualModel->mColorIndices.Erase( colorIndexBuffer + mTextUpdateInfo.mStartGlyphIndex,
755 colorIndexBuffer + endGlyphIndexPlusOne );
760 void Controller::Impl::ClearModelData( CharacterIndex startIndex, CharacterIndex endIndex, OperationsMask operations )
762 if( mTextUpdateInfo.mClearAll ||
763 ( ( 0u == startIndex ) &&
764 ( mTextUpdateInfo.mPreviousNumberOfCharacters == endIndex + 1u ) ) )
766 ClearFullModelData( operations );
770 // Clear the model data related with characters.
771 ClearCharacterModelData( startIndex, endIndex, operations );
773 // Clear the model data related with glyphs.
774 ClearGlyphModelData( startIndex, endIndex, operations );
777 // The estimated number of lines. Used to avoid reallocations when layouting.
778 mTextUpdateInfo.mEstimatedNumberOfLines = std::max( mModel->mVisualModel->mLines.Count(), mModel->mLogicalModel->mParagraphInfo.Count() );
780 mModel->mVisualModel->ClearCaches();
783 bool Controller::Impl::UpdateModel( OperationsMask operationsRequired )
785 DALI_LOG_INFO( gLogFilter, Debug::General, "Controller::UpdateModel\n" );
787 // Calculate the operations to be done.
788 const OperationsMask operations = static_cast<OperationsMask>( mOperationsPending & operationsRequired );
790 if( NO_OPERATION == operations )
792 // Nothing to do if no operations are pending and required.
796 Vector<Character>& srcCharacters = mModel->mLogicalModel->mText;
797 Vector<Character> displayCharacters;
798 bool useHiddenText = false;
799 if ( mHiddenInput && mEventData != NULL && !mEventData->mIsShowingPlaceholderText)
801 mHiddenInput->Substitute( srcCharacters,displayCharacters );
802 useHiddenText = true;
805 Vector<Character>& utf32Characters = useHiddenText ? displayCharacters : srcCharacters;
806 const Length numberOfCharacters = utf32Characters.Count();
808 // Index to the first character of the first paragraph to be updated.
809 CharacterIndex startIndex = 0u;
810 // Number of characters of the paragraphs to be removed.
811 Length paragraphCharacters = 0u;
813 CalculateTextUpdateIndices( paragraphCharacters );
815 // Check whether the indices for updating the text is valid
816 if ( numberOfCharacters > 0u &&
817 ( mTextUpdateInfo.mParagraphCharacterIndex >= numberOfCharacters ||
818 mTextUpdateInfo.mRequestedNumberOfCharacters > numberOfCharacters ) )
820 std::string currentText;
821 Utf32ToUtf8( mModel->mLogicalModel->mText.Begin(), numberOfCharacters, currentText );
823 DALI_LOG_ERROR( "Controller::Impl::UpdateModel: mTextUpdateInfo has invalid indices\n" );
824 DALI_LOG_ERROR( "Number of characters: %d, current text is: %s\n", numberOfCharacters, currentText.c_str() );
826 // Dump mTextUpdateInfo
827 DALI_LOG_ERROR( "Dump mTextUpdateInfo:\n" );
828 DALI_LOG_ERROR( " mTextUpdateInfo.mCharacterIndex = %u\n", mTextUpdateInfo.mCharacterIndex );
829 DALI_LOG_ERROR( " mTextUpdateInfo.mNumberOfCharactersToRemove = %u\n", mTextUpdateInfo.mNumberOfCharactersToRemove );
830 DALI_LOG_ERROR( " mTextUpdateInfo.mNumberOfCharactersToAdd = %u\n", mTextUpdateInfo.mNumberOfCharactersToAdd );
831 DALI_LOG_ERROR( " mTextUpdateInfo.mPreviousNumberOfCharacters = %u\n", mTextUpdateInfo.mPreviousNumberOfCharacters );
832 DALI_LOG_ERROR( " mTextUpdateInfo.mParagraphCharacterIndex = %u\n", mTextUpdateInfo.mParagraphCharacterIndex );
833 DALI_LOG_ERROR( " mTextUpdateInfo.mRequestedNumberOfCharacters = %u\n", mTextUpdateInfo.mRequestedNumberOfCharacters );
834 DALI_LOG_ERROR( " mTextUpdateInfo.mStartGlyphIndex = %u\n", mTextUpdateInfo.mStartGlyphIndex );
835 DALI_LOG_ERROR( " mTextUpdateInfo.mStartLineIndex = %u\n", mTextUpdateInfo.mStartLineIndex );
836 DALI_LOG_ERROR( " mTextUpdateInfo.mEstimatedNumberOfLines = %u\n", mTextUpdateInfo.mEstimatedNumberOfLines );
837 DALI_LOG_ERROR( " mTextUpdateInfo.mClearAll = %d\n", mTextUpdateInfo.mClearAll );
838 DALI_LOG_ERROR( " mTextUpdateInfo.mFullRelayoutNeeded = %d\n", mTextUpdateInfo.mFullRelayoutNeeded );
839 DALI_LOG_ERROR( " mTextUpdateInfo.mIsLastCharacterNewParagraph = %d\n", mTextUpdateInfo.mIsLastCharacterNewParagraph );
844 startIndex = mTextUpdateInfo.mParagraphCharacterIndex;
846 if( mTextUpdateInfo.mClearAll ||
847 ( 0u != paragraphCharacters ) )
849 ClearModelData( startIndex, startIndex + ( ( paragraphCharacters > 0u ) ? paragraphCharacters - 1u : 0u ), operations );
852 mTextUpdateInfo.mClearAll = false;
854 // Whether the model is updated.
855 bool updated = false;
857 Vector<LineBreakInfo>& lineBreakInfo = mModel->mLogicalModel->mLineBreakInfo;
858 const Length requestedNumberOfCharacters = mTextUpdateInfo.mRequestedNumberOfCharacters;
860 if( NO_OPERATION != ( GET_LINE_BREAKS & operations ) )
862 // Retrieves the line break info. The line break info is used to split the text in 'paragraphs' to
863 // calculate the bidirectional info for each 'paragraph'.
864 // It's also used to layout the text (where it should be a new line) or to shape the text (text in different lines
865 // is not shaped together).
866 lineBreakInfo.Resize( numberOfCharacters, TextAbstraction::LINE_NO_BREAK );
868 SetLineBreakInfo( utf32Characters,
870 requestedNumberOfCharacters,
873 // Create the paragraph info.
874 mModel->mLogicalModel->CreateParagraphInfo( startIndex,
875 requestedNumberOfCharacters );
879 Vector<WordBreakInfo>& wordBreakInfo = mModel->mLogicalModel->mWordBreakInfo;
880 if( NO_OPERATION != ( GET_WORD_BREAKS & operations ) )
882 // Retrieves the word break info. The word break info is used to layout the text (where to wrap the text in lines).
883 wordBreakInfo.Resize( numberOfCharacters, TextAbstraction::WORD_NO_BREAK );
885 SetWordBreakInfo( utf32Characters,
887 requestedNumberOfCharacters,
892 const bool getScripts = NO_OPERATION != ( GET_SCRIPTS & operations );
893 const bool validateFonts = NO_OPERATION != ( VALIDATE_FONTS & operations );
895 Vector<ScriptRun>& scripts = mModel->mLogicalModel->mScriptRuns;
896 Vector<FontRun>& validFonts = mModel->mLogicalModel->mFontRuns;
898 if( getScripts || validateFonts )
900 // Validates the fonts assigned by the application or assigns default ones.
901 // It makes sure all the characters are going to be rendered by the correct font.
902 MultilanguageSupport multilanguageSupport = MultilanguageSupport::Get();
906 // Retrieves the scripts used in the text.
907 multilanguageSupport.SetScripts( utf32Characters,
909 requestedNumberOfCharacters,
915 // Validate the fonts set through the mark-up string.
916 Vector<FontDescriptionRun>& fontDescriptionRuns = mModel->mLogicalModel->mFontDescriptionRuns;
918 // Get the default font's description.
919 TextAbstraction::FontDescription defaultFontDescription;
920 TextAbstraction::PointSize26Dot6 defaultPointSize = TextAbstraction::FontClient::DEFAULT_POINT_SIZE;
922 if( IsShowingPlaceholderText() && mEventData && ( NULL != mEventData->mPlaceholderFont ) )
924 // If the placeholder font is set specifically, only placeholder font is changed.
925 defaultFontDescription = mEventData->mPlaceholderFont->mFontDescription;
926 if( mEventData->mPlaceholderFont->sizeDefined )
928 defaultPointSize = mEventData->mPlaceholderFont->mDefaultPointSize * 64u;
931 else if( NULL != mFontDefaults )
933 // Set the normal font and the placeholder font.
934 defaultFontDescription = mFontDefaults->mFontDescription;
935 defaultPointSize = mFontDefaults->mDefaultPointSize * 64u;
938 // Validates the fonts. If there is a character with no assigned font it sets a default one.
939 // After this call, fonts are validated.
940 multilanguageSupport.ValidateFonts( utf32Characters,
943 defaultFontDescription,
946 requestedNumberOfCharacters,
952 Vector<Character> mirroredUtf32Characters;
953 bool textMirrored = false;
954 const Length numberOfParagraphs = mModel->mLogicalModel->mParagraphInfo.Count();
955 if( NO_OPERATION != ( BIDI_INFO & operations ) )
957 Vector<BidirectionalParagraphInfoRun>& bidirectionalInfo = mModel->mLogicalModel->mBidirectionalParagraphInfo;
958 bidirectionalInfo.Reserve( numberOfParagraphs );
960 // Calculates the bidirectional info for the whole paragraph if it contains right to left scripts.
961 SetBidirectionalInfo( utf32Characters,
965 requestedNumberOfCharacters,
968 if( 0u != bidirectionalInfo.Count() )
970 // Only set the character directions if there is right to left characters.
971 Vector<CharacterDirection>& directions = mModel->mLogicalModel->mCharacterDirections;
972 GetCharactersDirection( bidirectionalInfo,
975 requestedNumberOfCharacters,
978 // This paragraph has right to left text. Some characters may need to be mirrored.
979 // TODO: consider if the mirrored string can be stored as well.
981 textMirrored = GetMirroredText( utf32Characters,
985 requestedNumberOfCharacters,
986 mirroredUtf32Characters );
990 // There is no right to left characters. Clear the directions vector.
991 mModel->mLogicalModel->mCharacterDirections.Clear();
996 Vector<GlyphInfo>& glyphs = mModel->mVisualModel->mGlyphs;
997 Vector<CharacterIndex>& glyphsToCharactersMap = mModel->mVisualModel->mGlyphsToCharacters;
998 Vector<Length>& charactersPerGlyph = mModel->mVisualModel->mCharactersPerGlyph;
999 Vector<GlyphIndex> newParagraphGlyphs;
1000 newParagraphGlyphs.Reserve( numberOfParagraphs );
1002 const Length currentNumberOfGlyphs = glyphs.Count();
1003 if( NO_OPERATION != ( SHAPE_TEXT & operations ) )
1005 const Vector<Character>& textToShape = textMirrored ? mirroredUtf32Characters : utf32Characters;
1007 ShapeText( textToShape,
1012 mTextUpdateInfo.mStartGlyphIndex,
1013 requestedNumberOfCharacters,
1015 glyphsToCharactersMap,
1017 newParagraphGlyphs );
1019 // Create the 'number of glyphs' per character and the glyph to character conversion tables.
1020 mModel->mVisualModel->CreateGlyphsPerCharacterTable( startIndex, mTextUpdateInfo.mStartGlyphIndex, requestedNumberOfCharacters );
1021 mModel->mVisualModel->CreateCharacterToGlyphTable( startIndex, mTextUpdateInfo.mStartGlyphIndex, requestedNumberOfCharacters );
1025 const Length numberOfGlyphs = glyphs.Count() - currentNumberOfGlyphs;
1027 if( NO_OPERATION != ( GET_GLYPH_METRICS & operations ) )
1029 GlyphInfo* glyphsBuffer = glyphs.Begin();
1030 mMetrics->GetGlyphMetrics( glyphsBuffer + mTextUpdateInfo.mStartGlyphIndex, numberOfGlyphs );
1032 // Update the width and advance of all new paragraph characters.
1033 for( Vector<GlyphIndex>::ConstIterator it = newParagraphGlyphs.Begin(), endIt = newParagraphGlyphs.End(); it != endIt; ++it )
1035 const GlyphIndex index = *it;
1036 GlyphInfo& glyph = *( glyphsBuffer + index );
1038 glyph.xBearing = 0.f;
1040 glyph.advance = 0.f;
1045 if( NO_OPERATION != ( COLOR & operations ) )
1047 // Set the color runs in glyphs.
1048 SetColorSegmentationInfo( mModel->mLogicalModel->mColorRuns,
1049 mModel->mVisualModel->mCharactersToGlyph,
1050 mModel->mVisualModel->mGlyphsPerCharacter,
1052 mTextUpdateInfo.mStartGlyphIndex,
1053 requestedNumberOfCharacters,
1054 mModel->mVisualModel->mColors,
1055 mModel->mVisualModel->mColorIndices );
1060 if( ( NULL != mEventData ) &&
1061 mEventData->mPreEditFlag &&
1062 ( 0u != mModel->mVisualModel->mCharactersToGlyph.Count() ) )
1064 // Add the underline for the pre-edit text.
1065 const GlyphIndex* const charactersToGlyphBuffer = mModel->mVisualModel->mCharactersToGlyph.Begin();
1066 const Length* const glyphsPerCharacterBuffer = mModel->mVisualModel->mGlyphsPerCharacter.Begin();
1068 const GlyphIndex glyphStart = *( charactersToGlyphBuffer + mEventData->mPreEditStartPosition );
1069 const CharacterIndex lastPreEditCharacter = mEventData->mPreEditStartPosition + ( ( mEventData->mPreEditLength > 0u ) ? mEventData->mPreEditLength - 1u : 0u );
1070 const Length numberOfGlyphsLastCharacter = *( glyphsPerCharacterBuffer + lastPreEditCharacter );
1071 const GlyphIndex glyphEnd = *( charactersToGlyphBuffer + lastPreEditCharacter ) + ( numberOfGlyphsLastCharacter > 1u ? numberOfGlyphsLastCharacter - 1u : 0u );
1073 GlyphRun underlineRun;
1074 underlineRun.glyphIndex = glyphStart;
1075 underlineRun.numberOfGlyphs = 1u + glyphEnd - glyphStart;
1077 // TODO: At the moment the underline runs are only for pre-edit.
1078 mModel->mVisualModel->mUnderlineRuns.PushBack( underlineRun );
1081 // The estimated number of lines. Used to avoid reallocations when layouting.
1082 mTextUpdateInfo.mEstimatedNumberOfLines = std::max( mModel->mVisualModel->mLines.Count(), mModel->mLogicalModel->mParagraphInfo.Count() );
1084 // Set the previous number of characters for the next time the text is updated.
1085 mTextUpdateInfo.mPreviousNumberOfCharacters = numberOfCharacters;
1090 void Controller::Impl::RetrieveDefaultInputStyle( InputStyle& inputStyle )
1092 // Sets the default text's color.
1093 inputStyle.textColor = mTextColor;
1094 inputStyle.isDefaultColor = true;
1096 inputStyle.familyName.clear();
1097 inputStyle.weight = TextAbstraction::FontWeight::NORMAL;
1098 inputStyle.width = TextAbstraction::FontWidth::NORMAL;
1099 inputStyle.slant = TextAbstraction::FontSlant::NORMAL;
1100 inputStyle.size = 0.f;
1102 inputStyle.lineSpacing = 0.f;
1104 inputStyle.underlineProperties.clear();
1105 inputStyle.shadowProperties.clear();
1106 inputStyle.embossProperties.clear();
1107 inputStyle.outlineProperties.clear();
1109 inputStyle.isFamilyDefined = false;
1110 inputStyle.isWeightDefined = false;
1111 inputStyle.isWidthDefined = false;
1112 inputStyle.isSlantDefined = false;
1113 inputStyle.isSizeDefined = false;
1115 inputStyle.isLineSpacingDefined = false;
1117 inputStyle.isUnderlineDefined = false;
1118 inputStyle.isShadowDefined = false;
1119 inputStyle.isEmbossDefined = false;
1120 inputStyle.isOutlineDefined = false;
1122 // Sets the default font's family name, weight, width, slant and size.
1125 if( mFontDefaults->familyDefined )
1127 inputStyle.familyName = mFontDefaults->mFontDescription.family;
1128 inputStyle.isFamilyDefined = true;
1131 if( mFontDefaults->weightDefined )
1133 inputStyle.weight = mFontDefaults->mFontDescription.weight;
1134 inputStyle.isWeightDefined = true;
1137 if( mFontDefaults->widthDefined )
1139 inputStyle.width = mFontDefaults->mFontDescription.width;
1140 inputStyle.isWidthDefined = true;
1143 if( mFontDefaults->slantDefined )
1145 inputStyle.slant = mFontDefaults->mFontDescription.slant;
1146 inputStyle.isSlantDefined = true;
1149 if( mFontDefaults->sizeDefined )
1151 inputStyle.size = mFontDefaults->mDefaultPointSize;
1152 inputStyle.isSizeDefined = true;
1157 float Controller::Impl::GetDefaultFontLineHeight()
1159 FontId defaultFontId = 0u;
1160 if( NULL == mFontDefaults )
1162 TextAbstraction::FontDescription fontDescription;
1163 defaultFontId = mFontClient.GetFontId( fontDescription );
1167 defaultFontId = mFontDefaults->GetFontId( mFontClient );
1170 Text::FontMetrics fontMetrics;
1171 mMetrics->GetFontMetrics( defaultFontId, fontMetrics );
1173 return( fontMetrics.ascender - fontMetrics.descender );
1176 void Controller::Impl::OnCursorKeyEvent( const Event& event )
1178 if( NULL == mEventData || !IsShowingRealText() )
1180 // Nothing to do if there is no text input.
1184 int keyCode = event.p1.mInt;
1185 bool isShiftModifier = event.p2.mBool;
1187 CharacterIndex previousPrimaryCursorPosition = mEventData->mPrimaryCursorPosition;
1189 if( Dali::DALI_KEY_CURSOR_LEFT == keyCode )
1191 if( mEventData->mPrimaryCursorPosition > 0u )
1193 if ( !isShiftModifier && mEventData->mDecorator->IsHighlightVisible() )
1195 mEventData->mPrimaryCursorPosition = std::min(mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition);
1199 mEventData->mPrimaryCursorPosition = CalculateNewCursorIndex( mEventData->mPrimaryCursorPosition - 1u );
1203 else if( Dali::DALI_KEY_CURSOR_RIGHT == keyCode )
1205 if( mModel->mLogicalModel->mText.Count() > mEventData->mPrimaryCursorPosition )
1207 if ( !isShiftModifier && mEventData->mDecorator->IsHighlightVisible() )
1209 mEventData->mPrimaryCursorPosition = std::max(mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition);
1213 mEventData->mPrimaryCursorPosition = CalculateNewCursorIndex( mEventData->mPrimaryCursorPosition );
1217 else if( Dali::DALI_KEY_CURSOR_UP == keyCode && !isShiftModifier )
1219 // Ignore Shift-Up for text selection for now.
1221 // Get first the line index of the current cursor position index.
1222 CharacterIndex characterIndex = 0u;
1224 if( mEventData->mPrimaryCursorPosition > 0u )
1226 characterIndex = mEventData->mPrimaryCursorPosition - 1u;
1229 const LineIndex lineIndex = mModel->mVisualModel->GetLineOfCharacter( characterIndex );
1230 const LineIndex previousLineIndex = ( lineIndex > 0 ? lineIndex - 1u : lineIndex );
1232 // Retrieve the cursor position info.
1233 CursorInfo cursorInfo;
1234 GetCursorPosition( mEventData->mPrimaryCursorPosition,
1237 // Get the line above.
1238 const LineRun& line = *( mModel->mVisualModel->mLines.Begin() + previousLineIndex );
1240 // Get the next hit 'y' point.
1241 const float hitPointY = cursorInfo.lineOffset - 0.5f * ( line.ascender - line.descender );
1243 // Use the cursor hook position 'x' and the next hit 'y' position to calculate the new cursor index.
1244 bool matchedCharacter = false;
1245 mEventData->mPrimaryCursorPosition = Text::GetClosestCursorIndex( mModel->mVisualModel,
1246 mModel->mLogicalModel,
1248 mEventData->mCursorHookPositionX,
1250 CharacterHitTest::TAP,
1253 else if( Dali::DALI_KEY_CURSOR_DOWN == keyCode && !isShiftModifier )
1255 // Ignore Shift-Down for text selection for now.
1257 // Get first the line index of the current cursor position index.
1258 CharacterIndex characterIndex = 0u;
1260 if( mEventData->mPrimaryCursorPosition > 0u )
1262 characterIndex = mEventData->mPrimaryCursorPosition - 1u;
1265 const LineIndex lineIndex = mModel->mVisualModel->GetLineOfCharacter( characterIndex );
1267 if( lineIndex + 1u < mModel->mVisualModel->mLines.Count() )
1269 // Retrieve the cursor position info.
1270 CursorInfo cursorInfo;
1271 GetCursorPosition( mEventData->mPrimaryCursorPosition,
1274 // Get the line below.
1275 const LineRun& line = *( mModel->mVisualModel->mLines.Begin() + lineIndex + 1u );
1277 // Get the next hit 'y' point.
1278 const float hitPointY = cursorInfo.lineOffset + cursorInfo.lineHeight + 0.5f * ( line.ascender - line.descender );
1280 // Use the cursor hook position 'x' and the next hit 'y' position to calculate the new cursor index.
1281 bool matchedCharacter = false;
1282 mEventData->mPrimaryCursorPosition = Text::GetClosestCursorIndex( mModel->mVisualModel,
1283 mModel->mLogicalModel,
1285 mEventData->mCursorHookPositionX,
1287 CharacterHitTest::TAP,
1292 if ( !isShiftModifier && mEventData->mState != EventData::SELECTING )
1294 // Update selection position after moving the cursor
1295 mEventData->mLeftSelectionPosition = mEventData->mPrimaryCursorPosition;
1296 mEventData->mRightSelectionPosition = mEventData->mPrimaryCursorPosition;
1299 if ( isShiftModifier && IsShowingRealText() && mEventData->mShiftSelectionFlag )
1301 // Handle text selection
1302 bool selecting = false;
1304 if ( Dali::DALI_KEY_CURSOR_LEFT == keyCode || Dali::DALI_KEY_CURSOR_RIGHT == keyCode )
1306 // Shift-Left/Right to select the text
1307 int cursorPositionDelta = mEventData->mPrimaryCursorPosition - previousPrimaryCursorPosition;
1308 if ( cursorPositionDelta > 0 || mEventData->mRightSelectionPosition > 0u ) // Check the boundary
1310 mEventData->mRightSelectionPosition += cursorPositionDelta;
1314 else if ( mEventData->mLeftSelectionPosition != mEventData->mRightSelectionPosition )
1316 // Show no grab handles and text highlight if Shift-Up/Down pressed but no selected text
1322 // Notify the cursor position to the imf manager.
1323 if( mEventData->mImfManager )
1325 mEventData->mImfManager.SetCursorPosition( mEventData->mPrimaryCursorPosition );
1326 mEventData->mImfManager.NotifyCursorPosition();
1329 ChangeState( EventData::SELECTING );
1331 mEventData->mUpdateLeftSelectionPosition = true;
1332 mEventData->mUpdateRightSelectionPosition = true;
1333 mEventData->mUpdateGrabHandlePosition = true;
1334 mEventData->mUpdateHighlightBox = true;
1336 // Hide the text selection popup if select the text using keyboard instead of moving grab handles
1337 if( mEventData->mGrabHandlePopupEnabled )
1339 mEventData->mDecorator->SetPopupActive( false );
1345 // Handle normal cursor move
1346 ChangeState( EventData::EDITING );
1347 mEventData->mUpdateCursorPosition = true;
1350 mEventData->mUpdateInputStyle = true;
1351 mEventData->mScrollAfterUpdatePosition = true;
1354 void Controller::Impl::OnTapEvent( const Event& event )
1356 if( NULL != mEventData )
1358 const unsigned int tapCount = event.p1.mUint;
1360 if( 1u == tapCount )
1362 if( IsShowingRealText() )
1364 // Convert from control's coords to text's coords.
1365 const float xPosition = event.p2.mFloat - mModel->mScrollPosition.x;
1366 const float yPosition = event.p3.mFloat - mModel->mScrollPosition.y;
1368 // Keep the tap 'x' position. Used to move the cursor.
1369 mEventData->mCursorHookPositionX = xPosition;
1371 // Whether to touch point hits on a glyph.
1372 bool matchedCharacter = false;
1373 mEventData->mPrimaryCursorPosition = Text::GetClosestCursorIndex( mModel->mVisualModel,
1374 mModel->mLogicalModel,
1378 CharacterHitTest::TAP,
1381 // When the cursor position is changing, delay cursor blinking
1382 mEventData->mDecorator->DelayCursorBlink();
1386 mEventData->mPrimaryCursorPosition = 0u;
1389 // Update selection position after tapping
1390 mEventData->mLeftSelectionPosition = mEventData->mPrimaryCursorPosition;
1391 mEventData->mRightSelectionPosition = mEventData->mPrimaryCursorPosition;
1393 mEventData->mUpdateCursorPosition = true;
1394 mEventData->mUpdateGrabHandlePosition = true;
1395 mEventData->mScrollAfterUpdatePosition = true;
1396 mEventData->mUpdateInputStyle = true;
1398 // Notify the cursor position to the imf manager.
1399 if( mEventData->mImfManager )
1401 mEventData->mImfManager.SetCursorPosition( mEventData->mPrimaryCursorPosition );
1402 mEventData->mImfManager.NotifyCursorPosition();
1405 else if( 2u == tapCount )
1407 if( mEventData->mSelectionEnabled )
1409 // Convert from control's coords to text's coords.
1410 const float xPosition = event.p2.mFloat - mModel->mScrollPosition.x;
1411 const float yPosition = event.p3.mFloat - mModel->mScrollPosition.y;
1413 // Calculates the logical position from the x,y coords.
1414 RepositionSelectionHandles( xPosition,
1416 mEventData->mDoubleTapAction );
1422 void Controller::Impl::OnPanEvent( const Event& event )
1424 if( NULL == mEventData )
1426 // Nothing to do if there is no text input.
1430 const bool isHorizontalScrollEnabled = mEventData->mDecorator->IsHorizontalScrollEnabled();
1431 const bool isVerticalScrollEnabled = mEventData->mDecorator->IsVerticalScrollEnabled();
1433 if( !isHorizontalScrollEnabled && !isVerticalScrollEnabled )
1435 // Nothing to do if scrolling is not enabled.
1439 const int state = event.p1.mInt;
1443 case Gesture::Started:
1445 // Will remove the cursor, handles or text's popup, ...
1446 ChangeState( EventData::TEXT_PANNING );
1449 case Gesture::Continuing:
1451 const Vector2& layoutSize = mModel->mVisualModel->GetLayoutSize();
1452 const Vector2 currentScroll = mModel->mScrollPosition;
1454 if( isHorizontalScrollEnabled )
1456 const float displacementX = event.p2.mFloat;
1457 mModel->mScrollPosition.x += displacementX;
1459 ClampHorizontalScroll( layoutSize );
1462 if( isVerticalScrollEnabled )
1464 const float displacementY = event.p3.mFloat;
1465 mModel->mScrollPosition.y += displacementY;
1467 ClampVerticalScroll( layoutSize );
1470 mEventData->mDecorator->UpdatePositions( mModel->mScrollPosition - currentScroll );
1473 case Gesture::Finished:
1474 case Gesture::Cancelled: // FALLTHROUGH
1476 // Will go back to the previous state to show the cursor, handles, the text's popup, ...
1477 ChangeState( mEventData->mPreviousState );
1485 void Controller::Impl::OnLongPressEvent( const Event& event )
1487 DALI_LOG_INFO( gLogFilter, Debug::General, "Controller::OnLongPressEvent\n" );
1489 if( !IsShowingRealText() && ( EventData::EDITING == mEventData->mState ) )
1491 ChangeState( EventData::EDITING_WITH_POPUP );
1492 mEventData->mDecoratorUpdated = true;
1493 mEventData->mUpdateInputStyle = true;
1497 if( mEventData->mSelectionEnabled )
1499 // Convert from control's coords to text's coords.
1500 const float xPosition = event.p2.mFloat - mModel->mScrollPosition.x;
1501 const float yPosition = event.p3.mFloat - mModel->mScrollPosition.y;
1503 // Calculates the logical position from the x,y coords.
1504 RepositionSelectionHandles( xPosition,
1506 mEventData->mLongPressAction );
1511 void Controller::Impl::OnHandleEvent( const Event& event )
1513 if( NULL == mEventData )
1515 // Nothing to do if there is no text input.
1519 const unsigned int state = event.p1.mUint;
1520 const bool handleStopScrolling = ( HANDLE_STOP_SCROLLING == state );
1521 const bool isSmoothHandlePanEnabled = mEventData->mDecorator->IsSmoothHandlePanEnabled();
1523 if( HANDLE_PRESSED == state )
1525 // Convert from decorator's coords to text's coords.
1526 const float xPosition = event.p2.mFloat - mModel->mScrollPosition.x;
1527 const float yPosition = event.p3.mFloat - mModel->mScrollPosition.y;
1529 // Need to calculate the handle's new position.
1530 bool matchedCharacter = false;
1531 const CharacterIndex handleNewPosition = Text::GetClosestCursorIndex( mModel->mVisualModel,
1532 mModel->mLogicalModel,
1536 CharacterHitTest::SCROLL,
1539 if( Event::GRAB_HANDLE_EVENT == event.type )
1541 ChangeState ( EventData::GRAB_HANDLE_PANNING );
1543 if( handleNewPosition != mEventData->mPrimaryCursorPosition )
1545 // Updates the cursor position if the handle's new position is different than the current one.
1546 mEventData->mUpdateCursorPosition = true;
1547 // Does not update the grab handle position if the smooth panning is enabled. (The decorator does it smooth).
1548 mEventData->mUpdateGrabHandlePosition = !isSmoothHandlePanEnabled;
1549 mEventData->mPrimaryCursorPosition = handleNewPosition;
1552 // 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.
1553 mEventData->mDecoratorUpdated = isSmoothHandlePanEnabled;
1555 else if( Event::LEFT_SELECTION_HANDLE_EVENT == event.type )
1557 ChangeState ( EventData::SELECTION_HANDLE_PANNING );
1559 if( ( handleNewPosition != mEventData->mLeftSelectionPosition ) &&
1560 ( handleNewPosition != mEventData->mRightSelectionPosition ) )
1562 // Updates the highlight box if the handle's new position is different than the current one.
1563 mEventData->mUpdateHighlightBox = true;
1564 // Does not update the selection handle position if the smooth panning is enabled. (The decorator does it smooth).
1565 mEventData->mUpdateLeftSelectionPosition = !isSmoothHandlePanEnabled;
1566 mEventData->mLeftSelectionPosition = handleNewPosition;
1569 // 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.
1570 mEventData->mDecoratorUpdated = isSmoothHandlePanEnabled;
1572 // Will define the order to scroll the text to match the handle position.
1573 mEventData->mIsLeftHandleSelected = true;
1574 mEventData->mIsRightHandleSelected = false;
1576 else if( Event::RIGHT_SELECTION_HANDLE_EVENT == event.type )
1578 ChangeState ( EventData::SELECTION_HANDLE_PANNING );
1580 if( ( handleNewPosition != mEventData->mRightSelectionPosition ) &&
1581 ( handleNewPosition != mEventData->mLeftSelectionPosition ) )
1583 // Updates the highlight box if the handle's new position is different than the current one.
1584 mEventData->mUpdateHighlightBox = true;
1585 // Does not update the selection handle position if the smooth panning is enabled. (The decorator does it smooth).
1586 mEventData->mUpdateRightSelectionPosition = !isSmoothHandlePanEnabled;
1587 mEventData->mRightSelectionPosition = handleNewPosition;
1590 // 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.
1591 mEventData->mDecoratorUpdated = isSmoothHandlePanEnabled;
1593 // Will define the order to scroll the text to match the handle position.
1594 mEventData->mIsLeftHandleSelected = false;
1595 mEventData->mIsRightHandleSelected = true;
1597 } // end ( HANDLE_PRESSED == state )
1598 else if( ( HANDLE_RELEASED == state ) ||
1599 handleStopScrolling )
1601 CharacterIndex handlePosition = 0u;
1602 if( handleStopScrolling || isSmoothHandlePanEnabled )
1604 // Convert from decorator's coords to text's coords.
1605 const float xPosition = event.p2.mFloat - mModel->mScrollPosition.x;
1606 const float yPosition = event.p3.mFloat - mModel->mScrollPosition.y;
1608 bool matchedCharacter = false;
1609 handlePosition = Text::GetClosestCursorIndex( mModel->mVisualModel,
1610 mModel->mLogicalModel,
1614 CharacterHitTest::SCROLL,
1618 if( Event::GRAB_HANDLE_EVENT == event.type )
1620 mEventData->mUpdateCursorPosition = true;
1621 mEventData->mUpdateGrabHandlePosition = true;
1622 mEventData->mUpdateInputStyle = true;
1624 if( !IsClipboardEmpty() )
1626 ChangeState( EventData::EDITING_WITH_PASTE_POPUP ); // Moving grabhandle will show Paste Popup
1629 if( handleStopScrolling || isSmoothHandlePanEnabled )
1631 mEventData->mScrollAfterUpdatePosition = true;
1632 mEventData->mPrimaryCursorPosition = handlePosition;
1635 else if( Event::LEFT_SELECTION_HANDLE_EVENT == event.type )
1637 ChangeState( EventData::SELECTING );
1639 mEventData->mUpdateHighlightBox = true;
1640 mEventData->mUpdateLeftSelectionPosition = true;
1641 mEventData->mUpdateRightSelectionPosition = true;
1643 if( handleStopScrolling || isSmoothHandlePanEnabled )
1645 mEventData->mScrollAfterUpdatePosition = true;
1647 if( ( handlePosition != mEventData->mRightSelectionPosition ) &&
1648 ( handlePosition != mEventData->mLeftSelectionPosition ) )
1650 mEventData->mLeftSelectionPosition = handlePosition;
1654 else if( Event::RIGHT_SELECTION_HANDLE_EVENT == event.type )
1656 ChangeState( EventData::SELECTING );
1658 mEventData->mUpdateHighlightBox = true;
1659 mEventData->mUpdateRightSelectionPosition = true;
1660 mEventData->mUpdateLeftSelectionPosition = true;
1662 if( handleStopScrolling || isSmoothHandlePanEnabled )
1664 mEventData->mScrollAfterUpdatePosition = true;
1665 if( ( handlePosition != mEventData->mRightSelectionPosition ) &&
1666 ( handlePosition != mEventData->mLeftSelectionPosition ) )
1668 mEventData->mRightSelectionPosition = handlePosition;
1673 mEventData->mDecoratorUpdated = true;
1674 } // end ( ( HANDLE_RELEASED == state ) || ( HANDLE_STOP_SCROLLING == state ) )
1675 else if( HANDLE_SCROLLING == state )
1677 const float xSpeed = event.p2.mFloat;
1678 const float ySpeed = event.p3.mFloat;
1679 const Vector2& layoutSize = mModel->mVisualModel->GetLayoutSize();
1680 const Vector2 currentScrollPosition = mModel->mScrollPosition;
1682 mModel->mScrollPosition.x += xSpeed;
1683 mModel->mScrollPosition.y += ySpeed;
1685 ClampHorizontalScroll( layoutSize );
1686 ClampVerticalScroll( layoutSize );
1688 bool endOfScroll = false;
1689 if( Vector2::ZERO == ( currentScrollPosition - mModel->mScrollPosition ) )
1691 // Notify the decorator there is no more text to scroll.
1692 // The decorator won't send more scroll events.
1693 mEventData->mDecorator->NotifyEndOfScroll();
1694 // Still need to set the position of the handle.
1698 // Set the position of the handle.
1699 const bool scrollRightDirection = xSpeed > 0.f;
1700 const bool scrollBottomDirection = ySpeed > 0.f;
1701 const bool leftSelectionHandleEvent = Event::LEFT_SELECTION_HANDLE_EVENT == event.type;
1702 const bool rightSelectionHandleEvent = Event::RIGHT_SELECTION_HANDLE_EVENT == event.type;
1704 if( Event::GRAB_HANDLE_EVENT == event.type )
1706 ChangeState( EventData::GRAB_HANDLE_PANNING );
1708 // Get the grab handle position in decorator coords.
1709 Vector2 position = mEventData->mDecorator->GetPosition( GRAB_HANDLE );
1711 if( mEventData->mDecorator->IsHorizontalScrollEnabled() )
1713 // Position the grag handle close to either the left or right edge.
1714 position.x = scrollRightDirection ? 0.f : mModel->mVisualModel->mControlSize.width;
1717 if( mEventData->mDecorator->IsVerticalScrollEnabled() )
1719 position.x = mEventData->mCursorHookPositionX;
1721 // Position the grag handle close to either the top or bottom edge.
1722 position.y = scrollBottomDirection ? 0.f : mModel->mVisualModel->mControlSize.height;
1725 // Get the new handle position.
1726 // The grab handle's position is in decorator's coords. Need to transforms to text's coords.
1727 bool matchedCharacter = false;
1728 const CharacterIndex handlePosition = Text::GetClosestCursorIndex( mModel->mVisualModel,
1729 mModel->mLogicalModel,
1731 position.x - mModel->mScrollPosition.x,
1732 position.y - mModel->mScrollPosition.y,
1733 CharacterHitTest::SCROLL,
1736 if( mEventData->mPrimaryCursorPosition != handlePosition )
1738 mEventData->mUpdateCursorPosition = true;
1739 mEventData->mUpdateGrabHandlePosition = !isSmoothHandlePanEnabled;
1740 mEventData->mScrollAfterUpdatePosition = true;
1741 mEventData->mPrimaryCursorPosition = handlePosition;
1743 mEventData->mUpdateInputStyle = mEventData->mUpdateCursorPosition;
1745 // Updates the decorator if the soft handle panning is enabled.
1746 mEventData->mDecoratorUpdated = isSmoothHandlePanEnabled;
1748 else if( leftSelectionHandleEvent || rightSelectionHandleEvent )
1750 ChangeState( EventData::SELECTION_HANDLE_PANNING );
1752 // Get the selection handle position in decorator coords.
1753 Vector2 position = mEventData->mDecorator->GetPosition( leftSelectionHandleEvent ? Text::LEFT_SELECTION_HANDLE : Text::RIGHT_SELECTION_HANDLE );
1755 if( mEventData->mDecorator->IsHorizontalScrollEnabled() )
1757 // Position the selection handle close to either the left or right edge.
1758 position.x = scrollRightDirection ? 0.f : mModel->mVisualModel->mControlSize.width;
1761 if( mEventData->mDecorator->IsVerticalScrollEnabled() )
1763 position.x = mEventData->mCursorHookPositionX;
1765 // Position the grag handle close to either the top or bottom edge.
1766 position.y = scrollBottomDirection ? 0.f : mModel->mVisualModel->mControlSize.height;
1769 // Get the new handle position.
1770 // The selection handle's position is in decorator's coords. Need to transform to text's coords.
1771 bool matchedCharacter = false;
1772 const CharacterIndex handlePosition = Text::GetClosestCursorIndex( mModel->mVisualModel,
1773 mModel->mLogicalModel,
1775 position.x - mModel->mScrollPosition.x,
1776 position.y - mModel->mScrollPosition.y,
1777 CharacterHitTest::SCROLL,
1780 if( leftSelectionHandleEvent )
1782 const bool differentHandles = ( mEventData->mLeftSelectionPosition != handlePosition ) && ( mEventData->mRightSelectionPosition != handlePosition );
1784 if( differentHandles || endOfScroll )
1786 mEventData->mUpdateHighlightBox = true;
1787 mEventData->mUpdateLeftSelectionPosition = !isSmoothHandlePanEnabled;
1788 mEventData->mUpdateRightSelectionPosition = isSmoothHandlePanEnabled;
1789 mEventData->mLeftSelectionPosition = handlePosition;
1794 const bool differentHandles = ( mEventData->mRightSelectionPosition != handlePosition ) && ( mEventData->mLeftSelectionPosition != handlePosition );
1795 if( differentHandles || endOfScroll )
1797 mEventData->mUpdateHighlightBox = true;
1798 mEventData->mUpdateRightSelectionPosition = !isSmoothHandlePanEnabled;
1799 mEventData->mUpdateLeftSelectionPosition = isSmoothHandlePanEnabled;
1800 mEventData->mRightSelectionPosition = handlePosition;
1804 if( mEventData->mUpdateLeftSelectionPosition || mEventData->mUpdateRightSelectionPosition )
1806 RepositionSelectionHandles();
1808 mEventData->mScrollAfterUpdatePosition = !isSmoothHandlePanEnabled;
1811 mEventData->mDecoratorUpdated = true;
1812 } // end ( HANDLE_SCROLLING == state )
1815 void Controller::Impl::OnSelectEvent( const Event& event )
1817 if( NULL == mEventData )
1819 // Nothing to do if there is no text.
1823 if( mEventData->mSelectionEnabled )
1825 // Convert from control's coords to text's coords.
1826 const float xPosition = event.p2.mFloat - mModel->mScrollPosition.x;
1827 const float yPosition = event.p3.mFloat - mModel->mScrollPosition.y;
1829 // Calculates the logical position from the x,y coords.
1830 RepositionSelectionHandles( xPosition,
1832 Controller::NoTextTap::HIGHLIGHT );
1836 void Controller::Impl::OnSelectAllEvent()
1838 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "OnSelectAllEvent mEventData->mSelectionEnabled%s \n", mEventData->mSelectionEnabled?"true":"false");
1840 if( NULL == mEventData )
1842 // Nothing to do if there is no text.
1846 if( mEventData->mSelectionEnabled )
1848 ChangeState( EventData::SELECTING );
1850 mEventData->mLeftSelectionPosition = 0u;
1851 mEventData->mRightSelectionPosition = mModel->mLogicalModel->mText.Count();
1853 mEventData->mScrollAfterUpdatePosition = true;
1854 mEventData->mUpdateLeftSelectionPosition = true;
1855 mEventData->mUpdateRightSelectionPosition = true;
1856 mEventData->mUpdateHighlightBox = true;
1860 void Controller::Impl::RetrieveSelection( std::string& selectedText, bool deleteAfterRetrieval )
1862 if( mEventData->mLeftSelectionPosition == mEventData->mRightSelectionPosition )
1864 // Nothing to select if handles are in the same place.
1865 selectedText.clear();
1869 const bool handlesCrossed = mEventData->mLeftSelectionPosition > mEventData->mRightSelectionPosition;
1871 //Get start and end position of selection
1872 const CharacterIndex startOfSelectedText = handlesCrossed ? mEventData->mRightSelectionPosition : mEventData->mLeftSelectionPosition;
1873 const Length lengthOfSelectedText = ( handlesCrossed ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition ) - startOfSelectedText;
1875 Vector<Character>& utf32Characters = mModel->mLogicalModel->mText;
1876 const Length numberOfCharacters = utf32Characters.Count();
1878 // Validate the start and end selection points
1879 if( ( startOfSelectedText + lengthOfSelectedText ) <= numberOfCharacters )
1881 //Get text as a UTF8 string
1882 Utf32ToUtf8( &utf32Characters[startOfSelectedText], lengthOfSelectedText, selectedText );
1884 if( deleteAfterRetrieval ) // Only delete text if copied successfully
1886 // Keep a copy of the current input style.
1887 InputStyle currentInputStyle;
1888 currentInputStyle.Copy( mEventData->mInputStyle );
1890 // Set as input style the style of the first deleted character.
1891 mModel->mLogicalModel->RetrieveStyle( startOfSelectedText, mEventData->mInputStyle );
1893 // Compare if the input style has changed.
1894 const bool hasInputStyleChanged = !currentInputStyle.Equal( mEventData->mInputStyle );
1896 if( hasInputStyleChanged )
1898 const InputStyle::Mask styleChangedMask = currentInputStyle.GetInputStyleChangeMask( mEventData->mInputStyle );
1899 // Queue the input style changed signal.
1900 mEventData->mInputStyleChangedQueue.PushBack( styleChangedMask );
1903 mModel->mLogicalModel->UpdateTextStyleRuns( startOfSelectedText, -static_cast<int>( lengthOfSelectedText ) );
1905 // Mark the paragraphs to be updated.
1906 if( Layout::Engine::SINGLE_LINE_BOX == mLayoutEngine.GetLayout() )
1908 mTextUpdateInfo.mCharacterIndex = 0;
1909 mTextUpdateInfo.mNumberOfCharactersToRemove = mTextUpdateInfo.mPreviousNumberOfCharacters;
1910 mTextUpdateInfo.mNumberOfCharactersToAdd = mTextUpdateInfo.mPreviousNumberOfCharacters - lengthOfSelectedText;
1911 mTextUpdateInfo.mClearAll = true;
1915 mTextUpdateInfo.mCharacterIndex = startOfSelectedText;
1916 mTextUpdateInfo.mNumberOfCharactersToRemove = lengthOfSelectedText;
1919 // Delete text between handles
1920 Vector<Character>::Iterator first = utf32Characters.Begin() + startOfSelectedText;
1921 Vector<Character>::Iterator last = first + lengthOfSelectedText;
1922 utf32Characters.Erase( first, last );
1924 // Will show the cursor at the first character of the selection.
1925 mEventData->mPrimaryCursorPosition = handlesCrossed ? mEventData->mRightSelectionPosition : mEventData->mLeftSelectionPosition;
1929 // Will show the cursor at the last character of the selection.
1930 mEventData->mPrimaryCursorPosition = handlesCrossed ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition;
1933 mEventData->mDecoratorUpdated = true;
1937 void Controller::Impl::ShowClipboard()
1941 mClipboard.ShowClipboard();
1945 void Controller::Impl::HideClipboard()
1947 if( mClipboard && mClipboardHideEnabled )
1949 mClipboard.HideClipboard();
1953 void Controller::Impl::SetClipboardHideEnable(bool enable)
1955 mClipboardHideEnabled = enable;
1958 bool Controller::Impl::CopyStringToClipboard( std::string& source )
1960 //Send string to clipboard
1961 return ( mClipboard && mClipboard.SetItem( source ) );
1964 void Controller::Impl::SendSelectionToClipboard( bool deleteAfterSending )
1966 std::string selectedText;
1967 RetrieveSelection( selectedText, deleteAfterSending );
1968 CopyStringToClipboard( selectedText );
1969 ChangeState( EventData::EDITING );
1972 void Controller::Impl::RequestGetTextFromClipboard()
1976 mClipboard.RequestItem();
1980 void Controller::Impl::RepositionSelectionHandles()
1982 CharacterIndex selectionStart = mEventData->mLeftSelectionPosition;
1983 CharacterIndex selectionEnd = mEventData->mRightSelectionPosition;
1985 if( selectionStart == selectionEnd )
1987 // Nothing to select if handles are in the same place.
1988 // So, deactive Highlight box.
1989 mEventData->mDecorator->SetHighlightActive( false );
1993 mEventData->mDecorator->ClearHighlights();
1995 const GlyphIndex* const charactersToGlyphBuffer = mModel->mVisualModel->mCharactersToGlyph.Begin();
1996 const Length* const glyphsPerCharacterBuffer = mModel->mVisualModel->mGlyphsPerCharacter.Begin();
1997 const GlyphInfo* const glyphsBuffer = mModel->mVisualModel->mGlyphs.Begin();
1998 const Vector2* const positionsBuffer = mModel->mVisualModel->mGlyphPositions.Begin();
1999 const Length* const charactersPerGlyphBuffer = mModel->mVisualModel->mCharactersPerGlyph.Begin();
2000 const CharacterIndex* const glyphToCharacterBuffer = mModel->mVisualModel->mGlyphsToCharacters.Begin();
2001 const CharacterDirection* const modelCharacterDirectionsBuffer = ( 0u != mModel->mLogicalModel->mCharacterDirections.Count() ) ? mModel->mLogicalModel->mCharacterDirections.Begin() : NULL;
2003 const bool isLastCharacter = selectionEnd >= mModel->mLogicalModel->mText.Count();
2004 const CharacterDirection startDirection = ( ( NULL == modelCharacterDirectionsBuffer ) ? false : *( modelCharacterDirectionsBuffer + selectionStart ) );
2005 const CharacterDirection endDirection = ( ( NULL == modelCharacterDirectionsBuffer ) ? false : *( modelCharacterDirectionsBuffer + ( selectionEnd - ( isLastCharacter ? 1u : 0u ) ) ) );
2007 // Swap the indices if the start is greater than the end.
2008 const bool indicesSwapped = selectionStart > selectionEnd;
2010 // Tell the decorator to flip the selection handles if needed.
2011 mEventData->mDecorator->SetSelectionHandleFlipState( indicesSwapped, startDirection, endDirection );
2013 if( indicesSwapped )
2015 std::swap( selectionStart, selectionEnd );
2018 // Get the indices to the first and last selected glyphs.
2019 const CharacterIndex selectionEndMinusOne = selectionEnd - 1u;
2020 const GlyphIndex glyphStart = *( charactersToGlyphBuffer + selectionStart );
2021 const Length numberOfGlyphs = *( glyphsPerCharacterBuffer + selectionEndMinusOne );
2022 const GlyphIndex glyphEnd = *( charactersToGlyphBuffer + selectionEndMinusOne ) + ( ( numberOfGlyphs > 0 ) ? numberOfGlyphs - 1u : 0u );
2024 // Get the lines where the glyphs are laid-out.
2025 const LineRun* lineRun = mModel->mVisualModel->mLines.Begin();
2027 LineIndex lineIndex = 0u;
2028 Length numberOfLines = 0u;
2029 mModel->mVisualModel->GetNumberOfLines( glyphStart,
2030 1u + glyphEnd - glyphStart,
2033 const LineIndex firstLineIndex = lineIndex;
2035 // Create the structure to store some selection box info.
2036 Vector<SelectionBoxInfo> selectionBoxLinesInfo;
2037 selectionBoxLinesInfo.Resize( numberOfLines );
2039 SelectionBoxInfo* selectionBoxInfo = selectionBoxLinesInfo.Begin();
2040 selectionBoxInfo->minX = MAX_FLOAT;
2041 selectionBoxInfo->maxX = MIN_FLOAT;
2043 // Keep the min and max 'x' position to calculate the size and position of the highlighed text.
2044 float minHighlightX = std::numeric_limits<float>::max();
2045 float maxHighlightX = std::numeric_limits<float>::min();
2047 Vector2 highLightPosition; // The highlight position in decorator's coords.
2049 // Retrieve the first line and get the line's vertical offset, the line's height and the index to the last glyph.
2051 // The line's vertical offset of all the lines before the line where the first glyph is laid-out.
2052 selectionBoxInfo->lineOffset = CalculateLineOffset( mModel->mVisualModel->mLines,
2055 // Transform to decorator's (control) coords.
2056 selectionBoxInfo->lineOffset += mModel->mScrollPosition.y;
2058 lineRun += firstLineIndex;
2060 // The line height is the addition of the line ascender and the line descender.
2061 // However, the line descender has a negative value, hence the subtraction.
2062 selectionBoxInfo->lineHeight = lineRun->ascender - lineRun->descender;
2064 GlyphIndex lastGlyphOfLine = lineRun->glyphRun.glyphIndex + lineRun->glyphRun.numberOfGlyphs - 1u;
2066 // Check if the first glyph is a ligature that must be broken like Latin ff, fi, or Arabic ï»», etc which needs special code.
2067 const Length numberOfCharactersStart = *( charactersPerGlyphBuffer + glyphStart );
2068 bool splitStartGlyph = ( numberOfCharactersStart > 1u ) && HasLigatureMustBreak( mModel->mLogicalModel->GetScript( selectionStart ) );
2070 // Check if the last glyph is a ligature that must be broken like Latin ff, fi, or Arabic ï»», etc which needs special code.
2071 const Length numberOfCharactersEnd = *( charactersPerGlyphBuffer + glyphEnd );
2072 bool splitEndGlyph = ( glyphStart != glyphEnd ) && ( numberOfCharactersEnd > 1u ) && HasLigatureMustBreak( mModel->mLogicalModel->GetScript( selectionEndMinusOne ) );
2074 // The number of quads of the selection box.
2075 const unsigned int numberOfQuads = 1u + ( glyphEnd - glyphStart ) + ( ( numberOfLines > 1u ) ? 2u * numberOfLines : 0u );
2076 mEventData->mDecorator->ResizeHighlightQuads( numberOfQuads );
2078 // Count the actual number of quads.
2079 unsigned int actualNumberOfQuads = 0u;
2082 // Traverse the glyphs.
2083 for( GlyphIndex index = glyphStart; index <= glyphEnd; ++index )
2085 const GlyphInfo& glyph = *( glyphsBuffer + index );
2086 const Vector2& position = *( positionsBuffer + index );
2088 if( splitStartGlyph )
2090 // 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.
2092 const float glyphAdvance = glyph.advance / static_cast<float>( numberOfCharactersStart );
2093 const CharacterIndex interGlyphIndex = selectionStart - *( glyphToCharacterBuffer + glyphStart );
2094 // Get the direction of the character.
2095 CharacterDirection isCurrentRightToLeft = false;
2096 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
2098 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + selectionStart );
2101 // The end point could be in the middle of the ligature.
2102 // Calculate the number of characters selected.
2103 const Length numberOfCharacters = ( glyphStart == glyphEnd ) ? ( selectionEnd - selectionStart ) : ( numberOfCharactersStart - interGlyphIndex );
2105 quad.x = lineRun->alignmentOffset + position.x - glyph.xBearing + mModel->mScrollPosition.x + glyphAdvance * static_cast<float>( isCurrentRightToLeft ? ( numberOfCharactersStart - interGlyphIndex - numberOfCharacters ) : interGlyphIndex );
2106 quad.y = selectionBoxInfo->lineOffset;
2107 quad.z = quad.x + static_cast<float>( numberOfCharacters ) * glyphAdvance;
2108 quad.w = selectionBoxInfo->lineOffset + selectionBoxInfo->lineHeight;
2110 // Store the min and max 'x' for each line.
2111 selectionBoxInfo->minX = std::min( selectionBoxInfo->minX, quad.x );
2112 selectionBoxInfo->maxX = std::max( selectionBoxInfo->maxX, quad.z );
2114 mEventData->mDecorator->AddHighlight( actualNumberOfQuads, quad );
2115 ++actualNumberOfQuads;
2117 splitStartGlyph = false;
2121 if( splitEndGlyph && ( index == glyphEnd ) )
2123 // 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.
2125 const float glyphAdvance = glyph.advance / static_cast<float>( numberOfCharactersEnd );
2126 const CharacterIndex interGlyphIndex = selectionEnd - *( glyphToCharacterBuffer + glyphEnd );
2127 // Get the direction of the character.
2128 CharacterDirection isCurrentRightToLeft = false;
2129 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
2131 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + selectionEnd );
2134 const Length numberOfCharacters = numberOfCharactersEnd - interGlyphIndex;
2136 quad.x = lineRun->alignmentOffset + position.x - glyph.xBearing + mModel->mScrollPosition.x + ( isCurrentRightToLeft ? ( glyphAdvance * static_cast<float>( numberOfCharacters ) ) : 0.f );
2137 quad.y = selectionBoxInfo->lineOffset;
2138 quad.z = quad.x + static_cast<float>( interGlyphIndex ) * glyphAdvance;
2139 quad.w = quad.y + selectionBoxInfo->lineHeight;
2141 // Store the min and max 'x' for each line.
2142 selectionBoxInfo->minX = std::min( selectionBoxInfo->minX, quad.x );
2143 selectionBoxInfo->maxX = std::max( selectionBoxInfo->maxX, quad.z );
2145 mEventData->mDecorator->AddHighlight( actualNumberOfQuads,
2147 ++actualNumberOfQuads;
2149 splitEndGlyph = false;
2153 quad.x = lineRun->alignmentOffset + position.x - glyph.xBearing + mModel->mScrollPosition.x;
2154 quad.y = selectionBoxInfo->lineOffset;
2155 quad.z = quad.x + glyph.advance;
2156 quad.w = quad.y + selectionBoxInfo->lineHeight;
2158 // Store the min and max 'x' for each line.
2159 selectionBoxInfo->minX = std::min( selectionBoxInfo->minX, quad.x );
2160 selectionBoxInfo->maxX = std::max( selectionBoxInfo->maxX, quad.z );
2162 mEventData->mDecorator->AddHighlight( actualNumberOfQuads,
2164 ++actualNumberOfQuads;
2166 // Whether to retrieve the next line.
2167 if( index == lastGlyphOfLine )
2170 if( lineIndex < firstLineIndex + numberOfLines )
2172 // Retrieve the next line.
2175 // Get the last glyph of the new line.
2176 lastGlyphOfLine = lineRun->glyphRun.glyphIndex + lineRun->glyphRun.numberOfGlyphs - 1u;
2178 // Keep the offset and height of the current selection box.
2179 const float currentLineOffset = selectionBoxInfo->lineOffset;
2180 const float currentLineHeight = selectionBoxInfo->lineHeight;
2182 // Get the selection box info for the next line.
2185 selectionBoxInfo->minX = MAX_FLOAT;
2186 selectionBoxInfo->maxX = MIN_FLOAT;
2188 // Update the line's vertical offset.
2189 selectionBoxInfo->lineOffset = currentLineOffset + currentLineHeight;
2191 // The line height is the addition of the line ascender and the line descender.
2192 // However, the line descender has a negative value, hence the subtraction.
2193 selectionBoxInfo->lineHeight = lineRun->ascender - lineRun->descender;
2198 // Traverses all the lines and updates the min and max 'x' positions and the total height.
2199 // The final width is calculated after 'boxifying' the selection.
2200 for( Vector<SelectionBoxInfo>::ConstIterator it = selectionBoxLinesInfo.Begin(),
2201 endIt = selectionBoxLinesInfo.End();
2205 const SelectionBoxInfo& info = *it;
2207 // Update the size of the highlighted text.
2208 highLightSize.height += info.lineHeight;
2209 minHighlightX = std::min( minHighlightX, info.minX );
2210 maxHighlightX = std::max( maxHighlightX, info.maxX );
2213 // Add extra geometry to 'boxify' the selection.
2215 if( 1u < numberOfLines )
2217 // Boxify the first line.
2218 lineRun = mModel->mVisualModel->mLines.Begin() + firstLineIndex;
2219 const SelectionBoxInfo& firstSelectionBoxLineInfo = *( selectionBoxLinesInfo.Begin() );
2221 bool boxifyBegin = ( LTR != lineRun->direction ) && ( LTR != startDirection );
2222 bool boxifyEnd = ( LTR == lineRun->direction ) && ( LTR == startDirection );
2227 quad.y = firstSelectionBoxLineInfo.lineOffset;
2228 quad.z = firstSelectionBoxLineInfo.minX;
2229 quad.w = firstSelectionBoxLineInfo.lineOffset + firstSelectionBoxLineInfo.lineHeight;
2231 // Boxify at the beginning of the line.
2232 mEventData->mDecorator->AddHighlight( actualNumberOfQuads,
2234 ++actualNumberOfQuads;
2236 // Update the size of the highlighted text.
2237 minHighlightX = 0.f;
2242 quad.x = firstSelectionBoxLineInfo.maxX;
2243 quad.y = firstSelectionBoxLineInfo.lineOffset;
2244 quad.z = mModel->mVisualModel->mControlSize.width;
2245 quad.w = firstSelectionBoxLineInfo.lineOffset + firstSelectionBoxLineInfo.lineHeight;
2247 // Boxify at the end of the line.
2248 mEventData->mDecorator->AddHighlight( actualNumberOfQuads,
2250 ++actualNumberOfQuads;
2252 // Update the size of the highlighted text.
2253 maxHighlightX = mModel->mVisualModel->mControlSize.width;
2256 // Boxify the central lines.
2257 if( 2u < numberOfLines )
2259 for( Vector<SelectionBoxInfo>::ConstIterator it = selectionBoxLinesInfo.Begin() + 1u,
2260 endIt = selectionBoxLinesInfo.End() - 1u;
2264 const SelectionBoxInfo& info = *it;
2267 quad.y = info.lineOffset;
2269 quad.w = info.lineOffset + info.lineHeight;
2271 mEventData->mDecorator->AddHighlight( actualNumberOfQuads,
2273 ++actualNumberOfQuads;
2276 quad.y = info.lineOffset;
2277 quad.z = mModel->mVisualModel->mControlSize.width;
2278 quad.w = info.lineOffset + info.lineHeight;
2280 mEventData->mDecorator->AddHighlight( actualNumberOfQuads,
2282 ++actualNumberOfQuads;
2285 // Update the size of the highlighted text.
2286 minHighlightX = 0.f;
2287 maxHighlightX = mModel->mVisualModel->mControlSize.width;
2290 // Boxify the last line.
2291 lineRun = mModel->mVisualModel->mLines.Begin() + firstLineIndex + numberOfLines - 1u;
2292 const SelectionBoxInfo& lastSelectionBoxLineInfo = *( selectionBoxLinesInfo.End() - 1u );
2294 boxifyBegin = ( LTR == lineRun->direction ) && ( LTR == endDirection );
2295 boxifyEnd = ( LTR != lineRun->direction ) && ( LTR != endDirection );
2300 quad.y = lastSelectionBoxLineInfo.lineOffset;
2301 quad.z = lastSelectionBoxLineInfo.minX;
2302 quad.w = lastSelectionBoxLineInfo.lineOffset + lastSelectionBoxLineInfo.lineHeight;
2304 // Boxify at the beginning of the line.
2305 mEventData->mDecorator->AddHighlight( actualNumberOfQuads,
2307 ++actualNumberOfQuads;
2309 // Update the size of the highlighted text.
2310 minHighlightX = 0.f;
2315 quad.x = lastSelectionBoxLineInfo.maxX;
2316 quad.y = lastSelectionBoxLineInfo.lineOffset;
2317 quad.z = mModel->mVisualModel->mControlSize.width;
2318 quad.w = lastSelectionBoxLineInfo.lineOffset + lastSelectionBoxLineInfo.lineHeight;
2320 // Boxify at the end of the line.
2321 mEventData->mDecorator->AddHighlight( actualNumberOfQuads,
2323 ++actualNumberOfQuads;
2325 // Update the size of the highlighted text.
2326 maxHighlightX = mModel->mVisualModel->mControlSize.width;
2330 // Set the actual number of quads.
2331 mEventData->mDecorator->ResizeHighlightQuads( actualNumberOfQuads );
2333 // Sets the highlight's size and position. In decorator's coords.
2334 // The highlight's height has been calculated above (before 'boxifying' the highlight).
2335 highLightSize.width = maxHighlightX - minHighlightX;
2337 highLightPosition.x = minHighlightX;
2338 const SelectionBoxInfo& firstSelectionBoxLineInfo = *( selectionBoxLinesInfo.Begin() );
2339 highLightPosition.y = firstSelectionBoxLineInfo.lineOffset;
2341 mEventData->mDecorator->SetHighLightBox( highLightPosition, highLightSize, static_cast<float>( mModel->GetOutlineWidth() ) );
2343 if( !mEventData->mDecorator->IsSmoothHandlePanEnabled() )
2345 CursorInfo primaryCursorInfo;
2346 GetCursorPosition( mEventData->mLeftSelectionPosition,
2347 primaryCursorInfo );
2349 const Vector2 primaryPosition = primaryCursorInfo.primaryPosition + mModel->mScrollPosition;
2351 mEventData->mDecorator->SetPosition( LEFT_SELECTION_HANDLE,
2353 primaryCursorInfo.lineOffset + mModel->mScrollPosition.y,
2354 primaryCursorInfo.lineHeight );
2356 CursorInfo secondaryCursorInfo;
2357 GetCursorPosition( mEventData->mRightSelectionPosition,
2358 secondaryCursorInfo );
2360 const Vector2 secondaryPosition = secondaryCursorInfo.primaryPosition + mModel->mScrollPosition;
2362 mEventData->mDecorator->SetPosition( RIGHT_SELECTION_HANDLE,
2363 secondaryPosition.x,
2364 secondaryCursorInfo.lineOffset + mModel->mScrollPosition.y,
2365 secondaryCursorInfo.lineHeight );
2368 // Set the flag to update the decorator.
2369 mEventData->mDecoratorUpdated = true;
2372 void Controller::Impl::RepositionSelectionHandles( float visualX, float visualY, Controller::NoTextTap::Action action )
2374 if( NULL == mEventData )
2376 // Nothing to do if there is no text input.
2380 if( IsShowingPlaceholderText() )
2382 // Nothing to do if there is the place-holder text.
2386 const Length numberOfGlyphs = mModel->mVisualModel->mGlyphs.Count();
2387 const Length numberOfLines = mModel->mVisualModel->mLines.Count();
2388 if( ( 0 == numberOfGlyphs ) ||
2389 ( 0 == numberOfLines ) )
2391 // Nothing to do if there is no text.
2395 // Find which word was selected
2396 CharacterIndex selectionStart( 0 );
2397 CharacterIndex selectionEnd( 0 );
2398 CharacterIndex noTextHitIndex( 0 );
2399 const bool characterHit = FindSelectionIndices( mModel->mVisualModel,
2400 mModel->mLogicalModel,
2407 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "%p selectionStart %d selectionEnd %d\n", this, selectionStart, selectionEnd );
2409 if( characterHit || ( Controller::NoTextTap::HIGHLIGHT == action ) )
2411 ChangeState( EventData::SELECTING );
2413 mEventData->mLeftSelectionPosition = selectionStart;
2414 mEventData->mRightSelectionPosition = selectionEnd;
2416 mEventData->mUpdateLeftSelectionPosition = true;
2417 mEventData->mUpdateRightSelectionPosition = true;
2418 mEventData->mUpdateHighlightBox = true;
2420 // It may happen an IMF commit event arrives before the selection event
2421 // if the IMF manager is in pre-edit state. The commit event will set the
2422 // mEventData->mUpdateCursorPosition flag to true. If it's not set back
2423 // to false, the highlight box won't be updated.
2424 mEventData->mUpdateCursorPosition = false;
2426 mEventData->mScrollAfterUpdatePosition = ( mEventData->mLeftSelectionPosition != mEventData->mRightSelectionPosition );
2428 // Cursor to be positioned at end of selection so if selection interrupted and edit mode restarted the cursor will be at end of selection
2429 mEventData->mPrimaryCursorPosition = std::max( mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition );
2431 else if( Controller::NoTextTap::SHOW_SELECTION_POPUP == action )
2433 // Nothing to select. i.e. a white space, out of bounds
2434 ChangeState( EventData::EDITING_WITH_POPUP );
2436 mEventData->mPrimaryCursorPosition = noTextHitIndex;
2438 mEventData->mUpdateCursorPosition = true;
2439 mEventData->mUpdateGrabHandlePosition = true;
2440 mEventData->mScrollAfterUpdatePosition = true;
2441 mEventData->mUpdateInputStyle = true;
2443 else if( Controller::NoTextTap::NO_ACTION == action )
2445 // Nothing to select. i.e. a white space, out of bounds
2446 mEventData->mPrimaryCursorPosition = noTextHitIndex;
2448 mEventData->mUpdateCursorPosition = true;
2449 mEventData->mUpdateGrabHandlePosition = true;
2450 mEventData->mScrollAfterUpdatePosition = true;
2451 mEventData->mUpdateInputStyle = true;
2455 void Controller::Impl::SetPopupButtons()
2458 * Sets the Popup buttons to be shown depending on State.
2460 * If SELECTING : CUT & COPY + ( PASTE & CLIPBOARD if content available to paste )
2462 * If EDITING_WITH_POPUP : SELECT & SELECT_ALL
2465 TextSelectionPopup::Buttons buttonsToShow = TextSelectionPopup::NONE;
2467 if( EventData::SELECTING == mEventData->mState )
2469 buttonsToShow = TextSelectionPopup::Buttons( TextSelectionPopup::CUT | TextSelectionPopup::COPY );
2471 if( !IsClipboardEmpty() )
2473 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
2474 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::CLIPBOARD ) );
2477 if( !mEventData->mAllTextSelected )
2479 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::SELECT_ALL ) );
2482 else if( EventData::EDITING_WITH_POPUP == mEventData->mState )
2484 if( mModel->mLogicalModel->mText.Count() && !IsShowingPlaceholderText() )
2486 buttonsToShow = TextSelectionPopup::Buttons( TextSelectionPopup::SELECT | TextSelectionPopup::SELECT_ALL );
2489 if( !IsClipboardEmpty() )
2491 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
2492 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::CLIPBOARD ) );
2495 else if( EventData::EDITING_WITH_PASTE_POPUP == mEventData->mState )
2497 if ( !IsClipboardEmpty() )
2499 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
2500 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::CLIPBOARD ) );
2504 mEventData->mDecorator->SetEnabledPopupButtons( buttonsToShow );
2507 void Controller::Impl::ChangeState( EventData::State newState )
2509 if( NULL == mEventData )
2511 // Nothing to do if there is no text input.
2515 DALI_LOG_INFO( gLogFilter, Debug::General, "ChangeState state:%d newstate:%d\n", mEventData->mState, newState );
2517 if( mEventData->mState != newState )
2519 mEventData->mPreviousState = mEventData->mState;
2520 mEventData->mState = newState;
2522 switch( mEventData->mState )
2524 case EventData::INACTIVE:
2526 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
2527 mEventData->mDecorator->StopCursorBlink();
2528 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
2529 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
2530 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
2531 mEventData->mDecorator->SetHighlightActive( false );
2532 mEventData->mDecorator->SetPopupActive( false );
2533 mEventData->mDecoratorUpdated = true;
2536 case EventData::INTERRUPTED:
2538 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
2539 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
2540 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
2541 mEventData->mDecorator->SetHighlightActive( false );
2542 mEventData->mDecorator->SetPopupActive( false );
2543 mEventData->mDecoratorUpdated = true;
2546 case EventData::SELECTING:
2548 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
2549 mEventData->mDecorator->StopCursorBlink();
2550 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
2551 if ( mEventData->mGrabHandleEnabled )
2553 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true );
2554 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true );
2556 mEventData->mDecorator->SetHighlightActive( true );
2557 if( mEventData->mGrabHandlePopupEnabled )
2560 mEventData->mDecorator->SetPopupActive( true );
2562 mEventData->mDecoratorUpdated = true;
2565 case EventData::EDITING:
2567 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
2568 if( mEventData->mCursorBlinkEnabled )
2570 mEventData->mDecorator->StartCursorBlink();
2572 // Grab handle is not shown until a tap is received whilst EDITING
2573 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
2574 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
2575 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
2576 mEventData->mDecorator->SetHighlightActive( false );
2577 if( mEventData->mGrabHandlePopupEnabled )
2579 mEventData->mDecorator->SetPopupActive( false );
2581 mEventData->mDecoratorUpdated = true;
2584 case EventData::EDITING_WITH_POPUP:
2586 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "EDITING_WITH_POPUP \n", newState );
2588 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
2589 if( mEventData->mCursorBlinkEnabled )
2591 mEventData->mDecorator->StartCursorBlink();
2593 if( mEventData->mSelectionEnabled )
2595 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
2596 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
2597 mEventData->mDecorator->SetHighlightActive( false );
2599 else if ( mEventData->mGrabHandleEnabled )
2601 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
2603 if( mEventData->mGrabHandlePopupEnabled )
2606 mEventData->mDecorator->SetPopupActive( true );
2608 mEventData->mDecoratorUpdated = true;
2611 case EventData::EDITING_WITH_GRAB_HANDLE:
2613 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "EDITING_WITH_GRAB_HANDLE \n", newState );
2615 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
2616 if( mEventData->mCursorBlinkEnabled )
2618 mEventData->mDecorator->StartCursorBlink();
2620 // Grab handle is not shown until a tap is received whilst EDITING
2621 if ( mEventData->mGrabHandleEnabled )
2623 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
2625 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
2626 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
2627 mEventData->mDecorator->SetHighlightActive( false );
2628 if( mEventData->mGrabHandlePopupEnabled )
2630 mEventData->mDecorator->SetPopupActive( false );
2632 mEventData->mDecoratorUpdated = true;
2635 case EventData::SELECTION_HANDLE_PANNING:
2637 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
2638 mEventData->mDecorator->StopCursorBlink();
2639 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
2640 if ( mEventData->mGrabHandleEnabled )
2642 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true );
2643 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true );
2645 mEventData->mDecorator->SetHighlightActive( true );
2646 if( mEventData->mGrabHandlePopupEnabled )
2648 mEventData->mDecorator->SetPopupActive( false );
2650 mEventData->mDecoratorUpdated = true;
2653 case EventData::GRAB_HANDLE_PANNING:
2655 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "GRAB_HANDLE_PANNING \n", newState );
2657 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
2658 if( mEventData->mCursorBlinkEnabled )
2660 mEventData->mDecorator->StartCursorBlink();
2662 if ( mEventData->mGrabHandleEnabled )
2664 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
2666 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
2667 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
2668 mEventData->mDecorator->SetHighlightActive( false );
2669 if( mEventData->mGrabHandlePopupEnabled )
2671 mEventData->mDecorator->SetPopupActive( false );
2673 mEventData->mDecoratorUpdated = true;
2676 case EventData::EDITING_WITH_PASTE_POPUP:
2678 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "EDITING_WITH_PASTE_POPUP \n", newState );
2680 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
2681 if( mEventData->mCursorBlinkEnabled )
2683 mEventData->mDecorator->StartCursorBlink();
2686 if ( mEventData->mGrabHandleEnabled )
2688 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
2690 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
2691 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
2692 mEventData->mDecorator->SetHighlightActive( false );
2694 if( mEventData->mGrabHandlePopupEnabled )
2697 mEventData->mDecorator->SetPopupActive( true );
2699 mEventData->mDecoratorUpdated = true;
2702 case EventData::TEXT_PANNING:
2704 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
2705 mEventData->mDecorator->StopCursorBlink();
2706 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
2707 if( mEventData->mDecorator->IsHandleActive( LEFT_SELECTION_HANDLE ) ||
2708 mEventData->mDecorator->IsHandleActive( RIGHT_SELECTION_HANDLE ) )
2710 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
2711 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
2712 mEventData->mDecorator->SetHighlightActive( true );
2715 if( mEventData->mGrabHandlePopupEnabled )
2717 mEventData->mDecorator->SetPopupActive( false );
2720 mEventData->mDecoratorUpdated = true;
2727 void Controller::Impl::GetCursorPosition( CharacterIndex logical,
2728 CursorInfo& cursorInfo )
2730 if( !IsShowingRealText() )
2732 // Do not want to use the place-holder text to set the cursor position.
2734 // Use the line's height of the font's family set to set the cursor's size.
2735 // If there is no font's family set, use the default font.
2736 // Use the current alignment to place the cursor at the beginning, center or end of the box.
2738 cursorInfo.lineOffset = 0.f;
2739 cursorInfo.lineHeight = GetDefaultFontLineHeight();
2740 cursorInfo.primaryCursorHeight = cursorInfo.lineHeight;
2742 switch( mModel->mHorizontalAlignment )
2744 case Text::HorizontalAlignment::BEGIN :
2746 cursorInfo.primaryPosition.x = 0.f;
2749 case Text::HorizontalAlignment::CENTER:
2751 cursorInfo.primaryPosition.x = floorf( 0.5f * mModel->mVisualModel->mControlSize.width );
2754 case Text::HorizontalAlignment::END:
2756 cursorInfo.primaryPosition.x = mModel->mVisualModel->mControlSize.width - mEventData->mDecorator->GetCursorWidth();
2761 // Nothing else to do.
2765 const bool isMultiLine = ( Layout::Engine::MULTI_LINE_BOX == mLayoutEngine.GetLayout() );
2766 GetCursorPositionParameters parameters;
2767 parameters.visualModel = mModel->mVisualModel;
2768 parameters.logicalModel = mModel->mLogicalModel;
2769 parameters.metrics = mMetrics;
2770 parameters.logical = logical;
2771 parameters.isMultiline = isMultiLine;
2773 Text::GetCursorPosition( parameters,
2776 // Adds Outline offset.
2777 const float outlineWidth = static_cast<float>( mModel->GetOutlineWidth() );
2778 cursorInfo.primaryPosition.x += outlineWidth;
2779 cursorInfo.primaryPosition.y += outlineWidth;
2780 cursorInfo.secondaryPosition.x += outlineWidth;
2781 cursorInfo.secondaryPosition.y += outlineWidth;
2785 // If the text is editable and multi-line, the cursor position after a white space shouldn't exceed the boundaries of the text control.
2787 // Note the white spaces laid-out at the end of the line might exceed the boundaries of the control.
2788 // The reason is a wrapped line must not start with a white space so they are laid-out at the end of the line.
2790 if( 0.f > cursorInfo.primaryPosition.x )
2792 cursorInfo.primaryPosition.x = 0.f;
2795 const float edgeWidth = mModel->mVisualModel->mControlSize.width - static_cast<float>( mEventData->mDecorator->GetCursorWidth() );
2796 if( cursorInfo.primaryPosition.x > edgeWidth )
2798 cursorInfo.primaryPosition.x = edgeWidth;
2803 CharacterIndex Controller::Impl::CalculateNewCursorIndex( CharacterIndex index ) const
2805 if( NULL == mEventData )
2807 // Nothing to do if there is no text input.
2811 CharacterIndex cursorIndex = mEventData->mPrimaryCursorPosition;
2813 const GlyphIndex* const charactersToGlyphBuffer = mModel->mVisualModel->mCharactersToGlyph.Begin();
2814 const Length* const charactersPerGlyphBuffer = mModel->mVisualModel->mCharactersPerGlyph.Begin();
2816 GlyphIndex glyphIndex = *( charactersToGlyphBuffer + index );
2817 Length numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
2819 if( numberOfCharacters > 1u )
2821 const Script script = mModel->mLogicalModel->GetScript( index );
2822 if( HasLigatureMustBreak( script ) )
2824 // Prevents to jump the whole Latin ligatures like fi, ff, or Arabic ï»», ...
2825 numberOfCharacters = 1u;
2830 while( 0u == numberOfCharacters )
2833 numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
2837 if( index < mEventData->mPrimaryCursorPosition )
2839 cursorIndex -= numberOfCharacters;
2843 cursorIndex += numberOfCharacters;
2846 // Will update the cursor hook position.
2847 mEventData->mUpdateCursorHookPosition = true;
2852 void Controller::Impl::UpdateCursorPosition( const CursorInfo& cursorInfo )
2854 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "-->Controller::UpdateCursorPosition %p\n", this );
2855 if( NULL == mEventData )
2857 // Nothing to do if there is no text input.
2858 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition no event data\n" );
2862 const Vector2 cursorPosition = cursorInfo.primaryPosition + mModel->mScrollPosition;
2864 mEventData->mDecorator->SetGlyphOffset( PRIMARY_CURSOR, cursorInfo.glyphOffset );
2866 // Sets the cursor position.
2867 mEventData->mDecorator->SetPosition( PRIMARY_CURSOR,
2870 cursorInfo.primaryCursorHeight,
2871 cursorInfo.lineHeight );
2872 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Primary cursor position: %f,%f\n", cursorPosition.x, cursorPosition.y );
2874 if( mEventData->mUpdateGrabHandlePosition )
2876 // Sets the grab handle position.
2877 mEventData->mDecorator->SetPosition( GRAB_HANDLE,
2879 cursorInfo.lineOffset + mModel->mScrollPosition.y,
2880 cursorInfo.lineHeight );
2883 if( cursorInfo.isSecondaryCursor )
2885 mEventData->mDecorator->SetPosition( SECONDARY_CURSOR,
2886 cursorInfo.secondaryPosition.x + mModel->mScrollPosition.x,
2887 cursorInfo.secondaryPosition.y + mModel->mScrollPosition.y,
2888 cursorInfo.secondaryCursorHeight,
2889 cursorInfo.lineHeight );
2890 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Secondary cursor position: %f,%f\n", cursorInfo.secondaryPosition.x + mModel->mScrollPosition.x, cursorInfo.secondaryPosition.y + mModel->mScrollPosition.y );
2893 // Set which cursors are active according the state.
2894 if( EventData::IsEditingState( mEventData->mState ) || ( EventData::GRAB_HANDLE_PANNING == mEventData->mState ) )
2896 if( cursorInfo.isSecondaryCursor )
2898 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_BOTH );
2902 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
2907 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
2910 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition\n" );
2913 void Controller::Impl::UpdateSelectionHandle( HandleType handleType,
2914 const CursorInfo& cursorInfo )
2916 if( ( LEFT_SELECTION_HANDLE != handleType ) &&
2917 ( RIGHT_SELECTION_HANDLE != handleType ) )
2922 const Vector2 cursorPosition = cursorInfo.primaryPosition + mModel->mScrollPosition;
2924 // Sets the handle's position.
2925 mEventData->mDecorator->SetPosition( handleType,
2927 cursorInfo.lineOffset + mModel->mScrollPosition.y,
2928 cursorInfo.lineHeight );
2930 // If selection handle at start of the text and other at end of the text then all text is selected.
2931 const CharacterIndex startOfSelection = std::min( mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition );
2932 const CharacterIndex endOfSelection = std::max ( mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition );
2933 mEventData->mAllTextSelected = ( startOfSelection == 0 ) && ( endOfSelection == mModel->mLogicalModel->mText.Count() );
2936 void Controller::Impl::ClampHorizontalScroll( const Vector2& layoutSize )
2938 // Clamp between -space & -alignment offset.
2940 if( layoutSize.width > mModel->mVisualModel->mControlSize.width )
2942 const float space = ( layoutSize.width - mModel->mVisualModel->mControlSize.width ) + mModel->mAlignmentOffset;
2943 mModel->mScrollPosition.x = ( mModel->mScrollPosition.x < -space ) ? -space : mModel->mScrollPosition.x;
2944 mModel->mScrollPosition.x = ( mModel->mScrollPosition.x > -mModel->mAlignmentOffset ) ? -mModel->mAlignmentOffset : mModel->mScrollPosition.x;
2946 mEventData->mDecoratorUpdated = true;
2950 mModel->mScrollPosition.x = 0.f;
2954 void Controller::Impl::ClampVerticalScroll( const Vector2& layoutSize )
2956 if( Layout::Engine::SINGLE_LINE_BOX == mLayoutEngine.GetLayout() )
2958 // Nothing to do if the text is single line.
2962 // Clamp between -space & 0.
2963 if( layoutSize.height > mModel->mVisualModel->mControlSize.height )
2965 const float space = ( layoutSize.height - mModel->mVisualModel->mControlSize.height );
2966 mModel->mScrollPosition.y = ( mModel->mScrollPosition.y < -space ) ? -space : mModel->mScrollPosition.y;
2967 mModel->mScrollPosition.y = ( mModel->mScrollPosition.y > 0.f ) ? 0.f : mModel->mScrollPosition.y;
2969 mEventData->mDecoratorUpdated = true;
2973 mModel->mScrollPosition.y = 0.f;
2977 void Controller::Impl::ScrollToMakePositionVisible( const Vector2& position, float lineHeight )
2979 const float cursorWidth = mEventData->mDecorator ? static_cast<float>( mEventData->mDecorator->GetCursorWidth() ) : 0.f;
2981 // position is in actor's coords.
2982 const float positionEndX = position.x + cursorWidth;
2983 const float positionEndY = position.y + lineHeight;
2985 // Transform the position to decorator coords.
2986 const float decoratorPositionBeginX = position.x + mModel->mScrollPosition.x;
2987 const float decoratorPositionEndX = positionEndX + mModel->mScrollPosition.x;
2989 const float decoratorPositionBeginY = position.y + mModel->mScrollPosition.y;
2990 const float decoratorPositionEndY = positionEndY + mModel->mScrollPosition.y;
2992 if( decoratorPositionBeginX < 0.f )
2994 mModel->mScrollPosition.x = -position.x;
2996 else if( decoratorPositionEndX > mModel->mVisualModel->mControlSize.width )
2998 mModel->mScrollPosition.x = mModel->mVisualModel->mControlSize.width - positionEndX;
3001 if( Layout::Engine::MULTI_LINE_BOX == mLayoutEngine.GetLayout() )
3003 if( decoratorPositionBeginY < 0.f )
3005 mModel->mScrollPosition.y = -position.y;
3007 else if( decoratorPositionEndY > mModel->mVisualModel->mControlSize.height )
3009 mModel->mScrollPosition.y = mModel->mVisualModel->mControlSize.height - positionEndY;
3014 void Controller::Impl::ScrollTextToMatchCursor( const CursorInfo& cursorInfo )
3016 // Get the current cursor position in decorator coords.
3017 const Vector2& currentCursorPosition = mEventData->mDecorator->GetPosition( PRIMARY_CURSOR );
3019 const LineIndex lineIndex = mModel->mVisualModel->GetLineOfCharacter( mEventData->mPrimaryCursorPosition );
3023 // Calculate the offset to match the cursor position before the character was deleted.
3024 mModel->mScrollPosition.x = currentCursorPosition.x - cursorInfo.primaryPosition.x;
3026 //If text control has more than two lines and current line index is not last, calculate scrollpositionY
3027 if( mModel->mVisualModel->mLines.Count() > 1u && lineIndex != mModel->mVisualModel->mLines.Count() -1u )
3029 const float currentCursorGlyphOffset = mEventData->mDecorator->GetGlyphOffset( PRIMARY_CURSOR );
3030 mModel->mScrollPosition.y = currentCursorPosition.y - cursorInfo.lineOffset - currentCursorGlyphOffset;
3034 ClampHorizontalScroll( mModel->mVisualModel->GetLayoutSize() );
3035 ClampVerticalScroll( mModel->mVisualModel->GetLayoutSize() );
3037 // Makes the new cursor position visible if needed.
3038 ScrollToMakePositionVisible( cursorInfo.primaryPosition, cursorInfo.lineHeight );
3041 void Controller::Impl::RequestRelayout()
3043 if( NULL != mControlInterface )
3045 mControlInterface->RequestTextRelayout();
3051 } // namespace Toolkit