2 * Copyright (c) 2015 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-run-container.h>
40 * @brief Struct used to calculate the selection box.
42 struct SelectionBoxInfo
50 #if defined(DEBUG_ENABLED)
51 Debug::Filter* gLogFilter = Debug::Filter::New(Debug::NoLogging, true, "LOG_TEXT_CONTROLS");
54 const float MAX_FLOAT = std::numeric_limits<float>::max();
55 const float MIN_FLOAT = std::numeric_limits<float>::min();
56 const Dali::Toolkit::Text::CharacterDirection LTR = false; ///< Left To Right direction
69 EventData::EventData( DecoratorPtr decorator )
70 : mDecorator( decorator ),
72 mPlaceholderTextActive(),
73 mPlaceholderTextInactive(),
74 mPlaceholderTextColor( 0.8f, 0.8f, 0.8f, 0.8f ),
77 mPrimaryCursorPosition( 0u ),
78 mLeftSelectionPosition( 0u ),
79 mRightSelectionPosition( 0u ),
80 mPreEditStartPosition( 0u ),
82 mCursorHookPositionX( 0.f ),
83 mIsShowingPlaceholderText( false ),
84 mPreEditFlag( false ),
85 mDecoratorUpdated( false ),
86 mCursorBlinkEnabled( true ),
87 mGrabHandleEnabled( true ),
88 mGrabHandlePopupEnabled( true ),
89 mSelectionEnabled( true ),
90 mUpdateCursorPosition( false ),
91 mUpdateGrabHandlePosition( false ),
92 mUpdateLeftSelectionPosition( false ),
93 mUpdateRightSelectionPosition( false ),
94 mUpdateHighlightBox( false ),
95 mScrollAfterUpdatePosition( false ),
96 mScrollAfterDelete( false ),
97 mAllTextSelected( false ),
98 mUpdateInputStyle( false )
100 mImfManager = ImfManager::Get();
103 EventData::~EventData()
106 bool Controller::Impl::ProcessInputEvents()
108 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "-->Controller::ProcessInputEvents\n" );
109 if( NULL == mEventData )
111 // Nothing to do if there is no text input.
112 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::ProcessInputEvents no event data\n" );
116 if( mEventData->mDecorator )
118 for( std::vector<Event>::iterator iter = mEventData->mEventQueue.begin();
119 iter != mEventData->mEventQueue.end();
124 case Event::CURSOR_KEY_EVENT:
126 OnCursorKeyEvent( *iter );
129 case Event::TAP_EVENT:
134 case Event::LONG_PRESS_EVENT:
136 OnLongPressEvent( *iter );
139 case Event::PAN_EVENT:
144 case Event::GRAB_HANDLE_EVENT:
145 case Event::LEFT_SELECTION_HANDLE_EVENT:
146 case Event::RIGHT_SELECTION_HANDLE_EVENT: // Fall through
148 OnHandleEvent( *iter );
153 OnSelectEvent( *iter );
156 case Event::SELECT_ALL:
165 if( mEventData->mUpdateCursorPosition ||
166 mEventData->mUpdateHighlightBox )
171 // The cursor must also be repositioned after inserts into the model
172 if( mEventData->mUpdateCursorPosition )
174 // Updates the cursor position and scrolls the text to make it visible.
175 CursorInfo cursorInfo;
176 // Calculate the cursor position from the new cursor index.
177 GetCursorPosition( mEventData->mPrimaryCursorPosition,
180 if( mEventData->mUpdateCursorHookPosition )
182 // Update the cursor hook position. Used to move the cursor with the keys 'up' and 'down'.
183 mEventData->mCursorHookPositionX = cursorInfo.primaryPosition.x;
184 mEventData->mUpdateCursorHookPosition = false;
187 // Scroll first the text after delete ...
188 if( mEventData->mScrollAfterDelete )
190 ScrollTextToMatchCursor( cursorInfo );
193 // ... then, text can be scrolled to make the cursor visible.
194 if( mEventData->mScrollAfterUpdatePosition )
196 const Vector2 currentCursorPosition( cursorInfo.primaryPosition.x, cursorInfo.lineOffset );
197 ScrollToMakePositionVisible( currentCursorPosition, cursorInfo.lineHeight );
199 mEventData->mScrollAfterUpdatePosition = false;
200 mEventData->mScrollAfterDelete = false;
202 UpdateCursorPosition( cursorInfo );
204 mEventData->mDecoratorUpdated = true;
205 mEventData->mUpdateCursorPosition = false;
206 mEventData->mUpdateGrabHandlePosition = false;
210 CursorInfo leftHandleInfo;
211 CursorInfo rightHandleInfo;
213 if( mEventData->mUpdateHighlightBox )
215 GetCursorPosition( mEventData->mLeftSelectionPosition,
218 GetCursorPosition( mEventData->mRightSelectionPosition,
221 if( mEventData->mScrollAfterUpdatePosition && mEventData->mUpdateLeftSelectionPosition )
223 const Vector2 currentCursorPosition( leftHandleInfo.primaryPosition.x, leftHandleInfo.lineOffset );
224 ScrollToMakePositionVisible( currentCursorPosition, leftHandleInfo.lineHeight );
227 if( mEventData->mScrollAfterUpdatePosition && mEventData->mUpdateRightSelectionPosition )
229 const Vector2 currentCursorPosition( rightHandleInfo.primaryPosition.x, rightHandleInfo.lineOffset );
230 ScrollToMakePositionVisible( currentCursorPosition, rightHandleInfo.lineHeight );
234 if( mEventData->mUpdateLeftSelectionPosition )
236 UpdateSelectionHandle( LEFT_SELECTION_HANDLE,
240 mEventData->mDecoratorUpdated = true;
241 mEventData->mUpdateLeftSelectionPosition = false;
244 if( mEventData->mUpdateRightSelectionPosition )
246 UpdateSelectionHandle( RIGHT_SELECTION_HANDLE,
250 mEventData->mDecoratorUpdated = true;
251 mEventData->mUpdateRightSelectionPosition = false;
254 if( mEventData->mUpdateHighlightBox )
256 RepositionSelectionHandles();
258 mEventData->mUpdateLeftSelectionPosition = false;
259 mEventData->mUpdateRightSelectionPosition = false;
260 mEventData->mUpdateHighlightBox = false;
263 mEventData->mScrollAfterUpdatePosition = false;
266 if( mEventData->mUpdateInputStyle )
268 // Set the default style first.
269 RetrieveDefaultInputStyle( mEventData->mInputStyle );
271 // Get the character index from the cursor index.
272 const CharacterIndex styleIndex = ( mEventData->mPrimaryCursorPosition > 0u ) ? mEventData->mPrimaryCursorPosition - 1u : 0u;
274 // Retrieve the style from the style runs stored in the logical model.
275 mLogicalModel->RetrieveStyle( styleIndex, mEventData->mInputStyle );
277 mEventData->mUpdateInputStyle = false;
280 mEventData->mEventQueue.clear();
282 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::ProcessInputEvents\n" );
284 const bool decoratorUpdated = mEventData->mDecoratorUpdated;
285 mEventData->mDecoratorUpdated = false;
287 return decoratorUpdated;
290 void Controller::Impl::NotifyImfManager()
292 if( mEventData && mEventData->mImfManager )
294 CharacterIndex cursorPosition = GetLogicalCursorPosition();
296 const Length numberOfWhiteSpaces = GetNumberOfWhiteSpaces( 0u );
298 // Update the cursor position by removing the initial white spaces.
299 if( cursorPosition < numberOfWhiteSpaces )
305 cursorPosition -= numberOfWhiteSpaces;
308 mEventData->mImfManager.SetCursorPosition( cursorPosition );
309 mEventData->mImfManager.NotifyCursorPosition();
313 CharacterIndex Controller::Impl::GetLogicalCursorPosition() const
315 CharacterIndex cursorPosition = 0u;
319 if( ( EventData::SELECTING == mEventData->mState ) ||
320 ( EventData::SELECTION_HANDLE_PANNING == mEventData->mState ) )
322 cursorPosition = std::min( mEventData->mRightSelectionPosition, mEventData->mLeftSelectionPosition );
326 cursorPosition = mEventData->mPrimaryCursorPosition;
330 return cursorPosition;
333 Length Controller::Impl::GetNumberOfWhiteSpaces( CharacterIndex index ) const
335 Length numberOfWhiteSpaces = 0u;
337 // Get the buffer to the text.
338 Character* utf32CharacterBuffer = mLogicalModel->mText.Begin();
340 const Length totalNumberOfCharacters = mLogicalModel->mText.Count();
341 for( ; index < totalNumberOfCharacters; ++index, ++numberOfWhiteSpaces )
343 if( !TextAbstraction::IsWhiteSpace( *( utf32CharacterBuffer + index ) ) )
349 return numberOfWhiteSpaces;
352 void Controller::Impl::GetText( CharacterIndex index, std::string& text ) const
354 // Get the total number of characters.
355 Length numberOfCharacters = mLogicalModel->mText.Count();
357 // Retrieve the text.
358 if( 0u != numberOfCharacters )
360 Utf32ToUtf8( mLogicalModel->mText.Begin() + index, numberOfCharacters - index, text );
364 void Controller::Impl::CalculateTextUpdateIndices( Length& numberOfCharacters )
366 mTextUpdateInfo.mParagraphCharacterIndex = 0u;
367 mTextUpdateInfo.mStartGlyphIndex = 0u;
368 mTextUpdateInfo.mStartLineIndex = 0u;
369 numberOfCharacters = 0u;
371 const Length numberOfParagraphs = mLogicalModel->mParagraphInfo.Count();
372 if( 0u == numberOfParagraphs )
374 mTextUpdateInfo.mParagraphCharacterIndex = 0u;
375 numberOfCharacters = 0u;
377 mTextUpdateInfo.mRequestedNumberOfCharacters = mTextUpdateInfo.mNumberOfCharactersToAdd - mTextUpdateInfo.mNumberOfCharactersToRemove;
379 // Nothing else to do if there are no paragraphs.
383 // Find the paragraphs to be updated.
384 Vector<ParagraphRunIndex> paragraphsToBeUpdated;
385 if( mTextUpdateInfo.mCharacterIndex >= mTextUpdateInfo.mPreviousNumberOfCharacters )
387 // Text is being added at the end of the current text.
388 if( mTextUpdateInfo.mIsLastCharacterNewParagraph )
390 // Text is being added in a new paragraph after the last character of the text.
391 mTextUpdateInfo.mParagraphCharacterIndex = mTextUpdateInfo.mPreviousNumberOfCharacters;
392 numberOfCharacters = 0u;
393 mTextUpdateInfo.mRequestedNumberOfCharacters = mTextUpdateInfo.mNumberOfCharactersToAdd - mTextUpdateInfo.mNumberOfCharactersToRemove;
395 mTextUpdateInfo.mStartGlyphIndex = mVisualModel->mGlyphs.Count();
396 mTextUpdateInfo.mStartLineIndex = mVisualModel->mLines.Count() - 1u;
398 // Nothing else to do;
402 paragraphsToBeUpdated.PushBack( numberOfParagraphs - 1u );
406 Length numberOfCharactersToUpdate = 0u;
407 if( mTextUpdateInfo.mFullRelayoutNeeded )
409 numberOfCharactersToUpdate = mTextUpdateInfo.mPreviousNumberOfCharacters;
413 numberOfCharactersToUpdate = ( mTextUpdateInfo.mNumberOfCharactersToRemove > 0u ) ? mTextUpdateInfo.mNumberOfCharactersToRemove : 1u;
415 mLogicalModel->FindParagraphs( mTextUpdateInfo.mCharacterIndex,
416 numberOfCharactersToUpdate,
417 paragraphsToBeUpdated );
420 if( 0u != paragraphsToBeUpdated.Count() )
422 const ParagraphRunIndex firstParagraphIndex = *( paragraphsToBeUpdated.Begin() );
423 const ParagraphRun& firstParagraph = *( mLogicalModel->mParagraphInfo.Begin() + firstParagraphIndex );
424 mTextUpdateInfo.mParagraphCharacterIndex = firstParagraph.characterRun.characterIndex;
426 ParagraphRunIndex lastParagraphIndex = *( paragraphsToBeUpdated.End() - 1u );
427 const ParagraphRun& lastParagraph = *( mLogicalModel->mParagraphInfo.Begin() + lastParagraphIndex );
429 if( ( mTextUpdateInfo.mNumberOfCharactersToRemove > 0u ) && // Some character are removed.
430 ( lastParagraphIndex < numberOfParagraphs - 1u ) && // There is a next paragraph.
431 ( ( lastParagraph.characterRun.characterIndex + lastParagraph.characterRun.numberOfCharacters ) == // The last removed character is the new paragraph character.
432 ( mTextUpdateInfo.mCharacterIndex + mTextUpdateInfo.mNumberOfCharactersToRemove ) ) )
434 // The new paragraph character of the last updated paragraph has been removed so is going to be merged with the next one.
435 const ParagraphRun& lastParagraph = *( mLogicalModel->mParagraphInfo.Begin() + lastParagraphIndex + 1u );
437 numberOfCharacters = lastParagraph.characterRun.characterIndex + lastParagraph.characterRun.numberOfCharacters - mTextUpdateInfo.mParagraphCharacterIndex;
441 numberOfCharacters = lastParagraph.characterRun.characterIndex + lastParagraph.characterRun.numberOfCharacters - mTextUpdateInfo.mParagraphCharacterIndex;
445 mTextUpdateInfo.mRequestedNumberOfCharacters = numberOfCharacters + mTextUpdateInfo.mNumberOfCharactersToAdd - mTextUpdateInfo.mNumberOfCharactersToRemove;
446 mTextUpdateInfo.mStartGlyphIndex = *( mVisualModel->mCharactersToGlyph.Begin() + mTextUpdateInfo.mParagraphCharacterIndex );
449 void Controller::Impl::ClearFullModelData( OperationsMask operations )
451 if( NO_OPERATION != ( GET_LINE_BREAKS & operations ) )
453 mLogicalModel->mLineBreakInfo.Clear();
454 mLogicalModel->mParagraphInfo.Clear();
457 if( NO_OPERATION != ( GET_WORD_BREAKS & operations ) )
459 mLogicalModel->mLineBreakInfo.Clear();
462 if( NO_OPERATION != ( GET_SCRIPTS & operations ) )
464 mLogicalModel->mScriptRuns.Clear();
467 if( NO_OPERATION != ( VALIDATE_FONTS & operations ) )
469 mLogicalModel->mFontRuns.Clear();
472 if( 0u != mLogicalModel->mBidirectionalParagraphInfo.Count() )
474 if( NO_OPERATION != ( BIDI_INFO & operations ) )
476 mLogicalModel->mBidirectionalParagraphInfo.Clear();
477 mLogicalModel->mCharacterDirections.Clear();
480 if( NO_OPERATION != ( REORDER & operations ) )
482 // Free the allocated memory used to store the conversion table in the bidirectional line info run.
483 for( Vector<BidirectionalLineInfoRun>::Iterator it = mLogicalModel->mBidirectionalLineInfo.Begin(),
484 endIt = mLogicalModel->mBidirectionalLineInfo.End();
488 BidirectionalLineInfoRun& bidiLineInfo = *it;
490 free( bidiLineInfo.visualToLogicalMap );
491 bidiLineInfo.visualToLogicalMap = NULL;
493 mLogicalModel->mBidirectionalLineInfo.Clear();
497 if( NO_OPERATION != ( SHAPE_TEXT & operations ) )
499 mVisualModel->mGlyphs.Clear();
500 mVisualModel->mGlyphsToCharacters.Clear();
501 mVisualModel->mCharactersToGlyph.Clear();
502 mVisualModel->mCharactersPerGlyph.Clear();
503 mVisualModel->mGlyphsPerCharacter.Clear();
504 mVisualModel->mGlyphPositions.Clear();
507 if( NO_OPERATION != ( LAYOUT & operations ) )
509 mVisualModel->mLines.Clear();
512 if( NO_OPERATION != ( COLOR & operations ) )
514 mVisualModel->mColorIndices.Clear();
518 void Controller::Impl::ClearCharacterModelData( CharacterIndex startIndex, CharacterIndex endIndex, OperationsMask operations )
520 const CharacterIndex endIndexPlusOne = endIndex + 1u;
522 if( NO_OPERATION != ( GET_LINE_BREAKS & operations ) )
524 // Clear the line break info.
525 LineBreakInfo* lineBreakInfoBuffer = mLogicalModel->mLineBreakInfo.Begin();
527 mLogicalModel->mLineBreakInfo.Erase( lineBreakInfoBuffer + startIndex,
528 lineBreakInfoBuffer + endIndexPlusOne );
530 // Clear the paragraphs.
531 ClearCharacterRuns( startIndex,
533 mLogicalModel->mParagraphInfo );
536 if( NO_OPERATION != ( GET_WORD_BREAKS & operations ) )
538 // Clear the word break info.
539 WordBreakInfo* wordBreakInfoBuffer = mLogicalModel->mWordBreakInfo.Begin();
541 mLogicalModel->mWordBreakInfo.Erase( wordBreakInfoBuffer + startIndex,
542 wordBreakInfoBuffer + endIndexPlusOne );
545 if( NO_OPERATION != ( GET_SCRIPTS & operations ) )
547 // Clear the scripts.
548 ClearCharacterRuns( startIndex,
550 mLogicalModel->mScriptRuns );
553 if( NO_OPERATION != ( VALIDATE_FONTS & operations ) )
556 ClearCharacterRuns( startIndex,
558 mLogicalModel->mFontRuns );
561 if( 0u != mLogicalModel->mBidirectionalParagraphInfo.Count() )
563 if( NO_OPERATION != ( BIDI_INFO & operations ) )
565 // Clear the bidirectional paragraph info.
566 ClearCharacterRuns( startIndex,
568 mLogicalModel->mBidirectionalParagraphInfo );
570 // Clear the character's directions.
571 CharacterDirection* characterDirectionsBuffer = mLogicalModel->mCharacterDirections.Begin();
573 mLogicalModel->mCharacterDirections.Erase( characterDirectionsBuffer + startIndex,
574 characterDirectionsBuffer + endIndexPlusOne );
577 if( NO_OPERATION != ( REORDER & operations ) )
579 uint32_t startRemoveIndex = mLogicalModel->mBidirectionalLineInfo.Count();
580 uint32_t endRemoveIndex = startRemoveIndex;
581 ClearCharacterRuns( startIndex,
583 mLogicalModel->mBidirectionalLineInfo,
587 BidirectionalLineInfoRun* bidirectionalLineInfoBuffer = mLogicalModel->mBidirectionalLineInfo.Begin();
589 // Free the allocated memory used to store the conversion table in the bidirectional line info run.
590 for( Vector<BidirectionalLineInfoRun>::Iterator it = bidirectionalLineInfoBuffer + startRemoveIndex,
591 endIt = bidirectionalLineInfoBuffer + endRemoveIndex;
595 BidirectionalLineInfoRun& bidiLineInfo = *it;
597 free( bidiLineInfo.visualToLogicalMap );
598 bidiLineInfo.visualToLogicalMap = NULL;
601 mLogicalModel->mBidirectionalLineInfo.Erase( bidirectionalLineInfoBuffer + startRemoveIndex,
602 bidirectionalLineInfoBuffer + endRemoveIndex );
607 void Controller::Impl::ClearGlyphModelData( CharacterIndex startIndex, CharacterIndex endIndex, OperationsMask operations )
609 const CharacterIndex endIndexPlusOne = endIndex + 1u;
610 const Length numberOfCharactersRemoved = endIndexPlusOne - startIndex;
612 // Convert the character index to glyph index before deleting the character to glyph and the glyphs per character buffers.
613 GlyphIndex* charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
614 Length* glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
616 const GlyphIndex endGlyphIndexPlusOne = *( charactersToGlyphBuffer + endIndex ) + *( glyphsPerCharacterBuffer + endIndex );
617 const Length numberOfGlyphsRemoved = endGlyphIndexPlusOne - mTextUpdateInfo.mStartGlyphIndex;
619 if( NO_OPERATION != ( SHAPE_TEXT & operations ) )
621 // Update the character to glyph indices.
622 for( Vector<GlyphIndex>::Iterator it = charactersToGlyphBuffer + endIndexPlusOne,
623 endIt = charactersToGlyphBuffer + mVisualModel->mCharactersToGlyph.Count();
627 CharacterIndex& index = *it;
628 index -= numberOfGlyphsRemoved;
631 // Clear the character to glyph conversion table.
632 mVisualModel->mCharactersToGlyph.Erase( charactersToGlyphBuffer + startIndex,
633 charactersToGlyphBuffer + endIndexPlusOne );
635 // Clear the glyphs per character table.
636 mVisualModel->mGlyphsPerCharacter.Erase( glyphsPerCharacterBuffer + startIndex,
637 glyphsPerCharacterBuffer + endIndexPlusOne );
639 // Clear the glyphs buffer.
640 GlyphInfo* glyphsBuffer = mVisualModel->mGlyphs.Begin();
641 mVisualModel->mGlyphs.Erase( glyphsBuffer + mTextUpdateInfo.mStartGlyphIndex,
642 glyphsBuffer + endGlyphIndexPlusOne );
644 CharacterIndex* glyphsToCharactersBuffer = mVisualModel->mGlyphsToCharacters.Begin();
646 // Update the glyph to character indices.
647 for( Vector<CharacterIndex>::Iterator it = glyphsToCharactersBuffer + endGlyphIndexPlusOne,
648 endIt = glyphsToCharactersBuffer + mVisualModel->mGlyphsToCharacters.Count();
652 CharacterIndex& index = *it;
653 index -= numberOfCharactersRemoved;
656 // Clear the glyphs to characters buffer.
657 mVisualModel->mGlyphsToCharacters.Erase( glyphsToCharactersBuffer + mTextUpdateInfo.mStartGlyphIndex,
658 glyphsToCharactersBuffer + endGlyphIndexPlusOne );
660 // Clear the characters per glyph buffer.
661 Length* charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
662 mVisualModel->mCharactersPerGlyph.Erase( charactersPerGlyphBuffer + mTextUpdateInfo.mStartGlyphIndex,
663 charactersPerGlyphBuffer + endGlyphIndexPlusOne );
665 // Clear the positions buffer.
666 Vector2* positionsBuffer = mVisualModel->mGlyphPositions.Begin();
667 mVisualModel->mGlyphPositions.Erase( positionsBuffer + mTextUpdateInfo.mStartGlyphIndex,
668 positionsBuffer + endGlyphIndexPlusOne );
671 if( NO_OPERATION != ( LAYOUT & operations ) )
674 uint32_t startRemoveIndex = mVisualModel->mLines.Count();
675 uint32_t endRemoveIndex = startRemoveIndex;
676 ClearCharacterRuns( startIndex,
678 mVisualModel->mLines,
682 // Will update the glyph runs.
683 startRemoveIndex = mVisualModel->mLines.Count();
684 endRemoveIndex = startRemoveIndex;
685 ClearGlyphRuns( mTextUpdateInfo.mStartGlyphIndex,
686 endGlyphIndexPlusOne - 1u,
687 mVisualModel->mLines,
691 // Set the line index from where to insert the new laid-out lines.
692 mTextUpdateInfo.mStartLineIndex = startRemoveIndex;
694 LineRun* linesBuffer = mVisualModel->mLines.Begin();
695 mVisualModel->mLines.Erase( linesBuffer + startRemoveIndex,
696 linesBuffer + endRemoveIndex );
699 if( NO_OPERATION != ( COLOR & operations ) )
701 if( 0u != mVisualModel->mColorIndices.Count() )
703 ColorIndex* colorIndexBuffer = mVisualModel->mColorIndices.Begin();
704 mVisualModel->mColorIndices.Erase( colorIndexBuffer + mTextUpdateInfo.mStartGlyphIndex,
705 colorIndexBuffer + endGlyphIndexPlusOne );
710 void Controller::Impl::ClearModelData( CharacterIndex startIndex, CharacterIndex endIndex, OperationsMask operations )
712 if( mTextUpdateInfo.mClearAll ||
713 ( ( 0u == startIndex ) &&
714 ( mTextUpdateInfo.mPreviousNumberOfCharacters == endIndex + 1u ) ) )
716 ClearFullModelData( operations );
720 // Clear the model data related with characters.
721 ClearCharacterModelData( startIndex, endIndex, operations );
723 // Clear the model data related with glyphs.
724 ClearGlyphModelData( startIndex, endIndex, operations );
727 // The estimated number of lines. Used to avoid reallocations when layouting.
728 mTextUpdateInfo.mEstimatedNumberOfLines = std::max( mVisualModel->mLines.Count(), mLogicalModel->mParagraphInfo.Count() );
730 mVisualModel->ClearCaches();
733 bool Controller::Impl::UpdateModel( OperationsMask operationsRequired )
735 DALI_LOG_INFO( gLogFilter, Debug::General, "Controller::UpdateModel\n" );
737 // Calculate the operations to be done.
738 const OperationsMask operations = static_cast<OperationsMask>( mOperationsPending & operationsRequired );
740 if( NO_OPERATION == operations )
742 // Nothing to do if no operations are pending and required.
746 Vector<Character>& utf32Characters = mLogicalModel->mText;
748 const Length numberOfCharacters = utf32Characters.Count();
750 // Index to the first character of the first paragraph to be updated.
751 CharacterIndex startIndex = 0u;
752 // Number of characters of the paragraphs to be removed.
753 Length paragraphCharacters = 0u;
755 CalculateTextUpdateIndices( paragraphCharacters );
756 startIndex = mTextUpdateInfo.mParagraphCharacterIndex;
758 if( mTextUpdateInfo.mClearAll ||
759 ( 0u != paragraphCharacters ) )
761 ClearModelData( startIndex, startIndex + ( ( paragraphCharacters > 0u ) ? paragraphCharacters - 1u : 0u ), operations );
764 mTextUpdateInfo.mClearAll = false;
766 // Whether the model is updated.
767 bool updated = false;
769 Vector<LineBreakInfo>& lineBreakInfo = mLogicalModel->mLineBreakInfo;
770 const Length requestedNumberOfCharacters = mTextUpdateInfo.mRequestedNumberOfCharacters;
772 if( NO_OPERATION != ( GET_LINE_BREAKS & operations ) )
774 // Retrieves the line break info. The line break info is used to split the text in 'paragraphs' to
775 // calculate the bidirectional info for each 'paragraph'.
776 // It's also used to layout the text (where it should be a new line) or to shape the text (text in different lines
777 // is not shaped together).
778 lineBreakInfo.Resize( numberOfCharacters, TextAbstraction::LINE_NO_BREAK );
780 SetLineBreakInfo( utf32Characters,
782 requestedNumberOfCharacters,
785 // Create the paragraph info.
786 mLogicalModel->CreateParagraphInfo( startIndex,
787 requestedNumberOfCharacters );
791 Vector<WordBreakInfo>& wordBreakInfo = mLogicalModel->mWordBreakInfo;
792 if( NO_OPERATION != ( GET_WORD_BREAKS & operations ) )
794 // Retrieves the word break info. The word break info is used to layout the text (where to wrap the text in lines).
795 wordBreakInfo.Resize( numberOfCharacters, TextAbstraction::WORD_NO_BREAK );
797 SetWordBreakInfo( utf32Characters,
799 requestedNumberOfCharacters,
804 const bool getScripts = NO_OPERATION != ( GET_SCRIPTS & operations );
805 const bool validateFonts = NO_OPERATION != ( VALIDATE_FONTS & operations );
807 Vector<ScriptRun>& scripts = mLogicalModel->mScriptRuns;
808 Vector<FontRun>& validFonts = mLogicalModel->mFontRuns;
810 if( getScripts || validateFonts )
812 // Validates the fonts assigned by the application or assigns default ones.
813 // It makes sure all the characters are going to be rendered by the correct font.
814 MultilanguageSupport multilanguageSupport = MultilanguageSupport::Get();
818 // Retrieves the scripts used in the text.
819 multilanguageSupport.SetScripts( utf32Characters,
821 requestedNumberOfCharacters,
827 // Validate the fonts set through the mark-up string.
828 Vector<FontDescriptionRun>& fontDescriptionRuns = mLogicalModel->mFontDescriptionRuns;
830 // Get the default font id.
831 const FontId defaultFontId = ( NULL == mFontDefaults ) ? 0u : mFontDefaults->GetFontId( mFontClient );
833 // Validates the fonts. If there is a character with no assigned font it sets a default one.
834 // After this call, fonts are validated.
835 multilanguageSupport.ValidateFonts( utf32Characters,
840 requestedNumberOfCharacters,
846 Vector<Character> mirroredUtf32Characters;
847 bool textMirrored = false;
848 const Length numberOfParagraphs = mLogicalModel->mParagraphInfo.Count();
849 if( NO_OPERATION != ( BIDI_INFO & operations ) )
851 Vector<BidirectionalParagraphInfoRun>& bidirectionalInfo = mLogicalModel->mBidirectionalParagraphInfo;
852 bidirectionalInfo.Reserve( numberOfParagraphs );
854 // Calculates the bidirectional info for the whole paragraph if it contains right to left scripts.
855 SetBidirectionalInfo( utf32Characters,
859 requestedNumberOfCharacters,
862 if( 0u != bidirectionalInfo.Count() )
864 // Only set the character directions if there is right to left characters.
865 Vector<CharacterDirection>& directions = mLogicalModel->mCharacterDirections;
866 GetCharactersDirection( bidirectionalInfo,
869 requestedNumberOfCharacters,
872 // This paragraph has right to left text. Some characters may need to be mirrored.
873 // TODO: consider if the mirrored string can be stored as well.
875 textMirrored = GetMirroredText( utf32Characters,
879 requestedNumberOfCharacters,
880 mirroredUtf32Characters );
884 // There is no right to left characters. Clear the directions vector.
885 mLogicalModel->mCharacterDirections.Clear();
890 Vector<GlyphInfo>& glyphs = mVisualModel->mGlyphs;
891 Vector<CharacterIndex>& glyphsToCharactersMap = mVisualModel->mGlyphsToCharacters;
892 Vector<Length>& charactersPerGlyph = mVisualModel->mCharactersPerGlyph;
893 Vector<GlyphIndex> newParagraphGlyphs;
894 newParagraphGlyphs.Reserve( numberOfParagraphs );
896 const Length currentNumberOfGlyphs = glyphs.Count();
897 if( NO_OPERATION != ( SHAPE_TEXT & operations ) )
899 const Vector<Character>& textToShape = textMirrored ? mirroredUtf32Characters : utf32Characters;
901 ShapeText( textToShape,
906 mTextUpdateInfo.mStartGlyphIndex,
907 requestedNumberOfCharacters,
909 glyphsToCharactersMap,
911 newParagraphGlyphs );
913 // Create the 'number of glyphs' per character and the glyph to character conversion tables.
914 mVisualModel->CreateGlyphsPerCharacterTable( startIndex, mTextUpdateInfo.mStartGlyphIndex, requestedNumberOfCharacters );
915 mVisualModel->CreateCharacterToGlyphTable( startIndex, mTextUpdateInfo.mStartGlyphIndex, requestedNumberOfCharacters );
919 const Length numberOfGlyphs = glyphs.Count() - currentNumberOfGlyphs;
921 if( NO_OPERATION != ( GET_GLYPH_METRICS & operations ) )
923 GlyphInfo* glyphsBuffer = glyphs.Begin();
924 mMetrics->GetGlyphMetrics( glyphsBuffer + mTextUpdateInfo.mStartGlyphIndex, numberOfGlyphs );
926 // Update the width and advance of all new paragraph characters.
927 for( Vector<GlyphIndex>::ConstIterator it = newParagraphGlyphs.Begin(), endIt = newParagraphGlyphs.End(); it != endIt; ++it )
929 const GlyphIndex index = *it;
930 GlyphInfo& glyph = *( glyphsBuffer + index );
932 glyph.xBearing = 0.f;
939 if( NO_OPERATION != ( COLOR & operations ) )
941 // Set the color runs in glyphs.
942 SetColorSegmentationInfo( mLogicalModel->mColorRuns,
943 mVisualModel->mCharactersToGlyph,
944 mVisualModel->mGlyphsPerCharacter,
946 mTextUpdateInfo.mStartGlyphIndex,
947 requestedNumberOfCharacters,
948 mVisualModel->mColors,
949 mVisualModel->mColorIndices );
954 if( ( NULL != mEventData ) &&
955 mEventData->mPreEditFlag &&
956 ( 0u != mVisualModel->mCharactersToGlyph.Count() ) )
958 // Add the underline for the pre-edit text.
959 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
960 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
962 const GlyphIndex glyphStart = *( charactersToGlyphBuffer + mEventData->mPreEditStartPosition );
963 const CharacterIndex lastPreEditCharacter = mEventData->mPreEditStartPosition + ( ( mEventData->mPreEditLength > 0u ) ? mEventData->mPreEditLength - 1u : 0u );
964 const Length numberOfGlyphsLastCharacter = *( glyphsPerCharacterBuffer + lastPreEditCharacter );
965 const GlyphIndex glyphEnd = *( charactersToGlyphBuffer + lastPreEditCharacter ) + ( numberOfGlyphsLastCharacter > 1u ? numberOfGlyphsLastCharacter - 1u : 0u );
967 GlyphRun underlineRun;
968 underlineRun.glyphIndex = glyphStart;
969 underlineRun.numberOfGlyphs = 1u + glyphEnd - glyphStart;
971 // TODO: At the moment the underline runs are only for pre-edit.
972 mVisualModel->mUnderlineRuns.PushBack( underlineRun );
975 // The estimated number of lines. Used to avoid reallocations when layouting.
976 mTextUpdateInfo.mEstimatedNumberOfLines = std::max( mVisualModel->mLines.Count(), mLogicalModel->mParagraphInfo.Count() );
978 // Set the previous number of characters for the next time the text is updated.
979 mTextUpdateInfo.mPreviousNumberOfCharacters = numberOfCharacters;
984 void Controller::Impl::RetrieveDefaultInputStyle( InputStyle& inputStyle )
986 // Sets the default text's color.
987 inputStyle.textColor = mTextColor;
988 inputStyle.isDefaultColor = true;
990 inputStyle.familyName.clear();
991 inputStyle.weight = TextAbstraction::FontWeight::NORMAL;
992 inputStyle.width = TextAbstraction::FontWidth::NORMAL;
993 inputStyle.slant = TextAbstraction::FontSlant::NORMAL;
994 inputStyle.size = 0.f;
996 inputStyle.familyDefined = false;
997 inputStyle.weightDefined = false;
998 inputStyle.widthDefined = false;
999 inputStyle.slantDefined = false;
1000 inputStyle.sizeDefined = false;
1002 // Sets the default font's family name, weight, width, slant and size.
1005 if( mFontDefaults->familyDefined )
1007 inputStyle.familyName = mFontDefaults->mFontDescription.family;
1008 inputStyle.familyDefined = true;
1011 if( mFontDefaults->weightDefined )
1013 inputStyle.weight = mFontDefaults->mFontDescription.weight;
1014 inputStyle.weightDefined = true;
1017 if( mFontDefaults->widthDefined )
1019 inputStyle.width = mFontDefaults->mFontDescription.width;
1020 inputStyle.widthDefined = true;
1023 if( mFontDefaults->slantDefined )
1025 inputStyle.slant = mFontDefaults->mFontDescription.slant;
1026 inputStyle.slantDefined = true;
1029 if( mFontDefaults->sizeDefined )
1031 inputStyle.size = mFontDefaults->mDefaultPointSize;
1032 inputStyle.sizeDefined = true;
1037 float Controller::Impl::GetDefaultFontLineHeight()
1039 FontId defaultFontId = 0u;
1040 if( NULL == mFontDefaults )
1042 TextAbstraction::FontDescription fontDescription;
1043 defaultFontId = mFontClient.GetFontId( fontDescription );
1047 defaultFontId = mFontDefaults->GetFontId( mFontClient );
1050 Text::FontMetrics fontMetrics;
1051 mMetrics->GetFontMetrics( defaultFontId, fontMetrics );
1053 return( fontMetrics.ascender - fontMetrics.descender );
1056 void Controller::Impl::OnCursorKeyEvent( const Event& event )
1058 if( NULL == mEventData )
1060 // Nothing to do if there is no text input.
1064 int keyCode = event.p1.mInt;
1066 if( Dali::DALI_KEY_CURSOR_LEFT == keyCode )
1068 if( mEventData->mPrimaryCursorPosition > 0u )
1070 mEventData->mPrimaryCursorPosition = CalculateNewCursorIndex( mEventData->mPrimaryCursorPosition - 1u );
1073 else if( Dali::DALI_KEY_CURSOR_RIGHT == keyCode )
1075 if( mLogicalModel->mText.Count() > mEventData->mPrimaryCursorPosition )
1077 mEventData->mPrimaryCursorPosition = CalculateNewCursorIndex( mEventData->mPrimaryCursorPosition );
1080 else if( Dali::DALI_KEY_CURSOR_UP == keyCode )
1082 // Get first the line index of the current cursor position index.
1083 CharacterIndex characterIndex = 0u;
1085 if( mEventData->mPrimaryCursorPosition > 0u )
1087 characterIndex = mEventData->mPrimaryCursorPosition - 1u;
1090 const LineIndex lineIndex = mVisualModel->GetLineOfCharacter( characterIndex );
1092 if( lineIndex > 0u )
1094 // Retrieve the cursor position info.
1095 CursorInfo cursorInfo;
1096 GetCursorPosition( mEventData->mPrimaryCursorPosition,
1099 // Get the line above.
1100 const LineRun& line = *( mVisualModel->mLines.Begin() + ( lineIndex - 1u ) );
1102 // Get the next hit 'y' point.
1103 const float hitPointY = cursorInfo.lineOffset - 0.5f * ( line.ascender - line.descender );
1105 // Use the cursor hook position 'x' and the next hit 'y' position to calculate the new cursor index.
1106 mEventData->mPrimaryCursorPosition = Text::GetClosestCursorIndex( mVisualModel,
1109 mEventData->mCursorHookPositionX,
1113 else if( Dali::DALI_KEY_CURSOR_DOWN == keyCode )
1115 // Get first the line index of the current cursor position index.
1116 CharacterIndex characterIndex = 0u;
1118 if( mEventData->mPrimaryCursorPosition > 0u )
1120 characterIndex = mEventData->mPrimaryCursorPosition - 1u;
1123 const LineIndex lineIndex = mVisualModel->GetLineOfCharacter( characterIndex );
1125 if( lineIndex + 1u < mVisualModel->mLines.Count() )
1127 // Retrieve the cursor position info.
1128 CursorInfo cursorInfo;
1129 GetCursorPosition( mEventData->mPrimaryCursorPosition,
1132 // Get the line below.
1133 const LineRun& line = *( mVisualModel->mLines.Begin() + lineIndex + 1u );
1135 // Get the next hit 'y' point.
1136 const float hitPointY = cursorInfo.lineOffset + cursorInfo.lineHeight + 0.5f * ( line.ascender - line.descender );
1138 // Use the cursor hook position 'x' and the next hit 'y' position to calculate the new cursor index.
1139 mEventData->mPrimaryCursorPosition = Text::GetClosestCursorIndex( mVisualModel,
1142 mEventData->mCursorHookPositionX,
1147 mEventData->mUpdateCursorPosition = true;
1148 mEventData->mUpdateInputStyle = true;
1149 mEventData->mScrollAfterUpdatePosition = true;
1152 void Controller::Impl::OnTapEvent( const Event& event )
1154 if( NULL != mEventData )
1156 const unsigned int tapCount = event.p1.mUint;
1158 if( 1u == tapCount )
1160 if( IsShowingRealText() )
1162 // Convert from control's coords to text's coords.
1163 const float xPosition = event.p2.mFloat - mScrollPosition.x;
1164 const float yPosition = event.p3.mFloat - mScrollPosition.y;
1166 // Keep the tap 'x' position. Used to move the cursor.
1167 mEventData->mCursorHookPositionX = xPosition;
1169 mEventData->mPrimaryCursorPosition = Text::GetClosestCursorIndex( mVisualModel,
1175 // When the cursor position is changing, delay cursor blinking
1176 mEventData->mDecorator->DelayCursorBlink();
1180 mEventData->mPrimaryCursorPosition = 0u;
1183 mEventData->mUpdateCursorPosition = true;
1184 mEventData->mUpdateGrabHandlePosition = true;
1185 mEventData->mScrollAfterUpdatePosition = true;
1186 mEventData->mUpdateInputStyle = true;
1188 // Notify the cursor position to the imf manager.
1189 if( mEventData->mImfManager )
1191 mEventData->mImfManager.SetCursorPosition( mEventData->mPrimaryCursorPosition );
1192 mEventData->mImfManager.NotifyCursorPosition();
1198 void Controller::Impl::OnPanEvent( const Event& event )
1200 if( NULL == mEventData )
1202 // Nothing to do if there is no text input.
1206 int state = event.p1.mInt;
1208 if( ( Gesture::Started == state ) ||
1209 ( Gesture::Continuing == state ) )
1211 if( mEventData->mDecorator )
1213 const Vector2& layoutSize = mVisualModel->GetLayoutSize();
1214 const Vector2 currentScroll = mScrollPosition;
1216 if( mEventData->mDecorator->IsHorizontalScrollEnabled() )
1218 const float displacementX = event.p2.mFloat;
1219 mScrollPosition.x += displacementX;
1221 ClampHorizontalScroll( layoutSize );
1224 if( mEventData->mDecorator->IsVerticalScrollEnabled() )
1226 const float displacementY = event.p3.mFloat;
1227 mScrollPosition.y += displacementY;
1229 ClampVerticalScroll( layoutSize );
1232 mEventData->mDecorator->UpdatePositions( mScrollPosition - currentScroll );
1237 void Controller::Impl::OnLongPressEvent( const Event& event )
1239 DALI_LOG_INFO( gLogFilter, Debug::General, "Controller::OnLongPressEvent\n" );
1241 if( EventData::EDITING == mEventData->mState )
1243 ChangeState ( EventData::EDITING_WITH_POPUP );
1244 mEventData->mDecoratorUpdated = true;
1248 void Controller::Impl::OnHandleEvent( const Event& event )
1250 if( NULL == mEventData )
1252 // Nothing to do if there is no text input.
1256 const unsigned int state = event.p1.mUint;
1257 const bool handleStopScrolling = ( HANDLE_STOP_SCROLLING == state );
1258 const bool isSmoothHandlePanEnabled = mEventData->mDecorator->IsSmoothHandlePanEnabled();
1260 if( HANDLE_PRESSED == state )
1262 // Convert from decorator's coords to text's coords.
1263 const float xPosition = event.p2.mFloat - mScrollPosition.x;
1264 const float yPosition = event.p3.mFloat - mScrollPosition.y;
1266 // Need to calculate the handle's new position.
1267 const CharacterIndex handleNewPosition = Text::GetClosestCursorIndex( mVisualModel,
1273 if( Event::GRAB_HANDLE_EVENT == event.type )
1275 ChangeState ( EventData::GRAB_HANDLE_PANNING );
1277 if( handleNewPosition != mEventData->mPrimaryCursorPosition )
1279 // Updates the cursor position if the handle's new position is different than the current one.
1280 mEventData->mUpdateCursorPosition = true;
1281 // Does not update the grab handle position if the smooth panning is enabled. (The decorator does it smooth).
1282 mEventData->mUpdateGrabHandlePosition = !isSmoothHandlePanEnabled;
1283 mEventData->mPrimaryCursorPosition = handleNewPosition;
1286 // 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.
1287 mEventData->mDecoratorUpdated = isSmoothHandlePanEnabled;
1289 else if( Event::LEFT_SELECTION_HANDLE_EVENT == event.type )
1291 ChangeState ( EventData::SELECTION_HANDLE_PANNING );
1293 if( ( handleNewPosition != mEventData->mLeftSelectionPosition ) &&
1294 ( handleNewPosition != mEventData->mRightSelectionPosition ) )
1296 // Updates the highlight box if the handle's new position is different than the current one.
1297 mEventData->mUpdateHighlightBox = true;
1298 // Does not update the selection handle position if the smooth panning is enabled. (The decorator does it smooth).
1299 mEventData->mUpdateLeftSelectionPosition = !isSmoothHandlePanEnabled;
1300 mEventData->mLeftSelectionPosition = handleNewPosition;
1303 // 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.
1304 mEventData->mDecoratorUpdated = isSmoothHandlePanEnabled;
1306 else if( Event::RIGHT_SELECTION_HANDLE_EVENT == event.type )
1308 ChangeState ( EventData::SELECTION_HANDLE_PANNING );
1310 if( ( handleNewPosition != mEventData->mRightSelectionPosition ) &&
1311 ( handleNewPosition != mEventData->mLeftSelectionPosition ) )
1313 // Updates the highlight box if the handle's new position is different than the current one.
1314 mEventData->mUpdateHighlightBox = true;
1315 // Does not update the selection handle position if the smooth panning is enabled. (The decorator does it smooth).
1316 mEventData->mUpdateRightSelectionPosition = !isSmoothHandlePanEnabled;
1317 mEventData->mRightSelectionPosition = handleNewPosition;
1320 // 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.
1321 mEventData->mDecoratorUpdated = isSmoothHandlePanEnabled;
1323 } // end ( HANDLE_PRESSED == state )
1324 else if( ( HANDLE_RELEASED == state ) ||
1325 handleStopScrolling )
1327 CharacterIndex handlePosition = 0u;
1328 if( handleStopScrolling || isSmoothHandlePanEnabled )
1330 // Convert from decorator's coords to text's coords.
1331 const float xPosition = event.p2.mFloat - mScrollPosition.x;
1332 const float yPosition = event.p3.mFloat - mScrollPosition.y;
1334 handlePosition = Text::GetClosestCursorIndex( mVisualModel,
1341 if( Event::GRAB_HANDLE_EVENT == event.type )
1343 mEventData->mUpdateCursorPosition = true;
1344 mEventData->mUpdateGrabHandlePosition = true;
1345 mEventData->mUpdateInputStyle = true;
1347 if( !IsClipboardEmpty() )
1349 ChangeState( EventData::EDITING_WITH_PASTE_POPUP ); // Moving grabhandle will show Paste Popup
1352 if( handleStopScrolling || isSmoothHandlePanEnabled )
1354 mEventData->mScrollAfterUpdatePosition = true;
1355 mEventData->mPrimaryCursorPosition = handlePosition;
1358 else if( Event::LEFT_SELECTION_HANDLE_EVENT == event.type )
1360 ChangeState( EventData::SELECTING );
1362 mEventData->mUpdateHighlightBox = true;
1363 mEventData->mUpdateLeftSelectionPosition = true;
1365 if( handleStopScrolling || isSmoothHandlePanEnabled )
1367 mEventData->mScrollAfterUpdatePosition = true;
1369 if( ( handlePosition != mEventData->mRightSelectionPosition ) &&
1370 ( handlePosition != mEventData->mLeftSelectionPosition ) )
1372 mEventData->mLeftSelectionPosition = handlePosition;
1376 else if( Event::RIGHT_SELECTION_HANDLE_EVENT == event.type )
1378 ChangeState( EventData::SELECTING );
1380 mEventData->mUpdateHighlightBox = true;
1381 mEventData->mUpdateRightSelectionPosition = true;
1383 if( handleStopScrolling || isSmoothHandlePanEnabled )
1385 mEventData->mScrollAfterUpdatePosition = true;
1386 if( ( handlePosition != mEventData->mRightSelectionPosition ) &&
1387 ( handlePosition != mEventData->mLeftSelectionPosition ) )
1389 mEventData->mRightSelectionPosition = handlePosition;
1394 mEventData->mDecoratorUpdated = true;
1395 } // end ( ( HANDLE_RELEASED == state ) || ( HANDLE_STOP_SCROLLING == state ) )
1396 else if( HANDLE_SCROLLING == state )
1398 const float xSpeed = event.p2.mFloat;
1399 const float ySpeed = event.p3.mFloat;
1400 const Vector2& layoutSize = mVisualModel->GetLayoutSize();
1401 const Vector2 currentScrollPosition = mScrollPosition;
1403 mScrollPosition.x += xSpeed;
1404 mScrollPosition.y += ySpeed;
1406 ClampHorizontalScroll( layoutSize );
1407 ClampVerticalScroll( layoutSize );
1409 bool endOfScroll = false;
1410 if( Vector2::ZERO == ( currentScrollPosition - mScrollPosition ) )
1412 // Notify the decorator there is no more text to scroll.
1413 // The decorator won't send more scroll events.
1414 mEventData->mDecorator->NotifyEndOfScroll();
1415 // Still need to set the position of the handle.
1419 // Set the position of the handle.
1420 const bool scrollRightDirection = xSpeed > 0.f;
1421 const bool scrollBottomDirection = ySpeed > 0.f;
1422 const bool leftSelectionHandleEvent = Event::LEFT_SELECTION_HANDLE_EVENT == event.type;
1423 const bool rightSelectionHandleEvent = Event::RIGHT_SELECTION_HANDLE_EVENT == event.type;
1425 if( Event::GRAB_HANDLE_EVENT == event.type )
1427 ChangeState( EventData::GRAB_HANDLE_PANNING );
1429 // Get the grab handle position in decorator coords.
1430 Vector2 position = mEventData->mDecorator->GetPosition( GRAB_HANDLE );
1432 if( mEventData->mDecorator->IsHorizontalScrollEnabled() )
1434 // Position the grag handle close to either the left or right edge.
1435 position.x = scrollRightDirection ? 0.f : mVisualModel->mControlSize.width;
1438 if( mEventData->mDecorator->IsVerticalScrollEnabled() )
1440 position.x = mEventData->mCursorHookPositionX;
1442 // Position the grag handle close to either the top or bottom edge.
1443 position.y = scrollBottomDirection ? 0.f : mVisualModel->mControlSize.height;
1446 // Get the new handle position.
1447 // The grab handle's position is in decorator's coords. Need to transforms to text's coords.
1448 const CharacterIndex handlePosition = Text::GetClosestCursorIndex( mVisualModel,
1451 position.x - mScrollPosition.x,
1452 position.y - mScrollPosition.y );
1454 if( mEventData->mPrimaryCursorPosition != handlePosition )
1456 mEventData->mUpdateCursorPosition = true;
1457 mEventData->mUpdateGrabHandlePosition = !isSmoothHandlePanEnabled;
1458 mEventData->mScrollAfterUpdatePosition = true;
1459 mEventData->mPrimaryCursorPosition = handlePosition;
1461 mEventData->mUpdateInputStyle = mEventData->mUpdateCursorPosition;
1463 // Updates the decorator if the soft handle panning is enabled.
1464 mEventData->mDecoratorUpdated = isSmoothHandlePanEnabled;
1466 else if( leftSelectionHandleEvent || rightSelectionHandleEvent )
1468 ChangeState( EventData::SELECTION_HANDLE_PANNING );
1470 // Get the selection handle position in decorator coords.
1471 Vector2 position = mEventData->mDecorator->GetPosition( leftSelectionHandleEvent ? Text::LEFT_SELECTION_HANDLE : Text::RIGHT_SELECTION_HANDLE );
1473 if( mEventData->mDecorator->IsHorizontalScrollEnabled() )
1475 // Position the selection handle close to either the left or right edge.
1476 position.x = scrollRightDirection ? 0.f : mVisualModel->mControlSize.width;
1479 if( mEventData->mDecorator->IsVerticalScrollEnabled() )
1481 position.x = mEventData->mCursorHookPositionX;
1483 // Position the grag handle close to either the top or bottom edge.
1484 position.y = scrollBottomDirection ? 0.f : mVisualModel->mControlSize.height;
1487 // Get the new handle position.
1488 // The selection handle's position is in decorator's coords. Need to transform to text's coords.
1489 const CharacterIndex handlePosition = Text::GetClosestCursorIndex( mVisualModel,
1492 position.x - mScrollPosition.x,
1493 position.y - mScrollPosition.y );
1495 if( leftSelectionHandleEvent )
1497 const bool differentHandles = ( mEventData->mLeftSelectionPosition != handlePosition ) && ( mEventData->mRightSelectionPosition != handlePosition );
1499 if( differentHandles || endOfScroll )
1501 mEventData->mUpdateHighlightBox = true;
1502 mEventData->mUpdateLeftSelectionPosition = !isSmoothHandlePanEnabled;
1503 mEventData->mUpdateRightSelectionPosition = isSmoothHandlePanEnabled;
1504 mEventData->mLeftSelectionPosition = handlePosition;
1509 const bool differentHandles = ( mEventData->mRightSelectionPosition != handlePosition ) && ( mEventData->mLeftSelectionPosition != handlePosition );
1510 if( differentHandles || endOfScroll )
1512 mEventData->mUpdateHighlightBox = true;
1513 mEventData->mUpdateRightSelectionPosition = !isSmoothHandlePanEnabled;
1514 mEventData->mUpdateLeftSelectionPosition = isSmoothHandlePanEnabled;
1515 mEventData->mRightSelectionPosition = handlePosition;
1519 if( mEventData->mUpdateLeftSelectionPosition || mEventData->mUpdateRightSelectionPosition )
1521 RepositionSelectionHandles();
1523 mEventData->mScrollAfterUpdatePosition = !isSmoothHandlePanEnabled;
1526 mEventData->mDecoratorUpdated = true;
1527 } // end ( HANDLE_SCROLLING == state )
1530 void Controller::Impl::OnSelectEvent( const Event& event )
1532 if( NULL == mEventData )
1534 // Nothing to do if there is no text.
1538 if( mEventData->mSelectionEnabled )
1540 // Convert from control's coords to text's coords.
1541 const float xPosition = event.p2.mFloat - mScrollPosition.x;
1542 const float yPosition = event.p3.mFloat - mScrollPosition.y;
1544 // Calculates the logical position from the x,y coords.
1545 RepositionSelectionHandles( xPosition,
1550 void Controller::Impl::OnSelectAllEvent()
1552 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "OnSelectAllEvent mEventData->mSelectionEnabled%s \n", mEventData->mSelectionEnabled?"true":"false");
1554 if( NULL == mEventData )
1556 // Nothing to do if there is no text.
1560 if( mEventData->mSelectionEnabled )
1562 ChangeState( EventData::SELECTING );
1564 mEventData->mLeftSelectionPosition = 0u;
1565 mEventData->mRightSelectionPosition = mLogicalModel->mText.Count();
1567 mEventData->mScrollAfterUpdatePosition = true;
1568 mEventData->mUpdateLeftSelectionPosition = true;
1569 mEventData->mUpdateRightSelectionPosition = true;
1570 mEventData->mUpdateHighlightBox = true;
1574 void Controller::Impl::RetrieveSelection( std::string& selectedText, bool deleteAfterRetrieval )
1576 if( mEventData->mLeftSelectionPosition == mEventData->mRightSelectionPosition )
1578 // Nothing to select if handles are in the same place.
1579 selectedText.clear();
1583 const bool handlesCrossed = mEventData->mLeftSelectionPosition > mEventData->mRightSelectionPosition;
1585 //Get start and end position of selection
1586 const CharacterIndex startOfSelectedText = handlesCrossed ? mEventData->mRightSelectionPosition : mEventData->mLeftSelectionPosition;
1587 const Length lengthOfSelectedText = ( handlesCrossed ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition ) - startOfSelectedText;
1589 Vector<Character>& utf32Characters = mLogicalModel->mText;
1590 const Length numberOfCharacters = utf32Characters.Count();
1592 // Validate the start and end selection points
1593 if( ( startOfSelectedText + lengthOfSelectedText ) <= numberOfCharacters )
1595 //Get text as a UTF8 string
1596 Utf32ToUtf8( &utf32Characters[startOfSelectedText], lengthOfSelectedText, selectedText );
1598 if( deleteAfterRetrieval ) // Only delete text if copied successfully
1600 // Set as input style the style of the first deleted character.
1601 mLogicalModel->RetrieveStyle( startOfSelectedText, mEventData->mInputStyle );
1603 mLogicalModel->UpdateTextStyleRuns( startOfSelectedText, -static_cast<int>( lengthOfSelectedText ) );
1605 // Mark the paragraphs to be updated.
1606 mTextUpdateInfo.mCharacterIndex = startOfSelectedText;
1607 mTextUpdateInfo.mNumberOfCharactersToRemove = lengthOfSelectedText;
1609 // Delete text between handles
1610 Vector<Character>::Iterator first = utf32Characters.Begin() + startOfSelectedText;
1611 Vector<Character>::Iterator last = first + lengthOfSelectedText;
1612 utf32Characters.Erase( first, last );
1614 // Will show the cursor at the first character of the selection.
1615 mEventData->mPrimaryCursorPosition = handlesCrossed ? mEventData->mRightSelectionPosition : mEventData->mLeftSelectionPosition;
1619 // Will show the cursor at the last character of the selection.
1620 mEventData->mPrimaryCursorPosition = handlesCrossed ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition;
1623 mEventData->mDecoratorUpdated = true;
1627 void Controller::Impl::ShowClipboard()
1631 mClipboard.ShowClipboard();
1635 void Controller::Impl::HideClipboard()
1637 if( mClipboard && mClipboardHideEnabled )
1639 mClipboard.HideClipboard();
1643 void Controller::Impl::SetClipboardHideEnable(bool enable)
1645 mClipboardHideEnabled = enable;
1648 bool Controller::Impl::CopyStringToClipboard( std::string& source )
1650 //Send string to clipboard
1651 return ( mClipboard && mClipboard.SetItem( source ) );
1654 void Controller::Impl::SendSelectionToClipboard( bool deleteAfterSending )
1656 std::string selectedText;
1657 RetrieveSelection( selectedText, deleteAfterSending );
1658 CopyStringToClipboard( selectedText );
1659 ChangeState( EventData::EDITING );
1662 void Controller::Impl::GetTextFromClipboard( unsigned int itemIndex, std::string& retrievedString )
1666 retrievedString = mClipboard.GetItem( itemIndex );
1670 void Controller::Impl::RepositionSelectionHandles()
1672 CharacterIndex selectionStart = mEventData->mLeftSelectionPosition;
1673 CharacterIndex selectionEnd = mEventData->mRightSelectionPosition;
1675 if( selectionStart == selectionEnd )
1677 // Nothing to select if handles are in the same place.
1681 mEventData->mDecorator->ClearHighlights();
1683 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
1684 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
1685 const GlyphInfo* const glyphsBuffer = mVisualModel->mGlyphs.Begin();
1686 const Vector2* const positionsBuffer = mVisualModel->mGlyphPositions.Begin();
1687 const Length* const charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
1688 const CharacterIndex* const glyphToCharacterBuffer = mVisualModel->mGlyphsToCharacters.Begin();
1689 const CharacterDirection* const modelCharacterDirectionsBuffer = ( 0u != mLogicalModel->mCharacterDirections.Count() ) ? mLogicalModel->mCharacterDirections.Begin() : NULL;
1691 const bool isLastCharacter = selectionEnd >= mLogicalModel->mText.Count();
1692 const CharacterDirection startDirection = ( ( NULL == modelCharacterDirectionsBuffer ) ? false : *( modelCharacterDirectionsBuffer + selectionStart ) );
1693 const CharacterDirection endDirection = ( ( NULL == modelCharacterDirectionsBuffer ) ? false : *( modelCharacterDirectionsBuffer + ( selectionEnd - ( isLastCharacter ? 1u : 0u ) ) ) );
1695 // Swap the indices if the start is greater than the end.
1696 const bool indicesSwapped = selectionStart > selectionEnd;
1698 // Tell the decorator to flip the selection handles if needed.
1699 mEventData->mDecorator->SetSelectionHandleFlipState( indicesSwapped, startDirection, endDirection );
1701 if( indicesSwapped )
1703 std::swap( selectionStart, selectionEnd );
1706 // Get the indices to the first and last selected glyphs.
1707 const CharacterIndex selectionEndMinusOne = selectionEnd - 1u;
1708 const GlyphIndex glyphStart = *( charactersToGlyphBuffer + selectionStart );
1709 const Length numberOfGlyphs = *( glyphsPerCharacterBuffer + selectionEndMinusOne );
1710 const GlyphIndex glyphEnd = *( charactersToGlyphBuffer + selectionEndMinusOne ) + ( ( numberOfGlyphs > 0 ) ? numberOfGlyphs - 1u : 0u );
1712 // Get the lines where the glyphs are laid-out.
1713 const LineRun* lineRun = mVisualModel->mLines.Begin();
1715 LineIndex lineIndex = 0u;
1716 Length numberOfLines = 0u;
1717 mVisualModel->GetNumberOfLines( glyphStart,
1718 1u + glyphEnd - glyphStart,
1721 const LineIndex firstLineIndex = lineIndex;
1723 // Create the structure to store some selection box info.
1724 Vector<SelectionBoxInfo> selectionBoxLinesInfo;
1725 selectionBoxLinesInfo.Resize( numberOfLines );
1727 SelectionBoxInfo* selectionBoxInfo = selectionBoxLinesInfo.Begin();
1728 selectionBoxInfo->minX = MAX_FLOAT;
1729 selectionBoxInfo->maxX = MIN_FLOAT;
1731 // Keep the min and max 'x' position to calculate the size and position of the highlighed text.
1732 float minHighlightX = std::numeric_limits<float>::max();
1733 float maxHighlightX = std::numeric_limits<float>::min();
1735 Vector2 highLightPosition; // The highlight position in decorator's coords.
1737 // Retrieve the first line and get the line's vertical offset, the line's height and the index to the last glyph.
1739 // The line's vertical offset of all the lines before the line where the first glyph is laid-out.
1740 selectionBoxInfo->lineOffset = CalculateLineOffset( mVisualModel->mLines,
1743 // Transform to decorator's (control) coords.
1744 selectionBoxInfo->lineOffset += mScrollPosition.y;
1746 lineRun += firstLineIndex;
1748 // The line height is the addition of the line ascender and the line descender.
1749 // However, the line descender has a negative value, hence the subtraction.
1750 selectionBoxInfo->lineHeight = lineRun->ascender - lineRun->descender;
1752 GlyphIndex lastGlyphOfLine = lineRun->glyphRun.glyphIndex + lineRun->glyphRun.numberOfGlyphs - 1u;
1754 // Check if the first glyph is a ligature that must be broken like Latin ff, fi, or Arabic ﻻ, etc which needs special code.
1755 const Length numberOfCharactersStart = *( charactersPerGlyphBuffer + glyphStart );
1756 bool splitStartGlyph = ( numberOfCharactersStart > 1u ) && HasLigatureMustBreak( mLogicalModel->GetScript( selectionStart ) );
1758 // Check if the last glyph is a ligature that must be broken like Latin ff, fi, or Arabic ﻻ, etc which needs special code.
1759 const Length numberOfCharactersEnd = *( charactersPerGlyphBuffer + glyphEnd );
1760 bool splitEndGlyph = ( glyphStart != glyphEnd ) && ( numberOfCharactersEnd > 1u ) && HasLigatureMustBreak( mLogicalModel->GetScript( selectionEndMinusOne ) );
1762 // Traverse the glyphs.
1763 for( GlyphIndex index = glyphStart; index <= glyphEnd; ++index )
1765 const GlyphInfo& glyph = *( glyphsBuffer + index );
1766 const Vector2& position = *( positionsBuffer + index );
1768 if( splitStartGlyph )
1770 // 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.
1772 const float glyphAdvance = glyph.advance / static_cast<float>( numberOfCharactersStart );
1773 const CharacterIndex interGlyphIndex = selectionStart - *( glyphToCharacterBuffer + glyphStart );
1774 // Get the direction of the character.
1775 CharacterDirection isCurrentRightToLeft = false;
1776 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
1778 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + selectionStart );
1781 // The end point could be in the middle of the ligature.
1782 // Calculate the number of characters selected.
1783 const Length numberOfCharacters = ( glyphStart == glyphEnd ) ? ( selectionEnd - selectionStart ) : ( numberOfCharactersStart - interGlyphIndex );
1785 const float xPosition = lineRun->alignmentOffset + position.x - glyph.xBearing + mScrollPosition.x + glyphAdvance * static_cast<float>( isCurrentRightToLeft ? ( numberOfCharactersStart - interGlyphIndex - numberOfCharacters ) : interGlyphIndex );
1786 const float xPositionAdvance = xPosition + static_cast<float>( numberOfCharacters ) * glyphAdvance;
1787 const float yPosition = selectionBoxInfo->lineOffset;
1789 // Store the min and max 'x' for each line.
1790 selectionBoxInfo->minX = std::min( selectionBoxInfo->minX, xPosition );
1791 selectionBoxInfo->maxX = std::max( selectionBoxInfo->maxX, xPositionAdvance );
1793 mEventData->mDecorator->AddHighlight( xPosition,
1796 yPosition + selectionBoxInfo->lineHeight );
1798 splitStartGlyph = false;
1802 if( splitEndGlyph && ( index == glyphEnd ) )
1804 // 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.
1806 const float glyphAdvance = glyph.advance / static_cast<float>( numberOfCharactersEnd );
1807 const CharacterIndex interGlyphIndex = selectionEnd - *( glyphToCharacterBuffer + glyphEnd );
1808 // Get the direction of the character.
1809 CharacterDirection isCurrentRightToLeft = false;
1810 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
1812 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + selectionEnd );
1815 const Length numberOfCharacters = numberOfCharactersEnd - interGlyphIndex;
1817 const float xPosition = lineRun->alignmentOffset + position.x - glyph.xBearing + mScrollPosition.x + ( isCurrentRightToLeft ? ( glyphAdvance * static_cast<float>( numberOfCharacters ) ) : 0.f );
1818 const float xPositionAdvance = xPosition + static_cast<float>( interGlyphIndex ) * glyphAdvance;
1819 const float yPosition = selectionBoxInfo->lineOffset;
1821 // Store the min and max 'x' for each line.
1822 selectionBoxInfo->minX = std::min( selectionBoxInfo->minX, xPosition );
1823 selectionBoxInfo->maxX = std::max( selectionBoxInfo->maxX, xPositionAdvance );
1825 mEventData->mDecorator->AddHighlight( xPosition,
1828 yPosition + selectionBoxInfo->lineHeight );
1830 splitEndGlyph = false;
1834 const float xPosition = lineRun->alignmentOffset + position.x - glyph.xBearing + mScrollPosition.x;
1835 const float xPositionAdvance = xPosition + glyph.advance;
1836 const float yPosition = selectionBoxInfo->lineOffset;
1838 // Store the min and max 'x' for each line.
1839 selectionBoxInfo->minX = std::min( selectionBoxInfo->minX, xPosition );
1840 selectionBoxInfo->maxX = std::max( selectionBoxInfo->maxX, xPositionAdvance );
1842 mEventData->mDecorator->AddHighlight( xPosition,
1845 yPosition + selectionBoxInfo->lineHeight );
1847 // Whether to retrieve the next line.
1848 if( index == lastGlyphOfLine )
1850 // Retrieve the next line.
1853 // Get the last glyph of the new line.
1854 lastGlyphOfLine = lineRun->glyphRun.glyphIndex + lineRun->glyphRun.numberOfGlyphs - 1u;
1857 if( lineIndex < firstLineIndex + numberOfLines )
1859 // Keep the offset and height of the current selection box.
1860 const float currentLineOffset = selectionBoxInfo->lineOffset;
1861 const float currentLineHeight = selectionBoxInfo->lineHeight;
1863 // Get the selection box info for the next line.
1866 selectionBoxInfo->minX = MAX_FLOAT;
1867 selectionBoxInfo->maxX = MIN_FLOAT;
1869 // Update the line's vertical offset.
1870 selectionBoxInfo->lineOffset = currentLineOffset + currentLineHeight;
1872 // The line height is the addition of the line ascender and the line descender.
1873 // However, the line descender has a negative value, hence the subtraction.
1874 selectionBoxInfo->lineHeight = lineRun->ascender - lineRun->descender;
1879 // Traverses all the lines and updates the min and max 'x' positions and the total height.
1880 // The final width is calculated after 'boxifying' the selection.
1881 for( Vector<SelectionBoxInfo>::ConstIterator it = selectionBoxLinesInfo.Begin(),
1882 endIt = selectionBoxLinesInfo.End();
1886 const SelectionBoxInfo& info = *it;
1888 // Update the size of the highlighted text.
1889 highLightSize.height += selectionBoxInfo->lineHeight;
1890 minHighlightX = std::min( minHighlightX, info.minX );
1891 maxHighlightX = std::max( maxHighlightX, info.maxX );
1894 // Add extra geometry to 'boxify' the selection.
1896 if( 1u < numberOfLines )
1898 // Boxify the first line.
1899 lineRun = mVisualModel->mLines.Begin() + firstLineIndex;
1900 const SelectionBoxInfo& firstSelectionBoxLineInfo = *( selectionBoxLinesInfo.Begin() );
1902 bool boxifyBegin = ( LTR != lineRun->direction ) && ( LTR != startDirection );
1903 bool boxifyEnd = ( LTR == lineRun->direction ) && ( LTR == startDirection );
1907 // Boxify at the beginning of the line.
1908 mEventData->mDecorator->AddHighlight( 0.f,
1909 firstSelectionBoxLineInfo.lineOffset,
1910 firstSelectionBoxLineInfo.minX,
1911 firstSelectionBoxLineInfo.lineOffset + firstSelectionBoxLineInfo.lineHeight );
1913 // Update the size of the highlighted text.
1914 minHighlightX = 0.f;
1919 // Boxify at the end of the line.
1920 mEventData->mDecorator->AddHighlight( firstSelectionBoxLineInfo.maxX,
1921 firstSelectionBoxLineInfo.lineOffset,
1922 mVisualModel->mControlSize.width,
1923 firstSelectionBoxLineInfo.lineOffset + firstSelectionBoxLineInfo.lineHeight );
1925 // Update the size of the highlighted text.
1926 maxHighlightX = mVisualModel->mControlSize.width;
1929 // Boxify the central lines.
1930 if( 2u < numberOfLines )
1932 for( Vector<SelectionBoxInfo>::ConstIterator it = selectionBoxLinesInfo.Begin() + 1u,
1933 endIt = selectionBoxLinesInfo.End() - 1u;
1937 const SelectionBoxInfo& info = *it;
1939 mEventData->mDecorator->AddHighlight( 0.f,
1942 info.lineOffset + info.lineHeight );
1944 mEventData->mDecorator->AddHighlight( info.maxX,
1946 mVisualModel->mControlSize.width,
1947 info.lineOffset + info.lineHeight );
1950 // Update the size of the highlighted text.
1951 minHighlightX = 0.f;
1952 maxHighlightX = mVisualModel->mControlSize.width;
1955 // Boxify the last line.
1956 lineRun = mVisualModel->mLines.Begin() + firstLineIndex + numberOfLines - 1u;
1957 const SelectionBoxInfo& lastSelectionBoxLineInfo = *( selectionBoxLinesInfo.End() - 1u );
1959 boxifyBegin = ( LTR == lineRun->direction ) && ( LTR == endDirection );
1960 boxifyEnd = ( LTR != lineRun->direction ) && ( LTR != endDirection );
1964 // Boxify at the beginning of the line.
1965 mEventData->mDecorator->AddHighlight( 0.f,
1966 lastSelectionBoxLineInfo.lineOffset,
1967 lastSelectionBoxLineInfo.minX,
1968 lastSelectionBoxLineInfo.lineOffset + lastSelectionBoxLineInfo.lineHeight );
1970 // Update the size of the highlighted text.
1971 minHighlightX = 0.f;
1976 // Boxify at the end of the line.
1977 mEventData->mDecorator->AddHighlight( lastSelectionBoxLineInfo.maxX,
1978 lastSelectionBoxLineInfo.lineOffset,
1979 mVisualModel->mControlSize.width,
1980 lastSelectionBoxLineInfo.lineOffset + lastSelectionBoxLineInfo.lineHeight );
1982 // Update the size of the highlighted text.
1983 maxHighlightX = mVisualModel->mControlSize.width;
1987 // Sets the highlight's size and position. In decorator's coords.
1988 // The highlight's height has been calculated above (before 'boxifying' the highlight).
1989 highLightSize.width = maxHighlightX - minHighlightX;
1991 highLightPosition.x = minHighlightX;
1992 const SelectionBoxInfo& firstSelectionBoxLineInfo = *( selectionBoxLinesInfo.Begin() );
1993 highLightPosition.y = firstSelectionBoxLineInfo.lineOffset;
1995 mEventData->mDecorator->SetHighLightBox( highLightPosition, highLightSize );
1997 if( !mEventData->mDecorator->IsSmoothHandlePanEnabled() )
1999 CursorInfo primaryCursorInfo;
2000 GetCursorPosition( mEventData->mLeftSelectionPosition,
2001 primaryCursorInfo );
2003 const Vector2 primaryPosition = primaryCursorInfo.primaryPosition + mScrollPosition;
2005 mEventData->mDecorator->SetPosition( LEFT_SELECTION_HANDLE,
2007 primaryCursorInfo.lineOffset + mScrollPosition.y,
2008 primaryCursorInfo.lineHeight );
2010 CursorInfo secondaryCursorInfo;
2011 GetCursorPosition( mEventData->mRightSelectionPosition,
2012 secondaryCursorInfo );
2014 const Vector2 secondaryPosition = secondaryCursorInfo.primaryPosition + mScrollPosition;
2016 mEventData->mDecorator->SetPosition( RIGHT_SELECTION_HANDLE,
2017 secondaryPosition.x,
2018 secondaryCursorInfo.lineOffset + mScrollPosition.y,
2019 secondaryCursorInfo.lineHeight );
2022 // Cursor to be positioned at end of selection so if selection interrupted and edit mode restarted the cursor will be at end of selection
2023 mEventData->mPrimaryCursorPosition = ( indicesSwapped ) ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition;
2025 // Set the flag to update the decorator.
2026 mEventData->mDecoratorUpdated = true;
2029 void Controller::Impl::RepositionSelectionHandles( float visualX, float visualY )
2031 if( NULL == mEventData )
2033 // Nothing to do if there is no text input.
2037 if( IsShowingPlaceholderText() )
2039 // Nothing to do if there is the place-holder text.
2043 const Length numberOfGlyphs = mVisualModel->mGlyphs.Count();
2044 const Length numberOfLines = mVisualModel->mLines.Count();
2045 if( ( 0 == numberOfGlyphs ) ||
2046 ( 0 == numberOfLines ) )
2048 // Nothing to do if there is no text.
2052 // Find which word was selected
2053 CharacterIndex selectionStart( 0 );
2054 CharacterIndex selectionEnd( 0 );
2055 const bool indicesFound = FindSelectionIndices( mVisualModel,
2062 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "%p selectionStart %d selectionEnd %d\n", this, selectionStart, selectionEnd );
2066 ChangeState( EventData::SELECTING );
2068 mEventData->mLeftSelectionPosition = selectionStart;
2069 mEventData->mRightSelectionPosition = selectionEnd;
2071 mEventData->mUpdateLeftSelectionPosition = true;
2072 mEventData->mUpdateRightSelectionPosition = true;
2073 mEventData->mUpdateHighlightBox = true;
2075 mEventData->mScrollAfterUpdatePosition = ( mEventData->mLeftSelectionPosition != mEventData->mRightSelectionPosition );
2079 // Nothing to select. i.e. a white space, out of bounds
2080 ChangeState( EventData::EDITING );
2082 mEventData->mPrimaryCursorPosition = selectionEnd;
2084 mEventData->mUpdateCursorPosition = true;
2085 mEventData->mUpdateGrabHandlePosition = true;
2086 mEventData->mScrollAfterUpdatePosition = true;
2087 mEventData->mUpdateInputStyle = true;
2091 void Controller::Impl::SetPopupButtons()
2094 * Sets the Popup buttons to be shown depending on State.
2096 * If SELECTING : CUT & COPY + ( PASTE & CLIPBOARD if content available to paste )
2098 * If EDITING_WITH_POPUP : SELECT & SELECT_ALL
2101 TextSelectionPopup::Buttons buttonsToShow = TextSelectionPopup::NONE;
2103 if( EventData::SELECTING == mEventData->mState )
2105 buttonsToShow = TextSelectionPopup::Buttons( TextSelectionPopup::CUT | TextSelectionPopup::COPY );
2107 if( !IsClipboardEmpty() )
2109 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
2110 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::CLIPBOARD ) );
2113 if( !mEventData->mAllTextSelected )
2115 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::SELECT_ALL ) );
2118 else if( EventData::EDITING_WITH_POPUP == mEventData->mState )
2120 if( mLogicalModel->mText.Count() && !IsShowingPlaceholderText() )
2122 buttonsToShow = TextSelectionPopup::Buttons( TextSelectionPopup::SELECT | TextSelectionPopup::SELECT_ALL );
2125 if( !IsClipboardEmpty() )
2127 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
2128 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::CLIPBOARD ) );
2131 else if( EventData::EDITING_WITH_PASTE_POPUP == mEventData->mState )
2133 if ( !IsClipboardEmpty() )
2135 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
2136 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::CLIPBOARD ) );
2140 mEventData->mDecorator->SetEnabledPopupButtons( buttonsToShow );
2143 void Controller::Impl::ChangeState( EventData::State newState )
2145 if( NULL == mEventData )
2147 // Nothing to do if there is no text input.
2151 DALI_LOG_INFO( gLogFilter, Debug::General, "ChangeState state:%d newstate:%d\n", mEventData->mState, newState );
2153 if( mEventData->mState != newState )
2155 mEventData->mState = newState;
2157 if( EventData::INACTIVE == mEventData->mState )
2159 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
2160 mEventData->mDecorator->StopCursorBlink();
2161 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
2162 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
2163 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
2164 mEventData->mDecorator->SetPopupActive( false );
2165 mEventData->mDecoratorUpdated = true;
2168 else if( EventData::INTERRUPTED == mEventData->mState)
2170 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
2171 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
2172 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
2173 mEventData->mDecorator->SetPopupActive( false );
2174 mEventData->mDecoratorUpdated = true;
2177 else if( EventData::SELECTING == mEventData->mState )
2179 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
2180 mEventData->mDecorator->StopCursorBlink();
2181 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
2182 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true );
2183 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true );
2184 if( mEventData->mGrabHandlePopupEnabled )
2187 mEventData->mDecorator->SetPopupActive( true );
2189 mEventData->mDecoratorUpdated = true;
2191 else if( EventData::EDITING == mEventData->mState )
2193 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
2194 if( mEventData->mCursorBlinkEnabled )
2196 mEventData->mDecorator->StartCursorBlink();
2198 // Grab handle is not shown until a tap is received whilst EDITING
2199 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
2200 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
2201 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
2202 if( mEventData->mGrabHandlePopupEnabled )
2204 mEventData->mDecorator->SetPopupActive( false );
2206 mEventData->mDecoratorUpdated = true;
2209 else if( EventData::EDITING_WITH_POPUP == mEventData->mState )
2211 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "EDITING_WITH_POPUP \n", newState );
2213 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
2214 if( mEventData->mCursorBlinkEnabled )
2216 mEventData->mDecorator->StartCursorBlink();
2218 if( mEventData->mSelectionEnabled )
2220 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
2221 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
2225 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
2227 if( mEventData->mGrabHandlePopupEnabled )
2230 mEventData->mDecorator->SetPopupActive( true );
2233 mEventData->mDecoratorUpdated = true;
2235 else if( EventData::EDITING_WITH_GRAB_HANDLE == mEventData->mState )
2237 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "EDITING_WITH_GRAB_HANDLE \n", newState );
2239 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
2240 if( mEventData->mCursorBlinkEnabled )
2242 mEventData->mDecorator->StartCursorBlink();
2244 // Grab handle is not shown until a tap is received whilst EDITING
2245 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
2246 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
2247 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
2248 if( mEventData->mGrabHandlePopupEnabled )
2250 mEventData->mDecorator->SetPopupActive( false );
2252 mEventData->mDecoratorUpdated = true;
2255 else if( EventData::SELECTION_HANDLE_PANNING == mEventData->mState )
2257 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
2258 mEventData->mDecorator->StopCursorBlink();
2259 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
2260 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true );
2261 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true );
2262 if( mEventData->mGrabHandlePopupEnabled )
2264 mEventData->mDecorator->SetPopupActive( false );
2266 mEventData->mDecoratorUpdated = true;
2268 else if( EventData::GRAB_HANDLE_PANNING == mEventData->mState )
2270 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "GRAB_HANDLE_PANNING \n", newState );
2272 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
2273 if( mEventData->mCursorBlinkEnabled )
2275 mEventData->mDecorator->StartCursorBlink();
2277 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
2278 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
2279 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
2280 if( mEventData->mGrabHandlePopupEnabled )
2282 mEventData->mDecorator->SetPopupActive( false );
2284 mEventData->mDecoratorUpdated = true;
2286 else if( EventData::EDITING_WITH_PASTE_POPUP == mEventData->mState )
2288 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "EDITING_WITH_PASTE_POPUP \n", newState );
2290 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
2291 if( mEventData->mCursorBlinkEnabled )
2293 mEventData->mDecorator->StartCursorBlink();
2296 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
2297 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
2298 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
2300 if( mEventData->mGrabHandlePopupEnabled )
2303 mEventData->mDecorator->SetPopupActive( true );
2306 mEventData->mDecoratorUpdated = true;
2311 void Controller::Impl::GetCursorPosition( CharacterIndex logical,
2312 CursorInfo& cursorInfo )
2314 if( !IsShowingRealText() )
2316 // Do not want to use the place-holder text to set the cursor position.
2318 // Use the line's height of the font's family set to set the cursor's size.
2319 // If there is no font's family set, use the default font.
2320 // Use the current alignment to place the cursor at the beginning, center or end of the box.
2322 cursorInfo.lineOffset = 0.f;
2323 cursorInfo.lineHeight = GetDefaultFontLineHeight();
2324 cursorInfo.primaryCursorHeight = cursorInfo.lineHeight;
2326 switch( mLayoutEngine.GetHorizontalAlignment() )
2328 case LayoutEngine::HORIZONTAL_ALIGN_BEGIN:
2330 cursorInfo.primaryPosition.x = 0.f;
2333 case LayoutEngine::HORIZONTAL_ALIGN_CENTER:
2335 cursorInfo.primaryPosition.x = floorf( 0.5f * mVisualModel->mControlSize.width );
2338 case LayoutEngine::HORIZONTAL_ALIGN_END:
2340 cursorInfo.primaryPosition.x = mVisualModel->mControlSize.width - mEventData->mDecorator->GetCursorWidth();
2345 // Nothing else to do.
2349 Text::GetCursorPosition( mVisualModel,
2355 if( LayoutEngine::MULTI_LINE_BOX == mLayoutEngine.GetLayout() )
2357 // If the text is editable and multi-line, the cursor position after a white space shouldn't exceed the boundaries of the text control.
2359 // Note the white spaces laid-out at the end of the line might exceed the boundaries of the control.
2360 // The reason is a wrapped line must not start with a white space so they are laid-out at the end of the line.
2362 if( 0.f > cursorInfo.primaryPosition.x )
2364 cursorInfo.primaryPosition.x = 0.f;
2367 const float edgeWidth = mVisualModel->mControlSize.width - static_cast<float>( mEventData->mDecorator->GetCursorWidth() );
2368 if( cursorInfo.primaryPosition.x > edgeWidth )
2370 cursorInfo.primaryPosition.x = edgeWidth;
2375 CharacterIndex Controller::Impl::CalculateNewCursorIndex( CharacterIndex index ) const
2377 if( NULL == mEventData )
2379 // Nothing to do if there is no text input.
2383 CharacterIndex cursorIndex = mEventData->mPrimaryCursorPosition;
2385 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
2386 const Length* const charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
2388 GlyphIndex glyphIndex = *( charactersToGlyphBuffer + index );
2389 Length numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
2391 if( numberOfCharacters > 1u )
2393 const Script script = mLogicalModel->GetScript( index );
2394 if( HasLigatureMustBreak( script ) )
2396 // Prevents to jump the whole Latin ligatures like fi, ff, or Arabic ﻻ, ...
2397 numberOfCharacters = 1u;
2402 while( 0u == numberOfCharacters )
2405 numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
2409 if( index < mEventData->mPrimaryCursorPosition )
2411 cursorIndex -= numberOfCharacters;
2415 cursorIndex += numberOfCharacters;
2418 // Will update the cursor hook position.
2419 mEventData->mUpdateCursorHookPosition = true;
2424 void Controller::Impl::UpdateCursorPosition( const CursorInfo& cursorInfo )
2426 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "-->Controller::UpdateCursorPosition %p\n", this );
2427 if( NULL == mEventData )
2429 // Nothing to do if there is no text input.
2430 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition no event data\n" );
2434 const Vector2 cursorPosition = cursorInfo.primaryPosition + mScrollPosition;
2436 // Sets the cursor position.
2437 mEventData->mDecorator->SetPosition( PRIMARY_CURSOR,
2440 cursorInfo.primaryCursorHeight,
2441 cursorInfo.lineHeight );
2442 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Primary cursor position: %f,%f\n", cursorPosition.x, cursorPosition.y );
2444 if( mEventData->mUpdateGrabHandlePosition )
2446 // Sets the grab handle position.
2447 mEventData->mDecorator->SetPosition( GRAB_HANDLE,
2449 cursorInfo.lineOffset + mScrollPosition.y,
2450 cursorInfo.lineHeight );
2453 if( cursorInfo.isSecondaryCursor )
2455 mEventData->mDecorator->SetPosition( SECONDARY_CURSOR,
2456 cursorInfo.secondaryPosition.x + mScrollPosition.x,
2457 cursorInfo.secondaryPosition.y + mScrollPosition.y,
2458 cursorInfo.secondaryCursorHeight,
2459 cursorInfo.lineHeight );
2460 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Secondary cursor position: %f,%f\n", cursorInfo.secondaryPosition.x + mScrollPosition.x, cursorInfo.secondaryPosition.y + mScrollPosition.y );
2463 // Set which cursors are active according the state.
2464 if( EventData::IsEditingState( mEventData->mState ) || ( EventData::GRAB_HANDLE_PANNING == mEventData->mState ) )
2466 if( cursorInfo.isSecondaryCursor )
2468 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_BOTH );
2472 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
2477 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
2480 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition\n" );
2483 void Controller::Impl::UpdateSelectionHandle( HandleType handleType,
2484 const CursorInfo& cursorInfo )
2486 if( ( LEFT_SELECTION_HANDLE != handleType ) &&
2487 ( RIGHT_SELECTION_HANDLE != handleType ) )
2492 const Vector2 cursorPosition = cursorInfo.primaryPosition + mScrollPosition;
2494 // Sets the handle's position.
2495 mEventData->mDecorator->SetPosition( handleType,
2497 cursorInfo.lineOffset + mScrollPosition.y,
2498 cursorInfo.lineHeight );
2500 // If selection handle at start of the text and other at end of the text then all text is selected.
2501 const CharacterIndex startOfSelection = std::min( mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition );
2502 const CharacterIndex endOfSelection = std::max ( mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition );
2503 mEventData->mAllTextSelected = ( startOfSelection == 0 ) && ( endOfSelection == mLogicalModel->mText.Count() );
2506 void Controller::Impl::ClampHorizontalScroll( const Vector2& layoutSize )
2508 // Clamp between -space & 0.
2510 if( layoutSize.width > mVisualModel->mControlSize.width )
2512 const float space = ( layoutSize.width - mVisualModel->mControlSize.width );
2513 mScrollPosition.x = ( mScrollPosition.x < -space ) ? -space : mScrollPosition.x;
2514 mScrollPosition.x = ( mScrollPosition.x > 0.f ) ? 0.f : mScrollPosition.x;
2516 mEventData->mDecoratorUpdated = true;
2520 mScrollPosition.x = 0.f;
2524 void Controller::Impl::ClampVerticalScroll( const Vector2& layoutSize )
2526 // Clamp between -space & 0.
2527 if( layoutSize.height > mVisualModel->mControlSize.height )
2529 const float space = ( layoutSize.height - mVisualModel->mControlSize.height );
2530 mScrollPosition.y = ( mScrollPosition.y < -space ) ? -space : mScrollPosition.y;
2531 mScrollPosition.y = ( mScrollPosition.y > 0.f ) ? 0.f : mScrollPosition.y;
2533 mEventData->mDecoratorUpdated = true;
2537 mScrollPosition.y = 0.f;
2541 void Controller::Impl::ScrollToMakePositionVisible( const Vector2& position, float lineHeight )
2543 const float cursorWidth = mEventData->mDecorator ? static_cast<float>( mEventData->mDecorator->GetCursorWidth() ) : 0.f;
2545 // position is in actor's coords.
2546 const float positionEndX = position.x + cursorWidth;
2547 const float positionEndY = position.y + lineHeight;
2549 // Transform the position to decorator coords.
2550 const float decoratorPositionBeginX = position.x + mScrollPosition.x;
2551 const float decoratorPositionEndX = positionEndX + mScrollPosition.x;
2553 const float decoratorPositionBeginY = position.y + mScrollPosition.y;
2554 const float decoratorPositionEndY = positionEndY + mScrollPosition.y;
2556 if( decoratorPositionBeginX < 0.f )
2558 mScrollPosition.x = -position.x;
2560 else if( decoratorPositionEndX > mVisualModel->mControlSize.width )
2562 mScrollPosition.x = mVisualModel->mControlSize.width - positionEndX;
2565 if( decoratorPositionBeginY < 0.f )
2567 mScrollPosition.y = -position.y;
2569 else if( decoratorPositionEndY > mVisualModel->mControlSize.height )
2571 mScrollPosition.y = mVisualModel->mControlSize.height - positionEndY;
2575 void Controller::Impl::ScrollTextToMatchCursor( const CursorInfo& cursorInfo )
2577 // Get the current cursor position in decorator coords.
2578 const Vector2& currentCursorPosition = mEventData->mDecorator->GetPosition( PRIMARY_CURSOR );
2580 // Calculate the offset to match the cursor position before the character was deleted.
2581 mScrollPosition.x = currentCursorPosition.x - cursorInfo.primaryPosition.x;
2582 mScrollPosition.y = currentCursorPosition.y - cursorInfo.lineOffset;
2584 ClampHorizontalScroll( mVisualModel->GetLayoutSize() );
2585 ClampVerticalScroll( mVisualModel->GetLayoutSize() );
2587 // Makes the new cursor position visible if needed.
2588 ScrollToMakePositionVisible( cursorInfo.primaryPosition, cursorInfo.lineHeight );
2591 void Controller::Impl::RequestRelayout()
2593 mControlInterface.RequestTextRelayout();
2598 } // namespace Toolkit