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 inputStyle.familyName.clear();
900 inputStyle.weight = TextAbstraction::FontWeight::NORMAL;
901 inputStyle.width = TextAbstraction::FontWidth::NORMAL;
902 inputStyle.slant = TextAbstraction::FontSlant::NORMAL;
903 inputStyle.size = 0.f;
905 inputStyle.familyDefined = false;
906 inputStyle.weightDefined = false;
907 inputStyle.widthDefined = false;
908 inputStyle.slantDefined = false;
909 inputStyle.sizeDefined = false;
911 // Sets the default font's family name, weight, width, slant and size.
914 if( mFontDefaults->familyDefined )
916 inputStyle.familyName = mFontDefaults->mFontDescription.family;
917 inputStyle.familyDefined = true;
920 if( mFontDefaults->weightDefined )
922 inputStyle.weight = mFontDefaults->mFontDescription.weight;
923 inputStyle.weightDefined = true;
926 if( mFontDefaults->widthDefined )
928 inputStyle.width = mFontDefaults->mFontDescription.width;
929 inputStyle.widthDefined = true;
932 if( mFontDefaults->slantDefined )
934 inputStyle.slant = mFontDefaults->mFontDescription.slant;
935 inputStyle.slantDefined = true;
938 if( mFontDefaults->sizeDefined )
940 inputStyle.size = mFontDefaults->mDefaultPointSize;
941 inputStyle.sizeDefined = true;
946 float Controller::Impl::GetDefaultFontLineHeight()
948 FontId defaultFontId = 0u;
949 if( NULL == mFontDefaults )
951 TextAbstraction::FontDescription fontDescription;
952 defaultFontId = mFontClient.GetFontId( fontDescription );
956 defaultFontId = mFontDefaults->GetFontId( mFontClient );
959 Text::FontMetrics fontMetrics;
960 mMetrics->GetFontMetrics( defaultFontId, fontMetrics );
962 return( fontMetrics.ascender - fontMetrics.descender );
965 void Controller::Impl::OnCursorKeyEvent( const Event& event )
967 if( NULL == mEventData )
969 // Nothing to do if there is no text input.
973 int keyCode = event.p1.mInt;
975 if( Dali::DALI_KEY_CURSOR_LEFT == keyCode )
977 if( mEventData->mPrimaryCursorPosition > 0u )
979 mEventData->mPrimaryCursorPosition = CalculateNewCursorIndex( mEventData->mPrimaryCursorPosition - 1u );
982 else if( Dali::DALI_KEY_CURSOR_RIGHT == keyCode )
984 if( mLogicalModel->mText.Count() > mEventData->mPrimaryCursorPosition )
986 mEventData->mPrimaryCursorPosition = CalculateNewCursorIndex( mEventData->mPrimaryCursorPosition );
989 else if( Dali::DALI_KEY_CURSOR_UP == keyCode )
993 else if( Dali::DALI_KEY_CURSOR_DOWN == keyCode )
998 mEventData->mUpdateCursorPosition = true;
999 mEventData->mUpdateInputStyle = true;
1000 mEventData->mScrollAfterUpdatePosition = true;
1003 void Controller::Impl::OnTapEvent( const Event& event )
1005 if( NULL != mEventData )
1007 const unsigned int tapCount = event.p1.mUint;
1009 if( 1u == tapCount )
1011 if( IsShowingRealText() )
1013 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
1014 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
1016 mEventData->mPrimaryCursorPosition = GetClosestCursorIndex( xPosition,
1019 // When the cursor position is changing, delay cursor blinking
1020 mEventData->mDecorator->DelayCursorBlink();
1024 mEventData->mPrimaryCursorPosition = 0u;
1027 mEventData->mUpdateCursorPosition = true;
1028 mEventData->mScrollAfterUpdatePosition = true;
1029 mEventData->mUpdateInputStyle = true;
1031 // Notify the cursor position to the imf manager.
1032 if( mEventData->mImfManager )
1034 mEventData->mImfManager.SetCursorPosition( mEventData->mPrimaryCursorPosition );
1035 mEventData->mImfManager.NotifyCursorPosition();
1041 void Controller::Impl::OnPanEvent( const Event& event )
1043 if( NULL == mEventData )
1045 // Nothing to do if there is no text input.
1049 int state = event.p1.mInt;
1051 if( Gesture::Started == state ||
1052 Gesture::Continuing == state )
1054 const Vector2& actualSize = mVisualModel->GetLayoutSize();
1055 const Vector2 currentScroll = mEventData->mScrollPosition;
1057 if( mEventData->mHorizontalScrollingEnabled )
1059 const float displacementX = event.p2.mFloat;
1060 mEventData->mScrollPosition.x += displacementX;
1062 ClampHorizontalScroll( actualSize );
1065 if( mEventData->mVerticalScrollingEnabled )
1067 const float displacementY = event.p3.mFloat;
1068 mEventData->mScrollPosition.y += displacementY;
1070 ClampVerticalScroll( actualSize );
1073 if( mEventData->mDecorator )
1075 mEventData->mDecorator->UpdatePositions( mEventData->mScrollPosition - currentScroll );
1080 void Controller::Impl::OnLongPressEvent( const Event& event )
1082 DALI_LOG_INFO( gLogFilter, Debug::General, "Controller::OnLongPressEvent\n" );
1084 if( EventData::EDITING == mEventData->mState )
1086 ChangeState ( EventData::EDITING_WITH_POPUP );
1087 mEventData->mDecoratorUpdated = true;
1091 void Controller::Impl::OnHandleEvent( const Event& event )
1093 if( NULL == mEventData )
1095 // Nothing to do if there is no text input.
1099 const unsigned int state = event.p1.mUint;
1100 const bool handleStopScrolling = ( HANDLE_STOP_SCROLLING == state );
1102 if( HANDLE_PRESSED == state )
1104 // The event.p2 and event.p3 are in decorator coords. Need to transforms to text coords.
1105 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
1106 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
1108 const CharacterIndex handleNewPosition = GetClosestCursorIndex( xPosition, yPosition );
1110 if( Event::GRAB_HANDLE_EVENT == event.type )
1112 ChangeState ( EventData::GRAB_HANDLE_PANNING );
1114 if( handleNewPosition != mEventData->mPrimaryCursorPosition )
1116 mEventData->mPrimaryCursorPosition = handleNewPosition;
1117 mEventData->mUpdateCursorPosition = true;
1120 else if( Event::LEFT_SELECTION_HANDLE_EVENT == event.type )
1122 ChangeState ( EventData::SELECTION_HANDLE_PANNING );
1124 if( ( handleNewPosition != mEventData->mLeftSelectionPosition ) &&
1125 ( handleNewPosition != mEventData->mRightSelectionPosition ) )
1127 mEventData->mLeftSelectionPosition = handleNewPosition;
1129 mEventData->mUpdateLeftSelectionPosition = true;
1132 else if( Event::RIGHT_SELECTION_HANDLE_EVENT == event.type )
1134 ChangeState ( EventData::SELECTION_HANDLE_PANNING );
1136 if( ( handleNewPosition != mEventData->mRightSelectionPosition ) &&
1137 ( handleNewPosition != mEventData->mLeftSelectionPosition ) )
1139 mEventData->mRightSelectionPosition = handleNewPosition;
1141 mEventData->mUpdateRightSelectionPosition = true;
1144 } // end ( HANDLE_PRESSED == state )
1145 else if( ( HANDLE_RELEASED == state ) ||
1146 handleStopScrolling )
1148 CharacterIndex handlePosition = 0u;
1149 if( handleStopScrolling )
1151 // The event.p2 and event.p3 are in decorator coords. Need to transforms to text coords.
1152 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
1153 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
1155 handlePosition = GetClosestCursorIndex( xPosition, yPosition );
1158 if( Event::GRAB_HANDLE_EVENT == event.type )
1160 mEventData->mUpdateCursorPosition = true;
1161 mEventData->mUpdateInputStyle = true;
1163 if( !IsClipboardEmpty() )
1165 ChangeState( EventData::EDITING_WITH_PASTE_POPUP ); // Moving grabhandle will show Paste Popup
1168 if( handleStopScrolling )
1170 mEventData->mScrollAfterUpdatePosition = mEventData->mPrimaryCursorPosition != handlePosition;
1171 mEventData->mPrimaryCursorPosition = handlePosition;
1174 else if( Event::LEFT_SELECTION_HANDLE_EVENT == event.type )
1176 ChangeState( EventData::SELECTING );
1178 if( handleStopScrolling )
1180 mEventData->mUpdateLeftSelectionPosition = ( mEventData->mRightSelectionPosition != handlePosition );
1181 mEventData->mScrollAfterUpdatePosition = mEventData->mUpdateLeftSelectionPosition;
1183 if( mEventData->mUpdateLeftSelectionPosition )
1185 mEventData->mLeftSelectionPosition = handlePosition;
1189 else if( Event::RIGHT_SELECTION_HANDLE_EVENT == event.type )
1191 ChangeState( EventData::SELECTING );
1193 if( handleStopScrolling )
1195 mEventData->mUpdateRightSelectionPosition = ( mEventData->mLeftSelectionPosition != handlePosition );
1196 mEventData->mScrollAfterUpdatePosition = mEventData->mUpdateRightSelectionPosition;
1197 if( mEventData->mUpdateRightSelectionPosition )
1199 mEventData->mRightSelectionPosition = handlePosition;
1204 mEventData->mDecoratorUpdated = true;
1205 } // end ( ( HANDLE_RELEASED == state ) || ( HANDLE_STOP_SCROLLING == state ) )
1206 else if( HANDLE_SCROLLING == state )
1208 const float xSpeed = event.p2.mFloat;
1209 const Vector2& actualSize = mVisualModel->GetLayoutSize();
1210 const Vector2 currentScrollPosition = mEventData->mScrollPosition;
1212 mEventData->mScrollPosition.x += xSpeed;
1214 ClampHorizontalScroll( actualSize );
1216 bool endOfScroll = false;
1217 if( Vector2::ZERO == ( currentScrollPosition - mEventData->mScrollPosition ) )
1219 // Notify the decorator there is no more text to scroll.
1220 // The decorator won't send more scroll events.
1221 mEventData->mDecorator->NotifyEndOfScroll();
1222 // Still need to set the position of the handle.
1226 // Set the position of the handle.
1227 const bool scrollRightDirection = xSpeed > 0.f;
1228 const bool leftSelectionHandleEvent = Event::LEFT_SELECTION_HANDLE_EVENT == event.type;
1229 const bool rightSelectionHandleEvent = Event::RIGHT_SELECTION_HANDLE_EVENT == event.type;
1231 if( Event::GRAB_HANDLE_EVENT == event.type )
1233 ChangeState( EventData::GRAB_HANDLE_PANNING );
1235 Vector2 position = mEventData->mDecorator->GetPosition( GRAB_HANDLE );
1237 // Position the grag handle close to either the left or right edge.
1238 position.x = scrollRightDirection ? 0.f : mVisualModel->mControlSize.width;
1240 // Get the new handle position.
1241 // The grab handle's position is in decorator coords. Need to transforms to text coords.
1242 const CharacterIndex handlePosition = GetClosestCursorIndex( position.x - mEventData->mScrollPosition.x - mAlignmentOffset.x,
1243 position.y - mEventData->mScrollPosition.y - mAlignmentOffset.y );
1245 mEventData->mUpdateCursorPosition = mEventData->mPrimaryCursorPosition != handlePosition;
1246 mEventData->mScrollAfterUpdatePosition = mEventData->mUpdateCursorPosition;
1247 mEventData->mPrimaryCursorPosition = handlePosition;
1248 mEventData->mUpdateInputStyle = mEventData->mUpdateCursorPosition;
1250 else if( leftSelectionHandleEvent || rightSelectionHandleEvent )
1252 // TODO: This is recalculating the selection box every time the text is scrolled with the selection handles.
1253 // Think if something can be done to save power.
1255 ChangeState( EventData::SELECTION_HANDLE_PANNING );
1257 Vector2 position = mEventData->mDecorator->GetPosition( leftSelectionHandleEvent ? Text::LEFT_SELECTION_HANDLE : Text::RIGHT_SELECTION_HANDLE );
1259 // Position the selection handle close to either the left or right edge.
1260 position.x = scrollRightDirection ? 0.f : mVisualModel->mControlSize.width;
1262 // Get the new handle position.
1263 // The selection handle's position is in decorator coords. Need to transforms to text coords.
1264 const CharacterIndex handlePosition = GetClosestCursorIndex( position.x - mEventData->mScrollPosition.x - mAlignmentOffset.x,
1265 position.y - mEventData->mScrollPosition.y - mAlignmentOffset.y );
1267 if( leftSelectionHandleEvent )
1269 const bool differentHandles = ( mEventData->mLeftSelectionPosition != handlePosition ) && ( mEventData->mRightSelectionPosition != handlePosition );
1270 mEventData->mUpdateLeftSelectionPosition = endOfScroll || differentHandles;
1271 if( differentHandles )
1273 mEventData->mLeftSelectionPosition = handlePosition;
1278 const bool differentHandles = ( mEventData->mRightSelectionPosition != handlePosition ) && ( mEventData->mLeftSelectionPosition != handlePosition );
1279 mEventData->mUpdateRightSelectionPosition = endOfScroll || differentHandles;
1280 if( differentHandles )
1282 mEventData->mRightSelectionPosition = handlePosition;
1286 if( mEventData->mUpdateLeftSelectionPosition || mEventData->mUpdateRightSelectionPosition )
1288 RepositionSelectionHandles();
1290 mEventData->mScrollAfterUpdatePosition = true;
1293 mEventData->mDecoratorUpdated = true;
1294 } // end ( HANDLE_SCROLLING == state )
1297 void Controller::Impl::OnSelectEvent( const Event& event )
1299 if( NULL == mEventData )
1301 // Nothing to do if there is no text.
1305 if( mEventData->mSelectionEnabled )
1307 // The event.p2 and event.p3 are in decorator coords. Need to transforms to text coords.
1308 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
1309 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
1311 // Calculates the logical position from the x,y coords.
1312 RepositionSelectionHandles( xPosition,
1315 mEventData->mUpdateLeftSelectionPosition = true;
1316 mEventData->mUpdateRightSelectionPosition = true;
1318 mEventData->mScrollAfterUpdatePosition = ( mEventData->mLeftSelectionPosition != mEventData->mRightSelectionPosition );
1322 void Controller::Impl::OnSelectAllEvent()
1324 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "OnSelectAllEvent mEventData->mSelectionEnabled%s \n", mEventData->mSelectionEnabled?"true":"false");
1326 if( NULL == mEventData )
1328 // Nothing to do if there is no text.
1332 if( mEventData->mSelectionEnabled )
1334 mEventData->mLeftSelectionPosition = 0u;
1335 mEventData->mRightSelectionPosition = mLogicalModel->mText.Count();
1337 mEventData->mScrollAfterUpdatePosition = true;
1338 mEventData->mUpdateLeftSelectionPosition = true;
1339 mEventData->mUpdateRightSelectionPosition = true;
1343 void Controller::Impl::RetrieveSelection( std::string& selectedText, bool deleteAfterRetrieval )
1345 if( mEventData->mLeftSelectionPosition == mEventData->mRightSelectionPosition )
1347 // Nothing to select if handles are in the same place.
1348 selectedText.clear();
1352 const bool handlesCrossed = mEventData->mLeftSelectionPosition > mEventData->mRightSelectionPosition;
1354 //Get start and end position of selection
1355 const CharacterIndex startOfSelectedText = handlesCrossed ? mEventData->mRightSelectionPosition : mEventData->mLeftSelectionPosition;
1356 const Length lengthOfSelectedText = ( handlesCrossed ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition ) - startOfSelectedText;
1358 Vector<Character>& utf32Characters = mLogicalModel->mText;
1359 const Length numberOfCharacters = utf32Characters.Count();
1361 // Validate the start and end selection points
1362 if( ( startOfSelectedText + lengthOfSelectedText ) <= numberOfCharacters )
1364 //Get text as a UTF8 string
1365 Utf32ToUtf8( &utf32Characters[startOfSelectedText], lengthOfSelectedText, selectedText );
1367 if( deleteAfterRetrieval ) // Only delete text if copied successfully
1369 // Set as input style the style of the first deleted character.
1370 mLogicalModel->RetrieveStyle( startOfSelectedText, mEventData->mInputStyle );
1372 mLogicalModel->UpdateTextStyleRuns( startOfSelectedText, -static_cast<int>( lengthOfSelectedText ) );
1374 // Mark the paragraphs to be updated.
1375 mTextUpdateInfo.mCharacterIndex = startOfSelectedText;
1376 mTextUpdateInfo.mNumberOfCharactersToRemove = lengthOfSelectedText;
1378 // Delete text between handles
1379 Vector<Character>::Iterator first = utf32Characters.Begin() + startOfSelectedText;
1380 Vector<Character>::Iterator last = first + lengthOfSelectedText;
1381 utf32Characters.Erase( first, last );
1383 // Scroll after delete.
1384 mEventData->mPrimaryCursorPosition = handlesCrossed ? mEventData->mRightSelectionPosition : mEventData->mLeftSelectionPosition;
1385 mEventData->mScrollAfterDelete = true;
1387 // Udpade the cursor position and the decorator.
1388 // Scroll after the position is updated if is not scrolling after delete.
1389 mEventData->mUpdateCursorPosition = true;
1390 mEventData->mScrollAfterUpdatePosition = !mEventData->mScrollAfterDelete;
1391 mEventData->mDecoratorUpdated = true;
1395 void Controller::Impl::ShowClipboard()
1399 mClipboard.ShowClipboard();
1403 void Controller::Impl::HideClipboard()
1407 mClipboard.HideClipboard();
1411 bool Controller::Impl::CopyStringToClipboard( std::string& source )
1413 //Send string to clipboard
1414 return ( mClipboard && mClipboard.SetItem( source ) );
1417 void Controller::Impl::SendSelectionToClipboard( bool deleteAfterSending )
1419 std::string selectedText;
1420 RetrieveSelection( selectedText, deleteAfterSending );
1421 CopyStringToClipboard( selectedText );
1422 ChangeState( EventData::EDITING );
1425 void Controller::Impl::GetTextFromClipboard( unsigned int itemIndex, std::string& retrievedString )
1429 retrievedString = mClipboard.GetItem( itemIndex );
1433 void Controller::Impl::RepositionSelectionHandles()
1435 CharacterIndex selectionStart = mEventData->mLeftSelectionPosition;
1436 CharacterIndex selectionEnd = mEventData->mRightSelectionPosition;
1438 if( selectionStart == selectionEnd )
1440 // Nothing to select if handles are in the same place.
1444 mEventData->mDecorator->ClearHighlights();
1446 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
1447 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
1448 const GlyphInfo* const glyphsBuffer = mVisualModel->mGlyphs.Begin();
1449 const Vector2* const positionsBuffer = mVisualModel->mGlyphPositions.Begin();
1450 const Length* const charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
1451 const CharacterIndex* const glyphToCharacterBuffer = mVisualModel->mGlyphsToCharacters.Begin();
1452 const CharacterDirection* const modelCharacterDirectionsBuffer = ( 0u != mLogicalModel->mCharacterDirections.Count() ) ? mLogicalModel->mCharacterDirections.Begin() : NULL;
1454 // TODO: Better algorithm to create the highlight box.
1455 // TODO: Multi-line.
1457 // Get the height of the line.
1458 const Vector<LineRun>& lines = mVisualModel->mLines;
1459 const LineRun& firstLine = *lines.Begin();
1460 const float height = firstLine.ascender + -firstLine.descender;
1462 const bool isLastCharacter = selectionEnd >= mLogicalModel->mText.Count();
1463 const bool startDirection = ( ( NULL == modelCharacterDirectionsBuffer ) ? false : *( modelCharacterDirectionsBuffer + selectionStart ) );
1464 const bool endDirection = ( ( NULL == modelCharacterDirectionsBuffer ) ? false : *( modelCharacterDirectionsBuffer + ( selectionEnd - ( isLastCharacter ? 1u : 0u ) ) ) );
1466 // Swap the indices if the start is greater than the end.
1467 const bool indicesSwapped = selectionStart > selectionEnd;
1469 // Tell the decorator to flip the selection handles if needed.
1470 mEventData->mDecorator->SetSelectionHandleFlipState( indicesSwapped, startDirection, endDirection );
1472 if( indicesSwapped )
1474 std::swap( selectionStart, selectionEnd );
1477 // Get the indices to the first and last selected glyphs.
1478 const CharacterIndex selectionEndMinusOne = selectionEnd - 1u;
1479 const GlyphIndex glyphStart = *( charactersToGlyphBuffer + selectionStart );
1480 const Length numberOfGlyphs = *( glyphsPerCharacterBuffer + selectionEndMinusOne );
1481 const GlyphIndex glyphEnd = *( charactersToGlyphBuffer + selectionEndMinusOne ) + ( ( numberOfGlyphs > 0 ) ? numberOfGlyphs - 1u : 0u );
1483 // Check if the first glyph is a ligature that must be broken like Latin ff, fi, or Arabic ﻻ, etc which needs special code.
1484 const Length numberOfCharactersStart = *( charactersPerGlyphBuffer + glyphStart );
1485 bool splitStartGlyph = ( numberOfCharactersStart > 1u ) && HasLigatureMustBreak( mLogicalModel->GetScript( selectionStart ) );
1487 // Check if the last glyph is a ligature that must be broken like Latin ff, fi, or Arabic ﻻ, etc which needs special code.
1488 const Length numberOfCharactersEnd = *( charactersPerGlyphBuffer + glyphEnd );
1489 bool splitEndGlyph = ( glyphStart != glyphEnd ) && ( numberOfCharactersEnd > 1u ) && HasLigatureMustBreak( mLogicalModel->GetScript( selectionEndMinusOne ) );
1491 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
1493 // Traverse the glyphs.
1494 for( GlyphIndex index = glyphStart; index <= glyphEnd; ++index )
1496 const GlyphInfo& glyph = *( glyphsBuffer + index );
1497 const Vector2& position = *( positionsBuffer + index );
1499 if( splitStartGlyph )
1501 // 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.
1503 const float glyphAdvance = glyph.advance / static_cast<float>( numberOfCharactersStart );
1504 const CharacterIndex interGlyphIndex = selectionStart - *( glyphToCharacterBuffer + glyphStart );
1505 // Get the direction of the character.
1506 CharacterDirection isCurrentRightToLeft = false;
1507 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
1509 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + selectionStart );
1512 // The end point could be in the middle of the ligature.
1513 // Calculate the number of characters selected.
1514 const Length numberOfCharacters = ( glyphStart == glyphEnd ) ? ( selectionEnd - selectionStart ) : ( numberOfCharactersStart - interGlyphIndex );
1516 const float xPosition = position.x - glyph.xBearing + offset.x + glyphAdvance * static_cast<float>( isCurrentRightToLeft ? ( numberOfCharactersStart - interGlyphIndex - numberOfCharacters ) : interGlyphIndex );
1518 mEventData->mDecorator->AddHighlight( xPosition,
1520 xPosition + static_cast<float>( numberOfCharacters ) * glyphAdvance,
1521 offset.y + height );
1523 splitStartGlyph = false;
1527 if( splitEndGlyph && ( index == glyphEnd ) )
1529 // 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.
1531 const float glyphAdvance = glyph.advance / static_cast<float>( numberOfCharactersEnd );
1532 const CharacterIndex interGlyphIndex = selectionEnd - *( glyphToCharacterBuffer + glyphEnd );
1533 // Get the direction of the character.
1534 CharacterDirection isCurrentRightToLeft = false;
1535 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
1537 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + selectionEnd );
1540 const Length numberOfCharacters = numberOfCharactersEnd - interGlyphIndex;
1542 const float xPosition = position.x - glyph.xBearing + offset.x + ( isCurrentRightToLeft ? ( glyphAdvance * static_cast<float>( numberOfCharacters ) ) : 0.f );
1543 mEventData->mDecorator->AddHighlight( xPosition,
1545 xPosition + static_cast<float>( interGlyphIndex ) * glyphAdvance,
1546 offset.y + height );
1548 splitEndGlyph = false;
1552 const float xPosition = position.x - glyph.xBearing + offset.x;
1553 mEventData->mDecorator->AddHighlight( xPosition,
1555 xPosition + glyph.advance,
1556 offset.y + height );
1559 CursorInfo primaryCursorInfo;
1560 GetCursorPosition( mEventData->mLeftSelectionPosition,
1561 primaryCursorInfo );
1563 CursorInfo secondaryCursorInfo;
1564 GetCursorPosition( mEventData->mRightSelectionPosition,
1565 secondaryCursorInfo );
1567 const Vector2 primaryPosition = primaryCursorInfo.primaryPosition + offset;
1568 const Vector2 secondaryPosition = secondaryCursorInfo.primaryPosition + offset;
1570 mEventData->mDecorator->SetPosition( LEFT_SELECTION_HANDLE,
1572 primaryCursorInfo.lineOffset + offset.y,
1573 primaryCursorInfo.lineHeight );
1575 mEventData->mDecorator->SetPosition( RIGHT_SELECTION_HANDLE,
1576 secondaryPosition.x,
1577 secondaryCursorInfo.lineOffset + offset.y,
1578 secondaryCursorInfo.lineHeight );
1580 // Cursor to be positioned at end of selection so if selection interrupted and edit mode restarted the cursor will be at end of selection
1581 mEventData->mPrimaryCursorPosition = ( indicesSwapped ) ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition;
1583 // Set the flag to update the decorator.
1584 mEventData->mDecoratorUpdated = true;
1587 void Controller::Impl::RepositionSelectionHandles( float visualX, float visualY )
1589 if( NULL == mEventData )
1591 // Nothing to do if there is no text input.
1595 if( IsShowingPlaceholderText() )
1597 // Nothing to do if there is the place-holder text.
1601 const Length numberOfGlyphs = mVisualModel->mGlyphs.Count();
1602 const Length numberOfLines = mVisualModel->mLines.Count();
1603 if( ( 0 == numberOfGlyphs ) ||
1604 ( 0 == numberOfLines ) )
1606 // Nothing to do if there is no text.
1610 // Find which word was selected
1611 CharacterIndex selectionStart( 0 );
1612 CharacterIndex selectionEnd( 0 );
1613 FindSelectionIndices( visualX, visualY, selectionStart, selectionEnd );
1614 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "%p selectionStart %d selectionEnd %d\n", this, selectionStart, selectionEnd );
1616 if( selectionStart == selectionEnd )
1618 ChangeState( EventData::EDITING );
1619 // Nothing to select. i.e. a white space, out of bounds
1623 mEventData->mLeftSelectionPosition = selectionStart;
1624 mEventData->mRightSelectionPosition = selectionEnd;
1627 void Controller::Impl::SetPopupButtons()
1630 * Sets the Popup buttons to be shown depending on State.
1632 * If SELECTING : CUT & COPY + ( PASTE & CLIPBOARD if content available to paste )
1634 * If EDITING_WITH_POPUP : SELECT & SELECT_ALL
1637 TextSelectionPopup::Buttons buttonsToShow = TextSelectionPopup::NONE;
1639 if( EventData::SELECTING == mEventData->mState )
1641 buttonsToShow = TextSelectionPopup::Buttons( TextSelectionPopup::CUT | TextSelectionPopup::COPY );
1643 if( !IsClipboardEmpty() )
1645 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
1646 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::CLIPBOARD ) );
1649 if( !mEventData->mAllTextSelected )
1651 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::SELECT_ALL ) );
1654 else if( EventData::EDITING_WITH_POPUP == mEventData->mState )
1656 if( mLogicalModel->mText.Count() && !IsShowingPlaceholderText() )
1658 buttonsToShow = TextSelectionPopup::Buttons( TextSelectionPopup::SELECT | TextSelectionPopup::SELECT_ALL );
1661 if( !IsClipboardEmpty() )
1663 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
1664 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::CLIPBOARD ) );
1667 else if( EventData::EDITING_WITH_PASTE_POPUP == mEventData->mState )
1669 if ( !IsClipboardEmpty() )
1671 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
1672 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::CLIPBOARD ) );
1676 mEventData->mDecorator->SetEnabledPopupButtons( buttonsToShow );
1679 void Controller::Impl::ChangeState( EventData::State newState )
1681 if( NULL == mEventData )
1683 // Nothing to do if there is no text input.
1687 DALI_LOG_INFO( gLogFilter, Debug::General, "ChangeState state:%d newstate:%d\n", mEventData->mState, newState );
1689 if( mEventData->mState != newState )
1691 mEventData->mState = newState;
1693 if( EventData::INACTIVE == mEventData->mState )
1695 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
1696 mEventData->mDecorator->StopCursorBlink();
1697 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1698 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1699 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1700 mEventData->mDecorator->SetPopupActive( false );
1701 mEventData->mDecoratorUpdated = true;
1704 else if( EventData::INTERRUPTED == mEventData->mState)
1706 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1707 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1708 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1709 mEventData->mDecorator->SetPopupActive( false );
1710 mEventData->mDecoratorUpdated = true;
1713 else if( EventData::SELECTING == mEventData->mState )
1715 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
1716 mEventData->mDecorator->StopCursorBlink();
1717 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1718 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true );
1719 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true );
1720 if( mEventData->mGrabHandlePopupEnabled )
1723 mEventData->mDecorator->SetPopupActive( true );
1725 mEventData->mDecoratorUpdated = true;
1727 else if( EventData::EDITING == mEventData->mState )
1729 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1730 if( mEventData->mCursorBlinkEnabled )
1732 mEventData->mDecorator->StartCursorBlink();
1734 // Grab handle is not shown until a tap is received whilst EDITING
1735 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1736 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1737 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1738 if( mEventData->mGrabHandlePopupEnabled )
1740 mEventData->mDecorator->SetPopupActive( false );
1742 mEventData->mDecoratorUpdated = true;
1745 else if( EventData::EDITING_WITH_POPUP == mEventData->mState )
1747 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "EDITING_WITH_POPUP \n", newState );
1749 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1750 if( mEventData->mCursorBlinkEnabled )
1752 mEventData->mDecorator->StartCursorBlink();
1754 if( mEventData->mSelectionEnabled )
1756 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1757 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1761 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
1763 if( mEventData->mGrabHandlePopupEnabled )
1766 mEventData->mDecorator->SetPopupActive( true );
1769 mEventData->mDecoratorUpdated = true;
1771 else if( EventData::EDITING_WITH_GRAB_HANDLE == mEventData->mState )
1773 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "EDITING_WITH_GRAB_HANDLE \n", newState );
1775 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1776 if( mEventData->mCursorBlinkEnabled )
1778 mEventData->mDecorator->StartCursorBlink();
1780 // Grab handle is not shown until a tap is received whilst EDITING
1781 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
1782 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1783 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1784 if( mEventData->mGrabHandlePopupEnabled )
1786 mEventData->mDecorator->SetPopupActive( false );
1788 mEventData->mDecoratorUpdated = true;
1791 else if( EventData::SELECTION_HANDLE_PANNING == mEventData->mState )
1793 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
1794 mEventData->mDecorator->StopCursorBlink();
1795 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1796 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true );
1797 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true );
1798 if( mEventData->mGrabHandlePopupEnabled )
1800 mEventData->mDecorator->SetPopupActive( false );
1802 mEventData->mDecoratorUpdated = true;
1804 else if( EventData::GRAB_HANDLE_PANNING == mEventData->mState )
1806 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "GRAB_HANDLE_PANNING \n", newState );
1808 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1809 if( mEventData->mCursorBlinkEnabled )
1811 mEventData->mDecorator->StartCursorBlink();
1813 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
1814 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1815 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1816 if( mEventData->mGrabHandlePopupEnabled )
1818 mEventData->mDecorator->SetPopupActive( false );
1820 mEventData->mDecoratorUpdated = true;
1822 else if( EventData::EDITING_WITH_PASTE_POPUP == mEventData->mState )
1824 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "EDITING_WITH_PASTE_POPUP \n", newState );
1826 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1827 if( mEventData->mCursorBlinkEnabled )
1829 mEventData->mDecorator->StartCursorBlink();
1832 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
1833 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1834 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1836 if( mEventData->mGrabHandlePopupEnabled )
1839 mEventData->mDecorator->SetPopupActive( true );
1842 mEventData->mDecoratorUpdated = true;
1847 LineIndex Controller::Impl::GetClosestLine( float y ) const
1849 float totalHeight = 0.f;
1850 LineIndex lineIndex = 0u;
1852 const Vector<LineRun>& lines = mVisualModel->mLines;
1853 for( LineIndex endLine = lines.Count();
1854 lineIndex < endLine;
1857 const LineRun& lineRun = lines[lineIndex];
1858 totalHeight += lineRun.ascender + -lineRun.descender;
1859 if( y < totalHeight )
1865 if( lineIndex == 0 )
1873 void Controller::Impl::FindSelectionIndices( float visualX, float visualY, CharacterIndex& startIndex, CharacterIndex& endIndex )
1875 CharacterIndex hitCharacter = GetClosestCursorIndex( visualX, visualY );
1876 DALI_ASSERT_DEBUG( hitCharacter <= mLogicalModel->mText.Count() && "GetClosestCursorIndex returned out of bounds index" );
1878 if( mLogicalModel->mText.Count() == 0 )
1880 return; // if model empty
1883 if( hitCharacter >= mLogicalModel->mText.Count() )
1885 // Closest hit character is the last character.
1886 if( hitCharacter == mLogicalModel->mText.Count() )
1888 hitCharacter--; //Hit character index set to last character in logical model
1892 // hitCharacter is out of bounds
1897 startIndex = hitCharacter;
1898 endIndex = hitCharacter;
1899 bool isHitCharacterWhitespace = TextAbstraction::IsWhiteSpace( mLogicalModel->mText[hitCharacter] );
1901 // Find the start and end of the text
1902 for( startIndex = hitCharacter; startIndex > 0; --startIndex )
1904 if( isHitCharacterWhitespace != TextAbstraction::IsWhiteSpace( mLogicalModel->mText[ startIndex-1 ] ) )
1909 const CharacterIndex pastTheEnd = mLogicalModel->mText.Count();
1910 for( endIndex = hitCharacter + 1u; endIndex < pastTheEnd; ++endIndex )
1912 if( isHitCharacterWhitespace != TextAbstraction::IsWhiteSpace( mLogicalModel->mText[ endIndex ] ) )
1919 CharacterIndex Controller::Impl::GetClosestCursorIndex( float visualX,
1922 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "GetClosestCursorIndex %p closest visualX %f visualY %f\n", this, visualX, visualY );
1924 if( NULL == mEventData )
1926 // Nothing to do if there is no text input.
1930 CharacterIndex logicalIndex = 0u;
1932 const Length numberOfGlyphs = mVisualModel->mGlyphs.Count();
1933 const Length numberOfLines = mVisualModel->mLines.Count();
1934 if( ( 0 == numberOfGlyphs ) ||
1935 ( 0 == numberOfLines ) )
1937 return logicalIndex;
1940 // Find which line is closest
1941 const LineIndex lineIndex = GetClosestLine( visualY );
1942 const LineRun& line = mVisualModel->mLines[lineIndex];
1944 // Get the positions of the glyphs.
1945 const Vector<Vector2>& positions = mVisualModel->mGlyphPositions;
1946 const Vector2* const positionsBuffer = positions.Begin();
1948 // Get the visual to logical conversion tables.
1949 const CharacterIndex* const visualToLogicalBuffer = ( 0u != mLogicalModel->mVisualToLogicalMap.Count() ) ? mLogicalModel->mVisualToLogicalMap.Begin() : NULL;
1950 const CharacterIndex* const visualToLogicalCursorBuffer = mLogicalModel->mVisualToLogicalCursorMap.Begin();
1952 // Get the character to glyph conversion table.
1953 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
1955 // Get the glyphs per character table.
1956 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
1958 // Get the glyph's info buffer.
1959 const GlyphInfo* const glyphInfoBuffer = mVisualModel->mGlyphs.Begin();
1961 // If the vector is void, there is no right to left characters.
1962 const bool hasRightToLeftCharacters = NULL != visualToLogicalBuffer;
1964 const CharacterIndex startCharacter = line.characterRun.characterIndex;
1965 const CharacterIndex endCharacter = line.characterRun.characterIndex + line.characterRun.numberOfCharacters;
1966 DALI_ASSERT_DEBUG( endCharacter <= mLogicalModel->mText.Count() && "Invalid line info" );
1968 // Whether there is a hit on a glyph.
1969 bool matched = false;
1971 // Traverses glyphs in visual order. To do that use the visual to logical conversion table.
1972 CharacterIndex visualIndex = startCharacter;
1973 Length numberOfCharacters = 0u;
1974 for( ; !matched && ( visualIndex < endCharacter ); ++visualIndex )
1976 // The character in logical order.
1977 const CharacterIndex characterLogicalOrderIndex = hasRightToLeftCharacters ? *( visualToLogicalBuffer + visualIndex ) : visualIndex;
1979 // Get the script of the character.
1980 const Script script = mLogicalModel->GetScript( characterLogicalOrderIndex );
1982 // The number of glyphs for that character
1983 const Length numberOfGlyphs = *( glyphsPerCharacterBuffer + characterLogicalOrderIndex );
1984 ++numberOfCharacters;
1987 if( 0u != numberOfGlyphs )
1989 // Get the first character/glyph of the group of glyphs.
1990 const CharacterIndex firstVisualCharacterIndex = 1u + visualIndex - numberOfCharacters;
1991 const CharacterIndex firstLogicalCharacterIndex = hasRightToLeftCharacters ? *( visualToLogicalBuffer + firstVisualCharacterIndex ) : firstVisualCharacterIndex;
1992 const GlyphIndex firstLogicalGlyphIndex = *( charactersToGlyphBuffer + firstLogicalCharacterIndex );
1994 // Get the metrics for the group of glyphs.
1995 GlyphMetrics glyphMetrics;
1996 GetGlyphsMetrics( firstLogicalGlyphIndex,
2002 // Get the position of the first glyph.
2003 const Vector2& position = *( positionsBuffer + firstLogicalGlyphIndex );
2005 // Whether the glyph can be split, like Latin ligatures fi, ff or Arabic ﻻ.
2006 const bool isInterglyphIndex = ( numberOfCharacters > numberOfGlyphs ) && HasLigatureMustBreak( script );
2007 const Length numberOfBlocks = isInterglyphIndex ? numberOfCharacters : 1u;
2008 const float glyphAdvance = glyphMetrics.advance / static_cast<float>( numberOfBlocks );
2010 GlyphIndex index = 0u;
2011 for( ; !matched && ( index < numberOfBlocks ); ++index )
2013 // Find the mid-point of the area containing the glyph
2014 const float glyphCenter = -glyphMetrics.xBearing + position.x + ( static_cast<float>( index ) + 0.5f ) * glyphAdvance;
2016 if( visualX < glyphCenter )
2025 visualIndex = firstVisualCharacterIndex + index;
2029 numberOfCharacters = 0u;
2035 // Return the logical position of the cursor in characters.
2039 visualIndex = endCharacter;
2042 logicalIndex = hasRightToLeftCharacters ? *( visualToLogicalCursorBuffer + visualIndex ) : visualIndex;
2043 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "%p closest visualIndex %d logicalIndex %d\n", this, visualIndex, logicalIndex );
2045 DALI_ASSERT_DEBUG( ( logicalIndex <= mLogicalModel->mText.Count() && logicalIndex >= 0 ) && "GetClosestCursorIndex - Out of bounds index" );
2047 return logicalIndex;
2050 void Controller::Impl::GetCursorPosition( CharacterIndex logical,
2051 CursorInfo& cursorInfo )
2053 // TODO: Check for multiline with \n, etc...
2055 const Length numberOfCharacters = mLogicalModel->mText.Count();
2056 if( !IsShowingRealText() )
2058 // Do not want to use the place-holder text to set the cursor position.
2060 // Use the line's height of the font's family set to set the cursor's size.
2061 // If there is no font's family set, use the default font.
2062 // Use the current alignment to place the cursor at the beginning, center or end of the box.
2064 cursorInfo.lineOffset = 0.f;
2065 cursorInfo.lineHeight = GetDefaultFontLineHeight();
2066 cursorInfo.primaryCursorHeight = cursorInfo.lineHeight;
2068 switch( mLayoutEngine.GetHorizontalAlignment() )
2070 case LayoutEngine::HORIZONTAL_ALIGN_BEGIN:
2072 cursorInfo.primaryPosition.x = 0.f;
2075 case LayoutEngine::HORIZONTAL_ALIGN_CENTER:
2077 cursorInfo.primaryPosition.x = floorf( 0.5f * mVisualModel->mControlSize.width );
2080 case LayoutEngine::HORIZONTAL_ALIGN_END:
2082 cursorInfo.primaryPosition.x = mVisualModel->mControlSize.width - mEventData->mDecorator->GetCursorWidth();
2087 switch( mLayoutEngine.GetVerticalAlignment() )
2089 case LayoutEngine::VERTICAL_ALIGN_TOP:
2091 cursorInfo.primaryPosition.y = 0.f;
2094 case LayoutEngine::VERTICAL_ALIGN_CENTER:
2096 cursorInfo.primaryPosition.y = floorf( 0.5f * ( mVisualModel->mControlSize.height - cursorInfo.lineHeight ) );
2099 case LayoutEngine::VERTICAL_ALIGN_BOTTOM:
2101 cursorInfo.primaryPosition.y = mVisualModel->mControlSize.height - cursorInfo.lineHeight;
2106 // Nothing else to do.
2110 // Check if the logical position is the first or the last one of the text.
2111 const bool isFirstPosition = 0u == logical;
2112 const bool isLastPosition = numberOfCharacters == logical;
2114 // 'logical' is the logical 'cursor' index.
2115 // Get the next and current logical 'character' index.
2116 const CharacterIndex nextCharacterIndex = logical;
2117 const CharacterIndex characterIndex = isFirstPosition ? logical : logical - 1u;
2119 // Get the direction of the character and the next one.
2120 const CharacterDirection* const modelCharacterDirectionsBuffer = ( 0u != mLogicalModel->mCharacterDirections.Count() ) ? mLogicalModel->mCharacterDirections.Begin() : NULL;
2122 CharacterDirection isCurrentRightToLeft = false;
2123 CharacterDirection isNextRightToLeft = false;
2124 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
2126 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + characterIndex );
2127 isNextRightToLeft = *( modelCharacterDirectionsBuffer + nextCharacterIndex );
2130 // Get the line where the character is laid-out.
2131 const LineRun* const modelLines = mVisualModel->mLines.Begin();
2133 const LineIndex lineIndex = mVisualModel->GetLineOfCharacter( characterIndex );
2134 const LineRun& line = *( modelLines + lineIndex );
2136 // Get the paragraph's direction.
2137 const CharacterDirection isRightToLeftParagraph = line.direction;
2139 // Check whether there is an alternative position:
2141 cursorInfo.isSecondaryCursor = ( !isLastPosition && ( isCurrentRightToLeft != isNextRightToLeft ) ) ||
2142 ( isLastPosition && ( isRightToLeftParagraph != isCurrentRightToLeft ) );
2144 // Set the line offset and height.
2145 cursorInfo.lineOffset = 0.f;
2146 cursorInfo.lineHeight = line.ascender + -line.descender;
2148 // Calculate the primary cursor.
2150 CharacterIndex index = characterIndex;
2151 if( cursorInfo.isSecondaryCursor )
2153 // If there is a secondary position, the primary cursor may be in a different place than the logical index.
2155 if( isLastPosition )
2157 // The position of the cursor after the last character needs special
2158 // care depending on its direction and the direction of the paragraph.
2160 // Need to find the first character after the last character with the paragraph's direction.
2161 // i.e l0 l1 l2 r0 r1 should find r0.
2163 // TODO: check for more than one line!
2164 index = isRightToLeftParagraph ? line.characterRun.characterIndex : line.characterRun.characterIndex + line.characterRun.numberOfCharacters - 1u;
2165 index = mLogicalModel->GetLogicalCharacterIndex( index );
2169 index = ( isRightToLeftParagraph == isCurrentRightToLeft ) ? characterIndex : nextCharacterIndex;
2173 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
2174 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
2175 const Length* const charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
2176 const CharacterIndex* const glyphsToCharactersBuffer = mVisualModel->mGlyphsToCharacters.Begin();
2177 const Vector2* const glyphPositionsBuffer = mVisualModel->mGlyphPositions.Begin();
2178 const GlyphInfo* const glyphInfoBuffer = mVisualModel->mGlyphs.Begin();
2180 // Convert the cursor position into the glyph position.
2181 const GlyphIndex primaryGlyphIndex = *( charactersToGlyphBuffer + index );
2182 const Length primaryNumberOfGlyphs = *( glyphsPerCharacterBuffer + index );
2183 const Length primaryNumberOfCharacters = *( charactersPerGlyphBuffer + primaryGlyphIndex );
2185 // Get the metrics for the group of glyphs.
2186 GlyphMetrics glyphMetrics;
2187 GetGlyphsMetrics( primaryGlyphIndex,
2188 primaryNumberOfGlyphs,
2193 // Whether to add the glyph's advance to the cursor position.
2194 // 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,
2195 // if the logical cursor is one, the position is the position of the first glyph and the advance is added.
2196 // A 'truth table' was build and an online Karnaugh map tool was used to simplify the logic.
2217 // Where F -> isFirstPosition
2218 // L -> isLastPosition
2219 // C -> isCurrentRightToLeft
2220 // P -> isRightToLeftParagraph
2221 // A -> Whether to add the glyph's advance.
2223 const bool addGlyphAdvance = ( ( isLastPosition && !isRightToLeftParagraph ) ||
2224 ( isFirstPosition && isRightToLeftParagraph ) ||
2225 ( !isFirstPosition && !isLastPosition && !isCurrentRightToLeft ) );
2227 float glyphAdvance = addGlyphAdvance ? glyphMetrics.advance : 0.f;
2229 if( !isLastPosition &&
2230 ( primaryNumberOfCharacters > 1u ) )
2232 const CharacterIndex firstIndex = *( glyphsToCharactersBuffer + primaryGlyphIndex );
2234 bool isCurrentRightToLeft = false;
2235 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
2237 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + index );
2240 Length numberOfGlyphAdvance = ( isFirstPosition ? 0u : 1u ) + characterIndex - firstIndex;
2241 if( isCurrentRightToLeft )
2243 numberOfGlyphAdvance = primaryNumberOfCharacters - numberOfGlyphAdvance;
2246 glyphAdvance = static_cast<float>( numberOfGlyphAdvance ) * glyphMetrics.advance / static_cast<float>( primaryNumberOfCharacters );
2249 // Get the glyph position and x bearing.
2250 const Vector2& primaryPosition = *( glyphPositionsBuffer + primaryGlyphIndex );
2252 // Set the primary cursor's height.
2253 cursorInfo.primaryCursorHeight = cursorInfo.isSecondaryCursor ? 0.5f * glyphMetrics.fontHeight : glyphMetrics.fontHeight;
2255 // Set the primary cursor's position.
2256 cursorInfo.primaryPosition.x = -glyphMetrics.xBearing + primaryPosition.x + glyphAdvance;
2257 cursorInfo.primaryPosition.y = line.ascender - glyphMetrics.ascender;
2259 // Calculate the secondary cursor.
2261 if( cursorInfo.isSecondaryCursor )
2263 // Set the secondary cursor's height.
2264 cursorInfo.secondaryCursorHeight = 0.5f * glyphMetrics.fontHeight;
2266 CharacterIndex index = characterIndex;
2267 if( !isLastPosition )
2269 index = ( isRightToLeftParagraph == isCurrentRightToLeft ) ? nextCharacterIndex : characterIndex;
2272 const GlyphIndex secondaryGlyphIndex = *( charactersToGlyphBuffer + index );
2273 const Length secondaryNumberOfGlyphs = *( glyphsPerCharacterBuffer + index );
2275 const Vector2& secondaryPosition = *( glyphPositionsBuffer + secondaryGlyphIndex );
2277 GetGlyphsMetrics( secondaryGlyphIndex,
2278 secondaryNumberOfGlyphs,
2283 // Set the secondary cursor's position.
2284 cursorInfo.secondaryPosition.x = -glyphMetrics.xBearing + secondaryPosition.x + ( isCurrentRightToLeft ? 0.f : glyphMetrics.advance );
2285 cursorInfo.secondaryPosition.y = cursorInfo.lineHeight - cursorInfo.secondaryCursorHeight - line.descender - ( glyphMetrics.fontHeight - glyphMetrics.ascender );
2288 if( LayoutEngine::MULTI_LINE_BOX == mLayoutEngine.GetLayout() )
2290 // If the text is editable and multi-line, the cursor position after a white space shouldn't exceed the boundaries of the text control.
2292 // Note the white spaces laid-out at the end of the line might exceed the boundaries of the control.
2293 // The reason is a wrapped line must not start with a white space so they are laid-out at the end of the line.
2295 if( 0.f > cursorInfo.primaryPosition.x )
2297 cursorInfo.primaryPosition.x = 0.f;
2300 const float edgeWidth = mVisualModel->mControlSize.width - static_cast<float>( mEventData->mDecorator->GetCursorWidth() );
2301 if( cursorInfo.primaryPosition.x > edgeWidth )
2303 cursorInfo.primaryPosition.x = edgeWidth;
2308 CharacterIndex Controller::Impl::CalculateNewCursorIndex( CharacterIndex index ) const
2310 if( NULL == mEventData )
2312 // Nothing to do if there is no text input.
2316 CharacterIndex cursorIndex = mEventData->mPrimaryCursorPosition;
2318 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
2319 const Length* const charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
2321 GlyphIndex glyphIndex = *( charactersToGlyphBuffer + index );
2322 Length numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
2324 if( numberOfCharacters > 1u )
2326 const Script script = mLogicalModel->GetScript( index );
2327 if( HasLigatureMustBreak( script ) )
2329 // Prevents to jump the whole Latin ligatures like fi, ff, or Arabic ﻻ, ...
2330 numberOfCharacters = 1u;
2335 while( 0u == numberOfCharacters )
2338 numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
2342 if( index < mEventData->mPrimaryCursorPosition )
2344 cursorIndex -= numberOfCharacters;
2348 cursorIndex += numberOfCharacters;
2354 void Controller::Impl::UpdateCursorPosition( const CursorInfo& cursorInfo )
2356 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "-->Controller::UpdateCursorPosition %p\n", this );
2357 if( NULL == mEventData )
2359 // Nothing to do if there is no text input.
2360 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition no event data\n" );
2364 const Vector2 offset = mEventData->mScrollPosition + ( IsShowingRealText() ? mAlignmentOffset : Vector2::ZERO );
2365 const Vector2 cursorPosition = cursorInfo.primaryPosition + offset;
2367 // Sets the cursor position.
2368 mEventData->mDecorator->SetPosition( PRIMARY_CURSOR,
2371 cursorInfo.primaryCursorHeight,
2372 cursorInfo.lineHeight );
2373 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Primary cursor position: %f,%f\n", cursorPosition.x, cursorPosition.y );
2375 // Sets the grab handle position.
2376 mEventData->mDecorator->SetPosition( GRAB_HANDLE,
2378 cursorInfo.lineOffset + offset.y,
2379 cursorInfo.lineHeight );
2381 if( cursorInfo.isSecondaryCursor )
2383 mEventData->mDecorator->SetPosition( SECONDARY_CURSOR,
2384 cursorInfo.secondaryPosition.x + offset.x,
2385 cursorInfo.secondaryPosition.y + offset.y,
2386 cursorInfo.secondaryCursorHeight,
2387 cursorInfo.lineHeight );
2388 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Secondary cursor position: %f,%f\n", cursorInfo.secondaryPosition.x + offset.x, cursorInfo.secondaryPosition.y + offset.y );
2391 // Set which cursors are active according the state.
2392 if( EventData::IsEditingState( mEventData->mState ) || ( EventData::GRAB_HANDLE_PANNING == mEventData->mState ) )
2394 if( cursorInfo.isSecondaryCursor )
2396 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_BOTH );
2400 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
2405 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
2408 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition\n" );
2411 void Controller::Impl::UpdateSelectionHandle( HandleType handleType,
2412 const CursorInfo& cursorInfo )
2414 if( ( LEFT_SELECTION_HANDLE != handleType ) &&
2415 ( RIGHT_SELECTION_HANDLE != handleType ) )
2420 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
2421 const Vector2 cursorPosition = cursorInfo.primaryPosition + offset;
2423 // Sets the handle's position.
2424 mEventData->mDecorator->SetPosition( handleType,
2426 cursorInfo.lineOffset + offset.y,
2427 cursorInfo.lineHeight );
2429 // If selection handle at start of the text and other at end of the text then all text is selected.
2430 const CharacterIndex startOfSelection = std::min( mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition );
2431 const CharacterIndex endOfSelection = std::max ( mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition );
2432 mEventData->mAllTextSelected = ( startOfSelection == 0 ) && ( endOfSelection == mLogicalModel->mText.Count() );
2435 void Controller::Impl::ClampHorizontalScroll( const Vector2& actualSize )
2437 // Clamp between -space & 0 (and the text alignment).
2439 if( actualSize.width > mVisualModel->mControlSize.width )
2441 const float space = ( actualSize.width - mVisualModel->mControlSize.width ) + mAlignmentOffset.x;
2442 mEventData->mScrollPosition.x = ( mEventData->mScrollPosition.x < -space ) ? -space : mEventData->mScrollPosition.x;
2443 mEventData->mScrollPosition.x = ( mEventData->mScrollPosition.x > -mAlignmentOffset.x ) ? -mAlignmentOffset.x : mEventData->mScrollPosition.x;
2445 mEventData->mDecoratorUpdated = true;
2449 mEventData->mScrollPosition.x = 0.f;
2453 void Controller::Impl::ClampVerticalScroll( const Vector2& actualSize )
2455 // Clamp between -space & 0 (and the text alignment).
2456 if( actualSize.height > mVisualModel->mControlSize.height )
2458 const float space = ( actualSize.height - mVisualModel->mControlSize.height ) + mAlignmentOffset.y;
2459 mEventData->mScrollPosition.y = ( mEventData->mScrollPosition.y < -space ) ? -space : mEventData->mScrollPosition.y;
2460 mEventData->mScrollPosition.y = ( mEventData->mScrollPosition.y > -mAlignmentOffset.y ) ? -mAlignmentOffset.y : mEventData->mScrollPosition.y;
2462 mEventData->mDecoratorUpdated = true;
2466 mEventData->mScrollPosition.y = 0.f;
2470 void Controller::Impl::ScrollToMakePositionVisible( const Vector2& position )
2472 // position is in actor's coords.
2473 const float positionEnd = position.x + ( mEventData->mDecorator ? mEventData->mDecorator->GetCursorWidth() : 0.f );
2475 // Transform the position to decorator coords.
2476 const float alignment = IsShowingRealText() ? mAlignmentOffset.x : 0.f;
2477 const float offset = mEventData->mScrollPosition.x + alignment;
2478 const float decoratorPositionBegin = position.x + offset;
2479 const float decoratorPositionEnd = positionEnd + offset;
2481 if( decoratorPositionBegin < 0.f )
2483 mEventData->mScrollPosition.x = -position.x - alignment;
2485 else if( decoratorPositionEnd > mVisualModel->mControlSize.width )
2487 mEventData->mScrollPosition.x = mVisualModel->mControlSize.width - positionEnd - alignment;
2491 void Controller::Impl::ScrollTextToMatchCursor( const CursorInfo& cursorInfo )
2493 // Get the current cursor position in decorator coords.
2494 const Vector2& currentCursorPosition = mEventData->mDecorator->GetPosition( PRIMARY_CURSOR );
2496 // Calculate the offset to match the cursor position before the character was deleted.
2497 mEventData->mScrollPosition.x = currentCursorPosition.x - cursorInfo.primaryPosition.x - mAlignmentOffset.x;
2499 ClampHorizontalScroll( mVisualModel->GetLayoutSize() );
2502 void Controller::Impl::RequestRelayout()
2504 mControlInterface.RequestTextRelayout();
2509 } // namespace Toolkit