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, InputMethodContext& inputMethodContext )
71 : mDecorator( decorator ),
72 mInputMethodContext( inputMethodContext ),
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 ),
114 mUpdateAlignment( false )
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 )
183 NotifyInputMethodContext();
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::NotifyInputMethodContext()
334 if( mEventData && mEventData->mInputMethodContext )
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->mInputMethodContext.SetCursorPosition( cursorPosition );
351 mEventData->mInputMethodContext.NotifyCursorPosition();
355 void Controller::Impl::NotifyInputMethodContextMultiLineStatus()
357 if ( mEventData && mEventData->mInputMethodContext )
359 Text::Layout::Engine::Type layout = mLayoutEngine.GetLayout();
360 mEventData->mInputMethodContext.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_SCRIPTS & operations ) )
510 mModel->mLogicalModel->mScriptRuns.Clear();
513 if( NO_OPERATION != ( VALIDATE_FONTS & operations ) )
515 mModel->mLogicalModel->mFontRuns.Clear();
518 if( 0u != mModel->mLogicalModel->mBidirectionalParagraphInfo.Count() )
520 if( NO_OPERATION != ( BIDI_INFO & operations ) )
522 mModel->mLogicalModel->mBidirectionalParagraphInfo.Clear();
523 mModel->mLogicalModel->mCharacterDirections.Clear();
526 if( NO_OPERATION != ( REORDER & operations ) )
528 // Free the allocated memory used to store the conversion table in the bidirectional line info run.
529 for( Vector<BidirectionalLineInfoRun>::Iterator it = mModel->mLogicalModel->mBidirectionalLineInfo.Begin(),
530 endIt = mModel->mLogicalModel->mBidirectionalLineInfo.End();
534 BidirectionalLineInfoRun& bidiLineInfo = *it;
536 free( bidiLineInfo.visualToLogicalMap );
537 bidiLineInfo.visualToLogicalMap = NULL;
539 mModel->mLogicalModel->mBidirectionalLineInfo.Clear();
543 if( NO_OPERATION != ( SHAPE_TEXT & operations ) )
545 mModel->mVisualModel->mGlyphs.Clear();
546 mModel->mVisualModel->mGlyphsToCharacters.Clear();
547 mModel->mVisualModel->mCharactersToGlyph.Clear();
548 mModel->mVisualModel->mCharactersPerGlyph.Clear();
549 mModel->mVisualModel->mGlyphsPerCharacter.Clear();
550 mModel->mVisualModel->mGlyphPositions.Clear();
553 if( NO_OPERATION != ( LAYOUT & operations ) )
555 mModel->mVisualModel->mLines.Clear();
558 if( NO_OPERATION != ( COLOR & operations ) )
560 mModel->mVisualModel->mColorIndices.Clear();
564 void Controller::Impl::ClearCharacterModelData( CharacterIndex startIndex, CharacterIndex endIndex, OperationsMask operations )
566 const CharacterIndex endIndexPlusOne = endIndex + 1u;
568 if( NO_OPERATION != ( GET_LINE_BREAKS & operations ) )
570 // Clear the line break info.
571 LineBreakInfo* lineBreakInfoBuffer = mModel->mLogicalModel->mLineBreakInfo.Begin();
573 mModel->mLogicalModel->mLineBreakInfo.Erase( lineBreakInfoBuffer + startIndex,
574 lineBreakInfoBuffer + endIndexPlusOne );
576 // Clear the paragraphs.
577 ClearCharacterRuns( startIndex,
579 mModel->mLogicalModel->mParagraphInfo );
582 if( NO_OPERATION != ( GET_SCRIPTS & operations ) )
584 // Clear the scripts.
585 ClearCharacterRuns( startIndex,
587 mModel->mLogicalModel->mScriptRuns );
590 if( NO_OPERATION != ( VALIDATE_FONTS & operations ) )
593 ClearCharacterRuns( startIndex,
595 mModel->mLogicalModel->mFontRuns );
598 if( 0u != mModel->mLogicalModel->mBidirectionalParagraphInfo.Count() )
600 if( NO_OPERATION != ( BIDI_INFO & operations ) )
602 // Clear the bidirectional paragraph info.
603 ClearCharacterRuns( startIndex,
605 mModel->mLogicalModel->mBidirectionalParagraphInfo );
607 // Clear the character's directions.
608 CharacterDirection* characterDirectionsBuffer = mModel->mLogicalModel->mCharacterDirections.Begin();
610 mModel->mLogicalModel->mCharacterDirections.Erase( characterDirectionsBuffer + startIndex,
611 characterDirectionsBuffer + endIndexPlusOne );
614 if( NO_OPERATION != ( REORDER & operations ) )
616 uint32_t startRemoveIndex = mModel->mLogicalModel->mBidirectionalLineInfo.Count();
617 uint32_t endRemoveIndex = startRemoveIndex;
618 ClearCharacterRuns( startIndex,
620 mModel->mLogicalModel->mBidirectionalLineInfo,
624 BidirectionalLineInfoRun* bidirectionalLineInfoBuffer = mModel->mLogicalModel->mBidirectionalLineInfo.Begin();
626 // Free the allocated memory used to store the conversion table in the bidirectional line info run.
627 for( Vector<BidirectionalLineInfoRun>::Iterator it = bidirectionalLineInfoBuffer + startRemoveIndex,
628 endIt = bidirectionalLineInfoBuffer + endRemoveIndex;
632 BidirectionalLineInfoRun& bidiLineInfo = *it;
634 free( bidiLineInfo.visualToLogicalMap );
635 bidiLineInfo.visualToLogicalMap = NULL;
638 mModel->mLogicalModel->mBidirectionalLineInfo.Erase( bidirectionalLineInfoBuffer + startRemoveIndex,
639 bidirectionalLineInfoBuffer + endRemoveIndex );
644 void Controller::Impl::ClearGlyphModelData( CharacterIndex startIndex, CharacterIndex endIndex, OperationsMask operations )
646 const CharacterIndex endIndexPlusOne = endIndex + 1u;
647 const Length numberOfCharactersRemoved = endIndexPlusOne - startIndex;
649 // Convert the character index to glyph index before deleting the character to glyph and the glyphs per character buffers.
650 GlyphIndex* charactersToGlyphBuffer = mModel->mVisualModel->mCharactersToGlyph.Begin();
651 Length* glyphsPerCharacterBuffer = mModel->mVisualModel->mGlyphsPerCharacter.Begin();
653 const GlyphIndex endGlyphIndexPlusOne = *( charactersToGlyphBuffer + endIndex ) + *( glyphsPerCharacterBuffer + endIndex );
654 const Length numberOfGlyphsRemoved = endGlyphIndexPlusOne - mTextUpdateInfo.mStartGlyphIndex;
656 if( NO_OPERATION != ( SHAPE_TEXT & operations ) )
658 // Update the character to glyph indices.
659 for( Vector<GlyphIndex>::Iterator it = charactersToGlyphBuffer + endIndexPlusOne,
660 endIt = charactersToGlyphBuffer + mModel->mVisualModel->mCharactersToGlyph.Count();
664 CharacterIndex& index = *it;
665 index -= numberOfGlyphsRemoved;
668 // Clear the character to glyph conversion table.
669 mModel->mVisualModel->mCharactersToGlyph.Erase( charactersToGlyphBuffer + startIndex,
670 charactersToGlyphBuffer + endIndexPlusOne );
672 // Clear the glyphs per character table.
673 mModel->mVisualModel->mGlyphsPerCharacter.Erase( glyphsPerCharacterBuffer + startIndex,
674 glyphsPerCharacterBuffer + endIndexPlusOne );
676 // Clear the glyphs buffer.
677 GlyphInfo* glyphsBuffer = mModel->mVisualModel->mGlyphs.Begin();
678 mModel->mVisualModel->mGlyphs.Erase( glyphsBuffer + mTextUpdateInfo.mStartGlyphIndex,
679 glyphsBuffer + endGlyphIndexPlusOne );
681 CharacterIndex* glyphsToCharactersBuffer = mModel->mVisualModel->mGlyphsToCharacters.Begin();
683 // Update the glyph to character indices.
684 for( Vector<CharacterIndex>::Iterator it = glyphsToCharactersBuffer + endGlyphIndexPlusOne,
685 endIt = glyphsToCharactersBuffer + mModel->mVisualModel->mGlyphsToCharacters.Count();
689 CharacterIndex& index = *it;
690 index -= numberOfCharactersRemoved;
693 // Clear the glyphs to characters buffer.
694 mModel->mVisualModel->mGlyphsToCharacters.Erase( glyphsToCharactersBuffer + mTextUpdateInfo.mStartGlyphIndex,
695 glyphsToCharactersBuffer + endGlyphIndexPlusOne );
697 // Clear the characters per glyph buffer.
698 Length* charactersPerGlyphBuffer = mModel->mVisualModel->mCharactersPerGlyph.Begin();
699 mModel->mVisualModel->mCharactersPerGlyph.Erase( charactersPerGlyphBuffer + mTextUpdateInfo.mStartGlyphIndex,
700 charactersPerGlyphBuffer + endGlyphIndexPlusOne );
702 // Clear the positions buffer.
703 Vector2* positionsBuffer = mModel->mVisualModel->mGlyphPositions.Begin();
704 mModel->mVisualModel->mGlyphPositions.Erase( positionsBuffer + mTextUpdateInfo.mStartGlyphIndex,
705 positionsBuffer + endGlyphIndexPlusOne );
708 if( NO_OPERATION != ( LAYOUT & operations ) )
711 uint32_t startRemoveIndex = mModel->mVisualModel->mLines.Count();
712 uint32_t endRemoveIndex = startRemoveIndex;
713 ClearCharacterRuns( startIndex,
715 mModel->mVisualModel->mLines,
719 // Will update the glyph runs.
720 startRemoveIndex = mModel->mVisualModel->mLines.Count();
721 endRemoveIndex = startRemoveIndex;
722 ClearGlyphRuns( mTextUpdateInfo.mStartGlyphIndex,
723 endGlyphIndexPlusOne - 1u,
724 mModel->mVisualModel->mLines,
728 // Set the line index from where to insert the new laid-out lines.
729 mTextUpdateInfo.mStartLineIndex = startRemoveIndex;
731 LineRun* linesBuffer = mModel->mVisualModel->mLines.Begin();
732 mModel->mVisualModel->mLines.Erase( linesBuffer + startRemoveIndex,
733 linesBuffer + endRemoveIndex );
736 if( NO_OPERATION != ( COLOR & operations ) )
738 if( 0u != mModel->mVisualModel->mColorIndices.Count() )
740 ColorIndex* colorIndexBuffer = mModel->mVisualModel->mColorIndices.Begin();
741 mModel->mVisualModel->mColorIndices.Erase( colorIndexBuffer + mTextUpdateInfo.mStartGlyphIndex,
742 colorIndexBuffer + endGlyphIndexPlusOne );
747 void Controller::Impl::ClearModelData( CharacterIndex startIndex, CharacterIndex endIndex, OperationsMask operations )
749 if( mTextUpdateInfo.mClearAll ||
750 ( ( 0u == startIndex ) &&
751 ( mTextUpdateInfo.mPreviousNumberOfCharacters == endIndex + 1u ) ) )
753 ClearFullModelData( operations );
757 // Clear the model data related with characters.
758 ClearCharacterModelData( startIndex, endIndex, operations );
760 // Clear the model data related with glyphs.
761 ClearGlyphModelData( startIndex, endIndex, operations );
764 // The estimated number of lines. Used to avoid reallocations when layouting.
765 mTextUpdateInfo.mEstimatedNumberOfLines = std::max( mModel->mVisualModel->mLines.Count(), mModel->mLogicalModel->mParagraphInfo.Count() );
767 mModel->mVisualModel->ClearCaches();
770 bool Controller::Impl::UpdateModel( OperationsMask operationsRequired )
772 DALI_LOG_INFO( gLogFilter, Debug::General, "Controller::UpdateModel\n" );
774 // Calculate the operations to be done.
775 const OperationsMask operations = static_cast<OperationsMask>( mOperationsPending & operationsRequired );
777 if( NO_OPERATION == operations )
779 // Nothing to do if no operations are pending and required.
783 Vector<Character>& srcCharacters = mModel->mLogicalModel->mText;
784 Vector<Character> displayCharacters;
785 bool useHiddenText = false;
786 if ( mHiddenInput && mEventData != NULL && !mEventData->mIsShowingPlaceholderText)
788 mHiddenInput->Substitute( srcCharacters,displayCharacters );
789 useHiddenText = true;
792 Vector<Character>& utf32Characters = useHiddenText ? displayCharacters : srcCharacters;
793 const Length numberOfCharacters = utf32Characters.Count();
795 // Index to the first character of the first paragraph to be updated.
796 CharacterIndex startIndex = 0u;
797 // Number of characters of the paragraphs to be removed.
798 Length paragraphCharacters = 0u;
800 CalculateTextUpdateIndices( paragraphCharacters );
802 // Check whether the indices for updating the text is valid
803 if ( numberOfCharacters > 0u &&
804 ( mTextUpdateInfo.mParagraphCharacterIndex > numberOfCharacters ||
805 mTextUpdateInfo.mRequestedNumberOfCharacters > numberOfCharacters ) )
807 std::string currentText;
808 Utf32ToUtf8( mModel->mLogicalModel->mText.Begin(), numberOfCharacters, currentText );
810 DALI_LOG_ERROR( "Controller::Impl::UpdateModel: mTextUpdateInfo has invalid indices\n" );
811 DALI_LOG_ERROR( "Number of characters: %d, current text is: %s\n", numberOfCharacters, currentText.c_str() );
813 // Dump mTextUpdateInfo
814 DALI_LOG_ERROR( "Dump mTextUpdateInfo:\n" );
815 DALI_LOG_ERROR( " mTextUpdateInfo.mCharacterIndex = %u\n", mTextUpdateInfo.mCharacterIndex );
816 DALI_LOG_ERROR( " mTextUpdateInfo.mNumberOfCharactersToRemove = %u\n", mTextUpdateInfo.mNumberOfCharactersToRemove );
817 DALI_LOG_ERROR( " mTextUpdateInfo.mNumberOfCharactersToAdd = %u\n", mTextUpdateInfo.mNumberOfCharactersToAdd );
818 DALI_LOG_ERROR( " mTextUpdateInfo.mPreviousNumberOfCharacters = %u\n", mTextUpdateInfo.mPreviousNumberOfCharacters );
819 DALI_LOG_ERROR( " mTextUpdateInfo.mParagraphCharacterIndex = %u\n", mTextUpdateInfo.mParagraphCharacterIndex );
820 DALI_LOG_ERROR( " mTextUpdateInfo.mRequestedNumberOfCharacters = %u\n", mTextUpdateInfo.mRequestedNumberOfCharacters );
821 DALI_LOG_ERROR( " mTextUpdateInfo.mStartGlyphIndex = %u\n", mTextUpdateInfo.mStartGlyphIndex );
822 DALI_LOG_ERROR( " mTextUpdateInfo.mStartLineIndex = %u\n", mTextUpdateInfo.mStartLineIndex );
823 DALI_LOG_ERROR( " mTextUpdateInfo.mEstimatedNumberOfLines = %u\n", mTextUpdateInfo.mEstimatedNumberOfLines );
824 DALI_LOG_ERROR( " mTextUpdateInfo.mClearAll = %d\n", mTextUpdateInfo.mClearAll );
825 DALI_LOG_ERROR( " mTextUpdateInfo.mFullRelayoutNeeded = %d\n", mTextUpdateInfo.mFullRelayoutNeeded );
826 DALI_LOG_ERROR( " mTextUpdateInfo.mIsLastCharacterNewParagraph = %d\n", mTextUpdateInfo.mIsLastCharacterNewParagraph );
831 startIndex = mTextUpdateInfo.mParagraphCharacterIndex;
833 if( mTextUpdateInfo.mClearAll ||
834 ( 0u != paragraphCharacters ) )
836 ClearModelData( startIndex, startIndex + ( ( paragraphCharacters > 0u ) ? paragraphCharacters - 1u : 0u ), operations );
839 mTextUpdateInfo.mClearAll = false;
841 // Whether the model is updated.
842 bool updated = false;
844 Vector<LineBreakInfo>& lineBreakInfo = mModel->mLogicalModel->mLineBreakInfo;
845 const Length requestedNumberOfCharacters = mTextUpdateInfo.mRequestedNumberOfCharacters;
847 if( NO_OPERATION != ( GET_LINE_BREAKS & operations ) )
849 // Retrieves the line break info. The line break info is used to split the text in 'paragraphs' to
850 // calculate the bidirectional info for each 'paragraph'.
851 // It's also used to layout the text (where it should be a new line) or to shape the text (text in different lines
852 // is not shaped together).
853 lineBreakInfo.Resize( numberOfCharacters, TextAbstraction::LINE_NO_BREAK );
855 SetLineBreakInfo( utf32Characters,
857 requestedNumberOfCharacters,
860 // Create the paragraph info.
861 mModel->mLogicalModel->CreateParagraphInfo( startIndex,
862 requestedNumberOfCharacters );
866 const bool getScripts = NO_OPERATION != ( GET_SCRIPTS & operations );
867 const bool validateFonts = NO_OPERATION != ( VALIDATE_FONTS & operations );
869 Vector<ScriptRun>& scripts = mModel->mLogicalModel->mScriptRuns;
870 Vector<FontRun>& validFonts = mModel->mLogicalModel->mFontRuns;
872 if( getScripts || validateFonts )
874 // Validates the fonts assigned by the application or assigns default ones.
875 // It makes sure all the characters are going to be rendered by the correct font.
876 MultilanguageSupport multilanguageSupport = MultilanguageSupport::Get();
880 // Retrieves the scripts used in the text.
881 multilanguageSupport.SetScripts( utf32Characters,
883 requestedNumberOfCharacters,
889 // Validate the fonts set through the mark-up string.
890 Vector<FontDescriptionRun>& fontDescriptionRuns = mModel->mLogicalModel->mFontDescriptionRuns;
892 // Get the default font's description.
893 TextAbstraction::FontDescription defaultFontDescription;
894 TextAbstraction::PointSize26Dot6 defaultPointSize = TextAbstraction::FontClient::DEFAULT_POINT_SIZE;
896 if( IsShowingPlaceholderText() && mEventData && ( NULL != mEventData->mPlaceholderFont ) )
898 // If the placeholder font is set specifically, only placeholder font is changed.
899 defaultFontDescription = mEventData->mPlaceholderFont->mFontDescription;
900 if( mEventData->mPlaceholderFont->sizeDefined )
902 defaultPointSize = mEventData->mPlaceholderFont->mDefaultPointSize * 64u;
905 else if( NULL != mFontDefaults )
907 // Set the normal font and the placeholder font.
908 defaultFontDescription = mFontDefaults->mFontDescription;
910 if( mTextFitEnabled )
912 defaultPointSize = mFontDefaults->mFitPointSize * 64u;
916 defaultPointSize = mFontDefaults->mDefaultPointSize * 64u;
920 // Validates the fonts. If there is a character with no assigned font it sets a default one.
921 // After this call, fonts are validated.
922 multilanguageSupport.ValidateFonts( utf32Characters,
925 defaultFontDescription,
928 requestedNumberOfCharacters,
934 Vector<Character> mirroredUtf32Characters;
935 bool textMirrored = false;
936 const Length numberOfParagraphs = mModel->mLogicalModel->mParagraphInfo.Count();
937 if( NO_OPERATION != ( BIDI_INFO & operations ) )
939 Vector<BidirectionalParagraphInfoRun>& bidirectionalInfo = mModel->mLogicalModel->mBidirectionalParagraphInfo;
940 bidirectionalInfo.Reserve( numberOfParagraphs );
942 // Calculates the bidirectional info for the whole paragraph if it contains right to left scripts.
943 SetBidirectionalInfo( utf32Characters,
947 requestedNumberOfCharacters,
949 mModel->mMatchSystemLanguageDirection,
952 if( 0u != bidirectionalInfo.Count() )
954 // Only set the character directions if there is right to left characters.
955 Vector<CharacterDirection>& directions = mModel->mLogicalModel->mCharacterDirections;
956 GetCharactersDirection( bidirectionalInfo,
959 requestedNumberOfCharacters,
962 // This paragraph has right to left text. Some characters may need to be mirrored.
963 // TODO: consider if the mirrored string can be stored as well.
965 textMirrored = GetMirroredText( utf32Characters,
969 requestedNumberOfCharacters,
970 mirroredUtf32Characters );
974 // There is no right to left characters. Clear the directions vector.
975 mModel->mLogicalModel->mCharacterDirections.Clear();
980 Vector<GlyphInfo>& glyphs = mModel->mVisualModel->mGlyphs;
981 Vector<CharacterIndex>& glyphsToCharactersMap = mModel->mVisualModel->mGlyphsToCharacters;
982 Vector<Length>& charactersPerGlyph = mModel->mVisualModel->mCharactersPerGlyph;
983 Vector<GlyphIndex> newParagraphGlyphs;
984 newParagraphGlyphs.Reserve( numberOfParagraphs );
986 const Length currentNumberOfGlyphs = glyphs.Count();
987 if( NO_OPERATION != ( SHAPE_TEXT & operations ) )
989 const Vector<Character>& textToShape = textMirrored ? mirroredUtf32Characters : utf32Characters;
991 ShapeText( textToShape,
996 mTextUpdateInfo.mStartGlyphIndex,
997 requestedNumberOfCharacters,
999 glyphsToCharactersMap,
1001 newParagraphGlyphs );
1003 // Create the 'number of glyphs' per character and the glyph to character conversion tables.
1004 mModel->mVisualModel->CreateGlyphsPerCharacterTable( startIndex, mTextUpdateInfo.mStartGlyphIndex, requestedNumberOfCharacters );
1005 mModel->mVisualModel->CreateCharacterToGlyphTable( startIndex, mTextUpdateInfo.mStartGlyphIndex, requestedNumberOfCharacters );
1009 const Length numberOfGlyphs = glyphs.Count() - currentNumberOfGlyphs;
1011 if( NO_OPERATION != ( GET_GLYPH_METRICS & operations ) )
1013 GlyphInfo* glyphsBuffer = glyphs.Begin();
1014 mMetrics->GetGlyphMetrics( glyphsBuffer + mTextUpdateInfo.mStartGlyphIndex, numberOfGlyphs );
1016 // Update the width and advance of all new paragraph characters.
1017 for( Vector<GlyphIndex>::ConstIterator it = newParagraphGlyphs.Begin(), endIt = newParagraphGlyphs.End(); it != endIt; ++it )
1019 const GlyphIndex index = *it;
1020 GlyphInfo& glyph = *( glyphsBuffer + index );
1022 glyph.xBearing = 0.f;
1024 glyph.advance = 0.f;
1029 if( NO_OPERATION != ( COLOR & operations ) )
1031 // Set the color runs in glyphs.
1032 SetColorSegmentationInfo( mModel->mLogicalModel->mColorRuns,
1033 mModel->mVisualModel->mCharactersToGlyph,
1034 mModel->mVisualModel->mGlyphsPerCharacter,
1036 mTextUpdateInfo.mStartGlyphIndex,
1037 requestedNumberOfCharacters,
1038 mModel->mVisualModel->mColors,
1039 mModel->mVisualModel->mColorIndices );
1044 if( ( NULL != mEventData ) &&
1045 mEventData->mPreEditFlag &&
1046 ( 0u != mModel->mVisualModel->mCharactersToGlyph.Count() ) )
1048 Dali::InputMethodContext::PreeditStyle type = mEventData->mInputMethodContext.GetPreeditStyle();
1052 case Dali::InputMethodContext::PreeditStyle::UNDERLINE:
1054 // Add the underline for the pre-edit text.
1055 const GlyphIndex* const charactersToGlyphBuffer = mModel->mVisualModel->mCharactersToGlyph.Begin();
1056 const Length* const glyphsPerCharacterBuffer = mModel->mVisualModel->mGlyphsPerCharacter.Begin();
1058 const GlyphIndex glyphStart = *( charactersToGlyphBuffer + mEventData->mPreEditStartPosition );
1059 const CharacterIndex lastPreEditCharacter = mEventData->mPreEditStartPosition + ( ( mEventData->mPreEditLength > 0u ) ? mEventData->mPreEditLength - 1u : 0u );
1060 const Length numberOfGlyphsLastCharacter = *( glyphsPerCharacterBuffer + lastPreEditCharacter );
1061 const GlyphIndex glyphEnd = *( charactersToGlyphBuffer + lastPreEditCharacter ) + ( numberOfGlyphsLastCharacter > 1u ? numberOfGlyphsLastCharacter - 1u : 0u );
1063 GlyphRun underlineRun;
1064 underlineRun.glyphIndex = glyphStart;
1065 underlineRun.numberOfGlyphs = 1u + glyphEnd - glyphStart;
1067 mModel->mVisualModel->mUnderlineRuns.PushBack( underlineRun );
1070 // TODO : At this moment, other styles for preedit are not implemented yet.
1071 case Dali::InputMethodContext::PreeditStyle::REVERSE:
1072 case Dali::InputMethodContext::PreeditStyle::HIGHLIGHT:
1073 case Dali::InputMethodContext::PreeditStyle::NONE:
1079 // The estimated number of lines. Used to avoid reallocations when layouting.
1080 mTextUpdateInfo.mEstimatedNumberOfLines = std::max( mModel->mVisualModel->mLines.Count(), mModel->mLogicalModel->mParagraphInfo.Count() );
1082 // Set the previous number of characters for the next time the text is updated.
1083 mTextUpdateInfo.mPreviousNumberOfCharacters = numberOfCharacters;
1088 void Controller::Impl::RetrieveDefaultInputStyle( InputStyle& inputStyle )
1090 // Sets the default text's color.
1091 inputStyle.textColor = mTextColor;
1092 inputStyle.isDefaultColor = true;
1094 inputStyle.familyName.clear();
1095 inputStyle.weight = TextAbstraction::FontWeight::NORMAL;
1096 inputStyle.width = TextAbstraction::FontWidth::NORMAL;
1097 inputStyle.slant = TextAbstraction::FontSlant::NORMAL;
1098 inputStyle.size = 0.f;
1100 inputStyle.lineSpacing = 0.f;
1102 inputStyle.underlineProperties.clear();
1103 inputStyle.shadowProperties.clear();
1104 inputStyle.embossProperties.clear();
1105 inputStyle.outlineProperties.clear();
1107 inputStyle.isFamilyDefined = false;
1108 inputStyle.isWeightDefined = false;
1109 inputStyle.isWidthDefined = false;
1110 inputStyle.isSlantDefined = false;
1111 inputStyle.isSizeDefined = false;
1113 inputStyle.isLineSpacingDefined = false;
1115 inputStyle.isUnderlineDefined = false;
1116 inputStyle.isShadowDefined = false;
1117 inputStyle.isEmbossDefined = false;
1118 inputStyle.isOutlineDefined = false;
1120 // Sets the default font's family name, weight, width, slant and size.
1123 if( mFontDefaults->familyDefined )
1125 inputStyle.familyName = mFontDefaults->mFontDescription.family;
1126 inputStyle.isFamilyDefined = true;
1129 if( mFontDefaults->weightDefined )
1131 inputStyle.weight = mFontDefaults->mFontDescription.weight;
1132 inputStyle.isWeightDefined = true;
1135 if( mFontDefaults->widthDefined )
1137 inputStyle.width = mFontDefaults->mFontDescription.width;
1138 inputStyle.isWidthDefined = true;
1141 if( mFontDefaults->slantDefined )
1143 inputStyle.slant = mFontDefaults->mFontDescription.slant;
1144 inputStyle.isSlantDefined = true;
1147 if( mFontDefaults->sizeDefined )
1149 inputStyle.size = mFontDefaults->mDefaultPointSize;
1150 inputStyle.isSizeDefined = true;
1155 float Controller::Impl::GetDefaultFontLineHeight()
1157 FontId defaultFontId = 0u;
1158 if( NULL == mFontDefaults )
1160 TextAbstraction::FontDescription fontDescription;
1161 defaultFontId = mFontClient.GetFontId( fontDescription );
1165 defaultFontId = mFontDefaults->GetFontId( mFontClient );
1168 Text::FontMetrics fontMetrics;
1169 mMetrics->GetFontMetrics( defaultFontId, fontMetrics );
1171 return( fontMetrics.ascender - fontMetrics.descender );
1174 void Controller::Impl::OnCursorKeyEvent( const Event& event )
1176 if( NULL == mEventData || !IsShowingRealText() )
1178 // Nothing to do if there is no text input.
1182 int keyCode = event.p1.mInt;
1183 bool isShiftModifier = event.p2.mBool;
1185 CharacterIndex previousPrimaryCursorPosition = mEventData->mPrimaryCursorPosition;
1187 if( Dali::DALI_KEY_CURSOR_LEFT == keyCode )
1189 if( mEventData->mPrimaryCursorPosition > 0u )
1191 if ( !isShiftModifier && mEventData->mDecorator->IsHighlightVisible() )
1193 mEventData->mPrimaryCursorPosition = std::min(mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition);
1197 mEventData->mPrimaryCursorPosition = CalculateNewCursorIndex( mEventData->mPrimaryCursorPosition - 1u );
1201 else if( Dali::DALI_KEY_CURSOR_RIGHT == keyCode )
1203 if( mModel->mLogicalModel->mText.Count() > mEventData->mPrimaryCursorPosition )
1205 if ( !isShiftModifier && mEventData->mDecorator->IsHighlightVisible() )
1207 mEventData->mPrimaryCursorPosition = std::max(mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition);
1211 mEventData->mPrimaryCursorPosition = CalculateNewCursorIndex( mEventData->mPrimaryCursorPosition );
1215 else if( Dali::DALI_KEY_CURSOR_UP == keyCode && !isShiftModifier )
1217 // Ignore Shift-Up for text selection for now.
1219 // Get first the line index of the current cursor position index.
1220 CharacterIndex characterIndex = 0u;
1222 if( mEventData->mPrimaryCursorPosition > 0u )
1224 characterIndex = mEventData->mPrimaryCursorPosition - 1u;
1227 const LineIndex lineIndex = mModel->mVisualModel->GetLineOfCharacter( characterIndex );
1228 const LineIndex previousLineIndex = ( lineIndex > 0 ? lineIndex - 1u : lineIndex );
1230 // Retrieve the cursor position info.
1231 CursorInfo cursorInfo;
1232 GetCursorPosition( mEventData->mPrimaryCursorPosition,
1235 // Get the line above.
1236 const LineRun& line = *( mModel->mVisualModel->mLines.Begin() + previousLineIndex );
1238 // Get the next hit 'y' point.
1239 const float hitPointY = cursorInfo.lineOffset - 0.5f * ( line.ascender - line.descender );
1241 // Use the cursor hook position 'x' and the next hit 'y' position to calculate the new cursor index.
1242 bool matchedCharacter = false;
1243 mEventData->mPrimaryCursorPosition = Text::GetClosestCursorIndex( mModel->mVisualModel,
1244 mModel->mLogicalModel,
1246 mEventData->mCursorHookPositionX,
1248 CharacterHitTest::TAP,
1251 else if( Dali::DALI_KEY_CURSOR_DOWN == keyCode && !isShiftModifier )
1253 // Ignore Shift-Down for text selection for now.
1255 // Get first the line index of the current cursor position index.
1256 CharacterIndex characterIndex = 0u;
1258 if( mEventData->mPrimaryCursorPosition > 0u )
1260 characterIndex = mEventData->mPrimaryCursorPosition - 1u;
1263 const LineIndex lineIndex = mModel->mVisualModel->GetLineOfCharacter( characterIndex );
1265 if( lineIndex + 1u < mModel->mVisualModel->mLines.Count() )
1267 // Retrieve the cursor position info.
1268 CursorInfo cursorInfo;
1269 GetCursorPosition( mEventData->mPrimaryCursorPosition,
1272 // Get the line below.
1273 const LineRun& line = *( mModel->mVisualModel->mLines.Begin() + lineIndex + 1u );
1275 // Get the next hit 'y' point.
1276 const float hitPointY = cursorInfo.lineOffset + cursorInfo.lineHeight + 0.5f * ( line.ascender - line.descender );
1278 // Use the cursor hook position 'x' and the next hit 'y' position to calculate the new cursor index.
1279 bool matchedCharacter = false;
1280 mEventData->mPrimaryCursorPosition = Text::GetClosestCursorIndex( mModel->mVisualModel,
1281 mModel->mLogicalModel,
1283 mEventData->mCursorHookPositionX,
1285 CharacterHitTest::TAP,
1290 if ( !isShiftModifier && mEventData->mState != EventData::SELECTING )
1292 // Update selection position after moving the cursor
1293 mEventData->mLeftSelectionPosition = mEventData->mPrimaryCursorPosition;
1294 mEventData->mRightSelectionPosition = mEventData->mPrimaryCursorPosition;
1297 if ( isShiftModifier && IsShowingRealText() && mEventData->mShiftSelectionFlag )
1299 // Handle text selection
1300 bool selecting = false;
1302 if ( Dali::DALI_KEY_CURSOR_LEFT == keyCode || Dali::DALI_KEY_CURSOR_RIGHT == keyCode )
1304 // Shift-Left/Right to select the text
1305 int cursorPositionDelta = mEventData->mPrimaryCursorPosition - previousPrimaryCursorPosition;
1306 if ( cursorPositionDelta > 0 || mEventData->mRightSelectionPosition > 0u ) // Check the boundary
1308 mEventData->mRightSelectionPosition += cursorPositionDelta;
1312 else if ( mEventData->mLeftSelectionPosition != mEventData->mRightSelectionPosition )
1314 // Show no grab handles and text highlight if Shift-Up/Down pressed but no selected text
1320 // Notify the cursor position to the InputMethodContext.
1321 if( mEventData->mInputMethodContext )
1323 mEventData->mInputMethodContext.SetCursorPosition( mEventData->mPrimaryCursorPosition );
1324 mEventData->mInputMethodContext.NotifyCursorPosition();
1327 ChangeState( EventData::SELECTING );
1329 mEventData->mUpdateLeftSelectionPosition = true;
1330 mEventData->mUpdateRightSelectionPosition = true;
1331 mEventData->mUpdateGrabHandlePosition = true;
1332 mEventData->mUpdateHighlightBox = true;
1334 // Hide the text selection popup if select the text using keyboard instead of moving grab handles
1335 if( mEventData->mGrabHandlePopupEnabled )
1337 mEventData->mDecorator->SetPopupActive( false );
1343 // Handle normal cursor move
1344 ChangeState( EventData::EDITING );
1345 mEventData->mUpdateCursorPosition = true;
1348 mEventData->mUpdateInputStyle = true;
1349 mEventData->mScrollAfterUpdatePosition = true;
1352 void Controller::Impl::OnTapEvent( const Event& event )
1354 if( NULL != mEventData )
1356 const unsigned int tapCount = event.p1.mUint;
1358 if( 1u == tapCount )
1360 if( IsShowingRealText() )
1362 // Convert from control's coords to text's coords.
1363 const float xPosition = event.p2.mFloat - mModel->mScrollPosition.x;
1364 const float yPosition = event.p3.mFloat - mModel->mScrollPosition.y;
1366 // Keep the tap 'x' position. Used to move the cursor.
1367 mEventData->mCursorHookPositionX = xPosition;
1369 // Whether to touch point hits on a glyph.
1370 bool matchedCharacter = false;
1371 mEventData->mPrimaryCursorPosition = Text::GetClosestCursorIndex( mModel->mVisualModel,
1372 mModel->mLogicalModel,
1376 CharacterHitTest::TAP,
1379 // When the cursor position is changing, delay cursor blinking
1380 mEventData->mDecorator->DelayCursorBlink();
1384 mEventData->mPrimaryCursorPosition = 0u;
1387 // Update selection position after tapping
1388 mEventData->mLeftSelectionPosition = mEventData->mPrimaryCursorPosition;
1389 mEventData->mRightSelectionPosition = mEventData->mPrimaryCursorPosition;
1391 mEventData->mUpdateCursorPosition = true;
1392 mEventData->mUpdateGrabHandlePosition = true;
1393 mEventData->mScrollAfterUpdatePosition = true;
1394 mEventData->mUpdateInputStyle = true;
1396 // Notify the cursor position to the InputMethodContext.
1397 if( mEventData->mInputMethodContext )
1399 mEventData->mInputMethodContext.SetCursorPosition( mEventData->mPrimaryCursorPosition );
1400 mEventData->mInputMethodContext.NotifyCursorPosition();
1403 else if( 2u == tapCount )
1405 if( mEventData->mSelectionEnabled )
1407 // Convert from control's coords to text's coords.
1408 const float xPosition = event.p2.mFloat - mModel->mScrollPosition.x;
1409 const float yPosition = event.p3.mFloat - mModel->mScrollPosition.y;
1411 // Calculates the logical position from the x,y coords.
1412 RepositionSelectionHandles( xPosition,
1414 mEventData->mDoubleTapAction );
1420 void Controller::Impl::OnPanEvent( const Event& event )
1422 if( NULL == mEventData )
1424 // Nothing to do if there is no text input.
1428 const bool isHorizontalScrollEnabled = mEventData->mDecorator->IsHorizontalScrollEnabled();
1429 const bool isVerticalScrollEnabled = mEventData->mDecorator->IsVerticalScrollEnabled();
1431 if( !isHorizontalScrollEnabled && !isVerticalScrollEnabled )
1433 // Nothing to do if scrolling is not enabled.
1437 const int state = event.p1.mInt;
1441 case Gesture::Started:
1443 // Will remove the cursor, handles or text's popup, ...
1444 ChangeState( EventData::TEXT_PANNING );
1447 case Gesture::Continuing:
1449 const Vector2& layoutSize = mModel->mVisualModel->GetLayoutSize();
1450 const Vector2 currentScroll = mModel->mScrollPosition;
1452 if( isHorizontalScrollEnabled )
1454 const float displacementX = event.p2.mFloat;
1455 mModel->mScrollPosition.x += displacementX;
1457 ClampHorizontalScroll( layoutSize );
1460 if( isVerticalScrollEnabled )
1462 const float displacementY = event.p3.mFloat;
1463 mModel->mScrollPosition.y += displacementY;
1465 ClampVerticalScroll( layoutSize );
1468 mEventData->mDecorator->UpdatePositions( mModel->mScrollPosition - currentScroll );
1471 case Gesture::Finished:
1472 case Gesture::Cancelled: // FALLTHROUGH
1474 // Will go back to the previous state to show the cursor, handles, the text's popup, ...
1475 ChangeState( mEventData->mPreviousState );
1483 void Controller::Impl::OnLongPressEvent( const Event& event )
1485 DALI_LOG_INFO( gLogFilter, Debug::General, "Controller::OnLongPressEvent\n" );
1487 if( !IsShowingRealText() && ( EventData::EDITING == mEventData->mState ) )
1489 ChangeState( EventData::EDITING_WITH_POPUP );
1490 mEventData->mDecoratorUpdated = true;
1491 mEventData->mUpdateInputStyle = true;
1495 if( mEventData->mSelectionEnabled )
1497 // Convert from control's coords to text's coords.
1498 const float xPosition = event.p2.mFloat - mModel->mScrollPosition.x;
1499 const float yPosition = event.p3.mFloat - mModel->mScrollPosition.y;
1501 // Calculates the logical position from the x,y coords.
1502 RepositionSelectionHandles( xPosition,
1504 mEventData->mLongPressAction );
1509 void Controller::Impl::OnHandleEvent( const Event& event )
1511 if( NULL == mEventData )
1513 // Nothing to do if there is no text input.
1517 const unsigned int state = event.p1.mUint;
1518 const bool handleStopScrolling = ( HANDLE_STOP_SCROLLING == state );
1519 const bool isSmoothHandlePanEnabled = mEventData->mDecorator->IsSmoothHandlePanEnabled();
1521 if( HANDLE_PRESSED == state )
1523 // Convert from decorator's coords to text's coords.
1524 const float xPosition = event.p2.mFloat - mModel->mScrollPosition.x;
1525 const float yPosition = event.p3.mFloat - mModel->mScrollPosition.y;
1527 // Need to calculate the handle's new position.
1528 bool matchedCharacter = false;
1529 const CharacterIndex handleNewPosition = Text::GetClosestCursorIndex( mModel->mVisualModel,
1530 mModel->mLogicalModel,
1534 CharacterHitTest::SCROLL,
1537 if( Event::GRAB_HANDLE_EVENT == event.type )
1539 ChangeState ( EventData::GRAB_HANDLE_PANNING );
1541 if( handleNewPosition != mEventData->mPrimaryCursorPosition )
1543 // Updates the cursor position if the handle's new position is different than the current one.
1544 mEventData->mUpdateCursorPosition = true;
1545 // Does not update the grab handle position if the smooth panning is enabled. (The decorator does it smooth).
1546 mEventData->mUpdateGrabHandlePosition = !isSmoothHandlePanEnabled;
1547 mEventData->mPrimaryCursorPosition = handleNewPosition;
1550 // Updates the decorator if the soft handle panning is enabled. It triggers a relayout in the decorator and the new position of the handle is set.
1551 mEventData->mDecoratorUpdated = isSmoothHandlePanEnabled;
1553 else if( Event::LEFT_SELECTION_HANDLE_EVENT == event.type )
1555 ChangeState ( EventData::SELECTION_HANDLE_PANNING );
1557 if( ( handleNewPosition != mEventData->mLeftSelectionPosition ) &&
1558 ( handleNewPosition != mEventData->mRightSelectionPosition ) )
1560 // Updates the highlight box if the handle's new position is different than the current one.
1561 mEventData->mUpdateHighlightBox = true;
1562 // Does not update the selection handle position if the smooth panning is enabled. (The decorator does it smooth).
1563 mEventData->mUpdateLeftSelectionPosition = !isSmoothHandlePanEnabled;
1564 mEventData->mLeftSelectionPosition = handleNewPosition;
1567 // 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.
1568 mEventData->mDecoratorUpdated = isSmoothHandlePanEnabled;
1570 // Will define the order to scroll the text to match the handle position.
1571 mEventData->mIsLeftHandleSelected = true;
1572 mEventData->mIsRightHandleSelected = false;
1574 else if( Event::RIGHT_SELECTION_HANDLE_EVENT == event.type )
1576 ChangeState ( EventData::SELECTION_HANDLE_PANNING );
1578 if( ( handleNewPosition != mEventData->mRightSelectionPosition ) &&
1579 ( handleNewPosition != mEventData->mLeftSelectionPosition ) )
1581 // Updates the highlight box if the handle's new position is different than the current one.
1582 mEventData->mUpdateHighlightBox = true;
1583 // Does not update the selection handle position if the smooth panning is enabled. (The decorator does it smooth).
1584 mEventData->mUpdateRightSelectionPosition = !isSmoothHandlePanEnabled;
1585 mEventData->mRightSelectionPosition = handleNewPosition;
1588 // 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.
1589 mEventData->mDecoratorUpdated = isSmoothHandlePanEnabled;
1591 // Will define the order to scroll the text to match the handle position.
1592 mEventData->mIsLeftHandleSelected = false;
1593 mEventData->mIsRightHandleSelected = true;
1595 } // end ( HANDLE_PRESSED == state )
1596 else if( ( HANDLE_RELEASED == state ) ||
1597 handleStopScrolling )
1599 CharacterIndex handlePosition = 0u;
1600 if( handleStopScrolling || isSmoothHandlePanEnabled )
1602 // Convert from decorator's coords to text's coords.
1603 const float xPosition = event.p2.mFloat - mModel->mScrollPosition.x;
1604 const float yPosition = event.p3.mFloat - mModel->mScrollPosition.y;
1606 bool matchedCharacter = false;
1607 handlePosition = Text::GetClosestCursorIndex( mModel->mVisualModel,
1608 mModel->mLogicalModel,
1612 CharacterHitTest::SCROLL,
1616 if( Event::GRAB_HANDLE_EVENT == event.type )
1618 mEventData->mUpdateCursorPosition = true;
1619 mEventData->mUpdateGrabHandlePosition = true;
1620 mEventData->mUpdateInputStyle = true;
1622 if( !IsClipboardEmpty() )
1624 ChangeState( EventData::EDITING_WITH_PASTE_POPUP ); // Moving grabhandle will show Paste Popup
1627 if( handleStopScrolling || isSmoothHandlePanEnabled )
1629 mEventData->mScrollAfterUpdatePosition = true;
1630 mEventData->mPrimaryCursorPosition = handlePosition;
1633 else if( Event::LEFT_SELECTION_HANDLE_EVENT == event.type )
1635 ChangeState( EventData::SELECTING );
1637 mEventData->mUpdateHighlightBox = true;
1638 mEventData->mUpdateLeftSelectionPosition = true;
1639 mEventData->mUpdateRightSelectionPosition = true;
1641 if( handleStopScrolling || isSmoothHandlePanEnabled )
1643 mEventData->mScrollAfterUpdatePosition = true;
1645 if( ( handlePosition != mEventData->mRightSelectionPosition ) &&
1646 ( handlePosition != mEventData->mLeftSelectionPosition ) )
1648 mEventData->mLeftSelectionPosition = handlePosition;
1652 else if( Event::RIGHT_SELECTION_HANDLE_EVENT == event.type )
1654 ChangeState( EventData::SELECTING );
1656 mEventData->mUpdateHighlightBox = true;
1657 mEventData->mUpdateRightSelectionPosition = true;
1658 mEventData->mUpdateLeftSelectionPosition = true;
1660 if( handleStopScrolling || isSmoothHandlePanEnabled )
1662 mEventData->mScrollAfterUpdatePosition = true;
1663 if( ( handlePosition != mEventData->mRightSelectionPosition ) &&
1664 ( handlePosition != mEventData->mLeftSelectionPosition ) )
1666 mEventData->mRightSelectionPosition = handlePosition;
1671 mEventData->mDecoratorUpdated = true;
1672 } // end ( ( HANDLE_RELEASED == state ) || ( HANDLE_STOP_SCROLLING == state ) )
1673 else if( HANDLE_SCROLLING == state )
1675 const float xSpeed = event.p2.mFloat;
1676 const float ySpeed = event.p3.mFloat;
1677 const Vector2& layoutSize = mModel->mVisualModel->GetLayoutSize();
1678 const Vector2 currentScrollPosition = mModel->mScrollPosition;
1680 mModel->mScrollPosition.x += xSpeed;
1681 mModel->mScrollPosition.y += ySpeed;
1683 ClampHorizontalScroll( layoutSize );
1684 ClampVerticalScroll( layoutSize );
1686 bool endOfScroll = false;
1687 if( Vector2::ZERO == ( currentScrollPosition - mModel->mScrollPosition ) )
1689 // Notify the decorator there is no more text to scroll.
1690 // The decorator won't send more scroll events.
1691 mEventData->mDecorator->NotifyEndOfScroll();
1692 // Still need to set the position of the handle.
1696 // Set the position of the handle.
1697 const bool scrollRightDirection = xSpeed > 0.f;
1698 const bool scrollBottomDirection = ySpeed > 0.f;
1699 const bool leftSelectionHandleEvent = Event::LEFT_SELECTION_HANDLE_EVENT == event.type;
1700 const bool rightSelectionHandleEvent = Event::RIGHT_SELECTION_HANDLE_EVENT == event.type;
1702 if( Event::GRAB_HANDLE_EVENT == event.type )
1704 ChangeState( EventData::GRAB_HANDLE_PANNING );
1706 // Get the grab handle position in decorator coords.
1707 Vector2 position = mEventData->mDecorator->GetPosition( GRAB_HANDLE );
1709 if( mEventData->mDecorator->IsHorizontalScrollEnabled() )
1711 // Position the grag handle close to either the left or right edge.
1712 position.x = scrollRightDirection ? 0.f : mModel->mVisualModel->mControlSize.width;
1715 if( mEventData->mDecorator->IsVerticalScrollEnabled() )
1717 position.x = mEventData->mCursorHookPositionX;
1719 // Position the grag handle close to either the top or bottom edge.
1720 position.y = scrollBottomDirection ? 0.f : mModel->mVisualModel->mControlSize.height;
1723 // Get the new handle position.
1724 // The grab handle's position is in decorator's coords. Need to transforms to text's coords.
1725 bool matchedCharacter = false;
1726 const CharacterIndex handlePosition = Text::GetClosestCursorIndex( mModel->mVisualModel,
1727 mModel->mLogicalModel,
1729 position.x - mModel->mScrollPosition.x,
1730 position.y - mModel->mScrollPosition.y,
1731 CharacterHitTest::SCROLL,
1734 if( mEventData->mPrimaryCursorPosition != handlePosition )
1736 mEventData->mUpdateCursorPosition = true;
1737 mEventData->mUpdateGrabHandlePosition = !isSmoothHandlePanEnabled;
1738 mEventData->mScrollAfterUpdatePosition = true;
1739 mEventData->mPrimaryCursorPosition = handlePosition;
1741 mEventData->mUpdateInputStyle = mEventData->mUpdateCursorPosition;
1743 // Updates the decorator if the soft handle panning is enabled.
1744 mEventData->mDecoratorUpdated = isSmoothHandlePanEnabled;
1746 else if( leftSelectionHandleEvent || rightSelectionHandleEvent )
1748 ChangeState( EventData::SELECTION_HANDLE_PANNING );
1750 // Get the selection handle position in decorator coords.
1751 Vector2 position = mEventData->mDecorator->GetPosition( leftSelectionHandleEvent ? Text::LEFT_SELECTION_HANDLE : Text::RIGHT_SELECTION_HANDLE );
1753 if( mEventData->mDecorator->IsHorizontalScrollEnabled() )
1755 // Position the selection handle close to either the left or right edge.
1756 position.x = scrollRightDirection ? 0.f : mModel->mVisualModel->mControlSize.width;
1759 if( mEventData->mDecorator->IsVerticalScrollEnabled() )
1761 position.x = mEventData->mCursorHookPositionX;
1763 // Position the grag handle close to either the top or bottom edge.
1764 position.y = scrollBottomDirection ? 0.f : mModel->mVisualModel->mControlSize.height;
1767 // Get the new handle position.
1768 // The selection handle's position is in decorator's coords. Need to transform to text's coords.
1769 bool matchedCharacter = false;
1770 const CharacterIndex handlePosition = Text::GetClosestCursorIndex( mModel->mVisualModel,
1771 mModel->mLogicalModel,
1773 position.x - mModel->mScrollPosition.x,
1774 position.y - mModel->mScrollPosition.y,
1775 CharacterHitTest::SCROLL,
1778 if( leftSelectionHandleEvent )
1780 const bool differentHandles = ( mEventData->mLeftSelectionPosition != handlePosition ) && ( mEventData->mRightSelectionPosition != handlePosition );
1782 if( differentHandles || endOfScroll )
1784 mEventData->mUpdateHighlightBox = true;
1785 mEventData->mUpdateLeftSelectionPosition = !isSmoothHandlePanEnabled;
1786 mEventData->mUpdateRightSelectionPosition = isSmoothHandlePanEnabled;
1787 mEventData->mLeftSelectionPosition = handlePosition;
1792 const bool differentHandles = ( mEventData->mRightSelectionPosition != handlePosition ) && ( mEventData->mLeftSelectionPosition != handlePosition );
1793 if( differentHandles || endOfScroll )
1795 mEventData->mUpdateHighlightBox = true;
1796 mEventData->mUpdateRightSelectionPosition = !isSmoothHandlePanEnabled;
1797 mEventData->mUpdateLeftSelectionPosition = isSmoothHandlePanEnabled;
1798 mEventData->mRightSelectionPosition = handlePosition;
1802 if( mEventData->mUpdateLeftSelectionPosition || mEventData->mUpdateRightSelectionPosition )
1804 RepositionSelectionHandles();
1806 mEventData->mScrollAfterUpdatePosition = !isSmoothHandlePanEnabled;
1809 mEventData->mDecoratorUpdated = true;
1810 } // end ( HANDLE_SCROLLING == state )
1813 void Controller::Impl::OnSelectEvent( const Event& event )
1815 if( NULL == mEventData )
1817 // Nothing to do if there is no text.
1821 if( mEventData->mSelectionEnabled )
1823 // Convert from control's coords to text's coords.
1824 const float xPosition = event.p2.mFloat - mModel->mScrollPosition.x;
1825 const float yPosition = event.p3.mFloat - mModel->mScrollPosition.y;
1827 // Calculates the logical position from the x,y coords.
1828 RepositionSelectionHandles( xPosition,
1830 Controller::NoTextTap::HIGHLIGHT );
1834 void Controller::Impl::OnSelectAllEvent()
1836 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "OnSelectAllEvent mEventData->mSelectionEnabled%s \n", mEventData->mSelectionEnabled?"true":"false");
1838 if( NULL == mEventData )
1840 // Nothing to do if there is no text.
1844 if( mEventData->mSelectionEnabled )
1846 // Calculates the logical position from the start.
1847 RepositionSelectionHandles( 0.f - mModel->mScrollPosition.x,
1848 0.f - mModel->mScrollPosition.y,
1849 Controller::NoTextTap::HIGHLIGHT );
1851 mEventData->mLeftSelectionPosition = 0u;
1852 mEventData->mRightSelectionPosition = mModel->mLogicalModel->mText.Count();
1856 void Controller::Impl::RetrieveSelection( std::string& selectedText, bool deleteAfterRetrieval )
1858 if( mEventData->mLeftSelectionPosition == mEventData->mRightSelectionPosition )
1860 // Nothing to select if handles are in the same place.
1861 selectedText.clear();
1865 const bool handlesCrossed = mEventData->mLeftSelectionPosition > mEventData->mRightSelectionPosition;
1867 //Get start and end position of selection
1868 const CharacterIndex startOfSelectedText = handlesCrossed ? mEventData->mRightSelectionPosition : mEventData->mLeftSelectionPosition;
1869 const Length lengthOfSelectedText = ( handlesCrossed ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition ) - startOfSelectedText;
1871 Vector<Character>& utf32Characters = mModel->mLogicalModel->mText;
1872 const Length numberOfCharacters = utf32Characters.Count();
1874 // Validate the start and end selection points
1875 if( ( startOfSelectedText + lengthOfSelectedText ) <= numberOfCharacters )
1877 //Get text as a UTF8 string
1878 Utf32ToUtf8( &utf32Characters[startOfSelectedText], lengthOfSelectedText, selectedText );
1880 if( deleteAfterRetrieval ) // Only delete text if copied successfully
1882 // Keep a copy of the current input style.
1883 InputStyle currentInputStyle;
1884 currentInputStyle.Copy( mEventData->mInputStyle );
1886 // Set as input style the style of the first deleted character.
1887 mModel->mLogicalModel->RetrieveStyle( startOfSelectedText, mEventData->mInputStyle );
1889 // Compare if the input style has changed.
1890 const bool hasInputStyleChanged = !currentInputStyle.Equal( mEventData->mInputStyle );
1892 if( hasInputStyleChanged )
1894 const InputStyle::Mask styleChangedMask = currentInputStyle.GetInputStyleChangeMask( mEventData->mInputStyle );
1895 // Queue the input style changed signal.
1896 mEventData->mInputStyleChangedQueue.PushBack( styleChangedMask );
1899 mModel->mLogicalModel->UpdateTextStyleRuns( startOfSelectedText, -static_cast<int>( lengthOfSelectedText ) );
1901 // Mark the paragraphs to be updated.
1902 if( Layout::Engine::SINGLE_LINE_BOX == mLayoutEngine.GetLayout() )
1904 mTextUpdateInfo.mCharacterIndex = 0;
1905 mTextUpdateInfo.mNumberOfCharactersToRemove = mTextUpdateInfo.mPreviousNumberOfCharacters;
1906 mTextUpdateInfo.mNumberOfCharactersToAdd = mTextUpdateInfo.mPreviousNumberOfCharacters - lengthOfSelectedText;
1907 mTextUpdateInfo.mClearAll = true;
1911 mTextUpdateInfo.mCharacterIndex = startOfSelectedText;
1912 mTextUpdateInfo.mNumberOfCharactersToRemove = lengthOfSelectedText;
1915 // Delete text between handles
1916 Vector<Character>::Iterator first = utf32Characters.Begin() + startOfSelectedText;
1917 Vector<Character>::Iterator last = first + lengthOfSelectedText;
1918 utf32Characters.Erase( first, last );
1920 // Will show the cursor at the first character of the selection.
1921 mEventData->mPrimaryCursorPosition = handlesCrossed ? mEventData->mRightSelectionPosition : mEventData->mLeftSelectionPosition;
1925 // Will show the cursor at the last character of the selection.
1926 mEventData->mPrimaryCursorPosition = handlesCrossed ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition;
1929 mEventData->mDecoratorUpdated = true;
1933 void Controller::Impl::ShowClipboard()
1937 mClipboard.ShowClipboard();
1941 void Controller::Impl::HideClipboard()
1943 if( mClipboard && mClipboardHideEnabled )
1945 mClipboard.HideClipboard();
1949 void Controller::Impl::SetClipboardHideEnable(bool enable)
1951 mClipboardHideEnabled = enable;
1954 bool Controller::Impl::CopyStringToClipboard( std::string& source )
1956 //Send string to clipboard
1957 return ( mClipboard && mClipboard.SetItem( source ) );
1960 void Controller::Impl::SendSelectionToClipboard( bool deleteAfterSending )
1962 std::string selectedText;
1963 RetrieveSelection( selectedText, deleteAfterSending );
1964 CopyStringToClipboard( selectedText );
1965 ChangeState( EventData::EDITING );
1968 void Controller::Impl::RequestGetTextFromClipboard()
1972 mClipboard.RequestItem();
1976 void Controller::Impl::RepositionSelectionHandles()
1978 CharacterIndex selectionStart = mEventData->mLeftSelectionPosition;
1979 CharacterIndex selectionEnd = mEventData->mRightSelectionPosition;
1981 if( selectionStart == selectionEnd )
1983 // Nothing to select if handles are in the same place.
1984 // So, deactive Highlight box.
1985 mEventData->mDecorator->SetHighlightActive( false );
1989 mEventData->mDecorator->ClearHighlights();
1991 const GlyphIndex* const charactersToGlyphBuffer = mModel->mVisualModel->mCharactersToGlyph.Begin();
1992 const Length* const glyphsPerCharacterBuffer = mModel->mVisualModel->mGlyphsPerCharacter.Begin();
1993 const GlyphInfo* const glyphsBuffer = mModel->mVisualModel->mGlyphs.Begin();
1994 const Vector2* const positionsBuffer = mModel->mVisualModel->mGlyphPositions.Begin();
1995 const Length* const charactersPerGlyphBuffer = mModel->mVisualModel->mCharactersPerGlyph.Begin();
1996 const CharacterIndex* const glyphToCharacterBuffer = mModel->mVisualModel->mGlyphsToCharacters.Begin();
1997 const CharacterDirection* const modelCharacterDirectionsBuffer = ( 0u != mModel->mLogicalModel->mCharacterDirections.Count() ) ? mModel->mLogicalModel->mCharacterDirections.Begin() : NULL;
1999 const bool isLastCharacter = selectionEnd >= mModel->mLogicalModel->mText.Count();
2000 const CharacterDirection startDirection = ( ( NULL == modelCharacterDirectionsBuffer ) ? false : *( modelCharacterDirectionsBuffer + selectionStart ) );
2001 const CharacterDirection endDirection = ( ( NULL == modelCharacterDirectionsBuffer ) ? false : *( modelCharacterDirectionsBuffer + ( selectionEnd - ( isLastCharacter ? 1u : 0u ) ) ) );
2003 // Swap the indices if the start is greater than the end.
2004 const bool indicesSwapped = selectionStart > selectionEnd;
2006 // Tell the decorator to flip the selection handles if needed.
2007 mEventData->mDecorator->SetSelectionHandleFlipState( indicesSwapped, startDirection, endDirection );
2009 if( indicesSwapped )
2011 std::swap( selectionStart, selectionEnd );
2014 // Get the indices to the first and last selected glyphs.
2015 const CharacterIndex selectionEndMinusOne = selectionEnd - 1u;
2016 const GlyphIndex glyphStart = *( charactersToGlyphBuffer + selectionStart );
2017 const Length numberOfGlyphs = *( glyphsPerCharacterBuffer + selectionEndMinusOne );
2018 const GlyphIndex glyphEnd = *( charactersToGlyphBuffer + selectionEndMinusOne ) + ( ( numberOfGlyphs > 0 ) ? numberOfGlyphs - 1u : 0u );
2020 // Get the lines where the glyphs are laid-out.
2021 const LineRun* lineRun = mModel->mVisualModel->mLines.Begin();
2023 LineIndex lineIndex = 0u;
2024 Length numberOfLines = 0u;
2025 mModel->mVisualModel->GetNumberOfLines( glyphStart,
2026 1u + glyphEnd - glyphStart,
2029 const LineIndex firstLineIndex = lineIndex;
2031 // Create the structure to store some selection box info.
2032 Vector<SelectionBoxInfo> selectionBoxLinesInfo;
2033 selectionBoxLinesInfo.Resize( numberOfLines );
2035 SelectionBoxInfo* selectionBoxInfo = selectionBoxLinesInfo.Begin();
2036 selectionBoxInfo->minX = MAX_FLOAT;
2037 selectionBoxInfo->maxX = MIN_FLOAT;
2039 // Keep the min and max 'x' position to calculate the size and position of the highlighed text.
2040 float minHighlightX = std::numeric_limits<float>::max();
2041 float maxHighlightX = std::numeric_limits<float>::min();
2043 Vector2 highLightPosition; // The highlight position in decorator's coords.
2045 // Retrieve the first line and get the line's vertical offset, the line's height and the index to the last glyph.
2047 // The line's vertical offset of all the lines before the line where the first glyph is laid-out.
2048 selectionBoxInfo->lineOffset = CalculateLineOffset( mModel->mVisualModel->mLines,
2051 // Transform to decorator's (control) coords.
2052 selectionBoxInfo->lineOffset += mModel->mScrollPosition.y;
2054 lineRun += firstLineIndex;
2056 // The line height is the addition of the line ascender and the line descender.
2057 // However, the line descender has a negative value, hence the subtraction.
2058 selectionBoxInfo->lineHeight = lineRun->ascender - lineRun->descender;
2060 GlyphIndex lastGlyphOfLine = lineRun->glyphRun.glyphIndex + lineRun->glyphRun.numberOfGlyphs - 1u;
2062 // Check if the first glyph is a ligature that must be broken like Latin ff, fi, or Arabic ï»», etc which needs special code.
2063 const Length numberOfCharactersStart = *( charactersPerGlyphBuffer + glyphStart );
2064 bool splitStartGlyph = ( numberOfCharactersStart > 1u ) && HasLigatureMustBreak( mModel->mLogicalModel->GetScript( selectionStart ) );
2066 // Check if the last glyph is a ligature that must be broken like Latin ff, fi, or Arabic ï»», etc which needs special code.
2067 const Length numberOfCharactersEnd = *( charactersPerGlyphBuffer + glyphEnd );
2068 bool splitEndGlyph = ( glyphStart != glyphEnd ) && ( numberOfCharactersEnd > 1u ) && HasLigatureMustBreak( mModel->mLogicalModel->GetScript( selectionEndMinusOne ) );
2070 // The number of quads of the selection box.
2071 const unsigned int numberOfQuads = 1u + ( glyphEnd - glyphStart ) + ( ( numberOfLines > 1u ) ? 2u * numberOfLines : 0u );
2072 mEventData->mDecorator->ResizeHighlightQuads( numberOfQuads );
2074 // Count the actual number of quads.
2075 unsigned int actualNumberOfQuads = 0u;
2078 // Traverse the glyphs.
2079 for( GlyphIndex index = glyphStart; index <= glyphEnd; ++index )
2081 const GlyphInfo& glyph = *( glyphsBuffer + index );
2082 const Vector2& position = *( positionsBuffer + index );
2084 if( splitStartGlyph )
2086 // 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.
2088 const float glyphAdvance = glyph.advance / static_cast<float>( numberOfCharactersStart );
2089 const CharacterIndex interGlyphIndex = selectionStart - *( glyphToCharacterBuffer + glyphStart );
2090 // Get the direction of the character.
2091 CharacterDirection isCurrentRightToLeft = false;
2092 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
2094 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + selectionStart );
2097 // The end point could be in the middle of the ligature.
2098 // Calculate the number of characters selected.
2099 const Length numberOfCharacters = ( glyphStart == glyphEnd ) ? ( selectionEnd - selectionStart ) : ( numberOfCharactersStart - interGlyphIndex );
2101 quad.x = lineRun->alignmentOffset + position.x - glyph.xBearing + mModel->mScrollPosition.x + glyphAdvance * static_cast<float>( isCurrentRightToLeft ? ( numberOfCharactersStart - interGlyphIndex - numberOfCharacters ) : interGlyphIndex );
2102 quad.y = selectionBoxInfo->lineOffset;
2103 quad.z = quad.x + static_cast<float>( numberOfCharacters ) * glyphAdvance;
2104 quad.w = selectionBoxInfo->lineOffset + selectionBoxInfo->lineHeight;
2106 // Store the min and max 'x' for each line.
2107 selectionBoxInfo->minX = std::min( selectionBoxInfo->minX, quad.x );
2108 selectionBoxInfo->maxX = std::max( selectionBoxInfo->maxX, quad.z );
2110 mEventData->mDecorator->AddHighlight( actualNumberOfQuads, quad );
2111 ++actualNumberOfQuads;
2113 splitStartGlyph = false;
2117 if( splitEndGlyph && ( index == glyphEnd ) )
2119 // 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.
2121 const float glyphAdvance = glyph.advance / static_cast<float>( numberOfCharactersEnd );
2122 const CharacterIndex interGlyphIndex = selectionEnd - *( glyphToCharacterBuffer + glyphEnd );
2123 // Get the direction of the character.
2124 CharacterDirection isCurrentRightToLeft = false;
2125 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
2127 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + selectionEnd );
2130 const Length numberOfCharacters = numberOfCharactersEnd - interGlyphIndex;
2132 quad.x = lineRun->alignmentOffset + position.x - glyph.xBearing + mModel->mScrollPosition.x + ( isCurrentRightToLeft ? ( glyphAdvance * static_cast<float>( numberOfCharacters ) ) : 0.f );
2133 quad.y = selectionBoxInfo->lineOffset;
2134 quad.z = quad.x + static_cast<float>( interGlyphIndex ) * glyphAdvance;
2135 quad.w = quad.y + selectionBoxInfo->lineHeight;
2137 // Store the min and max 'x' for each line.
2138 selectionBoxInfo->minX = std::min( selectionBoxInfo->minX, quad.x );
2139 selectionBoxInfo->maxX = std::max( selectionBoxInfo->maxX, quad.z );
2141 mEventData->mDecorator->AddHighlight( actualNumberOfQuads,
2143 ++actualNumberOfQuads;
2145 splitEndGlyph = false;
2149 quad.x = lineRun->alignmentOffset + position.x - glyph.xBearing + mModel->mScrollPosition.x;
2150 quad.y = selectionBoxInfo->lineOffset;
2151 quad.z = quad.x + glyph.advance;
2152 quad.w = quad.y + selectionBoxInfo->lineHeight;
2154 // Store the min and max 'x' for each line.
2155 selectionBoxInfo->minX = std::min( selectionBoxInfo->minX, quad.x );
2156 selectionBoxInfo->maxX = std::max( selectionBoxInfo->maxX, quad.z );
2158 mEventData->mDecorator->AddHighlight( actualNumberOfQuads,
2160 ++actualNumberOfQuads;
2162 // Whether to retrieve the next line.
2163 if( index == lastGlyphOfLine )
2166 if( lineIndex < firstLineIndex + numberOfLines )
2168 // Retrieve the next line.
2171 // Get the last glyph of the new line.
2172 lastGlyphOfLine = lineRun->glyphRun.glyphIndex + lineRun->glyphRun.numberOfGlyphs - 1u;
2174 // Keep the offset and height of the current selection box.
2175 const float currentLineOffset = selectionBoxInfo->lineOffset;
2176 const float currentLineHeight = selectionBoxInfo->lineHeight;
2178 // Get the selection box info for the next line.
2181 selectionBoxInfo->minX = MAX_FLOAT;
2182 selectionBoxInfo->maxX = MIN_FLOAT;
2184 // Update the line's vertical offset.
2185 selectionBoxInfo->lineOffset = currentLineOffset + currentLineHeight;
2187 // The line height is the addition of the line ascender and the line descender.
2188 // However, the line descender has a negative value, hence the subtraction.
2189 selectionBoxInfo->lineHeight = lineRun->ascender - lineRun->descender;
2194 // Traverses all the lines and updates the min and max 'x' positions and the total height.
2195 // The final width is calculated after 'boxifying' the selection.
2196 for( Vector<SelectionBoxInfo>::ConstIterator it = selectionBoxLinesInfo.Begin(),
2197 endIt = selectionBoxLinesInfo.End();
2201 const SelectionBoxInfo& info = *it;
2203 // Update the size of the highlighted text.
2204 highLightSize.height += info.lineHeight;
2205 minHighlightX = std::min( minHighlightX, info.minX );
2206 maxHighlightX = std::max( maxHighlightX, info.maxX );
2209 // Add extra geometry to 'boxify' the selection.
2211 if( 1u < numberOfLines )
2213 // Boxify the first line.
2214 lineRun = mModel->mVisualModel->mLines.Begin() + firstLineIndex;
2215 const SelectionBoxInfo& firstSelectionBoxLineInfo = *( selectionBoxLinesInfo.Begin() );
2217 bool boxifyBegin = ( LTR != lineRun->direction ) && ( LTR != startDirection );
2218 bool boxifyEnd = ( LTR == lineRun->direction ) && ( LTR == startDirection );
2223 quad.y = firstSelectionBoxLineInfo.lineOffset;
2224 quad.z = firstSelectionBoxLineInfo.minX;
2225 quad.w = firstSelectionBoxLineInfo.lineOffset + firstSelectionBoxLineInfo.lineHeight;
2227 // Boxify at the beginning of the line.
2228 mEventData->mDecorator->AddHighlight( actualNumberOfQuads,
2230 ++actualNumberOfQuads;
2232 // Update the size of the highlighted text.
2233 minHighlightX = 0.f;
2238 quad.x = firstSelectionBoxLineInfo.maxX;
2239 quad.y = firstSelectionBoxLineInfo.lineOffset;
2240 quad.z = mModel->mVisualModel->mControlSize.width;
2241 quad.w = firstSelectionBoxLineInfo.lineOffset + firstSelectionBoxLineInfo.lineHeight;
2243 // Boxify at the end of the line.
2244 mEventData->mDecorator->AddHighlight( actualNumberOfQuads,
2246 ++actualNumberOfQuads;
2248 // Update the size of the highlighted text.
2249 maxHighlightX = mModel->mVisualModel->mControlSize.width;
2252 // Boxify the central lines.
2253 if( 2u < numberOfLines )
2255 for( Vector<SelectionBoxInfo>::ConstIterator it = selectionBoxLinesInfo.Begin() + 1u,
2256 endIt = selectionBoxLinesInfo.End() - 1u;
2260 const SelectionBoxInfo& info = *it;
2263 quad.y = info.lineOffset;
2265 quad.w = info.lineOffset + info.lineHeight;
2267 mEventData->mDecorator->AddHighlight( actualNumberOfQuads,
2269 ++actualNumberOfQuads;
2272 quad.y = info.lineOffset;
2273 quad.z = mModel->mVisualModel->mControlSize.width;
2274 quad.w = info.lineOffset + info.lineHeight;
2276 mEventData->mDecorator->AddHighlight( actualNumberOfQuads,
2278 ++actualNumberOfQuads;
2281 // Update the size of the highlighted text.
2282 minHighlightX = 0.f;
2283 maxHighlightX = mModel->mVisualModel->mControlSize.width;
2286 // Boxify the last line.
2287 lineRun = mModel->mVisualModel->mLines.Begin() + firstLineIndex + numberOfLines - 1u;
2288 const SelectionBoxInfo& lastSelectionBoxLineInfo = *( selectionBoxLinesInfo.End() - 1u );
2290 boxifyBegin = ( LTR == lineRun->direction ) && ( LTR == endDirection );
2291 boxifyEnd = ( LTR != lineRun->direction ) && ( LTR != endDirection );
2296 quad.y = lastSelectionBoxLineInfo.lineOffset;
2297 quad.z = lastSelectionBoxLineInfo.minX;
2298 quad.w = lastSelectionBoxLineInfo.lineOffset + lastSelectionBoxLineInfo.lineHeight;
2300 // Boxify at the beginning of the line.
2301 mEventData->mDecorator->AddHighlight( actualNumberOfQuads,
2303 ++actualNumberOfQuads;
2305 // Update the size of the highlighted text.
2306 minHighlightX = 0.f;
2311 quad.x = lastSelectionBoxLineInfo.maxX;
2312 quad.y = lastSelectionBoxLineInfo.lineOffset;
2313 quad.z = mModel->mVisualModel->mControlSize.width;
2314 quad.w = lastSelectionBoxLineInfo.lineOffset + lastSelectionBoxLineInfo.lineHeight;
2316 // Boxify at the end of the line.
2317 mEventData->mDecorator->AddHighlight( actualNumberOfQuads,
2319 ++actualNumberOfQuads;
2321 // Update the size of the highlighted text.
2322 maxHighlightX = mModel->mVisualModel->mControlSize.width;
2326 // Set the actual number of quads.
2327 mEventData->mDecorator->ResizeHighlightQuads( actualNumberOfQuads );
2329 // Sets the highlight's size and position. In decorator's coords.
2330 // The highlight's height has been calculated above (before 'boxifying' the highlight).
2331 highLightSize.width = maxHighlightX - minHighlightX;
2333 highLightPosition.x = minHighlightX;
2334 const SelectionBoxInfo& firstSelectionBoxLineInfo = *( selectionBoxLinesInfo.Begin() );
2335 highLightPosition.y = firstSelectionBoxLineInfo.lineOffset;
2337 mEventData->mDecorator->SetHighLightBox( highLightPosition, highLightSize, static_cast<float>( mModel->GetOutlineWidth() ) );
2339 if( !mEventData->mDecorator->IsSmoothHandlePanEnabled() )
2341 CursorInfo primaryCursorInfo;
2342 GetCursorPosition( mEventData->mLeftSelectionPosition,
2343 primaryCursorInfo );
2345 const Vector2 primaryPosition = primaryCursorInfo.primaryPosition + mModel->mScrollPosition;
2347 mEventData->mDecorator->SetPosition( LEFT_SELECTION_HANDLE,
2349 primaryCursorInfo.lineOffset + mModel->mScrollPosition.y,
2350 primaryCursorInfo.lineHeight );
2352 CursorInfo secondaryCursorInfo;
2353 GetCursorPosition( mEventData->mRightSelectionPosition,
2354 secondaryCursorInfo );
2356 const Vector2 secondaryPosition = secondaryCursorInfo.primaryPosition + mModel->mScrollPosition;
2358 mEventData->mDecorator->SetPosition( RIGHT_SELECTION_HANDLE,
2359 secondaryPosition.x,
2360 secondaryCursorInfo.lineOffset + mModel->mScrollPosition.y,
2361 secondaryCursorInfo.lineHeight );
2364 // Set the flag to update the decorator.
2365 mEventData->mDecoratorUpdated = true;
2368 void Controller::Impl::RepositionSelectionHandles( float visualX, float visualY, Controller::NoTextTap::Action action )
2370 if( NULL == mEventData )
2372 // Nothing to do if there is no text input.
2376 if( IsShowingPlaceholderText() )
2378 // Nothing to do if there is the place-holder text.
2382 const Length numberOfGlyphs = mModel->mVisualModel->mGlyphs.Count();
2383 const Length numberOfLines = mModel->mVisualModel->mLines.Count();
2384 if( ( 0 == numberOfGlyphs ) ||
2385 ( 0 == numberOfLines ) )
2387 // Nothing to do if there is no text.
2391 // Find which word was selected
2392 CharacterIndex selectionStart( 0 );
2393 CharacterIndex selectionEnd( 0 );
2394 CharacterIndex noTextHitIndex( 0 );
2395 const bool characterHit = FindSelectionIndices( mModel->mVisualModel,
2396 mModel->mLogicalModel,
2403 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "%p selectionStart %d selectionEnd %d\n", this, selectionStart, selectionEnd );
2405 if( characterHit || ( Controller::NoTextTap::HIGHLIGHT == action ) )
2407 ChangeState( EventData::SELECTING );
2409 mEventData->mLeftSelectionPosition = selectionStart;
2410 mEventData->mRightSelectionPosition = selectionEnd;
2412 mEventData->mUpdateLeftSelectionPosition = true;
2413 mEventData->mUpdateRightSelectionPosition = true;
2414 mEventData->mUpdateHighlightBox = true;
2416 // It may happen an InputMethodContext commit event arrives before the selection event
2417 // if the InputMethodContext is in pre-edit state. The commit event will set the
2418 // mEventData->mUpdateCursorPosition flag to true. If it's not set back
2419 // to false, the highlight box won't be updated.
2420 mEventData->mUpdateCursorPosition = false;
2422 mEventData->mScrollAfterUpdatePosition = ( mEventData->mLeftSelectionPosition != mEventData->mRightSelectionPosition );
2424 // Cursor to be positioned at end of selection so if selection interrupted and edit mode restarted the cursor will be at end of selection
2425 mEventData->mPrimaryCursorPosition = std::max( mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition );
2427 else if( Controller::NoTextTap::SHOW_SELECTION_POPUP == action )
2429 // Nothing to select. i.e. a white space, out of bounds
2430 ChangeState( EventData::EDITING_WITH_POPUP );
2432 mEventData->mPrimaryCursorPosition = noTextHitIndex;
2434 mEventData->mUpdateCursorPosition = true;
2435 mEventData->mUpdateGrabHandlePosition = true;
2436 mEventData->mScrollAfterUpdatePosition = true;
2437 mEventData->mUpdateInputStyle = true;
2439 else if( Controller::NoTextTap::NO_ACTION == action )
2441 // Nothing to select. i.e. a white space, out of bounds
2442 mEventData->mPrimaryCursorPosition = noTextHitIndex;
2444 mEventData->mUpdateCursorPosition = true;
2445 mEventData->mUpdateGrabHandlePosition = true;
2446 mEventData->mScrollAfterUpdatePosition = true;
2447 mEventData->mUpdateInputStyle = true;
2451 void Controller::Impl::SetPopupButtons()
2454 * Sets the Popup buttons to be shown depending on State.
2456 * If SELECTING : CUT & COPY + ( PASTE & CLIPBOARD if content available to paste )
2458 * If EDITING_WITH_POPUP : SELECT & SELECT_ALL
2461 TextSelectionPopup::Buttons buttonsToShow = TextSelectionPopup::NONE;
2463 if( EventData::SELECTING == mEventData->mState )
2465 buttonsToShow = TextSelectionPopup::Buttons( TextSelectionPopup::CUT | TextSelectionPopup::COPY );
2467 if( !IsClipboardEmpty() )
2469 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
2470 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::CLIPBOARD ) );
2473 if( !mEventData->mAllTextSelected )
2475 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::SELECT_ALL ) );
2478 else if( EventData::EDITING_WITH_POPUP == mEventData->mState )
2480 if( mModel->mLogicalModel->mText.Count() && !IsShowingPlaceholderText() )
2482 buttonsToShow = TextSelectionPopup::Buttons( TextSelectionPopup::SELECT | TextSelectionPopup::SELECT_ALL );
2485 if( !IsClipboardEmpty() )
2487 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
2488 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::CLIPBOARD ) );
2491 else if( EventData::EDITING_WITH_PASTE_POPUP == mEventData->mState )
2493 if ( !IsClipboardEmpty() )
2495 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
2496 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::CLIPBOARD ) );
2500 mEventData->mDecorator->SetEnabledPopupButtons( buttonsToShow );
2503 void Controller::Impl::ChangeState( EventData::State newState )
2505 if( NULL == mEventData )
2507 // Nothing to do if there is no text input.
2511 DALI_LOG_INFO( gLogFilter, Debug::General, "ChangeState state:%d newstate:%d\n", mEventData->mState, newState );
2513 if( mEventData->mState != newState )
2515 mEventData->mPreviousState = mEventData->mState;
2516 mEventData->mState = newState;
2518 switch( mEventData->mState )
2520 case EventData::INACTIVE:
2522 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
2523 mEventData->mDecorator->StopCursorBlink();
2524 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
2525 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
2526 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
2527 mEventData->mDecorator->SetHighlightActive( false );
2528 mEventData->mDecorator->SetPopupActive( false );
2529 mEventData->mDecoratorUpdated = true;
2532 case EventData::INTERRUPTED:
2534 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
2535 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
2536 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
2537 mEventData->mDecorator->SetHighlightActive( false );
2538 mEventData->mDecorator->SetPopupActive( false );
2539 mEventData->mDecoratorUpdated = true;
2542 case EventData::SELECTING:
2544 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
2545 mEventData->mDecorator->StopCursorBlink();
2546 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
2547 if ( mEventData->mGrabHandleEnabled )
2549 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true );
2550 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true );
2552 mEventData->mDecorator->SetHighlightActive( true );
2553 if( mEventData->mGrabHandlePopupEnabled )
2556 mEventData->mDecorator->SetPopupActive( true );
2558 mEventData->mDecoratorUpdated = true;
2561 case EventData::EDITING:
2563 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
2564 if( mEventData->mCursorBlinkEnabled )
2566 mEventData->mDecorator->StartCursorBlink();
2568 // Grab handle is not shown until a tap is received whilst EDITING
2569 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
2570 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
2571 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
2572 mEventData->mDecorator->SetHighlightActive( false );
2573 if( mEventData->mGrabHandlePopupEnabled )
2575 mEventData->mDecorator->SetPopupActive( false );
2577 mEventData->mDecoratorUpdated = true;
2580 case EventData::EDITING_WITH_POPUP:
2582 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "EDITING_WITH_POPUP \n", newState );
2584 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
2585 if( mEventData->mCursorBlinkEnabled )
2587 mEventData->mDecorator->StartCursorBlink();
2589 if( mEventData->mSelectionEnabled )
2591 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
2592 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
2593 mEventData->mDecorator->SetHighlightActive( false );
2595 else if ( mEventData->mGrabHandleEnabled )
2597 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
2599 if( mEventData->mGrabHandlePopupEnabled )
2602 mEventData->mDecorator->SetPopupActive( true );
2604 mEventData->mDecoratorUpdated = true;
2607 case EventData::EDITING_WITH_GRAB_HANDLE:
2609 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "EDITING_WITH_GRAB_HANDLE \n", newState );
2611 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
2612 if( mEventData->mCursorBlinkEnabled )
2614 mEventData->mDecorator->StartCursorBlink();
2616 // Grab handle is not shown until a tap is received whilst EDITING
2617 if ( mEventData->mGrabHandleEnabled )
2619 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
2621 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
2622 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
2623 mEventData->mDecorator->SetHighlightActive( false );
2624 if( mEventData->mGrabHandlePopupEnabled )
2626 mEventData->mDecorator->SetPopupActive( false );
2628 mEventData->mDecoratorUpdated = true;
2631 case EventData::SELECTION_HANDLE_PANNING:
2633 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
2634 mEventData->mDecorator->StopCursorBlink();
2635 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
2636 if ( mEventData->mGrabHandleEnabled )
2638 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true );
2639 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true );
2641 mEventData->mDecorator->SetHighlightActive( true );
2642 if( mEventData->mGrabHandlePopupEnabled )
2644 mEventData->mDecorator->SetPopupActive( false );
2646 mEventData->mDecoratorUpdated = true;
2649 case EventData::GRAB_HANDLE_PANNING:
2651 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "GRAB_HANDLE_PANNING \n", newState );
2653 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
2654 if( mEventData->mCursorBlinkEnabled )
2656 mEventData->mDecorator->StartCursorBlink();
2658 if ( mEventData->mGrabHandleEnabled )
2660 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
2662 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
2663 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
2664 mEventData->mDecorator->SetHighlightActive( false );
2665 if( mEventData->mGrabHandlePopupEnabled )
2667 mEventData->mDecorator->SetPopupActive( false );
2669 mEventData->mDecoratorUpdated = true;
2672 case EventData::EDITING_WITH_PASTE_POPUP:
2674 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "EDITING_WITH_PASTE_POPUP \n", newState );
2676 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
2677 if( mEventData->mCursorBlinkEnabled )
2679 mEventData->mDecorator->StartCursorBlink();
2682 if ( mEventData->mGrabHandleEnabled )
2684 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
2686 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
2687 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
2688 mEventData->mDecorator->SetHighlightActive( false );
2690 if( mEventData->mGrabHandlePopupEnabled )
2693 mEventData->mDecorator->SetPopupActive( true );
2695 mEventData->mDecoratorUpdated = true;
2698 case EventData::TEXT_PANNING:
2700 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
2701 mEventData->mDecorator->StopCursorBlink();
2702 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
2703 if( mEventData->mDecorator->IsHandleActive( LEFT_SELECTION_HANDLE ) ||
2704 mEventData->mDecorator->IsHandleActive( RIGHT_SELECTION_HANDLE ) )
2706 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
2707 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
2708 mEventData->mDecorator->SetHighlightActive( true );
2711 if( mEventData->mGrabHandlePopupEnabled )
2713 mEventData->mDecorator->SetPopupActive( false );
2716 mEventData->mDecoratorUpdated = true;
2723 void Controller::Impl::GetCursorPosition( CharacterIndex logical,
2724 CursorInfo& cursorInfo )
2726 if( !IsShowingRealText() )
2728 // Do not want to use the place-holder text to set the cursor position.
2730 // Use the line's height of the font's family set to set the cursor's size.
2731 // If there is no font's family set, use the default font.
2732 // Use the current alignment to place the cursor at the beginning, center or end of the box.
2734 cursorInfo.lineOffset = 0.f;
2735 cursorInfo.lineHeight = GetDefaultFontLineHeight();
2736 cursorInfo.primaryCursorHeight = cursorInfo.lineHeight;
2739 if( mModel->mMatchSystemLanguageDirection )
2741 isRTL = mLayoutDirection == LayoutDirection::RIGHT_TO_LEFT;
2744 switch( mModel->mHorizontalAlignment )
2746 case Text::HorizontalAlignment::BEGIN :
2750 cursorInfo.primaryPosition.x = mModel->mVisualModel->mControlSize.width - mEventData->mDecorator->GetCursorWidth();
2754 cursorInfo.primaryPosition.x = 0.f;
2758 case Text::HorizontalAlignment::CENTER:
2760 cursorInfo.primaryPosition.x = floorf( 0.5f * mModel->mVisualModel->mControlSize.width );
2763 case Text::HorizontalAlignment::END:
2767 cursorInfo.primaryPosition.x = 0.f;
2771 cursorInfo.primaryPosition.x = mModel->mVisualModel->mControlSize.width - mEventData->mDecorator->GetCursorWidth();
2777 // Nothing else to do.
2781 const bool isMultiLine = ( Layout::Engine::MULTI_LINE_BOX == mLayoutEngine.GetLayout() );
2782 GetCursorPositionParameters parameters;
2783 parameters.visualModel = mModel->mVisualModel;
2784 parameters.logicalModel = mModel->mLogicalModel;
2785 parameters.metrics = mMetrics;
2786 parameters.logical = logical;
2787 parameters.isMultiline = isMultiLine;
2789 Text::GetCursorPosition( parameters,
2792 // Adds Outline offset.
2793 const float outlineWidth = static_cast<float>( mModel->GetOutlineWidth() );
2794 cursorInfo.primaryPosition.x += outlineWidth;
2795 cursorInfo.primaryPosition.y += outlineWidth;
2796 cursorInfo.secondaryPosition.x += outlineWidth;
2797 cursorInfo.secondaryPosition.y += outlineWidth;
2801 // If the text is editable and multi-line, the cursor position after a white space shouldn't exceed the boundaries of the text control.
2803 // Note the white spaces laid-out at the end of the line might exceed the boundaries of the control.
2804 // The reason is a wrapped line must not start with a white space so they are laid-out at the end of the line.
2806 if( 0.f > cursorInfo.primaryPosition.x )
2808 cursorInfo.primaryPosition.x = 0.f;
2811 const float edgeWidth = mModel->mVisualModel->mControlSize.width - static_cast<float>( mEventData->mDecorator->GetCursorWidth() );
2812 if( cursorInfo.primaryPosition.x > edgeWidth )
2814 cursorInfo.primaryPosition.x = edgeWidth;
2819 CharacterIndex Controller::Impl::CalculateNewCursorIndex( CharacterIndex index ) const
2821 if( NULL == mEventData )
2823 // Nothing to do if there is no text input.
2827 CharacterIndex cursorIndex = mEventData->mPrimaryCursorPosition;
2829 const GlyphIndex* const charactersToGlyphBuffer = mModel->mVisualModel->mCharactersToGlyph.Begin();
2830 const Length* const charactersPerGlyphBuffer = mModel->mVisualModel->mCharactersPerGlyph.Begin();
2832 GlyphIndex glyphIndex = *( charactersToGlyphBuffer + index );
2833 Length numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
2835 if( numberOfCharacters > 1u )
2837 const Script script = mModel->mLogicalModel->GetScript( index );
2838 if( HasLigatureMustBreak( script ) )
2840 // Prevents to jump the whole Latin ligatures like fi, ff, or Arabic ï»», ...
2841 numberOfCharacters = 1u;
2846 while( 0u == numberOfCharacters )
2849 numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
2853 if( index < mEventData->mPrimaryCursorPosition )
2855 cursorIndex -= numberOfCharacters;
2859 cursorIndex += numberOfCharacters;
2862 // Will update the cursor hook position.
2863 mEventData->mUpdateCursorHookPosition = true;
2868 void Controller::Impl::UpdateCursorPosition( const CursorInfo& cursorInfo )
2870 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "-->Controller::UpdateCursorPosition %p\n", this );
2871 if( NULL == mEventData )
2873 // Nothing to do if there is no text input.
2874 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition no event data\n" );
2878 const Vector2 cursorPosition = cursorInfo.primaryPosition + mModel->mScrollPosition;
2880 mEventData->mDecorator->SetGlyphOffset( PRIMARY_CURSOR, cursorInfo.glyphOffset );
2882 // Sets the cursor position.
2883 mEventData->mDecorator->SetPosition( PRIMARY_CURSOR,
2886 cursorInfo.primaryCursorHeight,
2887 cursorInfo.lineHeight );
2888 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Primary cursor position: %f,%f\n", cursorPosition.x, cursorPosition.y );
2890 if( mEventData->mUpdateGrabHandlePosition )
2892 // Sets the grab handle position.
2893 mEventData->mDecorator->SetPosition( GRAB_HANDLE,
2895 cursorInfo.lineOffset + mModel->mScrollPosition.y,
2896 cursorInfo.lineHeight );
2899 if( cursorInfo.isSecondaryCursor )
2901 mEventData->mDecorator->SetPosition( SECONDARY_CURSOR,
2902 cursorInfo.secondaryPosition.x + mModel->mScrollPosition.x,
2903 cursorInfo.secondaryPosition.y + mModel->mScrollPosition.y,
2904 cursorInfo.secondaryCursorHeight,
2905 cursorInfo.lineHeight );
2906 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Secondary cursor position: %f,%f\n", cursorInfo.secondaryPosition.x + mModel->mScrollPosition.x, cursorInfo.secondaryPosition.y + mModel->mScrollPosition.y );
2909 // Set which cursors are active according the state.
2910 if( EventData::IsEditingState( mEventData->mState ) || ( EventData::GRAB_HANDLE_PANNING == mEventData->mState ) )
2912 if( cursorInfo.isSecondaryCursor )
2914 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_BOTH );
2918 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
2923 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
2926 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition\n" );
2929 void Controller::Impl::UpdateSelectionHandle( HandleType handleType,
2930 const CursorInfo& cursorInfo )
2932 if( ( LEFT_SELECTION_HANDLE != handleType ) &&
2933 ( RIGHT_SELECTION_HANDLE != handleType ) )
2938 const Vector2 cursorPosition = cursorInfo.primaryPosition + mModel->mScrollPosition;
2940 // Sets the handle's position.
2941 mEventData->mDecorator->SetPosition( handleType,
2943 cursorInfo.lineOffset + mModel->mScrollPosition.y,
2944 cursorInfo.lineHeight );
2946 // If selection handle at start of the text and other at end of the text then all text is selected.
2947 const CharacterIndex startOfSelection = std::min( mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition );
2948 const CharacterIndex endOfSelection = std::max ( mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition );
2949 mEventData->mAllTextSelected = ( startOfSelection == 0 ) && ( endOfSelection == mModel->mLogicalModel->mText.Count() );
2952 void Controller::Impl::ClampHorizontalScroll( const Vector2& layoutSize )
2954 // Clamp between -space & -alignment offset.
2956 if( layoutSize.width > mModel->mVisualModel->mControlSize.width )
2958 const float space = ( layoutSize.width - mModel->mVisualModel->mControlSize.width ) + mModel->mAlignmentOffset;
2959 mModel->mScrollPosition.x = ( mModel->mScrollPosition.x < -space ) ? -space : mModel->mScrollPosition.x;
2960 mModel->mScrollPosition.x = ( mModel->mScrollPosition.x > -mModel->mAlignmentOffset ) ? -mModel->mAlignmentOffset : mModel->mScrollPosition.x;
2962 mEventData->mDecoratorUpdated = true;
2966 mModel->mScrollPosition.x = 0.f;
2970 void Controller::Impl::ClampVerticalScroll( const Vector2& layoutSize )
2972 if( Layout::Engine::SINGLE_LINE_BOX == mLayoutEngine.GetLayout() )
2974 // Nothing to do if the text is single line.
2978 // Clamp between -space & 0.
2979 if( layoutSize.height > mModel->mVisualModel->mControlSize.height )
2981 const float space = ( layoutSize.height - mModel->mVisualModel->mControlSize.height );
2982 mModel->mScrollPosition.y = ( mModel->mScrollPosition.y < -space ) ? -space : mModel->mScrollPosition.y;
2983 mModel->mScrollPosition.y = ( mModel->mScrollPosition.y > 0.f ) ? 0.f : mModel->mScrollPosition.y;
2985 mEventData->mDecoratorUpdated = true;
2989 mModel->mScrollPosition.y = 0.f;
2993 void Controller::Impl::ScrollToMakePositionVisible( const Vector2& position, float lineHeight )
2995 const float cursorWidth = mEventData->mDecorator ? static_cast<float>( mEventData->mDecorator->GetCursorWidth() ) : 0.f;
2997 // position is in actor's coords.
2998 const float positionEndX = position.x + cursorWidth;
2999 const float positionEndY = position.y + lineHeight;
3001 // Transform the position to decorator coords.
3002 const float decoratorPositionBeginX = position.x + mModel->mScrollPosition.x;
3003 const float decoratorPositionEndX = positionEndX + mModel->mScrollPosition.x;
3005 const float decoratorPositionBeginY = position.y + mModel->mScrollPosition.y;
3006 const float decoratorPositionEndY = positionEndY + mModel->mScrollPosition.y;
3008 if( decoratorPositionBeginX < 0.f )
3010 mModel->mScrollPosition.x = -position.x;
3012 else if( decoratorPositionEndX > mModel->mVisualModel->mControlSize.width )
3014 mModel->mScrollPosition.x = mModel->mVisualModel->mControlSize.width - positionEndX;
3017 if( Layout::Engine::MULTI_LINE_BOX == mLayoutEngine.GetLayout() )
3019 if( decoratorPositionBeginY < 0.f )
3021 mModel->mScrollPosition.y = -position.y;
3023 else if( decoratorPositionEndY > mModel->mVisualModel->mControlSize.height )
3025 mModel->mScrollPosition.y = mModel->mVisualModel->mControlSize.height - positionEndY;
3030 void Controller::Impl::ScrollTextToMatchCursor( const CursorInfo& cursorInfo )
3032 // Get the current cursor position in decorator coords.
3033 const Vector2& currentCursorPosition = mEventData->mDecorator->GetPosition( PRIMARY_CURSOR );
3035 const LineIndex lineIndex = mModel->mVisualModel->GetLineOfCharacter( mEventData->mPrimaryCursorPosition );
3039 // Calculate the offset to match the cursor position before the character was deleted.
3040 mModel->mScrollPosition.x = currentCursorPosition.x - cursorInfo.primaryPosition.x;
3042 //If text control has more than two lines and current line index is not last, calculate scrollpositionY
3043 if( mModel->mVisualModel->mLines.Count() > 1u && lineIndex != mModel->mVisualModel->mLines.Count() -1u )
3045 const float currentCursorGlyphOffset = mEventData->mDecorator->GetGlyphOffset( PRIMARY_CURSOR );
3046 mModel->mScrollPosition.y = currentCursorPosition.y - cursorInfo.lineOffset - currentCursorGlyphOffset;
3050 ClampHorizontalScroll( mModel->mVisualModel->GetLayoutSize() );
3051 ClampVerticalScroll( mModel->mVisualModel->GetLayoutSize() );
3053 // Makes the new cursor position visible if needed.
3054 ScrollToMakePositionVisible( cursorInfo.primaryPosition, cursorInfo.lineHeight );
3057 void Controller::Impl::RequestRelayout()
3059 if( NULL != mControlInterface )
3061 mControlInterface->RequestTextRelayout();
3067 } // namespace Toolkit