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;
342 mTextUpdateInfo.mRequestedNumberOfCharacters = numberOfCharacters + mTextUpdateInfo.mNumberOfCharactersToAdd - mTextUpdateInfo.mNumberOfCharactersToRemove;
343 mTextUpdateInfo.mStartGlyphIndex = *( mVisualModel->mCharactersToGlyph.Begin() + mTextUpdateInfo.mParagraphCharacterIndex );
346 void Controller::Impl::ClearFullModelData( OperationsMask operations )
348 if( GET_LINE_BREAKS & operations )
350 mLogicalModel->mLineBreakInfo.Clear();
351 mLogicalModel->mParagraphInfo.Clear();
354 if( GET_WORD_BREAKS & operations )
356 mLogicalModel->mLineBreakInfo.Clear();
359 if( GET_SCRIPTS & operations )
361 mLogicalModel->mScriptRuns.Clear();
364 if( VALIDATE_FONTS & operations )
366 mLogicalModel->mFontRuns.Clear();
369 if( 0u != mLogicalModel->mBidirectionalParagraphInfo.Count() )
371 if( BIDI_INFO & operations )
373 mLogicalModel->mBidirectionalParagraphInfo.Clear();
374 mLogicalModel->mCharacterDirections.Clear();
377 if( REORDER & operations )
379 // Free the allocated memory used to store the conversion table in the bidirectional line info run.
380 for( Vector<BidirectionalLineInfoRun>::Iterator it = mLogicalModel->mBidirectionalLineInfo.Begin(),
381 endIt = mLogicalModel->mBidirectionalLineInfo.End();
385 BidirectionalLineInfoRun& bidiLineInfo = *it;
387 free( bidiLineInfo.visualToLogicalMap );
388 bidiLineInfo.visualToLogicalMap = NULL;
390 mLogicalModel->mBidirectionalLineInfo.Clear();
392 mLogicalModel->mLogicalToVisualMap.Clear();
393 mLogicalModel->mVisualToLogicalMap.Clear();
397 if( SHAPE_TEXT & operations )
399 mVisualModel->mGlyphs.Clear();
400 mVisualModel->mGlyphsToCharacters.Clear();
401 mVisualModel->mCharactersToGlyph.Clear();
402 mVisualModel->mCharactersPerGlyph.Clear();
403 mVisualModel->mGlyphsPerCharacter.Clear();
404 mVisualModel->mGlyphPositions.Clear();
407 if( LAYOUT & operations )
409 mVisualModel->mLines.Clear();
412 if( COLOR & operations )
414 mVisualModel->mColorIndices.Clear();
418 void Controller::Impl::ClearCharacterModelData( CharacterIndex startIndex, CharacterIndex endIndex, OperationsMask operations )
420 const CharacterIndex endIndexPlusOne = endIndex + 1u;
422 if( GET_LINE_BREAKS & operations )
424 // Clear the line break info.
425 LineBreakInfo* lineBreakInfoBuffer = mLogicalModel->mLineBreakInfo.Begin();
427 mLogicalModel->mLineBreakInfo.Erase( lineBreakInfoBuffer + startIndex,
428 lineBreakInfoBuffer + endIndexPlusOne );
430 // Clear the paragraphs.
431 ClearCharacterRuns( startIndex,
433 mLogicalModel->mParagraphInfo );
436 if( GET_WORD_BREAKS & operations )
438 // Clear the word break info.
439 WordBreakInfo* wordBreakInfoBuffer = mLogicalModel->mWordBreakInfo.Begin();
441 mLogicalModel->mWordBreakInfo.Erase( wordBreakInfoBuffer + startIndex,
442 wordBreakInfoBuffer + endIndexPlusOne );
445 if( GET_SCRIPTS & operations )
447 // Clear the scripts.
448 ClearCharacterRuns( startIndex,
450 mLogicalModel->mScriptRuns );
453 if( VALIDATE_FONTS & operations )
456 ClearCharacterRuns( startIndex,
458 mLogicalModel->mFontRuns );
461 if( 0u != mLogicalModel->mBidirectionalParagraphInfo.Count() )
463 if( BIDI_INFO & operations )
465 // Clear the bidirectional paragraph info.
466 ClearCharacterRuns( startIndex,
468 mLogicalModel->mBidirectionalParagraphInfo );
470 // Clear the character's directions.
471 CharacterDirection* characterDirectionsBuffer = mLogicalModel->mCharacterDirections.Begin();
473 mLogicalModel->mCharacterDirections.Erase( characterDirectionsBuffer + startIndex,
474 characterDirectionsBuffer + endIndexPlusOne );
477 if( REORDER & operations )
479 uint32_t startRemoveIndex = mLogicalModel->mBidirectionalLineInfo.Count();
480 uint32_t endRemoveIndex = startRemoveIndex;
481 ClearCharacterRuns( startIndex,
483 mLogicalModel->mBidirectionalLineInfo,
487 BidirectionalLineInfoRun* bidirectionalLineInfoBuffer = mLogicalModel->mBidirectionalLineInfo.Begin();
489 // Free the allocated memory used to store the conversion table in the bidirectional line info run.
490 for( Vector<BidirectionalLineInfoRun>::Iterator it = bidirectionalLineInfoBuffer + startRemoveIndex,
491 endIt = bidirectionalLineInfoBuffer + endRemoveIndex;
495 BidirectionalLineInfoRun& bidiLineInfo = *it;
497 free( bidiLineInfo.visualToLogicalMap );
498 bidiLineInfo.visualToLogicalMap = NULL;
501 mLogicalModel->mBidirectionalLineInfo.Erase( bidirectionalLineInfoBuffer + startRemoveIndex,
502 bidirectionalLineInfoBuffer + endRemoveIndex );
504 // Clear the logical to visual and the visual to logical conversion tables.
505 CharacterIndex* logicalToVisualMapBuffer = mLogicalModel->mLogicalToVisualMap.Begin();
506 mLogicalModel->mLogicalToVisualMap.Erase( logicalToVisualMapBuffer + startIndex,
507 logicalToVisualMapBuffer + endIndexPlusOne );
509 CharacterIndex* visualToLogicalMapBuffer = mLogicalModel->mVisualToLogicalMap.Begin();
510 mLogicalModel->mVisualToLogicalMap.Erase( visualToLogicalMapBuffer + startIndex,
511 visualToLogicalMapBuffer + endIndexPlusOne );
516 void Controller::Impl::ClearGlyphModelData( CharacterIndex startIndex, CharacterIndex endIndex, OperationsMask operations )
518 const CharacterIndex endIndexPlusOne = endIndex + 1u;
519 const Length numberOfCharactersRemoved = endIndexPlusOne - startIndex;
521 // Convert the character index to glyph index before deleting the character to glyph and the glyphs per character buffers.
522 GlyphIndex* charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
523 Length* glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
525 const GlyphIndex endGlyphIndexPlusOne = *( charactersToGlyphBuffer + endIndex ) + *( glyphsPerCharacterBuffer + endIndex );
526 const Length numberOfGlyphsRemoved = endGlyphIndexPlusOne - mTextUpdateInfo.mStartGlyphIndex;
528 if( SHAPE_TEXT & operations )
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 );
580 if( LAYOUT & operations )
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 startRemoveIndex = mVisualModel->mLines.Count();
593 endRemoveIndex = startRemoveIndex;
594 ClearGlyphRuns( mTextUpdateInfo.mStartGlyphIndex,
595 endGlyphIndexPlusOne - 1u,
596 mVisualModel->mLines,
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 );
608 if( COLOR & operations )
610 if( 0u != mVisualModel->mColorIndices.Count() )
612 ColorIndex* colorIndexBuffer = mVisualModel->mColorIndices.Begin();
613 mVisualModel->mColorIndices.Erase( colorIndexBuffer + mTextUpdateInfo.mStartGlyphIndex,
614 colorIndexBuffer + endGlyphIndexPlusOne );
619 void Controller::Impl::ClearModelData( CharacterIndex startIndex, CharacterIndex endIndex, OperationsMask operations )
621 if( mTextUpdateInfo.mClearAll ||
622 ( ( 0u == startIndex ) &&
623 ( mTextUpdateInfo.mPreviousNumberOfCharacters == endIndex + 1u ) ) )
625 ClearFullModelData( operations );
629 // Clear the model data related with characters.
630 ClearCharacterModelData( startIndex, endIndex, operations );
632 // Clear the model data related with glyphs.
633 ClearGlyphModelData( startIndex, endIndex, operations );
636 // The estimated number of lines. Used to avoid reallocations when layouting.
637 mTextUpdateInfo.mEstimatedNumberOfLines = std::max( mVisualModel->mLines.Count(), mLogicalModel->mParagraphInfo.Count() );
639 mVisualModel->ClearCaches();
642 bool Controller::Impl::UpdateModel( OperationsMask operationsRequired )
644 DALI_LOG_INFO( gLogFilter, Debug::General, "Controller::UpdateModel\n" );
646 // Calculate the operations to be done.
647 const OperationsMask operations = static_cast<OperationsMask>( mOperationsPending & operationsRequired );
649 if( NO_OPERATION == operations )
651 // Nothing to do if no operations are pending and required.
655 Vector<Character>& utf32Characters = mLogicalModel->mText;
657 const Length numberOfCharacters = utf32Characters.Count();
659 // Index to the first character of the first paragraph to be updated.
660 CharacterIndex startIndex = 0u;
661 // Number of characters of the paragraphs to be removed.
662 Length paragraphCharacters = 0u;
664 CalculateTextUpdateIndices( paragraphCharacters );
665 startIndex = mTextUpdateInfo.mParagraphCharacterIndex;
667 if( mTextUpdateInfo.mClearAll ||
668 ( 0u != paragraphCharacters ) )
670 ClearModelData( startIndex, startIndex + ( ( paragraphCharacters > 0u ) ? paragraphCharacters - 1u : 0u ), operationsRequired );
673 mTextUpdateInfo.mClearAll = false;
675 // Whether the model is updated.
676 bool updated = false;
678 Vector<LineBreakInfo>& lineBreakInfo = mLogicalModel->mLineBreakInfo;
679 const Length requestedNumberOfCharacters = mTextUpdateInfo.mRequestedNumberOfCharacters;
681 if( GET_LINE_BREAKS & operations )
683 // Retrieves the line break info. The line break info is used to split the text in 'paragraphs' to
684 // calculate the bidirectional info for each 'paragraph'.
685 // It's also used to layout the text (where it should be a new line) or to shape the text (text in different lines
686 // is not shaped together).
687 lineBreakInfo.Resize( numberOfCharacters, TextAbstraction::LINE_NO_BREAK );
689 SetLineBreakInfo( utf32Characters,
691 requestedNumberOfCharacters,
694 // Create the paragraph info.
695 mLogicalModel->CreateParagraphInfo( startIndex,
696 requestedNumberOfCharacters );
700 Vector<WordBreakInfo>& wordBreakInfo = mLogicalModel->mWordBreakInfo;
701 if( GET_WORD_BREAKS & operations )
703 // Retrieves the word break info. The word break info is used to layout the text (where to wrap the text in lines).
704 wordBreakInfo.Resize( numberOfCharacters, TextAbstraction::WORD_NO_BREAK );
706 SetWordBreakInfo( utf32Characters,
708 requestedNumberOfCharacters,
713 const bool getScripts = GET_SCRIPTS & operations;
714 const bool validateFonts = VALIDATE_FONTS & operations;
716 Vector<ScriptRun>& scripts = mLogicalModel->mScriptRuns;
717 Vector<FontRun>& validFonts = mLogicalModel->mFontRuns;
719 if( getScripts || validateFonts )
721 // Validates the fonts assigned by the application or assigns default ones.
722 // It makes sure all the characters are going to be rendered by the correct font.
723 MultilanguageSupport multilanguageSupport = MultilanguageSupport::Get();
727 // Retrieves the scripts used in the text.
728 multilanguageSupport.SetScripts( utf32Characters,
730 requestedNumberOfCharacters,
736 // Validate the fonts set through the mark-up string.
737 Vector<FontDescriptionRun>& fontDescriptionRuns = mLogicalModel->mFontDescriptionRuns;
739 // Get the default font id.
740 const FontId defaultFontId = ( NULL == mFontDefaults ) ? 0u : mFontDefaults->GetFontId( mFontClient );
742 // Validates the fonts. If there is a character with no assigned font it sets a default one.
743 // After this call, fonts are validated.
744 multilanguageSupport.ValidateFonts( utf32Characters,
749 requestedNumberOfCharacters,
755 Vector<Character> mirroredUtf32Characters;
756 bool textMirrored = false;
757 const Length numberOfParagraphs = mLogicalModel->mParagraphInfo.Count();
758 if( BIDI_INFO & operations )
760 Vector<BidirectionalParagraphInfoRun>& bidirectionalInfo = mLogicalModel->mBidirectionalParagraphInfo;
761 bidirectionalInfo.Reserve( numberOfParagraphs );
763 // Calculates the bidirectional info for the whole paragraph if it contains right to left scripts.
764 SetBidirectionalInfo( utf32Characters,
768 requestedNumberOfCharacters,
771 if( 0u != bidirectionalInfo.Count() )
773 // Only set the character directions if there is right to left characters.
774 Vector<CharacterDirection>& directions = mLogicalModel->mCharacterDirections;
775 GetCharactersDirection( bidirectionalInfo,
778 requestedNumberOfCharacters,
781 // This paragraph has right to left text. Some characters may need to be mirrored.
782 // TODO: consider if the mirrored string can be stored as well.
784 textMirrored = GetMirroredText( utf32Characters,
788 requestedNumberOfCharacters,
789 mirroredUtf32Characters );
793 // There is no right to left characters. Clear the directions vector.
794 mLogicalModel->mCharacterDirections.Clear();
799 Vector<GlyphInfo>& glyphs = mVisualModel->mGlyphs;
800 Vector<CharacterIndex>& glyphsToCharactersMap = mVisualModel->mGlyphsToCharacters;
801 Vector<Length>& charactersPerGlyph = mVisualModel->mCharactersPerGlyph;
802 Vector<GlyphIndex> newParagraphGlyphs;
803 newParagraphGlyphs.Reserve( numberOfParagraphs );
805 const Length currentNumberOfGlyphs = glyphs.Count();
806 if( SHAPE_TEXT & operations )
808 const Vector<Character>& textToShape = textMirrored ? mirroredUtf32Characters : utf32Characters;
810 ShapeText( textToShape,
815 mTextUpdateInfo.mStartGlyphIndex,
816 requestedNumberOfCharacters,
818 glyphsToCharactersMap,
820 newParagraphGlyphs );
822 // Create the 'number of glyphs' per character and the glyph to character conversion tables.
823 mVisualModel->CreateGlyphsPerCharacterTable( startIndex, mTextUpdateInfo.mStartGlyphIndex, requestedNumberOfCharacters );
824 mVisualModel->CreateCharacterToGlyphTable( startIndex, mTextUpdateInfo.mStartGlyphIndex, requestedNumberOfCharacters );
828 const Length numberOfGlyphs = glyphs.Count() - currentNumberOfGlyphs;
830 if( GET_GLYPH_METRICS & operations )
832 GlyphInfo* glyphsBuffer = glyphs.Begin();
833 mMetrics->GetGlyphMetrics( glyphsBuffer + mTextUpdateInfo.mStartGlyphIndex, numberOfGlyphs );
835 // Update the width and advance of all new paragraph characters.
836 for( Vector<GlyphIndex>::ConstIterator it = newParagraphGlyphs.Begin(), endIt = newParagraphGlyphs.End(); it != endIt; ++it )
838 const GlyphIndex index = *it;
839 GlyphInfo& glyph = *( glyphsBuffer + index );
841 glyph.xBearing = 0.f;
848 if( COLOR & operationsRequired )
850 // Set the color runs in glyphs.
851 SetColorSegmentationInfo( mLogicalModel->mColorRuns,
852 mVisualModel->mCharactersToGlyph,
853 mVisualModel->mGlyphsPerCharacter,
855 mTextUpdateInfo.mStartGlyphIndex,
856 requestedNumberOfCharacters,
857 mVisualModel->mColors,
858 mVisualModel->mColorIndices );
863 if( ( NULL != mEventData ) &&
864 mEventData->mPreEditFlag &&
865 ( 0u != mVisualModel->mCharactersToGlyph.Count() ) )
867 // Add the underline for the pre-edit text.
868 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
869 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
871 const GlyphIndex glyphStart = *( charactersToGlyphBuffer + mEventData->mPreEditStartPosition );
872 const CharacterIndex lastPreEditCharacter = mEventData->mPreEditStartPosition + ( ( mEventData->mPreEditLength > 0u ) ? mEventData->mPreEditLength - 1u : 0u );
873 const Length numberOfGlyphsLastCharacter = *( glyphsPerCharacterBuffer + lastPreEditCharacter );
874 const GlyphIndex glyphEnd = *( charactersToGlyphBuffer + lastPreEditCharacter ) + ( numberOfGlyphsLastCharacter > 1u ? numberOfGlyphsLastCharacter - 1u : 0u );
876 GlyphRun underlineRun;
877 underlineRun.glyphIndex = glyphStart;
878 underlineRun.numberOfGlyphs = 1u + glyphEnd - glyphStart;
880 // TODO: At the moment the underline runs are only for pre-edit.
881 mVisualModel->mUnderlineRuns.PushBack( underlineRun );
884 // The estimated number of lines. Used to avoid reallocations when layouting.
885 mTextUpdateInfo.mEstimatedNumberOfLines = std::max( mVisualModel->mLines.Count(), mLogicalModel->mParagraphInfo.Count() );
887 // Set the previous number of characters for the next time the text is updated.
888 mTextUpdateInfo.mPreviousNumberOfCharacters = numberOfCharacters;
893 void Controller::Impl::RetrieveDefaultInputStyle( InputStyle& inputStyle )
895 // Sets the default text's color.
896 inputStyle.textColor = mTextColor;
897 inputStyle.isDefaultColor = true;
899 // Sets the default font's family name, weight, width, slant and size.
902 inputStyle.familyName = mFontDefaults->mFontDescription.family;
903 inputStyle.weight = mFontDefaults->mFontDescription.weight;
904 inputStyle.width = mFontDefaults->mFontDescription.width;
905 inputStyle.slant = mFontDefaults->mFontDescription.slant;
906 inputStyle.size = mFontDefaults->mDefaultPointSize;
908 inputStyle.familyDefined = mFontDefaults->familyDefined;
909 inputStyle.weightDefined = mFontDefaults->weightDefined;
910 inputStyle.widthDefined = mFontDefaults->widthDefined;
911 inputStyle.slantDefined = mFontDefaults->slantDefined;
912 inputStyle.sizeDefined = mFontDefaults->sizeDefined;
916 inputStyle.familyName.clear();
917 inputStyle.weight = TextAbstraction::FontWeight::NORMAL;
918 inputStyle.width = TextAbstraction::FontWidth::NORMAL;
919 inputStyle.slant = TextAbstraction::FontSlant::NORMAL;
920 inputStyle.size = 0.f;
922 inputStyle.familyDefined = false;
923 inputStyle.weightDefined = false;
924 inputStyle.widthDefined = false;
925 inputStyle.slantDefined = false;
926 inputStyle.sizeDefined = false;
930 float Controller::Impl::GetDefaultFontLineHeight()
932 FontId defaultFontId = 0u;
933 if( NULL == mFontDefaults )
935 TextAbstraction::FontDescription fontDescription;
936 defaultFontId = mFontClient.GetFontId( fontDescription );
940 defaultFontId = mFontDefaults->GetFontId( mFontClient );
943 Text::FontMetrics fontMetrics;
944 mMetrics->GetFontMetrics( defaultFontId, fontMetrics );
946 return( fontMetrics.ascender - fontMetrics.descender );
949 void Controller::Impl::OnCursorKeyEvent( const Event& event )
951 if( NULL == mEventData )
953 // Nothing to do if there is no text input.
957 int keyCode = event.p1.mInt;
959 if( Dali::DALI_KEY_CURSOR_LEFT == keyCode )
961 if( mEventData->mPrimaryCursorPosition > 0u )
963 mEventData->mPrimaryCursorPosition = CalculateNewCursorIndex( mEventData->mPrimaryCursorPosition - 1u );
966 else if( Dali::DALI_KEY_CURSOR_RIGHT == keyCode )
968 if( mLogicalModel->mText.Count() > mEventData->mPrimaryCursorPosition )
970 mEventData->mPrimaryCursorPosition = CalculateNewCursorIndex( mEventData->mPrimaryCursorPosition );
973 else if( Dali::DALI_KEY_CURSOR_UP == keyCode )
977 else if( Dali::DALI_KEY_CURSOR_DOWN == keyCode )
982 mEventData->mUpdateCursorPosition = true;
983 mEventData->mUpdateInputStyle = true;
984 mEventData->mScrollAfterUpdatePosition = true;
987 void Controller::Impl::OnTapEvent( const Event& event )
989 if( NULL != mEventData )
991 const unsigned int tapCount = event.p1.mUint;
995 if( IsShowingRealText() )
997 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
998 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
1000 mEventData->mPrimaryCursorPosition = GetClosestCursorIndex( xPosition,
1003 // When the cursor position is changing, delay cursor blinking
1004 mEventData->mDecorator->DelayCursorBlink();
1008 mEventData->mPrimaryCursorPosition = 0u;
1011 mEventData->mUpdateCursorPosition = true;
1012 mEventData->mScrollAfterUpdatePosition = true;
1013 mEventData->mUpdateInputStyle = true;
1015 // Notify the cursor position to the imf manager.
1016 if( mEventData->mImfManager )
1018 mEventData->mImfManager.SetCursorPosition( mEventData->mPrimaryCursorPosition );
1019 mEventData->mImfManager.NotifyCursorPosition();
1025 void Controller::Impl::OnPanEvent( const Event& event )
1027 if( NULL == mEventData )
1029 // Nothing to do if there is no text input.
1033 int state = event.p1.mInt;
1035 if( Gesture::Started == state ||
1036 Gesture::Continuing == state )
1038 const Vector2& actualSize = mVisualModel->GetLayoutSize();
1039 const Vector2 currentScroll = mEventData->mScrollPosition;
1041 if( mEventData->mHorizontalScrollingEnabled )
1043 const float displacementX = event.p2.mFloat;
1044 mEventData->mScrollPosition.x += displacementX;
1046 ClampHorizontalScroll( actualSize );
1049 if( mEventData->mVerticalScrollingEnabled )
1051 const float displacementY = event.p3.mFloat;
1052 mEventData->mScrollPosition.y += displacementY;
1054 ClampVerticalScroll( actualSize );
1057 if( mEventData->mDecorator )
1059 mEventData->mDecorator->UpdatePositions( mEventData->mScrollPosition - currentScroll );
1064 void Controller::Impl::OnLongPressEvent( const Event& event )
1066 DALI_LOG_INFO( gLogFilter, Debug::General, "Controller::OnLongPressEvent\n" );
1068 if( EventData::EDITING == mEventData->mState )
1070 ChangeState ( EventData::EDITING_WITH_POPUP );
1071 mEventData->mDecoratorUpdated = true;
1075 void Controller::Impl::OnHandleEvent( const Event& event )
1077 if( NULL == mEventData )
1079 // Nothing to do if there is no text input.
1083 const unsigned int state = event.p1.mUint;
1084 const bool handleStopScrolling = ( HANDLE_STOP_SCROLLING == state );
1086 if( HANDLE_PRESSED == state )
1088 // The event.p2 and event.p3 are in decorator coords. Need to transforms to text coords.
1089 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
1090 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
1092 const CharacterIndex handleNewPosition = GetClosestCursorIndex( xPosition, yPosition );
1094 if( Event::GRAB_HANDLE_EVENT == event.type )
1096 ChangeState ( EventData::GRAB_HANDLE_PANNING );
1098 if( handleNewPosition != mEventData->mPrimaryCursorPosition )
1100 mEventData->mPrimaryCursorPosition = handleNewPosition;
1101 mEventData->mUpdateCursorPosition = true;
1104 else if( Event::LEFT_SELECTION_HANDLE_EVENT == event.type )
1106 ChangeState ( EventData::SELECTION_HANDLE_PANNING );
1108 if( ( handleNewPosition != mEventData->mLeftSelectionPosition ) &&
1109 ( handleNewPosition != mEventData->mRightSelectionPosition ) )
1111 mEventData->mLeftSelectionPosition = handleNewPosition;
1113 mEventData->mUpdateLeftSelectionPosition = true;
1116 else if( Event::RIGHT_SELECTION_HANDLE_EVENT == event.type )
1118 ChangeState ( EventData::SELECTION_HANDLE_PANNING );
1120 if( ( handleNewPosition != mEventData->mRightSelectionPosition ) &&
1121 ( handleNewPosition != mEventData->mLeftSelectionPosition ) )
1123 mEventData->mRightSelectionPosition = handleNewPosition;
1125 mEventData->mUpdateRightSelectionPosition = true;
1128 } // end ( HANDLE_PRESSED == state )
1129 else if( ( HANDLE_RELEASED == state ) ||
1130 handleStopScrolling )
1132 CharacterIndex handlePosition = 0u;
1133 if( handleStopScrolling )
1135 // The event.p2 and event.p3 are in decorator coords. Need to transforms to text coords.
1136 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
1137 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
1139 handlePosition = GetClosestCursorIndex( xPosition, yPosition );
1142 if( Event::GRAB_HANDLE_EVENT == event.type )
1144 mEventData->mUpdateCursorPosition = true;
1145 mEventData->mUpdateInputStyle = true;
1147 if( !IsClipboardEmpty() )
1149 ChangeState( EventData::EDITING_WITH_PASTE_POPUP ); // Moving grabhandle will show Paste Popup
1152 if( handleStopScrolling )
1154 mEventData->mScrollAfterUpdatePosition = mEventData->mPrimaryCursorPosition != handlePosition;
1155 mEventData->mPrimaryCursorPosition = handlePosition;
1158 else if( Event::LEFT_SELECTION_HANDLE_EVENT == event.type )
1160 ChangeState( EventData::SELECTING );
1162 if( handleStopScrolling )
1164 mEventData->mUpdateLeftSelectionPosition = ( mEventData->mRightSelectionPosition != handlePosition );
1165 mEventData->mScrollAfterUpdatePosition = mEventData->mUpdateLeftSelectionPosition;
1167 if( mEventData->mUpdateLeftSelectionPosition )
1169 mEventData->mLeftSelectionPosition = handlePosition;
1173 else if( Event::RIGHT_SELECTION_HANDLE_EVENT == event.type )
1175 ChangeState( EventData::SELECTING );
1177 if( handleStopScrolling )
1179 mEventData->mUpdateRightSelectionPosition = ( mEventData->mLeftSelectionPosition != handlePosition );
1180 mEventData->mScrollAfterUpdatePosition = mEventData->mUpdateRightSelectionPosition;
1181 if( mEventData->mUpdateRightSelectionPosition )
1183 mEventData->mRightSelectionPosition = handlePosition;
1188 mEventData->mDecoratorUpdated = true;
1189 } // end ( ( HANDLE_RELEASED == state ) || ( HANDLE_STOP_SCROLLING == state ) )
1190 else if( HANDLE_SCROLLING == state )
1192 const float xSpeed = event.p2.mFloat;
1193 const Vector2& actualSize = mVisualModel->GetLayoutSize();
1194 const Vector2 currentScrollPosition = mEventData->mScrollPosition;
1196 mEventData->mScrollPosition.x += xSpeed;
1198 ClampHorizontalScroll( actualSize );
1200 bool endOfScroll = false;
1201 if( Vector2::ZERO == ( currentScrollPosition - mEventData->mScrollPosition ) )
1203 // Notify the decorator there is no more text to scroll.
1204 // The decorator won't send more scroll events.
1205 mEventData->mDecorator->NotifyEndOfScroll();
1206 // Still need to set the position of the handle.
1210 // Set the position of the handle.
1211 const bool scrollRightDirection = xSpeed > 0.f;
1212 const bool leftSelectionHandleEvent = Event::LEFT_SELECTION_HANDLE_EVENT == event.type;
1213 const bool rightSelectionHandleEvent = Event::RIGHT_SELECTION_HANDLE_EVENT == event.type;
1215 if( Event::GRAB_HANDLE_EVENT == event.type )
1217 ChangeState( EventData::GRAB_HANDLE_PANNING );
1219 Vector2 position = mEventData->mDecorator->GetPosition( GRAB_HANDLE );
1221 // Position the grag handle close to either the left or right edge.
1222 position.x = scrollRightDirection ? 0.f : mVisualModel->mControlSize.width;
1224 // Get the new handle position.
1225 // The grab handle's position is in decorator coords. Need to transforms to text coords.
1226 const CharacterIndex handlePosition = GetClosestCursorIndex( position.x - mEventData->mScrollPosition.x - mAlignmentOffset.x,
1227 position.y - mEventData->mScrollPosition.y - mAlignmentOffset.y );
1229 mEventData->mUpdateCursorPosition = mEventData->mPrimaryCursorPosition != handlePosition;
1230 mEventData->mScrollAfterUpdatePosition = mEventData->mUpdateCursorPosition;
1231 mEventData->mPrimaryCursorPosition = handlePosition;
1232 mEventData->mUpdateInputStyle = mEventData->mUpdateCursorPosition;
1234 else if( leftSelectionHandleEvent || rightSelectionHandleEvent )
1236 // TODO: This is recalculating the selection box every time the text is scrolled with the selection handles.
1237 // Think if something can be done to save power.
1239 ChangeState( EventData::SELECTION_HANDLE_PANNING );
1241 Vector2 position = mEventData->mDecorator->GetPosition( leftSelectionHandleEvent ? Text::LEFT_SELECTION_HANDLE : Text::RIGHT_SELECTION_HANDLE );
1243 // Position the selection handle close to either the left or right edge.
1244 position.x = scrollRightDirection ? 0.f : mVisualModel->mControlSize.width;
1246 // Get the new handle position.
1247 // The selection handle's position is in decorator coords. Need to transforms to text coords.
1248 const CharacterIndex handlePosition = GetClosestCursorIndex( position.x - mEventData->mScrollPosition.x - mAlignmentOffset.x,
1249 position.y - mEventData->mScrollPosition.y - mAlignmentOffset.y );
1251 if( leftSelectionHandleEvent )
1253 const bool differentHandles = ( mEventData->mLeftSelectionPosition != handlePosition ) && ( mEventData->mRightSelectionPosition != handlePosition );
1254 mEventData->mUpdateLeftSelectionPosition = endOfScroll || differentHandles;
1255 if( differentHandles )
1257 mEventData->mLeftSelectionPosition = handlePosition;
1262 const bool differentHandles = ( mEventData->mRightSelectionPosition != handlePosition ) && ( mEventData->mLeftSelectionPosition != handlePosition );
1263 mEventData->mUpdateRightSelectionPosition = endOfScroll || differentHandles;
1264 if( differentHandles )
1266 mEventData->mRightSelectionPosition = handlePosition;
1270 if( mEventData->mUpdateLeftSelectionPosition || mEventData->mUpdateRightSelectionPosition )
1272 RepositionSelectionHandles();
1274 mEventData->mScrollAfterUpdatePosition = true;
1277 mEventData->mDecoratorUpdated = true;
1278 } // end ( HANDLE_SCROLLING == state )
1281 void Controller::Impl::OnSelectEvent( const Event& event )
1283 if( NULL == mEventData )
1285 // Nothing to do if there is no text.
1289 if( mEventData->mSelectionEnabled )
1291 // The event.p2 and event.p3 are in decorator coords. Need to transforms to text coords.
1292 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
1293 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
1295 // Calculates the logical position from the x,y coords.
1296 RepositionSelectionHandles( xPosition,
1299 mEventData->mUpdateLeftSelectionPosition = true;
1300 mEventData->mUpdateRightSelectionPosition = true;
1302 mEventData->mScrollAfterUpdatePosition = ( mEventData->mLeftSelectionPosition != mEventData->mRightSelectionPosition );
1306 void Controller::Impl::OnSelectAllEvent()
1308 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "OnSelectAllEvent mEventData->mSelectionEnabled%s \n", mEventData->mSelectionEnabled?"true":"false");
1310 if( NULL == mEventData )
1312 // Nothing to do if there is no text.
1316 if( mEventData->mSelectionEnabled )
1318 mEventData->mLeftSelectionPosition = 0u;
1319 mEventData->mRightSelectionPosition = mLogicalModel->mText.Count();
1321 mEventData->mScrollAfterUpdatePosition = true;
1322 mEventData->mUpdateLeftSelectionPosition = true;
1323 mEventData->mUpdateRightSelectionPosition = true;
1327 void Controller::Impl::RetrieveSelection( std::string& selectedText, bool deleteAfterRetrieval )
1329 if( mEventData->mLeftSelectionPosition == mEventData->mRightSelectionPosition )
1331 // Nothing to select if handles are in the same place.
1332 selectedText.clear();
1336 const bool handlesCrossed = mEventData->mLeftSelectionPosition > mEventData->mRightSelectionPosition;
1338 //Get start and end position of selection
1339 const CharacterIndex startOfSelectedText = handlesCrossed ? mEventData->mRightSelectionPosition : mEventData->mLeftSelectionPosition;
1340 const Length lengthOfSelectedText = ( handlesCrossed ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition ) - startOfSelectedText;
1342 Vector<Character>& utf32Characters = mLogicalModel->mText;
1343 const Length numberOfCharacters = utf32Characters.Count();
1345 // Validate the start and end selection points
1346 if( ( startOfSelectedText + lengthOfSelectedText ) <= numberOfCharacters )
1348 //Get text as a UTF8 string
1349 Utf32ToUtf8( &utf32Characters[startOfSelectedText], lengthOfSelectedText, selectedText );
1351 if( deleteAfterRetrieval ) // Only delete text if copied successfully
1353 // Set as input style the style of the first deleted character.
1354 mLogicalModel->RetrieveStyle( startOfSelectedText, mEventData->mInputStyle );
1356 mLogicalModel->UpdateTextStyleRuns( startOfSelectedText, -static_cast<int>( lengthOfSelectedText ) );
1358 // Mark the paragraphs to be updated.
1359 mTextUpdateInfo.mCharacterIndex = startOfSelectedText;
1360 mTextUpdateInfo.mNumberOfCharactersToRemove = lengthOfSelectedText;
1362 // Delete text between handles
1363 Vector<Character>::Iterator first = utf32Characters.Begin() + startOfSelectedText;
1364 Vector<Character>::Iterator last = first + lengthOfSelectedText;
1365 utf32Characters.Erase( first, last );
1367 // Scroll after delete.
1368 mEventData->mPrimaryCursorPosition = handlesCrossed ? mEventData->mRightSelectionPosition : mEventData->mLeftSelectionPosition;
1369 mEventData->mScrollAfterDelete = true;
1371 // Udpade the cursor position and the decorator.
1372 // Scroll after the position is updated if is not scrolling after delete.
1373 mEventData->mUpdateCursorPosition = true;
1374 mEventData->mScrollAfterUpdatePosition = !mEventData->mScrollAfterDelete;
1375 mEventData->mDecoratorUpdated = true;
1379 void Controller::Impl::ShowClipboard()
1383 mClipboard.ShowClipboard();
1387 void Controller::Impl::HideClipboard()
1391 mClipboard.HideClipboard();
1395 bool Controller::Impl::CopyStringToClipboard( std::string& source )
1397 //Send string to clipboard
1398 return ( mClipboard && mClipboard.SetItem( source ) );
1401 void Controller::Impl::SendSelectionToClipboard( bool deleteAfterSending )
1403 std::string selectedText;
1404 RetrieveSelection( selectedText, deleteAfterSending );
1405 CopyStringToClipboard( selectedText );
1406 ChangeState( EventData::EDITING );
1409 void Controller::Impl::GetTextFromClipboard( unsigned int itemIndex, std::string& retrievedString )
1413 retrievedString = mClipboard.GetItem( itemIndex );
1417 void Controller::Impl::RepositionSelectionHandles()
1419 CharacterIndex selectionStart = mEventData->mLeftSelectionPosition;
1420 CharacterIndex selectionEnd = mEventData->mRightSelectionPosition;
1422 if( selectionStart == selectionEnd )
1424 // Nothing to select if handles are in the same place.
1428 mEventData->mDecorator->ClearHighlights();
1430 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
1431 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
1432 const GlyphInfo* const glyphsBuffer = mVisualModel->mGlyphs.Begin();
1433 const Vector2* const positionsBuffer = mVisualModel->mGlyphPositions.Begin();
1434 const Length* const charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
1435 const CharacterIndex* const glyphToCharacterBuffer = mVisualModel->mGlyphsToCharacters.Begin();
1436 const CharacterDirection* const modelCharacterDirectionsBuffer = ( 0u != mLogicalModel->mCharacterDirections.Count() ) ? mLogicalModel->mCharacterDirections.Begin() : NULL;
1438 // TODO: Better algorithm to create the highlight box.
1439 // TODO: Multi-line.
1441 // Get the height of the line.
1442 const Vector<LineRun>& lines = mVisualModel->mLines;
1443 const LineRun& firstLine = *lines.Begin();
1444 const float height = firstLine.ascender + -firstLine.descender;
1446 const bool isLastCharacter = selectionEnd >= mLogicalModel->mText.Count();
1447 const bool startDirection = ( ( NULL == modelCharacterDirectionsBuffer ) ? false : *( modelCharacterDirectionsBuffer + selectionStart ) );
1448 const bool endDirection = ( ( NULL == modelCharacterDirectionsBuffer ) ? false : *( modelCharacterDirectionsBuffer + ( selectionEnd - ( isLastCharacter ? 1u : 0u ) ) ) );
1450 // Swap the indices if the start is greater than the end.
1451 const bool indicesSwapped = selectionStart > selectionEnd;
1453 // Tell the decorator to flip the selection handles if needed.
1454 mEventData->mDecorator->SetSelectionHandleFlipState( indicesSwapped, startDirection, endDirection );
1456 if( indicesSwapped )
1458 std::swap( selectionStart, selectionEnd );
1461 // Get the indices to the first and last selected glyphs.
1462 const CharacterIndex selectionEndMinusOne = selectionEnd - 1u;
1463 const GlyphIndex glyphStart = *( charactersToGlyphBuffer + selectionStart );
1464 const Length numberOfGlyphs = *( glyphsPerCharacterBuffer + selectionEndMinusOne );
1465 const GlyphIndex glyphEnd = *( charactersToGlyphBuffer + selectionEndMinusOne ) + ( ( numberOfGlyphs > 0 ) ? numberOfGlyphs - 1u : 0u );
1467 // Check if the first glyph is a ligature that must be broken like Latin ff, fi, or Arabic ﻻ, etc which needs special code.
1468 const Length numberOfCharactersStart = *( charactersPerGlyphBuffer + glyphStart );
1469 bool splitStartGlyph = ( numberOfCharactersStart > 1u ) && HasLigatureMustBreak( mLogicalModel->GetScript( selectionStart ) );
1471 // Check if the last glyph is a ligature that must be broken like Latin ff, fi, or Arabic ﻻ, etc which needs special code.
1472 const Length numberOfCharactersEnd = *( charactersPerGlyphBuffer + glyphEnd );
1473 bool splitEndGlyph = ( glyphStart != glyphEnd ) && ( numberOfCharactersEnd > 1u ) && HasLigatureMustBreak( mLogicalModel->GetScript( selectionEndMinusOne ) );
1475 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
1477 // Traverse the glyphs.
1478 for( GlyphIndex index = glyphStart; index <= glyphEnd; ++index )
1480 const GlyphInfo& glyph = *( glyphsBuffer + index );
1481 const Vector2& position = *( positionsBuffer + index );
1483 if( splitStartGlyph )
1485 // 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.
1487 const float glyphAdvance = glyph.advance / static_cast<float>( numberOfCharactersStart );
1488 const CharacterIndex interGlyphIndex = selectionStart - *( glyphToCharacterBuffer + glyphStart );
1489 // Get the direction of the character.
1490 CharacterDirection isCurrentRightToLeft = false;
1491 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
1493 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + selectionStart );
1496 // The end point could be in the middle of the ligature.
1497 // Calculate the number of characters selected.
1498 const Length numberOfCharacters = ( glyphStart == glyphEnd ) ? ( selectionEnd - selectionStart ) : ( numberOfCharactersStart - interGlyphIndex );
1500 const float xPosition = position.x - glyph.xBearing + offset.x + glyphAdvance * static_cast<float>( isCurrentRightToLeft ? ( numberOfCharactersStart - interGlyphIndex - numberOfCharacters ) : interGlyphIndex );
1502 mEventData->mDecorator->AddHighlight( xPosition,
1504 xPosition + static_cast<float>( numberOfCharacters ) * glyphAdvance,
1505 offset.y + height );
1507 splitStartGlyph = false;
1511 if( splitEndGlyph && ( index == glyphEnd ) )
1513 // 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.
1515 const float glyphAdvance = glyph.advance / static_cast<float>( numberOfCharactersEnd );
1516 const CharacterIndex interGlyphIndex = selectionEnd - *( glyphToCharacterBuffer + glyphEnd );
1517 // Get the direction of the character.
1518 CharacterDirection isCurrentRightToLeft = false;
1519 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
1521 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + selectionEnd );
1524 const Length numberOfCharacters = numberOfCharactersEnd - interGlyphIndex;
1526 const float xPosition = position.x - glyph.xBearing + offset.x + ( isCurrentRightToLeft ? ( glyphAdvance * static_cast<float>( numberOfCharacters ) ) : 0.f );
1527 mEventData->mDecorator->AddHighlight( xPosition,
1529 xPosition + static_cast<float>( interGlyphIndex ) * glyphAdvance,
1530 offset.y + height );
1532 splitEndGlyph = false;
1536 const float xPosition = position.x - glyph.xBearing + offset.x;
1537 mEventData->mDecorator->AddHighlight( xPosition,
1539 xPosition + glyph.advance,
1540 offset.y + height );
1543 CursorInfo primaryCursorInfo;
1544 GetCursorPosition( mEventData->mLeftSelectionPosition,
1545 primaryCursorInfo );
1547 CursorInfo secondaryCursorInfo;
1548 GetCursorPosition( mEventData->mRightSelectionPosition,
1549 secondaryCursorInfo );
1551 const Vector2 primaryPosition = primaryCursorInfo.primaryPosition + offset;
1552 const Vector2 secondaryPosition = secondaryCursorInfo.primaryPosition + offset;
1554 mEventData->mDecorator->SetPosition( LEFT_SELECTION_HANDLE,
1556 primaryCursorInfo.lineOffset + offset.y,
1557 primaryCursorInfo.lineHeight );
1559 mEventData->mDecorator->SetPosition( RIGHT_SELECTION_HANDLE,
1560 secondaryPosition.x,
1561 secondaryCursorInfo.lineOffset + offset.y,
1562 secondaryCursorInfo.lineHeight );
1564 // Cursor to be positioned at end of selection so if selection interrupted and edit mode restarted the cursor will be at end of selection
1565 mEventData->mPrimaryCursorPosition = ( indicesSwapped ) ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition;
1567 // Set the flag to update the decorator.
1568 mEventData->mDecoratorUpdated = true;
1571 void Controller::Impl::RepositionSelectionHandles( float visualX, float visualY )
1573 if( NULL == mEventData )
1575 // Nothing to do if there is no text input.
1579 if( IsShowingPlaceholderText() )
1581 // Nothing to do if there is the place-holder text.
1585 const Length numberOfGlyphs = mVisualModel->mGlyphs.Count();
1586 const Length numberOfLines = mVisualModel->mLines.Count();
1587 if( ( 0 == numberOfGlyphs ) ||
1588 ( 0 == numberOfLines ) )
1590 // Nothing to do if there is no text.
1594 // Find which word was selected
1595 CharacterIndex selectionStart( 0 );
1596 CharacterIndex selectionEnd( 0 );
1597 FindSelectionIndices( visualX, visualY, selectionStart, selectionEnd );
1598 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "%p selectionStart %d selectionEnd %d\n", this, selectionStart, selectionEnd );
1600 if( selectionStart == selectionEnd )
1602 ChangeState( EventData::EDITING );
1603 // Nothing to select. i.e. a white space, out of bounds
1607 mEventData->mLeftSelectionPosition = selectionStart;
1608 mEventData->mRightSelectionPosition = selectionEnd;
1611 void Controller::Impl::SetPopupButtons()
1614 * Sets the Popup buttons to be shown depending on State.
1616 * If SELECTING : CUT & COPY + ( PASTE & CLIPBOARD if content available to paste )
1618 * If EDITING_WITH_POPUP : SELECT & SELECT_ALL
1621 TextSelectionPopup::Buttons buttonsToShow = TextSelectionPopup::NONE;
1623 if( EventData::SELECTING == mEventData->mState )
1625 buttonsToShow = TextSelectionPopup::Buttons( TextSelectionPopup::CUT | TextSelectionPopup::COPY );
1627 if( !IsClipboardEmpty() )
1629 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
1630 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::CLIPBOARD ) );
1633 if( !mEventData->mAllTextSelected )
1635 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::SELECT_ALL ) );
1638 else if( EventData::EDITING_WITH_POPUP == mEventData->mState )
1640 if( mLogicalModel->mText.Count() && !IsShowingPlaceholderText() )
1642 buttonsToShow = TextSelectionPopup::Buttons( TextSelectionPopup::SELECT | TextSelectionPopup::SELECT_ALL );
1645 if( !IsClipboardEmpty() )
1647 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
1648 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::CLIPBOARD ) );
1651 else if( EventData::EDITING_WITH_PASTE_POPUP == mEventData->mState )
1653 if ( !IsClipboardEmpty() )
1655 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
1656 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::CLIPBOARD ) );
1660 mEventData->mDecorator->SetEnabledPopupButtons( buttonsToShow );
1663 void Controller::Impl::ChangeState( EventData::State newState )
1665 if( NULL == mEventData )
1667 // Nothing to do if there is no text input.
1671 DALI_LOG_INFO( gLogFilter, Debug::General, "ChangeState state:%d newstate:%d\n", mEventData->mState, newState );
1673 if( mEventData->mState != newState )
1675 mEventData->mState = newState;
1677 if( EventData::INACTIVE == mEventData->mState )
1679 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
1680 mEventData->mDecorator->StopCursorBlink();
1681 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1682 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1683 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1684 mEventData->mDecorator->SetPopupActive( false );
1685 mEventData->mDecoratorUpdated = true;
1688 else if( EventData::INTERRUPTED == mEventData->mState)
1690 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1691 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1692 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1693 mEventData->mDecorator->SetPopupActive( false );
1694 mEventData->mDecoratorUpdated = true;
1697 else if( EventData::SELECTING == mEventData->mState )
1699 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
1700 mEventData->mDecorator->StopCursorBlink();
1701 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1702 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true );
1703 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true );
1704 if( mEventData->mGrabHandlePopupEnabled )
1707 mEventData->mDecorator->SetPopupActive( true );
1709 mEventData->mDecoratorUpdated = true;
1711 else if( EventData::EDITING == mEventData->mState )
1713 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1714 if( mEventData->mCursorBlinkEnabled )
1716 mEventData->mDecorator->StartCursorBlink();
1718 // Grab handle is not shown until a tap is received whilst EDITING
1719 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1720 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1721 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1722 if( mEventData->mGrabHandlePopupEnabled )
1724 mEventData->mDecorator->SetPopupActive( false );
1726 mEventData->mDecoratorUpdated = true;
1729 else if( EventData::EDITING_WITH_POPUP == mEventData->mState )
1731 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "EDITING_WITH_POPUP \n", newState );
1733 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1734 if( mEventData->mCursorBlinkEnabled )
1736 mEventData->mDecorator->StartCursorBlink();
1738 if( mEventData->mSelectionEnabled )
1740 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1741 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1745 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
1747 if( mEventData->mGrabHandlePopupEnabled )
1750 mEventData->mDecorator->SetPopupActive( true );
1753 mEventData->mDecoratorUpdated = true;
1755 else if( EventData::EDITING_WITH_GRAB_HANDLE == mEventData->mState )
1757 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "EDITING_WITH_GRAB_HANDLE \n", newState );
1759 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1760 if( mEventData->mCursorBlinkEnabled )
1762 mEventData->mDecorator->StartCursorBlink();
1764 // Grab handle is not shown until a tap is received whilst EDITING
1765 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
1766 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1767 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1768 if( mEventData->mGrabHandlePopupEnabled )
1770 mEventData->mDecorator->SetPopupActive( false );
1772 mEventData->mDecoratorUpdated = true;
1775 else if( EventData::SELECTION_HANDLE_PANNING == mEventData->mState )
1777 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
1778 mEventData->mDecorator->StopCursorBlink();
1779 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1780 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true );
1781 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true );
1782 if( mEventData->mGrabHandlePopupEnabled )
1784 mEventData->mDecorator->SetPopupActive( false );
1786 mEventData->mDecoratorUpdated = true;
1788 else if( EventData::GRAB_HANDLE_PANNING == mEventData->mState )
1790 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "GRAB_HANDLE_PANNING \n", newState );
1792 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1793 if( mEventData->mCursorBlinkEnabled )
1795 mEventData->mDecorator->StartCursorBlink();
1797 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
1798 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1799 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1800 if( mEventData->mGrabHandlePopupEnabled )
1802 mEventData->mDecorator->SetPopupActive( false );
1804 mEventData->mDecoratorUpdated = true;
1806 else if( EventData::EDITING_WITH_PASTE_POPUP == mEventData->mState )
1808 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "EDITING_WITH_PASTE_POPUP \n", newState );
1810 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1811 if( mEventData->mCursorBlinkEnabled )
1813 mEventData->mDecorator->StartCursorBlink();
1816 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
1817 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1818 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1820 if( mEventData->mGrabHandlePopupEnabled )
1823 mEventData->mDecorator->SetPopupActive( true );
1826 mEventData->mDecoratorUpdated = true;
1831 LineIndex Controller::Impl::GetClosestLine( float y ) const
1833 float totalHeight = 0.f;
1834 LineIndex lineIndex = 0u;
1836 const Vector<LineRun>& lines = mVisualModel->mLines;
1837 for( LineIndex endLine = lines.Count();
1838 lineIndex < endLine;
1841 const LineRun& lineRun = lines[lineIndex];
1842 totalHeight += lineRun.ascender + -lineRun.descender;
1843 if( y < totalHeight )
1849 if( lineIndex == 0 )
1857 void Controller::Impl::FindSelectionIndices( float visualX, float visualY, CharacterIndex& startIndex, CharacterIndex& endIndex )
1859 CharacterIndex hitCharacter = GetClosestCursorIndex( visualX, visualY );
1860 DALI_ASSERT_DEBUG( hitCharacter <= mLogicalModel->mText.Count() && "GetClosestCursorIndex returned out of bounds index" );
1862 if( mLogicalModel->mText.Count() == 0 )
1864 return; // if model empty
1867 if( hitCharacter >= mLogicalModel->mText.Count() )
1869 // Closest hit character is the last character.
1870 if( hitCharacter == mLogicalModel->mText.Count() )
1872 hitCharacter--; //Hit character index set to last character in logical model
1876 // hitCharacter is out of bounds
1881 startIndex = hitCharacter;
1882 endIndex = hitCharacter;
1883 bool isHitCharacterWhitespace = TextAbstraction::IsWhiteSpace( mLogicalModel->mText[hitCharacter] );
1885 // Find the start and end of the text
1886 for( startIndex = hitCharacter; startIndex > 0; --startIndex )
1888 if( isHitCharacterWhitespace != TextAbstraction::IsWhiteSpace( mLogicalModel->mText[ startIndex-1 ] ) )
1893 const CharacterIndex pastTheEnd = mLogicalModel->mText.Count();
1894 for( endIndex = hitCharacter + 1u; endIndex < pastTheEnd; ++endIndex )
1896 if( isHitCharacterWhitespace != TextAbstraction::IsWhiteSpace( mLogicalModel->mText[ endIndex ] ) )
1903 CharacterIndex Controller::Impl::GetClosestCursorIndex( float visualX,
1906 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "GetClosestCursorIndex %p closest visualX %f visualY %f\n", this, visualX, visualY );
1908 if( NULL == mEventData )
1910 // Nothing to do if there is no text input.
1914 CharacterIndex logicalIndex = 0u;
1916 const Length numberOfGlyphs = mVisualModel->mGlyphs.Count();
1917 const Length numberOfLines = mVisualModel->mLines.Count();
1918 if( ( 0 == numberOfGlyphs ) ||
1919 ( 0 == numberOfLines ) )
1921 return logicalIndex;
1924 // Find which line is closest
1925 const LineIndex lineIndex = GetClosestLine( visualY );
1926 const LineRun& line = mVisualModel->mLines[lineIndex];
1928 // Get the positions of the glyphs.
1929 const Vector<Vector2>& positions = mVisualModel->mGlyphPositions;
1930 const Vector2* const positionsBuffer = positions.Begin();
1932 // Get the visual to logical conversion tables.
1933 const CharacterIndex* const visualToLogicalBuffer = ( 0u != mLogicalModel->mVisualToLogicalMap.Count() ) ? mLogicalModel->mVisualToLogicalMap.Begin() : NULL;
1934 const CharacterIndex* const visualToLogicalCursorBuffer = mLogicalModel->mVisualToLogicalCursorMap.Begin();
1936 // Get the character to glyph conversion table.
1937 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
1939 // Get the glyphs per character table.
1940 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
1942 // Get the glyph's info buffer.
1943 const GlyphInfo* const glyphInfoBuffer = mVisualModel->mGlyphs.Begin();
1945 // If the vector is void, there is no right to left characters.
1946 const bool hasRightToLeftCharacters = NULL != visualToLogicalBuffer;
1948 const CharacterIndex startCharacter = line.characterRun.characterIndex;
1949 const CharacterIndex endCharacter = line.characterRun.characterIndex + line.characterRun.numberOfCharacters;
1950 DALI_ASSERT_DEBUG( endCharacter <= mLogicalModel->mText.Count() && "Invalid line info" );
1952 // Whether there is a hit on a glyph.
1953 bool matched = false;
1955 // Traverses glyphs in visual order. To do that use the visual to logical conversion table.
1956 CharacterIndex visualIndex = startCharacter;
1957 Length numberOfCharacters = 0u;
1958 for( ; !matched && ( visualIndex < endCharacter ); ++visualIndex )
1960 // The character in logical order.
1961 const CharacterIndex characterLogicalOrderIndex = hasRightToLeftCharacters ? *( visualToLogicalBuffer + visualIndex ) : visualIndex;
1963 // Get the script of the character.
1964 const Script script = mLogicalModel->GetScript( characterLogicalOrderIndex );
1966 // The number of glyphs for that character
1967 const Length numberOfGlyphs = *( glyphsPerCharacterBuffer + characterLogicalOrderIndex );
1968 ++numberOfCharacters;
1971 if( 0u != numberOfGlyphs )
1973 // Get the first character/glyph of the group of glyphs.
1974 const CharacterIndex firstVisualCharacterIndex = 1u + visualIndex - numberOfCharacters;
1975 const CharacterIndex firstLogicalCharacterIndex = hasRightToLeftCharacters ? *( visualToLogicalBuffer + firstVisualCharacterIndex ) : firstVisualCharacterIndex;
1976 const GlyphIndex firstLogicalGlyphIndex = *( charactersToGlyphBuffer + firstLogicalCharacterIndex );
1978 // Get the metrics for the group of glyphs.
1979 GlyphMetrics glyphMetrics;
1980 GetGlyphsMetrics( firstLogicalGlyphIndex,
1986 // Get the position of the first glyph.
1987 const Vector2& position = *( positionsBuffer + firstLogicalGlyphIndex );
1989 // Whether the glyph can be split, like Latin ligatures fi, ff or Arabic ﻻ.
1990 const bool isInterglyphIndex = ( numberOfCharacters > numberOfGlyphs ) && HasLigatureMustBreak( script );
1991 const Length numberOfBlocks = isInterglyphIndex ? numberOfCharacters : 1u;
1992 const float glyphAdvance = glyphMetrics.advance / static_cast<float>( numberOfBlocks );
1994 GlyphIndex index = 0u;
1995 for( ; !matched && ( index < numberOfBlocks ); ++index )
1997 // Find the mid-point of the area containing the glyph
1998 const float glyphCenter = -glyphMetrics.xBearing + position.x + ( static_cast<float>( index ) + 0.5f ) * glyphAdvance;
2000 if( visualX < glyphCenter )
2009 visualIndex = firstVisualCharacterIndex + index;
2013 numberOfCharacters = 0u;
2019 // Return the logical position of the cursor in characters.
2023 visualIndex = endCharacter;
2026 logicalIndex = hasRightToLeftCharacters ? *( visualToLogicalCursorBuffer + visualIndex ) : visualIndex;
2027 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "%p closest visualIndex %d logicalIndex %d\n", this, visualIndex, logicalIndex );
2029 DALI_ASSERT_DEBUG( ( logicalIndex <= mLogicalModel->mText.Count() && logicalIndex >= 0 ) && "GetClosestCursorIndex - Out of bounds index" );
2031 return logicalIndex;
2034 void Controller::Impl::GetCursorPosition( CharacterIndex logical,
2035 CursorInfo& cursorInfo )
2037 // TODO: Check for multiline with \n, etc...
2039 const Length numberOfCharacters = mLogicalModel->mText.Count();
2040 if( !IsShowingRealText() )
2042 // Do not want to use the place-holder text to set the cursor position.
2044 // Use the line's height of the font's family set to set the cursor's size.
2045 // If there is no font's family set, use the default font.
2046 // Use the current alignment to place the cursor at the beginning, center or end of the box.
2048 cursorInfo.lineOffset = 0.f;
2049 cursorInfo.lineHeight = GetDefaultFontLineHeight();
2050 cursorInfo.primaryCursorHeight = cursorInfo.lineHeight;
2052 switch( mLayoutEngine.GetHorizontalAlignment() )
2054 case LayoutEngine::HORIZONTAL_ALIGN_BEGIN:
2056 cursorInfo.primaryPosition.x = 0.f;
2059 case LayoutEngine::HORIZONTAL_ALIGN_CENTER:
2061 cursorInfo.primaryPosition.x = floorf( 0.5f * mVisualModel->mControlSize.width );
2064 case LayoutEngine::HORIZONTAL_ALIGN_END:
2066 cursorInfo.primaryPosition.x = mVisualModel->mControlSize.width - mEventData->mDecorator->GetCursorWidth();
2071 switch( mLayoutEngine.GetVerticalAlignment() )
2073 case LayoutEngine::VERTICAL_ALIGN_TOP:
2075 cursorInfo.primaryPosition.y = 0.f;
2078 case LayoutEngine::VERTICAL_ALIGN_CENTER:
2080 cursorInfo.primaryPosition.y = floorf( 0.5f * ( mVisualModel->mControlSize.height - cursorInfo.lineHeight ) );
2083 case LayoutEngine::VERTICAL_ALIGN_BOTTOM:
2085 cursorInfo.primaryPosition.y = mVisualModel->mControlSize.height - cursorInfo.lineHeight;
2090 // Nothing else to do.
2094 // Check if the logical position is the first or the last one of the text.
2095 const bool isFirstPosition = 0u == logical;
2096 const bool isLastPosition = numberOfCharacters == logical;
2098 // 'logical' is the logical 'cursor' index.
2099 // Get the next and current logical 'character' index.
2100 const CharacterIndex nextCharacterIndex = logical;
2101 const CharacterIndex characterIndex = isFirstPosition ? logical : logical - 1u;
2103 // Get the direction of the character and the next one.
2104 const CharacterDirection* const modelCharacterDirectionsBuffer = ( 0u != mLogicalModel->mCharacterDirections.Count() ) ? mLogicalModel->mCharacterDirections.Begin() : NULL;
2106 CharacterDirection isCurrentRightToLeft = false;
2107 CharacterDirection isNextRightToLeft = false;
2108 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
2110 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + characterIndex );
2111 isNextRightToLeft = *( modelCharacterDirectionsBuffer + nextCharacterIndex );
2114 // Get the line where the character is laid-out.
2115 const LineRun* const modelLines = mVisualModel->mLines.Begin();
2117 const LineIndex lineIndex = mVisualModel->GetLineOfCharacter( characterIndex );
2118 const LineRun& line = *( modelLines + lineIndex );
2120 // Get the paragraph's direction.
2121 const CharacterDirection isRightToLeftParagraph = line.direction;
2123 // Check whether there is an alternative position:
2125 cursorInfo.isSecondaryCursor = ( !isLastPosition && ( isCurrentRightToLeft != isNextRightToLeft ) ) ||
2126 ( isLastPosition && ( isRightToLeftParagraph != isCurrentRightToLeft ) );
2128 // Set the line offset and height.
2129 cursorInfo.lineOffset = 0.f;
2130 cursorInfo.lineHeight = line.ascender + -line.descender;
2132 // Calculate the primary cursor.
2134 CharacterIndex index = characterIndex;
2135 if( cursorInfo.isSecondaryCursor )
2137 // If there is a secondary position, the primary cursor may be in a different place than the logical index.
2139 if( isLastPosition )
2141 // The position of the cursor after the last character needs special
2142 // care depending on its direction and the direction of the paragraph.
2144 // Need to find the first character after the last character with the paragraph's direction.
2145 // i.e l0 l1 l2 r0 r1 should find r0.
2147 // TODO: check for more than one line!
2148 index = isRightToLeftParagraph ? line.characterRun.characterIndex : line.characterRun.characterIndex + line.characterRun.numberOfCharacters - 1u;
2149 index = mLogicalModel->GetLogicalCharacterIndex( index );
2153 index = ( isRightToLeftParagraph == isCurrentRightToLeft ) ? characterIndex : nextCharacterIndex;
2157 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
2158 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
2159 const Length* const charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
2160 const CharacterIndex* const glyphsToCharactersBuffer = mVisualModel->mGlyphsToCharacters.Begin();
2161 const Vector2* const glyphPositionsBuffer = mVisualModel->mGlyphPositions.Begin();
2162 const GlyphInfo* const glyphInfoBuffer = mVisualModel->mGlyphs.Begin();
2164 // Convert the cursor position into the glyph position.
2165 const GlyphIndex primaryGlyphIndex = *( charactersToGlyphBuffer + index );
2166 const Length primaryNumberOfGlyphs = *( glyphsPerCharacterBuffer + index );
2167 const Length primaryNumberOfCharacters = *( charactersPerGlyphBuffer + primaryGlyphIndex );
2169 // Get the metrics for the group of glyphs.
2170 GlyphMetrics glyphMetrics;
2171 GetGlyphsMetrics( primaryGlyphIndex,
2172 primaryNumberOfGlyphs,
2177 // Whether to add the glyph's advance to the cursor position.
2178 // 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,
2179 // if the logical cursor is one, the position is the position of the first glyph and the advance is added.
2180 // A 'truth table' was build and an online Karnaugh map tool was used to simplify the logic.
2201 // Where F -> isFirstPosition
2202 // L -> isLastPosition
2203 // C -> isCurrentRightToLeft
2204 // P -> isRightToLeftParagraph
2205 // A -> Whether to add the glyph's advance.
2207 const bool addGlyphAdvance = ( ( isLastPosition && !isRightToLeftParagraph ) ||
2208 ( isFirstPosition && isRightToLeftParagraph ) ||
2209 ( !isFirstPosition && !isLastPosition && !isCurrentRightToLeft ) );
2211 float glyphAdvance = addGlyphAdvance ? glyphMetrics.advance : 0.f;
2213 if( !isLastPosition &&
2214 ( primaryNumberOfCharacters > 1u ) )
2216 const CharacterIndex firstIndex = *( glyphsToCharactersBuffer + primaryGlyphIndex );
2218 bool isCurrentRightToLeft = false;
2219 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
2221 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + index );
2224 Length numberOfGlyphAdvance = ( isFirstPosition ? 0u : 1u ) + characterIndex - firstIndex;
2225 if( isCurrentRightToLeft )
2227 numberOfGlyphAdvance = primaryNumberOfCharacters - numberOfGlyphAdvance;
2230 glyphAdvance = static_cast<float>( numberOfGlyphAdvance ) * glyphMetrics.advance / static_cast<float>( primaryNumberOfCharacters );
2233 // Get the glyph position and x bearing.
2234 const Vector2& primaryPosition = *( glyphPositionsBuffer + primaryGlyphIndex );
2236 // Set the primary cursor's height.
2237 cursorInfo.primaryCursorHeight = cursorInfo.isSecondaryCursor ? 0.5f * glyphMetrics.fontHeight : glyphMetrics.fontHeight;
2239 // Set the primary cursor's position.
2240 cursorInfo.primaryPosition.x = -glyphMetrics.xBearing + primaryPosition.x + glyphAdvance;
2241 cursorInfo.primaryPosition.y = line.ascender - glyphMetrics.ascender;
2243 // Calculate the secondary cursor.
2245 if( cursorInfo.isSecondaryCursor )
2247 // Set the secondary cursor's height.
2248 cursorInfo.secondaryCursorHeight = 0.5f * glyphMetrics.fontHeight;
2250 CharacterIndex index = characterIndex;
2251 if( !isLastPosition )
2253 index = ( isRightToLeftParagraph == isCurrentRightToLeft ) ? nextCharacterIndex : characterIndex;
2256 const GlyphIndex secondaryGlyphIndex = *( charactersToGlyphBuffer + index );
2257 const Length secondaryNumberOfGlyphs = *( glyphsPerCharacterBuffer + index );
2259 const Vector2& secondaryPosition = *( glyphPositionsBuffer + secondaryGlyphIndex );
2261 GetGlyphsMetrics( secondaryGlyphIndex,
2262 secondaryNumberOfGlyphs,
2267 // Set the secondary cursor's position.
2268 cursorInfo.secondaryPosition.x = -glyphMetrics.xBearing + secondaryPosition.x + ( isCurrentRightToLeft ? 0.f : glyphMetrics.advance );
2269 cursorInfo.secondaryPosition.y = cursorInfo.lineHeight - cursorInfo.secondaryCursorHeight - line.descender - ( glyphMetrics.fontHeight - glyphMetrics.ascender );
2272 if( LayoutEngine::MULTI_LINE_BOX == mLayoutEngine.GetLayout() )
2274 // If the text is editable and multi-line, the cursor position after a white space shouldn't exceed the boundaries of the text control.
2276 // Note the white spaces laid-out at the end of the line might exceed the boundaries of the control.
2277 // The reason is a wrapped line must not start with a white space so they are laid-out at the end of the line.
2279 if( 0.f > cursorInfo.primaryPosition.x )
2281 cursorInfo.primaryPosition.x = 0.f;
2284 const float edgeWidth = mVisualModel->mControlSize.width - static_cast<float>( mEventData->mDecorator->GetCursorWidth() );
2285 if( cursorInfo.primaryPosition.x > edgeWidth )
2287 cursorInfo.primaryPosition.x = edgeWidth;
2292 CharacterIndex Controller::Impl::CalculateNewCursorIndex( CharacterIndex index ) const
2294 if( NULL == mEventData )
2296 // Nothing to do if there is no text input.
2300 CharacterIndex cursorIndex = mEventData->mPrimaryCursorPosition;
2302 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
2303 const Length* const charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
2305 GlyphIndex glyphIndex = *( charactersToGlyphBuffer + index );
2306 Length numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
2308 if( numberOfCharacters > 1u )
2310 const Script script = mLogicalModel->GetScript( index );
2311 if( HasLigatureMustBreak( script ) )
2313 // Prevents to jump the whole Latin ligatures like fi, ff, or Arabic ﻻ, ...
2314 numberOfCharacters = 1u;
2319 while( 0u == numberOfCharacters )
2322 numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
2326 if( index < mEventData->mPrimaryCursorPosition )
2328 cursorIndex -= numberOfCharacters;
2332 cursorIndex += numberOfCharacters;
2338 void Controller::Impl::UpdateCursorPosition( const CursorInfo& cursorInfo )
2340 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "-->Controller::UpdateCursorPosition %p\n", this );
2341 if( NULL == mEventData )
2343 // Nothing to do if there is no text input.
2344 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition no event data\n" );
2348 const Vector2 offset = mEventData->mScrollPosition + ( IsShowingRealText() ? mAlignmentOffset : Vector2::ZERO );
2349 const Vector2 cursorPosition = cursorInfo.primaryPosition + offset;
2351 // Sets the cursor position.
2352 mEventData->mDecorator->SetPosition( PRIMARY_CURSOR,
2355 cursorInfo.primaryCursorHeight,
2356 cursorInfo.lineHeight );
2357 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Primary cursor position: %f,%f\n", cursorPosition.x, cursorPosition.y );
2359 // Sets the grab handle position.
2360 mEventData->mDecorator->SetPosition( GRAB_HANDLE,
2362 cursorInfo.lineOffset + offset.y,
2363 cursorInfo.lineHeight );
2365 if( cursorInfo.isSecondaryCursor )
2367 mEventData->mDecorator->SetPosition( SECONDARY_CURSOR,
2368 cursorInfo.secondaryPosition.x + offset.x,
2369 cursorInfo.secondaryPosition.y + offset.y,
2370 cursorInfo.secondaryCursorHeight,
2371 cursorInfo.lineHeight );
2372 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Secondary cursor position: %f,%f\n", cursorInfo.secondaryPosition.x + offset.x, cursorInfo.secondaryPosition.y + offset.y );
2375 // Set which cursors are active according the state.
2376 if( EventData::IsEditingState( mEventData->mState ) || ( EventData::GRAB_HANDLE_PANNING == mEventData->mState ) )
2378 if( cursorInfo.isSecondaryCursor )
2380 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_BOTH );
2384 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
2389 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
2392 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition\n" );
2395 void Controller::Impl::UpdateSelectionHandle( HandleType handleType,
2396 const CursorInfo& cursorInfo )
2398 if( ( LEFT_SELECTION_HANDLE != handleType ) &&
2399 ( RIGHT_SELECTION_HANDLE != handleType ) )
2404 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
2405 const Vector2 cursorPosition = cursorInfo.primaryPosition + offset;
2407 // Sets the handle's position.
2408 mEventData->mDecorator->SetPosition( handleType,
2410 cursorInfo.lineOffset + offset.y,
2411 cursorInfo.lineHeight );
2413 // If selection handle at start of the text and other at end of the text then all text is selected.
2414 const CharacterIndex startOfSelection = std::min( mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition );
2415 const CharacterIndex endOfSelection = std::max ( mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition );
2416 mEventData->mAllTextSelected = ( startOfSelection == 0 ) && ( endOfSelection == mLogicalModel->mText.Count() );
2419 void Controller::Impl::ClampHorizontalScroll( const Vector2& actualSize )
2421 // Clamp between -space & 0 (and the text alignment).
2423 if( actualSize.width > mVisualModel->mControlSize.width )
2425 const float space = ( actualSize.width - mVisualModel->mControlSize.width ) + mAlignmentOffset.x;
2426 mEventData->mScrollPosition.x = ( mEventData->mScrollPosition.x < -space ) ? -space : mEventData->mScrollPosition.x;
2427 mEventData->mScrollPosition.x = ( mEventData->mScrollPosition.x > -mAlignmentOffset.x ) ? -mAlignmentOffset.x : mEventData->mScrollPosition.x;
2429 mEventData->mDecoratorUpdated = true;
2433 mEventData->mScrollPosition.x = 0.f;
2437 void Controller::Impl::ClampVerticalScroll( const Vector2& actualSize )
2439 // Clamp between -space & 0 (and the text alignment).
2440 if( actualSize.height > mVisualModel->mControlSize.height )
2442 const float space = ( actualSize.height - mVisualModel->mControlSize.height ) + mAlignmentOffset.y;
2443 mEventData->mScrollPosition.y = ( mEventData->mScrollPosition.y < -space ) ? -space : mEventData->mScrollPosition.y;
2444 mEventData->mScrollPosition.y = ( mEventData->mScrollPosition.y > -mAlignmentOffset.y ) ? -mAlignmentOffset.y : mEventData->mScrollPosition.y;
2446 mEventData->mDecoratorUpdated = true;
2450 mEventData->mScrollPosition.y = 0.f;
2454 void Controller::Impl::ScrollToMakePositionVisible( const Vector2& position )
2456 // position is in actor's coords.
2457 const float positionEnd = position.x + ( mEventData->mDecorator ? mEventData->mDecorator->GetCursorWidth() : 0.f );
2459 // Transform the position to decorator coords.
2460 const float alignment = IsShowingRealText() ? mAlignmentOffset.x : 0.f;
2461 const float offset = mEventData->mScrollPosition.x + alignment;
2462 const float decoratorPositionBegin = position.x + offset;
2463 const float decoratorPositionEnd = positionEnd + offset;
2465 if( decoratorPositionBegin < 0.f )
2467 mEventData->mScrollPosition.x = -position.x - alignment;
2469 else if( decoratorPositionEnd > mVisualModel->mControlSize.width )
2471 mEventData->mScrollPosition.x = mVisualModel->mControlSize.width - positionEnd - alignment;
2475 void Controller::Impl::ScrollTextToMatchCursor( const CursorInfo& cursorInfo )
2477 // Get the current cursor position in decorator coords.
2478 const Vector2& currentCursorPosition = mEventData->mDecorator->GetPosition( PRIMARY_CURSOR );
2480 // Calculate the offset to match the cursor position before the character was deleted.
2481 mEventData->mScrollPosition.x = currentCursorPosition.x - cursorInfo.primaryPosition.x - mAlignmentOffset.x;
2483 ClampHorizontalScroll( mVisualModel->GetLayoutSize() );
2486 void Controller::Impl::RequestRelayout()
2488 mControlInterface.RequestTextRelayout();
2493 } // namespace Toolkit