2 * Copyright (c) 2016 Samsung Electronics Co., Ltd.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
19 #include <dali-toolkit/internal/text/text-controller-impl.h>
22 #include <dali/public-api/adaptor-framework/key.h>
23 #include <dali/integration-api/debug.h>
27 #include <dali-toolkit/internal/text/bidirectional-support.h>
28 #include <dali-toolkit/internal/text/character-set-conversion.h>
29 #include <dali-toolkit/internal/text/color-segmentation.h>
30 #include <dali-toolkit/internal/text/cursor-helper-functions.h>
31 #include <dali-toolkit/internal/text/multi-language-support.h>
32 #include <dali-toolkit/internal/text/segmentation.h>
33 #include <dali-toolkit/internal/text/shaper.h>
34 #include <dali-toolkit/internal/text/text-control-interface.h>
35 #include <dali-toolkit/internal/text/text-run-container.h>
41 * @brief Struct used to calculate the selection box.
43 struct SelectionBoxInfo
51 #if defined(DEBUG_ENABLED)
52 Debug::Filter* gLogFilter = Debug::Filter::New(Debug::NoLogging, true, "LOG_TEXT_CONTROLS");
55 const float MAX_FLOAT = std::numeric_limits<float>::max();
56 const float MIN_FLOAT = std::numeric_limits<float>::min();
57 const Dali::Toolkit::Text::CharacterDirection LTR = false; ///< Left To Right direction
70 EventData::EventData( DecoratorPtr decorator )
71 : mDecorator( decorator ),
73 mPlaceholderTextActive(),
74 mPlaceholderTextInactive(),
75 mPlaceholderTextColor( 0.8f, 0.8f, 0.8f, 0.8f ),
77 mInputStyleChangedQueue(),
78 mPreviousState( INACTIVE ),
80 mPrimaryCursorPosition( 0u ),
81 mLeftSelectionPosition( 0u ),
82 mRightSelectionPosition( 0u ),
83 mPreEditStartPosition( 0u ),
85 mCursorHookPositionX( 0.f ),
86 mIsShowingPlaceholderText( false ),
87 mPreEditFlag( false ),
88 mDecoratorUpdated( false ),
89 mCursorBlinkEnabled( true ),
90 mGrabHandleEnabled( true ),
91 mGrabHandlePopupEnabled( true ),
92 mSelectionEnabled( true ),
93 mUpdateCursorHookPosition( false ),
94 mUpdateCursorPosition( false ),
95 mUpdateGrabHandlePosition( false ),
96 mUpdateLeftSelectionPosition( false ),
97 mUpdateRightSelectionPosition( false ),
98 mIsLeftHandleSelected( false ),
99 mUpdateHighlightBox( false ),
100 mScrollAfterUpdatePosition( false ),
101 mScrollAfterDelete( false ),
102 mAllTextSelected( false ),
103 mUpdateInputStyle( false )
105 mImfManager = ImfManager::Get();
108 EventData::~EventData()
111 bool Controller::Impl::ProcessInputEvents()
113 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "-->Controller::ProcessInputEvents\n" );
114 if( NULL == mEventData )
116 // Nothing to do if there is no text input.
117 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::ProcessInputEvents no event data\n" );
121 if( mEventData->mDecorator )
123 for( std::vector<Event>::iterator iter = mEventData->mEventQueue.begin();
124 iter != mEventData->mEventQueue.end();
129 case Event::CURSOR_KEY_EVENT:
131 OnCursorKeyEvent( *iter );
134 case Event::TAP_EVENT:
139 case Event::LONG_PRESS_EVENT:
141 OnLongPressEvent( *iter );
144 case Event::PAN_EVENT:
149 case Event::GRAB_HANDLE_EVENT:
150 case Event::LEFT_SELECTION_HANDLE_EVENT:
151 case Event::RIGHT_SELECTION_HANDLE_EVENT: // Fall through
153 OnHandleEvent( *iter );
158 OnSelectEvent( *iter );
161 case Event::SELECT_ALL:
170 if( mEventData->mUpdateCursorPosition ||
171 mEventData->mUpdateHighlightBox )
176 // The cursor must also be repositioned after inserts into the model
177 if( mEventData->mUpdateCursorPosition )
179 // Updates the cursor position and scrolls the text to make it visible.
180 CursorInfo cursorInfo;
181 // Calculate the cursor position from the new cursor index.
182 GetCursorPosition( mEventData->mPrimaryCursorPosition,
185 if( mEventData->mUpdateCursorHookPosition )
187 // Update the cursor hook position. Used to move the cursor with the keys 'up' and 'down'.
188 mEventData->mCursorHookPositionX = cursorInfo.primaryPosition.x;
189 mEventData->mUpdateCursorHookPosition = false;
192 // Scroll first the text after delete ...
193 if( mEventData->mScrollAfterDelete )
195 ScrollTextToMatchCursor( cursorInfo );
198 // ... then, text can be scrolled to make the cursor visible.
199 if( mEventData->mScrollAfterUpdatePosition )
201 const Vector2 currentCursorPosition( cursorInfo.primaryPosition.x, cursorInfo.lineOffset );
202 ScrollToMakePositionVisible( currentCursorPosition, cursorInfo.lineHeight );
204 mEventData->mScrollAfterUpdatePosition = false;
205 mEventData->mScrollAfterDelete = false;
207 UpdateCursorPosition( cursorInfo );
209 mEventData->mDecoratorUpdated = true;
210 mEventData->mUpdateCursorPosition = false;
211 mEventData->mUpdateGrabHandlePosition = false;
215 CursorInfo leftHandleInfo;
216 CursorInfo rightHandleInfo;
218 if( mEventData->mUpdateHighlightBox )
220 GetCursorPosition( mEventData->mLeftSelectionPosition,
223 GetCursorPosition( mEventData->mRightSelectionPosition,
226 if( mEventData->mScrollAfterUpdatePosition && ( mEventData->mIsLeftHandleSelected ? mEventData->mUpdateLeftSelectionPosition : mEventData->mUpdateRightSelectionPosition ) )
228 CursorInfo& info = mEventData->mIsLeftHandleSelected ? leftHandleInfo : rightHandleInfo;
230 const Vector2 currentCursorPosition( info.primaryPosition.x, info.lineOffset );
231 ScrollToMakePositionVisible( currentCursorPosition, info.lineHeight );
235 if( mEventData->mUpdateLeftSelectionPosition )
237 UpdateSelectionHandle( LEFT_SELECTION_HANDLE,
241 mEventData->mDecoratorUpdated = true;
242 mEventData->mUpdateLeftSelectionPosition = false;
245 if( mEventData->mUpdateRightSelectionPosition )
247 UpdateSelectionHandle( RIGHT_SELECTION_HANDLE,
251 mEventData->mDecoratorUpdated = true;
252 mEventData->mUpdateRightSelectionPosition = false;
255 if( mEventData->mUpdateHighlightBox )
257 RepositionSelectionHandles();
259 mEventData->mUpdateLeftSelectionPosition = false;
260 mEventData->mUpdateRightSelectionPosition = false;
261 mEventData->mUpdateHighlightBox = false;
264 mEventData->mScrollAfterUpdatePosition = false;
267 if( mEventData->mUpdateInputStyle )
269 // Keep a copy of the current input style.
270 InputStyle currentInputStyle;
271 currentInputStyle.Copy( mEventData->mInputStyle );
273 // Set the default style first.
274 RetrieveDefaultInputStyle( mEventData->mInputStyle );
276 // Get the character index from the cursor index.
277 const CharacterIndex styleIndex = ( mEventData->mPrimaryCursorPosition > 0u ) ? mEventData->mPrimaryCursorPosition - 1u : 0u;
279 // Retrieve the style from the style runs stored in the logical model.
280 mModel->mLogicalModel->RetrieveStyle( styleIndex, mEventData->mInputStyle );
282 // Compare if the input style has changed.
283 const bool hasInputStyleChanged = !currentInputStyle.Equal( mEventData->mInputStyle );
285 if( hasInputStyleChanged )
287 const InputStyle::Mask styleChangedMask = currentInputStyle.GetInputStyleChangeMask( mEventData->mInputStyle );
288 // Queue the input style changed signal.
289 mEventData->mInputStyleChangedQueue.PushBack( styleChangedMask );
292 mEventData->mUpdateInputStyle = false;
295 mEventData->mEventQueue.clear();
297 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::ProcessInputEvents\n" );
299 const bool decoratorUpdated = mEventData->mDecoratorUpdated;
300 mEventData->mDecoratorUpdated = false;
302 return decoratorUpdated;
305 void Controller::Impl::NotifyImfManager()
307 if( mEventData && mEventData->mImfManager )
309 CharacterIndex cursorPosition = GetLogicalCursorPosition();
311 const Length numberOfWhiteSpaces = GetNumberOfWhiteSpaces( 0u );
313 // Update the cursor position by removing the initial white spaces.
314 if( cursorPosition < numberOfWhiteSpaces )
320 cursorPosition -= numberOfWhiteSpaces;
323 mEventData->mImfManager.SetCursorPosition( cursorPosition );
324 mEventData->mImfManager.NotifyCursorPosition();
328 void Controller::Impl::NotifyImfMultiLineStatus()
332 Text::Layout::Engine::Type layout = mLayoutEngine.GetLayout();
333 mEventData->mImfManager.NotifyTextInputMultiLine( layout == Text::Layout::Engine::MULTI_LINE_BOX );
337 CharacterIndex Controller::Impl::GetLogicalCursorPosition() const
339 CharacterIndex cursorPosition = 0u;
343 if( ( EventData::SELECTING == mEventData->mState ) ||
344 ( EventData::SELECTION_HANDLE_PANNING == mEventData->mState ) )
346 cursorPosition = std::min( mEventData->mRightSelectionPosition, mEventData->mLeftSelectionPosition );
350 cursorPosition = mEventData->mPrimaryCursorPosition;
354 return cursorPosition;
357 Length Controller::Impl::GetNumberOfWhiteSpaces( CharacterIndex index ) const
359 Length numberOfWhiteSpaces = 0u;
361 // Get the buffer to the text.
362 Character* utf32CharacterBuffer = mModel->mLogicalModel->mText.Begin();
364 const Length totalNumberOfCharacters = mModel->mLogicalModel->mText.Count();
365 for( ; index < totalNumberOfCharacters; ++index, ++numberOfWhiteSpaces )
367 if( !TextAbstraction::IsWhiteSpace( *( utf32CharacterBuffer + index ) ) )
373 return numberOfWhiteSpaces;
376 void Controller::Impl::GetText( CharacterIndex index, std::string& text ) const
378 // Get the total number of characters.
379 Length numberOfCharacters = mModel->mLogicalModel->mText.Count();
381 // Retrieve the text.
382 if( 0u != numberOfCharacters )
384 Utf32ToUtf8( mModel->mLogicalModel->mText.Begin() + index, numberOfCharacters - index, text );
388 void Controller::Impl::CalculateTextUpdateIndices( Length& numberOfCharacters )
390 mTextUpdateInfo.mParagraphCharacterIndex = 0u;
391 mTextUpdateInfo.mStartGlyphIndex = 0u;
392 mTextUpdateInfo.mStartLineIndex = 0u;
393 numberOfCharacters = 0u;
395 const Length numberOfParagraphs = mModel->mLogicalModel->mParagraphInfo.Count();
396 if( 0u == numberOfParagraphs )
398 mTextUpdateInfo.mParagraphCharacterIndex = 0u;
399 numberOfCharacters = 0u;
401 mTextUpdateInfo.mRequestedNumberOfCharacters = mTextUpdateInfo.mNumberOfCharactersToAdd - mTextUpdateInfo.mNumberOfCharactersToRemove;
403 // Nothing else to do if there are no paragraphs.
407 // Find the paragraphs to be updated.
408 Vector<ParagraphRunIndex> paragraphsToBeUpdated;
409 if( mTextUpdateInfo.mCharacterIndex >= mTextUpdateInfo.mPreviousNumberOfCharacters )
411 // Text is being added at the end of the current text.
412 if( mTextUpdateInfo.mIsLastCharacterNewParagraph )
414 // Text is being added in a new paragraph after the last character of the text.
415 mTextUpdateInfo.mParagraphCharacterIndex = mTextUpdateInfo.mPreviousNumberOfCharacters;
416 numberOfCharacters = 0u;
417 mTextUpdateInfo.mRequestedNumberOfCharacters = mTextUpdateInfo.mNumberOfCharactersToAdd - mTextUpdateInfo.mNumberOfCharactersToRemove;
419 mTextUpdateInfo.mStartGlyphIndex = mModel->mVisualModel->mGlyphs.Count();
420 mTextUpdateInfo.mStartLineIndex = mModel->mVisualModel->mLines.Count() - 1u;
422 // Nothing else to do;
426 paragraphsToBeUpdated.PushBack( numberOfParagraphs - 1u );
430 Length numberOfCharactersToUpdate = 0u;
431 if( mTextUpdateInfo.mFullRelayoutNeeded )
433 numberOfCharactersToUpdate = mTextUpdateInfo.mPreviousNumberOfCharacters;
437 numberOfCharactersToUpdate = ( mTextUpdateInfo.mNumberOfCharactersToRemove > 0u ) ? mTextUpdateInfo.mNumberOfCharactersToRemove : 1u;
439 mModel->mLogicalModel->FindParagraphs( mTextUpdateInfo.mCharacterIndex,
440 numberOfCharactersToUpdate,
441 paragraphsToBeUpdated );
444 if( 0u != paragraphsToBeUpdated.Count() )
446 const ParagraphRunIndex firstParagraphIndex = *( paragraphsToBeUpdated.Begin() );
447 const ParagraphRun& firstParagraph = *( mModel->mLogicalModel->mParagraphInfo.Begin() + firstParagraphIndex );
448 mTextUpdateInfo.mParagraphCharacterIndex = firstParagraph.characterRun.characterIndex;
450 ParagraphRunIndex lastParagraphIndex = *( paragraphsToBeUpdated.End() - 1u );
451 const ParagraphRun& lastParagraph = *( mModel->mLogicalModel->mParagraphInfo.Begin() + lastParagraphIndex );
453 if( ( mTextUpdateInfo.mNumberOfCharactersToRemove > 0u ) && // Some character are removed.
454 ( lastParagraphIndex < numberOfParagraphs - 1u ) && // There is a next paragraph.
455 ( ( lastParagraph.characterRun.characterIndex + lastParagraph.characterRun.numberOfCharacters ) == // The last removed character is the new paragraph character.
456 ( mTextUpdateInfo.mCharacterIndex + mTextUpdateInfo.mNumberOfCharactersToRemove ) ) )
458 // The new paragraph character of the last updated paragraph has been removed so is going to be merged with the next one.
459 const ParagraphRun& lastParagraph = *( mModel->mLogicalModel->mParagraphInfo.Begin() + lastParagraphIndex + 1u );
461 numberOfCharacters = lastParagraph.characterRun.characterIndex + lastParagraph.characterRun.numberOfCharacters - mTextUpdateInfo.mParagraphCharacterIndex;
465 numberOfCharacters = lastParagraph.characterRun.characterIndex + lastParagraph.characterRun.numberOfCharacters - mTextUpdateInfo.mParagraphCharacterIndex;
469 mTextUpdateInfo.mRequestedNumberOfCharacters = numberOfCharacters + mTextUpdateInfo.mNumberOfCharactersToAdd - mTextUpdateInfo.mNumberOfCharactersToRemove;
470 mTextUpdateInfo.mStartGlyphIndex = *( mModel->mVisualModel->mCharactersToGlyph.Begin() + mTextUpdateInfo.mParagraphCharacterIndex );
473 void Controller::Impl::ClearFullModelData( OperationsMask operations )
475 if( NO_OPERATION != ( GET_LINE_BREAKS & operations ) )
477 mModel->mLogicalModel->mLineBreakInfo.Clear();
478 mModel->mLogicalModel->mParagraphInfo.Clear();
481 if( NO_OPERATION != ( GET_WORD_BREAKS & operations ) )
483 mModel->mLogicalModel->mLineBreakInfo.Clear();
486 if( NO_OPERATION != ( GET_SCRIPTS & operations ) )
488 mModel->mLogicalModel->mScriptRuns.Clear();
491 if( NO_OPERATION != ( VALIDATE_FONTS & operations ) )
493 mModel->mLogicalModel->mFontRuns.Clear();
496 if( 0u != mModel->mLogicalModel->mBidirectionalParagraphInfo.Count() )
498 if( NO_OPERATION != ( BIDI_INFO & operations ) )
500 mModel->mLogicalModel->mBidirectionalParagraphInfo.Clear();
501 mModel->mLogicalModel->mCharacterDirections.Clear();
504 if( NO_OPERATION != ( REORDER & operations ) )
506 // Free the allocated memory used to store the conversion table in the bidirectional line info run.
507 for( Vector<BidirectionalLineInfoRun>::Iterator it = mModel->mLogicalModel->mBidirectionalLineInfo.Begin(),
508 endIt = mModel->mLogicalModel->mBidirectionalLineInfo.End();
512 BidirectionalLineInfoRun& bidiLineInfo = *it;
514 free( bidiLineInfo.visualToLogicalMap );
515 bidiLineInfo.visualToLogicalMap = NULL;
517 mModel->mLogicalModel->mBidirectionalLineInfo.Clear();
521 if( NO_OPERATION != ( SHAPE_TEXT & operations ) )
523 mModel->mVisualModel->mGlyphs.Clear();
524 mModel->mVisualModel->mGlyphsToCharacters.Clear();
525 mModel->mVisualModel->mCharactersToGlyph.Clear();
526 mModel->mVisualModel->mCharactersPerGlyph.Clear();
527 mModel->mVisualModel->mGlyphsPerCharacter.Clear();
528 mModel->mVisualModel->mGlyphPositions.Clear();
531 if( NO_OPERATION != ( LAYOUT & operations ) )
533 mModel->mVisualModel->mLines.Clear();
536 if( NO_OPERATION != ( COLOR & operations ) )
538 mModel->mVisualModel->mColorIndices.Clear();
542 void Controller::Impl::ClearCharacterModelData( CharacterIndex startIndex, CharacterIndex endIndex, OperationsMask operations )
544 const CharacterIndex endIndexPlusOne = endIndex + 1u;
546 if( NO_OPERATION != ( GET_LINE_BREAKS & operations ) )
548 // Clear the line break info.
549 LineBreakInfo* lineBreakInfoBuffer = mModel->mLogicalModel->mLineBreakInfo.Begin();
551 mModel->mLogicalModel->mLineBreakInfo.Erase( lineBreakInfoBuffer + startIndex,
552 lineBreakInfoBuffer + endIndexPlusOne );
554 // Clear the paragraphs.
555 ClearCharacterRuns( startIndex,
557 mModel->mLogicalModel->mParagraphInfo );
560 if( NO_OPERATION != ( GET_WORD_BREAKS & operations ) )
562 // Clear the word break info.
563 WordBreakInfo* wordBreakInfoBuffer = mModel->mLogicalModel->mWordBreakInfo.Begin();
565 mModel->mLogicalModel->mWordBreakInfo.Erase( wordBreakInfoBuffer + startIndex,
566 wordBreakInfoBuffer + endIndexPlusOne );
569 if( NO_OPERATION != ( GET_SCRIPTS & operations ) )
571 // Clear the scripts.
572 ClearCharacterRuns( startIndex,
574 mModel->mLogicalModel->mScriptRuns );
577 if( NO_OPERATION != ( VALIDATE_FONTS & operations ) )
580 ClearCharacterRuns( startIndex,
582 mModel->mLogicalModel->mFontRuns );
585 if( 0u != mModel->mLogicalModel->mBidirectionalParagraphInfo.Count() )
587 if( NO_OPERATION != ( BIDI_INFO & operations ) )
589 // Clear the bidirectional paragraph info.
590 ClearCharacterRuns( startIndex,
592 mModel->mLogicalModel->mBidirectionalParagraphInfo );
594 // Clear the character's directions.
595 CharacterDirection* characterDirectionsBuffer = mModel->mLogicalModel->mCharacterDirections.Begin();
597 mModel->mLogicalModel->mCharacterDirections.Erase( characterDirectionsBuffer + startIndex,
598 characterDirectionsBuffer + endIndexPlusOne );
601 if( NO_OPERATION != ( REORDER & operations ) )
603 uint32_t startRemoveIndex = mModel->mLogicalModel->mBidirectionalLineInfo.Count();
604 uint32_t endRemoveIndex = startRemoveIndex;
605 ClearCharacterRuns( startIndex,
607 mModel->mLogicalModel->mBidirectionalLineInfo,
611 BidirectionalLineInfoRun* bidirectionalLineInfoBuffer = mModel->mLogicalModel->mBidirectionalLineInfo.Begin();
613 // Free the allocated memory used to store the conversion table in the bidirectional line info run.
614 for( Vector<BidirectionalLineInfoRun>::Iterator it = bidirectionalLineInfoBuffer + startRemoveIndex,
615 endIt = bidirectionalLineInfoBuffer + endRemoveIndex;
619 BidirectionalLineInfoRun& bidiLineInfo = *it;
621 free( bidiLineInfo.visualToLogicalMap );
622 bidiLineInfo.visualToLogicalMap = NULL;
625 mModel->mLogicalModel->mBidirectionalLineInfo.Erase( bidirectionalLineInfoBuffer + startRemoveIndex,
626 bidirectionalLineInfoBuffer + endRemoveIndex );
631 void Controller::Impl::ClearGlyphModelData( CharacterIndex startIndex, CharacterIndex endIndex, OperationsMask operations )
633 const CharacterIndex endIndexPlusOne = endIndex + 1u;
634 const Length numberOfCharactersRemoved = endIndexPlusOne - startIndex;
636 // Convert the character index to glyph index before deleting the character to glyph and the glyphs per character buffers.
637 GlyphIndex* charactersToGlyphBuffer = mModel->mVisualModel->mCharactersToGlyph.Begin();
638 Length* glyphsPerCharacterBuffer = mModel->mVisualModel->mGlyphsPerCharacter.Begin();
640 const GlyphIndex endGlyphIndexPlusOne = *( charactersToGlyphBuffer + endIndex ) + *( glyphsPerCharacterBuffer + endIndex );
641 const Length numberOfGlyphsRemoved = endGlyphIndexPlusOne - mTextUpdateInfo.mStartGlyphIndex;
643 if( NO_OPERATION != ( SHAPE_TEXT & operations ) )
645 // Update the character to glyph indices.
646 for( Vector<GlyphIndex>::Iterator it = charactersToGlyphBuffer + endIndexPlusOne,
647 endIt = charactersToGlyphBuffer + mModel->mVisualModel->mCharactersToGlyph.Count();
651 CharacterIndex& index = *it;
652 index -= numberOfGlyphsRemoved;
655 // Clear the character to glyph conversion table.
656 mModel->mVisualModel->mCharactersToGlyph.Erase( charactersToGlyphBuffer + startIndex,
657 charactersToGlyphBuffer + endIndexPlusOne );
659 // Clear the glyphs per character table.
660 mModel->mVisualModel->mGlyphsPerCharacter.Erase( glyphsPerCharacterBuffer + startIndex,
661 glyphsPerCharacterBuffer + endIndexPlusOne );
663 // Clear the glyphs buffer.
664 GlyphInfo* glyphsBuffer = mModel->mVisualModel->mGlyphs.Begin();
665 mModel->mVisualModel->mGlyphs.Erase( glyphsBuffer + mTextUpdateInfo.mStartGlyphIndex,
666 glyphsBuffer + endGlyphIndexPlusOne );
668 CharacterIndex* glyphsToCharactersBuffer = mModel->mVisualModel->mGlyphsToCharacters.Begin();
670 // Update the glyph to character indices.
671 for( Vector<CharacterIndex>::Iterator it = glyphsToCharactersBuffer + endGlyphIndexPlusOne,
672 endIt = glyphsToCharactersBuffer + mModel->mVisualModel->mGlyphsToCharacters.Count();
676 CharacterIndex& index = *it;
677 index -= numberOfCharactersRemoved;
680 // Clear the glyphs to characters buffer.
681 mModel->mVisualModel->mGlyphsToCharacters.Erase( glyphsToCharactersBuffer + mTextUpdateInfo.mStartGlyphIndex,
682 glyphsToCharactersBuffer + endGlyphIndexPlusOne );
684 // Clear the characters per glyph buffer.
685 Length* charactersPerGlyphBuffer = mModel->mVisualModel->mCharactersPerGlyph.Begin();
686 mModel->mVisualModel->mCharactersPerGlyph.Erase( charactersPerGlyphBuffer + mTextUpdateInfo.mStartGlyphIndex,
687 charactersPerGlyphBuffer + endGlyphIndexPlusOne );
689 // Clear the positions buffer.
690 Vector2* positionsBuffer = mModel->mVisualModel->mGlyphPositions.Begin();
691 mModel->mVisualModel->mGlyphPositions.Erase( positionsBuffer + mTextUpdateInfo.mStartGlyphIndex,
692 positionsBuffer + endGlyphIndexPlusOne );
695 if( NO_OPERATION != ( LAYOUT & operations ) )
698 uint32_t startRemoveIndex = mModel->mVisualModel->mLines.Count();
699 uint32_t endRemoveIndex = startRemoveIndex;
700 ClearCharacterRuns( startIndex,
702 mModel->mVisualModel->mLines,
706 // Will update the glyph runs.
707 startRemoveIndex = mModel->mVisualModel->mLines.Count();
708 endRemoveIndex = startRemoveIndex;
709 ClearGlyphRuns( mTextUpdateInfo.mStartGlyphIndex,
710 endGlyphIndexPlusOne - 1u,
711 mModel->mVisualModel->mLines,
715 // Set the line index from where to insert the new laid-out lines.
716 mTextUpdateInfo.mStartLineIndex = startRemoveIndex;
718 LineRun* linesBuffer = mModel->mVisualModel->mLines.Begin();
719 mModel->mVisualModel->mLines.Erase( linesBuffer + startRemoveIndex,
720 linesBuffer + endRemoveIndex );
723 if( NO_OPERATION != ( COLOR & operations ) )
725 if( 0u != mModel->mVisualModel->mColorIndices.Count() )
727 ColorIndex* colorIndexBuffer = mModel->mVisualModel->mColorIndices.Begin();
728 mModel->mVisualModel->mColorIndices.Erase( colorIndexBuffer + mTextUpdateInfo.mStartGlyphIndex,
729 colorIndexBuffer + endGlyphIndexPlusOne );
734 void Controller::Impl::ClearModelData( CharacterIndex startIndex, CharacterIndex endIndex, OperationsMask operations )
736 if( mTextUpdateInfo.mClearAll ||
737 ( ( 0u == startIndex ) &&
738 ( mTextUpdateInfo.mPreviousNumberOfCharacters == endIndex + 1u ) ) )
740 ClearFullModelData( operations );
744 // Clear the model data related with characters.
745 ClearCharacterModelData( startIndex, endIndex, operations );
747 // Clear the model data related with glyphs.
748 ClearGlyphModelData( startIndex, endIndex, operations );
751 // The estimated number of lines. Used to avoid reallocations when layouting.
752 mTextUpdateInfo.mEstimatedNumberOfLines = std::max( mModel->mVisualModel->mLines.Count(), mModel->mLogicalModel->mParagraphInfo.Count() );
754 mModel->mVisualModel->ClearCaches();
757 bool Controller::Impl::UpdateModel( OperationsMask operationsRequired )
759 DALI_LOG_INFO( gLogFilter, Debug::General, "Controller::UpdateModel\n" );
761 // Calculate the operations to be done.
762 const OperationsMask operations = static_cast<OperationsMask>( mOperationsPending & operationsRequired );
764 if( NO_OPERATION == operations )
766 // Nothing to do if no operations are pending and required.
770 Vector<Character>& utf32Characters = mModel->mLogicalModel->mText;
772 const Length numberOfCharacters = utf32Characters.Count();
774 // Index to the first character of the first paragraph to be updated.
775 CharacterIndex startIndex = 0u;
776 // Number of characters of the paragraphs to be removed.
777 Length paragraphCharacters = 0u;
779 CalculateTextUpdateIndices( paragraphCharacters );
780 startIndex = mTextUpdateInfo.mParagraphCharacterIndex;
782 if( mTextUpdateInfo.mClearAll ||
783 ( 0u != paragraphCharacters ) )
785 ClearModelData( startIndex, startIndex + ( ( paragraphCharacters > 0u ) ? paragraphCharacters - 1u : 0u ), operations );
788 mTextUpdateInfo.mClearAll = false;
790 // Whether the model is updated.
791 bool updated = false;
793 Vector<LineBreakInfo>& lineBreakInfo = mModel->mLogicalModel->mLineBreakInfo;
794 const Length requestedNumberOfCharacters = mTextUpdateInfo.mRequestedNumberOfCharacters;
796 if( NO_OPERATION != ( GET_LINE_BREAKS & operations ) )
798 // Retrieves the line break info. The line break info is used to split the text in 'paragraphs' to
799 // calculate the bidirectional info for each 'paragraph'.
800 // It's also used to layout the text (where it should be a new line) or to shape the text (text in different lines
801 // is not shaped together).
802 lineBreakInfo.Resize( numberOfCharacters, TextAbstraction::LINE_NO_BREAK );
804 SetLineBreakInfo( utf32Characters,
806 requestedNumberOfCharacters,
809 // Create the paragraph info.
810 mModel->mLogicalModel->CreateParagraphInfo( startIndex,
811 requestedNumberOfCharacters );
815 Vector<WordBreakInfo>& wordBreakInfo = mModel->mLogicalModel->mWordBreakInfo;
816 if( NO_OPERATION != ( GET_WORD_BREAKS & operations ) )
818 // Retrieves the word break info. The word break info is used to layout the text (where to wrap the text in lines).
819 wordBreakInfo.Resize( numberOfCharacters, TextAbstraction::WORD_NO_BREAK );
821 SetWordBreakInfo( utf32Characters,
823 requestedNumberOfCharacters,
828 const bool getScripts = NO_OPERATION != ( GET_SCRIPTS & operations );
829 const bool validateFonts = NO_OPERATION != ( VALIDATE_FONTS & operations );
831 Vector<ScriptRun>& scripts = mModel->mLogicalModel->mScriptRuns;
832 Vector<FontRun>& validFonts = mModel->mLogicalModel->mFontRuns;
834 if( getScripts || validateFonts )
836 // Validates the fonts assigned by the application or assigns default ones.
837 // It makes sure all the characters are going to be rendered by the correct font.
838 MultilanguageSupport multilanguageSupport = MultilanguageSupport::Get();
842 // Retrieves the scripts used in the text.
843 multilanguageSupport.SetScripts( utf32Characters,
845 requestedNumberOfCharacters,
851 // Validate the fonts set through the mark-up string.
852 Vector<FontDescriptionRun>& fontDescriptionRuns = mModel->mLogicalModel->mFontDescriptionRuns;
854 // Get the default font's description.
855 TextAbstraction::FontDescription defaultFontDescription;
856 TextAbstraction::PointSize26Dot6 defaultPointSize = TextAbstraction::FontClient::DEFAULT_POINT_SIZE;
857 if( NULL != mFontDefaults )
859 defaultFontDescription = mFontDefaults->mFontDescription;
860 defaultPointSize = mFontDefaults->mDefaultPointSize * 64u;
863 // Validates the fonts. If there is a character with no assigned font it sets a default one.
864 // After this call, fonts are validated.
865 multilanguageSupport.ValidateFonts( utf32Characters,
868 defaultFontDescription,
871 requestedNumberOfCharacters,
877 Vector<Character> mirroredUtf32Characters;
878 bool textMirrored = false;
879 const Length numberOfParagraphs = mModel->mLogicalModel->mParagraphInfo.Count();
880 if( NO_OPERATION != ( BIDI_INFO & operations ) )
882 Vector<BidirectionalParagraphInfoRun>& bidirectionalInfo = mModel->mLogicalModel->mBidirectionalParagraphInfo;
883 bidirectionalInfo.Reserve( numberOfParagraphs );
885 // Calculates the bidirectional info for the whole paragraph if it contains right to left scripts.
886 SetBidirectionalInfo( utf32Characters,
890 requestedNumberOfCharacters,
893 if( 0u != bidirectionalInfo.Count() )
895 // Only set the character directions if there is right to left characters.
896 Vector<CharacterDirection>& directions = mModel->mLogicalModel->mCharacterDirections;
897 GetCharactersDirection( bidirectionalInfo,
900 requestedNumberOfCharacters,
903 // This paragraph has right to left text. Some characters may need to be mirrored.
904 // TODO: consider if the mirrored string can be stored as well.
906 textMirrored = GetMirroredText( utf32Characters,
910 requestedNumberOfCharacters,
911 mirroredUtf32Characters );
915 // There is no right to left characters. Clear the directions vector.
916 mModel->mLogicalModel->mCharacterDirections.Clear();
921 Vector<GlyphInfo>& glyphs = mModel->mVisualModel->mGlyphs;
922 Vector<CharacterIndex>& glyphsToCharactersMap = mModel->mVisualModel->mGlyphsToCharacters;
923 Vector<Length>& charactersPerGlyph = mModel->mVisualModel->mCharactersPerGlyph;
924 Vector<GlyphIndex> newParagraphGlyphs;
925 newParagraphGlyphs.Reserve( numberOfParagraphs );
927 const Length currentNumberOfGlyphs = glyphs.Count();
928 if( NO_OPERATION != ( SHAPE_TEXT & operations ) )
930 const Vector<Character>& textToShape = textMirrored ? mirroredUtf32Characters : utf32Characters;
932 ShapeText( textToShape,
937 mTextUpdateInfo.mStartGlyphIndex,
938 requestedNumberOfCharacters,
940 glyphsToCharactersMap,
942 newParagraphGlyphs );
944 // Create the 'number of glyphs' per character and the glyph to character conversion tables.
945 mModel->mVisualModel->CreateGlyphsPerCharacterTable( startIndex, mTextUpdateInfo.mStartGlyphIndex, requestedNumberOfCharacters );
946 mModel->mVisualModel->CreateCharacterToGlyphTable( startIndex, mTextUpdateInfo.mStartGlyphIndex, requestedNumberOfCharacters );
950 const Length numberOfGlyphs = glyphs.Count() - currentNumberOfGlyphs;
952 if( NO_OPERATION != ( GET_GLYPH_METRICS & operations ) )
954 GlyphInfo* glyphsBuffer = glyphs.Begin();
955 mMetrics->GetGlyphMetrics( glyphsBuffer + mTextUpdateInfo.mStartGlyphIndex, numberOfGlyphs );
957 // Update the width and advance of all new paragraph characters.
958 for( Vector<GlyphIndex>::ConstIterator it = newParagraphGlyphs.Begin(), endIt = newParagraphGlyphs.End(); it != endIt; ++it )
960 const GlyphIndex index = *it;
961 GlyphInfo& glyph = *( glyphsBuffer + index );
963 glyph.xBearing = 0.f;
970 if( NO_OPERATION != ( COLOR & operations ) )
972 // Set the color runs in glyphs.
973 SetColorSegmentationInfo( mModel->mLogicalModel->mColorRuns,
974 mModel->mVisualModel->mCharactersToGlyph,
975 mModel->mVisualModel->mGlyphsPerCharacter,
977 mTextUpdateInfo.mStartGlyphIndex,
978 requestedNumberOfCharacters,
979 mModel->mVisualModel->mColors,
980 mModel->mVisualModel->mColorIndices );
985 if( ( NULL != mEventData ) &&
986 mEventData->mPreEditFlag &&
987 ( 0u != mModel->mVisualModel->mCharactersToGlyph.Count() ) )
989 // Add the underline for the pre-edit text.
990 const GlyphIndex* const charactersToGlyphBuffer = mModel->mVisualModel->mCharactersToGlyph.Begin();
991 const Length* const glyphsPerCharacterBuffer = mModel->mVisualModel->mGlyphsPerCharacter.Begin();
993 const GlyphIndex glyphStart = *( charactersToGlyphBuffer + mEventData->mPreEditStartPosition );
994 const CharacterIndex lastPreEditCharacter = mEventData->mPreEditStartPosition + ( ( mEventData->mPreEditLength > 0u ) ? mEventData->mPreEditLength - 1u : 0u );
995 const Length numberOfGlyphsLastCharacter = *( glyphsPerCharacterBuffer + lastPreEditCharacter );
996 const GlyphIndex glyphEnd = *( charactersToGlyphBuffer + lastPreEditCharacter ) + ( numberOfGlyphsLastCharacter > 1u ? numberOfGlyphsLastCharacter - 1u : 0u );
998 GlyphRun underlineRun;
999 underlineRun.glyphIndex = glyphStart;
1000 underlineRun.numberOfGlyphs = 1u + glyphEnd - glyphStart;
1002 // TODO: At the moment the underline runs are only for pre-edit.
1003 mModel->mVisualModel->mUnderlineRuns.PushBack( underlineRun );
1006 // The estimated number of lines. Used to avoid reallocations when layouting.
1007 mTextUpdateInfo.mEstimatedNumberOfLines = std::max( mModel->mVisualModel->mLines.Count(), mModel->mLogicalModel->mParagraphInfo.Count() );
1009 // Set the previous number of characters for the next time the text is updated.
1010 mTextUpdateInfo.mPreviousNumberOfCharacters = numberOfCharacters;
1015 void Controller::Impl::RetrieveDefaultInputStyle( InputStyle& inputStyle )
1017 // Sets the default text's color.
1018 inputStyle.textColor = mTextColor;
1019 inputStyle.isDefaultColor = true;
1021 inputStyle.familyName.clear();
1022 inputStyle.weight = TextAbstraction::FontWeight::NORMAL;
1023 inputStyle.width = TextAbstraction::FontWidth::NORMAL;
1024 inputStyle.slant = TextAbstraction::FontSlant::NORMAL;
1025 inputStyle.size = 0.f;
1027 inputStyle.lineSpacing = 0.f;
1029 inputStyle.underlineProperties.clear();
1030 inputStyle.shadowProperties.clear();
1031 inputStyle.embossProperties.clear();
1032 inputStyle.outlineProperties.clear();
1034 inputStyle.isFamilyDefined = false;
1035 inputStyle.isWeightDefined = false;
1036 inputStyle.isWidthDefined = false;
1037 inputStyle.isSlantDefined = false;
1038 inputStyle.isSizeDefined = false;
1040 inputStyle.isLineSpacingDefined = false;
1042 inputStyle.isUnderlineDefined = false;
1043 inputStyle.isShadowDefined = false;
1044 inputStyle.isEmbossDefined = false;
1045 inputStyle.isOutlineDefined = false;
1047 // Sets the default font's family name, weight, width, slant and size.
1050 if( mFontDefaults->familyDefined )
1052 inputStyle.familyName = mFontDefaults->mFontDescription.family;
1053 inputStyle.isFamilyDefined = true;
1056 if( mFontDefaults->weightDefined )
1058 inputStyle.weight = mFontDefaults->mFontDescription.weight;
1059 inputStyle.isWeightDefined = true;
1062 if( mFontDefaults->widthDefined )
1064 inputStyle.width = mFontDefaults->mFontDescription.width;
1065 inputStyle.isWidthDefined = true;
1068 if( mFontDefaults->slantDefined )
1070 inputStyle.slant = mFontDefaults->mFontDescription.slant;
1071 inputStyle.isSlantDefined = true;
1074 if( mFontDefaults->sizeDefined )
1076 inputStyle.size = mFontDefaults->mDefaultPointSize;
1077 inputStyle.isSizeDefined = true;
1082 float Controller::Impl::GetDefaultFontLineHeight()
1084 FontId defaultFontId = 0u;
1085 if( NULL == mFontDefaults )
1087 TextAbstraction::FontDescription fontDescription;
1088 defaultFontId = mFontClient.GetFontId( fontDescription );
1092 defaultFontId = mFontDefaults->GetFontId( mFontClient );
1095 Text::FontMetrics fontMetrics;
1096 mMetrics->GetFontMetrics( defaultFontId, fontMetrics );
1098 return( fontMetrics.ascender - fontMetrics.descender );
1101 void Controller::Impl::OnCursorKeyEvent( const Event& event )
1103 if( NULL == mEventData )
1105 // Nothing to do if there is no text input.
1109 int keyCode = event.p1.mInt;
1111 if( Dali::DALI_KEY_CURSOR_LEFT == keyCode )
1113 if( mEventData->mPrimaryCursorPosition > 0u )
1115 mEventData->mPrimaryCursorPosition = CalculateNewCursorIndex( mEventData->mPrimaryCursorPosition - 1u );
1118 else if( Dali::DALI_KEY_CURSOR_RIGHT == keyCode )
1120 if( mModel->mLogicalModel->mText.Count() > mEventData->mPrimaryCursorPosition )
1122 mEventData->mPrimaryCursorPosition = CalculateNewCursorIndex( mEventData->mPrimaryCursorPosition );
1125 else if( Dali::DALI_KEY_CURSOR_UP == keyCode )
1127 // Get first the line index of the current cursor position index.
1128 CharacterIndex characterIndex = 0u;
1130 if( mEventData->mPrimaryCursorPosition > 0u )
1132 characterIndex = mEventData->mPrimaryCursorPosition - 1u;
1135 const LineIndex lineIndex = mModel->mVisualModel->GetLineOfCharacter( characterIndex );
1137 if( lineIndex > 0u )
1139 // Retrieve the cursor position info.
1140 CursorInfo cursorInfo;
1141 GetCursorPosition( mEventData->mPrimaryCursorPosition,
1144 // Get the line above.
1145 const LineRun& line = *( mModel->mVisualModel->mLines.Begin() + ( lineIndex - 1u ) );
1147 // Get the next hit 'y' point.
1148 const float hitPointY = cursorInfo.lineOffset - 0.5f * ( line.ascender - line.descender );
1150 // Use the cursor hook position 'x' and the next hit 'y' position to calculate the new cursor index.
1151 mEventData->mPrimaryCursorPosition = Text::GetClosestCursorIndex( mModel->mVisualModel,
1152 mModel->mLogicalModel,
1154 mEventData->mCursorHookPositionX,
1158 else if( Dali::DALI_KEY_CURSOR_DOWN == keyCode )
1160 // Get first the line index of the current cursor position index.
1161 CharacterIndex characterIndex = 0u;
1163 if( mEventData->mPrimaryCursorPosition > 0u )
1165 characterIndex = mEventData->mPrimaryCursorPosition - 1u;
1168 const LineIndex lineIndex = mModel->mVisualModel->GetLineOfCharacter( characterIndex );
1170 if( lineIndex + 1u < mModel->mVisualModel->mLines.Count() )
1172 // Retrieve the cursor position info.
1173 CursorInfo cursorInfo;
1174 GetCursorPosition( mEventData->mPrimaryCursorPosition,
1177 // Get the line below.
1178 const LineRun& line = *( mModel->mVisualModel->mLines.Begin() + lineIndex + 1u );
1180 // Get the next hit 'y' point.
1181 const float hitPointY = cursorInfo.lineOffset + cursorInfo.lineHeight + 0.5f * ( line.ascender - line.descender );
1183 // Use the cursor hook position 'x' and the next hit 'y' position to calculate the new cursor index.
1184 mEventData->mPrimaryCursorPosition = Text::GetClosestCursorIndex( mModel->mVisualModel,
1185 mModel->mLogicalModel,
1187 mEventData->mCursorHookPositionX,
1192 mEventData->mUpdateCursorPosition = true;
1193 mEventData->mUpdateInputStyle = true;
1194 mEventData->mScrollAfterUpdatePosition = true;
1197 void Controller::Impl::OnTapEvent( const Event& event )
1199 if( NULL != mEventData )
1201 const unsigned int tapCount = event.p1.mUint;
1203 if( 1u == tapCount )
1205 if( IsShowingRealText() )
1207 // Convert from control's coords to text's coords.
1208 const float xPosition = event.p2.mFloat - mModel->mScrollPosition.x;
1209 const float yPosition = event.p3.mFloat - mModel->mScrollPosition.y;
1211 // Keep the tap 'x' position. Used to move the cursor.
1212 mEventData->mCursorHookPositionX = xPosition;
1214 mEventData->mPrimaryCursorPosition = Text::GetClosestCursorIndex( mModel->mVisualModel,
1215 mModel->mLogicalModel,
1220 // When the cursor position is changing, delay cursor blinking
1221 mEventData->mDecorator->DelayCursorBlink();
1225 mEventData->mPrimaryCursorPosition = 0u;
1228 mEventData->mUpdateCursorPosition = true;
1229 mEventData->mUpdateGrabHandlePosition = true;
1230 mEventData->mScrollAfterUpdatePosition = true;
1231 mEventData->mUpdateInputStyle = true;
1233 // Notify the cursor position to the imf manager.
1234 if( mEventData->mImfManager )
1236 mEventData->mImfManager.SetCursorPosition( mEventData->mPrimaryCursorPosition );
1237 mEventData->mImfManager.NotifyCursorPosition();
1243 void Controller::Impl::OnPanEvent( const Event& event )
1245 if( NULL == mEventData )
1247 // Nothing to do if there is no text input.
1251 const bool isHorizontalScrollEnabled = mEventData->mDecorator->IsHorizontalScrollEnabled();
1252 const bool isVerticalScrollEnabled = mEventData->mDecorator->IsVerticalScrollEnabled();
1254 if( !isHorizontalScrollEnabled && !isVerticalScrollEnabled )
1256 // Nothing to do if scrolling is not enabled.
1260 const int state = event.p1.mInt;
1264 case Gesture::Started:
1266 // Will remove the cursor, handles or text's popup, ...
1267 ChangeState( EventData::TEXT_PANNING );
1270 case Gesture::Continuing:
1272 const Vector2& layoutSize = mModel->mVisualModel->GetLayoutSize();
1273 const Vector2 currentScroll = mModel->mScrollPosition;
1275 if( isHorizontalScrollEnabled )
1277 const float displacementX = event.p2.mFloat;
1278 mModel->mScrollPosition.x += displacementX;
1280 ClampHorizontalScroll( layoutSize );
1283 if( isVerticalScrollEnabled )
1285 const float displacementY = event.p3.mFloat;
1286 mModel->mScrollPosition.y += displacementY;
1288 ClampVerticalScroll( layoutSize );
1291 mEventData->mDecorator->UpdatePositions( mModel->mScrollPosition - currentScroll );
1294 case Gesture::Finished:
1295 case Gesture::Cancelled: // FALLTHROUGH
1297 // Will go back to the previous state to show the cursor, handles, the text's popup, ...
1298 ChangeState( mEventData->mPreviousState );
1306 void Controller::Impl::OnLongPressEvent( const Event& event )
1308 DALI_LOG_INFO( gLogFilter, Debug::General, "Controller::OnLongPressEvent\n" );
1310 if( EventData::EDITING == mEventData->mState )
1312 ChangeState ( EventData::EDITING_WITH_POPUP );
1313 mEventData->mDecoratorUpdated = true;
1314 mEventData->mUpdateInputStyle = true;
1318 void Controller::Impl::OnHandleEvent( const Event& event )
1320 if( NULL == mEventData )
1322 // Nothing to do if there is no text input.
1326 const unsigned int state = event.p1.mUint;
1327 const bool handleStopScrolling = ( HANDLE_STOP_SCROLLING == state );
1328 const bool isSmoothHandlePanEnabled = mEventData->mDecorator->IsSmoothHandlePanEnabled();
1330 if( HANDLE_PRESSED == state )
1332 // Convert from decorator's coords to text's coords.
1333 const float xPosition = event.p2.mFloat - mModel->mScrollPosition.x;
1334 const float yPosition = event.p3.mFloat - mModel->mScrollPosition.y;
1336 // Need to calculate the handle's new position.
1337 const CharacterIndex handleNewPosition = Text::GetClosestCursorIndex( mModel->mVisualModel,
1338 mModel->mLogicalModel,
1343 if( Event::GRAB_HANDLE_EVENT == event.type )
1345 ChangeState ( EventData::GRAB_HANDLE_PANNING );
1347 if( handleNewPosition != mEventData->mPrimaryCursorPosition )
1349 // Updates the cursor position if the handle's new position is different than the current one.
1350 mEventData->mUpdateCursorPosition = true;
1351 // Does not update the grab handle position if the smooth panning is enabled. (The decorator does it smooth).
1352 mEventData->mUpdateGrabHandlePosition = !isSmoothHandlePanEnabled;
1353 mEventData->mPrimaryCursorPosition = handleNewPosition;
1356 // 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.
1357 mEventData->mDecoratorUpdated = isSmoothHandlePanEnabled;
1359 else if( Event::LEFT_SELECTION_HANDLE_EVENT == event.type )
1361 ChangeState ( EventData::SELECTION_HANDLE_PANNING );
1363 if( ( handleNewPosition != mEventData->mLeftSelectionPosition ) &&
1364 ( handleNewPosition != mEventData->mRightSelectionPosition ) )
1366 // Updates the highlight box if the handle's new position is different than the current one.
1367 mEventData->mUpdateHighlightBox = true;
1368 // Does not update the selection handle position if the smooth panning is enabled. (The decorator does it smooth).
1369 mEventData->mUpdateLeftSelectionPosition = !isSmoothHandlePanEnabled;
1370 mEventData->mLeftSelectionPosition = handleNewPosition;
1373 // 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.
1374 mEventData->mDecoratorUpdated = isSmoothHandlePanEnabled;
1376 // Will define the order to scroll the text to match the handle position.
1377 mEventData->mIsLeftHandleSelected = true;
1379 else if( Event::RIGHT_SELECTION_HANDLE_EVENT == event.type )
1381 ChangeState ( EventData::SELECTION_HANDLE_PANNING );
1383 if( ( handleNewPosition != mEventData->mRightSelectionPosition ) &&
1384 ( handleNewPosition != mEventData->mLeftSelectionPosition ) )
1386 // Updates the highlight box if the handle's new position is different than the current one.
1387 mEventData->mUpdateHighlightBox = true;
1388 // Does not update the selection handle position if the smooth panning is enabled. (The decorator does it smooth).
1389 mEventData->mUpdateRightSelectionPosition = !isSmoothHandlePanEnabled;
1390 mEventData->mRightSelectionPosition = handleNewPosition;
1393 // 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.
1394 mEventData->mDecoratorUpdated = isSmoothHandlePanEnabled;
1396 // Will define the order to scroll the text to match the handle position.
1397 mEventData->mIsLeftHandleSelected = false;
1399 } // end ( HANDLE_PRESSED == state )
1400 else if( ( HANDLE_RELEASED == state ) ||
1401 handleStopScrolling )
1403 CharacterIndex handlePosition = 0u;
1404 if( handleStopScrolling || isSmoothHandlePanEnabled )
1406 // Convert from decorator's coords to text's coords.
1407 const float xPosition = event.p2.mFloat - mModel->mScrollPosition.x;
1408 const float yPosition = event.p3.mFloat - mModel->mScrollPosition.y;
1410 handlePosition = Text::GetClosestCursorIndex( mModel->mVisualModel,
1411 mModel->mLogicalModel,
1417 if( Event::GRAB_HANDLE_EVENT == event.type )
1419 mEventData->mUpdateCursorPosition = true;
1420 mEventData->mUpdateGrabHandlePosition = true;
1421 mEventData->mUpdateInputStyle = true;
1423 if( !IsClipboardEmpty() )
1425 ChangeState( EventData::EDITING_WITH_PASTE_POPUP ); // Moving grabhandle will show Paste Popup
1428 if( handleStopScrolling || isSmoothHandlePanEnabled )
1430 mEventData->mScrollAfterUpdatePosition = true;
1431 mEventData->mPrimaryCursorPosition = handlePosition;
1434 else if( Event::LEFT_SELECTION_HANDLE_EVENT == event.type )
1436 ChangeState( EventData::SELECTING );
1438 mEventData->mUpdateHighlightBox = true;
1439 mEventData->mUpdateLeftSelectionPosition = true;
1440 mEventData->mUpdateRightSelectionPosition = true;
1442 if( handleStopScrolling || isSmoothHandlePanEnabled )
1444 mEventData->mScrollAfterUpdatePosition = true;
1446 if( ( handlePosition != mEventData->mRightSelectionPosition ) &&
1447 ( handlePosition != mEventData->mLeftSelectionPosition ) )
1449 mEventData->mLeftSelectionPosition = handlePosition;
1453 else if( Event::RIGHT_SELECTION_HANDLE_EVENT == event.type )
1455 ChangeState( EventData::SELECTING );
1457 mEventData->mUpdateHighlightBox = true;
1458 mEventData->mUpdateRightSelectionPosition = true;
1459 mEventData->mUpdateLeftSelectionPosition = true;
1461 if( handleStopScrolling || isSmoothHandlePanEnabled )
1463 mEventData->mScrollAfterUpdatePosition = true;
1464 if( ( handlePosition != mEventData->mRightSelectionPosition ) &&
1465 ( handlePosition != mEventData->mLeftSelectionPosition ) )
1467 mEventData->mRightSelectionPosition = handlePosition;
1472 mEventData->mDecoratorUpdated = true;
1473 } // end ( ( HANDLE_RELEASED == state ) || ( HANDLE_STOP_SCROLLING == state ) )
1474 else if( HANDLE_SCROLLING == state )
1476 const float xSpeed = event.p2.mFloat;
1477 const float ySpeed = event.p3.mFloat;
1478 const Vector2& layoutSize = mModel->mVisualModel->GetLayoutSize();
1479 const Vector2 currentScrollPosition = mModel->mScrollPosition;
1481 mModel->mScrollPosition.x += xSpeed;
1482 mModel->mScrollPosition.y += ySpeed;
1484 ClampHorizontalScroll( layoutSize );
1485 ClampVerticalScroll( layoutSize );
1487 bool endOfScroll = false;
1488 if( Vector2::ZERO == ( currentScrollPosition - mModel->mScrollPosition ) )
1490 // Notify the decorator there is no more text to scroll.
1491 // The decorator won't send more scroll events.
1492 mEventData->mDecorator->NotifyEndOfScroll();
1493 // Still need to set the position of the handle.
1497 // Set the position of the handle.
1498 const bool scrollRightDirection = xSpeed > 0.f;
1499 const bool scrollBottomDirection = ySpeed > 0.f;
1500 const bool leftSelectionHandleEvent = Event::LEFT_SELECTION_HANDLE_EVENT == event.type;
1501 const bool rightSelectionHandleEvent = Event::RIGHT_SELECTION_HANDLE_EVENT == event.type;
1503 if( Event::GRAB_HANDLE_EVENT == event.type )
1505 ChangeState( EventData::GRAB_HANDLE_PANNING );
1507 // Get the grab handle position in decorator coords.
1508 Vector2 position = mEventData->mDecorator->GetPosition( GRAB_HANDLE );
1510 if( mEventData->mDecorator->IsHorizontalScrollEnabled() )
1512 // Position the grag handle close to either the left or right edge.
1513 position.x = scrollRightDirection ? 0.f : mModel->mVisualModel->mControlSize.width;
1516 if( mEventData->mDecorator->IsVerticalScrollEnabled() )
1518 position.x = mEventData->mCursorHookPositionX;
1520 // Position the grag handle close to either the top or bottom edge.
1521 position.y = scrollBottomDirection ? 0.f : mModel->mVisualModel->mControlSize.height;
1524 // Get the new handle position.
1525 // The grab handle's position is in decorator's coords. Need to transforms to text's coords.
1526 const CharacterIndex handlePosition = Text::GetClosestCursorIndex( mModel->mVisualModel,
1527 mModel->mLogicalModel,
1529 position.x - mModel->mScrollPosition.x,
1530 position.y - mModel->mScrollPosition.y );
1532 if( mEventData->mPrimaryCursorPosition != handlePosition )
1534 mEventData->mUpdateCursorPosition = true;
1535 mEventData->mUpdateGrabHandlePosition = !isSmoothHandlePanEnabled;
1536 mEventData->mScrollAfterUpdatePosition = true;
1537 mEventData->mPrimaryCursorPosition = handlePosition;
1539 mEventData->mUpdateInputStyle = mEventData->mUpdateCursorPosition;
1541 // Updates the decorator if the soft handle panning is enabled.
1542 mEventData->mDecoratorUpdated = isSmoothHandlePanEnabled;
1544 else if( leftSelectionHandleEvent || rightSelectionHandleEvent )
1546 ChangeState( EventData::SELECTION_HANDLE_PANNING );
1548 // Get the selection handle position in decorator coords.
1549 Vector2 position = mEventData->mDecorator->GetPosition( leftSelectionHandleEvent ? Text::LEFT_SELECTION_HANDLE : Text::RIGHT_SELECTION_HANDLE );
1551 if( mEventData->mDecorator->IsHorizontalScrollEnabled() )
1553 // Position the selection handle close to either the left or right edge.
1554 position.x = scrollRightDirection ? 0.f : mModel->mVisualModel->mControlSize.width;
1557 if( mEventData->mDecorator->IsVerticalScrollEnabled() )
1559 position.x = mEventData->mCursorHookPositionX;
1561 // Position the grag handle close to either the top or bottom edge.
1562 position.y = scrollBottomDirection ? 0.f : mModel->mVisualModel->mControlSize.height;
1565 // Get the new handle position.
1566 // The selection handle's position is in decorator's coords. Need to transform to text's coords.
1567 const CharacterIndex handlePosition = Text::GetClosestCursorIndex( mModel->mVisualModel,
1568 mModel->mLogicalModel,
1570 position.x - mModel->mScrollPosition.x,
1571 position.y - mModel->mScrollPosition.y );
1573 if( leftSelectionHandleEvent )
1575 const bool differentHandles = ( mEventData->mLeftSelectionPosition != handlePosition ) && ( mEventData->mRightSelectionPosition != handlePosition );
1577 if( differentHandles || endOfScroll )
1579 mEventData->mUpdateHighlightBox = true;
1580 mEventData->mUpdateLeftSelectionPosition = !isSmoothHandlePanEnabled;
1581 mEventData->mUpdateRightSelectionPosition = isSmoothHandlePanEnabled;
1582 mEventData->mLeftSelectionPosition = handlePosition;
1587 const bool differentHandles = ( mEventData->mRightSelectionPosition != handlePosition ) && ( mEventData->mLeftSelectionPosition != handlePosition );
1588 if( differentHandles || endOfScroll )
1590 mEventData->mUpdateHighlightBox = true;
1591 mEventData->mUpdateRightSelectionPosition = !isSmoothHandlePanEnabled;
1592 mEventData->mUpdateLeftSelectionPosition = isSmoothHandlePanEnabled;
1593 mEventData->mRightSelectionPosition = handlePosition;
1597 if( mEventData->mUpdateLeftSelectionPosition || mEventData->mUpdateRightSelectionPosition )
1599 RepositionSelectionHandles();
1601 mEventData->mScrollAfterUpdatePosition = !isSmoothHandlePanEnabled;
1604 mEventData->mDecoratorUpdated = true;
1605 } // end ( HANDLE_SCROLLING == state )
1608 void Controller::Impl::OnSelectEvent( const Event& event )
1610 if( NULL == mEventData )
1612 // Nothing to do if there is no text.
1616 if( mEventData->mSelectionEnabled )
1618 // Convert from control's coords to text's coords.
1619 const float xPosition = event.p2.mFloat - mModel->mScrollPosition.x;
1620 const float yPosition = event.p3.mFloat - mModel->mScrollPosition.y;
1622 // Calculates the logical position from the x,y coords.
1623 RepositionSelectionHandles( xPosition,
1628 void Controller::Impl::OnSelectAllEvent()
1630 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "OnSelectAllEvent mEventData->mSelectionEnabled%s \n", mEventData->mSelectionEnabled?"true":"false");
1632 if( NULL == mEventData )
1634 // Nothing to do if there is no text.
1638 if( mEventData->mSelectionEnabled )
1640 ChangeState( EventData::SELECTING );
1642 mEventData->mLeftSelectionPosition = 0u;
1643 mEventData->mRightSelectionPosition = mModel->mLogicalModel->mText.Count();
1645 mEventData->mScrollAfterUpdatePosition = true;
1646 mEventData->mUpdateLeftSelectionPosition = true;
1647 mEventData->mUpdateRightSelectionPosition = true;
1648 mEventData->mUpdateHighlightBox = true;
1652 void Controller::Impl::RetrieveSelection( std::string& selectedText, bool deleteAfterRetrieval )
1654 if( mEventData->mLeftSelectionPosition == mEventData->mRightSelectionPosition )
1656 // Nothing to select if handles are in the same place.
1657 selectedText.clear();
1661 const bool handlesCrossed = mEventData->mLeftSelectionPosition > mEventData->mRightSelectionPosition;
1663 //Get start and end position of selection
1664 const CharacterIndex startOfSelectedText = handlesCrossed ? mEventData->mRightSelectionPosition : mEventData->mLeftSelectionPosition;
1665 const Length lengthOfSelectedText = ( handlesCrossed ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition ) - startOfSelectedText;
1667 Vector<Character>& utf32Characters = mModel->mLogicalModel->mText;
1668 const Length numberOfCharacters = utf32Characters.Count();
1670 // Validate the start and end selection points
1671 if( ( startOfSelectedText + lengthOfSelectedText ) <= numberOfCharacters )
1673 //Get text as a UTF8 string
1674 Utf32ToUtf8( &utf32Characters[startOfSelectedText], lengthOfSelectedText, selectedText );
1676 if( deleteAfterRetrieval ) // Only delete text if copied successfully
1678 // Keep a copy of the current input style.
1679 InputStyle currentInputStyle;
1680 currentInputStyle.Copy( mEventData->mInputStyle );
1682 // Set as input style the style of the first deleted character.
1683 mModel->mLogicalModel->RetrieveStyle( startOfSelectedText, mEventData->mInputStyle );
1685 // Compare if the input style has changed.
1686 const bool hasInputStyleChanged = !currentInputStyle.Equal( mEventData->mInputStyle );
1688 if( hasInputStyleChanged )
1690 const InputStyle::Mask styleChangedMask = currentInputStyle.GetInputStyleChangeMask( mEventData->mInputStyle );
1691 // Queue the input style changed signal.
1692 mEventData->mInputStyleChangedQueue.PushBack( styleChangedMask );
1695 mModel->mLogicalModel->UpdateTextStyleRuns( startOfSelectedText, -static_cast<int>( lengthOfSelectedText ) );
1697 // Mark the paragraphs to be updated.
1698 mTextUpdateInfo.mCharacterIndex = startOfSelectedText;
1699 mTextUpdateInfo.mNumberOfCharactersToRemove = lengthOfSelectedText;
1701 // Delete text between handles
1702 Vector<Character>::Iterator first = utf32Characters.Begin() + startOfSelectedText;
1703 Vector<Character>::Iterator last = first + lengthOfSelectedText;
1704 utf32Characters.Erase( first, last );
1706 // Will show the cursor at the first character of the selection.
1707 mEventData->mPrimaryCursorPosition = handlesCrossed ? mEventData->mRightSelectionPosition : mEventData->mLeftSelectionPosition;
1711 // Will show the cursor at the last character of the selection.
1712 mEventData->mPrimaryCursorPosition = handlesCrossed ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition;
1715 mEventData->mDecoratorUpdated = true;
1719 void Controller::Impl::ShowClipboard()
1723 mClipboard.ShowClipboard();
1727 void Controller::Impl::HideClipboard()
1729 if( mClipboard && mClipboardHideEnabled )
1731 mClipboard.HideClipboard();
1735 void Controller::Impl::SetClipboardHideEnable(bool enable)
1737 mClipboardHideEnabled = enable;
1740 bool Controller::Impl::CopyStringToClipboard( std::string& source )
1742 //Send string to clipboard
1743 return ( mClipboard && mClipboard.SetItem( source ) );
1746 void Controller::Impl::SendSelectionToClipboard( bool deleteAfterSending )
1748 std::string selectedText;
1749 RetrieveSelection( selectedText, deleteAfterSending );
1750 CopyStringToClipboard( selectedText );
1751 ChangeState( EventData::EDITING );
1754 void Controller::Impl::RequestGetTextFromClipboard()
1758 mClipboard.RequestItem();
1762 void Controller::Impl::RepositionSelectionHandles()
1764 CharacterIndex selectionStart = mEventData->mLeftSelectionPosition;
1765 CharacterIndex selectionEnd = mEventData->mRightSelectionPosition;
1767 if( selectionStart == selectionEnd )
1769 // Nothing to select if handles are in the same place.
1773 mEventData->mDecorator->ClearHighlights();
1775 const GlyphIndex* const charactersToGlyphBuffer = mModel->mVisualModel->mCharactersToGlyph.Begin();
1776 const Length* const glyphsPerCharacterBuffer = mModel->mVisualModel->mGlyphsPerCharacter.Begin();
1777 const GlyphInfo* const glyphsBuffer = mModel->mVisualModel->mGlyphs.Begin();
1778 const Vector2* const positionsBuffer = mModel->mVisualModel->mGlyphPositions.Begin();
1779 const Length* const charactersPerGlyphBuffer = mModel->mVisualModel->mCharactersPerGlyph.Begin();
1780 const CharacterIndex* const glyphToCharacterBuffer = mModel->mVisualModel->mGlyphsToCharacters.Begin();
1781 const CharacterDirection* const modelCharacterDirectionsBuffer = ( 0u != mModel->mLogicalModel->mCharacterDirections.Count() ) ? mModel->mLogicalModel->mCharacterDirections.Begin() : NULL;
1783 const bool isLastCharacter = selectionEnd >= mModel->mLogicalModel->mText.Count();
1784 const CharacterDirection startDirection = ( ( NULL == modelCharacterDirectionsBuffer ) ? false : *( modelCharacterDirectionsBuffer + selectionStart ) );
1785 const CharacterDirection endDirection = ( ( NULL == modelCharacterDirectionsBuffer ) ? false : *( modelCharacterDirectionsBuffer + ( selectionEnd - ( isLastCharacter ? 1u : 0u ) ) ) );
1787 // Swap the indices if the start is greater than the end.
1788 const bool indicesSwapped = selectionStart > selectionEnd;
1790 // Tell the decorator to flip the selection handles if needed.
1791 mEventData->mDecorator->SetSelectionHandleFlipState( indicesSwapped, startDirection, endDirection );
1793 if( indicesSwapped )
1795 std::swap( selectionStart, selectionEnd );
1798 // Get the indices to the first and last selected glyphs.
1799 const CharacterIndex selectionEndMinusOne = selectionEnd - 1u;
1800 const GlyphIndex glyphStart = *( charactersToGlyphBuffer + selectionStart );
1801 const Length numberOfGlyphs = *( glyphsPerCharacterBuffer + selectionEndMinusOne );
1802 const GlyphIndex glyphEnd = *( charactersToGlyphBuffer + selectionEndMinusOne ) + ( ( numberOfGlyphs > 0 ) ? numberOfGlyphs - 1u : 0u );
1804 // Get the lines where the glyphs are laid-out.
1805 const LineRun* lineRun = mModel->mVisualModel->mLines.Begin();
1807 LineIndex lineIndex = 0u;
1808 Length numberOfLines = 0u;
1809 mModel->mVisualModel->GetNumberOfLines( glyphStart,
1810 1u + glyphEnd - glyphStart,
1813 const LineIndex firstLineIndex = lineIndex;
1815 // Create the structure to store some selection box info.
1816 Vector<SelectionBoxInfo> selectionBoxLinesInfo;
1817 selectionBoxLinesInfo.Resize( numberOfLines );
1819 SelectionBoxInfo* selectionBoxInfo = selectionBoxLinesInfo.Begin();
1820 selectionBoxInfo->minX = MAX_FLOAT;
1821 selectionBoxInfo->maxX = MIN_FLOAT;
1823 // Keep the min and max 'x' position to calculate the size and position of the highlighed text.
1824 float minHighlightX = std::numeric_limits<float>::max();
1825 float maxHighlightX = std::numeric_limits<float>::min();
1827 Vector2 highLightPosition; // The highlight position in decorator's coords.
1829 // Retrieve the first line and get the line's vertical offset, the line's height and the index to the last glyph.
1831 // The line's vertical offset of all the lines before the line where the first glyph is laid-out.
1832 selectionBoxInfo->lineOffset = CalculateLineOffset( mModel->mVisualModel->mLines,
1835 // Transform to decorator's (control) coords.
1836 selectionBoxInfo->lineOffset += mModel->mScrollPosition.y;
1838 lineRun += firstLineIndex;
1840 // The line height is the addition of the line ascender and the line descender.
1841 // However, the line descender has a negative value, hence the subtraction.
1842 selectionBoxInfo->lineHeight = lineRun->ascender - lineRun->descender;
1844 GlyphIndex lastGlyphOfLine = lineRun->glyphRun.glyphIndex + lineRun->glyphRun.numberOfGlyphs - 1u;
1846 // Check if the first glyph is a ligature that must be broken like Latin ff, fi, or Arabic ﻻ, etc which needs special code.
1847 const Length numberOfCharactersStart = *( charactersPerGlyphBuffer + glyphStart );
1848 bool splitStartGlyph = ( numberOfCharactersStart > 1u ) && HasLigatureMustBreak( mModel->mLogicalModel->GetScript( selectionStart ) );
1850 // Check if the last glyph is a ligature that must be broken like Latin ff, fi, or Arabic ﻻ, etc which needs special code.
1851 const Length numberOfCharactersEnd = *( charactersPerGlyphBuffer + glyphEnd );
1852 bool splitEndGlyph = ( glyphStart != glyphEnd ) && ( numberOfCharactersEnd > 1u ) && HasLigatureMustBreak( mModel->mLogicalModel->GetScript( selectionEndMinusOne ) );
1854 // The number of quads of the selection box.
1855 const unsigned int numberOfQuads = 1u + ( glyphEnd - glyphStart ) + ( ( numberOfLines > 1u ) ? 2u * numberOfLines : 0u );
1856 mEventData->mDecorator->ResizeHighlightQuads( numberOfQuads );
1858 // Count the actual number of quads.
1859 unsigned int actualNumberOfQuads = 0u;
1862 // Traverse the glyphs.
1863 for( GlyphIndex index = glyphStart; index <= glyphEnd; ++index )
1865 const GlyphInfo& glyph = *( glyphsBuffer + index );
1866 const Vector2& position = *( positionsBuffer + index );
1868 if( splitStartGlyph )
1870 // 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.
1872 const float glyphAdvance = glyph.advance / static_cast<float>( numberOfCharactersStart );
1873 const CharacterIndex interGlyphIndex = selectionStart - *( glyphToCharacterBuffer + glyphStart );
1874 // Get the direction of the character.
1875 CharacterDirection isCurrentRightToLeft = false;
1876 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
1878 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + selectionStart );
1881 // The end point could be in the middle of the ligature.
1882 // Calculate the number of characters selected.
1883 const Length numberOfCharacters = ( glyphStart == glyphEnd ) ? ( selectionEnd - selectionStart ) : ( numberOfCharactersStart - interGlyphIndex );
1885 quad.x = lineRun->alignmentOffset + position.x - glyph.xBearing + mModel->mScrollPosition.x + glyphAdvance * static_cast<float>( isCurrentRightToLeft ? ( numberOfCharactersStart - interGlyphIndex - numberOfCharacters ) : interGlyphIndex );
1886 quad.y = selectionBoxInfo->lineOffset;
1887 quad.z = quad.x + static_cast<float>( numberOfCharacters ) * glyphAdvance;
1888 quad.w = selectionBoxInfo->lineOffset + selectionBoxInfo->lineHeight;
1890 // Store the min and max 'x' for each line.
1891 selectionBoxInfo->minX = std::min( selectionBoxInfo->minX, quad.x );
1892 selectionBoxInfo->maxX = std::max( selectionBoxInfo->maxX, quad.z );
1894 mEventData->mDecorator->AddHighlight( actualNumberOfQuads, quad );
1895 ++actualNumberOfQuads;
1897 splitStartGlyph = false;
1901 if( splitEndGlyph && ( index == glyphEnd ) )
1903 // 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.
1905 const float glyphAdvance = glyph.advance / static_cast<float>( numberOfCharactersEnd );
1906 const CharacterIndex interGlyphIndex = selectionEnd - *( glyphToCharacterBuffer + glyphEnd );
1907 // Get the direction of the character.
1908 CharacterDirection isCurrentRightToLeft = false;
1909 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
1911 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + selectionEnd );
1914 const Length numberOfCharacters = numberOfCharactersEnd - interGlyphIndex;
1916 quad.x = lineRun->alignmentOffset + position.x - glyph.xBearing + mModel->mScrollPosition.x + ( isCurrentRightToLeft ? ( glyphAdvance * static_cast<float>( numberOfCharacters ) ) : 0.f );
1917 quad.y = selectionBoxInfo->lineOffset;
1918 quad.z = quad.x + static_cast<float>( interGlyphIndex ) * glyphAdvance;
1919 quad.w = quad.y + selectionBoxInfo->lineHeight;
1921 // Store the min and max 'x' for each line.
1922 selectionBoxInfo->minX = std::min( selectionBoxInfo->minX, quad.x );
1923 selectionBoxInfo->maxX = std::max( selectionBoxInfo->maxX, quad.z );
1925 mEventData->mDecorator->AddHighlight( actualNumberOfQuads,
1927 ++actualNumberOfQuads;
1929 splitEndGlyph = false;
1933 quad.x = lineRun->alignmentOffset + position.x - glyph.xBearing + mModel->mScrollPosition.x;
1934 quad.y = selectionBoxInfo->lineOffset;
1935 quad.z = quad.x + glyph.advance;
1936 quad.w = quad.y + selectionBoxInfo->lineHeight;
1938 // Store the min and max 'x' for each line.
1939 selectionBoxInfo->minX = std::min( selectionBoxInfo->minX, quad.x );
1940 selectionBoxInfo->maxX = std::max( selectionBoxInfo->maxX, quad.z );
1942 mEventData->mDecorator->AddHighlight( actualNumberOfQuads,
1944 ++actualNumberOfQuads;
1946 // Whether to retrieve the next line.
1947 if( index == lastGlyphOfLine )
1949 // Retrieve the next line.
1952 // Get the last glyph of the new line.
1953 lastGlyphOfLine = lineRun->glyphRun.glyphIndex + lineRun->glyphRun.numberOfGlyphs - 1u;
1956 if( lineIndex < firstLineIndex + numberOfLines )
1958 // Keep the offset and height of the current selection box.
1959 const float currentLineOffset = selectionBoxInfo->lineOffset;
1960 const float currentLineHeight = selectionBoxInfo->lineHeight;
1962 // Get the selection box info for the next line.
1965 selectionBoxInfo->minX = MAX_FLOAT;
1966 selectionBoxInfo->maxX = MIN_FLOAT;
1968 // Update the line's vertical offset.
1969 selectionBoxInfo->lineOffset = currentLineOffset + currentLineHeight;
1971 // The line height is the addition of the line ascender and the line descender.
1972 // However, the line descender has a negative value, hence the subtraction.
1973 selectionBoxInfo->lineHeight = lineRun->ascender - lineRun->descender;
1978 // Traverses all the lines and updates the min and max 'x' positions and the total height.
1979 // The final width is calculated after 'boxifying' the selection.
1980 for( Vector<SelectionBoxInfo>::ConstIterator it = selectionBoxLinesInfo.Begin(),
1981 endIt = selectionBoxLinesInfo.End();
1985 const SelectionBoxInfo& info = *it;
1987 // Update the size of the highlighted text.
1988 highLightSize.height += info.lineHeight;
1989 minHighlightX = std::min( minHighlightX, info.minX );
1990 maxHighlightX = std::max( maxHighlightX, info.maxX );
1993 // Add extra geometry to 'boxify' the selection.
1995 if( 1u < numberOfLines )
1997 // Boxify the first line.
1998 lineRun = mModel->mVisualModel->mLines.Begin() + firstLineIndex;
1999 const SelectionBoxInfo& firstSelectionBoxLineInfo = *( selectionBoxLinesInfo.Begin() );
2001 bool boxifyBegin = ( LTR != lineRun->direction ) && ( LTR != startDirection );
2002 bool boxifyEnd = ( LTR == lineRun->direction ) && ( LTR == startDirection );
2007 quad.y = firstSelectionBoxLineInfo.lineOffset;
2008 quad.z = firstSelectionBoxLineInfo.minX;
2009 quad.w = firstSelectionBoxLineInfo.lineOffset + firstSelectionBoxLineInfo.lineHeight;
2011 // Boxify at the beginning of the line.
2012 mEventData->mDecorator->AddHighlight( actualNumberOfQuads,
2014 ++actualNumberOfQuads;
2016 // Update the size of the highlighted text.
2017 minHighlightX = 0.f;
2022 quad.x = firstSelectionBoxLineInfo.maxX;
2023 quad.y = firstSelectionBoxLineInfo.lineOffset;
2024 quad.z = mModel->mVisualModel->mControlSize.width;
2025 quad.w = firstSelectionBoxLineInfo.lineOffset + firstSelectionBoxLineInfo.lineHeight;
2027 // Boxify at the end of the line.
2028 mEventData->mDecorator->AddHighlight( actualNumberOfQuads,
2030 ++actualNumberOfQuads;
2032 // Update the size of the highlighted text.
2033 maxHighlightX = mModel->mVisualModel->mControlSize.width;
2036 // Boxify the central lines.
2037 if( 2u < numberOfLines )
2039 for( Vector<SelectionBoxInfo>::ConstIterator it = selectionBoxLinesInfo.Begin() + 1u,
2040 endIt = selectionBoxLinesInfo.End() - 1u;
2044 const SelectionBoxInfo& info = *it;
2047 quad.y = info.lineOffset;
2049 quad.w = info.lineOffset + info.lineHeight;
2051 mEventData->mDecorator->AddHighlight( actualNumberOfQuads,
2053 ++actualNumberOfQuads;
2056 quad.y = info.lineOffset;
2057 quad.z = mModel->mVisualModel->mControlSize.width;
2058 quad.w = info.lineOffset + info.lineHeight;
2060 mEventData->mDecorator->AddHighlight( actualNumberOfQuads,
2062 ++actualNumberOfQuads;
2065 // Update the size of the highlighted text.
2066 minHighlightX = 0.f;
2067 maxHighlightX = mModel->mVisualModel->mControlSize.width;
2070 // Boxify the last line.
2071 lineRun = mModel->mVisualModel->mLines.Begin() + firstLineIndex + numberOfLines - 1u;
2072 const SelectionBoxInfo& lastSelectionBoxLineInfo = *( selectionBoxLinesInfo.End() - 1u );
2074 boxifyBegin = ( LTR == lineRun->direction ) && ( LTR == endDirection );
2075 boxifyEnd = ( LTR != lineRun->direction ) && ( LTR != endDirection );
2080 quad.y = lastSelectionBoxLineInfo.lineOffset;
2081 quad.z = lastSelectionBoxLineInfo.minX;
2082 quad.w = lastSelectionBoxLineInfo.lineOffset + lastSelectionBoxLineInfo.lineHeight;
2084 // Boxify at the beginning of the line.
2085 mEventData->mDecorator->AddHighlight( actualNumberOfQuads,
2087 ++actualNumberOfQuads;
2089 // Update the size of the highlighted text.
2090 minHighlightX = 0.f;
2095 quad.x = lastSelectionBoxLineInfo.maxX;
2096 quad.y = lastSelectionBoxLineInfo.lineOffset;
2097 quad.z = mModel->mVisualModel->mControlSize.width;
2098 quad.w = lastSelectionBoxLineInfo.lineOffset + lastSelectionBoxLineInfo.lineHeight;
2100 // Boxify at the end of the line.
2101 mEventData->mDecorator->AddHighlight( actualNumberOfQuads,
2103 ++actualNumberOfQuads;
2105 // Update the size of the highlighted text.
2106 maxHighlightX = mModel->mVisualModel->mControlSize.width;
2110 // Set the actual number of quads.
2111 mEventData->mDecorator->ResizeHighlightQuads( actualNumberOfQuads );
2113 // Sets the highlight's size and position. In decorator's coords.
2114 // The highlight's height has been calculated above (before 'boxifying' the highlight).
2115 highLightSize.width = maxHighlightX - minHighlightX;
2117 highLightPosition.x = minHighlightX;
2118 const SelectionBoxInfo& firstSelectionBoxLineInfo = *( selectionBoxLinesInfo.Begin() );
2119 highLightPosition.y = firstSelectionBoxLineInfo.lineOffset;
2121 mEventData->mDecorator->SetHighLightBox( highLightPosition, highLightSize );
2123 if( !mEventData->mDecorator->IsSmoothHandlePanEnabled() )
2125 CursorInfo primaryCursorInfo;
2126 GetCursorPosition( mEventData->mLeftSelectionPosition,
2127 primaryCursorInfo );
2129 const Vector2 primaryPosition = primaryCursorInfo.primaryPosition + mModel->mScrollPosition;
2131 mEventData->mDecorator->SetPosition( LEFT_SELECTION_HANDLE,
2133 primaryCursorInfo.lineOffset + mModel->mScrollPosition.y,
2134 primaryCursorInfo.lineHeight );
2136 CursorInfo secondaryCursorInfo;
2137 GetCursorPosition( mEventData->mRightSelectionPosition,
2138 secondaryCursorInfo );
2140 const Vector2 secondaryPosition = secondaryCursorInfo.primaryPosition + mModel->mScrollPosition;
2142 mEventData->mDecorator->SetPosition( RIGHT_SELECTION_HANDLE,
2143 secondaryPosition.x,
2144 secondaryCursorInfo.lineOffset + mModel->mScrollPosition.y,
2145 secondaryCursorInfo.lineHeight );
2148 // Cursor to be positioned at end of selection so if selection interrupted and edit mode restarted the cursor will be at end of selection
2149 mEventData->mPrimaryCursorPosition = ( indicesSwapped ) ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition;
2151 // Set the flag to update the decorator.
2152 mEventData->mDecoratorUpdated = true;
2155 void Controller::Impl::RepositionSelectionHandles( float visualX, float visualY )
2157 if( NULL == mEventData )
2159 // Nothing to do if there is no text input.
2163 if( IsShowingPlaceholderText() )
2165 // Nothing to do if there is the place-holder text.
2169 const Length numberOfGlyphs = mModel->mVisualModel->mGlyphs.Count();
2170 const Length numberOfLines = mModel->mVisualModel->mLines.Count();
2171 if( ( 0 == numberOfGlyphs ) ||
2172 ( 0 == numberOfLines ) )
2174 // Nothing to do if there is no text.
2178 // Find which word was selected
2179 CharacterIndex selectionStart( 0 );
2180 CharacterIndex selectionEnd( 0 );
2181 const bool indicesFound = FindSelectionIndices( mModel->mVisualModel,
2182 mModel->mLogicalModel,
2188 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "%p selectionStart %d selectionEnd %d\n", this, selectionStart, selectionEnd );
2192 ChangeState( EventData::SELECTING );
2194 mEventData->mLeftSelectionPosition = selectionStart;
2195 mEventData->mRightSelectionPosition = selectionEnd;
2197 mEventData->mUpdateLeftSelectionPosition = true;
2198 mEventData->mUpdateRightSelectionPosition = true;
2199 mEventData->mUpdateHighlightBox = true;
2201 // It may happen an IMF commit event arrives before the selection event
2202 // if the IMF manager is in pre-edit state. The commit event will set the
2203 // mEventData->mUpdateCursorPosition flag to true. If it's not set back
2204 // to false, the highlight box won't be updated.
2205 mEventData->mUpdateCursorPosition = false;
2207 mEventData->mScrollAfterUpdatePosition = ( mEventData->mLeftSelectionPosition != mEventData->mRightSelectionPosition );
2211 // Nothing to select. i.e. a white space, out of bounds
2212 ChangeState( EventData::EDITING );
2214 mEventData->mPrimaryCursorPosition = selectionEnd;
2216 mEventData->mUpdateCursorPosition = true;
2217 mEventData->mUpdateGrabHandlePosition = true;
2218 mEventData->mScrollAfterUpdatePosition = true;
2219 mEventData->mUpdateInputStyle = true;
2223 void Controller::Impl::SetPopupButtons()
2226 * Sets the Popup buttons to be shown depending on State.
2228 * If SELECTING : CUT & COPY + ( PASTE & CLIPBOARD if content available to paste )
2230 * If EDITING_WITH_POPUP : SELECT & SELECT_ALL
2233 TextSelectionPopup::Buttons buttonsToShow = TextSelectionPopup::NONE;
2235 if( EventData::SELECTING == mEventData->mState )
2237 buttonsToShow = TextSelectionPopup::Buttons( TextSelectionPopup::CUT | TextSelectionPopup::COPY );
2239 if( !IsClipboardEmpty() )
2241 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
2242 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::CLIPBOARD ) );
2245 if( !mEventData->mAllTextSelected )
2247 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::SELECT_ALL ) );
2250 else if( EventData::EDITING_WITH_POPUP == mEventData->mState )
2252 if( mModel->mLogicalModel->mText.Count() && !IsShowingPlaceholderText() )
2254 buttonsToShow = TextSelectionPopup::Buttons( TextSelectionPopup::SELECT | TextSelectionPopup::SELECT_ALL );
2257 if( !IsClipboardEmpty() )
2259 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
2260 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::CLIPBOARD ) );
2263 else if( EventData::EDITING_WITH_PASTE_POPUP == mEventData->mState )
2265 if ( !IsClipboardEmpty() )
2267 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
2268 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::CLIPBOARD ) );
2272 mEventData->mDecorator->SetEnabledPopupButtons( buttonsToShow );
2275 void Controller::Impl::ChangeState( EventData::State newState )
2277 if( NULL == mEventData )
2279 // Nothing to do if there is no text input.
2283 DALI_LOG_INFO( gLogFilter, Debug::General, "ChangeState state:%d newstate:%d\n", mEventData->mState, newState );
2285 if( mEventData->mState != newState )
2287 mEventData->mPreviousState = mEventData->mState;
2288 mEventData->mState = newState;
2290 switch( mEventData->mState )
2292 case EventData::INACTIVE:
2294 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
2295 mEventData->mDecorator->StopCursorBlink();
2296 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
2297 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
2298 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
2299 mEventData->mDecorator->SetHighlightActive( false );
2300 mEventData->mDecorator->SetPopupActive( false );
2301 mEventData->mDecoratorUpdated = true;
2304 case EventData::INTERRUPTED:
2306 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
2307 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
2308 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
2309 mEventData->mDecorator->SetHighlightActive( false );
2310 mEventData->mDecorator->SetPopupActive( false );
2311 mEventData->mDecoratorUpdated = true;
2314 case EventData::SELECTING:
2316 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
2317 mEventData->mDecorator->StopCursorBlink();
2318 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
2319 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true );
2320 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true );
2321 mEventData->mDecorator->SetHighlightActive( true );
2322 if( mEventData->mGrabHandlePopupEnabled )
2325 mEventData->mDecorator->SetPopupActive( true );
2327 mEventData->mDecoratorUpdated = true;
2330 case EventData::EDITING:
2332 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
2333 if( mEventData->mCursorBlinkEnabled )
2335 mEventData->mDecorator->StartCursorBlink();
2337 // Grab handle is not shown until a tap is received whilst EDITING
2338 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
2339 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
2340 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
2341 mEventData->mDecorator->SetHighlightActive( false );
2342 if( mEventData->mGrabHandlePopupEnabled )
2344 mEventData->mDecorator->SetPopupActive( false );
2346 mEventData->mDecoratorUpdated = true;
2349 case EventData::EDITING_WITH_POPUP:
2351 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "EDITING_WITH_POPUP \n", newState );
2353 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
2354 if( mEventData->mCursorBlinkEnabled )
2356 mEventData->mDecorator->StartCursorBlink();
2358 if( mEventData->mSelectionEnabled )
2360 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
2361 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
2362 mEventData->mDecorator->SetHighlightActive( false );
2366 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
2368 if( mEventData->mGrabHandlePopupEnabled )
2371 mEventData->mDecorator->SetPopupActive( true );
2373 mEventData->mDecoratorUpdated = true;
2376 case EventData::EDITING_WITH_GRAB_HANDLE:
2378 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "EDITING_WITH_GRAB_HANDLE \n", newState );
2380 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
2381 if( mEventData->mCursorBlinkEnabled )
2383 mEventData->mDecorator->StartCursorBlink();
2385 // Grab handle is not shown until a tap is received whilst EDITING
2386 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
2387 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
2388 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
2389 mEventData->mDecorator->SetHighlightActive( false );
2390 if( mEventData->mGrabHandlePopupEnabled )
2392 mEventData->mDecorator->SetPopupActive( false );
2394 mEventData->mDecoratorUpdated = true;
2397 case EventData::SELECTION_HANDLE_PANNING:
2399 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
2400 mEventData->mDecorator->StopCursorBlink();
2401 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
2402 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true );
2403 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true );
2404 mEventData->mDecorator->SetHighlightActive( true );
2405 if( mEventData->mGrabHandlePopupEnabled )
2407 mEventData->mDecorator->SetPopupActive( false );
2409 mEventData->mDecoratorUpdated = true;
2412 case EventData::GRAB_HANDLE_PANNING:
2414 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "GRAB_HANDLE_PANNING \n", newState );
2416 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
2417 if( mEventData->mCursorBlinkEnabled )
2419 mEventData->mDecorator->StartCursorBlink();
2421 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
2422 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
2423 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
2424 mEventData->mDecorator->SetHighlightActive( false );
2425 if( mEventData->mGrabHandlePopupEnabled )
2427 mEventData->mDecorator->SetPopupActive( false );
2429 mEventData->mDecoratorUpdated = true;
2432 case EventData::EDITING_WITH_PASTE_POPUP:
2434 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "EDITING_WITH_PASTE_POPUP \n", newState );
2436 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
2437 if( mEventData->mCursorBlinkEnabled )
2439 mEventData->mDecorator->StartCursorBlink();
2442 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
2443 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
2444 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
2445 mEventData->mDecorator->SetHighlightActive( false );
2447 if( mEventData->mGrabHandlePopupEnabled )
2450 mEventData->mDecorator->SetPopupActive( true );
2452 mEventData->mDecoratorUpdated = true;
2455 case EventData::TEXT_PANNING:
2457 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
2458 mEventData->mDecorator->StopCursorBlink();
2459 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
2460 if( mEventData->mDecorator->IsHandleActive( LEFT_SELECTION_HANDLE ) ||
2461 mEventData->mDecorator->IsHandleActive( RIGHT_SELECTION_HANDLE ) )
2463 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
2464 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
2465 mEventData->mDecorator->SetHighlightActive( true );
2468 if( mEventData->mGrabHandlePopupEnabled )
2470 mEventData->mDecorator->SetPopupActive( false );
2473 mEventData->mDecoratorUpdated = true;
2480 void Controller::Impl::GetCursorPosition( CharacterIndex logical,
2481 CursorInfo& cursorInfo )
2483 if( !IsShowingRealText() )
2485 // Do not want to use the place-holder text to set the cursor position.
2487 // Use the line's height of the font's family set to set the cursor's size.
2488 // If there is no font's family set, use the default font.
2489 // Use the current alignment to place the cursor at the beginning, center or end of the box.
2491 cursorInfo.lineOffset = 0.f;
2492 cursorInfo.lineHeight = GetDefaultFontLineHeight();
2493 cursorInfo.primaryCursorHeight = cursorInfo.lineHeight;
2495 switch( mModel->mHorizontalAlignment )
2497 case Layout::HORIZONTAL_ALIGN_BEGIN:
2499 cursorInfo.primaryPosition.x = 0.f;
2502 case Layout::HORIZONTAL_ALIGN_CENTER:
2504 cursorInfo.primaryPosition.x = floorf( 0.5f * mModel->mVisualModel->mControlSize.width );
2507 case Layout::HORIZONTAL_ALIGN_END:
2509 cursorInfo.primaryPosition.x = mModel->mVisualModel->mControlSize.width - mEventData->mDecorator->GetCursorWidth();
2514 // Nothing else to do.
2518 Text::GetCursorPosition( mModel->mVisualModel,
2519 mModel->mLogicalModel,
2524 if( Layout::Engine::MULTI_LINE_BOX == mLayoutEngine.GetLayout() )
2526 // If the text is editable and multi-line, the cursor position after a white space shouldn't exceed the boundaries of the text control.
2528 // Note the white spaces laid-out at the end of the line might exceed the boundaries of the control.
2529 // The reason is a wrapped line must not start with a white space so they are laid-out at the end of the line.
2531 if( 0.f > cursorInfo.primaryPosition.x )
2533 cursorInfo.primaryPosition.x = 0.f;
2536 const float edgeWidth = mModel->mVisualModel->mControlSize.width - static_cast<float>( mEventData->mDecorator->GetCursorWidth() );
2537 if( cursorInfo.primaryPosition.x > edgeWidth )
2539 cursorInfo.primaryPosition.x = edgeWidth;
2544 CharacterIndex Controller::Impl::CalculateNewCursorIndex( CharacterIndex index ) const
2546 if( NULL == mEventData )
2548 // Nothing to do if there is no text input.
2552 CharacterIndex cursorIndex = mEventData->mPrimaryCursorPosition;
2554 const GlyphIndex* const charactersToGlyphBuffer = mModel->mVisualModel->mCharactersToGlyph.Begin();
2555 const Length* const charactersPerGlyphBuffer = mModel->mVisualModel->mCharactersPerGlyph.Begin();
2557 GlyphIndex glyphIndex = *( charactersToGlyphBuffer + index );
2558 Length numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
2560 if( numberOfCharacters > 1u )
2562 const Script script = mModel->mLogicalModel->GetScript( index );
2563 if( HasLigatureMustBreak( script ) )
2565 // Prevents to jump the whole Latin ligatures like fi, ff, or Arabic ﻻ, ...
2566 numberOfCharacters = 1u;
2571 while( 0u == numberOfCharacters )
2574 numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
2578 if( index < mEventData->mPrimaryCursorPosition )
2580 cursorIndex -= numberOfCharacters;
2584 cursorIndex += numberOfCharacters;
2587 // Will update the cursor hook position.
2588 mEventData->mUpdateCursorHookPosition = true;
2593 void Controller::Impl::UpdateCursorPosition( const CursorInfo& cursorInfo )
2595 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "-->Controller::UpdateCursorPosition %p\n", this );
2596 if( NULL == mEventData )
2598 // Nothing to do if there is no text input.
2599 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition no event data\n" );
2603 const Vector2 cursorPosition = cursorInfo.primaryPosition + mModel->mScrollPosition;
2605 // Sets the cursor position.
2606 mEventData->mDecorator->SetPosition( PRIMARY_CURSOR,
2609 cursorInfo.primaryCursorHeight,
2610 cursorInfo.lineHeight );
2611 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Primary cursor position: %f,%f\n", cursorPosition.x, cursorPosition.y );
2613 if( mEventData->mUpdateGrabHandlePosition )
2615 // Sets the grab handle position.
2616 mEventData->mDecorator->SetPosition( GRAB_HANDLE,
2618 cursorInfo.lineOffset + mModel->mScrollPosition.y,
2619 cursorInfo.lineHeight );
2622 if( cursorInfo.isSecondaryCursor )
2624 mEventData->mDecorator->SetPosition( SECONDARY_CURSOR,
2625 cursorInfo.secondaryPosition.x + mModel->mScrollPosition.x,
2626 cursorInfo.secondaryPosition.y + mModel->mScrollPosition.y,
2627 cursorInfo.secondaryCursorHeight,
2628 cursorInfo.lineHeight );
2629 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Secondary cursor position: %f,%f\n", cursorInfo.secondaryPosition.x + mModel->mScrollPosition.x, cursorInfo.secondaryPosition.y + mModel->mScrollPosition.y );
2632 // Set which cursors are active according the state.
2633 if( EventData::IsEditingState( mEventData->mState ) || ( EventData::GRAB_HANDLE_PANNING == mEventData->mState ) )
2635 if( cursorInfo.isSecondaryCursor )
2637 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_BOTH );
2641 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
2646 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
2649 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition\n" );
2652 void Controller::Impl::UpdateSelectionHandle( HandleType handleType,
2653 const CursorInfo& cursorInfo )
2655 if( ( LEFT_SELECTION_HANDLE != handleType ) &&
2656 ( RIGHT_SELECTION_HANDLE != handleType ) )
2661 const Vector2 cursorPosition = cursorInfo.primaryPosition + mModel->mScrollPosition;
2663 // Sets the handle's position.
2664 mEventData->mDecorator->SetPosition( handleType,
2666 cursorInfo.lineOffset + mModel->mScrollPosition.y,
2667 cursorInfo.lineHeight );
2669 // If selection handle at start of the text and other at end of the text then all text is selected.
2670 const CharacterIndex startOfSelection = std::min( mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition );
2671 const CharacterIndex endOfSelection = std::max ( mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition );
2672 mEventData->mAllTextSelected = ( startOfSelection == 0 ) && ( endOfSelection == mModel->mLogicalModel->mText.Count() );
2675 void Controller::Impl::ClampHorizontalScroll( const Vector2& layoutSize )
2677 // Clamp between -space & -alignment offset.
2679 if( layoutSize.width > mModel->mVisualModel->mControlSize.width )
2681 const float space = ( layoutSize.width - mModel->mVisualModel->mControlSize.width ) + mModel->mAlignmentOffset;
2682 mModel->mScrollPosition.x = ( mModel->mScrollPosition.x < -space ) ? -space : mModel->mScrollPosition.x;
2683 mModel->mScrollPosition.x = ( mModel->mScrollPosition.x > -mModel->mAlignmentOffset ) ? -mModel->mAlignmentOffset : mModel->mScrollPosition.x;
2685 mEventData->mDecoratorUpdated = true;
2689 mModel->mScrollPosition.x = 0.f;
2693 void Controller::Impl::ClampVerticalScroll( const Vector2& layoutSize )
2695 if( Layout::Engine::SINGLE_LINE_BOX == mLayoutEngine.GetLayout() )
2697 // Nothing to do if the text is single line.
2701 // Clamp between -space & 0.
2702 if( layoutSize.height > mModel->mVisualModel->mControlSize.height )
2704 const float space = ( layoutSize.height - mModel->mVisualModel->mControlSize.height );
2705 mModel->mScrollPosition.y = ( mModel->mScrollPosition.y < -space ) ? -space : mModel->mScrollPosition.y;
2706 mModel->mScrollPosition.y = ( mModel->mScrollPosition.y > 0.f ) ? 0.f : mModel->mScrollPosition.y;
2708 mEventData->mDecoratorUpdated = true;
2712 mModel->mScrollPosition.y = 0.f;
2716 void Controller::Impl::ScrollToMakePositionVisible( const Vector2& position, float lineHeight )
2718 const float cursorWidth = mEventData->mDecorator ? static_cast<float>( mEventData->mDecorator->GetCursorWidth() ) : 0.f;
2720 // position is in actor's coords.
2721 const float positionEndX = position.x + cursorWidth;
2722 const float positionEndY = position.y + lineHeight;
2724 // Transform the position to decorator coords.
2725 const float decoratorPositionBeginX = position.x + mModel->mScrollPosition.x;
2726 const float decoratorPositionEndX = positionEndX + mModel->mScrollPosition.x;
2728 const float decoratorPositionBeginY = position.y + mModel->mScrollPosition.y;
2729 const float decoratorPositionEndY = positionEndY + mModel->mScrollPosition.y;
2731 if( decoratorPositionBeginX < 0.f )
2733 mModel->mScrollPosition.x = -position.x;
2735 else if( decoratorPositionEndX > mModel->mVisualModel->mControlSize.width )
2737 mModel->mScrollPosition.x = mModel->mVisualModel->mControlSize.width - positionEndX;
2740 if( decoratorPositionBeginY < 0.f )
2742 mModel->mScrollPosition.y = -position.y;
2744 else if( decoratorPositionEndY > mModel->mVisualModel->mControlSize.height )
2746 mModel->mScrollPosition.y = mModel->mVisualModel->mControlSize.height - positionEndY;
2750 void Controller::Impl::ScrollTextToMatchCursor( const CursorInfo& cursorInfo )
2752 // Get the current cursor position in decorator coords.
2753 const Vector2& currentCursorPosition = mEventData->mDecorator->GetPosition( PRIMARY_CURSOR );
2755 // Calculate the offset to match the cursor position before the character was deleted.
2756 mModel->mScrollPosition.x = currentCursorPosition.x - cursorInfo.primaryPosition.x;
2757 mModel->mScrollPosition.y = currentCursorPosition.y - cursorInfo.lineOffset;
2759 ClampHorizontalScroll( mModel->mVisualModel->GetLayoutSize() );
2760 ClampVerticalScroll( mModel->mVisualModel->GetLayoutSize() );
2762 // Makes the new cursor position visible if needed.
2763 ScrollToMakePositionVisible( cursorInfo.primaryPosition, cursorInfo.lineHeight );
2766 void Controller::Impl::RequestRelayout()
2768 if( NULL != mControlInterface )
2770 mControlInterface->RequestTextRelayout();
2776 } // namespace Toolkit