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>
26 #include <dali-toolkit/internal/text/bidirectional-support.h>
27 #include <dali-toolkit/internal/text/character-set-conversion.h>
28 #include <dali-toolkit/internal/text/color-segmentation.h>
29 #include <dali-toolkit/internal/text/glyph-metrics-helper.h>
30 #include <dali-toolkit/internal/text/multi-language-support.h>
31 #include <dali-toolkit/internal/text/segmentation.h>
32 #include <dali-toolkit/internal/text/shaper.h>
33 #include <dali-toolkit/internal/text/text-run-container.h>
38 #if defined(DEBUG_ENABLED)
39 Debug::Filter* gLogFilter = Debug::Filter::New(Debug::NoLogging, true, "LOG_TEXT_CONTROLS");
53 EventData::EventData( DecoratorPtr decorator )
54 : mDecorator( decorator ),
56 mPlaceholderTextActive(),
57 mPlaceholderTextInactive(),
58 mPlaceholderTextColor( 0.8f, 0.8f, 0.8f, 0.8f ),
62 mPrimaryCursorPosition( 0u ),
63 mLeftSelectionPosition( 0u ),
64 mRightSelectionPosition( 0u ),
65 mPreEditStartPosition( 0u ),
67 mIsShowingPlaceholderText( false ),
68 mPreEditFlag( false ),
69 mDecoratorUpdated( false ),
70 mCursorBlinkEnabled( true ),
71 mGrabHandleEnabled( true ),
72 mGrabHandlePopupEnabled( true ),
73 mSelectionEnabled( true ),
74 mHorizontalScrollingEnabled( true ),
75 mVerticalScrollingEnabled( false ),
76 mUpdateCursorPosition( false ),
77 mUpdateLeftSelectionPosition( false ),
78 mUpdateRightSelectionPosition( false ),
79 mScrollAfterUpdatePosition( false ),
80 mScrollAfterDelete( false ),
81 mAllTextSelected( false ),
82 mUpdateInputStyle( false )
84 mImfManager = ImfManager::Get();
87 EventData::~EventData()
90 bool Controller::Impl::ProcessInputEvents()
92 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "-->Controller::ProcessInputEvents\n" );
93 if( NULL == mEventData )
95 // Nothing to do if there is no text input.
96 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::ProcessInputEvents no event data\n" );
100 if( mEventData->mDecorator )
102 for( std::vector<Event>::iterator iter = mEventData->mEventQueue.begin();
103 iter != mEventData->mEventQueue.end();
108 case Event::CURSOR_KEY_EVENT:
110 OnCursorKeyEvent( *iter );
113 case Event::TAP_EVENT:
118 case Event::LONG_PRESS_EVENT:
120 OnLongPressEvent( *iter );
123 case Event::PAN_EVENT:
128 case Event::GRAB_HANDLE_EVENT:
129 case Event::LEFT_SELECTION_HANDLE_EVENT:
130 case Event::RIGHT_SELECTION_HANDLE_EVENT: // Fall through
132 OnHandleEvent( *iter );
137 OnSelectEvent( *iter );
140 case Event::SELECT_ALL:
149 // The cursor must also be repositioned after inserts into the model
150 if( mEventData->mUpdateCursorPosition )
152 // Updates the cursor position and scrolls the text to make it visible.
153 CursorInfo cursorInfo;
154 GetCursorPosition( mEventData->mPrimaryCursorPosition,
157 if( mEventData->mScrollAfterUpdatePosition )
159 ScrollToMakePositionVisible( cursorInfo.primaryPosition );
160 mEventData->mScrollAfterUpdatePosition = false;
162 else if( mEventData->mScrollAfterDelete )
164 ScrollTextToMatchCursor( cursorInfo );
165 mEventData->mScrollAfterDelete = false;
168 UpdateCursorPosition( cursorInfo );
170 mEventData->mDecoratorUpdated = true;
171 mEventData->mUpdateCursorPosition = false;
175 bool leftScroll = false;
176 bool rightScroll = false;
178 CursorInfo leftHandleInfo;
179 CursorInfo rightHandleInfo;
181 if( mEventData->mUpdateLeftSelectionPosition )
183 GetCursorPosition( mEventData->mLeftSelectionPosition,
186 if( mEventData->mScrollAfterUpdatePosition )
188 ScrollToMakePositionVisible( leftHandleInfo.primaryPosition );
193 if( mEventData->mUpdateRightSelectionPosition )
195 GetCursorPosition( mEventData->mRightSelectionPosition,
198 if( mEventData->mScrollAfterUpdatePosition )
200 ScrollToMakePositionVisible( rightHandleInfo.primaryPosition );
205 if( mEventData->mUpdateLeftSelectionPosition )
207 UpdateSelectionHandle( LEFT_SELECTION_HANDLE,
211 mEventData->mDecoratorUpdated = true;
214 if( mEventData->mUpdateRightSelectionPosition )
216 UpdateSelectionHandle( RIGHT_SELECTION_HANDLE,
220 mEventData->mDecoratorUpdated = true;
223 if( mEventData->mUpdateLeftSelectionPosition || mEventData->mUpdateRightSelectionPosition )
225 RepositionSelectionHandles();
227 mEventData->mUpdateLeftSelectionPosition = false;
228 mEventData->mUpdateRightSelectionPosition = false;
231 if( leftScroll || rightScroll )
233 mEventData->mScrollAfterUpdatePosition = false;
237 if( mEventData->mUpdateInputStyle )
239 // Set the default style first.
240 RetrieveDefaultInputStyle( mEventData->mInputStyle );
242 // Get the character index from the cursor index.
243 const CharacterIndex styleIndex = ( mEventData->mPrimaryCursorPosition > 0u ) ? mEventData->mPrimaryCursorPosition - 1u : 0u;
245 // Retrieve the style from the style runs stored in the logical model.
246 mLogicalModel->RetrieveStyle( styleIndex, mEventData->mInputStyle );
248 mEventData->mUpdateInputStyle = false;
251 mEventData->mEventQueue.clear();
253 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::ProcessInputEvents\n" );
255 const bool decoratorUpdated = mEventData->mDecoratorUpdated;
256 mEventData->mDecoratorUpdated = false;
258 return decoratorUpdated;
261 void Controller::Impl::CalculateTextUpdateIndices( Length& numberOfCharacters )
263 mTextUpdateInfo.mParagraphCharacterIndex = 0u;
264 mTextUpdateInfo.mStartGlyphIndex = 0u;
265 mTextUpdateInfo.mStartLineIndex = 0u;
266 numberOfCharacters = 0u;
268 const Length numberOfParagraphs = mLogicalModel->mParagraphInfo.Count();
269 if( 0u == numberOfParagraphs )
271 mTextUpdateInfo.mParagraphCharacterIndex = 0u;
272 numberOfCharacters = 0u;
274 mTextUpdateInfo.mRequestedNumberOfCharacters = mTextUpdateInfo.mNumberOfCharactersToAdd - mTextUpdateInfo.mNumberOfCharactersToRemove;
276 // Nothing else to do if there are no paragraphs.
280 // Find the paragraphs to be updated.
281 Vector<ParagraphRunIndex> paragraphsToBeUpdated;
282 if( mTextUpdateInfo.mCharacterIndex >= mTextUpdateInfo.mPreviousNumberOfCharacters )
284 // Text is being added at the end of the current text.
285 if( mTextUpdateInfo.mIsLastCharacterNewParagraph )
287 // Text is being added in a new paragraph after the last character of the text.
288 mTextUpdateInfo.mParagraphCharacterIndex = mTextUpdateInfo.mPreviousNumberOfCharacters;
289 numberOfCharacters = 0u;
290 mTextUpdateInfo.mRequestedNumberOfCharacters = mTextUpdateInfo.mNumberOfCharactersToAdd - mTextUpdateInfo.mNumberOfCharactersToRemove;
292 mTextUpdateInfo.mStartGlyphIndex = mVisualModel->mGlyphs.Count();
293 mTextUpdateInfo.mStartLineIndex = mVisualModel->mLines.Count() - 1u;
295 // Nothing else to do;
299 paragraphsToBeUpdated.PushBack( numberOfParagraphs - 1u );
303 CharacterIndex lastIndex = 0u;
304 if( mTextUpdateInfo.mFullRelayoutNeeded )
306 lastIndex = mTextUpdateInfo.mPreviousNumberOfCharacters;
310 lastIndex = ( mTextUpdateInfo.mNumberOfCharactersToRemove > 0u ) ? mTextUpdateInfo.mNumberOfCharactersToRemove : 1u;
312 mLogicalModel->FindParagraphs( mTextUpdateInfo.mCharacterIndex,
314 paragraphsToBeUpdated );
317 if( 0u != paragraphsToBeUpdated.Count() )
319 const ParagraphRunIndex firstParagraphIndex = *( paragraphsToBeUpdated.Begin() );
320 const ParagraphRun& firstParagraph = *( mLogicalModel->mParagraphInfo.Begin() + firstParagraphIndex );
321 mTextUpdateInfo.mParagraphCharacterIndex = firstParagraph.characterRun.characterIndex;
323 ParagraphRunIndex lastParagraphIndex = *( paragraphsToBeUpdated.End() - 1u );
324 const ParagraphRun& lastParagraph = *( mLogicalModel->mParagraphInfo.Begin() + lastParagraphIndex );
326 if( ( mTextUpdateInfo.mNumberOfCharactersToRemove > 0u ) && // Some character are removed.
327 ( lastParagraphIndex < numberOfParagraphs - 1u ) && // There is a next paragraph.
328 ( ( lastParagraph.characterRun.characterIndex + lastParagraph.characterRun.numberOfCharacters ) == // The last removed character is the new paragraph character.
329 ( mTextUpdateInfo.mCharacterIndex + mTextUpdateInfo.mNumberOfCharactersToRemove ) ) )
331 // The new paragraph character of the last updated paragraph has been removed so is going to be merged with the next one.
332 const ParagraphRun& lastParagraph = *( mLogicalModel->mParagraphInfo.Begin() + lastParagraphIndex + 1u );
334 numberOfCharacters = lastParagraph.characterRun.characterIndex + lastParagraph.characterRun.numberOfCharacters - mTextUpdateInfo.mParagraphCharacterIndex;
338 numberOfCharacters = lastParagraph.characterRun.characterIndex + lastParagraph.characterRun.numberOfCharacters - mTextUpdateInfo.mParagraphCharacterIndex;
341 mTextUpdateInfo.mRequestedNumberOfCharacters = numberOfCharacters + mTextUpdateInfo.mNumberOfCharactersToAdd - mTextUpdateInfo.mNumberOfCharactersToRemove;
342 mTextUpdateInfo.mStartGlyphIndex = *( mVisualModel->mCharactersToGlyph.Begin() + mTextUpdateInfo.mParagraphCharacterIndex );
345 void Controller::Impl::ClearFullModelData( OperationsMask operations )
347 if( GET_LINE_BREAKS & operations )
349 mLogicalModel->mLineBreakInfo.Clear();
350 mLogicalModel->mParagraphInfo.Clear();
353 if( GET_WORD_BREAKS & operations )
355 mLogicalModel->mLineBreakInfo.Clear();
358 if( GET_SCRIPTS & operations )
360 mLogicalModel->mScriptRuns.Clear();
363 if( VALIDATE_FONTS & operations )
365 mLogicalModel->mFontRuns.Clear();
368 if( 0u != mLogicalModel->mBidirectionalParagraphInfo.Count() )
370 if( BIDI_INFO & operations )
372 mLogicalModel->mBidirectionalParagraphInfo.Clear();
373 mLogicalModel->mCharacterDirections.Clear();
376 if( REORDER & operations )
378 // Free the allocated memory used to store the conversion table in the bidirectional line info run.
379 for( Vector<BidirectionalLineInfoRun>::Iterator it = mLogicalModel->mBidirectionalLineInfo.Begin(),
380 endIt = mLogicalModel->mBidirectionalLineInfo.End();
384 BidirectionalLineInfoRun& bidiLineInfo = *it;
386 free( bidiLineInfo.visualToLogicalMap );
387 bidiLineInfo.visualToLogicalMap = NULL;
389 mLogicalModel->mBidirectionalLineInfo.Clear();
391 mLogicalModel->mLogicalToVisualMap.Clear();
392 mLogicalModel->mVisualToLogicalMap.Clear();
396 if( SHAPE_TEXT & operations )
398 mVisualModel->mGlyphs.Clear();
399 mVisualModel->mGlyphsToCharacters.Clear();
400 mVisualModel->mCharactersToGlyph.Clear();
401 mVisualModel->mCharactersPerGlyph.Clear();
402 mVisualModel->mGlyphsPerCharacter.Clear();
403 mVisualModel->mGlyphPositions.Clear();
406 if( LAYOUT & operations )
408 mVisualModel->mLines.Clear();
412 void Controller::Impl::ClearCharacterModelData( CharacterIndex startIndex, CharacterIndex endIndex, OperationsMask operations )
414 const CharacterIndex endIndexPlusOne = endIndex + 1u;
416 if( GET_LINE_BREAKS & operations )
418 // Clear the line break info.
419 LineBreakInfo* lineBreakInfoBuffer = mLogicalModel->mLineBreakInfo.Begin();
421 mLogicalModel->mLineBreakInfo.Erase( lineBreakInfoBuffer + startIndex,
422 lineBreakInfoBuffer + endIndexPlusOne );
424 // Clear the paragraphs.
425 ClearCharacterRuns( startIndex,
427 mLogicalModel->mParagraphInfo );
430 if( GET_WORD_BREAKS & operations )
432 // Clear the word break info.
433 WordBreakInfo* wordBreakInfoBuffer = mLogicalModel->mWordBreakInfo.Begin();
435 mLogicalModel->mWordBreakInfo.Erase( wordBreakInfoBuffer + startIndex,
436 wordBreakInfoBuffer + endIndexPlusOne );
439 if( GET_SCRIPTS & operations )
441 // Clear the scripts.
442 ClearCharacterRuns( startIndex,
444 mLogicalModel->mScriptRuns );
447 if( VALIDATE_FONTS & operations )
450 ClearCharacterRuns( startIndex,
452 mLogicalModel->mFontRuns );
455 if( 0u != mLogicalModel->mBidirectionalParagraphInfo.Count() )
457 if( BIDI_INFO & operations )
459 // Clear the bidirectional paragraph info.
460 ClearCharacterRuns( startIndex,
462 mLogicalModel->mBidirectionalParagraphInfo );
464 // Clear the character's directions.
465 CharacterDirection* characterDirectionsBuffer = mLogicalModel->mCharacterDirections.Begin();
467 mLogicalModel->mCharacterDirections.Erase( characterDirectionsBuffer + startIndex,
468 characterDirectionsBuffer + endIndexPlusOne );
471 if( REORDER & operations )
473 uint32_t startRemoveIndex = mLogicalModel->mBidirectionalLineInfo.Count();
474 uint32_t endRemoveIndex = startRemoveIndex;
475 ClearCharacterRuns( startIndex,
477 mLogicalModel->mBidirectionalLineInfo,
481 BidirectionalLineInfoRun* bidirectionalLineInfoBuffer = mLogicalModel->mBidirectionalLineInfo.Begin();
483 // Free the allocated memory used to store the conversion table in the bidirectional line info run.
484 for( Vector<BidirectionalLineInfoRun>::Iterator it = bidirectionalLineInfoBuffer + startRemoveIndex,
485 endIt = bidirectionalLineInfoBuffer + endRemoveIndex;
489 BidirectionalLineInfoRun& bidiLineInfo = *it;
491 free( bidiLineInfo.visualToLogicalMap );
492 bidiLineInfo.visualToLogicalMap = NULL;
495 mLogicalModel->mBidirectionalLineInfo.Erase( bidirectionalLineInfoBuffer + startRemoveIndex,
496 bidirectionalLineInfoBuffer + endRemoveIndex );
498 // Clear the logical to visual and the visual to logical conversion tables.
499 CharacterIndex* logicalToVisualMapBuffer = mLogicalModel->mLogicalToVisualMap.Begin();
500 mLogicalModel->mLogicalToVisualMap.Erase( logicalToVisualMapBuffer + startIndex,
501 logicalToVisualMapBuffer + endIndexPlusOne );
503 CharacterIndex* visualToLogicalMapBuffer = mLogicalModel->mVisualToLogicalMap.Begin();
504 mLogicalModel->mVisualToLogicalMap.Erase( visualToLogicalMapBuffer + startIndex,
505 visualToLogicalMapBuffer + endIndexPlusOne );
510 void Controller::Impl::ClearGlyphModelData( CharacterIndex startIndex, CharacterIndex endIndex, OperationsMask operations )
512 const CharacterIndex endIndexPlusOne = endIndex + 1u;
513 const Length numberOfCharactersRemoved = endIndexPlusOne - startIndex;
515 const bool clearShape = SHAPE_TEXT & operations;
516 const bool clearLayout = LAYOUT & operations;
518 if( clearShape || clearLayout )
520 // Convert the character index to glyph index before deleting the character to glyph and the glyphs per character buffers.
521 GlyphIndex* charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
522 Length* glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
524 const GlyphIndex endGlyphIndex = *( charactersToGlyphBuffer + endIndex );
525 const GlyphIndex endGlyphIndexPlusOne = endGlyphIndex + *( glyphsPerCharacterBuffer + endIndex );
526 const Length numberOfGlyphsRemoved = endGlyphIndexPlusOne - mTextUpdateInfo.mStartGlyphIndex;
530 // Update the character to glyph indices.
531 for( Vector<GlyphIndex>::Iterator it = charactersToGlyphBuffer + endIndexPlusOne,
532 endIt = charactersToGlyphBuffer + mVisualModel->mCharactersToGlyph.Count();
536 CharacterIndex& index = *it;
537 index -= numberOfGlyphsRemoved;
540 // Clear the character to glyph conversion table.
541 mVisualModel->mCharactersToGlyph.Erase( charactersToGlyphBuffer + startIndex,
542 charactersToGlyphBuffer + endIndexPlusOne );
544 // Clear the glyphs per character table.
545 mVisualModel->mGlyphsPerCharacter.Erase( glyphsPerCharacterBuffer + startIndex,
546 glyphsPerCharacterBuffer + endIndexPlusOne );
548 // Clear the glyphs buffer.
549 GlyphInfo* glyphsBuffer = mVisualModel->mGlyphs.Begin();
550 mVisualModel->mGlyphs.Erase( glyphsBuffer + mTextUpdateInfo.mStartGlyphIndex,
551 glyphsBuffer + endGlyphIndexPlusOne );
553 CharacterIndex* glyphsToCharactersBuffer = mVisualModel->mGlyphsToCharacters.Begin();
555 // Update the glyph to character indices.
556 for( Vector<CharacterIndex>::Iterator it = glyphsToCharactersBuffer + endGlyphIndexPlusOne,
557 endIt = glyphsToCharactersBuffer + mVisualModel->mGlyphsToCharacters.Count();
561 CharacterIndex& index = *it;
562 index -= numberOfCharactersRemoved;
565 // Clear the glyphs to characters buffer.
566 mVisualModel->mGlyphsToCharacters.Erase( glyphsToCharactersBuffer + mTextUpdateInfo.mStartGlyphIndex,
567 glyphsToCharactersBuffer + endGlyphIndexPlusOne );
569 // Clear the characters per glyph buffer.
570 Length* charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
571 mVisualModel->mCharactersPerGlyph.Erase( charactersPerGlyphBuffer + mTextUpdateInfo.mStartGlyphIndex,
572 charactersPerGlyphBuffer + endGlyphIndexPlusOne );
574 // Clear the positions buffer.
575 Vector2* positionsBuffer = mVisualModel->mGlyphPositions.Begin();
576 mVisualModel->mGlyphPositions.Erase( positionsBuffer + mTextUpdateInfo.mStartGlyphIndex,
577 positionsBuffer + endGlyphIndexPlusOne );
583 uint32_t startRemoveIndex = mVisualModel->mLines.Count();
584 uint32_t endRemoveIndex = startRemoveIndex;
585 ClearCharacterRuns( startIndex,
587 mVisualModel->mLines,
591 // Will update the glyph runs.
592 uint32_t startRemoveGlyphIndex = mVisualModel->mLines.Count();
593 uint32_t endRemoveGlyphIndex = startRemoveIndex;
594 ClearGlyphRuns( mTextUpdateInfo.mStartGlyphIndex,
596 mVisualModel->mLines,
597 startRemoveGlyphIndex,
598 endRemoveGlyphIndex );
600 // Set the line index from where to insert the new laid-out lines.
601 mTextUpdateInfo.mStartLineIndex = startRemoveIndex;
603 LineRun* linesBuffer = mVisualModel->mLines.Begin();
604 mVisualModel->mLines.Erase( linesBuffer + startRemoveIndex,
605 linesBuffer + endRemoveIndex );
610 void Controller::Impl::ClearModelData( CharacterIndex startIndex, CharacterIndex endIndex, OperationsMask operations )
612 if( mTextUpdateInfo.mClearAll ||
613 ( ( 0u == startIndex ) &&
614 ( mTextUpdateInfo.mPreviousNumberOfCharacters == endIndex + 1u ) ) )
616 ClearFullModelData( operations );
620 // Clear the model data related with characters.
621 ClearCharacterModelData( startIndex, endIndex, operations );
623 // Clear the model data related with glyphs.
624 ClearGlyphModelData( startIndex, endIndex, operations );
627 // The estimated number of lines. Used to avoid reallocations when layouting.
628 mTextUpdateInfo.mEstimatedNumberOfLines = std::max( mVisualModel->mLines.Count(), mLogicalModel->mParagraphInfo.Count() );
630 mVisualModel->ClearCaches();
632 // TODO finish the mark-up.
633 mVisualModel->mColorRuns.Clear();
636 void Controller::Impl::UpdateModel( OperationsMask operationsRequired )
638 DALI_LOG_INFO( gLogFilter, Debug::General, "Controller::UpdateModel\n" );
640 // Calculate the operations to be done.
641 const OperationsMask operations = static_cast<OperationsMask>( mOperationsPending & operationsRequired );
643 if( NO_OPERATION == operations )
645 // Nothing to do if no operations are pending and required.
649 Vector<Character>& utf32Characters = mLogicalModel->mText;
651 const Length numberOfCharacters = utf32Characters.Count();
653 // Index to the first character of the first paragraph to be updated.
654 CharacterIndex startIndex = 0u;
655 // Number of characters of the paragraphs to be removed.
656 Length paragraphCharacters = 0u;
658 CalculateTextUpdateIndices( paragraphCharacters );
659 startIndex = mTextUpdateInfo.mParagraphCharacterIndex;
661 if( mTextUpdateInfo.mClearAll ||
662 ( 0u != paragraphCharacters ) )
664 ClearModelData( startIndex, startIndex + ( ( paragraphCharacters > 0u ) ? paragraphCharacters - 1u : 0u ), operationsRequired );
667 mTextUpdateInfo.mClearAll = false;
669 Vector<LineBreakInfo>& lineBreakInfo = mLogicalModel->mLineBreakInfo;
670 const Length requestedNumberOfCharacters = mTextUpdateInfo.mRequestedNumberOfCharacters;
672 if( GET_LINE_BREAKS & operations )
674 // Retrieves the line break info. The line break info is used to split the text in 'paragraphs' to
675 // calculate the bidirectional info for each 'paragraph'.
676 // It's also used to layout the text (where it should be a new line) or to shape the text (text in different lines
677 // is not shaped together).
678 lineBreakInfo.Resize( numberOfCharacters, TextAbstraction::LINE_NO_BREAK );
680 SetLineBreakInfo( utf32Characters,
682 requestedNumberOfCharacters,
685 // Create the paragraph info.
686 mLogicalModel->CreateParagraphInfo( startIndex,
687 requestedNumberOfCharacters );
690 Vector<WordBreakInfo>& wordBreakInfo = mLogicalModel->mWordBreakInfo;
691 if( GET_WORD_BREAKS & operations )
693 // Retrieves the word break info. The word break info is used to layout the text (where to wrap the text in lines).
694 wordBreakInfo.Resize( numberOfCharacters, TextAbstraction::WORD_NO_BREAK );
696 SetWordBreakInfo( utf32Characters,
698 requestedNumberOfCharacters,
702 const bool getScripts = GET_SCRIPTS & operations;
703 const bool validateFonts = VALIDATE_FONTS & operations;
705 Vector<ScriptRun>& scripts = mLogicalModel->mScriptRuns;
706 Vector<FontRun>& validFonts = mLogicalModel->mFontRuns;
708 if( getScripts || validateFonts )
710 // Validates the fonts assigned by the application or assigns default ones.
711 // It makes sure all the characters are going to be rendered by the correct font.
712 MultilanguageSupport multilanguageSupport = MultilanguageSupport::Get();
716 // Retrieves the scripts used in the text.
717 multilanguageSupport.SetScripts( utf32Characters,
719 requestedNumberOfCharacters,
725 // Validate the fonts set through the mark-up string.
726 Vector<FontDescriptionRun>& fontDescriptionRuns = mLogicalModel->mFontDescriptionRuns;
728 // Get the default font id.
729 const FontId defaultFontId = ( NULL == mFontDefaults ) ? 0u : mFontDefaults->GetFontId( mFontClient );
731 // Validates the fonts. If there is a character with no assigned font it sets a default one.
732 // After this call, fonts are validated.
733 multilanguageSupport.ValidateFonts( utf32Characters,
738 requestedNumberOfCharacters,
743 Vector<Character> mirroredUtf32Characters;
744 bool textMirrored = false;
745 const Length numberOfParagraphs = mLogicalModel->mParagraphInfo.Count();
746 if( BIDI_INFO & operations )
748 Vector<BidirectionalParagraphInfoRun>& bidirectionalInfo = mLogicalModel->mBidirectionalParagraphInfo;
749 bidirectionalInfo.Reserve( numberOfParagraphs );
751 // Calculates the bidirectional info for the whole paragraph if it contains right to left scripts.
752 SetBidirectionalInfo( utf32Characters,
756 requestedNumberOfCharacters,
759 if( 0u != bidirectionalInfo.Count() )
761 // Only set the character directions if there is right to left characters.
762 Vector<CharacterDirection>& directions = mLogicalModel->mCharacterDirections;
763 GetCharactersDirection( bidirectionalInfo,
766 requestedNumberOfCharacters,
769 // This paragraph has right to left text. Some characters may need to be mirrored.
770 // TODO: consider if the mirrored string can be stored as well.
772 textMirrored = GetMirroredText( utf32Characters,
776 requestedNumberOfCharacters,
777 mirroredUtf32Characters );
781 // There is no right to left characters. Clear the directions vector.
782 mLogicalModel->mCharacterDirections.Clear();
786 Vector<GlyphInfo>& glyphs = mVisualModel->mGlyphs;
787 Vector<CharacterIndex>& glyphsToCharactersMap = mVisualModel->mGlyphsToCharacters;
788 Vector<Length>& charactersPerGlyph = mVisualModel->mCharactersPerGlyph;
789 Vector<GlyphIndex> newParagraphGlyphs;
790 newParagraphGlyphs.Reserve( numberOfParagraphs );
792 const Length currentNumberOfGlyphs = glyphs.Count();
793 if( SHAPE_TEXT & operations )
795 const Vector<Character>& textToShape = textMirrored ? mirroredUtf32Characters : utf32Characters;
797 ShapeText( textToShape,
802 mTextUpdateInfo.mStartGlyphIndex,
803 requestedNumberOfCharacters,
805 glyphsToCharactersMap,
807 newParagraphGlyphs );
809 // Create the 'number of glyphs' per character and the glyph to character conversion tables.
810 mVisualModel->CreateGlyphsPerCharacterTable( startIndex, mTextUpdateInfo.mStartGlyphIndex, requestedNumberOfCharacters );
811 mVisualModel->CreateCharacterToGlyphTable( startIndex, mTextUpdateInfo.mStartGlyphIndex, requestedNumberOfCharacters );
814 const Length numberOfGlyphs = glyphs.Count() - currentNumberOfGlyphs;
816 if( GET_GLYPH_METRICS & operations )
818 GlyphInfo* glyphsBuffer = glyphs.Begin();
819 mMetrics->GetGlyphMetrics( glyphsBuffer + mTextUpdateInfo.mStartGlyphIndex, numberOfGlyphs );
821 // Update the width and advance of all new paragraph characters.
822 for( Vector<GlyphIndex>::ConstIterator it = newParagraphGlyphs.Begin(), endIt = newParagraphGlyphs.End(); it != endIt; ++it )
824 const GlyphIndex index = *it;
825 GlyphInfo& glyph = *( glyphsBuffer + index );
827 glyph.xBearing = 0.f;
833 if( ( NULL != mEventData ) &&
834 mEventData->mPreEditFlag &&
835 ( 0u != mVisualModel->mCharactersToGlyph.Count() ) )
837 // Add the underline for the pre-edit text.
838 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
839 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
841 const GlyphIndex glyphStart = *( charactersToGlyphBuffer + mEventData->mPreEditStartPosition );
842 const CharacterIndex lastPreEditCharacter = mEventData->mPreEditStartPosition + ( ( mEventData->mPreEditLength > 0u ) ? mEventData->mPreEditLength - 1u : 0u );
843 const Length numberOfGlyphsLastCharacter = *( glyphsPerCharacterBuffer + lastPreEditCharacter );
844 const GlyphIndex glyphEnd = *( charactersToGlyphBuffer + lastPreEditCharacter ) + ( numberOfGlyphsLastCharacter > 1u ? numberOfGlyphsLastCharacter - 1u : 0u );
846 GlyphRun underlineRun;
847 underlineRun.glyphIndex = glyphStart;
848 underlineRun.numberOfGlyphs = 1u + glyphEnd - glyphStart;
850 // TODO: At the moment the underline runs are only for pre-edit.
851 mVisualModel->mUnderlineRuns.PushBack( underlineRun );
854 // The estimated number of lines. Used to avoid reallocations when layouting.
855 mTextUpdateInfo.mEstimatedNumberOfLines = std::max( mVisualModel->mLines.Count(), mLogicalModel->mParagraphInfo.Count() );
857 // Set the previous number of characters for the next time the text is updated.
858 mTextUpdateInfo.mPreviousNumberOfCharacters = numberOfCharacters;
861 bool Controller::Impl::UpdateModelStyle( OperationsMask operationsRequired )
863 bool updated = false;
865 if( COLOR & operationsRequired )
867 // Set the color runs in glyphs.
868 SetColorSegmentationInfo( mLogicalModel->mColorRuns,
869 mVisualModel->mCharactersToGlyph,
870 mVisualModel->mGlyphsPerCharacter,
871 mVisualModel->mColorRuns );
879 void Controller::Impl::RetrieveDefaultInputStyle( InputStyle& inputStyle )
881 // Sets the default text's color.
882 inputStyle.textColor = mTextColor;
884 // Sets the default font's family name, weight, width, slant and size.
887 inputStyle.familyName = mFontDefaults->mFontDescription.family;
888 inputStyle.weight = mFontDefaults->mFontDescription.weight;
889 inputStyle.width = mFontDefaults->mFontDescription.width;
890 inputStyle.slant = mFontDefaults->mFontDescription.slant;
891 inputStyle.size = mFontDefaults->mDefaultPointSize;
893 inputStyle.familyDefined = mFontDefaults->familyDefined;
894 inputStyle.weightDefined = mFontDefaults->weightDefined;
895 inputStyle.widthDefined = mFontDefaults->widthDefined;
896 inputStyle.slantDefined = mFontDefaults->slantDefined;
897 inputStyle.sizeDefined = mFontDefaults->sizeDefined;
901 inputStyle.familyName.clear();
902 inputStyle.weight = TextAbstraction::FontWeight::NORMAL;
903 inputStyle.width = TextAbstraction::FontWidth::NORMAL;
904 inputStyle.slant = TextAbstraction::FontSlant::NORMAL;
905 inputStyle.size = 0.f;
907 inputStyle.familyDefined = false;
908 inputStyle.weightDefined = false;
909 inputStyle.widthDefined = false;
910 inputStyle.slantDefined = false;
911 inputStyle.sizeDefined = false;
915 float Controller::Impl::GetDefaultFontLineHeight()
917 FontId defaultFontId = 0u;
918 if( NULL == mFontDefaults )
920 TextAbstraction::FontDescription fontDescription;
921 defaultFontId = mFontClient.GetFontId( fontDescription );
925 defaultFontId = mFontDefaults->GetFontId( mFontClient );
928 Text::FontMetrics fontMetrics;
929 mMetrics->GetFontMetrics( defaultFontId, fontMetrics );
931 return( fontMetrics.ascender - fontMetrics.descender );
934 void Controller::Impl::OnCursorKeyEvent( const Event& event )
936 if( NULL == mEventData )
938 // Nothing to do if there is no text input.
942 int keyCode = event.p1.mInt;
944 if( Dali::DALI_KEY_CURSOR_LEFT == keyCode )
946 if( mEventData->mPrimaryCursorPosition > 0u )
948 mEventData->mPrimaryCursorPosition = CalculateNewCursorIndex( mEventData->mPrimaryCursorPosition - 1u );
951 else if( Dali::DALI_KEY_CURSOR_RIGHT == keyCode )
953 if( mLogicalModel->mText.Count() > mEventData->mPrimaryCursorPosition )
955 mEventData->mPrimaryCursorPosition = CalculateNewCursorIndex( mEventData->mPrimaryCursorPosition );
958 else if( Dali::DALI_KEY_CURSOR_UP == keyCode )
962 else if( Dali::DALI_KEY_CURSOR_DOWN == keyCode )
967 mEventData->mUpdateCursorPosition = true;
968 mEventData->mUpdateInputStyle = true;
969 mEventData->mScrollAfterUpdatePosition = true;
972 void Controller::Impl::OnTapEvent( const Event& event )
974 if( NULL != mEventData )
976 const unsigned int tapCount = event.p1.mUint;
980 if( IsShowingRealText() )
982 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
983 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
985 mEventData->mPrimaryCursorPosition = GetClosestCursorIndex( xPosition,
988 // When the cursor position is changing, delay cursor blinking
989 mEventData->mDecorator->DelayCursorBlink();
993 mEventData->mPrimaryCursorPosition = 0u;
996 mEventData->mUpdateCursorPosition = true;
997 mEventData->mScrollAfterUpdatePosition = true;
998 mEventData->mUpdateInputStyle = true;
1000 // Notify the cursor position to the imf manager.
1001 if( mEventData->mImfManager )
1003 mEventData->mImfManager.SetCursorPosition( mEventData->mPrimaryCursorPosition );
1004 mEventData->mImfManager.NotifyCursorPosition();
1010 void Controller::Impl::OnPanEvent( const Event& event )
1012 if( NULL == mEventData )
1014 // Nothing to do if there is no text input.
1018 int state = event.p1.mInt;
1020 if( Gesture::Started == state ||
1021 Gesture::Continuing == state )
1023 const Vector2& actualSize = mVisualModel->GetLayoutSize();
1024 const Vector2 currentScroll = mEventData->mScrollPosition;
1026 if( mEventData->mHorizontalScrollingEnabled )
1028 const float displacementX = event.p2.mFloat;
1029 mEventData->mScrollPosition.x += displacementX;
1031 ClampHorizontalScroll( actualSize );
1034 if( mEventData->mVerticalScrollingEnabled )
1036 const float displacementY = event.p3.mFloat;
1037 mEventData->mScrollPosition.y += displacementY;
1039 ClampVerticalScroll( actualSize );
1042 if( mEventData->mDecorator )
1044 mEventData->mDecorator->UpdatePositions( mEventData->mScrollPosition - currentScroll );
1049 void Controller::Impl::OnLongPressEvent( const Event& event )
1051 DALI_LOG_INFO( gLogFilter, Debug::General, "Controller::OnLongPressEvent\n" );
1053 if( EventData::EDITING == mEventData->mState )
1055 ChangeState ( EventData::EDITING_WITH_POPUP );
1056 mEventData->mDecoratorUpdated = true;
1060 void Controller::Impl::OnHandleEvent( const Event& event )
1062 if( NULL == mEventData )
1064 // Nothing to do if there is no text input.
1068 const unsigned int state = event.p1.mUint;
1069 const bool handleStopScrolling = ( HANDLE_STOP_SCROLLING == state );
1071 if( HANDLE_PRESSED == state )
1073 // The event.p2 and event.p3 are in decorator coords. Need to transforms to text coords.
1074 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
1075 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
1077 const CharacterIndex handleNewPosition = GetClosestCursorIndex( xPosition, yPosition );
1079 if( Event::GRAB_HANDLE_EVENT == event.type )
1081 ChangeState ( EventData::GRAB_HANDLE_PANNING );
1083 if( handleNewPosition != mEventData->mPrimaryCursorPosition )
1085 mEventData->mPrimaryCursorPosition = handleNewPosition;
1086 mEventData->mUpdateCursorPosition = true;
1089 else if( Event::LEFT_SELECTION_HANDLE_EVENT == event.type )
1091 ChangeState ( EventData::SELECTION_HANDLE_PANNING );
1093 if( ( handleNewPosition != mEventData->mLeftSelectionPosition ) &&
1094 ( handleNewPosition != mEventData->mRightSelectionPosition ) )
1096 mEventData->mLeftSelectionPosition = handleNewPosition;
1098 mEventData->mUpdateLeftSelectionPosition = true;
1101 else if( Event::RIGHT_SELECTION_HANDLE_EVENT == event.type )
1103 ChangeState ( EventData::SELECTION_HANDLE_PANNING );
1105 if( ( handleNewPosition != mEventData->mRightSelectionPosition ) &&
1106 ( handleNewPosition != mEventData->mLeftSelectionPosition ) )
1108 mEventData->mRightSelectionPosition = handleNewPosition;
1110 mEventData->mUpdateRightSelectionPosition = true;
1113 } // end ( HANDLE_PRESSED == state )
1114 else if( ( HANDLE_RELEASED == state ) ||
1115 handleStopScrolling )
1117 CharacterIndex handlePosition = 0u;
1118 if( handleStopScrolling )
1120 // The event.p2 and event.p3 are in decorator coords. Need to transforms to text coords.
1121 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
1122 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
1124 handlePosition = GetClosestCursorIndex( xPosition, yPosition );
1127 if( Event::GRAB_HANDLE_EVENT == event.type )
1129 mEventData->mUpdateCursorPosition = true;
1130 mEventData->mUpdateInputStyle = true;
1132 if( !IsClipboardEmpty() )
1134 ChangeState( EventData::EDITING_WITH_PASTE_POPUP ); // Moving grabhandle will show Paste Popup
1137 if( handleStopScrolling )
1139 mEventData->mScrollAfterUpdatePosition = mEventData->mPrimaryCursorPosition != handlePosition;
1140 mEventData->mPrimaryCursorPosition = handlePosition;
1143 else if( Event::LEFT_SELECTION_HANDLE_EVENT == event.type )
1145 ChangeState( EventData::SELECTING );
1147 if( handleStopScrolling )
1149 mEventData->mUpdateLeftSelectionPosition = ( mEventData->mRightSelectionPosition != handlePosition );
1150 mEventData->mScrollAfterUpdatePosition = mEventData->mUpdateLeftSelectionPosition;
1152 if( mEventData->mUpdateLeftSelectionPosition )
1154 mEventData->mLeftSelectionPosition = handlePosition;
1158 else if( Event::RIGHT_SELECTION_HANDLE_EVENT == event.type )
1160 ChangeState( EventData::SELECTING );
1162 if( handleStopScrolling )
1164 mEventData->mUpdateRightSelectionPosition = ( mEventData->mLeftSelectionPosition != handlePosition );
1165 mEventData->mScrollAfterUpdatePosition = mEventData->mUpdateRightSelectionPosition;
1166 if( mEventData->mUpdateRightSelectionPosition )
1168 mEventData->mRightSelectionPosition = handlePosition;
1173 mEventData->mDecoratorUpdated = true;
1174 } // end ( ( HANDLE_RELEASED == state ) || ( HANDLE_STOP_SCROLLING == state ) )
1175 else if( HANDLE_SCROLLING == state )
1177 const float xSpeed = event.p2.mFloat;
1178 const Vector2& actualSize = mVisualModel->GetLayoutSize();
1179 const Vector2 currentScrollPosition = mEventData->mScrollPosition;
1181 mEventData->mScrollPosition.x += xSpeed;
1183 ClampHorizontalScroll( actualSize );
1185 bool endOfScroll = false;
1186 if( Vector2::ZERO == ( currentScrollPosition - mEventData->mScrollPosition ) )
1188 // Notify the decorator there is no more text to scroll.
1189 // The decorator won't send more scroll events.
1190 mEventData->mDecorator->NotifyEndOfScroll();
1191 // Still need to set the position of the handle.
1195 // Set the position of the handle.
1196 const bool scrollRightDirection = xSpeed > 0.f;
1197 const bool leftSelectionHandleEvent = Event::LEFT_SELECTION_HANDLE_EVENT == event.type;
1198 const bool rightSelectionHandleEvent = Event::RIGHT_SELECTION_HANDLE_EVENT == event.type;
1200 if( Event::GRAB_HANDLE_EVENT == event.type )
1202 ChangeState( EventData::GRAB_HANDLE_PANNING );
1204 Vector2 position = mEventData->mDecorator->GetPosition( GRAB_HANDLE );
1206 // Position the grag handle close to either the left or right edge.
1207 position.x = scrollRightDirection ? 0.f : mVisualModel->mControlSize.width;
1209 // Get the new handle position.
1210 // The grab handle's position is in decorator coords. Need to transforms to text coords.
1211 const CharacterIndex handlePosition = GetClosestCursorIndex( position.x - mEventData->mScrollPosition.x - mAlignmentOffset.x,
1212 position.y - mEventData->mScrollPosition.y - mAlignmentOffset.y );
1214 mEventData->mUpdateCursorPosition = mEventData->mPrimaryCursorPosition != handlePosition;
1215 mEventData->mScrollAfterUpdatePosition = mEventData->mUpdateCursorPosition;
1216 mEventData->mPrimaryCursorPosition = handlePosition;
1217 mEventData->mUpdateInputStyle = mEventData->mUpdateCursorPosition;
1219 else if( leftSelectionHandleEvent || rightSelectionHandleEvent )
1221 // TODO: This is recalculating the selection box every time the text is scrolled with the selection handles.
1222 // Think if something can be done to save power.
1224 ChangeState( EventData::SELECTION_HANDLE_PANNING );
1226 Vector2 position = mEventData->mDecorator->GetPosition( leftSelectionHandleEvent ? Text::LEFT_SELECTION_HANDLE : Text::RIGHT_SELECTION_HANDLE );
1228 // Position the selection handle close to either the left or right edge.
1229 position.x = scrollRightDirection ? 0.f : mVisualModel->mControlSize.width;
1231 // Get the new handle position.
1232 // The selection handle's position is in decorator coords. Need to transforms to text coords.
1233 const CharacterIndex handlePosition = GetClosestCursorIndex( position.x - mEventData->mScrollPosition.x - mAlignmentOffset.x,
1234 position.y - mEventData->mScrollPosition.y - mAlignmentOffset.y );
1236 if( leftSelectionHandleEvent )
1238 const bool differentHandles = ( mEventData->mLeftSelectionPosition != handlePosition ) && ( mEventData->mRightSelectionPosition != handlePosition );
1239 mEventData->mUpdateLeftSelectionPosition = endOfScroll || differentHandles;
1240 if( differentHandles )
1242 mEventData->mLeftSelectionPosition = handlePosition;
1247 const bool differentHandles = ( mEventData->mRightSelectionPosition != handlePosition ) && ( mEventData->mLeftSelectionPosition != handlePosition );
1248 mEventData->mUpdateRightSelectionPosition = endOfScroll || differentHandles;
1249 if( differentHandles )
1251 mEventData->mRightSelectionPosition = handlePosition;
1255 if( mEventData->mUpdateLeftSelectionPosition || mEventData->mUpdateRightSelectionPosition )
1257 RepositionSelectionHandles();
1259 mEventData->mScrollAfterUpdatePosition = true;
1262 mEventData->mDecoratorUpdated = true;
1263 } // end ( HANDLE_SCROLLING == state )
1266 void Controller::Impl::OnSelectEvent( const Event& event )
1268 if( NULL == mEventData )
1270 // Nothing to do if there is no text.
1274 if( mEventData->mSelectionEnabled )
1276 // The event.p2 and event.p3 are in decorator coords. Need to transforms to text coords.
1277 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
1278 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
1280 // Calculates the logical position from the x,y coords.
1281 RepositionSelectionHandles( xPosition,
1284 mEventData->mUpdateLeftSelectionPosition = true;
1285 mEventData->mUpdateRightSelectionPosition = true;
1287 mEventData->mScrollAfterUpdatePosition = ( mEventData->mLeftSelectionPosition != mEventData->mRightSelectionPosition );
1291 void Controller::Impl::OnSelectAllEvent()
1293 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "OnSelectAllEvent mEventData->mSelectionEnabled%s \n", mEventData->mSelectionEnabled?"true":"false");
1295 if( NULL == mEventData )
1297 // Nothing to do if there is no text.
1301 if( mEventData->mSelectionEnabled )
1303 mEventData->mLeftSelectionPosition = 0u;
1304 mEventData->mRightSelectionPosition = mLogicalModel->mText.Count();
1306 mEventData->mScrollAfterUpdatePosition = true;
1307 mEventData->mUpdateLeftSelectionPosition = true;
1308 mEventData->mUpdateRightSelectionPosition = true;
1312 void Controller::Impl::RetrieveSelection( std::string& selectedText, bool deleteAfterRetrieval )
1314 if( mEventData->mLeftSelectionPosition == mEventData->mRightSelectionPosition )
1316 // Nothing to select if handles are in the same place.
1317 selectedText.clear();
1321 const bool handlesCrossed = mEventData->mLeftSelectionPosition > mEventData->mRightSelectionPosition;
1323 //Get start and end position of selection
1324 const CharacterIndex startOfSelectedText = handlesCrossed ? mEventData->mRightSelectionPosition : mEventData->mLeftSelectionPosition;
1325 const Length lengthOfSelectedText = ( handlesCrossed ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition ) - startOfSelectedText;
1327 Vector<Character>& utf32Characters = mLogicalModel->mText;
1328 const Length numberOfCharacters = utf32Characters.Count();
1330 // Validate the start and end selection points
1331 if( ( startOfSelectedText + lengthOfSelectedText ) <= numberOfCharacters )
1333 //Get text as a UTF8 string
1334 Utf32ToUtf8( &utf32Characters[startOfSelectedText], lengthOfSelectedText, selectedText );
1336 if( deleteAfterRetrieval ) // Only delete text if copied successfully
1338 // Set as input style the style of the first deleted character.
1339 mLogicalModel->RetrieveStyle( startOfSelectedText, mEventData->mInputStyle );
1341 mLogicalModel->UpdateTextStyleRuns( startOfSelectedText, -static_cast<int>( lengthOfSelectedText ) );
1343 // Mark the paragraphs to be updated.
1344 mTextUpdateInfo.mCharacterIndex = startOfSelectedText;
1345 mTextUpdateInfo.mNumberOfCharactersToRemove = lengthOfSelectedText;
1347 // Delete text between handles
1348 Vector<Character>::Iterator first = utf32Characters.Begin() + startOfSelectedText;
1349 Vector<Character>::Iterator last = first + lengthOfSelectedText;
1350 utf32Characters.Erase( first, last );
1352 // Scroll after delete.
1353 mEventData->mPrimaryCursorPosition = handlesCrossed ? mEventData->mRightSelectionPosition : mEventData->mLeftSelectionPosition;
1354 mEventData->mScrollAfterDelete = true;
1356 // Udpade the cursor position and the decorator.
1357 // Scroll after the position is updated if is not scrolling after delete.
1358 mEventData->mUpdateCursorPosition = true;
1359 mEventData->mScrollAfterUpdatePosition = !mEventData->mScrollAfterDelete;
1360 mEventData->mDecoratorUpdated = true;
1364 void Controller::Impl::ShowClipboard()
1368 mClipboard.ShowClipboard();
1372 void Controller::Impl::HideClipboard()
1376 mClipboard.HideClipboard();
1380 bool Controller::Impl::CopyStringToClipboard( std::string& source )
1382 //Send string to clipboard
1383 return ( mClipboard && mClipboard.SetItem( source ) );
1386 void Controller::Impl::SendSelectionToClipboard( bool deleteAfterSending )
1388 std::string selectedText;
1389 RetrieveSelection( selectedText, deleteAfterSending );
1390 CopyStringToClipboard( selectedText );
1391 ChangeState( EventData::EDITING );
1394 void Controller::Impl::GetTextFromClipboard( unsigned int itemIndex, std::string& retrievedString )
1398 retrievedString = mClipboard.GetItem( itemIndex );
1402 void Controller::Impl::RepositionSelectionHandles()
1404 CharacterIndex selectionStart = mEventData->mLeftSelectionPosition;
1405 CharacterIndex selectionEnd = mEventData->mRightSelectionPosition;
1407 if( selectionStart == selectionEnd )
1409 // Nothing to select if handles are in the same place.
1413 mEventData->mDecorator->ClearHighlights();
1415 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
1416 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
1417 const GlyphInfo* const glyphsBuffer = mVisualModel->mGlyphs.Begin();
1418 const Vector2* const positionsBuffer = mVisualModel->mGlyphPositions.Begin();
1419 const Length* const charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
1420 const CharacterIndex* const glyphToCharacterBuffer = mVisualModel->mGlyphsToCharacters.Begin();
1421 const CharacterDirection* const modelCharacterDirectionsBuffer = ( 0u != mLogicalModel->mCharacterDirections.Count() ) ? mLogicalModel->mCharacterDirections.Begin() : NULL;
1423 // TODO: Better algorithm to create the highlight box.
1424 // TODO: Multi-line.
1426 // Get the height of the line.
1427 const Vector<LineRun>& lines = mVisualModel->mLines;
1428 const LineRun& firstLine = *lines.Begin();
1429 const float height = firstLine.ascender + -firstLine.descender;
1431 const bool isLastCharacter = selectionEnd >= mLogicalModel->mText.Count();
1432 const bool startDirection = ( ( NULL == modelCharacterDirectionsBuffer ) ? false : *( modelCharacterDirectionsBuffer + selectionStart ) );
1433 const bool endDirection = ( ( NULL == modelCharacterDirectionsBuffer ) ? false : *( modelCharacterDirectionsBuffer + ( selectionEnd - ( isLastCharacter ? 1u : 0u ) ) ) );
1435 // Swap the indices if the start is greater than the end.
1436 const bool indicesSwapped = selectionStart > selectionEnd;
1438 // Tell the decorator to flip the selection handles if needed.
1439 mEventData->mDecorator->SetSelectionHandleFlipState( indicesSwapped, startDirection, endDirection );
1441 if( indicesSwapped )
1443 std::swap( selectionStart, selectionEnd );
1446 // Get the indices to the first and last selected glyphs.
1447 const CharacterIndex selectionEndMinusOne = selectionEnd - 1u;
1448 const GlyphIndex glyphStart = *( charactersToGlyphBuffer + selectionStart );
1449 const Length numberOfGlyphs = *( glyphsPerCharacterBuffer + selectionEndMinusOne );
1450 const GlyphIndex glyphEnd = *( charactersToGlyphBuffer + selectionEndMinusOne ) + ( ( numberOfGlyphs > 0 ) ? numberOfGlyphs - 1u : 0u );
1452 // Check if the first glyph is a ligature that must be broken like Latin ff, fi, or Arabic ﻻ, etc which needs special code.
1453 const Length numberOfCharactersStart = *( charactersPerGlyphBuffer + glyphStart );
1454 bool splitStartGlyph = ( numberOfCharactersStart > 1u ) && HasLigatureMustBreak( mLogicalModel->GetScript( selectionStart ) );
1456 // Check if the last glyph is a ligature that must be broken like Latin ff, fi, or Arabic ﻻ, etc which needs special code.
1457 const Length numberOfCharactersEnd = *( charactersPerGlyphBuffer + glyphEnd );
1458 bool splitEndGlyph = ( glyphStart != glyphEnd ) && ( numberOfCharactersEnd > 1u ) && HasLigatureMustBreak( mLogicalModel->GetScript( selectionEndMinusOne ) );
1460 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
1462 // Traverse the glyphs.
1463 for( GlyphIndex index = glyphStart; index <= glyphEnd; ++index )
1465 const GlyphInfo& glyph = *( glyphsBuffer + index );
1466 const Vector2& position = *( positionsBuffer + index );
1468 if( splitStartGlyph )
1470 // 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.
1472 const float glyphAdvance = glyph.advance / static_cast<float>( numberOfCharactersStart );
1473 const CharacterIndex interGlyphIndex = selectionStart - *( glyphToCharacterBuffer + glyphStart );
1474 // Get the direction of the character.
1475 CharacterDirection isCurrentRightToLeft = false;
1476 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
1478 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + selectionStart );
1481 // The end point could be in the middle of the ligature.
1482 // Calculate the number of characters selected.
1483 const Length numberOfCharacters = ( glyphStart == glyphEnd ) ? ( selectionEnd - selectionStart ) : ( numberOfCharactersStart - interGlyphIndex );
1485 const float xPosition = position.x - glyph.xBearing + offset.x + glyphAdvance * static_cast<float>( isCurrentRightToLeft ? ( numberOfCharactersStart - interGlyphIndex - numberOfCharacters ) : interGlyphIndex );
1487 mEventData->mDecorator->AddHighlight( xPosition,
1489 xPosition + static_cast<float>( numberOfCharacters ) * glyphAdvance,
1490 offset.y + height );
1492 splitStartGlyph = false;
1496 if( splitEndGlyph && ( index == glyphEnd ) )
1498 // 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.
1500 const float glyphAdvance = glyph.advance / static_cast<float>( numberOfCharactersEnd );
1501 const CharacterIndex interGlyphIndex = selectionEnd - *( glyphToCharacterBuffer + glyphEnd );
1502 // Get the direction of the character.
1503 CharacterDirection isCurrentRightToLeft = false;
1504 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
1506 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + selectionEnd );
1509 const Length numberOfCharacters = numberOfCharactersEnd - interGlyphIndex;
1511 const float xPosition = position.x - glyph.xBearing + offset.x + ( isCurrentRightToLeft ? ( glyphAdvance * static_cast<float>( numberOfCharacters ) ) : 0.f );
1512 mEventData->mDecorator->AddHighlight( xPosition,
1514 xPosition + static_cast<float>( interGlyphIndex ) * glyphAdvance,
1515 offset.y + height );
1517 splitEndGlyph = false;
1521 const float xPosition = position.x - glyph.xBearing + offset.x;
1522 mEventData->mDecorator->AddHighlight( xPosition,
1524 xPosition + glyph.advance,
1525 offset.y + height );
1528 CursorInfo primaryCursorInfo;
1529 GetCursorPosition( mEventData->mLeftSelectionPosition,
1530 primaryCursorInfo );
1532 CursorInfo secondaryCursorInfo;
1533 GetCursorPosition( mEventData->mRightSelectionPosition,
1534 secondaryCursorInfo );
1536 const Vector2 primaryPosition = primaryCursorInfo.primaryPosition + offset;
1537 const Vector2 secondaryPosition = secondaryCursorInfo.primaryPosition + offset;
1539 mEventData->mDecorator->SetPosition( LEFT_SELECTION_HANDLE,
1541 primaryCursorInfo.lineOffset + offset.y,
1542 primaryCursorInfo.lineHeight );
1544 mEventData->mDecorator->SetPosition( RIGHT_SELECTION_HANDLE,
1545 secondaryPosition.x,
1546 secondaryCursorInfo.lineOffset + offset.y,
1547 secondaryCursorInfo.lineHeight );
1549 // Cursor to be positioned at end of selection so if selection interrupted and edit mode restarted the cursor will be at end of selection
1550 mEventData->mPrimaryCursorPosition = ( indicesSwapped ) ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition;
1552 // Set the flag to update the decorator.
1553 mEventData->mDecoratorUpdated = true;
1556 void Controller::Impl::RepositionSelectionHandles( float visualX, float visualY )
1558 if( NULL == mEventData )
1560 // Nothing to do if there is no text input.
1564 if( IsShowingPlaceholderText() )
1566 // Nothing to do if there is the place-holder text.
1570 const Length numberOfGlyphs = mVisualModel->mGlyphs.Count();
1571 const Length numberOfLines = mVisualModel->mLines.Count();
1572 if( ( 0 == numberOfGlyphs ) ||
1573 ( 0 == numberOfLines ) )
1575 // Nothing to do if there is no text.
1579 // Find which word was selected
1580 CharacterIndex selectionStart( 0 );
1581 CharacterIndex selectionEnd( 0 );
1582 FindSelectionIndices( visualX, visualY, selectionStart, selectionEnd );
1583 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "%p selectionStart %d selectionEnd %d\n", this, selectionStart, selectionEnd );
1585 if( selectionStart == selectionEnd )
1587 ChangeState( EventData::EDITING );
1588 // Nothing to select. i.e. a white space, out of bounds
1592 mEventData->mLeftSelectionPosition = selectionStart;
1593 mEventData->mRightSelectionPosition = selectionEnd;
1596 void Controller::Impl::SetPopupButtons()
1599 * Sets the Popup buttons to be shown depending on State.
1601 * If SELECTING : CUT & COPY + ( PASTE & CLIPBOARD if content available to paste )
1603 * If EDITING_WITH_POPUP : SELECT & SELECT_ALL
1606 TextSelectionPopup::Buttons buttonsToShow = TextSelectionPopup::NONE;
1608 if( EventData::SELECTING == mEventData->mState )
1610 buttonsToShow = TextSelectionPopup::Buttons( TextSelectionPopup::CUT | TextSelectionPopup::COPY );
1612 if( !IsClipboardEmpty() )
1614 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
1615 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::CLIPBOARD ) );
1618 if( !mEventData->mAllTextSelected )
1620 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::SELECT_ALL ) );
1623 else if( EventData::EDITING_WITH_POPUP == mEventData->mState )
1625 if( mLogicalModel->mText.Count() && !IsShowingPlaceholderText() )
1627 buttonsToShow = TextSelectionPopup::Buttons( TextSelectionPopup::SELECT | TextSelectionPopup::SELECT_ALL );
1630 if( !IsClipboardEmpty() )
1632 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
1633 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::CLIPBOARD ) );
1636 else if( EventData::EDITING_WITH_PASTE_POPUP == mEventData->mState )
1638 if ( !IsClipboardEmpty() )
1640 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
1641 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::CLIPBOARD ) );
1645 mEventData->mDecorator->SetEnabledPopupButtons( buttonsToShow );
1648 void Controller::Impl::ChangeState( EventData::State newState )
1650 if( NULL == mEventData )
1652 // Nothing to do if there is no text input.
1656 DALI_LOG_INFO( gLogFilter, Debug::General, "ChangeState state:%d newstate:%d\n", mEventData->mState, newState );
1658 if( mEventData->mState != newState )
1660 mEventData->mState = newState;
1662 if( EventData::INACTIVE == mEventData->mState )
1664 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
1665 mEventData->mDecorator->StopCursorBlink();
1666 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1667 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1668 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1669 mEventData->mDecorator->SetPopupActive( false );
1670 mEventData->mDecoratorUpdated = true;
1673 else if( EventData::INTERRUPTED == mEventData->mState)
1675 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1676 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1677 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1678 mEventData->mDecorator->SetPopupActive( false );
1679 mEventData->mDecoratorUpdated = true;
1682 else if( EventData::SELECTING == mEventData->mState )
1684 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
1685 mEventData->mDecorator->StopCursorBlink();
1686 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1687 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true );
1688 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true );
1689 if( mEventData->mGrabHandlePopupEnabled )
1692 mEventData->mDecorator->SetPopupActive( true );
1694 mEventData->mDecoratorUpdated = true;
1696 else if( EventData::EDITING == mEventData->mState )
1698 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1699 if( mEventData->mCursorBlinkEnabled )
1701 mEventData->mDecorator->StartCursorBlink();
1703 // Grab handle is not shown until a tap is received whilst EDITING
1704 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1705 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1706 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1707 if( mEventData->mGrabHandlePopupEnabled )
1709 mEventData->mDecorator->SetPopupActive( false );
1711 mEventData->mDecoratorUpdated = true;
1714 else if( EventData::EDITING_WITH_POPUP == mEventData->mState )
1716 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "EDITING_WITH_POPUP \n", newState );
1718 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1719 if( mEventData->mCursorBlinkEnabled )
1721 mEventData->mDecorator->StartCursorBlink();
1723 if( mEventData->mSelectionEnabled )
1725 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1726 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1730 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
1732 if( mEventData->mGrabHandlePopupEnabled )
1735 mEventData->mDecorator->SetPopupActive( true );
1738 mEventData->mDecoratorUpdated = true;
1740 else if( EventData::EDITING_WITH_GRAB_HANDLE == mEventData->mState )
1742 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "EDITING_WITH_GRAB_HANDLE \n", newState );
1744 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1745 if( mEventData->mCursorBlinkEnabled )
1747 mEventData->mDecorator->StartCursorBlink();
1749 // Grab handle is not shown until a tap is received whilst EDITING
1750 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
1751 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1752 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1753 if( mEventData->mGrabHandlePopupEnabled )
1755 mEventData->mDecorator->SetPopupActive( false );
1757 mEventData->mDecoratorUpdated = true;
1760 else if( EventData::SELECTION_HANDLE_PANNING == mEventData->mState )
1762 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
1763 mEventData->mDecorator->StopCursorBlink();
1764 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1765 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true );
1766 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true );
1767 if( mEventData->mGrabHandlePopupEnabled )
1769 mEventData->mDecorator->SetPopupActive( false );
1771 mEventData->mDecoratorUpdated = true;
1773 else if( EventData::GRAB_HANDLE_PANNING == mEventData->mState )
1775 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "GRAB_HANDLE_PANNING \n", newState );
1777 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1778 if( mEventData->mCursorBlinkEnabled )
1780 mEventData->mDecorator->StartCursorBlink();
1782 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
1783 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1784 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1785 if( mEventData->mGrabHandlePopupEnabled )
1787 mEventData->mDecorator->SetPopupActive( false );
1789 mEventData->mDecoratorUpdated = true;
1791 else if( EventData::EDITING_WITH_PASTE_POPUP == mEventData->mState )
1793 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "EDITING_WITH_PASTE_POPUP \n", newState );
1795 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1796 if( mEventData->mCursorBlinkEnabled )
1798 mEventData->mDecorator->StartCursorBlink();
1801 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
1802 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1803 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1805 if( mEventData->mGrabHandlePopupEnabled )
1808 mEventData->mDecorator->SetPopupActive( true );
1811 mEventData->mDecoratorUpdated = true;
1816 LineIndex Controller::Impl::GetClosestLine( float y ) const
1818 float totalHeight = 0.f;
1819 LineIndex lineIndex = 0u;
1821 const Vector<LineRun>& lines = mVisualModel->mLines;
1822 for( LineIndex endLine = lines.Count();
1823 lineIndex < endLine;
1826 const LineRun& lineRun = lines[lineIndex];
1827 totalHeight += lineRun.ascender + -lineRun.descender;
1828 if( y < totalHeight )
1834 if( lineIndex == 0 )
1842 void Controller::Impl::FindSelectionIndices( float visualX, float visualY, CharacterIndex& startIndex, CharacterIndex& endIndex )
1844 CharacterIndex hitCharacter = GetClosestCursorIndex( visualX, visualY );
1845 DALI_ASSERT_DEBUG( hitCharacter <= mLogicalModel->mText.Count() && "GetClosestCursorIndex returned out of bounds index" );
1847 if( mLogicalModel->mText.Count() == 0 )
1849 return; // if model empty
1852 if( hitCharacter >= mLogicalModel->mText.Count() )
1854 // Closest hit character is the last character.
1855 if( hitCharacter == mLogicalModel->mText.Count() )
1857 hitCharacter--; //Hit character index set to last character in logical model
1861 // hitCharacter is out of bounds
1866 startIndex = hitCharacter;
1867 endIndex = hitCharacter;
1868 bool isHitCharacterWhitespace = TextAbstraction::IsWhiteSpace( mLogicalModel->mText[hitCharacter] );
1870 // Find the start and end of the text
1871 for( startIndex = hitCharacter; startIndex > 0; --startIndex )
1873 if( isHitCharacterWhitespace != TextAbstraction::IsWhiteSpace( mLogicalModel->mText[ startIndex-1 ] ) )
1878 const CharacterIndex pastTheEnd = mLogicalModel->mText.Count();
1879 for( endIndex = hitCharacter + 1u; endIndex < pastTheEnd; ++endIndex )
1881 if( isHitCharacterWhitespace != TextAbstraction::IsWhiteSpace( mLogicalModel->mText[ endIndex ] ) )
1888 CharacterIndex Controller::Impl::GetClosestCursorIndex( float visualX,
1891 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "GetClosestCursorIndex %p closest visualX %f visualY %f\n", this, visualX, visualY );
1893 if( NULL == mEventData )
1895 // Nothing to do if there is no text input.
1899 CharacterIndex logicalIndex = 0u;
1901 const Length numberOfGlyphs = mVisualModel->mGlyphs.Count();
1902 const Length numberOfLines = mVisualModel->mLines.Count();
1903 if( ( 0 == numberOfGlyphs ) ||
1904 ( 0 == numberOfLines ) )
1906 return logicalIndex;
1909 // Find which line is closest
1910 const LineIndex lineIndex = GetClosestLine( visualY );
1911 const LineRun& line = mVisualModel->mLines[lineIndex];
1913 // Get the positions of the glyphs.
1914 const Vector<Vector2>& positions = mVisualModel->mGlyphPositions;
1915 const Vector2* const positionsBuffer = positions.Begin();
1917 // Get the visual to logical conversion tables.
1918 const CharacterIndex* const visualToLogicalBuffer = ( 0u != mLogicalModel->mVisualToLogicalMap.Count() ) ? mLogicalModel->mVisualToLogicalMap.Begin() : NULL;
1919 const CharacterIndex* const visualToLogicalCursorBuffer = mLogicalModel->mVisualToLogicalCursorMap.Begin();
1921 // Get the character to glyph conversion table.
1922 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
1924 // Get the glyphs per character table.
1925 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
1927 // Get the glyph's info buffer.
1928 const GlyphInfo* const glyphInfoBuffer = mVisualModel->mGlyphs.Begin();
1930 // If the vector is void, there is no right to left characters.
1931 const bool hasRightToLeftCharacters = NULL != visualToLogicalBuffer;
1933 const CharacterIndex startCharacter = line.characterRun.characterIndex;
1934 const CharacterIndex endCharacter = line.characterRun.characterIndex + line.characterRun.numberOfCharacters;
1935 DALI_ASSERT_DEBUG( endCharacter <= mLogicalModel->mText.Count() && "Invalid line info" );
1937 // Whether there is a hit on a glyph.
1938 bool matched = false;
1940 // Traverses glyphs in visual order. To do that use the visual to logical conversion table.
1941 CharacterIndex visualIndex = startCharacter;
1942 Length numberOfCharacters = 0u;
1943 for( ; !matched && ( visualIndex < endCharacter ); ++visualIndex )
1945 // The character in logical order.
1946 const CharacterIndex characterLogicalOrderIndex = hasRightToLeftCharacters ? *( visualToLogicalBuffer + visualIndex ) : visualIndex;
1948 // Get the script of the character.
1949 const Script script = mLogicalModel->GetScript( characterLogicalOrderIndex );
1951 // The number of glyphs for that character
1952 const Length numberOfGlyphs = *( glyphsPerCharacterBuffer + characterLogicalOrderIndex );
1953 ++numberOfCharacters;
1956 if( 0u != numberOfGlyphs )
1958 // Get the first character/glyph of the group of glyphs.
1959 const CharacterIndex firstVisualCharacterIndex = 1u + visualIndex - numberOfCharacters;
1960 const CharacterIndex firstLogicalCharacterIndex = hasRightToLeftCharacters ? *( visualToLogicalBuffer + firstVisualCharacterIndex ) : firstVisualCharacterIndex;
1961 const GlyphIndex firstLogicalGlyphIndex = *( charactersToGlyphBuffer + firstLogicalCharacterIndex );
1963 // Get the metrics for the group of glyphs.
1964 GlyphMetrics glyphMetrics;
1965 GetGlyphsMetrics( firstLogicalGlyphIndex,
1971 // Get the position of the first glyph.
1972 const Vector2& position = *( positionsBuffer + firstLogicalGlyphIndex );
1974 // Whether the glyph can be split, like Latin ligatures fi, ff or Arabic ﻻ.
1975 const bool isInterglyphIndex = ( numberOfCharacters > numberOfGlyphs ) && HasLigatureMustBreak( script );
1976 const Length numberOfBlocks = isInterglyphIndex ? numberOfCharacters : 1u;
1977 const float glyphAdvance = glyphMetrics.advance / static_cast<float>( numberOfBlocks );
1979 GlyphIndex index = 0u;
1980 for( ; !matched && ( index < numberOfBlocks ); ++index )
1982 // Find the mid-point of the area containing the glyph
1983 const float glyphCenter = -glyphMetrics.xBearing + position.x + ( static_cast<float>( index ) + 0.5f ) * glyphAdvance;
1985 if( visualX < glyphCenter )
1994 visualIndex = firstVisualCharacterIndex + index;
1998 numberOfCharacters = 0u;
2004 // Return the logical position of the cursor in characters.
2008 visualIndex = endCharacter;
2011 logicalIndex = hasRightToLeftCharacters ? *( visualToLogicalCursorBuffer + visualIndex ) : visualIndex;
2012 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "%p closest visualIndex %d logicalIndex %d\n", this, visualIndex, logicalIndex );
2014 DALI_ASSERT_DEBUG( ( logicalIndex <= mLogicalModel->mText.Count() && logicalIndex >= 0 ) && "GetClosestCursorIndex - Out of bounds index" );
2016 return logicalIndex;
2019 void Controller::Impl::GetCursorPosition( CharacterIndex logical,
2020 CursorInfo& cursorInfo )
2022 // TODO: Check for multiline with \n, etc...
2024 const Length numberOfCharacters = mLogicalModel->mText.Count();
2025 if( !IsShowingRealText() )
2027 // Do not want to use the place-holder text to set the cursor position.
2029 // Use the line's height of the font's family set to set the cursor's size.
2030 // If there is no font's family set, use the default font.
2031 // Use the current alignment to place the cursor at the beginning, center or end of the box.
2033 cursorInfo.lineOffset = 0.f;
2034 cursorInfo.lineHeight = GetDefaultFontLineHeight();
2035 cursorInfo.primaryCursorHeight = cursorInfo.lineHeight;
2037 switch( mLayoutEngine.GetHorizontalAlignment() )
2039 case LayoutEngine::HORIZONTAL_ALIGN_BEGIN:
2041 cursorInfo.primaryPosition.x = 0.f;
2044 case LayoutEngine::HORIZONTAL_ALIGN_CENTER:
2046 cursorInfo.primaryPosition.x = floorf( 0.5f * mVisualModel->mControlSize.width );
2049 case LayoutEngine::HORIZONTAL_ALIGN_END:
2051 cursorInfo.primaryPosition.x = mVisualModel->mControlSize.width - mEventData->mDecorator->GetCursorWidth();
2056 switch( mLayoutEngine.GetVerticalAlignment() )
2058 case LayoutEngine::VERTICAL_ALIGN_TOP:
2060 cursorInfo.primaryPosition.y = 0.f;
2063 case LayoutEngine::VERTICAL_ALIGN_CENTER:
2065 cursorInfo.primaryPosition.y = floorf( 0.5f * ( mVisualModel->mControlSize.height - cursorInfo.lineHeight ) );
2068 case LayoutEngine::VERTICAL_ALIGN_BOTTOM:
2070 cursorInfo.primaryPosition.y = mVisualModel->mControlSize.height - cursorInfo.lineHeight;
2075 // Nothing else to do.
2079 // Check if the logical position is the first or the last one of the text.
2080 const bool isFirstPosition = 0u == logical;
2081 const bool isLastPosition = numberOfCharacters == logical;
2083 // 'logical' is the logical 'cursor' index.
2084 // Get the next and current logical 'character' index.
2085 const CharacterIndex nextCharacterIndex = logical;
2086 const CharacterIndex characterIndex = isFirstPosition ? logical : logical - 1u;
2088 // Get the direction of the character and the next one.
2089 const CharacterDirection* const modelCharacterDirectionsBuffer = ( 0u != mLogicalModel->mCharacterDirections.Count() ) ? mLogicalModel->mCharacterDirections.Begin() : NULL;
2091 CharacterDirection isCurrentRightToLeft = false;
2092 CharacterDirection isNextRightToLeft = false;
2093 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
2095 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + characterIndex );
2096 isNextRightToLeft = *( modelCharacterDirectionsBuffer + nextCharacterIndex );
2099 // Get the line where the character is laid-out.
2100 const LineRun* const modelLines = mVisualModel->mLines.Begin();
2102 const LineIndex lineIndex = mVisualModel->GetLineOfCharacter( characterIndex );
2103 const LineRun& line = *( modelLines + lineIndex );
2105 // Get the paragraph's direction.
2106 const CharacterDirection isRightToLeftParagraph = line.direction;
2108 // Check whether there is an alternative position:
2110 cursorInfo.isSecondaryCursor = ( !isLastPosition && ( isCurrentRightToLeft != isNextRightToLeft ) ) ||
2111 ( isLastPosition && ( isRightToLeftParagraph != isCurrentRightToLeft ) );
2113 // Set the line offset and height.
2114 cursorInfo.lineOffset = 0.f;
2115 cursorInfo.lineHeight = line.ascender + -line.descender;
2117 // Calculate the primary cursor.
2119 CharacterIndex index = characterIndex;
2120 if( cursorInfo.isSecondaryCursor )
2122 // If there is a secondary position, the primary cursor may be in a different place than the logical index.
2124 if( isLastPosition )
2126 // The position of the cursor after the last character needs special
2127 // care depending on its direction and the direction of the paragraph.
2129 // Need to find the first character after the last character with the paragraph's direction.
2130 // i.e l0 l1 l2 r0 r1 should find r0.
2132 // TODO: check for more than one line!
2133 index = isRightToLeftParagraph ? line.characterRun.characterIndex : line.characterRun.characterIndex + line.characterRun.numberOfCharacters - 1u;
2134 index = mLogicalModel->GetLogicalCharacterIndex( index );
2138 index = ( isRightToLeftParagraph == isCurrentRightToLeft ) ? characterIndex : nextCharacterIndex;
2142 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
2143 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
2144 const Length* const charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
2145 const CharacterIndex* const glyphsToCharactersBuffer = mVisualModel->mGlyphsToCharacters.Begin();
2146 const Vector2* const glyphPositionsBuffer = mVisualModel->mGlyphPositions.Begin();
2147 const GlyphInfo* const glyphInfoBuffer = mVisualModel->mGlyphs.Begin();
2149 // Convert the cursor position into the glyph position.
2150 const GlyphIndex primaryGlyphIndex = *( charactersToGlyphBuffer + index );
2151 const Length primaryNumberOfGlyphs = *( glyphsPerCharacterBuffer + index );
2152 const Length primaryNumberOfCharacters = *( charactersPerGlyphBuffer + primaryGlyphIndex );
2154 // Get the metrics for the group of glyphs.
2155 GlyphMetrics glyphMetrics;
2156 GetGlyphsMetrics( primaryGlyphIndex,
2157 primaryNumberOfGlyphs,
2162 // Whether to add the glyph's advance to the cursor position.
2163 // i.e if the paragraph is left to right and the logical cursor is zero, the position is the position of the first glyph and the advance is not added,
2164 // if the logical cursor is one, the position is the position of the first glyph and the advance is added.
2165 // A 'truth table' was build and an online Karnaugh map tool was used to simplify the logic.
2186 // Where F -> isFirstPosition
2187 // L -> isLastPosition
2188 // C -> isCurrentRightToLeft
2189 // P -> isRightToLeftParagraph
2190 // A -> Whether to add the glyph's advance.
2192 const bool addGlyphAdvance = ( ( isLastPosition && !isRightToLeftParagraph ) ||
2193 ( isFirstPosition && isRightToLeftParagraph ) ||
2194 ( !isFirstPosition && !isLastPosition && !isCurrentRightToLeft ) );
2196 float glyphAdvance = addGlyphAdvance ? glyphMetrics.advance : 0.f;
2198 if( !isLastPosition &&
2199 ( primaryNumberOfCharacters > 1u ) )
2201 const CharacterIndex firstIndex = *( glyphsToCharactersBuffer + primaryGlyphIndex );
2203 bool isCurrentRightToLeft = false;
2204 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
2206 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + index );
2209 Length numberOfGlyphAdvance = ( isFirstPosition ? 0u : 1u ) + characterIndex - firstIndex;
2210 if( isCurrentRightToLeft )
2212 numberOfGlyphAdvance = primaryNumberOfCharacters - numberOfGlyphAdvance;
2215 glyphAdvance = static_cast<float>( numberOfGlyphAdvance ) * glyphMetrics.advance / static_cast<float>( primaryNumberOfCharacters );
2218 // Get the glyph position and x bearing.
2219 const Vector2& primaryPosition = *( glyphPositionsBuffer + primaryGlyphIndex );
2221 // Set the primary cursor's height.
2222 cursorInfo.primaryCursorHeight = cursorInfo.isSecondaryCursor ? 0.5f * glyphMetrics.fontHeight : glyphMetrics.fontHeight;
2224 // Set the primary cursor's position.
2225 cursorInfo.primaryPosition.x = -glyphMetrics.xBearing + primaryPosition.x + glyphAdvance;
2226 cursorInfo.primaryPosition.y = line.ascender - glyphMetrics.ascender;
2228 // Calculate the secondary cursor.
2230 if( cursorInfo.isSecondaryCursor )
2232 // Set the secondary cursor's height.
2233 cursorInfo.secondaryCursorHeight = 0.5f * glyphMetrics.fontHeight;
2235 CharacterIndex index = characterIndex;
2236 if( !isLastPosition )
2238 index = ( isRightToLeftParagraph == isCurrentRightToLeft ) ? nextCharacterIndex : characterIndex;
2241 const GlyphIndex secondaryGlyphIndex = *( charactersToGlyphBuffer + index );
2242 const Length secondaryNumberOfGlyphs = *( glyphsPerCharacterBuffer + index );
2244 const Vector2& secondaryPosition = *( glyphPositionsBuffer + secondaryGlyphIndex );
2246 GetGlyphsMetrics( secondaryGlyphIndex,
2247 secondaryNumberOfGlyphs,
2252 // Set the secondary cursor's position.
2253 cursorInfo.secondaryPosition.x = -glyphMetrics.xBearing + secondaryPosition.x + ( isCurrentRightToLeft ? 0.f : glyphMetrics.advance );
2254 cursorInfo.secondaryPosition.y = cursorInfo.lineHeight - cursorInfo.secondaryCursorHeight - line.descender - ( glyphMetrics.fontHeight - glyphMetrics.ascender );
2257 if( LayoutEngine::MULTI_LINE_BOX == mLayoutEngine.GetLayout() )
2259 // If the text is editable and multi-line, the cursor position after a white space shouldn't exceed the boundaries of the text control.
2261 // Note the white spaces laid-out at the end of the line might exceed the boundaries of the control.
2262 // The reason is a wrapped line must not start with a white space so they are laid-out at the end of the line.
2264 if( 0.f > cursorInfo.primaryPosition.x )
2266 cursorInfo.primaryPosition.x = 0.f;
2269 const float edgeWidth = mVisualModel->mControlSize.width - static_cast<float>( mEventData->mDecorator->GetCursorWidth() );
2270 if( cursorInfo.primaryPosition.x > edgeWidth )
2272 cursorInfo.primaryPosition.x = edgeWidth;
2277 CharacterIndex Controller::Impl::CalculateNewCursorIndex( CharacterIndex index ) const
2279 if( NULL == mEventData )
2281 // Nothing to do if there is no text input.
2285 CharacterIndex cursorIndex = mEventData->mPrimaryCursorPosition;
2287 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
2288 const Length* const charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
2290 GlyphIndex glyphIndex = *( charactersToGlyphBuffer + index );
2291 Length numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
2293 if( numberOfCharacters > 1u )
2295 const Script script = mLogicalModel->GetScript( index );
2296 if( HasLigatureMustBreak( script ) )
2298 // Prevents to jump the whole Latin ligatures like fi, ff, or Arabic ﻻ, ...
2299 numberOfCharacters = 1u;
2304 while( 0u == numberOfCharacters )
2307 numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
2311 if( index < mEventData->mPrimaryCursorPosition )
2313 cursorIndex -= numberOfCharacters;
2317 cursorIndex += numberOfCharacters;
2323 void Controller::Impl::UpdateCursorPosition( const CursorInfo& cursorInfo )
2325 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "-->Controller::UpdateCursorPosition %p\n", this );
2326 if( NULL == mEventData )
2328 // Nothing to do if there is no text input.
2329 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition no event data\n" );
2333 const Vector2 offset = mEventData->mScrollPosition + ( IsShowingRealText() ? mAlignmentOffset : Vector2::ZERO );
2334 const Vector2 cursorPosition = cursorInfo.primaryPosition + offset;
2336 // Sets the cursor position.
2337 mEventData->mDecorator->SetPosition( PRIMARY_CURSOR,
2340 cursorInfo.primaryCursorHeight,
2341 cursorInfo.lineHeight );
2342 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Primary cursor position: %f,%f\n", cursorPosition.x, cursorPosition.y );
2344 // Sets the grab handle position.
2345 mEventData->mDecorator->SetPosition( GRAB_HANDLE,
2347 cursorInfo.lineOffset + offset.y,
2348 cursorInfo.lineHeight );
2350 if( cursorInfo.isSecondaryCursor )
2352 mEventData->mDecorator->SetPosition( SECONDARY_CURSOR,
2353 cursorInfo.secondaryPosition.x + offset.x,
2354 cursorInfo.secondaryPosition.y + offset.y,
2355 cursorInfo.secondaryCursorHeight,
2356 cursorInfo.lineHeight );
2357 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Secondary cursor position: %f,%f\n", cursorInfo.secondaryPosition.x + offset.x, cursorInfo.secondaryPosition.y + offset.y );
2360 // Set which cursors are active according the state.
2361 if( EventData::IsEditingState( mEventData->mState ) || ( EventData::GRAB_HANDLE_PANNING == mEventData->mState ) )
2363 if( cursorInfo.isSecondaryCursor )
2365 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_BOTH );
2369 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
2374 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
2377 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition\n" );
2380 void Controller::Impl::UpdateSelectionHandle( HandleType handleType,
2381 const CursorInfo& cursorInfo )
2383 if( ( LEFT_SELECTION_HANDLE != handleType ) &&
2384 ( RIGHT_SELECTION_HANDLE != handleType ) )
2389 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
2390 const Vector2 cursorPosition = cursorInfo.primaryPosition + offset;
2392 // Sets the handle's position.
2393 mEventData->mDecorator->SetPosition( handleType,
2395 cursorInfo.lineOffset + offset.y,
2396 cursorInfo.lineHeight );
2398 // If selection handle at start of the text and other at end of the text then all text is selected.
2399 const CharacterIndex startOfSelection = std::min( mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition );
2400 const CharacterIndex endOfSelection = std::max ( mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition );
2401 mEventData->mAllTextSelected = ( startOfSelection == 0 ) && ( endOfSelection == mLogicalModel->mText.Count() );
2404 void Controller::Impl::ClampHorizontalScroll( const Vector2& actualSize )
2406 // Clamp between -space & 0 (and the text alignment).
2408 if( actualSize.width > mVisualModel->mControlSize.width )
2410 const float space = ( actualSize.width - mVisualModel->mControlSize.width ) + mAlignmentOffset.x;
2411 mEventData->mScrollPosition.x = ( mEventData->mScrollPosition.x < -space ) ? -space : mEventData->mScrollPosition.x;
2412 mEventData->mScrollPosition.x = ( mEventData->mScrollPosition.x > -mAlignmentOffset.x ) ? -mAlignmentOffset.x : mEventData->mScrollPosition.x;
2414 mEventData->mDecoratorUpdated = true;
2418 mEventData->mScrollPosition.x = 0.f;
2422 void Controller::Impl::ClampVerticalScroll( const Vector2& actualSize )
2424 // Clamp between -space & 0 (and the text alignment).
2425 if( actualSize.height > mVisualModel->mControlSize.height )
2427 const float space = ( actualSize.height - mVisualModel->mControlSize.height ) + mAlignmentOffset.y;
2428 mEventData->mScrollPosition.y = ( mEventData->mScrollPosition.y < -space ) ? -space : mEventData->mScrollPosition.y;
2429 mEventData->mScrollPosition.y = ( mEventData->mScrollPosition.y > -mAlignmentOffset.y ) ? -mAlignmentOffset.y : mEventData->mScrollPosition.y;
2431 mEventData->mDecoratorUpdated = true;
2435 mEventData->mScrollPosition.y = 0.f;
2439 void Controller::Impl::ScrollToMakePositionVisible( const Vector2& position )
2441 // position is in actor's coords.
2442 const float positionEnd = position.x + ( mEventData->mDecorator ? mEventData->mDecorator->GetCursorWidth() : 0.f );
2444 // Transform the position to decorator coords.
2445 const float alignment = IsShowingRealText() ? mAlignmentOffset.x : 0.f;
2446 const float offset = mEventData->mScrollPosition.x + alignment;
2447 const float decoratorPositionBegin = position.x + offset;
2448 const float decoratorPositionEnd = positionEnd + offset;
2450 if( decoratorPositionBegin < 0.f )
2452 mEventData->mScrollPosition.x = -position.x - alignment;
2454 else if( decoratorPositionEnd > mVisualModel->mControlSize.width )
2456 mEventData->mScrollPosition.x = mVisualModel->mControlSize.width - positionEnd - alignment;
2460 void Controller::Impl::ScrollTextToMatchCursor( const CursorInfo& cursorInfo )
2462 // Get the current cursor position in decorator coords.
2463 const Vector2& currentCursorPosition = mEventData->mDecorator->GetPosition( PRIMARY_CURSOR );
2465 // Calculate the offset to match the cursor position before the character was deleted.
2466 mEventData->mScrollPosition.x = currentCursorPosition.x - cursorInfo.primaryPosition.x - mAlignmentOffset.x;
2468 ClampHorizontalScroll( mVisualModel->GetLayoutSize() );
2471 void Controller::Impl::RequestRelayout()
2473 mControlInterface.RequestTextRelayout();
2478 } // namespace Toolkit