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/multi-language-support.h>
30 #include <dali-toolkit/internal/text/segmentation.h>
31 #include <dali-toolkit/internal/text/shaper.h>
32 #include <dali-toolkit/internal/text/text-run-container.h>
37 #if defined(DEBUG_ENABLED)
38 Debug::Filter* gLogFilter = Debug::Filter::New(Debug::NoLogging, true, "LOG_TEXT_CONTROLS");
42 * @brief Some characters can be shaped in more than one glyph.
43 * This struct is used to retrieve metrics from these group of glyphs.
57 float fontHeight; ///< The font's height of that glyphs.
58 float advance; ///< The sum of all the advances of all the glyphs.
59 float ascender; ///< The font's ascender.
60 float xBearing; ///< The x bearing of the first glyph.
75 * @brief Get some glyph's metrics of a group of glyphs formed as a result of shaping one character.
77 * @param[in] glyphIndex The index to the first glyph.
78 * @param[in] numberOfGlyphs The number of glyphs.
79 * @param[out] glyphMetrics Some glyph metrics (font height, advance, ascender and x bearing).
80 * @param[in] visualModel The visual model.
81 * @param[in] metrics Used to access metrics from FontClient.
83 void GetGlyphsMetrics( GlyphIndex glyphIndex,
84 Length numberOfGlyphs,
85 GlyphMetrics& glyphMetrics,
86 VisualModelPtr& visualModel,
89 const GlyphInfo* glyphsBuffer = visualModel->mGlyphs.Begin();
91 const GlyphInfo& firstGlyph = *( glyphsBuffer + glyphIndex );
93 Text::FontMetrics fontMetrics;
94 metrics->GetFontMetrics( firstGlyph.fontId, fontMetrics );
96 glyphMetrics.fontHeight = fontMetrics.height;
97 glyphMetrics.advance = firstGlyph.advance;
98 glyphMetrics.ascender = fontMetrics.ascender;
99 glyphMetrics.xBearing = firstGlyph.xBearing;
101 for( unsigned int i = 1u; i < numberOfGlyphs; ++i )
103 const GlyphInfo& glyphInfo = *( glyphsBuffer + glyphIndex + i );
105 glyphMetrics.advance += glyphInfo.advance;
109 EventData::EventData( DecoratorPtr decorator )
110 : mDecorator( decorator ),
112 mPlaceholderTextActive(),
113 mPlaceholderTextInactive(),
114 mPlaceholderTextColor( 0.8f, 0.8f, 0.8f, 0.8f ),
118 mPrimaryCursorPosition( 0u ),
119 mLeftSelectionPosition( 0u ),
120 mRightSelectionPosition( 0u ),
121 mPreEditStartPosition( 0u ),
122 mPreEditLength( 0u ),
123 mIsShowingPlaceholderText( false ),
124 mPreEditFlag( false ),
125 mDecoratorUpdated( false ),
126 mCursorBlinkEnabled( true ),
127 mGrabHandleEnabled( true ),
128 mGrabHandlePopupEnabled( true ),
129 mSelectionEnabled( true ),
130 mHorizontalScrollingEnabled( true ),
131 mVerticalScrollingEnabled( false ),
132 mUpdateCursorPosition( false ),
133 mUpdateLeftSelectionPosition( false ),
134 mUpdateRightSelectionPosition( false ),
135 mScrollAfterUpdatePosition( false ),
136 mScrollAfterDelete( false ),
137 mAllTextSelected( false ),
138 mUpdateInputStyle( false )
140 mImfManager = ImfManager::Get();
143 EventData::~EventData()
146 bool Controller::Impl::ProcessInputEvents()
148 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "-->Controller::ProcessInputEvents\n" );
149 if( NULL == mEventData )
151 // Nothing to do if there is no text input.
152 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::ProcessInputEvents no event data\n" );
156 if( mEventData->mDecorator )
158 for( std::vector<Event>::iterator iter = mEventData->mEventQueue.begin();
159 iter != mEventData->mEventQueue.end();
164 case Event::CURSOR_KEY_EVENT:
166 OnCursorKeyEvent( *iter );
169 case Event::TAP_EVENT:
174 case Event::LONG_PRESS_EVENT:
176 OnLongPressEvent( *iter );
179 case Event::PAN_EVENT:
184 case Event::GRAB_HANDLE_EVENT:
185 case Event::LEFT_SELECTION_HANDLE_EVENT:
186 case Event::RIGHT_SELECTION_HANDLE_EVENT: // Fall through
188 OnHandleEvent( *iter );
193 OnSelectEvent( *iter );
196 case Event::SELECT_ALL:
205 // The cursor must also be repositioned after inserts into the model
206 if( mEventData->mUpdateCursorPosition )
208 // Updates the cursor position and scrolls the text to make it visible.
209 CursorInfo cursorInfo;
210 GetCursorPosition( mEventData->mPrimaryCursorPosition,
213 if( mEventData->mScrollAfterUpdatePosition )
215 ScrollToMakePositionVisible( cursorInfo.primaryPosition );
216 mEventData->mScrollAfterUpdatePosition = false;
218 else if( mEventData->mScrollAfterDelete )
220 ScrollTextToMatchCursor( cursorInfo );
221 mEventData->mScrollAfterDelete = false;
224 UpdateCursorPosition( cursorInfo );
226 mEventData->mDecoratorUpdated = true;
227 mEventData->mUpdateCursorPosition = false;
231 bool leftScroll = false;
232 bool rightScroll = false;
234 CursorInfo leftHandleInfo;
235 CursorInfo rightHandleInfo;
237 if( mEventData->mUpdateLeftSelectionPosition )
239 GetCursorPosition( mEventData->mLeftSelectionPosition,
242 if( mEventData->mScrollAfterUpdatePosition )
244 ScrollToMakePositionVisible( leftHandleInfo.primaryPosition );
249 if( mEventData->mUpdateRightSelectionPosition )
251 GetCursorPosition( mEventData->mRightSelectionPosition,
254 if( mEventData->mScrollAfterUpdatePosition )
256 ScrollToMakePositionVisible( rightHandleInfo.primaryPosition );
261 if( mEventData->mUpdateLeftSelectionPosition )
263 UpdateSelectionHandle( LEFT_SELECTION_HANDLE,
267 mEventData->mDecoratorUpdated = true;
270 if( mEventData->mUpdateRightSelectionPosition )
272 UpdateSelectionHandle( RIGHT_SELECTION_HANDLE,
276 mEventData->mDecoratorUpdated = true;
279 if( mEventData->mUpdateLeftSelectionPosition || mEventData->mUpdateRightSelectionPosition )
281 RepositionSelectionHandles();
283 mEventData->mUpdateLeftSelectionPosition = false;
284 mEventData->mUpdateRightSelectionPosition = false;
287 if( leftScroll || rightScroll )
289 mEventData->mScrollAfterUpdatePosition = false;
293 if( mEventData->mUpdateInputStyle )
295 // Set the default style first.
296 RetrieveDefaultInputStyle( mEventData->mInputStyle );
298 // Get the character index from the cursor index.
299 const CharacterIndex styleIndex = ( mEventData->mPrimaryCursorPosition > 0u ) ? mEventData->mPrimaryCursorPosition - 1u : 0u;
301 // Retrieve the style from the style runs stored in the logical model.
302 mLogicalModel->RetrieveStyle( styleIndex, mEventData->mInputStyle );
304 mEventData->mUpdateInputStyle = false;
307 mEventData->mEventQueue.clear();
309 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::ProcessInputEvents\n" );
311 const bool decoratorUpdated = mEventData->mDecoratorUpdated;
312 mEventData->mDecoratorUpdated = false;
314 return decoratorUpdated;
317 void Controller::Impl::CalculateTextUpdateIndices( Length& numberOfCharacters )
319 mTextUpdateInfo.mParagraphCharacterIndex = 0u;
320 mTextUpdateInfo.mStartGlyphIndex = 0u;
321 mTextUpdateInfo.mStartLineIndex = 0u;
322 numberOfCharacters = 0u;
324 const Length numberOfParagraphs = mLogicalModel->mParagraphInfo.Count();
325 if( 0u == numberOfParagraphs )
327 mTextUpdateInfo.mParagraphCharacterIndex = 0u;
328 numberOfCharacters = 0u;
330 mTextUpdateInfo.mRequestedNumberOfCharacters = mTextUpdateInfo.mNumberOfCharactersToAdd - mTextUpdateInfo.mNumberOfCharactersToRemove;
332 // Nothing else to do if there are no paragraphs.
336 // Find the paragraphs to be updated.
337 Vector<ParagraphRunIndex> paragraphsToBeUpdated;
338 if( mTextUpdateInfo.mCharacterIndex >= mTextUpdateInfo.mPreviousNumberOfCharacters )
340 // Text is being added at the end of the current text.
341 if( mTextUpdateInfo.mIsLastCharacterNewParagraph )
343 // Text is being added in a new paragraph after the last character of the text.
344 mTextUpdateInfo.mParagraphCharacterIndex = mTextUpdateInfo.mPreviousNumberOfCharacters;
345 numberOfCharacters = 0u;
346 mTextUpdateInfo.mRequestedNumberOfCharacters = mTextUpdateInfo.mNumberOfCharactersToAdd - mTextUpdateInfo.mNumberOfCharactersToRemove;
348 mTextUpdateInfo.mStartGlyphIndex = mVisualModel->mGlyphs.Count();
349 mTextUpdateInfo.mStartLineIndex = mVisualModel->mLines.Count() - 1u;
351 // Nothing else to do;
355 paragraphsToBeUpdated.PushBack( numberOfParagraphs - 1u );
359 CharacterIndex lastIndex = 0u;
360 if( mTextUpdateInfo.mFullRelayoutNeeded )
362 lastIndex = mTextUpdateInfo.mPreviousNumberOfCharacters;
366 lastIndex = ( mTextUpdateInfo.mNumberOfCharactersToRemove > 0u ) ? mTextUpdateInfo.mNumberOfCharactersToRemove : 1u;
368 mLogicalModel->FindParagraphs( mTextUpdateInfo.mCharacterIndex,
370 paragraphsToBeUpdated );
373 if( 0u != paragraphsToBeUpdated.Count() )
375 const ParagraphRunIndex firstParagraphIndex = *( paragraphsToBeUpdated.Begin() );
376 const ParagraphRun& firstParagraph = *( mLogicalModel->mParagraphInfo.Begin() + firstParagraphIndex );
377 mTextUpdateInfo.mParagraphCharacterIndex = firstParagraph.characterRun.characterIndex;
379 ParagraphRunIndex lastParagraphIndex = *( paragraphsToBeUpdated.End() - 1u );
380 const ParagraphRun& lastParagraph = *( mLogicalModel->mParagraphInfo.Begin() + lastParagraphIndex );
382 if( ( mTextUpdateInfo.mNumberOfCharactersToRemove > 0u ) && // Some character are removed.
383 ( lastParagraphIndex < numberOfParagraphs - 1u ) && // There is a next paragraph.
384 ( ( lastParagraph.characterRun.characterIndex + lastParagraph.characterRun.numberOfCharacters ) == // The last removed character is the new paragraph character.
385 ( mTextUpdateInfo.mCharacterIndex + mTextUpdateInfo.mNumberOfCharactersToRemove ) ) )
387 // The new paragraph character of the last updated paragraph has been removed so is going to be merged with the next one.
388 const ParagraphRun& lastParagraph = *( mLogicalModel->mParagraphInfo.Begin() + lastParagraphIndex + 1u );
390 numberOfCharacters = lastParagraph.characterRun.characterIndex + lastParagraph.characterRun.numberOfCharacters - mTextUpdateInfo.mParagraphCharacterIndex;
394 numberOfCharacters = lastParagraph.characterRun.characterIndex + lastParagraph.characterRun.numberOfCharacters - mTextUpdateInfo.mParagraphCharacterIndex;
397 mTextUpdateInfo.mRequestedNumberOfCharacters = numberOfCharacters + mTextUpdateInfo.mNumberOfCharactersToAdd - mTextUpdateInfo.mNumberOfCharactersToRemove;
398 mTextUpdateInfo.mStartGlyphIndex = *( mVisualModel->mCharactersToGlyph.Begin() + mTextUpdateInfo.mParagraphCharacterIndex );
401 void Controller::Impl::ClearFullModelData( OperationsMask operations )
403 if( GET_LINE_BREAKS & operations )
405 mLogicalModel->mLineBreakInfo.Clear();
406 mLogicalModel->mParagraphInfo.Clear();
409 if( GET_WORD_BREAKS & operations )
411 mLogicalModel->mLineBreakInfo.Clear();
414 if( GET_SCRIPTS & operations )
416 mLogicalModel->mScriptRuns.Clear();
419 if( VALIDATE_FONTS & operations )
421 mLogicalModel->mFontRuns.Clear();
424 if( 0u != mLogicalModel->mBidirectionalParagraphInfo.Count() )
426 if( BIDI_INFO & operations )
428 mLogicalModel->mBidirectionalParagraphInfo.Clear();
429 mLogicalModel->mCharacterDirections.Clear();
432 if( REORDER & operations )
434 // Free the allocated memory used to store the conversion table in the bidirectional line info run.
435 for( Vector<BidirectionalLineInfoRun>::Iterator it = mLogicalModel->mBidirectionalLineInfo.Begin(),
436 endIt = mLogicalModel->mBidirectionalLineInfo.End();
440 BidirectionalLineInfoRun& bidiLineInfo = *it;
442 free( bidiLineInfo.visualToLogicalMap );
443 bidiLineInfo.visualToLogicalMap = NULL;
445 mLogicalModel->mBidirectionalLineInfo.Clear();
447 mLogicalModel->mLogicalToVisualMap.Clear();
448 mLogicalModel->mVisualToLogicalMap.Clear();
452 if( SHAPE_TEXT & operations )
454 mVisualModel->mGlyphs.Clear();
455 mVisualModel->mGlyphsToCharacters.Clear();
456 mVisualModel->mCharactersToGlyph.Clear();
457 mVisualModel->mCharactersPerGlyph.Clear();
458 mVisualModel->mGlyphsPerCharacter.Clear();
459 mVisualModel->mGlyphPositions.Clear();
462 if( LAYOUT & operations )
464 mVisualModel->mLines.Clear();
468 void Controller::Impl::ClearCharacterModelData( CharacterIndex startIndex, CharacterIndex endIndex, OperationsMask operations )
470 const CharacterIndex endIndexPlusOne = endIndex + 1u;
472 if( GET_LINE_BREAKS & operations )
474 // Clear the line break info.
475 LineBreakInfo* lineBreakInfoBuffer = mLogicalModel->mLineBreakInfo.Begin();
477 mLogicalModel->mLineBreakInfo.Erase( lineBreakInfoBuffer + startIndex,
478 lineBreakInfoBuffer + endIndexPlusOne );
480 // Clear the paragraphs.
481 ClearCharacterRuns( startIndex,
483 mLogicalModel->mParagraphInfo );
486 if( GET_WORD_BREAKS & operations )
488 // Clear the word break info.
489 WordBreakInfo* wordBreakInfoBuffer = mLogicalModel->mWordBreakInfo.Begin();
491 mLogicalModel->mWordBreakInfo.Erase( wordBreakInfoBuffer + startIndex,
492 wordBreakInfoBuffer + endIndexPlusOne );
495 if( GET_SCRIPTS & operations )
497 // Clear the scripts.
498 ClearCharacterRuns( startIndex,
500 mLogicalModel->mScriptRuns );
503 if( VALIDATE_FONTS & operations )
506 ClearCharacterRuns( startIndex,
508 mLogicalModel->mFontRuns );
511 if( 0u != mLogicalModel->mBidirectionalParagraphInfo.Count() )
513 if( BIDI_INFO & operations )
515 // Clear the bidirectional paragraph info.
516 ClearCharacterRuns( startIndex,
518 mLogicalModel->mBidirectionalParagraphInfo );
520 // Clear the character's directions.
521 CharacterDirection* characterDirectionsBuffer = mLogicalModel->mCharacterDirections.Begin();
523 mLogicalModel->mCharacterDirections.Erase( characterDirectionsBuffer + startIndex,
524 characterDirectionsBuffer + endIndexPlusOne );
527 if( REORDER & operations )
529 uint32_t startRemoveIndex = mLogicalModel->mBidirectionalLineInfo.Count();
530 uint32_t endRemoveIndex = startRemoveIndex;
531 ClearCharacterRuns( startIndex,
533 mLogicalModel->mBidirectionalLineInfo,
537 BidirectionalLineInfoRun* bidirectionalLineInfoBuffer = mLogicalModel->mBidirectionalLineInfo.Begin();
539 // Free the allocated memory used to store the conversion table in the bidirectional line info run.
540 for( Vector<BidirectionalLineInfoRun>::Iterator it = bidirectionalLineInfoBuffer + startRemoveIndex,
541 endIt = bidirectionalLineInfoBuffer + endRemoveIndex;
545 BidirectionalLineInfoRun& bidiLineInfo = *it;
547 free( bidiLineInfo.visualToLogicalMap );
548 bidiLineInfo.visualToLogicalMap = NULL;
551 mLogicalModel->mBidirectionalLineInfo.Erase( bidirectionalLineInfoBuffer + startRemoveIndex,
552 bidirectionalLineInfoBuffer + endRemoveIndex );
554 // Clear the logical to visual and the visual to logical conversion tables.
555 CharacterIndex* logicalToVisualMapBuffer = mLogicalModel->mLogicalToVisualMap.Begin();
556 mLogicalModel->mLogicalToVisualMap.Erase( logicalToVisualMapBuffer + startIndex,
557 logicalToVisualMapBuffer + endIndexPlusOne );
559 CharacterIndex* visualToLogicalMapBuffer = mLogicalModel->mVisualToLogicalMap.Begin();
560 mLogicalModel->mVisualToLogicalMap.Erase( visualToLogicalMapBuffer + startIndex,
561 visualToLogicalMapBuffer + endIndexPlusOne );
566 void Controller::Impl::ClearGlyphModelData( CharacterIndex startIndex, CharacterIndex endIndex, OperationsMask operations )
568 const CharacterIndex endIndexPlusOne = endIndex + 1u;
569 const Length numberOfCharactersRemoved = endIndexPlusOne - startIndex;
571 const bool clearShape = SHAPE_TEXT & operations;
572 const bool clearLayout = LAYOUT & operations;
574 if( clearShape || clearLayout )
576 // Convert the character index to glyph index before deleting the character to glyph and the glyphs per character buffers.
577 GlyphIndex* charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
578 Length* glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
580 const GlyphIndex endGlyphIndex = *( charactersToGlyphBuffer + endIndex );
581 const GlyphIndex endGlyphIndexPlusOne = endGlyphIndex + *( glyphsPerCharacterBuffer + endIndex );
582 const Length numberOfGlyphsRemoved = endGlyphIndexPlusOne - mTextUpdateInfo.mStartGlyphIndex;
586 // Update the character to glyph indices.
587 for( Vector<GlyphIndex>::Iterator it = charactersToGlyphBuffer + endIndexPlusOne,
588 endIt = charactersToGlyphBuffer + mVisualModel->mCharactersToGlyph.Count();
592 CharacterIndex& index = *it;
593 index -= numberOfGlyphsRemoved;
596 // Clear the character to glyph conversion table.
597 mVisualModel->mCharactersToGlyph.Erase( charactersToGlyphBuffer + startIndex,
598 charactersToGlyphBuffer + endIndexPlusOne );
600 // Clear the glyphs per character table.
601 mVisualModel->mGlyphsPerCharacter.Erase( glyphsPerCharacterBuffer + startIndex,
602 glyphsPerCharacterBuffer + endIndexPlusOne );
604 // Clear the glyphs buffer.
605 GlyphInfo* glyphsBuffer = mVisualModel->mGlyphs.Begin();
606 mVisualModel->mGlyphs.Erase( glyphsBuffer + mTextUpdateInfo.mStartGlyphIndex,
607 glyphsBuffer + endGlyphIndexPlusOne );
609 CharacterIndex* glyphsToCharactersBuffer = mVisualModel->mGlyphsToCharacters.Begin();
611 // Update the glyph to character indices.
612 for( Vector<CharacterIndex>::Iterator it = glyphsToCharactersBuffer + endGlyphIndexPlusOne,
613 endIt = glyphsToCharactersBuffer + mVisualModel->mGlyphsToCharacters.Count();
617 CharacterIndex& index = *it;
618 index -= numberOfCharactersRemoved;
621 // Clear the glyphs to characters buffer.
622 mVisualModel->mGlyphsToCharacters.Erase( glyphsToCharactersBuffer + mTextUpdateInfo.mStartGlyphIndex,
623 glyphsToCharactersBuffer + endGlyphIndexPlusOne );
625 // Clear the characters per glyph buffer.
626 Length* charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
627 mVisualModel->mCharactersPerGlyph.Erase( charactersPerGlyphBuffer + mTextUpdateInfo.mStartGlyphIndex,
628 charactersPerGlyphBuffer + endGlyphIndexPlusOne );
630 // Clear the positions buffer.
631 Vector2* positionsBuffer = mVisualModel->mGlyphPositions.Begin();
632 mVisualModel->mGlyphPositions.Erase( positionsBuffer + mTextUpdateInfo.mStartGlyphIndex,
633 positionsBuffer + endGlyphIndexPlusOne );
639 uint32_t startRemoveIndex = mVisualModel->mLines.Count();
640 uint32_t endRemoveIndex = startRemoveIndex;
641 ClearCharacterRuns( startIndex,
643 mVisualModel->mLines,
647 // Will update the glyph runs.
648 uint32_t startRemoveGlyphIndex = mVisualModel->mLines.Count();
649 uint32_t endRemoveGlyphIndex = startRemoveIndex;
650 ClearGlyphRuns( mTextUpdateInfo.mStartGlyphIndex,
652 mVisualModel->mLines,
653 startRemoveGlyphIndex,
654 endRemoveGlyphIndex );
656 // Set the line index from where to insert the new laid-out lines.
657 mTextUpdateInfo.mStartLineIndex = startRemoveIndex;
659 LineRun* linesBuffer = mVisualModel->mLines.Begin();
660 mVisualModel->mLines.Erase( linesBuffer + startRemoveIndex,
661 linesBuffer + endRemoveIndex );
666 void Controller::Impl::ClearModelData( CharacterIndex startIndex, CharacterIndex endIndex, OperationsMask operations )
668 if( mTextUpdateInfo.mClearAll ||
669 ( ( 0u == startIndex ) &&
670 ( mTextUpdateInfo.mPreviousNumberOfCharacters == endIndex + 1u ) ) )
672 ClearFullModelData( operations );
676 // Clear the model data related with characters.
677 ClearCharacterModelData( startIndex, endIndex, operations );
679 // Clear the model data related with glyphs.
680 ClearGlyphModelData( startIndex, endIndex, operations );
683 // The estimated number of lines. Used to avoid reallocations when layouting.
684 mTextUpdateInfo.mEstimatedNumberOfLines = std::max( mVisualModel->mLines.Count(), mLogicalModel->mParagraphInfo.Count() );
686 mVisualModel->ClearCaches();
688 // TODO finish the mark-up.
689 mVisualModel->mColorRuns.Clear();
692 void Controller::Impl::UpdateModel( OperationsMask operationsRequired )
694 DALI_LOG_INFO( gLogFilter, Debug::General, "Controller::UpdateModel\n" );
696 // Calculate the operations to be done.
697 const OperationsMask operations = static_cast<OperationsMask>( mOperationsPending & operationsRequired );
699 if( NO_OPERATION == operations )
701 // Nothing to do if no operations are pending and required.
705 Vector<Character>& utf32Characters = mLogicalModel->mText;
707 const Length numberOfCharacters = utf32Characters.Count();
709 // Index to the first character of the first paragraph to be updated.
710 CharacterIndex startIndex = 0u;
711 // Number of characters of the paragraphs to be removed.
712 Length paragraphCharacters = 0u;
714 CalculateTextUpdateIndices( paragraphCharacters );
715 startIndex = mTextUpdateInfo.mParagraphCharacterIndex;
717 if( mTextUpdateInfo.mClearAll ||
718 ( 0u != paragraphCharacters ) )
720 ClearModelData( startIndex, startIndex + ( ( paragraphCharacters > 0u ) ? paragraphCharacters - 1u : 0u ), operationsRequired );
723 mTextUpdateInfo.mClearAll = false;
725 Vector<LineBreakInfo>& lineBreakInfo = mLogicalModel->mLineBreakInfo;
726 const Length requestedNumberOfCharacters = mTextUpdateInfo.mRequestedNumberOfCharacters;
728 if( GET_LINE_BREAKS & operations )
730 // Retrieves the line break info. The line break info is used to split the text in 'paragraphs' to
731 // calculate the bidirectional info for each 'paragraph'.
732 // It's also used to layout the text (where it should be a new line) or to shape the text (text in different lines
733 // is not shaped together).
734 lineBreakInfo.Resize( numberOfCharacters, TextAbstraction::LINE_NO_BREAK );
736 SetLineBreakInfo( utf32Characters,
738 requestedNumberOfCharacters,
741 // Create the paragraph info.
742 mLogicalModel->CreateParagraphInfo( startIndex,
743 requestedNumberOfCharacters );
746 Vector<WordBreakInfo>& wordBreakInfo = mLogicalModel->mWordBreakInfo;
747 if( GET_WORD_BREAKS & operations )
749 // Retrieves the word break info. The word break info is used to layout the text (where to wrap the text in lines).
750 wordBreakInfo.Resize( numberOfCharacters, TextAbstraction::WORD_NO_BREAK );
752 SetWordBreakInfo( utf32Characters,
754 requestedNumberOfCharacters,
758 const bool getScripts = GET_SCRIPTS & operations;
759 const bool validateFonts = VALIDATE_FONTS & operations;
761 Vector<ScriptRun>& scripts = mLogicalModel->mScriptRuns;
762 Vector<FontRun>& validFonts = mLogicalModel->mFontRuns;
764 if( getScripts || validateFonts )
766 // Validates the fonts assigned by the application or assigns default ones.
767 // It makes sure all the characters are going to be rendered by the correct font.
768 MultilanguageSupport multilanguageSupport = MultilanguageSupport::Get();
772 // Retrieves the scripts used in the text.
773 multilanguageSupport.SetScripts( utf32Characters,
775 requestedNumberOfCharacters,
781 // Validate the fonts set through the mark-up string.
782 Vector<FontDescriptionRun>& fontDescriptionRuns = mLogicalModel->mFontDescriptionRuns;
784 // Get the default font id.
785 const FontId defaultFontId = ( NULL == mFontDefaults ) ? 0u : mFontDefaults->GetFontId( mFontClient );
787 // Validates the fonts. If there is a character with no assigned font it sets a default one.
788 // After this call, fonts are validated.
789 multilanguageSupport.ValidateFonts( utf32Characters,
794 requestedNumberOfCharacters,
799 Vector<Character> mirroredUtf32Characters;
800 bool textMirrored = false;
801 const Length numberOfParagraphs = mLogicalModel->mParagraphInfo.Count();
802 if( BIDI_INFO & operations )
804 Vector<BidirectionalParagraphInfoRun>& bidirectionalInfo = mLogicalModel->mBidirectionalParagraphInfo;
805 bidirectionalInfo.Reserve( numberOfParagraphs );
807 // Calculates the bidirectional info for the whole paragraph if it contains right to left scripts.
808 SetBidirectionalInfo( utf32Characters,
812 requestedNumberOfCharacters,
815 if( 0u != bidirectionalInfo.Count() )
817 // Only set the character directions if there is right to left characters.
818 Vector<CharacterDirection>& directions = mLogicalModel->mCharacterDirections;
819 GetCharactersDirection( bidirectionalInfo,
822 requestedNumberOfCharacters,
825 // This paragraph has right to left text. Some characters may need to be mirrored.
826 // TODO: consider if the mirrored string can be stored as well.
828 textMirrored = GetMirroredText( utf32Characters,
832 requestedNumberOfCharacters,
833 mirroredUtf32Characters );
837 // There is no right to left characters. Clear the directions vector.
838 mLogicalModel->mCharacterDirections.Clear();
842 Vector<GlyphInfo>& glyphs = mVisualModel->mGlyphs;
843 Vector<CharacterIndex>& glyphsToCharactersMap = mVisualModel->mGlyphsToCharacters;
844 Vector<Length>& charactersPerGlyph = mVisualModel->mCharactersPerGlyph;
845 Vector<GlyphIndex> newParagraphGlyphs;
846 newParagraphGlyphs.Reserve( numberOfParagraphs );
848 const Length currentNumberOfGlyphs = glyphs.Count();
849 if( SHAPE_TEXT & operations )
851 const Vector<Character>& textToShape = textMirrored ? mirroredUtf32Characters : utf32Characters;
853 ShapeText( textToShape,
858 mTextUpdateInfo.mStartGlyphIndex,
859 requestedNumberOfCharacters,
861 glyphsToCharactersMap,
863 newParagraphGlyphs );
865 // Create the 'number of glyphs' per character and the glyph to character conversion tables.
866 mVisualModel->CreateGlyphsPerCharacterTable( startIndex, mTextUpdateInfo.mStartGlyphIndex, requestedNumberOfCharacters );
867 mVisualModel->CreateCharacterToGlyphTable( startIndex, mTextUpdateInfo.mStartGlyphIndex, requestedNumberOfCharacters );
870 const Length numberOfGlyphs = glyphs.Count() - currentNumberOfGlyphs;
872 if( GET_GLYPH_METRICS & operations )
874 GlyphInfo* glyphsBuffer = glyphs.Begin();
875 mMetrics->GetGlyphMetrics( glyphsBuffer + mTextUpdateInfo.mStartGlyphIndex, numberOfGlyphs );
877 // Update the width and advance of all new paragraph characters.
878 for( Vector<GlyphIndex>::ConstIterator it = newParagraphGlyphs.Begin(), endIt = newParagraphGlyphs.End(); it != endIt; ++it )
880 const GlyphIndex index = *it;
881 GlyphInfo& glyph = *( glyphsBuffer + index );
883 glyph.xBearing = 0.f;
889 if( ( NULL != mEventData ) &&
890 mEventData->mPreEditFlag &&
891 ( 0u != mVisualModel->mCharactersToGlyph.Count() ) )
893 // Add the underline for the pre-edit text.
894 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
895 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
897 const GlyphIndex glyphStart = *( charactersToGlyphBuffer + mEventData->mPreEditStartPosition );
898 const CharacterIndex lastPreEditCharacter = mEventData->mPreEditStartPosition + ( ( mEventData->mPreEditLength > 0u ) ? mEventData->mPreEditLength - 1u : 0u );
899 const Length numberOfGlyphsLastCharacter = *( glyphsPerCharacterBuffer + lastPreEditCharacter );
900 const GlyphIndex glyphEnd = *( charactersToGlyphBuffer + lastPreEditCharacter ) + ( numberOfGlyphsLastCharacter > 1u ? numberOfGlyphsLastCharacter - 1u : 0u );
902 GlyphRun underlineRun;
903 underlineRun.glyphIndex = glyphStart;
904 underlineRun.numberOfGlyphs = 1u + glyphEnd - glyphStart;
906 // TODO: At the moment the underline runs are only for pre-edit.
907 mVisualModel->mUnderlineRuns.PushBack( underlineRun );
910 // The estimated number of lines. Used to avoid reallocations when layouting.
911 mTextUpdateInfo.mEstimatedNumberOfLines = std::max( mVisualModel->mLines.Count(), mLogicalModel->mParagraphInfo.Count() );
913 // Set the previous number of characters for the next time the text is updated.
914 mTextUpdateInfo.mPreviousNumberOfCharacters = numberOfCharacters;
917 bool Controller::Impl::UpdateModelStyle( OperationsMask operationsRequired )
919 bool updated = false;
921 if( COLOR & operationsRequired )
923 // Set the color runs in glyphs.
924 SetColorSegmentationInfo( mLogicalModel->mColorRuns,
925 mVisualModel->mCharactersToGlyph,
926 mVisualModel->mGlyphsPerCharacter,
927 mVisualModel->mColorRuns );
935 void Controller::Impl::RetrieveDefaultInputStyle( InputStyle& inputStyle )
937 // Sets the default text's color.
938 inputStyle.textColor = mTextColor;
940 // Sets the default font's family name, weight, width, slant and size.
943 inputStyle.familyName = mFontDefaults->mFontDescription.family;
944 inputStyle.weight = mFontDefaults->mFontDescription.weight;
945 inputStyle.width = mFontDefaults->mFontDescription.width;
946 inputStyle.slant = mFontDefaults->mFontDescription.slant;
947 inputStyle.size = mFontDefaults->mDefaultPointSize;
949 inputStyle.familyDefined = mFontDefaults->familyDefined;
950 inputStyle.weightDefined = mFontDefaults->weightDefined;
951 inputStyle.widthDefined = mFontDefaults->widthDefined;
952 inputStyle.slantDefined = mFontDefaults->slantDefined;
953 inputStyle.sizeDefined = mFontDefaults->sizeDefined;
957 inputStyle.familyName.clear();
958 inputStyle.weight = TextAbstraction::FontWeight::NORMAL;
959 inputStyle.width = TextAbstraction::FontWidth::NORMAL;
960 inputStyle.slant = TextAbstraction::FontSlant::NORMAL;
961 inputStyle.size = 0.f;
963 inputStyle.familyDefined = false;
964 inputStyle.weightDefined = false;
965 inputStyle.widthDefined = false;
966 inputStyle.slantDefined = false;
967 inputStyle.sizeDefined = false;
971 float Controller::Impl::GetDefaultFontLineHeight()
973 FontId defaultFontId = 0u;
974 if( NULL == mFontDefaults )
976 TextAbstraction::FontDescription fontDescription;
977 defaultFontId = mFontClient.GetFontId( fontDescription );
981 defaultFontId = mFontDefaults->GetFontId( mFontClient );
984 Text::FontMetrics fontMetrics;
985 mMetrics->GetFontMetrics( defaultFontId, fontMetrics );
987 return( fontMetrics.ascender - fontMetrics.descender );
990 void Controller::Impl::OnCursorKeyEvent( const Event& event )
992 if( NULL == mEventData )
994 // Nothing to do if there is no text input.
998 int keyCode = event.p1.mInt;
1000 if( Dali::DALI_KEY_CURSOR_LEFT == keyCode )
1002 if( mEventData->mPrimaryCursorPosition > 0u )
1004 mEventData->mPrimaryCursorPosition = CalculateNewCursorIndex( mEventData->mPrimaryCursorPosition - 1u );
1007 else if( Dali::DALI_KEY_CURSOR_RIGHT == keyCode )
1009 if( mLogicalModel->mText.Count() > mEventData->mPrimaryCursorPosition )
1011 mEventData->mPrimaryCursorPosition = CalculateNewCursorIndex( mEventData->mPrimaryCursorPosition );
1014 else if( Dali::DALI_KEY_CURSOR_UP == keyCode )
1018 else if( Dali::DALI_KEY_CURSOR_DOWN == keyCode )
1023 mEventData->mUpdateCursorPosition = true;
1024 mEventData->mUpdateInputStyle = true;
1025 mEventData->mScrollAfterUpdatePosition = true;
1028 void Controller::Impl::OnTapEvent( const Event& event )
1030 if( NULL != mEventData )
1032 const unsigned int tapCount = event.p1.mUint;
1034 if( 1u == tapCount )
1036 if( IsShowingRealText() )
1038 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
1039 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
1041 mEventData->mPrimaryCursorPosition = GetClosestCursorIndex( xPosition,
1044 // When the cursor position is changing, delay cursor blinking
1045 mEventData->mDecorator->DelayCursorBlink();
1049 mEventData->mPrimaryCursorPosition = 0u;
1052 mEventData->mUpdateCursorPosition = true;
1053 mEventData->mScrollAfterUpdatePosition = true;
1054 mEventData->mUpdateInputStyle = true;
1056 // Notify the cursor position to the imf manager.
1057 if( mEventData->mImfManager )
1059 mEventData->mImfManager.SetCursorPosition( mEventData->mPrimaryCursorPosition );
1060 mEventData->mImfManager.NotifyCursorPosition();
1066 void Controller::Impl::OnPanEvent( const Event& event )
1068 if( NULL == mEventData )
1070 // Nothing to do if there is no text input.
1074 int state = event.p1.mInt;
1076 if( Gesture::Started == state ||
1077 Gesture::Continuing == state )
1079 const Vector2& actualSize = mVisualModel->GetLayoutSize();
1080 const Vector2 currentScroll = mEventData->mScrollPosition;
1082 if( mEventData->mHorizontalScrollingEnabled )
1084 const float displacementX = event.p2.mFloat;
1085 mEventData->mScrollPosition.x += displacementX;
1087 ClampHorizontalScroll( actualSize );
1090 if( mEventData->mVerticalScrollingEnabled )
1092 const float displacementY = event.p3.mFloat;
1093 mEventData->mScrollPosition.y += displacementY;
1095 ClampVerticalScroll( actualSize );
1098 if( mEventData->mDecorator )
1100 mEventData->mDecorator->UpdatePositions( mEventData->mScrollPosition - currentScroll );
1105 void Controller::Impl::OnLongPressEvent( const Event& event )
1107 DALI_LOG_INFO( gLogFilter, Debug::General, "Controller::OnLongPressEvent\n" );
1109 if( EventData::EDITING == mEventData->mState )
1111 ChangeState ( EventData::EDITING_WITH_POPUP );
1112 mEventData->mDecoratorUpdated = true;
1116 void Controller::Impl::OnHandleEvent( const Event& event )
1118 if( NULL == mEventData )
1120 // Nothing to do if there is no text input.
1124 const unsigned int state = event.p1.mUint;
1125 const bool handleStopScrolling = ( HANDLE_STOP_SCROLLING == state );
1127 if( HANDLE_PRESSED == state )
1129 // The event.p2 and event.p3 are in decorator coords. Need to transforms to text coords.
1130 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
1131 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
1133 const CharacterIndex handleNewPosition = GetClosestCursorIndex( xPosition, yPosition );
1135 if( Event::GRAB_HANDLE_EVENT == event.type )
1137 ChangeState ( EventData::GRAB_HANDLE_PANNING );
1139 if( handleNewPosition != mEventData->mPrimaryCursorPosition )
1141 mEventData->mPrimaryCursorPosition = handleNewPosition;
1142 mEventData->mUpdateCursorPosition = true;
1145 else if( Event::LEFT_SELECTION_HANDLE_EVENT == event.type )
1147 ChangeState ( EventData::SELECTION_HANDLE_PANNING );
1149 if( ( handleNewPosition != mEventData->mLeftSelectionPosition ) &&
1150 ( handleNewPosition != mEventData->mRightSelectionPosition ) )
1152 mEventData->mLeftSelectionPosition = handleNewPosition;
1154 mEventData->mUpdateLeftSelectionPosition = true;
1157 else if( Event::RIGHT_SELECTION_HANDLE_EVENT == event.type )
1159 ChangeState ( EventData::SELECTION_HANDLE_PANNING );
1161 if( ( handleNewPosition != mEventData->mRightSelectionPosition ) &&
1162 ( handleNewPosition != mEventData->mLeftSelectionPosition ) )
1164 mEventData->mRightSelectionPosition = handleNewPosition;
1166 mEventData->mUpdateRightSelectionPosition = true;
1169 } // end ( HANDLE_PRESSED == state )
1170 else if( ( HANDLE_RELEASED == state ) ||
1171 handleStopScrolling )
1173 CharacterIndex handlePosition = 0u;
1174 if( handleStopScrolling )
1176 // The event.p2 and event.p3 are in decorator coords. Need to transforms to text coords.
1177 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
1178 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
1180 handlePosition = GetClosestCursorIndex( xPosition, yPosition );
1183 if( Event::GRAB_HANDLE_EVENT == event.type )
1185 mEventData->mUpdateCursorPosition = true;
1186 mEventData->mUpdateInputStyle = true;
1188 if( !IsClipboardEmpty() )
1190 ChangeState( EventData::EDITING_WITH_PASTE_POPUP ); // Moving grabhandle will show Paste Popup
1193 if( handleStopScrolling )
1195 mEventData->mScrollAfterUpdatePosition = mEventData->mPrimaryCursorPosition != handlePosition;
1196 mEventData->mPrimaryCursorPosition = handlePosition;
1199 else if( Event::LEFT_SELECTION_HANDLE_EVENT == event.type )
1201 ChangeState( EventData::SELECTING );
1203 if( handleStopScrolling )
1205 mEventData->mUpdateLeftSelectionPosition = ( mEventData->mRightSelectionPosition != handlePosition );
1206 mEventData->mScrollAfterUpdatePosition = mEventData->mUpdateLeftSelectionPosition;
1208 if( mEventData->mUpdateLeftSelectionPosition )
1210 mEventData->mLeftSelectionPosition = handlePosition;
1214 else if( Event::RIGHT_SELECTION_HANDLE_EVENT == event.type )
1216 ChangeState( EventData::SELECTING );
1218 if( handleStopScrolling )
1220 mEventData->mUpdateRightSelectionPosition = ( mEventData->mLeftSelectionPosition != handlePosition );
1221 mEventData->mScrollAfterUpdatePosition = mEventData->mUpdateRightSelectionPosition;
1222 if( mEventData->mUpdateRightSelectionPosition )
1224 mEventData->mRightSelectionPosition = handlePosition;
1229 mEventData->mDecoratorUpdated = true;
1230 } // end ( ( HANDLE_RELEASED == state ) || ( HANDLE_STOP_SCROLLING == state ) )
1231 else if( HANDLE_SCROLLING == state )
1233 const float xSpeed = event.p2.mFloat;
1234 const Vector2& actualSize = mVisualModel->GetLayoutSize();
1235 const Vector2 currentScrollPosition = mEventData->mScrollPosition;
1237 mEventData->mScrollPosition.x += xSpeed;
1239 ClampHorizontalScroll( actualSize );
1241 bool endOfScroll = false;
1242 if( Vector2::ZERO == ( currentScrollPosition - mEventData->mScrollPosition ) )
1244 // Notify the decorator there is no more text to scroll.
1245 // The decorator won't send more scroll events.
1246 mEventData->mDecorator->NotifyEndOfScroll();
1247 // Still need to set the position of the handle.
1251 // Set the position of the handle.
1252 const bool scrollRightDirection = xSpeed > 0.f;
1253 const bool leftSelectionHandleEvent = Event::LEFT_SELECTION_HANDLE_EVENT == event.type;
1254 const bool rightSelectionHandleEvent = Event::RIGHT_SELECTION_HANDLE_EVENT == event.type;
1256 if( Event::GRAB_HANDLE_EVENT == event.type )
1258 ChangeState( EventData::GRAB_HANDLE_PANNING );
1260 Vector2 position = mEventData->mDecorator->GetPosition( GRAB_HANDLE );
1262 // Position the grag handle close to either the left or right edge.
1263 position.x = scrollRightDirection ? 0.f : mVisualModel->mControlSize.width;
1265 // Get the new handle position.
1266 // The grab handle's position is in decorator coords. Need to transforms to text coords.
1267 const CharacterIndex handlePosition = GetClosestCursorIndex( position.x - mEventData->mScrollPosition.x - mAlignmentOffset.x,
1268 position.y - mEventData->mScrollPosition.y - mAlignmentOffset.y );
1270 mEventData->mUpdateCursorPosition = mEventData->mPrimaryCursorPosition != handlePosition;
1271 mEventData->mScrollAfterUpdatePosition = mEventData->mUpdateCursorPosition;
1272 mEventData->mPrimaryCursorPosition = handlePosition;
1273 mEventData->mUpdateInputStyle = mEventData->mUpdateCursorPosition;
1275 else if( leftSelectionHandleEvent || rightSelectionHandleEvent )
1277 // TODO: This is recalculating the selection box every time the text is scrolled with the selection handles.
1278 // Think if something can be done to save power.
1280 ChangeState( EventData::SELECTION_HANDLE_PANNING );
1282 Vector2 position = mEventData->mDecorator->GetPosition( leftSelectionHandleEvent ? Text::LEFT_SELECTION_HANDLE : Text::RIGHT_SELECTION_HANDLE );
1284 // Position the selection handle close to either the left or right edge.
1285 position.x = scrollRightDirection ? 0.f : mVisualModel->mControlSize.width;
1287 // Get the new handle position.
1288 // The selection handle's position is in decorator coords. Need to transforms to text coords.
1289 const CharacterIndex handlePosition = GetClosestCursorIndex( position.x - mEventData->mScrollPosition.x - mAlignmentOffset.x,
1290 position.y - mEventData->mScrollPosition.y - mAlignmentOffset.y );
1292 if( leftSelectionHandleEvent )
1294 const bool differentHandles = ( mEventData->mLeftSelectionPosition != handlePosition ) && ( mEventData->mRightSelectionPosition != handlePosition );
1295 mEventData->mUpdateLeftSelectionPosition = endOfScroll || differentHandles;
1296 if( differentHandles )
1298 mEventData->mLeftSelectionPosition = handlePosition;
1303 const bool differentHandles = ( mEventData->mRightSelectionPosition != handlePosition ) && ( mEventData->mLeftSelectionPosition != handlePosition );
1304 mEventData->mUpdateRightSelectionPosition = endOfScroll || differentHandles;
1305 if( differentHandles )
1307 mEventData->mRightSelectionPosition = handlePosition;
1311 if( mEventData->mUpdateLeftSelectionPosition || mEventData->mUpdateRightSelectionPosition )
1313 RepositionSelectionHandles();
1315 mEventData->mScrollAfterUpdatePosition = true;
1318 mEventData->mDecoratorUpdated = true;
1319 } // end ( HANDLE_SCROLLING == state )
1322 void Controller::Impl::OnSelectEvent( const Event& event )
1324 if( NULL == mEventData )
1326 // Nothing to do if there is no text.
1330 if( mEventData->mSelectionEnabled )
1332 // The event.p2 and event.p3 are in decorator coords. Need to transforms to text coords.
1333 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
1334 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
1336 // Calculates the logical position from the x,y coords.
1337 RepositionSelectionHandles( xPosition,
1340 mEventData->mUpdateLeftSelectionPosition = true;
1341 mEventData->mUpdateRightSelectionPosition = true;
1343 mEventData->mScrollAfterUpdatePosition = ( mEventData->mLeftSelectionPosition != mEventData->mRightSelectionPosition );
1347 void Controller::Impl::OnSelectAllEvent()
1349 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "OnSelectAllEvent mEventData->mSelectionEnabled%s \n", mEventData->mSelectionEnabled?"true":"false");
1351 if( NULL == mEventData )
1353 // Nothing to do if there is no text.
1357 if( mEventData->mSelectionEnabled )
1359 mEventData->mLeftSelectionPosition = 0u;
1360 mEventData->mRightSelectionPosition = mLogicalModel->mText.Count();
1362 mEventData->mScrollAfterUpdatePosition = true;
1363 mEventData->mUpdateLeftSelectionPosition = true;
1364 mEventData->mUpdateRightSelectionPosition = true;
1368 void Controller::Impl::RetrieveSelection( std::string& selectedText, bool deleteAfterRetrieval )
1370 if( mEventData->mLeftSelectionPosition == mEventData->mRightSelectionPosition )
1372 // Nothing to select if handles are in the same place.
1373 selectedText.clear();
1377 const bool handlesCrossed = mEventData->mLeftSelectionPosition > mEventData->mRightSelectionPosition;
1379 //Get start and end position of selection
1380 const CharacterIndex startOfSelectedText = handlesCrossed ? mEventData->mRightSelectionPosition : mEventData->mLeftSelectionPosition;
1381 const Length lengthOfSelectedText = ( handlesCrossed ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition ) - startOfSelectedText;
1383 Vector<Character>& utf32Characters = mLogicalModel->mText;
1384 const Length numberOfCharacters = utf32Characters.Count();
1386 // Validate the start and end selection points
1387 if( ( startOfSelectedText + lengthOfSelectedText ) <= numberOfCharacters )
1389 //Get text as a UTF8 string
1390 Utf32ToUtf8( &utf32Characters[startOfSelectedText], lengthOfSelectedText, selectedText );
1392 if( deleteAfterRetrieval ) // Only delete text if copied successfully
1394 // Set as input style the style of the first deleted character.
1395 mLogicalModel->RetrieveStyle( startOfSelectedText, mEventData->mInputStyle );
1397 mLogicalModel->UpdateTextStyleRuns( startOfSelectedText, -static_cast<int>( lengthOfSelectedText ) );
1399 // Mark the paragraphs to be updated.
1400 mTextUpdateInfo.mCharacterIndex = startOfSelectedText;
1401 mTextUpdateInfo.mNumberOfCharactersToRemove = lengthOfSelectedText;
1403 // Delete text between handles
1404 Vector<Character>::Iterator first = utf32Characters.Begin() + startOfSelectedText;
1405 Vector<Character>::Iterator last = first + lengthOfSelectedText;
1406 utf32Characters.Erase( first, last );
1408 // Scroll after delete.
1409 mEventData->mPrimaryCursorPosition = handlesCrossed ? mEventData->mRightSelectionPosition : mEventData->mLeftSelectionPosition;
1410 mEventData->mScrollAfterDelete = true;
1412 // Udpade the cursor position and the decorator.
1413 // Scroll after the position is updated if is not scrolling after delete.
1414 mEventData->mUpdateCursorPosition = true;
1415 mEventData->mScrollAfterUpdatePosition = !mEventData->mScrollAfterDelete;
1416 mEventData->mDecoratorUpdated = true;
1420 void Controller::Impl::ShowClipboard()
1424 mClipboard.ShowClipboard();
1428 void Controller::Impl::HideClipboard()
1432 mClipboard.HideClipboard();
1436 bool Controller::Impl::CopyStringToClipboard( std::string& source )
1438 //Send string to clipboard
1439 return ( mClipboard && mClipboard.SetItem( source ) );
1442 void Controller::Impl::SendSelectionToClipboard( bool deleteAfterSending )
1444 std::string selectedText;
1445 RetrieveSelection( selectedText, deleteAfterSending );
1446 CopyStringToClipboard( selectedText );
1447 ChangeState( EventData::EDITING );
1450 void Controller::Impl::GetTextFromClipboard( unsigned int itemIndex, std::string& retrievedString )
1454 retrievedString = mClipboard.GetItem( itemIndex );
1458 void Controller::Impl::RepositionSelectionHandles()
1460 CharacterIndex selectionStart = mEventData->mLeftSelectionPosition;
1461 CharacterIndex selectionEnd = mEventData->mRightSelectionPosition;
1463 if( selectionStart == selectionEnd )
1465 // Nothing to select if handles are in the same place.
1469 mEventData->mDecorator->ClearHighlights();
1471 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
1472 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
1473 const GlyphInfo* const glyphsBuffer = mVisualModel->mGlyphs.Begin();
1474 const Vector2* const positionsBuffer = mVisualModel->mGlyphPositions.Begin();
1475 const Length* const charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
1476 const CharacterIndex* const glyphToCharacterBuffer = mVisualModel->mGlyphsToCharacters.Begin();
1477 const CharacterDirection* const modelCharacterDirectionsBuffer = ( 0u != mLogicalModel->mCharacterDirections.Count() ) ? mLogicalModel->mCharacterDirections.Begin() : NULL;
1479 // TODO: Better algorithm to create the highlight box.
1480 // TODO: Multi-line.
1482 // Get the height of the line.
1483 const Vector<LineRun>& lines = mVisualModel->mLines;
1484 const LineRun& firstLine = *lines.Begin();
1485 const float height = firstLine.ascender + -firstLine.descender;
1487 const bool isLastCharacter = selectionEnd >= mLogicalModel->mText.Count();
1488 const bool startDirection = ( ( NULL == modelCharacterDirectionsBuffer ) ? false : *( modelCharacterDirectionsBuffer + selectionStart ) );
1489 const bool endDirection = ( ( NULL == modelCharacterDirectionsBuffer ) ? false : *( modelCharacterDirectionsBuffer + ( selectionEnd - ( isLastCharacter ? 1u : 0u ) ) ) );
1491 // Swap the indices if the start is greater than the end.
1492 const bool indicesSwapped = selectionStart > selectionEnd;
1494 // Tell the decorator to flip the selection handles if needed.
1495 mEventData->mDecorator->SetSelectionHandleFlipState( indicesSwapped, startDirection, endDirection );
1497 if( indicesSwapped )
1499 std::swap( selectionStart, selectionEnd );
1502 // Get the indices to the first and last selected glyphs.
1503 const CharacterIndex selectionEndMinusOne = selectionEnd - 1u;
1504 const GlyphIndex glyphStart = *( charactersToGlyphBuffer + selectionStart );
1505 const Length numberOfGlyphs = *( glyphsPerCharacterBuffer + selectionEndMinusOne );
1506 const GlyphIndex glyphEnd = *( charactersToGlyphBuffer + selectionEndMinusOne ) + ( ( numberOfGlyphs > 0 ) ? numberOfGlyphs - 1u : 0u );
1508 // Check if the first glyph is a ligature that must be broken like Latin ff, fi, or Arabic ﻻ, etc which needs special code.
1509 const Length numberOfCharactersStart = *( charactersPerGlyphBuffer + glyphStart );
1510 bool splitStartGlyph = ( numberOfCharactersStart > 1u ) && HasLigatureMustBreak( mLogicalModel->GetScript( selectionStart ) );
1512 // Check if the last glyph is a ligature that must be broken like Latin ff, fi, or Arabic ﻻ, etc which needs special code.
1513 const Length numberOfCharactersEnd = *( charactersPerGlyphBuffer + glyphEnd );
1514 bool splitEndGlyph = ( glyphStart != glyphEnd ) && ( numberOfCharactersEnd > 1u ) && HasLigatureMustBreak( mLogicalModel->GetScript( selectionEndMinusOne ) );
1516 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
1518 // Traverse the glyphs.
1519 for( GlyphIndex index = glyphStart; index <= glyphEnd; ++index )
1521 const GlyphInfo& glyph = *( glyphsBuffer + index );
1522 const Vector2& position = *( positionsBuffer + index );
1524 if( splitStartGlyph )
1526 // 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.
1528 const float glyphAdvance = glyph.advance / static_cast<float>( numberOfCharactersStart );
1529 const CharacterIndex interGlyphIndex = selectionStart - *( glyphToCharacterBuffer + glyphStart );
1530 // Get the direction of the character.
1531 CharacterDirection isCurrentRightToLeft = false;
1532 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
1534 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + selectionStart );
1537 // The end point could be in the middle of the ligature.
1538 // Calculate the number of characters selected.
1539 const Length numberOfCharacters = ( glyphStart == glyphEnd ) ? ( selectionEnd - selectionStart ) : ( numberOfCharactersStart - interGlyphIndex );
1541 const float xPosition = position.x - glyph.xBearing + offset.x + glyphAdvance * static_cast<float>( isCurrentRightToLeft ? ( numberOfCharactersStart - interGlyphIndex - numberOfCharacters ) : interGlyphIndex );
1543 mEventData->mDecorator->AddHighlight( xPosition,
1545 xPosition + static_cast<float>( numberOfCharacters ) * glyphAdvance,
1546 offset.y + height );
1548 splitStartGlyph = false;
1552 if( splitEndGlyph && ( index == glyphEnd ) )
1554 // 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.
1556 const float glyphAdvance = glyph.advance / static_cast<float>( numberOfCharactersEnd );
1557 const CharacterIndex interGlyphIndex = selectionEnd - *( glyphToCharacterBuffer + glyphEnd );
1558 // Get the direction of the character.
1559 CharacterDirection isCurrentRightToLeft = false;
1560 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
1562 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + selectionEnd );
1565 const Length numberOfCharacters = numberOfCharactersEnd - interGlyphIndex;
1567 const float xPosition = position.x - glyph.xBearing + offset.x + ( isCurrentRightToLeft ? ( glyphAdvance * static_cast<float>( numberOfCharacters ) ) : 0.f );
1568 mEventData->mDecorator->AddHighlight( xPosition,
1570 xPosition + static_cast<float>( interGlyphIndex ) * glyphAdvance,
1571 offset.y + height );
1573 splitEndGlyph = false;
1577 const float xPosition = position.x - glyph.xBearing + offset.x;
1578 mEventData->mDecorator->AddHighlight( xPosition,
1580 xPosition + glyph.advance,
1581 offset.y + height );
1584 CursorInfo primaryCursorInfo;
1585 GetCursorPosition( mEventData->mLeftSelectionPosition,
1586 primaryCursorInfo );
1588 CursorInfo secondaryCursorInfo;
1589 GetCursorPosition( mEventData->mRightSelectionPosition,
1590 secondaryCursorInfo );
1592 const Vector2 primaryPosition = primaryCursorInfo.primaryPosition + offset;
1593 const Vector2 secondaryPosition = secondaryCursorInfo.primaryPosition + offset;
1595 mEventData->mDecorator->SetPosition( LEFT_SELECTION_HANDLE,
1597 primaryCursorInfo.lineOffset + offset.y,
1598 primaryCursorInfo.lineHeight );
1600 mEventData->mDecorator->SetPosition( RIGHT_SELECTION_HANDLE,
1601 secondaryPosition.x,
1602 secondaryCursorInfo.lineOffset + offset.y,
1603 secondaryCursorInfo.lineHeight );
1605 // Cursor to be positioned at end of selection so if selection interrupted and edit mode restarted the cursor will be at end of selection
1606 mEventData->mPrimaryCursorPosition = ( indicesSwapped ) ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition;
1608 // Set the flag to update the decorator.
1609 mEventData->mDecoratorUpdated = true;
1612 void Controller::Impl::RepositionSelectionHandles( float visualX, float visualY )
1614 if( NULL == mEventData )
1616 // Nothing to do if there is no text input.
1620 if( IsShowingPlaceholderText() )
1622 // Nothing to do if there is the place-holder text.
1626 const Length numberOfGlyphs = mVisualModel->mGlyphs.Count();
1627 const Length numberOfLines = mVisualModel->mLines.Count();
1628 if( ( 0 == numberOfGlyphs ) ||
1629 ( 0 == numberOfLines ) )
1631 // Nothing to do if there is no text.
1635 // Find which word was selected
1636 CharacterIndex selectionStart( 0 );
1637 CharacterIndex selectionEnd( 0 );
1638 FindSelectionIndices( visualX, visualY, selectionStart, selectionEnd );
1639 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "%p selectionStart %d selectionEnd %d\n", this, selectionStart, selectionEnd );
1641 if( selectionStart == selectionEnd )
1643 ChangeState( EventData::EDITING );
1644 // Nothing to select. i.e. a white space, out of bounds
1648 mEventData->mLeftSelectionPosition = selectionStart;
1649 mEventData->mRightSelectionPosition = selectionEnd;
1652 void Controller::Impl::SetPopupButtons()
1655 * Sets the Popup buttons to be shown depending on State.
1657 * If SELECTING : CUT & COPY + ( PASTE & CLIPBOARD if content available to paste )
1659 * If EDITING_WITH_POPUP : SELECT & SELECT_ALL
1662 TextSelectionPopup::Buttons buttonsToShow = TextSelectionPopup::NONE;
1664 if( EventData::SELECTING == mEventData->mState )
1666 buttonsToShow = TextSelectionPopup::Buttons( TextSelectionPopup::CUT | TextSelectionPopup::COPY );
1668 if( !IsClipboardEmpty() )
1670 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
1671 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::CLIPBOARD ) );
1674 if( !mEventData->mAllTextSelected )
1676 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::SELECT_ALL ) );
1679 else if( EventData::EDITING_WITH_POPUP == mEventData->mState )
1681 if( mLogicalModel->mText.Count() && !IsShowingPlaceholderText() )
1683 buttonsToShow = TextSelectionPopup::Buttons( TextSelectionPopup::SELECT | TextSelectionPopup::SELECT_ALL );
1686 if( !IsClipboardEmpty() )
1688 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
1689 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::CLIPBOARD ) );
1692 else if( EventData::EDITING_WITH_PASTE_POPUP == mEventData->mState )
1694 if ( !IsClipboardEmpty() )
1696 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
1697 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::CLIPBOARD ) );
1701 mEventData->mDecorator->SetEnabledPopupButtons( buttonsToShow );
1704 void Controller::Impl::ChangeState( EventData::State newState )
1706 if( NULL == mEventData )
1708 // Nothing to do if there is no text input.
1712 DALI_LOG_INFO( gLogFilter, Debug::General, "ChangeState state:%d newstate:%d\n", mEventData->mState, newState );
1714 if( mEventData->mState != newState )
1716 mEventData->mState = newState;
1718 if( EventData::INACTIVE == mEventData->mState )
1720 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
1721 mEventData->mDecorator->StopCursorBlink();
1722 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1723 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1724 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1725 mEventData->mDecorator->SetPopupActive( false );
1726 mEventData->mDecoratorUpdated = true;
1729 else if( EventData::INTERRUPTED == mEventData->mState)
1731 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1732 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1733 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1734 mEventData->mDecorator->SetPopupActive( false );
1735 mEventData->mDecoratorUpdated = true;
1738 else if( EventData::SELECTING == mEventData->mState )
1740 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
1741 mEventData->mDecorator->StopCursorBlink();
1742 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1743 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true );
1744 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true );
1745 if( mEventData->mGrabHandlePopupEnabled )
1748 mEventData->mDecorator->SetPopupActive( true );
1750 mEventData->mDecoratorUpdated = true;
1752 else if( EventData::EDITING == mEventData->mState )
1754 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1755 if( mEventData->mCursorBlinkEnabled )
1757 mEventData->mDecorator->StartCursorBlink();
1759 // Grab handle is not shown until a tap is received whilst EDITING
1760 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1761 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1762 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1763 if( mEventData->mGrabHandlePopupEnabled )
1765 mEventData->mDecorator->SetPopupActive( false );
1767 mEventData->mDecoratorUpdated = true;
1770 else if( EventData::EDITING_WITH_POPUP == mEventData->mState )
1772 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "EDITING_WITH_POPUP \n", newState );
1774 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1775 if( mEventData->mCursorBlinkEnabled )
1777 mEventData->mDecorator->StartCursorBlink();
1779 if( mEventData->mSelectionEnabled )
1781 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1782 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1786 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
1788 if( mEventData->mGrabHandlePopupEnabled )
1791 mEventData->mDecorator->SetPopupActive( true );
1794 mEventData->mDecoratorUpdated = true;
1796 else if( EventData::EDITING_WITH_GRAB_HANDLE == mEventData->mState )
1798 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "EDITING_WITH_GRAB_HANDLE \n", newState );
1800 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1801 if( mEventData->mCursorBlinkEnabled )
1803 mEventData->mDecorator->StartCursorBlink();
1805 // Grab handle is not shown until a tap is received whilst EDITING
1806 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
1807 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1808 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1809 if( mEventData->mGrabHandlePopupEnabled )
1811 mEventData->mDecorator->SetPopupActive( false );
1813 mEventData->mDecoratorUpdated = true;
1816 else if( EventData::SELECTION_HANDLE_PANNING == mEventData->mState )
1818 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
1819 mEventData->mDecorator->StopCursorBlink();
1820 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1821 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true );
1822 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true );
1823 if( mEventData->mGrabHandlePopupEnabled )
1825 mEventData->mDecorator->SetPopupActive( false );
1827 mEventData->mDecoratorUpdated = true;
1829 else if( EventData::GRAB_HANDLE_PANNING == mEventData->mState )
1831 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "GRAB_HANDLE_PANNING \n", newState );
1833 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1834 if( mEventData->mCursorBlinkEnabled )
1836 mEventData->mDecorator->StartCursorBlink();
1838 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
1839 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1840 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1841 if( mEventData->mGrabHandlePopupEnabled )
1843 mEventData->mDecorator->SetPopupActive( false );
1845 mEventData->mDecoratorUpdated = true;
1847 else if( EventData::EDITING_WITH_PASTE_POPUP == mEventData->mState )
1849 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "EDITING_WITH_PASTE_POPUP \n", newState );
1851 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1852 if( mEventData->mCursorBlinkEnabled )
1854 mEventData->mDecorator->StartCursorBlink();
1857 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
1858 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1859 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1861 if( mEventData->mGrabHandlePopupEnabled )
1864 mEventData->mDecorator->SetPopupActive( true );
1867 mEventData->mDecoratorUpdated = true;
1872 LineIndex Controller::Impl::GetClosestLine( float y ) const
1874 float totalHeight = 0.f;
1875 LineIndex lineIndex = 0u;
1877 const Vector<LineRun>& lines = mVisualModel->mLines;
1878 for( LineIndex endLine = lines.Count();
1879 lineIndex < endLine;
1882 const LineRun& lineRun = lines[lineIndex];
1883 totalHeight += lineRun.ascender + -lineRun.descender;
1884 if( y < totalHeight )
1890 if( lineIndex == 0 )
1898 void Controller::Impl::FindSelectionIndices( float visualX, float visualY, CharacterIndex& startIndex, CharacterIndex& endIndex )
1900 CharacterIndex hitCharacter = GetClosestCursorIndex( visualX, visualY );
1901 DALI_ASSERT_DEBUG( hitCharacter <= mLogicalModel->mText.Count() && "GetClosestCursorIndex returned out of bounds index" );
1903 if( mLogicalModel->mText.Count() == 0 )
1905 return; // if model empty
1908 if( hitCharacter >= mLogicalModel->mText.Count() )
1910 // Closest hit character is the last character.
1911 if( hitCharacter == mLogicalModel->mText.Count() )
1913 hitCharacter--; //Hit character index set to last character in logical model
1917 // hitCharacter is out of bounds
1922 startIndex = hitCharacter;
1923 endIndex = hitCharacter;
1924 bool isHitCharacterWhitespace = TextAbstraction::IsWhiteSpace( mLogicalModel->mText[hitCharacter] );
1926 // Find the start and end of the text
1927 for( startIndex = hitCharacter; startIndex > 0; --startIndex )
1929 if( isHitCharacterWhitespace != TextAbstraction::IsWhiteSpace( mLogicalModel->mText[ startIndex-1 ] ) )
1934 const CharacterIndex pastTheEnd = mLogicalModel->mText.Count();
1935 for( endIndex = hitCharacter + 1u; endIndex < pastTheEnd; ++endIndex )
1937 if( isHitCharacterWhitespace != TextAbstraction::IsWhiteSpace( mLogicalModel->mText[ endIndex ] ) )
1944 CharacterIndex Controller::Impl::GetClosestCursorIndex( float visualX,
1947 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "GetClosestCursorIndex %p closest visualX %f visualY %f\n", this, visualX, visualY );
1949 if( NULL == mEventData )
1951 // Nothing to do if there is no text input.
1955 CharacterIndex logicalIndex = 0u;
1957 const Length numberOfGlyphs = mVisualModel->mGlyphs.Count();
1958 const Length numberOfLines = mVisualModel->mLines.Count();
1959 if( ( 0 == numberOfGlyphs ) ||
1960 ( 0 == numberOfLines ) )
1962 return logicalIndex;
1965 // Find which line is closest
1966 const LineIndex lineIndex = GetClosestLine( visualY );
1967 const LineRun& line = mVisualModel->mLines[lineIndex];
1969 // Get the positions of the glyphs.
1970 const Vector<Vector2>& positions = mVisualModel->mGlyphPositions;
1971 const Vector2* const positionsBuffer = positions.Begin();
1973 // Get the visual to logical conversion tables.
1974 const CharacterIndex* const visualToLogicalBuffer = ( 0u != mLogicalModel->mVisualToLogicalMap.Count() ) ? mLogicalModel->mVisualToLogicalMap.Begin() : NULL;
1975 const CharacterIndex* const visualToLogicalCursorBuffer = mLogicalModel->mVisualToLogicalCursorMap.Begin();
1977 // Get the character to glyph conversion table.
1978 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
1980 // Get the glyphs per character table.
1981 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
1983 // If the vector is void, there is no right to left characters.
1984 const bool hasRightToLeftCharacters = NULL != visualToLogicalBuffer;
1986 const CharacterIndex startCharacter = line.characterRun.characterIndex;
1987 const CharacterIndex endCharacter = line.characterRun.characterIndex + line.characterRun.numberOfCharacters;
1988 DALI_ASSERT_DEBUG( endCharacter <= mLogicalModel->mText.Count() && "Invalid line info" );
1990 // Whether there is a hit on a glyph.
1991 bool matched = false;
1993 // Traverses glyphs in visual order. To do that use the visual to logical conversion table.
1994 CharacterIndex visualIndex = startCharacter;
1995 Length numberOfCharacters = 0u;
1996 for( ; !matched && ( visualIndex < endCharacter ); ++visualIndex )
1998 // The character in logical order.
1999 const CharacterIndex characterLogicalOrderIndex = hasRightToLeftCharacters ? *( visualToLogicalBuffer + visualIndex ) : visualIndex;
2001 // Get the script of the character.
2002 const Script script = mLogicalModel->GetScript( characterLogicalOrderIndex );
2004 // The number of glyphs for that character
2005 const Length numberOfGlyphs = *( glyphsPerCharacterBuffer + characterLogicalOrderIndex );
2006 ++numberOfCharacters;
2009 if( 0u != numberOfGlyphs )
2011 // Get the first character/glyph of the group of glyphs.
2012 const CharacterIndex firstVisualCharacterIndex = 1u + visualIndex - numberOfCharacters;
2013 const CharacterIndex firstLogicalCharacterIndex = hasRightToLeftCharacters ? *( visualToLogicalBuffer + firstVisualCharacterIndex ) : firstVisualCharacterIndex;
2014 const GlyphIndex firstLogicalGlyphIndex = *( charactersToGlyphBuffer + firstLogicalCharacterIndex );
2016 // Get the metrics for the group of glyphs.
2017 GlyphMetrics glyphMetrics;
2018 GetGlyphsMetrics( firstLogicalGlyphIndex,
2024 // Get the position of the first glyph.
2025 const Vector2& position = *( positionsBuffer + firstLogicalGlyphIndex );
2027 // Whether the glyph can be split, like Latin ligatures fi, ff or Arabic ﻻ.
2028 const bool isInterglyphIndex = ( numberOfCharacters > numberOfGlyphs ) && HasLigatureMustBreak( script );
2029 const Length numberOfBlocks = isInterglyphIndex ? numberOfCharacters : 1u;
2030 const float glyphAdvance = glyphMetrics.advance / static_cast<float>( numberOfBlocks );
2032 GlyphIndex index = 0u;
2033 for( ; !matched && ( index < numberOfBlocks ); ++index )
2035 // Find the mid-point of the area containing the glyph
2036 const float glyphCenter = -glyphMetrics.xBearing + position.x + ( static_cast<float>( index ) + 0.5f ) * glyphAdvance;
2038 if( visualX < glyphCenter )
2047 visualIndex = firstVisualCharacterIndex + index;
2051 numberOfCharacters = 0u;
2057 // Return the logical position of the cursor in characters.
2061 visualIndex = endCharacter;
2064 logicalIndex = hasRightToLeftCharacters ? *( visualToLogicalCursorBuffer + visualIndex ) : visualIndex;
2065 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "%p closest visualIndex %d logicalIndex %d\n", this, visualIndex, logicalIndex );
2067 DALI_ASSERT_DEBUG( ( logicalIndex <= mLogicalModel->mText.Count() && logicalIndex >= 0 ) && "GetClosestCursorIndex - Out of bounds index" );
2069 return logicalIndex;
2072 void Controller::Impl::GetCursorPosition( CharacterIndex logical,
2073 CursorInfo& cursorInfo )
2075 // TODO: Check for multiline with \n, etc...
2077 const Length numberOfCharacters = mLogicalModel->mText.Count();
2078 if( !IsShowingRealText() )
2080 // Do not want to use the place-holder text to set the cursor position.
2082 // Use the line's height of the font's family set to set the cursor's size.
2083 // If there is no font's family set, use the default font.
2084 // Use the current alignment to place the cursor at the beginning, center or end of the box.
2086 cursorInfo.lineOffset = 0.f;
2087 cursorInfo.lineHeight = GetDefaultFontLineHeight();
2088 cursorInfo.primaryCursorHeight = cursorInfo.lineHeight;
2090 switch( mLayoutEngine.GetHorizontalAlignment() )
2092 case LayoutEngine::HORIZONTAL_ALIGN_BEGIN:
2094 cursorInfo.primaryPosition.x = 0.f;
2097 case LayoutEngine::HORIZONTAL_ALIGN_CENTER:
2099 cursorInfo.primaryPosition.x = floorf( 0.5f * mVisualModel->mControlSize.width );
2102 case LayoutEngine::HORIZONTAL_ALIGN_END:
2104 cursorInfo.primaryPosition.x = mVisualModel->mControlSize.width - mEventData->mDecorator->GetCursorWidth();
2109 switch( mLayoutEngine.GetVerticalAlignment() )
2111 case LayoutEngine::VERTICAL_ALIGN_TOP:
2113 cursorInfo.primaryPosition.y = 0.f;
2116 case LayoutEngine::VERTICAL_ALIGN_CENTER:
2118 cursorInfo.primaryPosition.y = floorf( 0.5f * ( mVisualModel->mControlSize.height - cursorInfo.lineHeight ) );
2121 case LayoutEngine::VERTICAL_ALIGN_BOTTOM:
2123 cursorInfo.primaryPosition.y = mVisualModel->mControlSize.height - cursorInfo.lineHeight;
2128 // Nothing else to do.
2132 // Check if the logical position is the first or the last one of the text.
2133 const bool isFirstPosition = 0u == logical;
2134 const bool isLastPosition = numberOfCharacters == logical;
2136 // 'logical' is the logical 'cursor' index.
2137 // Get the next and current logical 'character' index.
2138 const CharacterIndex nextCharacterIndex = logical;
2139 const CharacterIndex characterIndex = isFirstPosition ? logical : logical - 1u;
2141 // Get the direction of the character and the next one.
2142 const CharacterDirection* const modelCharacterDirectionsBuffer = ( 0u != mLogicalModel->mCharacterDirections.Count() ) ? mLogicalModel->mCharacterDirections.Begin() : NULL;
2144 CharacterDirection isCurrentRightToLeft = false;
2145 CharacterDirection isNextRightToLeft = false;
2146 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
2148 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + characterIndex );
2149 isNextRightToLeft = *( modelCharacterDirectionsBuffer + nextCharacterIndex );
2152 // Get the line where the character is laid-out.
2153 const LineRun* const modelLines = mVisualModel->mLines.Begin();
2155 const LineIndex lineIndex = mVisualModel->GetLineOfCharacter( characterIndex );
2156 const LineRun& line = *( modelLines + lineIndex );
2158 // Get the paragraph's direction.
2159 const CharacterDirection isRightToLeftParagraph = line.direction;
2161 // Check whether there is an alternative position:
2163 cursorInfo.isSecondaryCursor = ( !isLastPosition && ( isCurrentRightToLeft != isNextRightToLeft ) ) ||
2164 ( isLastPosition && ( isRightToLeftParagraph != isCurrentRightToLeft ) );
2166 // Set the line offset and height.
2167 cursorInfo.lineOffset = 0.f;
2168 cursorInfo.lineHeight = line.ascender + -line.descender;
2170 // Calculate the primary cursor.
2172 CharacterIndex index = characterIndex;
2173 if( cursorInfo.isSecondaryCursor )
2175 // If there is a secondary position, the primary cursor may be in a different place than the logical index.
2177 if( isLastPosition )
2179 // The position of the cursor after the last character needs special
2180 // care depending on its direction and the direction of the paragraph.
2182 // Need to find the first character after the last character with the paragraph's direction.
2183 // i.e l0 l1 l2 r0 r1 should find r0.
2185 // TODO: check for more than one line!
2186 index = isRightToLeftParagraph ? line.characterRun.characterIndex : line.characterRun.characterIndex + line.characterRun.numberOfCharacters - 1u;
2187 index = mLogicalModel->GetLogicalCharacterIndex( index );
2191 index = ( isRightToLeftParagraph == isCurrentRightToLeft ) ? characterIndex : nextCharacterIndex;
2195 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
2196 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
2197 const Length* const charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
2198 const CharacterIndex* const glyphsToCharactersBuffer = mVisualModel->mGlyphsToCharacters.Begin();
2199 const Vector2* const glyphPositionsBuffer = mVisualModel->mGlyphPositions.Begin();
2201 // Convert the cursor position into the glyph position.
2202 const GlyphIndex primaryGlyphIndex = *( charactersToGlyphBuffer + index );
2203 const Length primaryNumberOfGlyphs = *( glyphsPerCharacterBuffer + index );
2204 const Length primaryNumberOfCharacters = *( charactersPerGlyphBuffer + primaryGlyphIndex );
2206 // Get the metrics for the group of glyphs.
2207 GlyphMetrics glyphMetrics;
2208 GetGlyphsMetrics( primaryGlyphIndex,
2209 primaryNumberOfGlyphs,
2214 // Whether to add the glyph's advance to the cursor position.
2215 // 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,
2216 // if the logical cursor is one, the position is the position of the first glyph and the advance is added.
2217 // A 'truth table' was build and an online Karnaugh map tool was used to simplify the logic.
2238 // Where F -> isFirstPosition
2239 // L -> isLastPosition
2240 // C -> isCurrentRightToLeft
2241 // P -> isRightToLeftParagraph
2242 // A -> Whether to add the glyph's advance.
2244 const bool addGlyphAdvance = ( ( isLastPosition && !isRightToLeftParagraph ) ||
2245 ( isFirstPosition && isRightToLeftParagraph ) ||
2246 ( !isFirstPosition && !isLastPosition && !isCurrentRightToLeft ) );
2248 float glyphAdvance = addGlyphAdvance ? glyphMetrics.advance : 0.f;
2250 if( !isLastPosition &&
2251 ( primaryNumberOfCharacters > 1u ) )
2253 const CharacterIndex firstIndex = *( glyphsToCharactersBuffer + primaryGlyphIndex );
2255 bool isCurrentRightToLeft = false;
2256 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
2258 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + index );
2261 Length numberOfGlyphAdvance = ( isFirstPosition ? 0u : 1u ) + characterIndex - firstIndex;
2262 if( isCurrentRightToLeft )
2264 numberOfGlyphAdvance = primaryNumberOfCharacters - numberOfGlyphAdvance;
2267 glyphAdvance = static_cast<float>( numberOfGlyphAdvance ) * glyphMetrics.advance / static_cast<float>( primaryNumberOfCharacters );
2270 // Get the glyph position and x bearing.
2271 const Vector2& primaryPosition = *( glyphPositionsBuffer + primaryGlyphIndex );
2273 // Set the primary cursor's height.
2274 cursorInfo.primaryCursorHeight = cursorInfo.isSecondaryCursor ? 0.5f * glyphMetrics.fontHeight : glyphMetrics.fontHeight;
2276 // Set the primary cursor's position.
2277 cursorInfo.primaryPosition.x = -glyphMetrics.xBearing + primaryPosition.x + glyphAdvance;
2278 cursorInfo.primaryPosition.y = line.ascender - glyphMetrics.ascender;
2280 // Calculate the secondary cursor.
2282 if( cursorInfo.isSecondaryCursor )
2284 // Set the secondary cursor's height.
2285 cursorInfo.secondaryCursorHeight = 0.5f * glyphMetrics.fontHeight;
2287 CharacterIndex index = characterIndex;
2288 if( !isLastPosition )
2290 index = ( isRightToLeftParagraph == isCurrentRightToLeft ) ? nextCharacterIndex : characterIndex;
2293 const GlyphIndex secondaryGlyphIndex = *( charactersToGlyphBuffer + index );
2294 const Length secondaryNumberOfGlyphs = *( glyphsPerCharacterBuffer + index );
2296 const Vector2& secondaryPosition = *( glyphPositionsBuffer + secondaryGlyphIndex );
2298 GetGlyphsMetrics( secondaryGlyphIndex,
2299 secondaryNumberOfGlyphs,
2304 // Set the secondary cursor's position.
2305 cursorInfo.secondaryPosition.x = -glyphMetrics.xBearing + secondaryPosition.x + ( isCurrentRightToLeft ? 0.f : glyphMetrics.advance );
2306 cursorInfo.secondaryPosition.y = cursorInfo.lineHeight - cursorInfo.secondaryCursorHeight - line.descender - ( glyphMetrics.fontHeight - glyphMetrics.ascender );
2309 if( LayoutEngine::MULTI_LINE_BOX == mLayoutEngine.GetLayout() )
2311 // If the text is editable and multi-line, the cursor position after a white space shouldn't exceed the boundaries of the text control.
2313 // Note the white spaces laid-out at the end of the line might exceed the boundaries of the control.
2314 // The reason is a wrapped line must not start with a white space so they are laid-out at the end of the line.
2316 if( 0.f > cursorInfo.primaryPosition.x )
2318 cursorInfo.primaryPosition.x = 0.f;
2321 const float edgeWidth = mVisualModel->mControlSize.width - static_cast<float>( mEventData->mDecorator->GetCursorWidth() );
2322 if( cursorInfo.primaryPosition.x > edgeWidth )
2324 cursorInfo.primaryPosition.x = edgeWidth;
2329 CharacterIndex Controller::Impl::CalculateNewCursorIndex( CharacterIndex index ) const
2331 if( NULL == mEventData )
2333 // Nothing to do if there is no text input.
2337 CharacterIndex cursorIndex = mEventData->mPrimaryCursorPosition;
2339 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
2340 const Length* const charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
2342 GlyphIndex glyphIndex = *( charactersToGlyphBuffer + index );
2343 Length numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
2345 if( numberOfCharacters > 1u )
2347 const Script script = mLogicalModel->GetScript( index );
2348 if( HasLigatureMustBreak( script ) )
2350 // Prevents to jump the whole Latin ligatures like fi, ff, or Arabic ﻻ, ...
2351 numberOfCharacters = 1u;
2356 while( 0u == numberOfCharacters )
2359 numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
2363 if( index < mEventData->mPrimaryCursorPosition )
2365 cursorIndex -= numberOfCharacters;
2369 cursorIndex += numberOfCharacters;
2375 void Controller::Impl::UpdateCursorPosition( const CursorInfo& cursorInfo )
2377 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "-->Controller::UpdateCursorPosition %p\n", this );
2378 if( NULL == mEventData )
2380 // Nothing to do if there is no text input.
2381 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition no event data\n" );
2385 const Vector2 offset = mEventData->mScrollPosition + ( IsShowingRealText() ? mAlignmentOffset : Vector2::ZERO );
2386 const Vector2 cursorPosition = cursorInfo.primaryPosition + offset;
2388 // Sets the cursor position.
2389 mEventData->mDecorator->SetPosition( PRIMARY_CURSOR,
2392 cursorInfo.primaryCursorHeight,
2393 cursorInfo.lineHeight );
2394 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Primary cursor position: %f,%f\n", cursorPosition.x, cursorPosition.y );
2396 // Sets the grab handle position.
2397 mEventData->mDecorator->SetPosition( GRAB_HANDLE,
2399 cursorInfo.lineOffset + offset.y,
2400 cursorInfo.lineHeight );
2402 if( cursorInfo.isSecondaryCursor )
2404 mEventData->mDecorator->SetPosition( SECONDARY_CURSOR,
2405 cursorInfo.secondaryPosition.x + offset.x,
2406 cursorInfo.secondaryPosition.y + offset.y,
2407 cursorInfo.secondaryCursorHeight,
2408 cursorInfo.lineHeight );
2409 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Secondary cursor position: %f,%f\n", cursorInfo.secondaryPosition.x + offset.x, cursorInfo.secondaryPosition.y + offset.y );
2412 // Set which cursors are active according the state.
2413 if( EventData::IsEditingState( mEventData->mState ) || ( EventData::GRAB_HANDLE_PANNING == mEventData->mState ) )
2415 if( cursorInfo.isSecondaryCursor )
2417 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_BOTH );
2421 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
2426 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
2429 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition\n" );
2432 void Controller::Impl::UpdateSelectionHandle( HandleType handleType,
2433 const CursorInfo& cursorInfo )
2435 if( ( LEFT_SELECTION_HANDLE != handleType ) &&
2436 ( RIGHT_SELECTION_HANDLE != handleType ) )
2441 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
2442 const Vector2 cursorPosition = cursorInfo.primaryPosition + offset;
2444 // Sets the handle's position.
2445 mEventData->mDecorator->SetPosition( handleType,
2447 cursorInfo.lineOffset + offset.y,
2448 cursorInfo.lineHeight );
2450 // If selection handle at start of the text and other at end of the text then all text is selected.
2451 const CharacterIndex startOfSelection = std::min( mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition );
2452 const CharacterIndex endOfSelection = std::max ( mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition );
2453 mEventData->mAllTextSelected = ( startOfSelection == 0 ) && ( endOfSelection == mLogicalModel->mText.Count() );
2456 void Controller::Impl::ClampHorizontalScroll( const Vector2& actualSize )
2458 // Clamp between -space & 0 (and the text alignment).
2460 if( actualSize.width > mVisualModel->mControlSize.width )
2462 const float space = ( actualSize.width - mVisualModel->mControlSize.width ) + mAlignmentOffset.x;
2463 mEventData->mScrollPosition.x = ( mEventData->mScrollPosition.x < -space ) ? -space : mEventData->mScrollPosition.x;
2464 mEventData->mScrollPosition.x = ( mEventData->mScrollPosition.x > -mAlignmentOffset.x ) ? -mAlignmentOffset.x : mEventData->mScrollPosition.x;
2466 mEventData->mDecoratorUpdated = true;
2470 mEventData->mScrollPosition.x = 0.f;
2474 void Controller::Impl::ClampVerticalScroll( const Vector2& actualSize )
2476 // Clamp between -space & 0 (and the text alignment).
2477 if( actualSize.height > mVisualModel->mControlSize.height )
2479 const float space = ( actualSize.height - mVisualModel->mControlSize.height ) + mAlignmentOffset.y;
2480 mEventData->mScrollPosition.y = ( mEventData->mScrollPosition.y < -space ) ? -space : mEventData->mScrollPosition.y;
2481 mEventData->mScrollPosition.y = ( mEventData->mScrollPosition.y > -mAlignmentOffset.y ) ? -mAlignmentOffset.y : mEventData->mScrollPosition.y;
2483 mEventData->mDecoratorUpdated = true;
2487 mEventData->mScrollPosition.y = 0.f;
2491 void Controller::Impl::ScrollToMakePositionVisible( const Vector2& position )
2493 // position is in actor's coords.
2494 const float positionEnd = position.x + ( mEventData->mDecorator ? mEventData->mDecorator->GetCursorWidth() : 0.f );
2496 // Transform the position to decorator coords.
2497 const float alignment = IsShowingRealText() ? mAlignmentOffset.x : 0.f;
2498 const float offset = mEventData->mScrollPosition.x + alignment;
2499 const float decoratorPositionBegin = position.x + offset;
2500 const float decoratorPositionEnd = positionEnd + offset;
2502 if( decoratorPositionBegin < 0.f )
2504 mEventData->mScrollPosition.x = -position.x - alignment;
2506 else if( decoratorPositionEnd > mVisualModel->mControlSize.width )
2508 mEventData->mScrollPosition.x = mVisualModel->mControlSize.width - positionEnd - alignment;
2512 void Controller::Impl::ScrollTextToMatchCursor( const CursorInfo& cursorInfo )
2514 // Get the current cursor position in decorator coords.
2515 const Vector2& currentCursorPosition = mEventData->mDecorator->GetPosition( PRIMARY_CURSOR );
2517 // Calculate the offset to match the cursor position before the character was deleted.
2518 mEventData->mScrollPosition.x = currentCursorPosition.x - cursorInfo.primaryPosition.x - mAlignmentOffset.x;
2520 ClampHorizontalScroll( mVisualModel->GetLayoutSize() );
2523 void Controller::Impl::RequestRelayout()
2525 mControlInterface.RequestTextRelayout();
2530 } // namespace Toolkit