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 mCheckScrollAmount( false ),
110 mIsPlaceholderPixelSize( false ),
111 mIsPlaceholderElideEnabled( false ),
112 mPlaceholderEllipsisFlag( false ),
113 mShiftSelectionFlag( true )
115 mImfManager = ImfManager::Get();
118 EventData::~EventData()
121 bool Controller::Impl::ProcessInputEvents()
123 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "-->Controller::ProcessInputEvents\n" );
124 if( NULL == mEventData )
126 // Nothing to do if there is no text input.
127 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::ProcessInputEvents no event data\n" );
131 if( mEventData->mDecorator )
133 for( std::vector<Event>::iterator iter = mEventData->mEventQueue.begin();
134 iter != mEventData->mEventQueue.end();
139 case Event::CURSOR_KEY_EVENT:
141 OnCursorKeyEvent( *iter );
144 case Event::TAP_EVENT:
149 case Event::LONG_PRESS_EVENT:
151 OnLongPressEvent( *iter );
154 case Event::PAN_EVENT:
159 case Event::GRAB_HANDLE_EVENT:
160 case Event::LEFT_SELECTION_HANDLE_EVENT:
161 case Event::RIGHT_SELECTION_HANDLE_EVENT: // Fall through
163 OnHandleEvent( *iter );
168 OnSelectEvent( *iter );
171 case Event::SELECT_ALL:
180 if( mEventData->mUpdateCursorPosition ||
181 mEventData->mUpdateHighlightBox )
186 // The cursor must also be repositioned after inserts into the model
187 if( mEventData->mUpdateCursorPosition )
189 // Updates the cursor position and scrolls the text to make it visible.
190 CursorInfo cursorInfo;
191 // Calculate the cursor position from the new cursor index.
192 GetCursorPosition( mEventData->mPrimaryCursorPosition,
195 if( mEventData->mUpdateCursorHookPosition )
197 // Update the cursor hook position. Used to move the cursor with the keys 'up' and 'down'.
198 mEventData->mCursorHookPositionX = cursorInfo.primaryPosition.x;
199 mEventData->mUpdateCursorHookPosition = false;
202 // Scroll first the text after delete ...
203 if( mEventData->mScrollAfterDelete )
205 ScrollTextToMatchCursor( cursorInfo );
208 // ... then, text can be scrolled to make the cursor visible.
209 if( mEventData->mScrollAfterUpdatePosition )
211 const Vector2 currentCursorPosition( cursorInfo.primaryPosition.x, cursorInfo.lineOffset );
212 ScrollToMakePositionVisible( currentCursorPosition, cursorInfo.lineHeight );
214 mEventData->mScrollAfterUpdatePosition = false;
215 mEventData->mScrollAfterDelete = false;
217 UpdateCursorPosition( cursorInfo );
219 mEventData->mDecoratorUpdated = true;
220 mEventData->mUpdateCursorPosition = false;
221 mEventData->mUpdateGrabHandlePosition = false;
225 CursorInfo leftHandleInfo;
226 CursorInfo rightHandleInfo;
228 if( mEventData->mUpdateHighlightBox )
230 GetCursorPosition( mEventData->mLeftSelectionPosition,
233 GetCursorPosition( mEventData->mRightSelectionPosition,
236 if( mEventData->mScrollAfterUpdatePosition && ( mEventData->mIsLeftHandleSelected ? mEventData->mUpdateLeftSelectionPosition : mEventData->mUpdateRightSelectionPosition ) )
238 if( mEventData->mIsLeftHandleSelected && mEventData->mIsRightHandleSelected )
240 CursorInfo& infoLeft = leftHandleInfo;
242 const Vector2 currentCursorPositionLeft( infoLeft.primaryPosition.x, infoLeft.lineOffset );
243 ScrollToMakePositionVisible( currentCursorPositionLeft, infoLeft.lineHeight );
245 CursorInfo& infoRight = rightHandleInfo;
247 const Vector2 currentCursorPositionRight( infoRight.primaryPosition.x, infoRight.lineOffset );
248 ScrollToMakePositionVisible( currentCursorPositionRight, infoRight.lineHeight );
252 CursorInfo& info = mEventData->mIsLeftHandleSelected ? leftHandleInfo : rightHandleInfo;
254 const Vector2 currentCursorPosition( info.primaryPosition.x, info.lineOffset );
255 ScrollToMakePositionVisible( currentCursorPosition, info.lineHeight );
260 if( mEventData->mUpdateLeftSelectionPosition )
262 UpdateSelectionHandle( LEFT_SELECTION_HANDLE,
266 mEventData->mDecoratorUpdated = true;
267 mEventData->mUpdateLeftSelectionPosition = false;
270 if( mEventData->mUpdateRightSelectionPosition )
272 UpdateSelectionHandle( RIGHT_SELECTION_HANDLE,
276 mEventData->mDecoratorUpdated = true;
277 mEventData->mUpdateRightSelectionPosition = false;
280 if( mEventData->mUpdateHighlightBox )
282 RepositionSelectionHandles();
284 mEventData->mUpdateLeftSelectionPosition = false;
285 mEventData->mUpdateRightSelectionPosition = false;
286 mEventData->mUpdateHighlightBox = false;
287 mEventData->mIsLeftHandleSelected = false;
288 mEventData->mIsRightHandleSelected = false;
291 mEventData->mScrollAfterUpdatePosition = false;
294 if( mEventData->mUpdateInputStyle )
296 // Keep a copy of the current input style.
297 InputStyle currentInputStyle;
298 currentInputStyle.Copy( mEventData->mInputStyle );
300 // Set the default style first.
301 RetrieveDefaultInputStyle( mEventData->mInputStyle );
303 // Get the character index from the cursor index.
304 const CharacterIndex styleIndex = ( mEventData->mPrimaryCursorPosition > 0u ) ? mEventData->mPrimaryCursorPosition - 1u : 0u;
306 // Retrieve the style from the style runs stored in the logical model.
307 mModel->mLogicalModel->RetrieveStyle( styleIndex, mEventData->mInputStyle );
309 // Compare if the input style has changed.
310 const bool hasInputStyleChanged = !currentInputStyle.Equal( mEventData->mInputStyle );
312 if( hasInputStyleChanged )
314 const InputStyle::Mask styleChangedMask = currentInputStyle.GetInputStyleChangeMask( mEventData->mInputStyle );
315 // Queue the input style changed signal.
316 mEventData->mInputStyleChangedQueue.PushBack( styleChangedMask );
319 mEventData->mUpdateInputStyle = false;
322 mEventData->mEventQueue.clear();
324 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::ProcessInputEvents\n" );
326 const bool decoratorUpdated = mEventData->mDecoratorUpdated;
327 mEventData->mDecoratorUpdated = false;
329 return decoratorUpdated;
332 void Controller::Impl::NotifyImfManager()
334 if( mEventData && mEventData->mImfManager )
336 CharacterIndex cursorPosition = GetLogicalCursorPosition();
338 const Length numberOfWhiteSpaces = GetNumberOfWhiteSpaces( 0u );
340 // Update the cursor position by removing the initial white spaces.
341 if( cursorPosition < numberOfWhiteSpaces )
347 cursorPosition -= numberOfWhiteSpaces;
350 mEventData->mImfManager.SetCursorPosition( cursorPosition );
351 mEventData->mImfManager.NotifyCursorPosition();
355 void Controller::Impl::NotifyImfMultiLineStatus()
359 Text::Layout::Engine::Type layout = mLayoutEngine.GetLayout();
360 mEventData->mImfManager.NotifyTextInputMultiLine( layout == Text::Layout::Engine::MULTI_LINE_BOX );
364 CharacterIndex Controller::Impl::GetLogicalCursorPosition() const
366 CharacterIndex cursorPosition = 0u;
370 if( ( EventData::SELECTING == mEventData->mState ) ||
371 ( EventData::SELECTION_HANDLE_PANNING == mEventData->mState ) )
373 cursorPosition = std::min( mEventData->mRightSelectionPosition, mEventData->mLeftSelectionPosition );
377 cursorPosition = mEventData->mPrimaryCursorPosition;
381 return cursorPosition;
384 Length Controller::Impl::GetNumberOfWhiteSpaces( CharacterIndex index ) const
386 Length numberOfWhiteSpaces = 0u;
388 // Get the buffer to the text.
389 Character* utf32CharacterBuffer = mModel->mLogicalModel->mText.Begin();
391 const Length totalNumberOfCharacters = mModel->mLogicalModel->mText.Count();
392 for( ; index < totalNumberOfCharacters; ++index, ++numberOfWhiteSpaces )
394 if( !TextAbstraction::IsWhiteSpace( *( utf32CharacterBuffer + index ) ) )
400 return numberOfWhiteSpaces;
403 void Controller::Impl::GetText( CharacterIndex index, std::string& text ) const
405 // Get the total number of characters.
406 Length numberOfCharacters = mModel->mLogicalModel->mText.Count();
408 // Retrieve the text.
409 if( 0u != numberOfCharacters )
411 Utf32ToUtf8( mModel->mLogicalModel->mText.Begin() + index, numberOfCharacters - index, text );
415 void Controller::Impl::CalculateTextUpdateIndices( Length& numberOfCharacters )
417 mTextUpdateInfo.mParagraphCharacterIndex = 0u;
418 mTextUpdateInfo.mStartGlyphIndex = 0u;
419 mTextUpdateInfo.mStartLineIndex = 0u;
420 numberOfCharacters = 0u;
422 const Length numberOfParagraphs = mModel->mLogicalModel->mParagraphInfo.Count();
423 if( 0u == numberOfParagraphs )
425 mTextUpdateInfo.mParagraphCharacterIndex = 0u;
426 numberOfCharacters = 0u;
428 mTextUpdateInfo.mRequestedNumberOfCharacters = mTextUpdateInfo.mNumberOfCharactersToAdd - mTextUpdateInfo.mNumberOfCharactersToRemove;
430 // Nothing else to do if there are no paragraphs.
434 // Find the paragraphs to be updated.
435 Vector<ParagraphRunIndex> paragraphsToBeUpdated;
436 if( mTextUpdateInfo.mCharacterIndex >= mTextUpdateInfo.mPreviousNumberOfCharacters )
438 // Text is being added at the end of the current text.
439 if( mTextUpdateInfo.mIsLastCharacterNewParagraph )
441 // Text is being added in a new paragraph after the last character of the text.
442 mTextUpdateInfo.mParagraphCharacterIndex = mTextUpdateInfo.mPreviousNumberOfCharacters;
443 numberOfCharacters = 0u;
444 mTextUpdateInfo.mRequestedNumberOfCharacters = mTextUpdateInfo.mNumberOfCharactersToAdd - mTextUpdateInfo.mNumberOfCharactersToRemove;
446 mTextUpdateInfo.mStartGlyphIndex = mModel->mVisualModel->mGlyphs.Count();
447 mTextUpdateInfo.mStartLineIndex = mModel->mVisualModel->mLines.Count() - 1u;
449 // Nothing else to do;
453 paragraphsToBeUpdated.PushBack( numberOfParagraphs - 1u );
457 Length numberOfCharactersToUpdate = 0u;
458 if( mTextUpdateInfo.mFullRelayoutNeeded )
460 numberOfCharactersToUpdate = mTextUpdateInfo.mPreviousNumberOfCharacters;
464 numberOfCharactersToUpdate = ( mTextUpdateInfo.mNumberOfCharactersToRemove > 0u ) ? mTextUpdateInfo.mNumberOfCharactersToRemove : 1u;
466 mModel->mLogicalModel->FindParagraphs( mTextUpdateInfo.mCharacterIndex,
467 numberOfCharactersToUpdate,
468 paragraphsToBeUpdated );
471 if( 0u != paragraphsToBeUpdated.Count() )
473 const ParagraphRunIndex firstParagraphIndex = *( paragraphsToBeUpdated.Begin() );
474 const ParagraphRun& firstParagraph = *( mModel->mLogicalModel->mParagraphInfo.Begin() + firstParagraphIndex );
475 mTextUpdateInfo.mParagraphCharacterIndex = firstParagraph.characterRun.characterIndex;
477 ParagraphRunIndex lastParagraphIndex = *( paragraphsToBeUpdated.End() - 1u );
478 const ParagraphRun& lastParagraph = *( mModel->mLogicalModel->mParagraphInfo.Begin() + lastParagraphIndex );
480 if( ( mTextUpdateInfo.mNumberOfCharactersToRemove > 0u ) && // Some character are removed.
481 ( lastParagraphIndex < numberOfParagraphs - 1u ) && // There is a next paragraph.
482 ( ( lastParagraph.characterRun.characterIndex + lastParagraph.characterRun.numberOfCharacters ) == // The last removed character is the new paragraph character.
483 ( mTextUpdateInfo.mCharacterIndex + mTextUpdateInfo.mNumberOfCharactersToRemove ) ) )
485 // The new paragraph character of the last updated paragraph has been removed so is going to be merged with the next one.
486 const ParagraphRun& lastParagraph = *( mModel->mLogicalModel->mParagraphInfo.Begin() + lastParagraphIndex + 1u );
488 numberOfCharacters = lastParagraph.characterRun.characterIndex + lastParagraph.characterRun.numberOfCharacters - mTextUpdateInfo.mParagraphCharacterIndex;
492 numberOfCharacters = lastParagraph.characterRun.characterIndex + lastParagraph.characterRun.numberOfCharacters - mTextUpdateInfo.mParagraphCharacterIndex;
496 mTextUpdateInfo.mRequestedNumberOfCharacters = numberOfCharacters + mTextUpdateInfo.mNumberOfCharactersToAdd - mTextUpdateInfo.mNumberOfCharactersToRemove;
497 mTextUpdateInfo.mStartGlyphIndex = *( mModel->mVisualModel->mCharactersToGlyph.Begin() + mTextUpdateInfo.mParagraphCharacterIndex );
500 void Controller::Impl::ClearFullModelData( OperationsMask operations )
502 if( NO_OPERATION != ( GET_LINE_BREAKS & operations ) )
504 mModel->mLogicalModel->mLineBreakInfo.Clear();
505 mModel->mLogicalModel->mParagraphInfo.Clear();
508 if( NO_OPERATION != ( GET_WORD_BREAKS & operations ) )
510 mModel->mLogicalModel->mLineBreakInfo.Clear();
513 if( NO_OPERATION != ( GET_SCRIPTS & operations ) )
515 mModel->mLogicalModel->mScriptRuns.Clear();
518 if( NO_OPERATION != ( VALIDATE_FONTS & operations ) )
520 mModel->mLogicalModel->mFontRuns.Clear();
523 if( 0u != mModel->mLogicalModel->mBidirectionalParagraphInfo.Count() )
525 if( NO_OPERATION != ( BIDI_INFO & operations ) )
527 mModel->mLogicalModel->mBidirectionalParagraphInfo.Clear();
528 mModel->mLogicalModel->mCharacterDirections.Clear();
531 if( NO_OPERATION != ( REORDER & operations ) )
533 // Free the allocated memory used to store the conversion table in the bidirectional line info run.
534 for( Vector<BidirectionalLineInfoRun>::Iterator it = mModel->mLogicalModel->mBidirectionalLineInfo.Begin(),
535 endIt = mModel->mLogicalModel->mBidirectionalLineInfo.End();
539 BidirectionalLineInfoRun& bidiLineInfo = *it;
541 free( bidiLineInfo.visualToLogicalMap );
542 bidiLineInfo.visualToLogicalMap = NULL;
544 mModel->mLogicalModel->mBidirectionalLineInfo.Clear();
548 if( NO_OPERATION != ( SHAPE_TEXT & operations ) )
550 mModel->mVisualModel->mGlyphs.Clear();
551 mModel->mVisualModel->mGlyphsToCharacters.Clear();
552 mModel->mVisualModel->mCharactersToGlyph.Clear();
553 mModel->mVisualModel->mCharactersPerGlyph.Clear();
554 mModel->mVisualModel->mGlyphsPerCharacter.Clear();
555 mModel->mVisualModel->mGlyphPositions.Clear();
558 if( NO_OPERATION != ( LAYOUT & operations ) )
560 mModel->mVisualModel->mLines.Clear();
563 if( NO_OPERATION != ( COLOR & operations ) )
565 mModel->mVisualModel->mColorIndices.Clear();
569 void Controller::Impl::ClearCharacterModelData( CharacterIndex startIndex, CharacterIndex endIndex, OperationsMask operations )
571 const CharacterIndex endIndexPlusOne = endIndex + 1u;
573 if( NO_OPERATION != ( GET_LINE_BREAKS & operations ) )
575 // Clear the line break info.
576 LineBreakInfo* lineBreakInfoBuffer = mModel->mLogicalModel->mLineBreakInfo.Begin();
578 mModel->mLogicalModel->mLineBreakInfo.Erase( lineBreakInfoBuffer + startIndex,
579 lineBreakInfoBuffer + endIndexPlusOne );
581 // Clear the paragraphs.
582 ClearCharacterRuns( startIndex,
584 mModel->mLogicalModel->mParagraphInfo );
587 if( NO_OPERATION != ( GET_WORD_BREAKS & operations ) )
589 // Clear the word break info.
590 WordBreakInfo* wordBreakInfoBuffer = mModel->mLogicalModel->mWordBreakInfo.Begin();
592 mModel->mLogicalModel->mWordBreakInfo.Erase( wordBreakInfoBuffer + startIndex,
593 wordBreakInfoBuffer + endIndexPlusOne );
596 if( NO_OPERATION != ( GET_SCRIPTS & operations ) )
598 // Clear the scripts.
599 ClearCharacterRuns( startIndex,
601 mModel->mLogicalModel->mScriptRuns );
604 if( NO_OPERATION != ( VALIDATE_FONTS & operations ) )
607 ClearCharacterRuns( startIndex,
609 mModel->mLogicalModel->mFontRuns );
612 if( 0u != mModel->mLogicalModel->mBidirectionalParagraphInfo.Count() )
614 if( NO_OPERATION != ( BIDI_INFO & operations ) )
616 // Clear the bidirectional paragraph info.
617 ClearCharacterRuns( startIndex,
619 mModel->mLogicalModel->mBidirectionalParagraphInfo );
621 // Clear the character's directions.
622 CharacterDirection* characterDirectionsBuffer = mModel->mLogicalModel->mCharacterDirections.Begin();
624 mModel->mLogicalModel->mCharacterDirections.Erase( characterDirectionsBuffer + startIndex,
625 characterDirectionsBuffer + endIndexPlusOne );
628 if( NO_OPERATION != ( REORDER & operations ) )
630 uint32_t startRemoveIndex = mModel->mLogicalModel->mBidirectionalLineInfo.Count();
631 uint32_t endRemoveIndex = startRemoveIndex;
632 ClearCharacterRuns( startIndex,
634 mModel->mLogicalModel->mBidirectionalLineInfo,
638 BidirectionalLineInfoRun* bidirectionalLineInfoBuffer = mModel->mLogicalModel->mBidirectionalLineInfo.Begin();
640 // Free the allocated memory used to store the conversion table in the bidirectional line info run.
641 for( Vector<BidirectionalLineInfoRun>::Iterator it = bidirectionalLineInfoBuffer + startRemoveIndex,
642 endIt = bidirectionalLineInfoBuffer + endRemoveIndex;
646 BidirectionalLineInfoRun& bidiLineInfo = *it;
648 free( bidiLineInfo.visualToLogicalMap );
649 bidiLineInfo.visualToLogicalMap = NULL;
652 mModel->mLogicalModel->mBidirectionalLineInfo.Erase( bidirectionalLineInfoBuffer + startRemoveIndex,
653 bidirectionalLineInfoBuffer + endRemoveIndex );
658 void Controller::Impl::ClearGlyphModelData( CharacterIndex startIndex, CharacterIndex endIndex, OperationsMask operations )
660 const CharacterIndex endIndexPlusOne = endIndex + 1u;
661 const Length numberOfCharactersRemoved = endIndexPlusOne - startIndex;
663 // Convert the character index to glyph index before deleting the character to glyph and the glyphs per character buffers.
664 GlyphIndex* charactersToGlyphBuffer = mModel->mVisualModel->mCharactersToGlyph.Begin();
665 Length* glyphsPerCharacterBuffer = mModel->mVisualModel->mGlyphsPerCharacter.Begin();
667 const GlyphIndex endGlyphIndexPlusOne = *( charactersToGlyphBuffer + endIndex ) + *( glyphsPerCharacterBuffer + endIndex );
668 const Length numberOfGlyphsRemoved = endGlyphIndexPlusOne - mTextUpdateInfo.mStartGlyphIndex;
670 if( NO_OPERATION != ( SHAPE_TEXT & operations ) )
672 // Update the character to glyph indices.
673 for( Vector<GlyphIndex>::Iterator it = charactersToGlyphBuffer + endIndexPlusOne,
674 endIt = charactersToGlyphBuffer + mModel->mVisualModel->mCharactersToGlyph.Count();
678 CharacterIndex& index = *it;
679 index -= numberOfGlyphsRemoved;
682 // Clear the character to glyph conversion table.
683 mModel->mVisualModel->mCharactersToGlyph.Erase( charactersToGlyphBuffer + startIndex,
684 charactersToGlyphBuffer + endIndexPlusOne );
686 // Clear the glyphs per character table.
687 mModel->mVisualModel->mGlyphsPerCharacter.Erase( glyphsPerCharacterBuffer + startIndex,
688 glyphsPerCharacterBuffer + endIndexPlusOne );
690 // Clear the glyphs buffer.
691 GlyphInfo* glyphsBuffer = mModel->mVisualModel->mGlyphs.Begin();
692 mModel->mVisualModel->mGlyphs.Erase( glyphsBuffer + mTextUpdateInfo.mStartGlyphIndex,
693 glyphsBuffer + endGlyphIndexPlusOne );
695 CharacterIndex* glyphsToCharactersBuffer = mModel->mVisualModel->mGlyphsToCharacters.Begin();
697 // Update the glyph to character indices.
698 for( Vector<CharacterIndex>::Iterator it = glyphsToCharactersBuffer + endGlyphIndexPlusOne,
699 endIt = glyphsToCharactersBuffer + mModel->mVisualModel->mGlyphsToCharacters.Count();
703 CharacterIndex& index = *it;
704 index -= numberOfCharactersRemoved;
707 // Clear the glyphs to characters buffer.
708 mModel->mVisualModel->mGlyphsToCharacters.Erase( glyphsToCharactersBuffer + mTextUpdateInfo.mStartGlyphIndex,
709 glyphsToCharactersBuffer + endGlyphIndexPlusOne );
711 // Clear the characters per glyph buffer.
712 Length* charactersPerGlyphBuffer = mModel->mVisualModel->mCharactersPerGlyph.Begin();
713 mModel->mVisualModel->mCharactersPerGlyph.Erase( charactersPerGlyphBuffer + mTextUpdateInfo.mStartGlyphIndex,
714 charactersPerGlyphBuffer + endGlyphIndexPlusOne );
716 // Clear the positions buffer.
717 Vector2* positionsBuffer = mModel->mVisualModel->mGlyphPositions.Begin();
718 mModel->mVisualModel->mGlyphPositions.Erase( positionsBuffer + mTextUpdateInfo.mStartGlyphIndex,
719 positionsBuffer + endGlyphIndexPlusOne );
722 if( NO_OPERATION != ( LAYOUT & operations ) )
725 uint32_t startRemoveIndex = mModel->mVisualModel->mLines.Count();
726 uint32_t endRemoveIndex = startRemoveIndex;
727 ClearCharacterRuns( startIndex,
729 mModel->mVisualModel->mLines,
733 // Will update the glyph runs.
734 startRemoveIndex = mModel->mVisualModel->mLines.Count();
735 endRemoveIndex = startRemoveIndex;
736 ClearGlyphRuns( mTextUpdateInfo.mStartGlyphIndex,
737 endGlyphIndexPlusOne - 1u,
738 mModel->mVisualModel->mLines,
742 // Set the line index from where to insert the new laid-out lines.
743 mTextUpdateInfo.mStartLineIndex = startRemoveIndex;
745 LineRun* linesBuffer = mModel->mVisualModel->mLines.Begin();
746 mModel->mVisualModel->mLines.Erase( linesBuffer + startRemoveIndex,
747 linesBuffer + endRemoveIndex );
750 if( NO_OPERATION != ( COLOR & operations ) )
752 if( 0u != mModel->mVisualModel->mColorIndices.Count() )
754 ColorIndex* colorIndexBuffer = mModel->mVisualModel->mColorIndices.Begin();
755 mModel->mVisualModel->mColorIndices.Erase( colorIndexBuffer + mTextUpdateInfo.mStartGlyphIndex,
756 colorIndexBuffer + endGlyphIndexPlusOne );
761 void Controller::Impl::ClearModelData( CharacterIndex startIndex, CharacterIndex endIndex, OperationsMask operations )
763 if( mTextUpdateInfo.mClearAll ||
764 ( ( 0u == startIndex ) &&
765 ( mTextUpdateInfo.mPreviousNumberOfCharacters == endIndex + 1u ) ) )
767 ClearFullModelData( operations );
771 // Clear the model data related with characters.
772 ClearCharacterModelData( startIndex, endIndex, operations );
774 // Clear the model data related with glyphs.
775 ClearGlyphModelData( startIndex, endIndex, operations );
778 // The estimated number of lines. Used to avoid reallocations when layouting.
779 mTextUpdateInfo.mEstimatedNumberOfLines = std::max( mModel->mVisualModel->mLines.Count(), mModel->mLogicalModel->mParagraphInfo.Count() );
781 mModel->mVisualModel->ClearCaches();
784 bool Controller::Impl::UpdateModel( OperationsMask operationsRequired )
786 DALI_LOG_INFO( gLogFilter, Debug::General, "Controller::UpdateModel\n" );
788 // Calculate the operations to be done.
789 const OperationsMask operations = static_cast<OperationsMask>( mOperationsPending & operationsRequired );
791 if( NO_OPERATION == operations )
793 // Nothing to do if no operations are pending and required.
797 Vector<Character>& srcCharacters = mModel->mLogicalModel->mText;
798 Vector<Character> displayCharacters;
799 bool useHiddenText = false;
800 if ( mHiddenInput && mEventData != NULL && !mEventData->mIsShowingPlaceholderText)
802 mHiddenInput->Substitute( srcCharacters,displayCharacters );
803 useHiddenText = true;
806 Vector<Character>& utf32Characters = useHiddenText ? displayCharacters : srcCharacters;
807 const Length numberOfCharacters = utf32Characters.Count();
809 // Index to the first character of the first paragraph to be updated.
810 CharacterIndex startIndex = 0u;
811 // Number of characters of the paragraphs to be removed.
812 Length paragraphCharacters = 0u;
814 CalculateTextUpdateIndices( paragraphCharacters );
816 // Check whether the indices for updating the text is valid
817 if ( numberOfCharacters > 0u &&
818 ( mTextUpdateInfo.mParagraphCharacterIndex >= numberOfCharacters ||
819 mTextUpdateInfo.mRequestedNumberOfCharacters > numberOfCharacters ) )
821 std::string currentText;
822 Utf32ToUtf8( mModel->mLogicalModel->mText.Begin(), numberOfCharacters, currentText );
824 DALI_LOG_ERROR( "Controller::Impl::UpdateModel: mTextUpdateInfo has invalid indices\n" );
825 DALI_LOG_ERROR( "Number of characters: %d, current text is: %s\n", numberOfCharacters, currentText.c_str() );
827 // Dump mTextUpdateInfo
828 DALI_LOG_ERROR( "Dump mTextUpdateInfo:\n" );
829 DALI_LOG_ERROR( " mTextUpdateInfo.mCharacterIndex = %u\n", mTextUpdateInfo.mCharacterIndex );
830 DALI_LOG_ERROR( " mTextUpdateInfo.mNumberOfCharactersToRemove = %u\n", mTextUpdateInfo.mNumberOfCharactersToRemove );
831 DALI_LOG_ERROR( " mTextUpdateInfo.mNumberOfCharactersToAdd = %u\n", mTextUpdateInfo.mNumberOfCharactersToAdd );
832 DALI_LOG_ERROR( " mTextUpdateInfo.mPreviousNumberOfCharacters = %u\n", mTextUpdateInfo.mPreviousNumberOfCharacters );
833 DALI_LOG_ERROR( " mTextUpdateInfo.mParagraphCharacterIndex = %u\n", mTextUpdateInfo.mParagraphCharacterIndex );
834 DALI_LOG_ERROR( " mTextUpdateInfo.mRequestedNumberOfCharacters = %u\n", mTextUpdateInfo.mRequestedNumberOfCharacters );
835 DALI_LOG_ERROR( " mTextUpdateInfo.mStartGlyphIndex = %u\n", mTextUpdateInfo.mStartGlyphIndex );
836 DALI_LOG_ERROR( " mTextUpdateInfo.mStartLineIndex = %u\n", mTextUpdateInfo.mStartLineIndex );
837 DALI_LOG_ERROR( " mTextUpdateInfo.mEstimatedNumberOfLines = %u\n", mTextUpdateInfo.mEstimatedNumberOfLines );
838 DALI_LOG_ERROR( " mTextUpdateInfo.mClearAll = %d\n", mTextUpdateInfo.mClearAll );
839 DALI_LOG_ERROR( " mTextUpdateInfo.mFullRelayoutNeeded = %d\n", mTextUpdateInfo.mFullRelayoutNeeded );
840 DALI_LOG_ERROR( " mTextUpdateInfo.mIsLastCharacterNewParagraph = %d\n", mTextUpdateInfo.mIsLastCharacterNewParagraph );
845 startIndex = mTextUpdateInfo.mParagraphCharacterIndex;
847 if( mTextUpdateInfo.mClearAll ||
848 ( 0u != paragraphCharacters ) )
850 ClearModelData( startIndex, startIndex + ( ( paragraphCharacters > 0u ) ? paragraphCharacters - 1u : 0u ), operations );
853 mTextUpdateInfo.mClearAll = false;
855 // Whether the model is updated.
856 bool updated = false;
858 Vector<LineBreakInfo>& lineBreakInfo = mModel->mLogicalModel->mLineBreakInfo;
859 const Length requestedNumberOfCharacters = mTextUpdateInfo.mRequestedNumberOfCharacters;
861 if( NO_OPERATION != ( GET_LINE_BREAKS & operations ) )
863 // Retrieves the line break info. The line break info is used to split the text in 'paragraphs' to
864 // calculate the bidirectional info for each 'paragraph'.
865 // It's also used to layout the text (where it should be a new line) or to shape the text (text in different lines
866 // is not shaped together).
867 lineBreakInfo.Resize( numberOfCharacters, TextAbstraction::LINE_NO_BREAK );
869 SetLineBreakInfo( utf32Characters,
871 requestedNumberOfCharacters,
874 // Create the paragraph info.
875 mModel->mLogicalModel->CreateParagraphInfo( startIndex,
876 requestedNumberOfCharacters );
880 Vector<WordBreakInfo>& wordBreakInfo = mModel->mLogicalModel->mWordBreakInfo;
881 if( NO_OPERATION != ( GET_WORD_BREAKS & operations ) )
883 // Retrieves the word break info. The word break info is used to layout the text (where to wrap the text in lines).
884 wordBreakInfo.Resize( numberOfCharacters, TextAbstraction::WORD_NO_BREAK );
886 SetWordBreakInfo( utf32Characters,
888 requestedNumberOfCharacters,
893 const bool getScripts = NO_OPERATION != ( GET_SCRIPTS & operations );
894 const bool validateFonts = NO_OPERATION != ( VALIDATE_FONTS & operations );
896 Vector<ScriptRun>& scripts = mModel->mLogicalModel->mScriptRuns;
897 Vector<FontRun>& validFonts = mModel->mLogicalModel->mFontRuns;
899 if( getScripts || validateFonts )
901 // Validates the fonts assigned by the application or assigns default ones.
902 // It makes sure all the characters are going to be rendered by the correct font.
903 MultilanguageSupport multilanguageSupport = MultilanguageSupport::Get();
907 // Retrieves the scripts used in the text.
908 multilanguageSupport.SetScripts( utf32Characters,
910 requestedNumberOfCharacters,
916 // Validate the fonts set through the mark-up string.
917 Vector<FontDescriptionRun>& fontDescriptionRuns = mModel->mLogicalModel->mFontDescriptionRuns;
919 // Get the default font's description.
920 TextAbstraction::FontDescription defaultFontDescription;
921 TextAbstraction::PointSize26Dot6 defaultPointSize = TextAbstraction::FontClient::DEFAULT_POINT_SIZE;
923 if( IsShowingPlaceholderText() && mEventData && ( NULL != mEventData->mPlaceholderFont ) )
925 // If the placeholder font is set specifically, only placeholder font is changed.
926 defaultFontDescription = mEventData->mPlaceholderFont->mFontDescription;
927 if( mEventData->mPlaceholderFont->sizeDefined )
929 defaultPointSize = mEventData->mPlaceholderFont->mDefaultPointSize * 64u;
932 else if( NULL != mFontDefaults )
934 // Set the normal font and the placeholder font.
935 defaultFontDescription = mFontDefaults->mFontDescription;
936 defaultPointSize = mFontDefaults->mDefaultPointSize * 64u;
939 // Validates the fonts. If there is a character with no assigned font it sets a default one.
940 // After this call, fonts are validated.
941 multilanguageSupport.ValidateFonts( utf32Characters,
944 defaultFontDescription,
947 requestedNumberOfCharacters,
953 Vector<Character> mirroredUtf32Characters;
954 bool textMirrored = false;
955 const Length numberOfParagraphs = mModel->mLogicalModel->mParagraphInfo.Count();
956 if( NO_OPERATION != ( BIDI_INFO & operations ) )
958 Vector<BidirectionalParagraphInfoRun>& bidirectionalInfo = mModel->mLogicalModel->mBidirectionalParagraphInfo;
959 bidirectionalInfo.Reserve( numberOfParagraphs );
961 // Calculates the bidirectional info for the whole paragraph if it contains right to left scripts.
962 SetBidirectionalInfo( utf32Characters,
966 requestedNumberOfCharacters,
969 if( 0u != bidirectionalInfo.Count() )
971 // Only set the character directions if there is right to left characters.
972 Vector<CharacterDirection>& directions = mModel->mLogicalModel->mCharacterDirections;
973 GetCharactersDirection( bidirectionalInfo,
976 requestedNumberOfCharacters,
979 // This paragraph has right to left text. Some characters may need to be mirrored.
980 // TODO: consider if the mirrored string can be stored as well.
982 textMirrored = GetMirroredText( utf32Characters,
986 requestedNumberOfCharacters,
987 mirroredUtf32Characters );
991 // There is no right to left characters. Clear the directions vector.
992 mModel->mLogicalModel->mCharacterDirections.Clear();
997 Vector<GlyphInfo>& glyphs = mModel->mVisualModel->mGlyphs;
998 Vector<CharacterIndex>& glyphsToCharactersMap = mModel->mVisualModel->mGlyphsToCharacters;
999 Vector<Length>& charactersPerGlyph = mModel->mVisualModel->mCharactersPerGlyph;
1000 Vector<GlyphIndex> newParagraphGlyphs;
1001 newParagraphGlyphs.Reserve( numberOfParagraphs );
1003 const Length currentNumberOfGlyphs = glyphs.Count();
1004 if( NO_OPERATION != ( SHAPE_TEXT & operations ) )
1006 const Vector<Character>& textToShape = textMirrored ? mirroredUtf32Characters : utf32Characters;
1008 ShapeText( textToShape,
1013 mTextUpdateInfo.mStartGlyphIndex,
1014 requestedNumberOfCharacters,
1016 glyphsToCharactersMap,
1018 newParagraphGlyphs );
1020 // Create the 'number of glyphs' per character and the glyph to character conversion tables.
1021 mModel->mVisualModel->CreateGlyphsPerCharacterTable( startIndex, mTextUpdateInfo.mStartGlyphIndex, requestedNumberOfCharacters );
1022 mModel->mVisualModel->CreateCharacterToGlyphTable( startIndex, mTextUpdateInfo.mStartGlyphIndex, requestedNumberOfCharacters );
1026 const Length numberOfGlyphs = glyphs.Count() - currentNumberOfGlyphs;
1028 if( NO_OPERATION != ( GET_GLYPH_METRICS & operations ) )
1030 GlyphInfo* glyphsBuffer = glyphs.Begin();
1031 mMetrics->GetGlyphMetrics( glyphsBuffer + mTextUpdateInfo.mStartGlyphIndex, numberOfGlyphs );
1033 // Update the width and advance of all new paragraph characters.
1034 for( Vector<GlyphIndex>::ConstIterator it = newParagraphGlyphs.Begin(), endIt = newParagraphGlyphs.End(); it != endIt; ++it )
1036 const GlyphIndex index = *it;
1037 GlyphInfo& glyph = *( glyphsBuffer + index );
1039 glyph.xBearing = 0.f;
1041 glyph.advance = 0.f;
1046 if( NO_OPERATION != ( COLOR & operations ) )
1048 // Set the color runs in glyphs.
1049 SetColorSegmentationInfo( mModel->mLogicalModel->mColorRuns,
1050 mModel->mVisualModel->mCharactersToGlyph,
1051 mModel->mVisualModel->mGlyphsPerCharacter,
1053 mTextUpdateInfo.mStartGlyphIndex,
1054 requestedNumberOfCharacters,
1055 mModel->mVisualModel->mColors,
1056 mModel->mVisualModel->mColorIndices );
1061 if( ( NULL != mEventData ) &&
1062 mEventData->mPreEditFlag &&
1063 ( 0u != mModel->mVisualModel->mCharactersToGlyph.Count() ) )
1065 // Add the underline for the pre-edit text.
1066 const GlyphIndex* const charactersToGlyphBuffer = mModel->mVisualModel->mCharactersToGlyph.Begin();
1067 const Length* const glyphsPerCharacterBuffer = mModel->mVisualModel->mGlyphsPerCharacter.Begin();
1069 const GlyphIndex glyphStart = *( charactersToGlyphBuffer + mEventData->mPreEditStartPosition );
1070 const CharacterIndex lastPreEditCharacter = mEventData->mPreEditStartPosition + ( ( mEventData->mPreEditLength > 0u ) ? mEventData->mPreEditLength - 1u : 0u );
1071 const Length numberOfGlyphsLastCharacter = *( glyphsPerCharacterBuffer + lastPreEditCharacter );
1072 const GlyphIndex glyphEnd = *( charactersToGlyphBuffer + lastPreEditCharacter ) + ( numberOfGlyphsLastCharacter > 1u ? numberOfGlyphsLastCharacter - 1u : 0u );
1074 GlyphRun underlineRun;
1075 underlineRun.glyphIndex = glyphStart;
1076 underlineRun.numberOfGlyphs = 1u + glyphEnd - glyphStart;
1078 // TODO: At the moment the underline runs are only for pre-edit.
1079 mModel->mVisualModel->mUnderlineRuns.PushBack( underlineRun );
1082 // The estimated number of lines. Used to avoid reallocations when layouting.
1083 mTextUpdateInfo.mEstimatedNumberOfLines = std::max( mModel->mVisualModel->mLines.Count(), mModel->mLogicalModel->mParagraphInfo.Count() );
1085 // Set the previous number of characters for the next time the text is updated.
1086 mTextUpdateInfo.mPreviousNumberOfCharacters = numberOfCharacters;
1091 void Controller::Impl::RetrieveDefaultInputStyle( InputStyle& inputStyle )
1093 // Sets the default text's color.
1094 inputStyle.textColor = mTextColor;
1095 inputStyle.isDefaultColor = true;
1097 inputStyle.familyName.clear();
1098 inputStyle.weight = TextAbstraction::FontWeight::NORMAL;
1099 inputStyle.width = TextAbstraction::FontWidth::NORMAL;
1100 inputStyle.slant = TextAbstraction::FontSlant::NORMAL;
1101 inputStyle.size = 0.f;
1103 inputStyle.lineSpacing = 0.f;
1105 inputStyle.underlineProperties.clear();
1106 inputStyle.shadowProperties.clear();
1107 inputStyle.embossProperties.clear();
1108 inputStyle.outlineProperties.clear();
1110 inputStyle.isFamilyDefined = false;
1111 inputStyle.isWeightDefined = false;
1112 inputStyle.isWidthDefined = false;
1113 inputStyle.isSlantDefined = false;
1114 inputStyle.isSizeDefined = false;
1116 inputStyle.isLineSpacingDefined = false;
1118 inputStyle.isUnderlineDefined = false;
1119 inputStyle.isShadowDefined = false;
1120 inputStyle.isEmbossDefined = false;
1121 inputStyle.isOutlineDefined = false;
1123 // Sets the default font's family name, weight, width, slant and size.
1126 if( mFontDefaults->familyDefined )
1128 inputStyle.familyName = mFontDefaults->mFontDescription.family;
1129 inputStyle.isFamilyDefined = true;
1132 if( mFontDefaults->weightDefined )
1134 inputStyle.weight = mFontDefaults->mFontDescription.weight;
1135 inputStyle.isWeightDefined = true;
1138 if( mFontDefaults->widthDefined )
1140 inputStyle.width = mFontDefaults->mFontDescription.width;
1141 inputStyle.isWidthDefined = true;
1144 if( mFontDefaults->slantDefined )
1146 inputStyle.slant = mFontDefaults->mFontDescription.slant;
1147 inputStyle.isSlantDefined = true;
1150 if( mFontDefaults->sizeDefined )
1152 inputStyle.size = mFontDefaults->mDefaultPointSize;
1153 inputStyle.isSizeDefined = true;
1158 float Controller::Impl::GetDefaultFontLineHeight()
1160 FontId defaultFontId = 0u;
1161 if( NULL == mFontDefaults )
1163 TextAbstraction::FontDescription fontDescription;
1164 defaultFontId = mFontClient.GetFontId( fontDescription );
1168 defaultFontId = mFontDefaults->GetFontId( mFontClient );
1171 Text::FontMetrics fontMetrics;
1172 mMetrics->GetFontMetrics( defaultFontId, fontMetrics );
1174 return( fontMetrics.ascender - fontMetrics.descender );
1177 void Controller::Impl::OnCursorKeyEvent( const Event& event )
1179 if( NULL == mEventData || !IsShowingRealText() )
1181 // Nothing to do if there is no text input.
1185 int keyCode = event.p1.mInt;
1186 bool isShiftModifier = event.p2.mBool;
1188 CharacterIndex previousPrimaryCursorPosition = mEventData->mPrimaryCursorPosition;
1190 if( Dali::DALI_KEY_CURSOR_LEFT == keyCode )
1192 if( mEventData->mPrimaryCursorPosition > 0u )
1194 if ( !isShiftModifier && mEventData->mDecorator->IsHighlightVisible() )
1196 mEventData->mPrimaryCursorPosition = std::min(mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition);
1200 mEventData->mPrimaryCursorPosition = CalculateNewCursorIndex( mEventData->mPrimaryCursorPosition - 1u );
1204 else if( Dali::DALI_KEY_CURSOR_RIGHT == keyCode )
1206 if( mModel->mLogicalModel->mText.Count() > mEventData->mPrimaryCursorPosition )
1208 if ( !isShiftModifier && mEventData->mDecorator->IsHighlightVisible() )
1210 mEventData->mPrimaryCursorPosition = std::max(mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition);
1214 mEventData->mPrimaryCursorPosition = CalculateNewCursorIndex( mEventData->mPrimaryCursorPosition );
1218 else if( Dali::DALI_KEY_CURSOR_UP == keyCode && !isShiftModifier )
1220 // Ignore Shift-Up for text selection for now.
1222 // Get first the line index of the current cursor position index.
1223 CharacterIndex characterIndex = 0u;
1225 if( mEventData->mPrimaryCursorPosition > 0u )
1227 characterIndex = mEventData->mPrimaryCursorPosition - 1u;
1230 const LineIndex lineIndex = mModel->mVisualModel->GetLineOfCharacter( characterIndex );
1231 const LineIndex previousLineIndex = ( lineIndex > 0 ? lineIndex - 1u : lineIndex );
1233 // Retrieve the cursor position info.
1234 CursorInfo cursorInfo;
1235 GetCursorPosition( mEventData->mPrimaryCursorPosition,
1238 // Get the line above.
1239 const LineRun& line = *( mModel->mVisualModel->mLines.Begin() + previousLineIndex );
1241 // Get the next hit 'y' point.
1242 const float hitPointY = cursorInfo.lineOffset - 0.5f * ( line.ascender - line.descender );
1244 // Use the cursor hook position 'x' and the next hit 'y' position to calculate the new cursor index.
1245 bool matchedCharacter = false;
1246 mEventData->mPrimaryCursorPosition = Text::GetClosestCursorIndex( mModel->mVisualModel,
1247 mModel->mLogicalModel,
1249 mEventData->mCursorHookPositionX,
1251 CharacterHitTest::TAP,
1254 else if( Dali::DALI_KEY_CURSOR_DOWN == keyCode && !isShiftModifier )
1256 // Ignore Shift-Down for text selection for now.
1258 // Get first the line index of the current cursor position index.
1259 CharacterIndex characterIndex = 0u;
1261 if( mEventData->mPrimaryCursorPosition > 0u )
1263 characterIndex = mEventData->mPrimaryCursorPosition - 1u;
1266 const LineIndex lineIndex = mModel->mVisualModel->GetLineOfCharacter( characterIndex );
1268 if( lineIndex + 1u < mModel->mVisualModel->mLines.Count() )
1270 // Retrieve the cursor position info.
1271 CursorInfo cursorInfo;
1272 GetCursorPosition( mEventData->mPrimaryCursorPosition,
1275 // Get the line below.
1276 const LineRun& line = *( mModel->mVisualModel->mLines.Begin() + lineIndex + 1u );
1278 // Get the next hit 'y' point.
1279 const float hitPointY = cursorInfo.lineOffset + cursorInfo.lineHeight + 0.5f * ( line.ascender - line.descender );
1281 // Use the cursor hook position 'x' and the next hit 'y' position to calculate the new cursor index.
1282 bool matchedCharacter = false;
1283 mEventData->mPrimaryCursorPosition = Text::GetClosestCursorIndex( mModel->mVisualModel,
1284 mModel->mLogicalModel,
1286 mEventData->mCursorHookPositionX,
1288 CharacterHitTest::TAP,
1293 if ( !isShiftModifier && mEventData->mState != EventData::SELECTING )
1295 // Update selection position after moving the cursor
1296 mEventData->mLeftSelectionPosition = mEventData->mPrimaryCursorPosition;
1297 mEventData->mRightSelectionPosition = mEventData->mPrimaryCursorPosition;
1300 if ( isShiftModifier && IsShowingRealText() && mEventData->mShiftSelectionFlag )
1302 // Handle text selection
1303 bool selecting = false;
1305 if ( Dali::DALI_KEY_CURSOR_LEFT == keyCode || Dali::DALI_KEY_CURSOR_RIGHT == keyCode )
1307 // Shift-Left/Right to select the text
1308 int cursorPositionDelta = mEventData->mPrimaryCursorPosition - previousPrimaryCursorPosition;
1309 if ( cursorPositionDelta > 0 || mEventData->mRightSelectionPosition > 0u ) // Check the boundary
1311 mEventData->mRightSelectionPosition += cursorPositionDelta;
1315 else if ( mEventData->mLeftSelectionPosition != mEventData->mRightSelectionPosition )
1317 // Show no grab handles and text highlight if Shift-Up/Down pressed but no selected text
1323 // Notify the cursor position to the imf manager.
1324 if( mEventData->mImfManager )
1326 mEventData->mImfManager.SetCursorPosition( mEventData->mPrimaryCursorPosition );
1327 mEventData->mImfManager.NotifyCursorPosition();
1330 ChangeState( EventData::SELECTING );
1332 mEventData->mUpdateLeftSelectionPosition = true;
1333 mEventData->mUpdateRightSelectionPosition = true;
1334 mEventData->mUpdateGrabHandlePosition = true;
1335 mEventData->mUpdateHighlightBox = true;
1337 // Hide the text selection popup if select the text using keyboard instead of moving grab handles
1338 if( mEventData->mGrabHandlePopupEnabled )
1340 mEventData->mDecorator->SetPopupActive( false );
1346 // Handle normal cursor move
1347 ChangeState( EventData::EDITING );
1348 mEventData->mUpdateCursorPosition = true;
1351 mEventData->mUpdateInputStyle = true;
1352 mEventData->mScrollAfterUpdatePosition = true;
1355 void Controller::Impl::OnTapEvent( const Event& event )
1357 if( NULL != mEventData )
1359 const unsigned int tapCount = event.p1.mUint;
1361 if( 1u == tapCount )
1363 if( IsShowingRealText() )
1365 // Convert from control's coords to text's coords.
1366 const float xPosition = event.p2.mFloat - mModel->mScrollPosition.x;
1367 const float yPosition = event.p3.mFloat - mModel->mScrollPosition.y;
1369 // Keep the tap 'x' position. Used to move the cursor.
1370 mEventData->mCursorHookPositionX = xPosition;
1372 // Whether to touch point hits on a glyph.
1373 bool matchedCharacter = false;
1374 mEventData->mPrimaryCursorPosition = Text::GetClosestCursorIndex( mModel->mVisualModel,
1375 mModel->mLogicalModel,
1379 CharacterHitTest::TAP,
1382 // When the cursor position is changing, delay cursor blinking
1383 mEventData->mDecorator->DelayCursorBlink();
1387 mEventData->mPrimaryCursorPosition = 0u;
1390 // Update selection position after tapping
1391 mEventData->mLeftSelectionPosition = mEventData->mPrimaryCursorPosition;
1392 mEventData->mRightSelectionPosition = mEventData->mPrimaryCursorPosition;
1394 mEventData->mUpdateCursorPosition = true;
1395 mEventData->mUpdateGrabHandlePosition = true;
1396 mEventData->mScrollAfterUpdatePosition = true;
1397 mEventData->mUpdateInputStyle = true;
1399 // Notify the cursor position to the imf manager.
1400 if( mEventData->mImfManager )
1402 mEventData->mImfManager.SetCursorPosition( mEventData->mPrimaryCursorPosition );
1403 mEventData->mImfManager.NotifyCursorPosition();
1406 else if( 2u == tapCount )
1408 if( mEventData->mSelectionEnabled )
1410 // Convert from control's coords to text's coords.
1411 const float xPosition = event.p2.mFloat - mModel->mScrollPosition.x;
1412 const float yPosition = event.p3.mFloat - mModel->mScrollPosition.y;
1414 // Calculates the logical position from the x,y coords.
1415 RepositionSelectionHandles( xPosition,
1417 mEventData->mDoubleTapAction );
1423 void Controller::Impl::OnPanEvent( const Event& event )
1425 if( NULL == mEventData )
1427 // Nothing to do if there is no text input.
1431 const bool isHorizontalScrollEnabled = mEventData->mDecorator->IsHorizontalScrollEnabled();
1432 const bool isVerticalScrollEnabled = mEventData->mDecorator->IsVerticalScrollEnabled();
1434 if( !isHorizontalScrollEnabled && !isVerticalScrollEnabled )
1436 // Nothing to do if scrolling is not enabled.
1440 const int state = event.p1.mInt;
1444 case Gesture::Started:
1446 // Will remove the cursor, handles or text's popup, ...
1447 ChangeState( EventData::TEXT_PANNING );
1450 case Gesture::Continuing:
1452 const Vector2& layoutSize = mModel->mVisualModel->GetLayoutSize();
1453 const Vector2 currentScroll = mModel->mScrollPosition;
1455 if( isHorizontalScrollEnabled )
1457 const float displacementX = event.p2.mFloat;
1458 mModel->mScrollPosition.x += displacementX;
1460 ClampHorizontalScroll( layoutSize );
1463 if( isVerticalScrollEnabled )
1465 const float displacementY = event.p3.mFloat;
1466 mModel->mScrollPosition.y += displacementY;
1468 ClampVerticalScroll( layoutSize );
1471 mEventData->mDecorator->UpdatePositions( mModel->mScrollPosition - currentScroll );
1474 case Gesture::Finished:
1475 case Gesture::Cancelled: // FALLTHROUGH
1477 // Will go back to the previous state to show the cursor, handles, the text's popup, ...
1478 ChangeState( mEventData->mPreviousState );
1486 void Controller::Impl::OnLongPressEvent( const Event& event )
1488 DALI_LOG_INFO( gLogFilter, Debug::General, "Controller::OnLongPressEvent\n" );
1490 if( !IsShowingRealText() && ( EventData::EDITING == mEventData->mState ) )
1492 ChangeState( EventData::EDITING_WITH_POPUP );
1493 mEventData->mDecoratorUpdated = true;
1494 mEventData->mUpdateInputStyle = true;
1498 if( mEventData->mSelectionEnabled )
1500 // Convert from control's coords to text's coords.
1501 const float xPosition = event.p2.mFloat - mModel->mScrollPosition.x;
1502 const float yPosition = event.p3.mFloat - mModel->mScrollPosition.y;
1504 // Calculates the logical position from the x,y coords.
1505 RepositionSelectionHandles( xPosition,
1507 mEventData->mLongPressAction );
1512 void Controller::Impl::OnHandleEvent( const Event& event )
1514 if( NULL == mEventData )
1516 // Nothing to do if there is no text input.
1520 const unsigned int state = event.p1.mUint;
1521 const bool handleStopScrolling = ( HANDLE_STOP_SCROLLING == state );
1522 const bool isSmoothHandlePanEnabled = mEventData->mDecorator->IsSmoothHandlePanEnabled();
1524 if( HANDLE_PRESSED == state )
1526 // Convert from decorator's coords to text's coords.
1527 const float xPosition = event.p2.mFloat - mModel->mScrollPosition.x;
1528 const float yPosition = event.p3.mFloat - mModel->mScrollPosition.y;
1530 // Need to calculate the handle's new position.
1531 bool matchedCharacter = false;
1532 const CharacterIndex handleNewPosition = Text::GetClosestCursorIndex( mModel->mVisualModel,
1533 mModel->mLogicalModel,
1537 CharacterHitTest::SCROLL,
1540 if( Event::GRAB_HANDLE_EVENT == event.type )
1542 ChangeState ( EventData::GRAB_HANDLE_PANNING );
1544 if( handleNewPosition != mEventData->mPrimaryCursorPosition )
1546 // Updates the cursor position if the handle's new position is different than the current one.
1547 mEventData->mUpdateCursorPosition = true;
1548 // Does not update the grab handle position if the smooth panning is enabled. (The decorator does it smooth).
1549 mEventData->mUpdateGrabHandlePosition = !isSmoothHandlePanEnabled;
1550 mEventData->mPrimaryCursorPosition = handleNewPosition;
1553 // 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.
1554 mEventData->mDecoratorUpdated = isSmoothHandlePanEnabled;
1556 else if( Event::LEFT_SELECTION_HANDLE_EVENT == event.type )
1558 ChangeState ( EventData::SELECTION_HANDLE_PANNING );
1560 if( ( handleNewPosition != mEventData->mLeftSelectionPosition ) &&
1561 ( handleNewPosition != mEventData->mRightSelectionPosition ) )
1563 // Updates the highlight box if the handle's new position is different than the current one.
1564 mEventData->mUpdateHighlightBox = true;
1565 // Does not update the selection handle position if the smooth panning is enabled. (The decorator does it smooth).
1566 mEventData->mUpdateLeftSelectionPosition = !isSmoothHandlePanEnabled;
1567 mEventData->mLeftSelectionPosition = handleNewPosition;
1570 // 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.
1571 mEventData->mDecoratorUpdated = isSmoothHandlePanEnabled;
1573 // Will define the order to scroll the text to match the handle position.
1574 mEventData->mIsLeftHandleSelected = true;
1575 mEventData->mIsRightHandleSelected = false;
1577 else if( Event::RIGHT_SELECTION_HANDLE_EVENT == event.type )
1579 ChangeState ( EventData::SELECTION_HANDLE_PANNING );
1581 if( ( handleNewPosition != mEventData->mRightSelectionPosition ) &&
1582 ( handleNewPosition != mEventData->mLeftSelectionPosition ) )
1584 // Updates the highlight box if the handle's new position is different than the current one.
1585 mEventData->mUpdateHighlightBox = true;
1586 // Does not update the selection handle position if the smooth panning is enabled. (The decorator does it smooth).
1587 mEventData->mUpdateRightSelectionPosition = !isSmoothHandlePanEnabled;
1588 mEventData->mRightSelectionPosition = handleNewPosition;
1591 // 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.
1592 mEventData->mDecoratorUpdated = isSmoothHandlePanEnabled;
1594 // Will define the order to scroll the text to match the handle position.
1595 mEventData->mIsLeftHandleSelected = false;
1596 mEventData->mIsRightHandleSelected = true;
1598 } // end ( HANDLE_PRESSED == state )
1599 else if( ( HANDLE_RELEASED == state ) ||
1600 handleStopScrolling )
1602 CharacterIndex handlePosition = 0u;
1603 if( handleStopScrolling || isSmoothHandlePanEnabled )
1605 // Convert from decorator's coords to text's coords.
1606 const float xPosition = event.p2.mFloat - mModel->mScrollPosition.x;
1607 const float yPosition = event.p3.mFloat - mModel->mScrollPosition.y;
1609 bool matchedCharacter = false;
1610 handlePosition = Text::GetClosestCursorIndex( mModel->mVisualModel,
1611 mModel->mLogicalModel,
1615 CharacterHitTest::SCROLL,
1619 if( Event::GRAB_HANDLE_EVENT == event.type )
1621 mEventData->mUpdateCursorPosition = true;
1622 mEventData->mUpdateGrabHandlePosition = true;
1623 mEventData->mUpdateInputStyle = true;
1625 if( !IsClipboardEmpty() )
1627 ChangeState( EventData::EDITING_WITH_PASTE_POPUP ); // Moving grabhandle will show Paste Popup
1630 if( handleStopScrolling || isSmoothHandlePanEnabled )
1632 mEventData->mScrollAfterUpdatePosition = true;
1633 mEventData->mPrimaryCursorPosition = handlePosition;
1636 else if( Event::LEFT_SELECTION_HANDLE_EVENT == event.type )
1638 ChangeState( EventData::SELECTING );
1640 mEventData->mUpdateHighlightBox = true;
1641 mEventData->mUpdateLeftSelectionPosition = true;
1642 mEventData->mUpdateRightSelectionPosition = true;
1644 if( handleStopScrolling || isSmoothHandlePanEnabled )
1646 mEventData->mScrollAfterUpdatePosition = true;
1648 if( ( handlePosition != mEventData->mRightSelectionPosition ) &&
1649 ( handlePosition != mEventData->mLeftSelectionPosition ) )
1651 mEventData->mLeftSelectionPosition = handlePosition;
1655 else if( Event::RIGHT_SELECTION_HANDLE_EVENT == event.type )
1657 ChangeState( EventData::SELECTING );
1659 mEventData->mUpdateHighlightBox = true;
1660 mEventData->mUpdateRightSelectionPosition = true;
1661 mEventData->mUpdateLeftSelectionPosition = true;
1663 if( handleStopScrolling || isSmoothHandlePanEnabled )
1665 mEventData->mScrollAfterUpdatePosition = true;
1666 if( ( handlePosition != mEventData->mRightSelectionPosition ) &&
1667 ( handlePosition != mEventData->mLeftSelectionPosition ) )
1669 mEventData->mRightSelectionPosition = handlePosition;
1674 mEventData->mDecoratorUpdated = true;
1675 } // end ( ( HANDLE_RELEASED == state ) || ( HANDLE_STOP_SCROLLING == state ) )
1676 else if( HANDLE_SCROLLING == state )
1678 const float xSpeed = event.p2.mFloat;
1679 const float ySpeed = event.p3.mFloat;
1680 const Vector2& layoutSize = mModel->mVisualModel->GetLayoutSize();
1681 const Vector2 currentScrollPosition = mModel->mScrollPosition;
1683 mModel->mScrollPosition.x += xSpeed;
1684 mModel->mScrollPosition.y += ySpeed;
1686 ClampHorizontalScroll( layoutSize );
1687 ClampVerticalScroll( layoutSize );
1689 bool endOfScroll = false;
1690 if( Vector2::ZERO == ( currentScrollPosition - mModel->mScrollPosition ) )
1692 // Notify the decorator there is no more text to scroll.
1693 // The decorator won't send more scroll events.
1694 mEventData->mDecorator->NotifyEndOfScroll();
1695 // Still need to set the position of the handle.
1699 // Set the position of the handle.
1700 const bool scrollRightDirection = xSpeed > 0.f;
1701 const bool scrollBottomDirection = ySpeed > 0.f;
1702 const bool leftSelectionHandleEvent = Event::LEFT_SELECTION_HANDLE_EVENT == event.type;
1703 const bool rightSelectionHandleEvent = Event::RIGHT_SELECTION_HANDLE_EVENT == event.type;
1705 if( Event::GRAB_HANDLE_EVENT == event.type )
1707 ChangeState( EventData::GRAB_HANDLE_PANNING );
1709 // Get the grab handle position in decorator coords.
1710 Vector2 position = mEventData->mDecorator->GetPosition( GRAB_HANDLE );
1712 if( mEventData->mDecorator->IsHorizontalScrollEnabled() )
1714 // Position the grag handle close to either the left or right edge.
1715 position.x = scrollRightDirection ? 0.f : mModel->mVisualModel->mControlSize.width;
1718 if( mEventData->mDecorator->IsVerticalScrollEnabled() )
1720 position.x = mEventData->mCursorHookPositionX;
1722 // Position the grag handle close to either the top or bottom edge.
1723 position.y = scrollBottomDirection ? 0.f : mModel->mVisualModel->mControlSize.height;
1726 // Get the new handle position.
1727 // The grab handle's position is in decorator's coords. Need to transforms to text's coords.
1728 bool matchedCharacter = false;
1729 const CharacterIndex handlePosition = Text::GetClosestCursorIndex( mModel->mVisualModel,
1730 mModel->mLogicalModel,
1732 position.x - mModel->mScrollPosition.x,
1733 position.y - mModel->mScrollPosition.y,
1734 CharacterHitTest::SCROLL,
1737 if( mEventData->mPrimaryCursorPosition != handlePosition )
1739 mEventData->mUpdateCursorPosition = true;
1740 mEventData->mUpdateGrabHandlePosition = !isSmoothHandlePanEnabled;
1741 mEventData->mScrollAfterUpdatePosition = true;
1742 mEventData->mPrimaryCursorPosition = handlePosition;
1744 mEventData->mUpdateInputStyle = mEventData->mUpdateCursorPosition;
1746 // Updates the decorator if the soft handle panning is enabled.
1747 mEventData->mDecoratorUpdated = isSmoothHandlePanEnabled;
1749 else if( leftSelectionHandleEvent || rightSelectionHandleEvent )
1751 ChangeState( EventData::SELECTION_HANDLE_PANNING );
1753 // Get the selection handle position in decorator coords.
1754 Vector2 position = mEventData->mDecorator->GetPosition( leftSelectionHandleEvent ? Text::LEFT_SELECTION_HANDLE : Text::RIGHT_SELECTION_HANDLE );
1756 if( mEventData->mDecorator->IsHorizontalScrollEnabled() )
1758 // Position the selection handle close to either the left or right edge.
1759 position.x = scrollRightDirection ? 0.f : mModel->mVisualModel->mControlSize.width;
1762 if( mEventData->mDecorator->IsVerticalScrollEnabled() )
1764 position.x = mEventData->mCursorHookPositionX;
1766 // Position the grag handle close to either the top or bottom edge.
1767 position.y = scrollBottomDirection ? 0.f : mModel->mVisualModel->mControlSize.height;
1770 // Get the new handle position.
1771 // The selection handle's position is in decorator's coords. Need to transform to text's coords.
1772 bool matchedCharacter = false;
1773 const CharacterIndex handlePosition = Text::GetClosestCursorIndex( mModel->mVisualModel,
1774 mModel->mLogicalModel,
1776 position.x - mModel->mScrollPosition.x,
1777 position.y - mModel->mScrollPosition.y,
1778 CharacterHitTest::SCROLL,
1781 if( leftSelectionHandleEvent )
1783 const bool differentHandles = ( mEventData->mLeftSelectionPosition != handlePosition ) && ( mEventData->mRightSelectionPosition != handlePosition );
1785 if( differentHandles || endOfScroll )
1787 mEventData->mUpdateHighlightBox = true;
1788 mEventData->mUpdateLeftSelectionPosition = !isSmoothHandlePanEnabled;
1789 mEventData->mUpdateRightSelectionPosition = isSmoothHandlePanEnabled;
1790 mEventData->mLeftSelectionPosition = handlePosition;
1795 const bool differentHandles = ( mEventData->mRightSelectionPosition != handlePosition ) && ( mEventData->mLeftSelectionPosition != handlePosition );
1796 if( differentHandles || endOfScroll )
1798 mEventData->mUpdateHighlightBox = true;
1799 mEventData->mUpdateRightSelectionPosition = !isSmoothHandlePanEnabled;
1800 mEventData->mUpdateLeftSelectionPosition = isSmoothHandlePanEnabled;
1801 mEventData->mRightSelectionPosition = handlePosition;
1805 if( mEventData->mUpdateLeftSelectionPosition || mEventData->mUpdateRightSelectionPosition )
1807 RepositionSelectionHandles();
1809 mEventData->mScrollAfterUpdatePosition = !isSmoothHandlePanEnabled;
1812 mEventData->mDecoratorUpdated = true;
1813 } // end ( HANDLE_SCROLLING == state )
1816 void Controller::Impl::OnSelectEvent( const Event& event )
1818 if( NULL == mEventData )
1820 // Nothing to do if there is no text.
1824 if( mEventData->mSelectionEnabled )
1826 // Convert from control's coords to text's coords.
1827 const float xPosition = event.p2.mFloat - mModel->mScrollPosition.x;
1828 const float yPosition = event.p3.mFloat - mModel->mScrollPosition.y;
1830 // Calculates the logical position from the x,y coords.
1831 RepositionSelectionHandles( xPosition,
1833 Controller::NoTextTap::HIGHLIGHT );
1837 void Controller::Impl::OnSelectAllEvent()
1839 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "OnSelectAllEvent mEventData->mSelectionEnabled%s \n", mEventData->mSelectionEnabled?"true":"false");
1841 if( NULL == mEventData )
1843 // Nothing to do if there is no text.
1847 if( mEventData->mSelectionEnabled )
1849 ChangeState( EventData::SELECTING );
1851 mEventData->mLeftSelectionPosition = 0u;
1852 mEventData->mRightSelectionPosition = mModel->mLogicalModel->mText.Count();
1854 mEventData->mScrollAfterUpdatePosition = true;
1855 mEventData->mUpdateLeftSelectionPosition = true;
1856 mEventData->mUpdateRightSelectionPosition = true;
1857 mEventData->mUpdateHighlightBox = true;
1861 void Controller::Impl::RetrieveSelection( std::string& selectedText, bool deleteAfterRetrieval )
1863 if( mEventData->mLeftSelectionPosition == mEventData->mRightSelectionPosition )
1865 // Nothing to select if handles are in the same place.
1866 selectedText.clear();
1870 const bool handlesCrossed = mEventData->mLeftSelectionPosition > mEventData->mRightSelectionPosition;
1872 //Get start and end position of selection
1873 const CharacterIndex startOfSelectedText = handlesCrossed ? mEventData->mRightSelectionPosition : mEventData->mLeftSelectionPosition;
1874 const Length lengthOfSelectedText = ( handlesCrossed ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition ) - startOfSelectedText;
1876 Vector<Character>& utf32Characters = mModel->mLogicalModel->mText;
1877 const Length numberOfCharacters = utf32Characters.Count();
1879 // Validate the start and end selection points
1880 if( ( startOfSelectedText + lengthOfSelectedText ) <= numberOfCharacters )
1882 //Get text as a UTF8 string
1883 Utf32ToUtf8( &utf32Characters[startOfSelectedText], lengthOfSelectedText, selectedText );
1885 if( deleteAfterRetrieval ) // Only delete text if copied successfully
1887 // Keep a copy of the current input style.
1888 InputStyle currentInputStyle;
1889 currentInputStyle.Copy( mEventData->mInputStyle );
1891 // Set as input style the style of the first deleted character.
1892 mModel->mLogicalModel->RetrieveStyle( startOfSelectedText, mEventData->mInputStyle );
1894 // Compare if the input style has changed.
1895 const bool hasInputStyleChanged = !currentInputStyle.Equal( mEventData->mInputStyle );
1897 if( hasInputStyleChanged )
1899 const InputStyle::Mask styleChangedMask = currentInputStyle.GetInputStyleChangeMask( mEventData->mInputStyle );
1900 // Queue the input style changed signal.
1901 mEventData->mInputStyleChangedQueue.PushBack( styleChangedMask );
1904 mModel->mLogicalModel->UpdateTextStyleRuns( startOfSelectedText, -static_cast<int>( lengthOfSelectedText ) );
1906 // Mark the paragraphs to be updated.
1907 if( Layout::Engine::SINGLE_LINE_BOX == mLayoutEngine.GetLayout() )
1909 mTextUpdateInfo.mCharacterIndex = 0;
1910 mTextUpdateInfo.mNumberOfCharactersToRemove = mTextUpdateInfo.mPreviousNumberOfCharacters;
1911 mTextUpdateInfo.mNumberOfCharactersToAdd = mTextUpdateInfo.mPreviousNumberOfCharacters - lengthOfSelectedText;
1912 mTextUpdateInfo.mClearAll = true;
1916 mTextUpdateInfo.mCharacterIndex = startOfSelectedText;
1917 mTextUpdateInfo.mNumberOfCharactersToRemove = lengthOfSelectedText;
1920 // Delete text between handles
1921 Vector<Character>::Iterator first = utf32Characters.Begin() + startOfSelectedText;
1922 Vector<Character>::Iterator last = first + lengthOfSelectedText;
1923 utf32Characters.Erase( first, last );
1925 // Will show the cursor at the first character of the selection.
1926 mEventData->mPrimaryCursorPosition = handlesCrossed ? mEventData->mRightSelectionPosition : mEventData->mLeftSelectionPosition;
1930 // Will show the cursor at the last character of the selection.
1931 mEventData->mPrimaryCursorPosition = handlesCrossed ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition;
1934 mEventData->mDecoratorUpdated = true;
1938 void Controller::Impl::ShowClipboard()
1942 mClipboard.ShowClipboard();
1946 void Controller::Impl::HideClipboard()
1948 if( mClipboard && mClipboardHideEnabled )
1950 mClipboard.HideClipboard();
1954 void Controller::Impl::SetClipboardHideEnable(bool enable)
1956 mClipboardHideEnabled = enable;
1959 bool Controller::Impl::CopyStringToClipboard( std::string& source )
1961 //Send string to clipboard
1962 return ( mClipboard && mClipboard.SetItem( source ) );
1965 void Controller::Impl::SendSelectionToClipboard( bool deleteAfterSending )
1967 std::string selectedText;
1968 RetrieveSelection( selectedText, deleteAfterSending );
1969 CopyStringToClipboard( selectedText );
1970 ChangeState( EventData::EDITING );
1973 void Controller::Impl::RequestGetTextFromClipboard()
1977 mClipboard.RequestItem();
1981 void Controller::Impl::RepositionSelectionHandles()
1983 CharacterIndex selectionStart = mEventData->mLeftSelectionPosition;
1984 CharacterIndex selectionEnd = mEventData->mRightSelectionPosition;
1986 if( selectionStart == selectionEnd )
1988 // Nothing to select if handles are in the same place.
1989 // So, deactive Highlight box.
1990 mEventData->mDecorator->SetHighlightActive( false );
1994 mEventData->mDecorator->ClearHighlights();
1996 const GlyphIndex* const charactersToGlyphBuffer = mModel->mVisualModel->mCharactersToGlyph.Begin();
1997 const Length* const glyphsPerCharacterBuffer = mModel->mVisualModel->mGlyphsPerCharacter.Begin();
1998 const GlyphInfo* const glyphsBuffer = mModel->mVisualModel->mGlyphs.Begin();
1999 const Vector2* const positionsBuffer = mModel->mVisualModel->mGlyphPositions.Begin();
2000 const Length* const charactersPerGlyphBuffer = mModel->mVisualModel->mCharactersPerGlyph.Begin();
2001 const CharacterIndex* const glyphToCharacterBuffer = mModel->mVisualModel->mGlyphsToCharacters.Begin();
2002 const CharacterDirection* const modelCharacterDirectionsBuffer = ( 0u != mModel->mLogicalModel->mCharacterDirections.Count() ) ? mModel->mLogicalModel->mCharacterDirections.Begin() : NULL;
2004 const bool isLastCharacter = selectionEnd >= mModel->mLogicalModel->mText.Count();
2005 const CharacterDirection startDirection = ( ( NULL == modelCharacterDirectionsBuffer ) ? false : *( modelCharacterDirectionsBuffer + selectionStart ) );
2006 const CharacterDirection endDirection = ( ( NULL == modelCharacterDirectionsBuffer ) ? false : *( modelCharacterDirectionsBuffer + ( selectionEnd - ( isLastCharacter ? 1u : 0u ) ) ) );
2008 // Swap the indices if the start is greater than the end.
2009 const bool indicesSwapped = selectionStart > selectionEnd;
2011 // Tell the decorator to flip the selection handles if needed.
2012 mEventData->mDecorator->SetSelectionHandleFlipState( indicesSwapped, startDirection, endDirection );
2014 if( indicesSwapped )
2016 std::swap( selectionStart, selectionEnd );
2019 // Get the indices to the first and last selected glyphs.
2020 const CharacterIndex selectionEndMinusOne = selectionEnd - 1u;
2021 const GlyphIndex glyphStart = *( charactersToGlyphBuffer + selectionStart );
2022 const Length numberOfGlyphs = *( glyphsPerCharacterBuffer + selectionEndMinusOne );
2023 const GlyphIndex glyphEnd = *( charactersToGlyphBuffer + selectionEndMinusOne ) + ( ( numberOfGlyphs > 0 ) ? numberOfGlyphs - 1u : 0u );
2025 // Get the lines where the glyphs are laid-out.
2026 const LineRun* lineRun = mModel->mVisualModel->mLines.Begin();
2028 LineIndex lineIndex = 0u;
2029 Length numberOfLines = 0u;
2030 mModel->mVisualModel->GetNumberOfLines( glyphStart,
2031 1u + glyphEnd - glyphStart,
2034 const LineIndex firstLineIndex = lineIndex;
2036 // Create the structure to store some selection box info.
2037 Vector<SelectionBoxInfo> selectionBoxLinesInfo;
2038 selectionBoxLinesInfo.Resize( numberOfLines );
2040 SelectionBoxInfo* selectionBoxInfo = selectionBoxLinesInfo.Begin();
2041 selectionBoxInfo->minX = MAX_FLOAT;
2042 selectionBoxInfo->maxX = MIN_FLOAT;
2044 // Keep the min and max 'x' position to calculate the size and position of the highlighed text.
2045 float minHighlightX = std::numeric_limits<float>::max();
2046 float maxHighlightX = std::numeric_limits<float>::min();
2048 Vector2 highLightPosition; // The highlight position in decorator's coords.
2050 // Retrieve the first line and get the line's vertical offset, the line's height and the index to the last glyph.
2052 // The line's vertical offset of all the lines before the line where the first glyph is laid-out.
2053 selectionBoxInfo->lineOffset = CalculateLineOffset( mModel->mVisualModel->mLines,
2056 // Transform to decorator's (control) coords.
2057 selectionBoxInfo->lineOffset += mModel->mScrollPosition.y;
2059 lineRun += firstLineIndex;
2061 // The line height is the addition of the line ascender and the line descender.
2062 // However, the line descender has a negative value, hence the subtraction.
2063 selectionBoxInfo->lineHeight = lineRun->ascender - lineRun->descender;
2065 GlyphIndex lastGlyphOfLine = lineRun->glyphRun.glyphIndex + lineRun->glyphRun.numberOfGlyphs - 1u;
2067 // Check if the first glyph is a ligature that must be broken like Latin ff, fi, or Arabic ï»», etc which needs special code.
2068 const Length numberOfCharactersStart = *( charactersPerGlyphBuffer + glyphStart );
2069 bool splitStartGlyph = ( numberOfCharactersStart > 1u ) && HasLigatureMustBreak( mModel->mLogicalModel->GetScript( selectionStart ) );
2071 // Check if the last glyph is a ligature that must be broken like Latin ff, fi, or Arabic ï»», etc which needs special code.
2072 const Length numberOfCharactersEnd = *( charactersPerGlyphBuffer + glyphEnd );
2073 bool splitEndGlyph = ( glyphStart != glyphEnd ) && ( numberOfCharactersEnd > 1u ) && HasLigatureMustBreak( mModel->mLogicalModel->GetScript( selectionEndMinusOne ) );
2075 // The number of quads of the selection box.
2076 const unsigned int numberOfQuads = 1u + ( glyphEnd - glyphStart ) + ( ( numberOfLines > 1u ) ? 2u * numberOfLines : 0u );
2077 mEventData->mDecorator->ResizeHighlightQuads( numberOfQuads );
2079 // Count the actual number of quads.
2080 unsigned int actualNumberOfQuads = 0u;
2083 // Traverse the glyphs.
2084 for( GlyphIndex index = glyphStart; index <= glyphEnd; ++index )
2086 const GlyphInfo& glyph = *( glyphsBuffer + index );
2087 const Vector2& position = *( positionsBuffer + index );
2089 if( splitStartGlyph )
2091 // 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.
2093 const float glyphAdvance = glyph.advance / static_cast<float>( numberOfCharactersStart );
2094 const CharacterIndex interGlyphIndex = selectionStart - *( glyphToCharacterBuffer + glyphStart );
2095 // Get the direction of the character.
2096 CharacterDirection isCurrentRightToLeft = false;
2097 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
2099 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + selectionStart );
2102 // The end point could be in the middle of the ligature.
2103 // Calculate the number of characters selected.
2104 const Length numberOfCharacters = ( glyphStart == glyphEnd ) ? ( selectionEnd - selectionStart ) : ( numberOfCharactersStart - interGlyphIndex );
2106 quad.x = lineRun->alignmentOffset + position.x - glyph.xBearing + mModel->mScrollPosition.x + glyphAdvance * static_cast<float>( isCurrentRightToLeft ? ( numberOfCharactersStart - interGlyphIndex - numberOfCharacters ) : interGlyphIndex );
2107 quad.y = selectionBoxInfo->lineOffset;
2108 quad.z = quad.x + static_cast<float>( numberOfCharacters ) * glyphAdvance;
2109 quad.w = selectionBoxInfo->lineOffset + selectionBoxInfo->lineHeight;
2111 // Store the min and max 'x' for each line.
2112 selectionBoxInfo->minX = std::min( selectionBoxInfo->minX, quad.x );
2113 selectionBoxInfo->maxX = std::max( selectionBoxInfo->maxX, quad.z );
2115 mEventData->mDecorator->AddHighlight( actualNumberOfQuads, quad );
2116 ++actualNumberOfQuads;
2118 splitStartGlyph = false;
2122 if( splitEndGlyph && ( index == glyphEnd ) )
2124 // 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.
2126 const float glyphAdvance = glyph.advance / static_cast<float>( numberOfCharactersEnd );
2127 const CharacterIndex interGlyphIndex = selectionEnd - *( glyphToCharacterBuffer + glyphEnd );
2128 // Get the direction of the character.
2129 CharacterDirection isCurrentRightToLeft = false;
2130 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
2132 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + selectionEnd );
2135 const Length numberOfCharacters = numberOfCharactersEnd - interGlyphIndex;
2137 quad.x = lineRun->alignmentOffset + position.x - glyph.xBearing + mModel->mScrollPosition.x + ( isCurrentRightToLeft ? ( glyphAdvance * static_cast<float>( numberOfCharacters ) ) : 0.f );
2138 quad.y = selectionBoxInfo->lineOffset;
2139 quad.z = quad.x + static_cast<float>( interGlyphIndex ) * glyphAdvance;
2140 quad.w = quad.y + selectionBoxInfo->lineHeight;
2142 // Store the min and max 'x' for each line.
2143 selectionBoxInfo->minX = std::min( selectionBoxInfo->minX, quad.x );
2144 selectionBoxInfo->maxX = std::max( selectionBoxInfo->maxX, quad.z );
2146 mEventData->mDecorator->AddHighlight( actualNumberOfQuads,
2148 ++actualNumberOfQuads;
2150 splitEndGlyph = false;
2154 quad.x = lineRun->alignmentOffset + position.x - glyph.xBearing + mModel->mScrollPosition.x;
2155 quad.y = selectionBoxInfo->lineOffset;
2156 quad.z = quad.x + glyph.advance;
2157 quad.w = quad.y + selectionBoxInfo->lineHeight;
2159 // Store the min and max 'x' for each line.
2160 selectionBoxInfo->minX = std::min( selectionBoxInfo->minX, quad.x );
2161 selectionBoxInfo->maxX = std::max( selectionBoxInfo->maxX, quad.z );
2163 mEventData->mDecorator->AddHighlight( actualNumberOfQuads,
2165 ++actualNumberOfQuads;
2167 // Whether to retrieve the next line.
2168 if( index == lastGlyphOfLine )
2171 if( lineIndex < firstLineIndex + numberOfLines )
2173 // Retrieve the next line.
2176 // Get the last glyph of the new line.
2177 lastGlyphOfLine = lineRun->glyphRun.glyphIndex + lineRun->glyphRun.numberOfGlyphs - 1u;
2179 // Keep the offset and height of the current selection box.
2180 const float currentLineOffset = selectionBoxInfo->lineOffset;
2181 const float currentLineHeight = selectionBoxInfo->lineHeight;
2183 // Get the selection box info for the next line.
2186 selectionBoxInfo->minX = MAX_FLOAT;
2187 selectionBoxInfo->maxX = MIN_FLOAT;
2189 // Update the line's vertical offset.
2190 selectionBoxInfo->lineOffset = currentLineOffset + currentLineHeight;
2192 // The line height is the addition of the line ascender and the line descender.
2193 // However, the line descender has a negative value, hence the subtraction.
2194 selectionBoxInfo->lineHeight = lineRun->ascender - lineRun->descender;
2199 // Traverses all the lines and updates the min and max 'x' positions and the total height.
2200 // The final width is calculated after 'boxifying' the selection.
2201 for( Vector<SelectionBoxInfo>::ConstIterator it = selectionBoxLinesInfo.Begin(),
2202 endIt = selectionBoxLinesInfo.End();
2206 const SelectionBoxInfo& info = *it;
2208 // Update the size of the highlighted text.
2209 highLightSize.height += info.lineHeight;
2210 minHighlightX = std::min( minHighlightX, info.minX );
2211 maxHighlightX = std::max( maxHighlightX, info.maxX );
2214 // Add extra geometry to 'boxify' the selection.
2216 if( 1u < numberOfLines )
2218 // Boxify the first line.
2219 lineRun = mModel->mVisualModel->mLines.Begin() + firstLineIndex;
2220 const SelectionBoxInfo& firstSelectionBoxLineInfo = *( selectionBoxLinesInfo.Begin() );
2222 bool boxifyBegin = ( LTR != lineRun->direction ) && ( LTR != startDirection );
2223 bool boxifyEnd = ( LTR == lineRun->direction ) && ( LTR == startDirection );
2228 quad.y = firstSelectionBoxLineInfo.lineOffset;
2229 quad.z = firstSelectionBoxLineInfo.minX;
2230 quad.w = firstSelectionBoxLineInfo.lineOffset + firstSelectionBoxLineInfo.lineHeight;
2232 // Boxify at the beginning of the line.
2233 mEventData->mDecorator->AddHighlight( actualNumberOfQuads,
2235 ++actualNumberOfQuads;
2237 // Update the size of the highlighted text.
2238 minHighlightX = 0.f;
2243 quad.x = firstSelectionBoxLineInfo.maxX;
2244 quad.y = firstSelectionBoxLineInfo.lineOffset;
2245 quad.z = mModel->mVisualModel->mControlSize.width;
2246 quad.w = firstSelectionBoxLineInfo.lineOffset + firstSelectionBoxLineInfo.lineHeight;
2248 // Boxify at the end of the line.
2249 mEventData->mDecorator->AddHighlight( actualNumberOfQuads,
2251 ++actualNumberOfQuads;
2253 // Update the size of the highlighted text.
2254 maxHighlightX = mModel->mVisualModel->mControlSize.width;
2257 // Boxify the central lines.
2258 if( 2u < numberOfLines )
2260 for( Vector<SelectionBoxInfo>::ConstIterator it = selectionBoxLinesInfo.Begin() + 1u,
2261 endIt = selectionBoxLinesInfo.End() - 1u;
2265 const SelectionBoxInfo& info = *it;
2268 quad.y = info.lineOffset;
2270 quad.w = info.lineOffset + info.lineHeight;
2272 mEventData->mDecorator->AddHighlight( actualNumberOfQuads,
2274 ++actualNumberOfQuads;
2277 quad.y = info.lineOffset;
2278 quad.z = mModel->mVisualModel->mControlSize.width;
2279 quad.w = info.lineOffset + info.lineHeight;
2281 mEventData->mDecorator->AddHighlight( actualNumberOfQuads,
2283 ++actualNumberOfQuads;
2286 // Update the size of the highlighted text.
2287 minHighlightX = 0.f;
2288 maxHighlightX = mModel->mVisualModel->mControlSize.width;
2291 // Boxify the last line.
2292 lineRun = mModel->mVisualModel->mLines.Begin() + firstLineIndex + numberOfLines - 1u;
2293 const SelectionBoxInfo& lastSelectionBoxLineInfo = *( selectionBoxLinesInfo.End() - 1u );
2295 boxifyBegin = ( LTR == lineRun->direction ) && ( LTR == endDirection );
2296 boxifyEnd = ( LTR != lineRun->direction ) && ( LTR != endDirection );
2301 quad.y = lastSelectionBoxLineInfo.lineOffset;
2302 quad.z = lastSelectionBoxLineInfo.minX;
2303 quad.w = lastSelectionBoxLineInfo.lineOffset + lastSelectionBoxLineInfo.lineHeight;
2305 // Boxify at the beginning of the line.
2306 mEventData->mDecorator->AddHighlight( actualNumberOfQuads,
2308 ++actualNumberOfQuads;
2310 // Update the size of the highlighted text.
2311 minHighlightX = 0.f;
2316 quad.x = lastSelectionBoxLineInfo.maxX;
2317 quad.y = lastSelectionBoxLineInfo.lineOffset;
2318 quad.z = mModel->mVisualModel->mControlSize.width;
2319 quad.w = lastSelectionBoxLineInfo.lineOffset + lastSelectionBoxLineInfo.lineHeight;
2321 // Boxify at the end of the line.
2322 mEventData->mDecorator->AddHighlight( actualNumberOfQuads,
2324 ++actualNumberOfQuads;
2326 // Update the size of the highlighted text.
2327 maxHighlightX = mModel->mVisualModel->mControlSize.width;
2331 // Set the actual number of quads.
2332 mEventData->mDecorator->ResizeHighlightQuads( actualNumberOfQuads );
2334 // Sets the highlight's size and position. In decorator's coords.
2335 // The highlight's height has been calculated above (before 'boxifying' the highlight).
2336 highLightSize.width = maxHighlightX - minHighlightX;
2338 highLightPosition.x = minHighlightX;
2339 const SelectionBoxInfo& firstSelectionBoxLineInfo = *( selectionBoxLinesInfo.Begin() );
2340 highLightPosition.y = firstSelectionBoxLineInfo.lineOffset;
2342 mEventData->mDecorator->SetHighLightBox( highLightPosition, highLightSize, static_cast<float>( mModel->GetOutlineWidth() ) );
2344 if( !mEventData->mDecorator->IsSmoothHandlePanEnabled() )
2346 CursorInfo primaryCursorInfo;
2347 GetCursorPosition( mEventData->mLeftSelectionPosition,
2348 primaryCursorInfo );
2350 const Vector2 primaryPosition = primaryCursorInfo.primaryPosition + mModel->mScrollPosition;
2352 mEventData->mDecorator->SetPosition( LEFT_SELECTION_HANDLE,
2354 primaryCursorInfo.lineOffset + mModel->mScrollPosition.y,
2355 primaryCursorInfo.lineHeight );
2357 CursorInfo secondaryCursorInfo;
2358 GetCursorPosition( mEventData->mRightSelectionPosition,
2359 secondaryCursorInfo );
2361 const Vector2 secondaryPosition = secondaryCursorInfo.primaryPosition + mModel->mScrollPosition;
2363 mEventData->mDecorator->SetPosition( RIGHT_SELECTION_HANDLE,
2364 secondaryPosition.x,
2365 secondaryCursorInfo.lineOffset + mModel->mScrollPosition.y,
2366 secondaryCursorInfo.lineHeight );
2369 // Set the flag to update the decorator.
2370 mEventData->mDecoratorUpdated = true;
2373 void Controller::Impl::RepositionSelectionHandles( float visualX, float visualY, Controller::NoTextTap::Action action )
2375 if( NULL == mEventData )
2377 // Nothing to do if there is no text input.
2381 if( IsShowingPlaceholderText() )
2383 // Nothing to do if there is the place-holder text.
2387 const Length numberOfGlyphs = mModel->mVisualModel->mGlyphs.Count();
2388 const Length numberOfLines = mModel->mVisualModel->mLines.Count();
2389 if( ( 0 == numberOfGlyphs ) ||
2390 ( 0 == numberOfLines ) )
2392 // Nothing to do if there is no text.
2396 // Find which word was selected
2397 CharacterIndex selectionStart( 0 );
2398 CharacterIndex selectionEnd( 0 );
2399 CharacterIndex noTextHitIndex( 0 );
2400 const bool characterHit = FindSelectionIndices( mModel->mVisualModel,
2401 mModel->mLogicalModel,
2408 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "%p selectionStart %d selectionEnd %d\n", this, selectionStart, selectionEnd );
2410 if( characterHit || ( Controller::NoTextTap::HIGHLIGHT == action ) )
2412 ChangeState( EventData::SELECTING );
2414 mEventData->mLeftSelectionPosition = selectionStart;
2415 mEventData->mRightSelectionPosition = selectionEnd;
2417 mEventData->mUpdateLeftSelectionPosition = true;
2418 mEventData->mUpdateRightSelectionPosition = true;
2419 mEventData->mUpdateHighlightBox = true;
2421 // It may happen an IMF commit event arrives before the selection event
2422 // if the IMF manager is in pre-edit state. The commit event will set the
2423 // mEventData->mUpdateCursorPosition flag to true. If it's not set back
2424 // to false, the highlight box won't be updated.
2425 mEventData->mUpdateCursorPosition = false;
2427 mEventData->mScrollAfterUpdatePosition = ( mEventData->mLeftSelectionPosition != mEventData->mRightSelectionPosition );
2429 // Cursor to be positioned at end of selection so if selection interrupted and edit mode restarted the cursor will be at end of selection
2430 mEventData->mPrimaryCursorPosition = std::max( mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition );
2432 else if( Controller::NoTextTap::SHOW_SELECTION_POPUP == action )
2434 // Nothing to select. i.e. a white space, out of bounds
2435 ChangeState( EventData::EDITING_WITH_POPUP );
2437 mEventData->mPrimaryCursorPosition = noTextHitIndex;
2439 mEventData->mUpdateCursorPosition = true;
2440 mEventData->mUpdateGrabHandlePosition = true;
2441 mEventData->mScrollAfterUpdatePosition = true;
2442 mEventData->mUpdateInputStyle = true;
2444 else if( Controller::NoTextTap::NO_ACTION == action )
2446 // Nothing to select. i.e. a white space, out of bounds
2447 mEventData->mPrimaryCursorPosition = noTextHitIndex;
2449 mEventData->mUpdateCursorPosition = true;
2450 mEventData->mUpdateGrabHandlePosition = true;
2451 mEventData->mScrollAfterUpdatePosition = true;
2452 mEventData->mUpdateInputStyle = true;
2456 void Controller::Impl::SetPopupButtons()
2459 * Sets the Popup buttons to be shown depending on State.
2461 * If SELECTING : CUT & COPY + ( PASTE & CLIPBOARD if content available to paste )
2463 * If EDITING_WITH_POPUP : SELECT & SELECT_ALL
2466 TextSelectionPopup::Buttons buttonsToShow = TextSelectionPopup::NONE;
2468 if( EventData::SELECTING == mEventData->mState )
2470 buttonsToShow = TextSelectionPopup::Buttons( TextSelectionPopup::CUT | TextSelectionPopup::COPY );
2472 if( !IsClipboardEmpty() )
2474 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
2475 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::CLIPBOARD ) );
2478 if( !mEventData->mAllTextSelected )
2480 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::SELECT_ALL ) );
2483 else if( EventData::EDITING_WITH_POPUP == mEventData->mState )
2485 if( mModel->mLogicalModel->mText.Count() && !IsShowingPlaceholderText() )
2487 buttonsToShow = TextSelectionPopup::Buttons( TextSelectionPopup::SELECT | TextSelectionPopup::SELECT_ALL );
2490 if( !IsClipboardEmpty() )
2492 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
2493 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::CLIPBOARD ) );
2496 else if( EventData::EDITING_WITH_PASTE_POPUP == mEventData->mState )
2498 if ( !IsClipboardEmpty() )
2500 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
2501 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::CLIPBOARD ) );
2505 mEventData->mDecorator->SetEnabledPopupButtons( buttonsToShow );
2508 void Controller::Impl::ChangeState( EventData::State newState )
2510 if( NULL == mEventData )
2512 // Nothing to do if there is no text input.
2516 DALI_LOG_INFO( gLogFilter, Debug::General, "ChangeState state:%d newstate:%d\n", mEventData->mState, newState );
2518 if( mEventData->mState != newState )
2520 mEventData->mPreviousState = mEventData->mState;
2521 mEventData->mState = newState;
2523 switch( mEventData->mState )
2525 case EventData::INACTIVE:
2527 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
2528 mEventData->mDecorator->StopCursorBlink();
2529 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
2530 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
2531 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
2532 mEventData->mDecorator->SetHighlightActive( false );
2533 mEventData->mDecorator->SetPopupActive( false );
2534 mEventData->mDecoratorUpdated = true;
2537 case EventData::INTERRUPTED:
2539 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
2540 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
2541 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
2542 mEventData->mDecorator->SetHighlightActive( false );
2543 mEventData->mDecorator->SetPopupActive( false );
2544 mEventData->mDecoratorUpdated = true;
2547 case EventData::SELECTING:
2549 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
2550 mEventData->mDecorator->StopCursorBlink();
2551 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
2552 if ( mEventData->mGrabHandleEnabled )
2554 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true );
2555 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true );
2557 mEventData->mDecorator->SetHighlightActive( true );
2558 if( mEventData->mGrabHandlePopupEnabled )
2561 mEventData->mDecorator->SetPopupActive( true );
2563 mEventData->mDecoratorUpdated = true;
2566 case EventData::EDITING:
2568 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
2569 if( mEventData->mCursorBlinkEnabled )
2571 mEventData->mDecorator->StartCursorBlink();
2573 // Grab handle is not shown until a tap is received whilst EDITING
2574 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
2575 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
2576 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
2577 mEventData->mDecorator->SetHighlightActive( false );
2578 if( mEventData->mGrabHandlePopupEnabled )
2580 mEventData->mDecorator->SetPopupActive( false );
2582 mEventData->mDecoratorUpdated = true;
2585 case EventData::EDITING_WITH_POPUP:
2587 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "EDITING_WITH_POPUP \n", newState );
2589 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
2590 if( mEventData->mCursorBlinkEnabled )
2592 mEventData->mDecorator->StartCursorBlink();
2594 if( mEventData->mSelectionEnabled )
2596 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
2597 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
2598 mEventData->mDecorator->SetHighlightActive( false );
2600 else if ( mEventData->mGrabHandleEnabled )
2602 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
2604 if( mEventData->mGrabHandlePopupEnabled )
2607 mEventData->mDecorator->SetPopupActive( true );
2609 mEventData->mDecoratorUpdated = true;
2612 case EventData::EDITING_WITH_GRAB_HANDLE:
2614 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "EDITING_WITH_GRAB_HANDLE \n", newState );
2616 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
2617 if( mEventData->mCursorBlinkEnabled )
2619 mEventData->mDecorator->StartCursorBlink();
2621 // Grab handle is not shown until a tap is received whilst EDITING
2622 if ( mEventData->mGrabHandleEnabled )
2624 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
2626 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
2627 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
2628 mEventData->mDecorator->SetHighlightActive( false );
2629 if( mEventData->mGrabHandlePopupEnabled )
2631 mEventData->mDecorator->SetPopupActive( false );
2633 mEventData->mDecoratorUpdated = true;
2636 case EventData::SELECTION_HANDLE_PANNING:
2638 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
2639 mEventData->mDecorator->StopCursorBlink();
2640 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
2641 if ( mEventData->mGrabHandleEnabled )
2643 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true );
2644 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true );
2646 mEventData->mDecorator->SetHighlightActive( true );
2647 if( mEventData->mGrabHandlePopupEnabled )
2649 mEventData->mDecorator->SetPopupActive( false );
2651 mEventData->mDecoratorUpdated = true;
2654 case EventData::GRAB_HANDLE_PANNING:
2656 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "GRAB_HANDLE_PANNING \n", newState );
2658 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
2659 if( mEventData->mCursorBlinkEnabled )
2661 mEventData->mDecorator->StartCursorBlink();
2663 if ( mEventData->mGrabHandleEnabled )
2665 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
2667 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
2668 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
2669 mEventData->mDecorator->SetHighlightActive( false );
2670 if( mEventData->mGrabHandlePopupEnabled )
2672 mEventData->mDecorator->SetPopupActive( false );
2674 mEventData->mDecoratorUpdated = true;
2677 case EventData::EDITING_WITH_PASTE_POPUP:
2679 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "EDITING_WITH_PASTE_POPUP \n", newState );
2681 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
2682 if( mEventData->mCursorBlinkEnabled )
2684 mEventData->mDecorator->StartCursorBlink();
2687 if ( mEventData->mGrabHandleEnabled )
2689 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
2691 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
2692 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
2693 mEventData->mDecorator->SetHighlightActive( false );
2695 if( mEventData->mGrabHandlePopupEnabled )
2698 mEventData->mDecorator->SetPopupActive( true );
2700 mEventData->mDecoratorUpdated = true;
2703 case EventData::TEXT_PANNING:
2705 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
2706 mEventData->mDecorator->StopCursorBlink();
2707 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
2708 if( mEventData->mDecorator->IsHandleActive( LEFT_SELECTION_HANDLE ) ||
2709 mEventData->mDecorator->IsHandleActive( RIGHT_SELECTION_HANDLE ) )
2711 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
2712 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
2713 mEventData->mDecorator->SetHighlightActive( true );
2716 if( mEventData->mGrabHandlePopupEnabled )
2718 mEventData->mDecorator->SetPopupActive( false );
2721 mEventData->mDecoratorUpdated = true;
2728 void Controller::Impl::GetCursorPosition( CharacterIndex logical,
2729 CursorInfo& cursorInfo )
2731 if( !IsShowingRealText() )
2733 // Do not want to use the place-holder text to set the cursor position.
2735 // Use the line's height of the font's family set to set the cursor's size.
2736 // If there is no font's family set, use the default font.
2737 // Use the current alignment to place the cursor at the beginning, center or end of the box.
2739 cursorInfo.lineOffset = 0.f;
2740 cursorInfo.lineHeight = GetDefaultFontLineHeight();
2741 cursorInfo.primaryCursorHeight = cursorInfo.lineHeight;
2743 switch( mModel->mHorizontalAlignment )
2745 case Text::HorizontalAlignment::BEGIN :
2747 cursorInfo.primaryPosition.x = 0.f;
2750 case Text::HorizontalAlignment::CENTER:
2752 cursorInfo.primaryPosition.x = floorf( 0.5f * mModel->mVisualModel->mControlSize.width );
2755 case Text::HorizontalAlignment::END:
2757 cursorInfo.primaryPosition.x = mModel->mVisualModel->mControlSize.width - mEventData->mDecorator->GetCursorWidth();
2762 // Nothing else to do.
2766 const bool isMultiLine = ( Layout::Engine::MULTI_LINE_BOX == mLayoutEngine.GetLayout() );
2767 GetCursorPositionParameters parameters;
2768 parameters.visualModel = mModel->mVisualModel;
2769 parameters.logicalModel = mModel->mLogicalModel;
2770 parameters.metrics = mMetrics;
2771 parameters.logical = logical;
2772 parameters.isMultiline = isMultiLine;
2774 Text::GetCursorPosition( parameters,
2777 // Adds Outline offset.
2778 const float outlineWidth = static_cast<float>( mModel->GetOutlineWidth() );
2779 cursorInfo.primaryPosition.x += outlineWidth;
2780 cursorInfo.primaryPosition.y += outlineWidth;
2781 cursorInfo.secondaryPosition.x += outlineWidth;
2782 cursorInfo.secondaryPosition.y += outlineWidth;
2786 // If the text is editable and multi-line, the cursor position after a white space shouldn't exceed the boundaries of the text control.
2788 // Note the white spaces laid-out at the end of the line might exceed the boundaries of the control.
2789 // The reason is a wrapped line must not start with a white space so they are laid-out at the end of the line.
2791 if( 0.f > cursorInfo.primaryPosition.x )
2793 cursorInfo.primaryPosition.x = 0.f;
2796 const float edgeWidth = mModel->mVisualModel->mControlSize.width - static_cast<float>( mEventData->mDecorator->GetCursorWidth() );
2797 if( cursorInfo.primaryPosition.x > edgeWidth )
2799 cursorInfo.primaryPosition.x = edgeWidth;
2804 CharacterIndex Controller::Impl::CalculateNewCursorIndex( CharacterIndex index ) const
2806 if( NULL == mEventData )
2808 // Nothing to do if there is no text input.
2812 CharacterIndex cursorIndex = mEventData->mPrimaryCursorPosition;
2814 const GlyphIndex* const charactersToGlyphBuffer = mModel->mVisualModel->mCharactersToGlyph.Begin();
2815 const Length* const charactersPerGlyphBuffer = mModel->mVisualModel->mCharactersPerGlyph.Begin();
2817 GlyphIndex glyphIndex = *( charactersToGlyphBuffer + index );
2818 Length numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
2820 if( numberOfCharacters > 1u )
2822 const Script script = mModel->mLogicalModel->GetScript( index );
2823 if( HasLigatureMustBreak( script ) )
2825 // Prevents to jump the whole Latin ligatures like fi, ff, or Arabic ï»», ...
2826 numberOfCharacters = 1u;
2831 while( 0u == numberOfCharacters )
2834 numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
2838 if( index < mEventData->mPrimaryCursorPosition )
2840 cursorIndex -= numberOfCharacters;
2844 cursorIndex += numberOfCharacters;
2847 // Will update the cursor hook position.
2848 mEventData->mUpdateCursorHookPosition = true;
2853 void Controller::Impl::UpdateCursorPosition( const CursorInfo& cursorInfo )
2855 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "-->Controller::UpdateCursorPosition %p\n", this );
2856 if( NULL == mEventData )
2858 // Nothing to do if there is no text input.
2859 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition no event data\n" );
2863 const Vector2 cursorPosition = cursorInfo.primaryPosition + mModel->mScrollPosition;
2865 mEventData->mDecorator->SetGlyphOffset( PRIMARY_CURSOR, cursorInfo.glyphOffset );
2867 // Sets the cursor position.
2868 mEventData->mDecorator->SetPosition( PRIMARY_CURSOR,
2871 cursorInfo.primaryCursorHeight,
2872 cursorInfo.lineHeight );
2873 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Primary cursor position: %f,%f\n", cursorPosition.x, cursorPosition.y );
2875 if( mEventData->mUpdateGrabHandlePosition )
2877 // Sets the grab handle position.
2878 mEventData->mDecorator->SetPosition( GRAB_HANDLE,
2880 cursorInfo.lineOffset + mModel->mScrollPosition.y,
2881 cursorInfo.lineHeight );
2884 if( cursorInfo.isSecondaryCursor )
2886 mEventData->mDecorator->SetPosition( SECONDARY_CURSOR,
2887 cursorInfo.secondaryPosition.x + mModel->mScrollPosition.x,
2888 cursorInfo.secondaryPosition.y + mModel->mScrollPosition.y,
2889 cursorInfo.secondaryCursorHeight,
2890 cursorInfo.lineHeight );
2891 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Secondary cursor position: %f,%f\n", cursorInfo.secondaryPosition.x + mModel->mScrollPosition.x, cursorInfo.secondaryPosition.y + mModel->mScrollPosition.y );
2894 // Set which cursors are active according the state.
2895 if( EventData::IsEditingState( mEventData->mState ) || ( EventData::GRAB_HANDLE_PANNING == mEventData->mState ) )
2897 if( cursorInfo.isSecondaryCursor )
2899 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_BOTH );
2903 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
2908 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
2911 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition\n" );
2914 void Controller::Impl::UpdateSelectionHandle( HandleType handleType,
2915 const CursorInfo& cursorInfo )
2917 if( ( LEFT_SELECTION_HANDLE != handleType ) &&
2918 ( RIGHT_SELECTION_HANDLE != handleType ) )
2923 const Vector2 cursorPosition = cursorInfo.primaryPosition + mModel->mScrollPosition;
2925 // Sets the handle's position.
2926 mEventData->mDecorator->SetPosition( handleType,
2928 cursorInfo.lineOffset + mModel->mScrollPosition.y,
2929 cursorInfo.lineHeight );
2931 // If selection handle at start of the text and other at end of the text then all text is selected.
2932 const CharacterIndex startOfSelection = std::min( mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition );
2933 const CharacterIndex endOfSelection = std::max ( mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition );
2934 mEventData->mAllTextSelected = ( startOfSelection == 0 ) && ( endOfSelection == mModel->mLogicalModel->mText.Count() );
2937 void Controller::Impl::ClampHorizontalScroll( const Vector2& layoutSize )
2939 // Clamp between -space & -alignment offset.
2941 if( layoutSize.width > mModel->mVisualModel->mControlSize.width )
2943 const float space = ( layoutSize.width - mModel->mVisualModel->mControlSize.width ) + mModel->mAlignmentOffset;
2944 mModel->mScrollPosition.x = ( mModel->mScrollPosition.x < -space ) ? -space : mModel->mScrollPosition.x;
2945 mModel->mScrollPosition.x = ( mModel->mScrollPosition.x > -mModel->mAlignmentOffset ) ? -mModel->mAlignmentOffset : mModel->mScrollPosition.x;
2947 mEventData->mDecoratorUpdated = true;
2951 mModel->mScrollPosition.x = 0.f;
2955 void Controller::Impl::ClampVerticalScroll( const Vector2& layoutSize )
2957 if( Layout::Engine::SINGLE_LINE_BOX == mLayoutEngine.GetLayout() )
2959 // Nothing to do if the text is single line.
2963 // Clamp between -space & 0.
2964 if( layoutSize.height > mModel->mVisualModel->mControlSize.height )
2966 const float space = ( layoutSize.height - mModel->mVisualModel->mControlSize.height );
2967 mModel->mScrollPosition.y = ( mModel->mScrollPosition.y < -space ) ? -space : mModel->mScrollPosition.y;
2968 mModel->mScrollPosition.y = ( mModel->mScrollPosition.y > 0.f ) ? 0.f : mModel->mScrollPosition.y;
2970 mEventData->mDecoratorUpdated = true;
2974 mModel->mScrollPosition.y = 0.f;
2978 void Controller::Impl::ScrollToMakePositionVisible( const Vector2& position, float lineHeight )
2980 const float cursorWidth = mEventData->mDecorator ? static_cast<float>( mEventData->mDecorator->GetCursorWidth() ) : 0.f;
2982 // position is in actor's coords.
2983 const float positionEndX = position.x + cursorWidth;
2984 const float positionEndY = position.y + lineHeight;
2986 // Transform the position to decorator coords.
2987 const float decoratorPositionBeginX = position.x + mModel->mScrollPosition.x;
2988 const float decoratorPositionEndX = positionEndX + mModel->mScrollPosition.x;
2990 const float decoratorPositionBeginY = position.y + mModel->mScrollPosition.y;
2991 const float decoratorPositionEndY = positionEndY + mModel->mScrollPosition.y;
2993 if( decoratorPositionBeginX < 0.f )
2995 mModel->mScrollPosition.x = -position.x;
2997 else if( decoratorPositionEndX > mModel->mVisualModel->mControlSize.width )
2999 mModel->mScrollPosition.x = mModel->mVisualModel->mControlSize.width - positionEndX;
3002 if( Layout::Engine::MULTI_LINE_BOX == mLayoutEngine.GetLayout() )
3004 if( decoratorPositionBeginY < 0.f )
3006 mModel->mScrollPosition.y = -position.y;
3008 else if( decoratorPositionEndY > mModel->mVisualModel->mControlSize.height )
3010 mModel->mScrollPosition.y = mModel->mVisualModel->mControlSize.height - positionEndY;
3015 void Controller::Impl::ScrollTextToMatchCursor( const CursorInfo& cursorInfo )
3017 // Get the current cursor position in decorator coords.
3018 const Vector2& currentCursorPosition = mEventData->mDecorator->GetPosition( PRIMARY_CURSOR );
3020 const LineIndex lineIndex = mModel->mVisualModel->GetLineOfCharacter( mEventData->mPrimaryCursorPosition );
3024 // Calculate the offset to match the cursor position before the character was deleted.
3025 mModel->mScrollPosition.x = currentCursorPosition.x - cursorInfo.primaryPosition.x;
3027 //If text control has more than two lines and current line index is not last, calculate scrollpositionY
3028 if( mModel->mVisualModel->mLines.Count() > 1u && lineIndex != mModel->mVisualModel->mLines.Count() -1u )
3030 const float currentCursorGlyphOffset = mEventData->mDecorator->GetGlyphOffset( PRIMARY_CURSOR );
3031 mModel->mScrollPosition.y = currentCursorPosition.y - cursorInfo.lineOffset - currentCursorGlyphOffset;
3035 ClampHorizontalScroll( mModel->mVisualModel->GetLayoutSize() );
3036 ClampVerticalScroll( mModel->mVisualModel->GetLayoutSize() );
3038 // Makes the new cursor position visible if needed.
3039 ScrollToMakePositionVisible( cursorInfo.primaryPosition, cursorInfo.lineHeight );
3042 void Controller::Impl::RequestRelayout()
3044 if( NULL != mControlInterface )
3046 mControlInterface->RequestTextRelayout();
3052 } // namespace Toolkit