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->mVisualToLogicalMap.Clear();
396 if( SHAPE_TEXT & operations )
398 mVisualModel->mGlyphs.Clear();
399 mVisualModel->mGlyphsToCharacters.Clear();
400 mVisualModel->mCharactersToGlyph.Clear();
401 mVisualModel->mCharactersPerGlyph.Clear();
402 mVisualModel->mGlyphsPerCharacter.Clear();
403 mVisualModel->mGlyphPositions.Clear();
406 if( LAYOUT & operations )
408 mVisualModel->mLines.Clear();
411 if( COLOR & operations )
413 mVisualModel->mColorIndices.Clear();
417 void Controller::Impl::ClearCharacterModelData( CharacterIndex startIndex, CharacterIndex endIndex, OperationsMask operations )
419 const CharacterIndex endIndexPlusOne = endIndex + 1u;
421 if( GET_LINE_BREAKS & operations )
423 // Clear the line break info.
424 LineBreakInfo* lineBreakInfoBuffer = mLogicalModel->mLineBreakInfo.Begin();
426 mLogicalModel->mLineBreakInfo.Erase( lineBreakInfoBuffer + startIndex,
427 lineBreakInfoBuffer + endIndexPlusOne );
429 // Clear the paragraphs.
430 ClearCharacterRuns( startIndex,
432 mLogicalModel->mParagraphInfo );
435 if( GET_WORD_BREAKS & operations )
437 // Clear the word break info.
438 WordBreakInfo* wordBreakInfoBuffer = mLogicalModel->mWordBreakInfo.Begin();
440 mLogicalModel->mWordBreakInfo.Erase( wordBreakInfoBuffer + startIndex,
441 wordBreakInfoBuffer + endIndexPlusOne );
444 if( GET_SCRIPTS & operations )
446 // Clear the scripts.
447 ClearCharacterRuns( startIndex,
449 mLogicalModel->mScriptRuns );
452 if( VALIDATE_FONTS & operations )
455 ClearCharacterRuns( startIndex,
457 mLogicalModel->mFontRuns );
460 if( 0u != mLogicalModel->mBidirectionalParagraphInfo.Count() )
462 if( BIDI_INFO & operations )
464 // Clear the bidirectional paragraph info.
465 ClearCharacterRuns( startIndex,
467 mLogicalModel->mBidirectionalParagraphInfo );
469 // Clear the character's directions.
470 CharacterDirection* characterDirectionsBuffer = mLogicalModel->mCharacterDirections.Begin();
472 mLogicalModel->mCharacterDirections.Erase( characterDirectionsBuffer + startIndex,
473 characterDirectionsBuffer + endIndexPlusOne );
476 if( REORDER & operations )
478 uint32_t startRemoveIndex = mLogicalModel->mBidirectionalLineInfo.Count();
479 uint32_t endRemoveIndex = startRemoveIndex;
480 ClearCharacterRuns( startIndex,
482 mLogicalModel->mBidirectionalLineInfo,
486 BidirectionalLineInfoRun* bidirectionalLineInfoBuffer = mLogicalModel->mBidirectionalLineInfo.Begin();
488 // Free the allocated memory used to store the conversion table in the bidirectional line info run.
489 for( Vector<BidirectionalLineInfoRun>::Iterator it = bidirectionalLineInfoBuffer + startRemoveIndex,
490 endIt = bidirectionalLineInfoBuffer + endRemoveIndex;
494 BidirectionalLineInfoRun& bidiLineInfo = *it;
496 free( bidiLineInfo.visualToLogicalMap );
497 bidiLineInfo.visualToLogicalMap = NULL;
500 mLogicalModel->mBidirectionalLineInfo.Erase( bidirectionalLineInfoBuffer + startRemoveIndex,
501 bidirectionalLineInfoBuffer + endRemoveIndex );
503 CharacterIndex* visualToLogicalMapBuffer = mLogicalModel->mVisualToLogicalMap.Begin();
504 mLogicalModel->mVisualToLogicalMap.Erase( visualToLogicalMapBuffer + startIndex,
505 visualToLogicalMapBuffer + endIndexPlusOne );
510 void Controller::Impl::ClearGlyphModelData( CharacterIndex startIndex, CharacterIndex endIndex, OperationsMask operations )
512 const CharacterIndex endIndexPlusOne = endIndex + 1u;
513 const Length numberOfCharactersRemoved = endIndexPlusOne - startIndex;
515 // Convert the character index to glyph index before deleting the character to glyph and the glyphs per character buffers.
516 GlyphIndex* charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
517 Length* glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
519 const GlyphIndex endGlyphIndexPlusOne = *( charactersToGlyphBuffer + endIndex ) + *( glyphsPerCharacterBuffer + endIndex );
520 const Length numberOfGlyphsRemoved = endGlyphIndexPlusOne - mTextUpdateInfo.mStartGlyphIndex;
522 if( SHAPE_TEXT & operations )
524 // Update the character to glyph indices.
525 for( Vector<GlyphIndex>::Iterator it = charactersToGlyphBuffer + endIndexPlusOne,
526 endIt = charactersToGlyphBuffer + mVisualModel->mCharactersToGlyph.Count();
530 CharacterIndex& index = *it;
531 index -= numberOfGlyphsRemoved;
534 // Clear the character to glyph conversion table.
535 mVisualModel->mCharactersToGlyph.Erase( charactersToGlyphBuffer + startIndex,
536 charactersToGlyphBuffer + endIndexPlusOne );
538 // Clear the glyphs per character table.
539 mVisualModel->mGlyphsPerCharacter.Erase( glyphsPerCharacterBuffer + startIndex,
540 glyphsPerCharacterBuffer + endIndexPlusOne );
542 // Clear the glyphs buffer.
543 GlyphInfo* glyphsBuffer = mVisualModel->mGlyphs.Begin();
544 mVisualModel->mGlyphs.Erase( glyphsBuffer + mTextUpdateInfo.mStartGlyphIndex,
545 glyphsBuffer + endGlyphIndexPlusOne );
547 CharacterIndex* glyphsToCharactersBuffer = mVisualModel->mGlyphsToCharacters.Begin();
549 // Update the glyph to character indices.
550 for( Vector<CharacterIndex>::Iterator it = glyphsToCharactersBuffer + endGlyphIndexPlusOne,
551 endIt = glyphsToCharactersBuffer + mVisualModel->mGlyphsToCharacters.Count();
555 CharacterIndex& index = *it;
556 index -= numberOfCharactersRemoved;
559 // Clear the glyphs to characters buffer.
560 mVisualModel->mGlyphsToCharacters.Erase( glyphsToCharactersBuffer + mTextUpdateInfo.mStartGlyphIndex,
561 glyphsToCharactersBuffer + endGlyphIndexPlusOne );
563 // Clear the characters per glyph buffer.
564 Length* charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
565 mVisualModel->mCharactersPerGlyph.Erase( charactersPerGlyphBuffer + mTextUpdateInfo.mStartGlyphIndex,
566 charactersPerGlyphBuffer + endGlyphIndexPlusOne );
568 // Clear the positions buffer.
569 Vector2* positionsBuffer = mVisualModel->mGlyphPositions.Begin();
570 mVisualModel->mGlyphPositions.Erase( positionsBuffer + mTextUpdateInfo.mStartGlyphIndex,
571 positionsBuffer + endGlyphIndexPlusOne );
574 if( LAYOUT & operations )
577 uint32_t startRemoveIndex = mVisualModel->mLines.Count();
578 uint32_t endRemoveIndex = startRemoveIndex;
579 ClearCharacterRuns( startIndex,
581 mVisualModel->mLines,
585 // Will update the glyph runs.
586 startRemoveIndex = mVisualModel->mLines.Count();
587 endRemoveIndex = startRemoveIndex;
588 ClearGlyphRuns( mTextUpdateInfo.mStartGlyphIndex,
589 endGlyphIndexPlusOne - 1u,
590 mVisualModel->mLines,
594 // Set the line index from where to insert the new laid-out lines.
595 mTextUpdateInfo.mStartLineIndex = startRemoveIndex;
597 LineRun* linesBuffer = mVisualModel->mLines.Begin();
598 mVisualModel->mLines.Erase( linesBuffer + startRemoveIndex,
599 linesBuffer + endRemoveIndex );
602 if( COLOR & operations )
604 if( 0u != mVisualModel->mColorIndices.Count() )
606 ColorIndex* colorIndexBuffer = mVisualModel->mColorIndices.Begin();
607 mVisualModel->mColorIndices.Erase( colorIndexBuffer + mTextUpdateInfo.mStartGlyphIndex,
608 colorIndexBuffer + endGlyphIndexPlusOne );
613 void Controller::Impl::ClearModelData( CharacterIndex startIndex, CharacterIndex endIndex, OperationsMask operations )
615 if( mTextUpdateInfo.mClearAll ||
616 ( ( 0u == startIndex ) &&
617 ( mTextUpdateInfo.mPreviousNumberOfCharacters == endIndex + 1u ) ) )
619 ClearFullModelData( operations );
623 // Clear the model data related with characters.
624 ClearCharacterModelData( startIndex, endIndex, operations );
626 // Clear the model data related with glyphs.
627 ClearGlyphModelData( startIndex, endIndex, operations );
630 // The estimated number of lines. Used to avoid reallocations when layouting.
631 mTextUpdateInfo.mEstimatedNumberOfLines = std::max( mVisualModel->mLines.Count(), mLogicalModel->mParagraphInfo.Count() );
633 mVisualModel->ClearCaches();
636 bool Controller::Impl::UpdateModel( OperationsMask operationsRequired )
638 DALI_LOG_INFO( gLogFilter, Debug::General, "Controller::UpdateModel\n" );
640 // Calculate the operations to be done.
641 const OperationsMask operations = static_cast<OperationsMask>( mOperationsPending & operationsRequired );
643 if( NO_OPERATION == operations )
645 // Nothing to do if no operations are pending and required.
649 Vector<Character>& utf32Characters = mLogicalModel->mText;
651 const Length numberOfCharacters = utf32Characters.Count();
653 // Index to the first character of the first paragraph to be updated.
654 CharacterIndex startIndex = 0u;
655 // Number of characters of the paragraphs to be removed.
656 Length paragraphCharacters = 0u;
658 CalculateTextUpdateIndices( paragraphCharacters );
659 startIndex = mTextUpdateInfo.mParagraphCharacterIndex;
661 if( mTextUpdateInfo.mClearAll ||
662 ( 0u != paragraphCharacters ) )
664 ClearModelData( startIndex, startIndex + ( ( paragraphCharacters > 0u ) ? paragraphCharacters - 1u : 0u ), operationsRequired );
667 mTextUpdateInfo.mClearAll = false;
669 // Whether the model is updated.
670 bool updated = false;
672 Vector<LineBreakInfo>& lineBreakInfo = mLogicalModel->mLineBreakInfo;
673 const Length requestedNumberOfCharacters = mTextUpdateInfo.mRequestedNumberOfCharacters;
675 if( GET_LINE_BREAKS & operations )
677 // Retrieves the line break info. The line break info is used to split the text in 'paragraphs' to
678 // calculate the bidirectional info for each 'paragraph'.
679 // It's also used to layout the text (where it should be a new line) or to shape the text (text in different lines
680 // is not shaped together).
681 lineBreakInfo.Resize( numberOfCharacters, TextAbstraction::LINE_NO_BREAK );
683 SetLineBreakInfo( utf32Characters,
685 requestedNumberOfCharacters,
688 // Create the paragraph info.
689 mLogicalModel->CreateParagraphInfo( startIndex,
690 requestedNumberOfCharacters );
694 Vector<WordBreakInfo>& wordBreakInfo = mLogicalModel->mWordBreakInfo;
695 if( GET_WORD_BREAKS & operations )
697 // Retrieves the word break info. The word break info is used to layout the text (where to wrap the text in lines).
698 wordBreakInfo.Resize( numberOfCharacters, TextAbstraction::WORD_NO_BREAK );
700 SetWordBreakInfo( utf32Characters,
702 requestedNumberOfCharacters,
707 const bool getScripts = GET_SCRIPTS & operations;
708 const bool validateFonts = VALIDATE_FONTS & operations;
710 Vector<ScriptRun>& scripts = mLogicalModel->mScriptRuns;
711 Vector<FontRun>& validFonts = mLogicalModel->mFontRuns;
713 if( getScripts || validateFonts )
715 // Validates the fonts assigned by the application or assigns default ones.
716 // It makes sure all the characters are going to be rendered by the correct font.
717 MultilanguageSupport multilanguageSupport = MultilanguageSupport::Get();
721 // Retrieves the scripts used in the text.
722 multilanguageSupport.SetScripts( utf32Characters,
724 requestedNumberOfCharacters,
730 // Validate the fonts set through the mark-up string.
731 Vector<FontDescriptionRun>& fontDescriptionRuns = mLogicalModel->mFontDescriptionRuns;
733 // Get the default font id.
734 const FontId defaultFontId = ( NULL == mFontDefaults ) ? 0u : mFontDefaults->GetFontId( mFontClient );
736 // Validates the fonts. If there is a character with no assigned font it sets a default one.
737 // After this call, fonts are validated.
738 multilanguageSupport.ValidateFonts( utf32Characters,
743 requestedNumberOfCharacters,
749 Vector<Character> mirroredUtf32Characters;
750 bool textMirrored = false;
751 const Length numberOfParagraphs = mLogicalModel->mParagraphInfo.Count();
752 if( BIDI_INFO & operations )
754 Vector<BidirectionalParagraphInfoRun>& bidirectionalInfo = mLogicalModel->mBidirectionalParagraphInfo;
755 bidirectionalInfo.Reserve( numberOfParagraphs );
757 // Calculates the bidirectional info for the whole paragraph if it contains right to left scripts.
758 SetBidirectionalInfo( utf32Characters,
762 requestedNumberOfCharacters,
765 if( 0u != bidirectionalInfo.Count() )
767 // Only set the character directions if there is right to left characters.
768 Vector<CharacterDirection>& directions = mLogicalModel->mCharacterDirections;
769 GetCharactersDirection( bidirectionalInfo,
772 requestedNumberOfCharacters,
775 // This paragraph has right to left text. Some characters may need to be mirrored.
776 // TODO: consider if the mirrored string can be stored as well.
778 textMirrored = GetMirroredText( utf32Characters,
782 requestedNumberOfCharacters,
783 mirroredUtf32Characters );
787 // There is no right to left characters. Clear the directions vector.
788 mLogicalModel->mCharacterDirections.Clear();
793 Vector<GlyphInfo>& glyphs = mVisualModel->mGlyphs;
794 Vector<CharacterIndex>& glyphsToCharactersMap = mVisualModel->mGlyphsToCharacters;
795 Vector<Length>& charactersPerGlyph = mVisualModel->mCharactersPerGlyph;
796 Vector<GlyphIndex> newParagraphGlyphs;
797 newParagraphGlyphs.Reserve( numberOfParagraphs );
799 const Length currentNumberOfGlyphs = glyphs.Count();
800 if( SHAPE_TEXT & operations )
802 const Vector<Character>& textToShape = textMirrored ? mirroredUtf32Characters : utf32Characters;
804 ShapeText( textToShape,
809 mTextUpdateInfo.mStartGlyphIndex,
810 requestedNumberOfCharacters,
812 glyphsToCharactersMap,
814 newParagraphGlyphs );
816 // Create the 'number of glyphs' per character and the glyph to character conversion tables.
817 mVisualModel->CreateGlyphsPerCharacterTable( startIndex, mTextUpdateInfo.mStartGlyphIndex, requestedNumberOfCharacters );
818 mVisualModel->CreateCharacterToGlyphTable( startIndex, mTextUpdateInfo.mStartGlyphIndex, requestedNumberOfCharacters );
822 const Length numberOfGlyphs = glyphs.Count() - currentNumberOfGlyphs;
824 if( GET_GLYPH_METRICS & operations )
826 GlyphInfo* glyphsBuffer = glyphs.Begin();
827 mMetrics->GetGlyphMetrics( glyphsBuffer + mTextUpdateInfo.mStartGlyphIndex, numberOfGlyphs );
829 // Update the width and advance of all new paragraph characters.
830 for( Vector<GlyphIndex>::ConstIterator it = newParagraphGlyphs.Begin(), endIt = newParagraphGlyphs.End(); it != endIt; ++it )
832 const GlyphIndex index = *it;
833 GlyphInfo& glyph = *( glyphsBuffer + index );
835 glyph.xBearing = 0.f;
842 if( COLOR & operationsRequired )
844 // Set the color runs in glyphs.
845 SetColorSegmentationInfo( mLogicalModel->mColorRuns,
846 mVisualModel->mCharactersToGlyph,
847 mVisualModel->mGlyphsPerCharacter,
849 mTextUpdateInfo.mStartGlyphIndex,
850 requestedNumberOfCharacters,
851 mVisualModel->mColors,
852 mVisualModel->mColorIndices );
857 if( ( NULL != mEventData ) &&
858 mEventData->mPreEditFlag &&
859 ( 0u != mVisualModel->mCharactersToGlyph.Count() ) )
861 // Add the underline for the pre-edit text.
862 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
863 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
865 const GlyphIndex glyphStart = *( charactersToGlyphBuffer + mEventData->mPreEditStartPosition );
866 const CharacterIndex lastPreEditCharacter = mEventData->mPreEditStartPosition + ( ( mEventData->mPreEditLength > 0u ) ? mEventData->mPreEditLength - 1u : 0u );
867 const Length numberOfGlyphsLastCharacter = *( glyphsPerCharacterBuffer + lastPreEditCharacter );
868 const GlyphIndex glyphEnd = *( charactersToGlyphBuffer + lastPreEditCharacter ) + ( numberOfGlyphsLastCharacter > 1u ? numberOfGlyphsLastCharacter - 1u : 0u );
870 GlyphRun underlineRun;
871 underlineRun.glyphIndex = glyphStart;
872 underlineRun.numberOfGlyphs = 1u + glyphEnd - glyphStart;
874 // TODO: At the moment the underline runs are only for pre-edit.
875 mVisualModel->mUnderlineRuns.PushBack( underlineRun );
878 // The estimated number of lines. Used to avoid reallocations when layouting.
879 mTextUpdateInfo.mEstimatedNumberOfLines = std::max( mVisualModel->mLines.Count(), mLogicalModel->mParagraphInfo.Count() );
881 // Set the previous number of characters for the next time the text is updated.
882 mTextUpdateInfo.mPreviousNumberOfCharacters = numberOfCharacters;
887 void Controller::Impl::RetrieveDefaultInputStyle( InputStyle& inputStyle )
889 // Sets the default text's color.
890 inputStyle.textColor = mTextColor;
891 inputStyle.isDefaultColor = true;
893 inputStyle.familyName.clear();
894 inputStyle.weight = TextAbstraction::FontWeight::NORMAL;
895 inputStyle.width = TextAbstraction::FontWidth::NORMAL;
896 inputStyle.slant = TextAbstraction::FontSlant::NORMAL;
897 inputStyle.size = 0.f;
899 inputStyle.familyDefined = false;
900 inputStyle.weightDefined = false;
901 inputStyle.widthDefined = false;
902 inputStyle.slantDefined = false;
903 inputStyle.sizeDefined = false;
905 // Sets the default font's family name, weight, width, slant and size.
908 if( mFontDefaults->familyDefined )
910 inputStyle.familyName = mFontDefaults->mFontDescription.family;
911 inputStyle.familyDefined = true;
914 if( mFontDefaults->weightDefined )
916 inputStyle.weight = mFontDefaults->mFontDescription.weight;
917 inputStyle.weightDefined = true;
920 if( mFontDefaults->widthDefined )
922 inputStyle.width = mFontDefaults->mFontDescription.width;
923 inputStyle.widthDefined = true;
926 if( mFontDefaults->slantDefined )
928 inputStyle.slant = mFontDefaults->mFontDescription.slant;
929 inputStyle.slantDefined = true;
932 if( mFontDefaults->sizeDefined )
934 inputStyle.size = mFontDefaults->mDefaultPointSize;
935 inputStyle.sizeDefined = true;
940 float Controller::Impl::GetDefaultFontLineHeight()
942 FontId defaultFontId = 0u;
943 if( NULL == mFontDefaults )
945 TextAbstraction::FontDescription fontDescription;
946 defaultFontId = mFontClient.GetFontId( fontDescription );
950 defaultFontId = mFontDefaults->GetFontId( mFontClient );
953 Text::FontMetrics fontMetrics;
954 mMetrics->GetFontMetrics( defaultFontId, fontMetrics );
956 return( fontMetrics.ascender - fontMetrics.descender );
959 void Controller::Impl::OnCursorKeyEvent( const Event& event )
961 if( NULL == mEventData )
963 // Nothing to do if there is no text input.
967 int keyCode = event.p1.mInt;
969 if( Dali::DALI_KEY_CURSOR_LEFT == keyCode )
971 if( mEventData->mPrimaryCursorPosition > 0u )
973 mEventData->mPrimaryCursorPosition = CalculateNewCursorIndex( mEventData->mPrimaryCursorPosition - 1u );
976 else if( Dali::DALI_KEY_CURSOR_RIGHT == keyCode )
978 if( mLogicalModel->mText.Count() > mEventData->mPrimaryCursorPosition )
980 mEventData->mPrimaryCursorPosition = CalculateNewCursorIndex( mEventData->mPrimaryCursorPosition );
983 else if( Dali::DALI_KEY_CURSOR_UP == keyCode )
987 else if( Dali::DALI_KEY_CURSOR_DOWN == keyCode )
992 mEventData->mUpdateCursorPosition = true;
993 mEventData->mUpdateInputStyle = true;
994 mEventData->mScrollAfterUpdatePosition = true;
997 void Controller::Impl::OnTapEvent( const Event& event )
999 if( NULL != mEventData )
1001 const unsigned int tapCount = event.p1.mUint;
1003 if( 1u == tapCount )
1005 if( IsShowingRealText() )
1007 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
1008 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
1010 mEventData->mPrimaryCursorPosition = GetClosestCursorIndex( xPosition,
1013 // When the cursor position is changing, delay cursor blinking
1014 mEventData->mDecorator->DelayCursorBlink();
1018 mEventData->mPrimaryCursorPosition = 0u;
1021 mEventData->mUpdateCursorPosition = true;
1022 mEventData->mScrollAfterUpdatePosition = true;
1023 mEventData->mUpdateInputStyle = true;
1025 // Notify the cursor position to the imf manager.
1026 if( mEventData->mImfManager )
1028 mEventData->mImfManager.SetCursorPosition( mEventData->mPrimaryCursorPosition );
1029 mEventData->mImfManager.NotifyCursorPosition();
1035 void Controller::Impl::OnPanEvent( const Event& event )
1037 if( NULL == mEventData )
1039 // Nothing to do if there is no text input.
1043 int state = event.p1.mInt;
1045 if( Gesture::Started == state ||
1046 Gesture::Continuing == state )
1048 const Vector2& actualSize = mVisualModel->GetLayoutSize();
1049 const Vector2 currentScroll = mEventData->mScrollPosition;
1051 if( mEventData->mHorizontalScrollingEnabled )
1053 const float displacementX = event.p2.mFloat;
1054 mEventData->mScrollPosition.x += displacementX;
1056 ClampHorizontalScroll( actualSize );
1059 if( mEventData->mVerticalScrollingEnabled )
1061 const float displacementY = event.p3.mFloat;
1062 mEventData->mScrollPosition.y += displacementY;
1064 ClampVerticalScroll( actualSize );
1067 if( mEventData->mDecorator )
1069 mEventData->mDecorator->UpdatePositions( mEventData->mScrollPosition - currentScroll );
1074 void Controller::Impl::OnLongPressEvent( const Event& event )
1076 DALI_LOG_INFO( gLogFilter, Debug::General, "Controller::OnLongPressEvent\n" );
1078 if( EventData::EDITING == mEventData->mState )
1080 ChangeState ( EventData::EDITING_WITH_POPUP );
1081 mEventData->mDecoratorUpdated = true;
1085 void Controller::Impl::OnHandleEvent( const Event& event )
1087 if( NULL == mEventData )
1089 // Nothing to do if there is no text input.
1093 const unsigned int state = event.p1.mUint;
1094 const bool handleStopScrolling = ( HANDLE_STOP_SCROLLING == state );
1096 if( HANDLE_PRESSED == state )
1098 // The event.p2 and event.p3 are in decorator coords. Need to transforms to text coords.
1099 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
1100 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
1102 const CharacterIndex handleNewPosition = GetClosestCursorIndex( xPosition, yPosition );
1104 if( Event::GRAB_HANDLE_EVENT == event.type )
1106 ChangeState ( EventData::GRAB_HANDLE_PANNING );
1108 if( handleNewPosition != mEventData->mPrimaryCursorPosition )
1110 mEventData->mPrimaryCursorPosition = handleNewPosition;
1111 mEventData->mUpdateCursorPosition = true;
1114 else if( Event::LEFT_SELECTION_HANDLE_EVENT == event.type )
1116 ChangeState ( EventData::SELECTION_HANDLE_PANNING );
1118 if( ( handleNewPosition != mEventData->mLeftSelectionPosition ) &&
1119 ( handleNewPosition != mEventData->mRightSelectionPosition ) )
1121 mEventData->mLeftSelectionPosition = handleNewPosition;
1123 mEventData->mUpdateLeftSelectionPosition = true;
1126 else if( Event::RIGHT_SELECTION_HANDLE_EVENT == event.type )
1128 ChangeState ( EventData::SELECTION_HANDLE_PANNING );
1130 if( ( handleNewPosition != mEventData->mRightSelectionPosition ) &&
1131 ( handleNewPosition != mEventData->mLeftSelectionPosition ) )
1133 mEventData->mRightSelectionPosition = handleNewPosition;
1135 mEventData->mUpdateRightSelectionPosition = true;
1138 } // end ( HANDLE_PRESSED == state )
1139 else if( ( HANDLE_RELEASED == state ) ||
1140 handleStopScrolling )
1142 CharacterIndex handlePosition = 0u;
1143 if( handleStopScrolling )
1145 // The event.p2 and event.p3 are in decorator coords. Need to transforms to text coords.
1146 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
1147 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
1149 handlePosition = GetClosestCursorIndex( xPosition, yPosition );
1152 if( Event::GRAB_HANDLE_EVENT == event.type )
1154 mEventData->mUpdateCursorPosition = true;
1155 mEventData->mUpdateInputStyle = true;
1157 if( !IsClipboardEmpty() )
1159 ChangeState( EventData::EDITING_WITH_PASTE_POPUP ); // Moving grabhandle will show Paste Popup
1162 if( handleStopScrolling )
1164 mEventData->mScrollAfterUpdatePosition = mEventData->mPrimaryCursorPosition != handlePosition;
1165 mEventData->mPrimaryCursorPosition = handlePosition;
1168 else if( Event::LEFT_SELECTION_HANDLE_EVENT == event.type )
1170 ChangeState( EventData::SELECTING );
1172 if( handleStopScrolling )
1174 mEventData->mUpdateLeftSelectionPosition = ( mEventData->mRightSelectionPosition != handlePosition );
1175 mEventData->mScrollAfterUpdatePosition = mEventData->mUpdateLeftSelectionPosition;
1177 if( mEventData->mUpdateLeftSelectionPosition )
1179 mEventData->mLeftSelectionPosition = handlePosition;
1183 else if( Event::RIGHT_SELECTION_HANDLE_EVENT == event.type )
1185 ChangeState( EventData::SELECTING );
1187 if( handleStopScrolling )
1189 mEventData->mUpdateRightSelectionPosition = ( mEventData->mLeftSelectionPosition != handlePosition );
1190 mEventData->mScrollAfterUpdatePosition = mEventData->mUpdateRightSelectionPosition;
1191 if( mEventData->mUpdateRightSelectionPosition )
1193 mEventData->mRightSelectionPosition = handlePosition;
1198 mEventData->mDecoratorUpdated = true;
1199 } // end ( ( HANDLE_RELEASED == state ) || ( HANDLE_STOP_SCROLLING == state ) )
1200 else if( HANDLE_SCROLLING == state )
1202 const float xSpeed = event.p2.mFloat;
1203 const Vector2& actualSize = mVisualModel->GetLayoutSize();
1204 const Vector2 currentScrollPosition = mEventData->mScrollPosition;
1206 mEventData->mScrollPosition.x += xSpeed;
1208 ClampHorizontalScroll( actualSize );
1210 bool endOfScroll = false;
1211 if( Vector2::ZERO == ( currentScrollPosition - mEventData->mScrollPosition ) )
1213 // Notify the decorator there is no more text to scroll.
1214 // The decorator won't send more scroll events.
1215 mEventData->mDecorator->NotifyEndOfScroll();
1216 // Still need to set the position of the handle.
1220 // Set the position of the handle.
1221 const bool scrollRightDirection = xSpeed > 0.f;
1222 const bool leftSelectionHandleEvent = Event::LEFT_SELECTION_HANDLE_EVENT == event.type;
1223 const bool rightSelectionHandleEvent = Event::RIGHT_SELECTION_HANDLE_EVENT == event.type;
1225 if( Event::GRAB_HANDLE_EVENT == event.type )
1227 ChangeState( EventData::GRAB_HANDLE_PANNING );
1229 Vector2 position = mEventData->mDecorator->GetPosition( GRAB_HANDLE );
1231 // Position the grag handle close to either the left or right edge.
1232 position.x = scrollRightDirection ? 0.f : mVisualModel->mControlSize.width;
1234 // Get the new handle position.
1235 // The grab handle's position is in decorator coords. Need to transforms to text coords.
1236 const CharacterIndex handlePosition = GetClosestCursorIndex( position.x - mEventData->mScrollPosition.x - mAlignmentOffset.x,
1237 position.y - mEventData->mScrollPosition.y - mAlignmentOffset.y );
1239 mEventData->mUpdateCursorPosition = mEventData->mPrimaryCursorPosition != handlePosition;
1240 mEventData->mScrollAfterUpdatePosition = mEventData->mUpdateCursorPosition;
1241 mEventData->mPrimaryCursorPosition = handlePosition;
1242 mEventData->mUpdateInputStyle = mEventData->mUpdateCursorPosition;
1244 else if( leftSelectionHandleEvent || rightSelectionHandleEvent )
1246 // TODO: This is recalculating the selection box every time the text is scrolled with the selection handles.
1247 // Think if something can be done to save power.
1249 ChangeState( EventData::SELECTION_HANDLE_PANNING );
1251 Vector2 position = mEventData->mDecorator->GetPosition( leftSelectionHandleEvent ? Text::LEFT_SELECTION_HANDLE : Text::RIGHT_SELECTION_HANDLE );
1253 // Position the selection handle close to either the left or right edge.
1254 position.x = scrollRightDirection ? 0.f : mVisualModel->mControlSize.width;
1256 // Get the new handle position.
1257 // The selection handle's position is in decorator coords. Need to transforms to text coords.
1258 const CharacterIndex handlePosition = GetClosestCursorIndex( position.x - mEventData->mScrollPosition.x - mAlignmentOffset.x,
1259 position.y - mEventData->mScrollPosition.y - mAlignmentOffset.y );
1261 if( leftSelectionHandleEvent )
1263 const bool differentHandles = ( mEventData->mLeftSelectionPosition != handlePosition ) && ( mEventData->mRightSelectionPosition != handlePosition );
1264 mEventData->mUpdateLeftSelectionPosition = endOfScroll || differentHandles;
1265 if( differentHandles )
1267 mEventData->mLeftSelectionPosition = handlePosition;
1272 const bool differentHandles = ( mEventData->mRightSelectionPosition != handlePosition ) && ( mEventData->mLeftSelectionPosition != handlePosition );
1273 mEventData->mUpdateRightSelectionPosition = endOfScroll || differentHandles;
1274 if( differentHandles )
1276 mEventData->mRightSelectionPosition = handlePosition;
1280 if( mEventData->mUpdateLeftSelectionPosition || mEventData->mUpdateRightSelectionPosition )
1282 RepositionSelectionHandles();
1284 mEventData->mScrollAfterUpdatePosition = true;
1287 mEventData->mDecoratorUpdated = true;
1288 } // end ( HANDLE_SCROLLING == state )
1291 void Controller::Impl::OnSelectEvent( const Event& event )
1293 if( NULL == mEventData )
1295 // Nothing to do if there is no text.
1299 if( mEventData->mSelectionEnabled )
1301 // The event.p2 and event.p3 are in decorator coords. Need to transforms to text coords.
1302 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
1303 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
1305 // Calculates the logical position from the x,y coords.
1306 RepositionSelectionHandles( xPosition,
1309 mEventData->mUpdateLeftSelectionPosition = true;
1310 mEventData->mUpdateRightSelectionPosition = true;
1312 mEventData->mScrollAfterUpdatePosition = ( mEventData->mLeftSelectionPosition != mEventData->mRightSelectionPosition );
1316 void Controller::Impl::OnSelectAllEvent()
1318 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "OnSelectAllEvent mEventData->mSelectionEnabled%s \n", mEventData->mSelectionEnabled?"true":"false");
1320 if( NULL == mEventData )
1322 // Nothing to do if there is no text.
1326 if( mEventData->mSelectionEnabled )
1328 mEventData->mLeftSelectionPosition = 0u;
1329 mEventData->mRightSelectionPosition = mLogicalModel->mText.Count();
1331 mEventData->mScrollAfterUpdatePosition = true;
1332 mEventData->mUpdateLeftSelectionPosition = true;
1333 mEventData->mUpdateRightSelectionPosition = true;
1337 void Controller::Impl::RetrieveSelection( std::string& selectedText, bool deleteAfterRetrieval )
1339 if( mEventData->mLeftSelectionPosition == mEventData->mRightSelectionPosition )
1341 // Nothing to select if handles are in the same place.
1342 selectedText.clear();
1346 const bool handlesCrossed = mEventData->mLeftSelectionPosition > mEventData->mRightSelectionPosition;
1348 //Get start and end position of selection
1349 const CharacterIndex startOfSelectedText = handlesCrossed ? mEventData->mRightSelectionPosition : mEventData->mLeftSelectionPosition;
1350 const Length lengthOfSelectedText = ( handlesCrossed ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition ) - startOfSelectedText;
1352 Vector<Character>& utf32Characters = mLogicalModel->mText;
1353 const Length numberOfCharacters = utf32Characters.Count();
1355 // Validate the start and end selection points
1356 if( ( startOfSelectedText + lengthOfSelectedText ) <= numberOfCharacters )
1358 //Get text as a UTF8 string
1359 Utf32ToUtf8( &utf32Characters[startOfSelectedText], lengthOfSelectedText, selectedText );
1361 if( deleteAfterRetrieval ) // Only delete text if copied successfully
1363 // Set as input style the style of the first deleted character.
1364 mLogicalModel->RetrieveStyle( startOfSelectedText, mEventData->mInputStyle );
1366 mLogicalModel->UpdateTextStyleRuns( startOfSelectedText, -static_cast<int>( lengthOfSelectedText ) );
1368 // Mark the paragraphs to be updated.
1369 mTextUpdateInfo.mCharacterIndex = startOfSelectedText;
1370 mTextUpdateInfo.mNumberOfCharactersToRemove = lengthOfSelectedText;
1372 // Delete text between handles
1373 Vector<Character>::Iterator first = utf32Characters.Begin() + startOfSelectedText;
1374 Vector<Character>::Iterator last = first + lengthOfSelectedText;
1375 utf32Characters.Erase( first, last );
1377 // Scroll after delete.
1378 mEventData->mPrimaryCursorPosition = handlesCrossed ? mEventData->mRightSelectionPosition : mEventData->mLeftSelectionPosition;
1379 mEventData->mScrollAfterDelete = true;
1381 // Udpade the cursor position and the decorator.
1382 // Scroll after the position is updated if is not scrolling after delete.
1383 mEventData->mUpdateCursorPosition = true;
1384 mEventData->mScrollAfterUpdatePosition = !mEventData->mScrollAfterDelete;
1385 mEventData->mDecoratorUpdated = true;
1389 void Controller::Impl::ShowClipboard()
1393 mClipboard.ShowClipboard();
1397 void Controller::Impl::HideClipboard()
1401 mClipboard.HideClipboard();
1405 bool Controller::Impl::CopyStringToClipboard( std::string& source )
1407 //Send string to clipboard
1408 return ( mClipboard && mClipboard.SetItem( source ) );
1411 void Controller::Impl::SendSelectionToClipboard( bool deleteAfterSending )
1413 std::string selectedText;
1414 RetrieveSelection( selectedText, deleteAfterSending );
1415 CopyStringToClipboard( selectedText );
1416 ChangeState( EventData::EDITING );
1419 void Controller::Impl::GetTextFromClipboard( unsigned int itemIndex, std::string& retrievedString )
1423 retrievedString = mClipboard.GetItem( itemIndex );
1427 void Controller::Impl::RepositionSelectionHandles()
1429 CharacterIndex selectionStart = mEventData->mLeftSelectionPosition;
1430 CharacterIndex selectionEnd = mEventData->mRightSelectionPosition;
1432 if( selectionStart == selectionEnd )
1434 // Nothing to select if handles are in the same place.
1438 mEventData->mDecorator->ClearHighlights();
1440 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
1441 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
1442 const GlyphInfo* const glyphsBuffer = mVisualModel->mGlyphs.Begin();
1443 const Vector2* const positionsBuffer = mVisualModel->mGlyphPositions.Begin();
1444 const Length* const charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
1445 const CharacterIndex* const glyphToCharacterBuffer = mVisualModel->mGlyphsToCharacters.Begin();
1446 const CharacterDirection* const modelCharacterDirectionsBuffer = ( 0u != mLogicalModel->mCharacterDirections.Count() ) ? mLogicalModel->mCharacterDirections.Begin() : NULL;
1448 // TODO: Better algorithm to create the highlight box.
1449 // TODO: Multi-line.
1451 // Get the height of the line.
1452 const Vector<LineRun>& lines = mVisualModel->mLines;
1453 const LineRun& firstLine = *lines.Begin();
1454 const float height = firstLine.ascender + -firstLine.descender;
1456 const bool isLastCharacter = selectionEnd >= mLogicalModel->mText.Count();
1457 const bool startDirection = ( ( NULL == modelCharacterDirectionsBuffer ) ? false : *( modelCharacterDirectionsBuffer + selectionStart ) );
1458 const bool endDirection = ( ( NULL == modelCharacterDirectionsBuffer ) ? false : *( modelCharacterDirectionsBuffer + ( selectionEnd - ( isLastCharacter ? 1u : 0u ) ) ) );
1460 // Swap the indices if the start is greater than the end.
1461 const bool indicesSwapped = selectionStart > selectionEnd;
1463 // Tell the decorator to flip the selection handles if needed.
1464 mEventData->mDecorator->SetSelectionHandleFlipState( indicesSwapped, startDirection, endDirection );
1466 if( indicesSwapped )
1468 std::swap( selectionStart, selectionEnd );
1471 // Get the indices to the first and last selected glyphs.
1472 const CharacterIndex selectionEndMinusOne = selectionEnd - 1u;
1473 const GlyphIndex glyphStart = *( charactersToGlyphBuffer + selectionStart );
1474 const Length numberOfGlyphs = *( glyphsPerCharacterBuffer + selectionEndMinusOne );
1475 const GlyphIndex glyphEnd = *( charactersToGlyphBuffer + selectionEndMinusOne ) + ( ( numberOfGlyphs > 0 ) ? numberOfGlyphs - 1u : 0u );
1477 // Check if the first glyph is a ligature that must be broken like Latin ff, fi, or Arabic ﻻ, etc which needs special code.
1478 const Length numberOfCharactersStart = *( charactersPerGlyphBuffer + glyphStart );
1479 bool splitStartGlyph = ( numberOfCharactersStart > 1u ) && HasLigatureMustBreak( mLogicalModel->GetScript( selectionStart ) );
1481 // Check if the last glyph is a ligature that must be broken like Latin ff, fi, or Arabic ﻻ, etc which needs special code.
1482 const Length numberOfCharactersEnd = *( charactersPerGlyphBuffer + glyphEnd );
1483 bool splitEndGlyph = ( glyphStart != glyphEnd ) && ( numberOfCharactersEnd > 1u ) && HasLigatureMustBreak( mLogicalModel->GetScript( selectionEndMinusOne ) );
1485 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
1487 // Traverse the glyphs.
1488 for( GlyphIndex index = glyphStart; index <= glyphEnd; ++index )
1490 const GlyphInfo& glyph = *( glyphsBuffer + index );
1491 const Vector2& position = *( positionsBuffer + index );
1493 if( splitStartGlyph )
1495 // 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.
1497 const float glyphAdvance = glyph.advance / static_cast<float>( numberOfCharactersStart );
1498 const CharacterIndex interGlyphIndex = selectionStart - *( glyphToCharacterBuffer + glyphStart );
1499 // Get the direction of the character.
1500 CharacterDirection isCurrentRightToLeft = false;
1501 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
1503 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + selectionStart );
1506 // The end point could be in the middle of the ligature.
1507 // Calculate the number of characters selected.
1508 const Length numberOfCharacters = ( glyphStart == glyphEnd ) ? ( selectionEnd - selectionStart ) : ( numberOfCharactersStart - interGlyphIndex );
1510 const float xPosition = position.x - glyph.xBearing + offset.x + glyphAdvance * static_cast<float>( isCurrentRightToLeft ? ( numberOfCharactersStart - interGlyphIndex - numberOfCharacters ) : interGlyphIndex );
1512 mEventData->mDecorator->AddHighlight( xPosition,
1514 xPosition + static_cast<float>( numberOfCharacters ) * glyphAdvance,
1515 offset.y + height );
1517 splitStartGlyph = false;
1521 if( splitEndGlyph && ( index == glyphEnd ) )
1523 // 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.
1525 const float glyphAdvance = glyph.advance / static_cast<float>( numberOfCharactersEnd );
1526 const CharacterIndex interGlyphIndex = selectionEnd - *( glyphToCharacterBuffer + glyphEnd );
1527 // Get the direction of the character.
1528 CharacterDirection isCurrentRightToLeft = false;
1529 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
1531 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + selectionEnd );
1534 const Length numberOfCharacters = numberOfCharactersEnd - interGlyphIndex;
1536 const float xPosition = position.x - glyph.xBearing + offset.x + ( isCurrentRightToLeft ? ( glyphAdvance * static_cast<float>( numberOfCharacters ) ) : 0.f );
1537 mEventData->mDecorator->AddHighlight( xPosition,
1539 xPosition + static_cast<float>( interGlyphIndex ) * glyphAdvance,
1540 offset.y + height );
1542 splitEndGlyph = false;
1546 const float xPosition = position.x - glyph.xBearing + offset.x;
1547 mEventData->mDecorator->AddHighlight( xPosition,
1549 xPosition + glyph.advance,
1550 offset.y + height );
1553 CursorInfo primaryCursorInfo;
1554 GetCursorPosition( mEventData->mLeftSelectionPosition,
1555 primaryCursorInfo );
1557 CursorInfo secondaryCursorInfo;
1558 GetCursorPosition( mEventData->mRightSelectionPosition,
1559 secondaryCursorInfo );
1561 const Vector2 primaryPosition = primaryCursorInfo.primaryPosition + offset;
1562 const Vector2 secondaryPosition = secondaryCursorInfo.primaryPosition + offset;
1564 mEventData->mDecorator->SetPosition( LEFT_SELECTION_HANDLE,
1566 primaryCursorInfo.lineOffset + offset.y,
1567 primaryCursorInfo.lineHeight );
1569 mEventData->mDecorator->SetPosition( RIGHT_SELECTION_HANDLE,
1570 secondaryPosition.x,
1571 secondaryCursorInfo.lineOffset + offset.y,
1572 secondaryCursorInfo.lineHeight );
1574 // Cursor to be positioned at end of selection so if selection interrupted and edit mode restarted the cursor will be at end of selection
1575 mEventData->mPrimaryCursorPosition = ( indicesSwapped ) ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition;
1577 // Set the flag to update the decorator.
1578 mEventData->mDecoratorUpdated = true;
1581 void Controller::Impl::RepositionSelectionHandles( float visualX, float visualY )
1583 if( NULL == mEventData )
1585 // Nothing to do if there is no text input.
1589 if( IsShowingPlaceholderText() )
1591 // Nothing to do if there is the place-holder text.
1595 const Length numberOfGlyphs = mVisualModel->mGlyphs.Count();
1596 const Length numberOfLines = mVisualModel->mLines.Count();
1597 if( ( 0 == numberOfGlyphs ) ||
1598 ( 0 == numberOfLines ) )
1600 // Nothing to do if there is no text.
1604 // Find which word was selected
1605 CharacterIndex selectionStart( 0 );
1606 CharacterIndex selectionEnd( 0 );
1607 FindSelectionIndices( visualX, visualY, selectionStart, selectionEnd );
1608 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "%p selectionStart %d selectionEnd %d\n", this, selectionStart, selectionEnd );
1610 if( selectionStart == selectionEnd )
1612 ChangeState( EventData::EDITING );
1613 // Nothing to select. i.e. a white space, out of bounds
1617 mEventData->mLeftSelectionPosition = selectionStart;
1618 mEventData->mRightSelectionPosition = selectionEnd;
1621 void Controller::Impl::SetPopupButtons()
1624 * Sets the Popup buttons to be shown depending on State.
1626 * If SELECTING : CUT & COPY + ( PASTE & CLIPBOARD if content available to paste )
1628 * If EDITING_WITH_POPUP : SELECT & SELECT_ALL
1631 TextSelectionPopup::Buttons buttonsToShow = TextSelectionPopup::NONE;
1633 if( EventData::SELECTING == mEventData->mState )
1635 buttonsToShow = TextSelectionPopup::Buttons( TextSelectionPopup::CUT | TextSelectionPopup::COPY );
1637 if( !IsClipboardEmpty() )
1639 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
1640 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::CLIPBOARD ) );
1643 if( !mEventData->mAllTextSelected )
1645 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::SELECT_ALL ) );
1648 else if( EventData::EDITING_WITH_POPUP == mEventData->mState )
1650 if( mLogicalModel->mText.Count() && !IsShowingPlaceholderText() )
1652 buttonsToShow = TextSelectionPopup::Buttons( TextSelectionPopup::SELECT | TextSelectionPopup::SELECT_ALL );
1655 if( !IsClipboardEmpty() )
1657 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
1658 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::CLIPBOARD ) );
1661 else if( EventData::EDITING_WITH_PASTE_POPUP == mEventData->mState )
1663 if ( !IsClipboardEmpty() )
1665 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
1666 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::CLIPBOARD ) );
1670 mEventData->mDecorator->SetEnabledPopupButtons( buttonsToShow );
1673 void Controller::Impl::ChangeState( EventData::State newState )
1675 if( NULL == mEventData )
1677 // Nothing to do if there is no text input.
1681 DALI_LOG_INFO( gLogFilter, Debug::General, "ChangeState state:%d newstate:%d\n", mEventData->mState, newState );
1683 if( mEventData->mState != newState )
1685 mEventData->mState = newState;
1687 if( EventData::INACTIVE == mEventData->mState )
1689 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
1690 mEventData->mDecorator->StopCursorBlink();
1691 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1692 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1693 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1694 mEventData->mDecorator->SetPopupActive( false );
1695 mEventData->mDecoratorUpdated = true;
1698 else if( EventData::INTERRUPTED == mEventData->mState)
1700 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1701 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1702 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1703 mEventData->mDecorator->SetPopupActive( false );
1704 mEventData->mDecoratorUpdated = true;
1707 else if( EventData::SELECTING == mEventData->mState )
1709 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
1710 mEventData->mDecorator->StopCursorBlink();
1711 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1712 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true );
1713 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true );
1714 if( mEventData->mGrabHandlePopupEnabled )
1717 mEventData->mDecorator->SetPopupActive( true );
1719 mEventData->mDecoratorUpdated = true;
1721 else if( EventData::EDITING == mEventData->mState )
1723 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1724 if( mEventData->mCursorBlinkEnabled )
1726 mEventData->mDecorator->StartCursorBlink();
1728 // Grab handle is not shown until a tap is received whilst EDITING
1729 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1730 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1731 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1732 if( mEventData->mGrabHandlePopupEnabled )
1734 mEventData->mDecorator->SetPopupActive( false );
1736 mEventData->mDecoratorUpdated = true;
1739 else if( EventData::EDITING_WITH_POPUP == mEventData->mState )
1741 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "EDITING_WITH_POPUP \n", newState );
1743 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1744 if( mEventData->mCursorBlinkEnabled )
1746 mEventData->mDecorator->StartCursorBlink();
1748 if( mEventData->mSelectionEnabled )
1750 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1751 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1755 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
1757 if( mEventData->mGrabHandlePopupEnabled )
1760 mEventData->mDecorator->SetPopupActive( true );
1763 mEventData->mDecoratorUpdated = true;
1765 else if( EventData::EDITING_WITH_GRAB_HANDLE == mEventData->mState )
1767 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "EDITING_WITH_GRAB_HANDLE \n", newState );
1769 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1770 if( mEventData->mCursorBlinkEnabled )
1772 mEventData->mDecorator->StartCursorBlink();
1774 // Grab handle is not shown until a tap is received whilst EDITING
1775 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
1776 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1777 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1778 if( mEventData->mGrabHandlePopupEnabled )
1780 mEventData->mDecorator->SetPopupActive( false );
1782 mEventData->mDecoratorUpdated = true;
1785 else if( EventData::SELECTION_HANDLE_PANNING == mEventData->mState )
1787 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
1788 mEventData->mDecorator->StopCursorBlink();
1789 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1790 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true );
1791 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true );
1792 if( mEventData->mGrabHandlePopupEnabled )
1794 mEventData->mDecorator->SetPopupActive( false );
1796 mEventData->mDecoratorUpdated = true;
1798 else if( EventData::GRAB_HANDLE_PANNING == mEventData->mState )
1800 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "GRAB_HANDLE_PANNING \n", newState );
1802 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1803 if( mEventData->mCursorBlinkEnabled )
1805 mEventData->mDecorator->StartCursorBlink();
1807 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
1808 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1809 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1810 if( mEventData->mGrabHandlePopupEnabled )
1812 mEventData->mDecorator->SetPopupActive( false );
1814 mEventData->mDecoratorUpdated = true;
1816 else if( EventData::EDITING_WITH_PASTE_POPUP == mEventData->mState )
1818 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "EDITING_WITH_PASTE_POPUP \n", newState );
1820 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1821 if( mEventData->mCursorBlinkEnabled )
1823 mEventData->mDecorator->StartCursorBlink();
1826 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
1827 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1828 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1830 if( mEventData->mGrabHandlePopupEnabled )
1833 mEventData->mDecorator->SetPopupActive( true );
1836 mEventData->mDecoratorUpdated = true;
1841 LineIndex Controller::Impl::GetClosestLine( float y ) const
1843 float totalHeight = 0.f;
1844 LineIndex lineIndex = 0u;
1846 const Vector<LineRun>& lines = mVisualModel->mLines;
1847 for( LineIndex endLine = lines.Count();
1848 lineIndex < endLine;
1851 const LineRun& lineRun = lines[lineIndex];
1852 totalHeight += lineRun.ascender + -lineRun.descender;
1853 if( y < totalHeight )
1859 if( lineIndex == 0 )
1867 void Controller::Impl::FindSelectionIndices( float visualX, float visualY, CharacterIndex& startIndex, CharacterIndex& endIndex )
1869 CharacterIndex hitCharacter = GetClosestCursorIndex( visualX, visualY );
1870 DALI_ASSERT_DEBUG( hitCharacter <= mLogicalModel->mText.Count() && "GetClosestCursorIndex returned out of bounds index" );
1872 if( mLogicalModel->mText.Count() == 0 )
1874 return; // if model empty
1877 if( hitCharacter >= mLogicalModel->mText.Count() )
1879 // Closest hit character is the last character.
1880 if( hitCharacter == mLogicalModel->mText.Count() )
1882 hitCharacter--; //Hit character index set to last character in logical model
1886 // hitCharacter is out of bounds
1891 startIndex = hitCharacter;
1892 endIndex = hitCharacter;
1893 bool isHitCharacterWhitespace = TextAbstraction::IsWhiteSpace( mLogicalModel->mText[hitCharacter] );
1895 // Find the start and end of the text
1896 for( startIndex = hitCharacter; startIndex > 0; --startIndex )
1898 if( isHitCharacterWhitespace != TextAbstraction::IsWhiteSpace( mLogicalModel->mText[ startIndex-1 ] ) )
1903 const CharacterIndex pastTheEnd = mLogicalModel->mText.Count();
1904 for( endIndex = hitCharacter + 1u; endIndex < pastTheEnd; ++endIndex )
1906 if( isHitCharacterWhitespace != TextAbstraction::IsWhiteSpace( mLogicalModel->mText[ endIndex ] ) )
1913 CharacterIndex Controller::Impl::GetClosestCursorIndex( float visualX,
1916 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "GetClosestCursorIndex %p closest visualX %f visualY %f\n", this, visualX, visualY );
1918 if( NULL == mEventData )
1920 // Nothing to do if there is no text input.
1924 CharacterIndex logicalIndex = 0u;
1926 const Length numberOfGlyphs = mVisualModel->mGlyphs.Count();
1927 const Length numberOfLines = mVisualModel->mLines.Count();
1928 if( ( 0 == numberOfGlyphs ) ||
1929 ( 0 == numberOfLines ) )
1931 return logicalIndex;
1934 // Find which line is closest
1935 const LineIndex lineIndex = GetClosestLine( visualY );
1936 const LineRun& line = mVisualModel->mLines[lineIndex];
1938 // Get the positions of the glyphs.
1939 const Vector<Vector2>& positions = mVisualModel->mGlyphPositions;
1940 const Vector2* const positionsBuffer = positions.Begin();
1942 // Get the visual to logical conversion tables.
1943 const CharacterIndex* const visualToLogicalBuffer = ( 0u != mLogicalModel->mVisualToLogicalMap.Count() ) ? mLogicalModel->mVisualToLogicalMap.Begin() : NULL;
1944 const CharacterIndex* const visualToLogicalCursorBuffer = mLogicalModel->mVisualToLogicalCursorMap.Begin();
1946 // Get the character to glyph conversion table.
1947 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
1949 // Get the glyphs per character table.
1950 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
1952 // Get the glyph's info buffer.
1953 const GlyphInfo* const glyphInfoBuffer = mVisualModel->mGlyphs.Begin();
1955 // If the vector is void, there is no right to left characters.
1956 const bool hasRightToLeftCharacters = NULL != visualToLogicalBuffer;
1958 const CharacterIndex startCharacter = line.characterRun.characterIndex;
1959 const CharacterIndex endCharacter = line.characterRun.characterIndex + line.characterRun.numberOfCharacters;
1960 DALI_ASSERT_DEBUG( endCharacter <= mLogicalModel->mText.Count() && "Invalid line info" );
1962 // Whether there is a hit on a glyph.
1963 bool matched = false;
1965 // Traverses glyphs in visual order. To do that use the visual to logical conversion table.
1966 CharacterIndex visualIndex = startCharacter;
1967 Length numberOfCharacters = 0u;
1968 for( ; !matched && ( visualIndex < endCharacter ); ++visualIndex )
1970 // The character in logical order.
1971 const CharacterIndex characterLogicalOrderIndex = hasRightToLeftCharacters ? *( visualToLogicalBuffer + visualIndex ) : visualIndex;
1973 // Get the script of the character.
1974 const Script script = mLogicalModel->GetScript( characterLogicalOrderIndex );
1976 // The number of glyphs for that character
1977 const Length numberOfGlyphs = *( glyphsPerCharacterBuffer + characterLogicalOrderIndex );
1978 ++numberOfCharacters;
1981 if( 0u != numberOfGlyphs )
1983 // Get the first character/glyph of the group of glyphs.
1984 const CharacterIndex firstVisualCharacterIndex = 1u + visualIndex - numberOfCharacters;
1985 const CharacterIndex firstLogicalCharacterIndex = hasRightToLeftCharacters ? *( visualToLogicalBuffer + firstVisualCharacterIndex ) : firstVisualCharacterIndex;
1986 const GlyphIndex firstLogicalGlyphIndex = *( charactersToGlyphBuffer + firstLogicalCharacterIndex );
1988 // Get the metrics for the group of glyphs.
1989 GlyphMetrics glyphMetrics;
1990 GetGlyphsMetrics( firstLogicalGlyphIndex,
1996 // Get the position of the first glyph.
1997 const Vector2& position = *( positionsBuffer + firstLogicalGlyphIndex );
1999 // Whether the glyph can be split, like Latin ligatures fi, ff or Arabic ﻻ.
2000 const bool isInterglyphIndex = ( numberOfCharacters > numberOfGlyphs ) && HasLigatureMustBreak( script );
2001 const Length numberOfBlocks = isInterglyphIndex ? numberOfCharacters : 1u;
2002 const float glyphAdvance = glyphMetrics.advance / static_cast<float>( numberOfBlocks );
2004 GlyphIndex index = 0u;
2005 for( ; !matched && ( index < numberOfBlocks ); ++index )
2007 // Find the mid-point of the area containing the glyph
2008 const float glyphCenter = -glyphMetrics.xBearing + position.x + ( static_cast<float>( index ) + 0.5f ) * glyphAdvance;
2010 if( visualX < glyphCenter )
2019 visualIndex = firstVisualCharacterIndex + index;
2023 numberOfCharacters = 0u;
2029 // Return the logical position of the cursor in characters.
2033 visualIndex = endCharacter;
2036 logicalIndex = hasRightToLeftCharacters ? *( visualToLogicalCursorBuffer + visualIndex ) : visualIndex;
2037 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "%p closest visualIndex %d logicalIndex %d\n", this, visualIndex, logicalIndex );
2039 DALI_ASSERT_DEBUG( ( logicalIndex <= mLogicalModel->mText.Count() && logicalIndex >= 0 ) && "GetClosestCursorIndex - Out of bounds index" );
2041 return logicalIndex;
2044 void Controller::Impl::GetCursorPosition( CharacterIndex logical,
2045 CursorInfo& cursorInfo )
2047 // TODO: Check for multiline with \n, etc...
2049 const Length numberOfCharacters = mLogicalModel->mText.Count();
2050 if( !IsShowingRealText() )
2052 // Do not want to use the place-holder text to set the cursor position.
2054 // Use the line's height of the font's family set to set the cursor's size.
2055 // If there is no font's family set, use the default font.
2056 // Use the current alignment to place the cursor at the beginning, center or end of the box.
2058 cursorInfo.lineOffset = 0.f;
2059 cursorInfo.lineHeight = GetDefaultFontLineHeight();
2060 cursorInfo.primaryCursorHeight = cursorInfo.lineHeight;
2062 switch( mLayoutEngine.GetHorizontalAlignment() )
2064 case LayoutEngine::HORIZONTAL_ALIGN_BEGIN:
2066 cursorInfo.primaryPosition.x = 0.f;
2069 case LayoutEngine::HORIZONTAL_ALIGN_CENTER:
2071 cursorInfo.primaryPosition.x = floorf( 0.5f * mVisualModel->mControlSize.width );
2074 case LayoutEngine::HORIZONTAL_ALIGN_END:
2076 cursorInfo.primaryPosition.x = mVisualModel->mControlSize.width - mEventData->mDecorator->GetCursorWidth();
2081 switch( mLayoutEngine.GetVerticalAlignment() )
2083 case LayoutEngine::VERTICAL_ALIGN_TOP:
2085 cursorInfo.primaryPosition.y = 0.f;
2088 case LayoutEngine::VERTICAL_ALIGN_CENTER:
2090 cursorInfo.primaryPosition.y = floorf( 0.5f * ( mVisualModel->mControlSize.height - cursorInfo.lineHeight ) );
2093 case LayoutEngine::VERTICAL_ALIGN_BOTTOM:
2095 cursorInfo.primaryPosition.y = mVisualModel->mControlSize.height - cursorInfo.lineHeight;
2100 // Nothing else to do.
2104 // Check if the logical position is the first or the last one of the text.
2105 const bool isFirstPosition = 0u == logical;
2106 const bool isLastPosition = numberOfCharacters == logical;
2108 // 'logical' is the logical 'cursor' index.
2109 // Get the next and current logical 'character' index.
2110 const CharacterIndex nextCharacterIndex = logical;
2111 const CharacterIndex characterIndex = isFirstPosition ? logical : logical - 1u;
2113 // Get the direction of the character and the next one.
2114 const CharacterDirection* const modelCharacterDirectionsBuffer = ( 0u != mLogicalModel->mCharacterDirections.Count() ) ? mLogicalModel->mCharacterDirections.Begin() : NULL;
2116 CharacterDirection isCurrentRightToLeft = false;
2117 CharacterDirection isNextRightToLeft = false;
2118 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
2120 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + characterIndex );
2121 isNextRightToLeft = *( modelCharacterDirectionsBuffer + nextCharacterIndex );
2124 // Get the line where the character is laid-out.
2125 const LineRun* const modelLines = mVisualModel->mLines.Begin();
2127 const LineIndex lineIndex = mVisualModel->GetLineOfCharacter( characterIndex );
2128 const LineRun& line = *( modelLines + lineIndex );
2130 // Get the paragraph's direction.
2131 const CharacterDirection isRightToLeftParagraph = line.direction;
2133 // Check whether there is an alternative position:
2135 cursorInfo.isSecondaryCursor = ( !isLastPosition && ( isCurrentRightToLeft != isNextRightToLeft ) ) ||
2136 ( isLastPosition && ( isRightToLeftParagraph != isCurrentRightToLeft ) );
2138 // Set the line offset and height.
2139 cursorInfo.lineOffset = 0.f;
2140 cursorInfo.lineHeight = line.ascender + -line.descender;
2142 // Calculate the primary cursor.
2144 CharacterIndex index = characterIndex;
2145 if( cursorInfo.isSecondaryCursor )
2147 // If there is a secondary position, the primary cursor may be in a different place than the logical index.
2149 if( isLastPosition )
2151 // The position of the cursor after the last character needs special
2152 // care depending on its direction and the direction of the paragraph.
2154 // Need to find the first character after the last character with the paragraph's direction.
2155 // i.e l0 l1 l2 r0 r1 should find r0.
2157 // TODO: check for more than one line!
2158 index = isRightToLeftParagraph ? line.characterRun.characterIndex : line.characterRun.characterIndex + line.characterRun.numberOfCharacters - 1u;
2159 index = mLogicalModel->GetLogicalCharacterIndex( index );
2163 index = ( isRightToLeftParagraph == isCurrentRightToLeft ) ? characterIndex : nextCharacterIndex;
2167 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
2168 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
2169 const Length* const charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
2170 const CharacterIndex* const glyphsToCharactersBuffer = mVisualModel->mGlyphsToCharacters.Begin();
2171 const Vector2* const glyphPositionsBuffer = mVisualModel->mGlyphPositions.Begin();
2172 const GlyphInfo* const glyphInfoBuffer = mVisualModel->mGlyphs.Begin();
2174 // Convert the cursor position into the glyph position.
2175 const GlyphIndex primaryGlyphIndex = *( charactersToGlyphBuffer + index );
2176 const Length primaryNumberOfGlyphs = *( glyphsPerCharacterBuffer + index );
2177 const Length primaryNumberOfCharacters = *( charactersPerGlyphBuffer + primaryGlyphIndex );
2179 // Get the metrics for the group of glyphs.
2180 GlyphMetrics glyphMetrics;
2181 GetGlyphsMetrics( primaryGlyphIndex,
2182 primaryNumberOfGlyphs,
2187 // Whether to add the glyph's advance to the cursor position.
2188 // 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,
2189 // if the logical cursor is one, the position is the position of the first glyph and the advance is added.
2190 // A 'truth table' was build and an online Karnaugh map tool was used to simplify the logic.
2211 // Where F -> isFirstPosition
2212 // L -> isLastPosition
2213 // C -> isCurrentRightToLeft
2214 // P -> isRightToLeftParagraph
2215 // A -> Whether to add the glyph's advance.
2217 const bool addGlyphAdvance = ( ( isLastPosition && !isRightToLeftParagraph ) ||
2218 ( isFirstPosition && isRightToLeftParagraph ) ||
2219 ( !isFirstPosition && !isLastPosition && !isCurrentRightToLeft ) );
2221 float glyphAdvance = addGlyphAdvance ? glyphMetrics.advance : 0.f;
2223 if( !isLastPosition &&
2224 ( primaryNumberOfCharacters > 1u ) )
2226 const CharacterIndex firstIndex = *( glyphsToCharactersBuffer + primaryGlyphIndex );
2228 bool isCurrentRightToLeft = false;
2229 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
2231 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + index );
2234 Length numberOfGlyphAdvance = ( isFirstPosition ? 0u : 1u ) + characterIndex - firstIndex;
2235 if( isCurrentRightToLeft )
2237 numberOfGlyphAdvance = primaryNumberOfCharacters - numberOfGlyphAdvance;
2240 glyphAdvance = static_cast<float>( numberOfGlyphAdvance ) * glyphMetrics.advance / static_cast<float>( primaryNumberOfCharacters );
2243 // Get the glyph position and x bearing.
2244 const Vector2& primaryPosition = *( glyphPositionsBuffer + primaryGlyphIndex );
2246 // Set the primary cursor's height.
2247 cursorInfo.primaryCursorHeight = cursorInfo.isSecondaryCursor ? 0.5f * glyphMetrics.fontHeight : glyphMetrics.fontHeight;
2249 // Set the primary cursor's position.
2250 cursorInfo.primaryPosition.x = -glyphMetrics.xBearing + primaryPosition.x + glyphAdvance;
2251 cursorInfo.primaryPosition.y = line.ascender - glyphMetrics.ascender;
2253 // Calculate the secondary cursor.
2255 if( cursorInfo.isSecondaryCursor )
2257 // Set the secondary cursor's height.
2258 cursorInfo.secondaryCursorHeight = 0.5f * glyphMetrics.fontHeight;
2260 CharacterIndex index = characterIndex;
2261 if( !isLastPosition )
2263 index = ( isRightToLeftParagraph == isCurrentRightToLeft ) ? nextCharacterIndex : characterIndex;
2266 const GlyphIndex secondaryGlyphIndex = *( charactersToGlyphBuffer + index );
2267 const Length secondaryNumberOfGlyphs = *( glyphsPerCharacterBuffer + index );
2269 const Vector2& secondaryPosition = *( glyphPositionsBuffer + secondaryGlyphIndex );
2271 GetGlyphsMetrics( secondaryGlyphIndex,
2272 secondaryNumberOfGlyphs,
2277 // Set the secondary cursor's position.
2278 cursorInfo.secondaryPosition.x = -glyphMetrics.xBearing + secondaryPosition.x + ( isCurrentRightToLeft ? 0.f : glyphMetrics.advance );
2279 cursorInfo.secondaryPosition.y = cursorInfo.lineHeight - cursorInfo.secondaryCursorHeight - line.descender - ( glyphMetrics.fontHeight - glyphMetrics.ascender );
2282 if( LayoutEngine::MULTI_LINE_BOX == mLayoutEngine.GetLayout() )
2284 // If the text is editable and multi-line, the cursor position after a white space shouldn't exceed the boundaries of the text control.
2286 // Note the white spaces laid-out at the end of the line might exceed the boundaries of the control.
2287 // The reason is a wrapped line must not start with a white space so they are laid-out at the end of the line.
2289 if( 0.f > cursorInfo.primaryPosition.x )
2291 cursorInfo.primaryPosition.x = 0.f;
2294 const float edgeWidth = mVisualModel->mControlSize.width - static_cast<float>( mEventData->mDecorator->GetCursorWidth() );
2295 if( cursorInfo.primaryPosition.x > edgeWidth )
2297 cursorInfo.primaryPosition.x = edgeWidth;
2302 CharacterIndex Controller::Impl::CalculateNewCursorIndex( CharacterIndex index ) const
2304 if( NULL == mEventData )
2306 // Nothing to do if there is no text input.
2310 CharacterIndex cursorIndex = mEventData->mPrimaryCursorPosition;
2312 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
2313 const Length* const charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
2315 GlyphIndex glyphIndex = *( charactersToGlyphBuffer + index );
2316 Length numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
2318 if( numberOfCharacters > 1u )
2320 const Script script = mLogicalModel->GetScript( index );
2321 if( HasLigatureMustBreak( script ) )
2323 // Prevents to jump the whole Latin ligatures like fi, ff, or Arabic ﻻ, ...
2324 numberOfCharacters = 1u;
2329 while( 0u == numberOfCharacters )
2332 numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
2336 if( index < mEventData->mPrimaryCursorPosition )
2338 cursorIndex -= numberOfCharacters;
2342 cursorIndex += numberOfCharacters;
2348 void Controller::Impl::UpdateCursorPosition( const CursorInfo& cursorInfo )
2350 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "-->Controller::UpdateCursorPosition %p\n", this );
2351 if( NULL == mEventData )
2353 // Nothing to do if there is no text input.
2354 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition no event data\n" );
2358 const Vector2 offset = mEventData->mScrollPosition + ( IsShowingRealText() ? mAlignmentOffset : Vector2::ZERO );
2359 const Vector2 cursorPosition = cursorInfo.primaryPosition + offset;
2361 // Sets the cursor position.
2362 mEventData->mDecorator->SetPosition( PRIMARY_CURSOR,
2365 cursorInfo.primaryCursorHeight,
2366 cursorInfo.lineHeight );
2367 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Primary cursor position: %f,%f\n", cursorPosition.x, cursorPosition.y );
2369 // Sets the grab handle position.
2370 mEventData->mDecorator->SetPosition( GRAB_HANDLE,
2372 cursorInfo.lineOffset + offset.y,
2373 cursorInfo.lineHeight );
2375 if( cursorInfo.isSecondaryCursor )
2377 mEventData->mDecorator->SetPosition( SECONDARY_CURSOR,
2378 cursorInfo.secondaryPosition.x + offset.x,
2379 cursorInfo.secondaryPosition.y + offset.y,
2380 cursorInfo.secondaryCursorHeight,
2381 cursorInfo.lineHeight );
2382 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Secondary cursor position: %f,%f\n", cursorInfo.secondaryPosition.x + offset.x, cursorInfo.secondaryPosition.y + offset.y );
2385 // Set which cursors are active according the state.
2386 if( EventData::IsEditingState( mEventData->mState ) || ( EventData::GRAB_HANDLE_PANNING == mEventData->mState ) )
2388 if( cursorInfo.isSecondaryCursor )
2390 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_BOTH );
2394 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
2399 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
2402 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition\n" );
2405 void Controller::Impl::UpdateSelectionHandle( HandleType handleType,
2406 const CursorInfo& cursorInfo )
2408 if( ( LEFT_SELECTION_HANDLE != handleType ) &&
2409 ( RIGHT_SELECTION_HANDLE != handleType ) )
2414 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
2415 const Vector2 cursorPosition = cursorInfo.primaryPosition + offset;
2417 // Sets the handle's position.
2418 mEventData->mDecorator->SetPosition( handleType,
2420 cursorInfo.lineOffset + offset.y,
2421 cursorInfo.lineHeight );
2423 // If selection handle at start of the text and other at end of the text then all text is selected.
2424 const CharacterIndex startOfSelection = std::min( mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition );
2425 const CharacterIndex endOfSelection = std::max ( mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition );
2426 mEventData->mAllTextSelected = ( startOfSelection == 0 ) && ( endOfSelection == mLogicalModel->mText.Count() );
2429 void Controller::Impl::ClampHorizontalScroll( const Vector2& actualSize )
2431 // Clamp between -space & 0 (and the text alignment).
2433 if( actualSize.width > mVisualModel->mControlSize.width )
2435 const float space = ( actualSize.width - mVisualModel->mControlSize.width ) + mAlignmentOffset.x;
2436 mEventData->mScrollPosition.x = ( mEventData->mScrollPosition.x < -space ) ? -space : mEventData->mScrollPosition.x;
2437 mEventData->mScrollPosition.x = ( mEventData->mScrollPosition.x > -mAlignmentOffset.x ) ? -mAlignmentOffset.x : mEventData->mScrollPosition.x;
2439 mEventData->mDecoratorUpdated = true;
2443 mEventData->mScrollPosition.x = 0.f;
2447 void Controller::Impl::ClampVerticalScroll( const Vector2& actualSize )
2449 // Clamp between -space & 0 (and the text alignment).
2450 if( actualSize.height > mVisualModel->mControlSize.height )
2452 const float space = ( actualSize.height - mVisualModel->mControlSize.height ) + mAlignmentOffset.y;
2453 mEventData->mScrollPosition.y = ( mEventData->mScrollPosition.y < -space ) ? -space : mEventData->mScrollPosition.y;
2454 mEventData->mScrollPosition.y = ( mEventData->mScrollPosition.y > -mAlignmentOffset.y ) ? -mAlignmentOffset.y : mEventData->mScrollPosition.y;
2456 mEventData->mDecoratorUpdated = true;
2460 mEventData->mScrollPosition.y = 0.f;
2464 void Controller::Impl::ScrollToMakePositionVisible( const Vector2& position )
2466 // position is in actor's coords.
2467 const float positionEnd = position.x + ( mEventData->mDecorator ? mEventData->mDecorator->GetCursorWidth() : 0.f );
2469 // Transform the position to decorator coords.
2470 const float alignment = IsShowingRealText() ? mAlignmentOffset.x : 0.f;
2471 const float offset = mEventData->mScrollPosition.x + alignment;
2472 const float decoratorPositionBegin = position.x + offset;
2473 const float decoratorPositionEnd = positionEnd + offset;
2475 if( decoratorPositionBegin < 0.f )
2477 mEventData->mScrollPosition.x = -position.x - alignment;
2479 else if( decoratorPositionEnd > mVisualModel->mControlSize.width )
2481 mEventData->mScrollPosition.x = mVisualModel->mControlSize.width - positionEnd - alignment;
2485 void Controller::Impl::ScrollTextToMatchCursor( const CursorInfo& cursorInfo )
2487 // Get the current cursor position in decorator coords.
2488 const Vector2& currentCursorPosition = mEventData->mDecorator->GetPosition( PRIMARY_CURSOR );
2490 // Calculate the offset to match the cursor position before the character was deleted.
2491 mEventData->mScrollPosition.x = currentCursorPosition.x - cursorInfo.primaryPosition.x - mAlignmentOffset.x;
2493 ClampHorizontalScroll( mVisualModel->GetLayoutSize() );
2496 void Controller::Impl::RequestRelayout()
2498 mControlInterface.RequestTextRelayout();
2503 } // namespace Toolkit