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 // Scroll first the text after delete ...
158 if( mEventData->mScrollAfterDelete )
160 ScrollTextToMatchCursor( cursorInfo );
163 // ... then, text can be scrolled to make the cursor visible.
164 if( mEventData->mScrollAfterUpdatePosition )
166 ScrollToMakePositionVisible( cursorInfo.primaryPosition );
168 mEventData->mScrollAfterUpdatePosition = false;
169 mEventData->mScrollAfterDelete = false;
171 UpdateCursorPosition( cursorInfo );
173 mEventData->mDecoratorUpdated = true;
174 mEventData->mUpdateCursorPosition = false;
178 bool leftScroll = false;
179 bool rightScroll = false;
181 CursorInfo leftHandleInfo;
182 CursorInfo rightHandleInfo;
184 if( mEventData->mUpdateLeftSelectionPosition )
186 GetCursorPosition( mEventData->mLeftSelectionPosition,
189 if( mEventData->mScrollAfterUpdatePosition )
191 ScrollToMakePositionVisible( leftHandleInfo.primaryPosition );
196 if( mEventData->mUpdateRightSelectionPosition )
198 GetCursorPosition( mEventData->mRightSelectionPosition,
201 if( mEventData->mScrollAfterUpdatePosition )
203 ScrollToMakePositionVisible( rightHandleInfo.primaryPosition );
208 if( mEventData->mUpdateLeftSelectionPosition )
210 UpdateSelectionHandle( LEFT_SELECTION_HANDLE,
214 mEventData->mDecoratorUpdated = true;
217 if( mEventData->mUpdateRightSelectionPosition )
219 UpdateSelectionHandle( RIGHT_SELECTION_HANDLE,
223 mEventData->mDecoratorUpdated = true;
226 if( mEventData->mUpdateLeftSelectionPosition || mEventData->mUpdateRightSelectionPosition )
228 RepositionSelectionHandles();
230 mEventData->mUpdateLeftSelectionPosition = false;
231 mEventData->mUpdateRightSelectionPosition = false;
234 if( leftScroll || rightScroll )
236 mEventData->mScrollAfterUpdatePosition = false;
240 if( mEventData->mUpdateInputStyle )
242 // Set the default style first.
243 RetrieveDefaultInputStyle( mEventData->mInputStyle );
245 // Get the character index from the cursor index.
246 const CharacterIndex styleIndex = ( mEventData->mPrimaryCursorPosition > 0u ) ? mEventData->mPrimaryCursorPosition - 1u : 0u;
248 // Retrieve the style from the style runs stored in the logical model.
249 mLogicalModel->RetrieveStyle( styleIndex, mEventData->mInputStyle );
251 mEventData->mUpdateInputStyle = false;
254 mEventData->mEventQueue.clear();
256 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::ProcessInputEvents\n" );
258 const bool decoratorUpdated = mEventData->mDecoratorUpdated;
259 mEventData->mDecoratorUpdated = false;
261 return decoratorUpdated;
264 void Controller::Impl::CalculateTextUpdateIndices( Length& numberOfCharacters )
266 mTextUpdateInfo.mParagraphCharacterIndex = 0u;
267 mTextUpdateInfo.mStartGlyphIndex = 0u;
268 mTextUpdateInfo.mStartLineIndex = 0u;
269 numberOfCharacters = 0u;
271 const Length numberOfParagraphs = mLogicalModel->mParagraphInfo.Count();
272 if( 0u == numberOfParagraphs )
274 mTextUpdateInfo.mParagraphCharacterIndex = 0u;
275 numberOfCharacters = 0u;
277 mTextUpdateInfo.mRequestedNumberOfCharacters = mTextUpdateInfo.mNumberOfCharactersToAdd - mTextUpdateInfo.mNumberOfCharactersToRemove;
279 // Nothing else to do if there are no paragraphs.
283 // Find the paragraphs to be updated.
284 Vector<ParagraphRunIndex> paragraphsToBeUpdated;
285 if( mTextUpdateInfo.mCharacterIndex >= mTextUpdateInfo.mPreviousNumberOfCharacters )
287 // Text is being added at the end of the current text.
288 if( mTextUpdateInfo.mIsLastCharacterNewParagraph )
290 // Text is being added in a new paragraph after the last character of the text.
291 mTextUpdateInfo.mParagraphCharacterIndex = mTextUpdateInfo.mPreviousNumberOfCharacters;
292 numberOfCharacters = 0u;
293 mTextUpdateInfo.mRequestedNumberOfCharacters = mTextUpdateInfo.mNumberOfCharactersToAdd - mTextUpdateInfo.mNumberOfCharactersToRemove;
295 mTextUpdateInfo.mStartGlyphIndex = mVisualModel->mGlyphs.Count();
296 mTextUpdateInfo.mStartLineIndex = mVisualModel->mLines.Count() - 1u;
298 // Nothing else to do;
302 paragraphsToBeUpdated.PushBack( numberOfParagraphs - 1u );
306 Length numberOfCharactersToUpdate = 0u;
307 if( mTextUpdateInfo.mFullRelayoutNeeded )
309 numberOfCharactersToUpdate = mTextUpdateInfo.mPreviousNumberOfCharacters;
313 numberOfCharactersToUpdate = ( mTextUpdateInfo.mNumberOfCharactersToRemove > 0u ) ? mTextUpdateInfo.mNumberOfCharactersToRemove : 1u;
315 mLogicalModel->FindParagraphs( mTextUpdateInfo.mCharacterIndex,
316 numberOfCharactersToUpdate,
317 paragraphsToBeUpdated );
320 if( 0u != paragraphsToBeUpdated.Count() )
322 const ParagraphRunIndex firstParagraphIndex = *( paragraphsToBeUpdated.Begin() );
323 const ParagraphRun& firstParagraph = *( mLogicalModel->mParagraphInfo.Begin() + firstParagraphIndex );
324 mTextUpdateInfo.mParagraphCharacterIndex = firstParagraph.characterRun.characterIndex;
326 ParagraphRunIndex lastParagraphIndex = *( paragraphsToBeUpdated.End() - 1u );
327 const ParagraphRun& lastParagraph = *( mLogicalModel->mParagraphInfo.Begin() + lastParagraphIndex );
329 if( ( mTextUpdateInfo.mNumberOfCharactersToRemove > 0u ) && // Some character are removed.
330 ( lastParagraphIndex < numberOfParagraphs - 1u ) && // There is a next paragraph.
331 ( ( lastParagraph.characterRun.characterIndex + lastParagraph.characterRun.numberOfCharacters ) == // The last removed character is the new paragraph character.
332 ( mTextUpdateInfo.mCharacterIndex + mTextUpdateInfo.mNumberOfCharactersToRemove ) ) )
334 // The new paragraph character of the last updated paragraph has been removed so is going to be merged with the next one.
335 const ParagraphRun& lastParagraph = *( mLogicalModel->mParagraphInfo.Begin() + lastParagraphIndex + 1u );
337 numberOfCharacters = lastParagraph.characterRun.characterIndex + lastParagraph.characterRun.numberOfCharacters - mTextUpdateInfo.mParagraphCharacterIndex;
341 numberOfCharacters = lastParagraph.characterRun.characterIndex + lastParagraph.characterRun.numberOfCharacters - mTextUpdateInfo.mParagraphCharacterIndex;
345 mTextUpdateInfo.mRequestedNumberOfCharacters = numberOfCharacters + mTextUpdateInfo.mNumberOfCharactersToAdd - mTextUpdateInfo.mNumberOfCharactersToRemove;
346 mTextUpdateInfo.mStartGlyphIndex = *( mVisualModel->mCharactersToGlyph.Begin() + mTextUpdateInfo.mParagraphCharacterIndex );
349 void Controller::Impl::ClearFullModelData( OperationsMask operations )
351 if( NO_OPERATION != ( GET_LINE_BREAKS & operations ) )
353 mLogicalModel->mLineBreakInfo.Clear();
354 mLogicalModel->mParagraphInfo.Clear();
357 if( NO_OPERATION != ( GET_WORD_BREAKS & operations ) )
359 mLogicalModel->mLineBreakInfo.Clear();
362 if( NO_OPERATION != ( GET_SCRIPTS & operations ) )
364 mLogicalModel->mScriptRuns.Clear();
367 if( NO_OPERATION != ( VALIDATE_FONTS & operations ) )
369 mLogicalModel->mFontRuns.Clear();
372 if( 0u != mLogicalModel->mBidirectionalParagraphInfo.Count() )
374 if( NO_OPERATION != ( BIDI_INFO & operations ) )
376 mLogicalModel->mBidirectionalParagraphInfo.Clear();
377 mLogicalModel->mCharacterDirections.Clear();
380 if( NO_OPERATION != ( REORDER & operations ) )
382 // Free the allocated memory used to store the conversion table in the bidirectional line info run.
383 for( Vector<BidirectionalLineInfoRun>::Iterator it = mLogicalModel->mBidirectionalLineInfo.Begin(),
384 endIt = mLogicalModel->mBidirectionalLineInfo.End();
388 BidirectionalLineInfoRun& bidiLineInfo = *it;
390 free( bidiLineInfo.visualToLogicalMap );
391 bidiLineInfo.visualToLogicalMap = NULL;
393 mLogicalModel->mBidirectionalLineInfo.Clear();
397 if( NO_OPERATION != ( SHAPE_TEXT & operations ) )
399 mVisualModel->mGlyphs.Clear();
400 mVisualModel->mGlyphsToCharacters.Clear();
401 mVisualModel->mCharactersToGlyph.Clear();
402 mVisualModel->mCharactersPerGlyph.Clear();
403 mVisualModel->mGlyphsPerCharacter.Clear();
404 mVisualModel->mGlyphPositions.Clear();
407 if( NO_OPERATION != ( LAYOUT & operations ) )
409 mVisualModel->mLines.Clear();
412 if( NO_OPERATION != ( COLOR & operations ) )
414 mVisualModel->mColorIndices.Clear();
418 void Controller::Impl::ClearCharacterModelData( CharacterIndex startIndex, CharacterIndex endIndex, OperationsMask operations )
420 const CharacterIndex endIndexPlusOne = endIndex + 1u;
422 if( NO_OPERATION != ( GET_LINE_BREAKS & operations ) )
424 // Clear the line break info.
425 LineBreakInfo* lineBreakInfoBuffer = mLogicalModel->mLineBreakInfo.Begin();
427 mLogicalModel->mLineBreakInfo.Erase( lineBreakInfoBuffer + startIndex,
428 lineBreakInfoBuffer + endIndexPlusOne );
430 // Clear the paragraphs.
431 ClearCharacterRuns( startIndex,
433 mLogicalModel->mParagraphInfo );
436 if( NO_OPERATION != ( GET_WORD_BREAKS & operations ) )
438 // Clear the word break info.
439 WordBreakInfo* wordBreakInfoBuffer = mLogicalModel->mWordBreakInfo.Begin();
441 mLogicalModel->mWordBreakInfo.Erase( wordBreakInfoBuffer + startIndex,
442 wordBreakInfoBuffer + endIndexPlusOne );
445 if( NO_OPERATION != ( GET_SCRIPTS & operations ) )
447 // Clear the scripts.
448 ClearCharacterRuns( startIndex,
450 mLogicalModel->mScriptRuns );
453 if( NO_OPERATION != ( VALIDATE_FONTS & operations ) )
456 ClearCharacterRuns( startIndex,
458 mLogicalModel->mFontRuns );
461 if( 0u != mLogicalModel->mBidirectionalParagraphInfo.Count() )
463 if( NO_OPERATION != ( BIDI_INFO & operations ) )
465 // Clear the bidirectional paragraph info.
466 ClearCharacterRuns( startIndex,
468 mLogicalModel->mBidirectionalParagraphInfo );
470 // Clear the character's directions.
471 CharacterDirection* characterDirectionsBuffer = mLogicalModel->mCharacterDirections.Begin();
473 mLogicalModel->mCharacterDirections.Erase( characterDirectionsBuffer + startIndex,
474 characterDirectionsBuffer + endIndexPlusOne );
477 if( NO_OPERATION != ( REORDER & operations ) )
479 uint32_t startRemoveIndex = mLogicalModel->mBidirectionalLineInfo.Count();
480 uint32_t endRemoveIndex = startRemoveIndex;
481 ClearCharacterRuns( startIndex,
483 mLogicalModel->mBidirectionalLineInfo,
487 BidirectionalLineInfoRun* bidirectionalLineInfoBuffer = mLogicalModel->mBidirectionalLineInfo.Begin();
489 // Free the allocated memory used to store the conversion table in the bidirectional line info run.
490 for( Vector<BidirectionalLineInfoRun>::Iterator it = bidirectionalLineInfoBuffer + startRemoveIndex,
491 endIt = bidirectionalLineInfoBuffer + endRemoveIndex;
495 BidirectionalLineInfoRun& bidiLineInfo = *it;
497 free( bidiLineInfo.visualToLogicalMap );
498 bidiLineInfo.visualToLogicalMap = NULL;
501 mLogicalModel->mBidirectionalLineInfo.Erase( bidirectionalLineInfoBuffer + startRemoveIndex,
502 bidirectionalLineInfoBuffer + endRemoveIndex );
507 void Controller::Impl::ClearGlyphModelData( CharacterIndex startIndex, CharacterIndex endIndex, OperationsMask operations )
509 const CharacterIndex endIndexPlusOne = endIndex + 1u;
510 const Length numberOfCharactersRemoved = endIndexPlusOne - startIndex;
512 // Convert the character index to glyph index before deleting the character to glyph and the glyphs per character buffers.
513 GlyphIndex* charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
514 Length* glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
516 const GlyphIndex endGlyphIndexPlusOne = *( charactersToGlyphBuffer + endIndex ) + *( glyphsPerCharacterBuffer + endIndex );
517 const Length numberOfGlyphsRemoved = endGlyphIndexPlusOne - mTextUpdateInfo.mStartGlyphIndex;
519 if( NO_OPERATION != ( SHAPE_TEXT & operations ) )
521 // Update the character to glyph indices.
522 for( Vector<GlyphIndex>::Iterator it = charactersToGlyphBuffer + endIndexPlusOne,
523 endIt = charactersToGlyphBuffer + mVisualModel->mCharactersToGlyph.Count();
527 CharacterIndex& index = *it;
528 index -= numberOfGlyphsRemoved;
531 // Clear the character to glyph conversion table.
532 mVisualModel->mCharactersToGlyph.Erase( charactersToGlyphBuffer + startIndex,
533 charactersToGlyphBuffer + endIndexPlusOne );
535 // Clear the glyphs per character table.
536 mVisualModel->mGlyphsPerCharacter.Erase( glyphsPerCharacterBuffer + startIndex,
537 glyphsPerCharacterBuffer + endIndexPlusOne );
539 // Clear the glyphs buffer.
540 GlyphInfo* glyphsBuffer = mVisualModel->mGlyphs.Begin();
541 mVisualModel->mGlyphs.Erase( glyphsBuffer + mTextUpdateInfo.mStartGlyphIndex,
542 glyphsBuffer + endGlyphIndexPlusOne );
544 CharacterIndex* glyphsToCharactersBuffer = mVisualModel->mGlyphsToCharacters.Begin();
546 // Update the glyph to character indices.
547 for( Vector<CharacterIndex>::Iterator it = glyphsToCharactersBuffer + endGlyphIndexPlusOne,
548 endIt = glyphsToCharactersBuffer + mVisualModel->mGlyphsToCharacters.Count();
552 CharacterIndex& index = *it;
553 index -= numberOfCharactersRemoved;
556 // Clear the glyphs to characters buffer.
557 mVisualModel->mGlyphsToCharacters.Erase( glyphsToCharactersBuffer + mTextUpdateInfo.mStartGlyphIndex,
558 glyphsToCharactersBuffer + endGlyphIndexPlusOne );
560 // Clear the characters per glyph buffer.
561 Length* charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
562 mVisualModel->mCharactersPerGlyph.Erase( charactersPerGlyphBuffer + mTextUpdateInfo.mStartGlyphIndex,
563 charactersPerGlyphBuffer + endGlyphIndexPlusOne );
565 // Clear the positions buffer.
566 Vector2* positionsBuffer = mVisualModel->mGlyphPositions.Begin();
567 mVisualModel->mGlyphPositions.Erase( positionsBuffer + mTextUpdateInfo.mStartGlyphIndex,
568 positionsBuffer + endGlyphIndexPlusOne );
571 if( NO_OPERATION != ( LAYOUT & operations ) )
574 uint32_t startRemoveIndex = mVisualModel->mLines.Count();
575 uint32_t endRemoveIndex = startRemoveIndex;
576 ClearCharacterRuns( startIndex,
578 mVisualModel->mLines,
582 // Will update the glyph runs.
583 startRemoveIndex = mVisualModel->mLines.Count();
584 endRemoveIndex = startRemoveIndex;
585 ClearGlyphRuns( mTextUpdateInfo.mStartGlyphIndex,
586 endGlyphIndexPlusOne - 1u,
587 mVisualModel->mLines,
591 // Set the line index from where to insert the new laid-out lines.
592 mTextUpdateInfo.mStartLineIndex = startRemoveIndex;
594 LineRun* linesBuffer = mVisualModel->mLines.Begin();
595 mVisualModel->mLines.Erase( linesBuffer + startRemoveIndex,
596 linesBuffer + endRemoveIndex );
599 if( NO_OPERATION != ( COLOR & operations ) )
601 if( 0u != mVisualModel->mColorIndices.Count() )
603 ColorIndex* colorIndexBuffer = mVisualModel->mColorIndices.Begin();
604 mVisualModel->mColorIndices.Erase( colorIndexBuffer + mTextUpdateInfo.mStartGlyphIndex,
605 colorIndexBuffer + endGlyphIndexPlusOne );
610 void Controller::Impl::ClearModelData( CharacterIndex startIndex, CharacterIndex endIndex, OperationsMask operations )
612 if( mTextUpdateInfo.mClearAll ||
613 ( ( 0u == startIndex ) &&
614 ( mTextUpdateInfo.mPreviousNumberOfCharacters == endIndex + 1u ) ) )
616 ClearFullModelData( operations );
620 // Clear the model data related with characters.
621 ClearCharacterModelData( startIndex, endIndex, operations );
623 // Clear the model data related with glyphs.
624 ClearGlyphModelData( startIndex, endIndex, operations );
627 // The estimated number of lines. Used to avoid reallocations when layouting.
628 mTextUpdateInfo.mEstimatedNumberOfLines = std::max( mVisualModel->mLines.Count(), mLogicalModel->mParagraphInfo.Count() );
630 mVisualModel->ClearCaches();
633 bool Controller::Impl::UpdateModel( OperationsMask operationsRequired )
635 DALI_LOG_INFO( gLogFilter, Debug::General, "Controller::UpdateModel\n" );
637 // Calculate the operations to be done.
638 const OperationsMask operations = static_cast<OperationsMask>( mOperationsPending & operationsRequired );
640 if( NO_OPERATION == operations )
642 // Nothing to do if no operations are pending and required.
646 Vector<Character>& utf32Characters = mLogicalModel->mText;
648 const Length numberOfCharacters = utf32Characters.Count();
650 // Index to the first character of the first paragraph to be updated.
651 CharacterIndex startIndex = 0u;
652 // Number of characters of the paragraphs to be removed.
653 Length paragraphCharacters = 0u;
655 CalculateTextUpdateIndices( paragraphCharacters );
656 startIndex = mTextUpdateInfo.mParagraphCharacterIndex;
658 if( mTextUpdateInfo.mClearAll ||
659 ( 0u != paragraphCharacters ) )
661 ClearModelData( startIndex, startIndex + ( ( paragraphCharacters > 0u ) ? paragraphCharacters - 1u : 0u ), operations );
664 mTextUpdateInfo.mClearAll = false;
666 // Whether the model is updated.
667 bool updated = false;
669 Vector<LineBreakInfo>& lineBreakInfo = mLogicalModel->mLineBreakInfo;
670 const Length requestedNumberOfCharacters = mTextUpdateInfo.mRequestedNumberOfCharacters;
672 if( NO_OPERATION != ( GET_LINE_BREAKS & operations ) )
674 // Retrieves the line break info. The line break info is used to split the text in 'paragraphs' to
675 // calculate the bidirectional info for each 'paragraph'.
676 // It's also used to layout the text (where it should be a new line) or to shape the text (text in different lines
677 // is not shaped together).
678 lineBreakInfo.Resize( numberOfCharacters, TextAbstraction::LINE_NO_BREAK );
680 SetLineBreakInfo( utf32Characters,
682 requestedNumberOfCharacters,
685 // Create the paragraph info.
686 mLogicalModel->CreateParagraphInfo( startIndex,
687 requestedNumberOfCharacters );
691 Vector<WordBreakInfo>& wordBreakInfo = mLogicalModel->mWordBreakInfo;
692 if( NO_OPERATION != ( GET_WORD_BREAKS & operations ) )
694 // Retrieves the word break info. The word break info is used to layout the text (where to wrap the text in lines).
695 wordBreakInfo.Resize( numberOfCharacters, TextAbstraction::WORD_NO_BREAK );
697 SetWordBreakInfo( utf32Characters,
699 requestedNumberOfCharacters,
704 const bool getScripts = NO_OPERATION != ( GET_SCRIPTS & operations );
705 const bool validateFonts = NO_OPERATION != ( VALIDATE_FONTS & operations );
707 Vector<ScriptRun>& scripts = mLogicalModel->mScriptRuns;
708 Vector<FontRun>& validFonts = mLogicalModel->mFontRuns;
710 if( getScripts || validateFonts )
712 // Validates the fonts assigned by the application or assigns default ones.
713 // It makes sure all the characters are going to be rendered by the correct font.
714 MultilanguageSupport multilanguageSupport = MultilanguageSupport::Get();
718 // Retrieves the scripts used in the text.
719 multilanguageSupport.SetScripts( utf32Characters,
721 requestedNumberOfCharacters,
727 // Validate the fonts set through the mark-up string.
728 Vector<FontDescriptionRun>& fontDescriptionRuns = mLogicalModel->mFontDescriptionRuns;
730 // Get the default font id.
731 const FontId defaultFontId = ( NULL == mFontDefaults ) ? 0u : mFontDefaults->GetFontId( mFontClient );
733 // Validates the fonts. If there is a character with no assigned font it sets a default one.
734 // After this call, fonts are validated.
735 multilanguageSupport.ValidateFonts( utf32Characters,
740 requestedNumberOfCharacters,
746 Vector<Character> mirroredUtf32Characters;
747 bool textMirrored = false;
748 const Length numberOfParagraphs = mLogicalModel->mParagraphInfo.Count();
749 if( NO_OPERATION != ( BIDI_INFO & operations ) )
751 Vector<BidirectionalParagraphInfoRun>& bidirectionalInfo = mLogicalModel->mBidirectionalParagraphInfo;
752 bidirectionalInfo.Reserve( numberOfParagraphs );
754 // Calculates the bidirectional info for the whole paragraph if it contains right to left scripts.
755 SetBidirectionalInfo( utf32Characters,
759 requestedNumberOfCharacters,
762 if( 0u != bidirectionalInfo.Count() )
764 // Only set the character directions if there is right to left characters.
765 Vector<CharacterDirection>& directions = mLogicalModel->mCharacterDirections;
766 GetCharactersDirection( bidirectionalInfo,
769 requestedNumberOfCharacters,
772 // This paragraph has right to left text. Some characters may need to be mirrored.
773 // TODO: consider if the mirrored string can be stored as well.
775 textMirrored = GetMirroredText( utf32Characters,
779 requestedNumberOfCharacters,
780 mirroredUtf32Characters );
784 // There is no right to left characters. Clear the directions vector.
785 mLogicalModel->mCharacterDirections.Clear();
790 Vector<GlyphInfo>& glyphs = mVisualModel->mGlyphs;
791 Vector<CharacterIndex>& glyphsToCharactersMap = mVisualModel->mGlyphsToCharacters;
792 Vector<Length>& charactersPerGlyph = mVisualModel->mCharactersPerGlyph;
793 Vector<GlyphIndex> newParagraphGlyphs;
794 newParagraphGlyphs.Reserve( numberOfParagraphs );
796 const Length currentNumberOfGlyphs = glyphs.Count();
797 if( NO_OPERATION != ( SHAPE_TEXT & operations ) )
799 const Vector<Character>& textToShape = textMirrored ? mirroredUtf32Characters : utf32Characters;
801 ShapeText( textToShape,
806 mTextUpdateInfo.mStartGlyphIndex,
807 requestedNumberOfCharacters,
809 glyphsToCharactersMap,
811 newParagraphGlyphs );
813 // Create the 'number of glyphs' per character and the glyph to character conversion tables.
814 mVisualModel->CreateGlyphsPerCharacterTable( startIndex, mTextUpdateInfo.mStartGlyphIndex, requestedNumberOfCharacters );
815 mVisualModel->CreateCharacterToGlyphTable( startIndex, mTextUpdateInfo.mStartGlyphIndex, requestedNumberOfCharacters );
819 const Length numberOfGlyphs = glyphs.Count() - currentNumberOfGlyphs;
821 if( NO_OPERATION != ( GET_GLYPH_METRICS & operations ) )
823 GlyphInfo* glyphsBuffer = glyphs.Begin();
824 mMetrics->GetGlyphMetrics( glyphsBuffer + mTextUpdateInfo.mStartGlyphIndex, numberOfGlyphs );
826 // Update the width and advance of all new paragraph characters.
827 for( Vector<GlyphIndex>::ConstIterator it = newParagraphGlyphs.Begin(), endIt = newParagraphGlyphs.End(); it != endIt; ++it )
829 const GlyphIndex index = *it;
830 GlyphInfo& glyph = *( glyphsBuffer + index );
832 glyph.xBearing = 0.f;
839 if( NO_OPERATION != ( COLOR & operations ) )
841 // Set the color runs in glyphs.
842 SetColorSegmentationInfo( mLogicalModel->mColorRuns,
843 mVisualModel->mCharactersToGlyph,
844 mVisualModel->mGlyphsPerCharacter,
846 mTextUpdateInfo.mStartGlyphIndex,
847 requestedNumberOfCharacters,
848 mVisualModel->mColors,
849 mVisualModel->mColorIndices );
854 if( ( NULL != mEventData ) &&
855 mEventData->mPreEditFlag &&
856 ( 0u != mVisualModel->mCharactersToGlyph.Count() ) )
858 // Add the underline for the pre-edit text.
859 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
860 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
862 const GlyphIndex glyphStart = *( charactersToGlyphBuffer + mEventData->mPreEditStartPosition );
863 const CharacterIndex lastPreEditCharacter = mEventData->mPreEditStartPosition + ( ( mEventData->mPreEditLength > 0u ) ? mEventData->mPreEditLength - 1u : 0u );
864 const Length numberOfGlyphsLastCharacter = *( glyphsPerCharacterBuffer + lastPreEditCharacter );
865 const GlyphIndex glyphEnd = *( charactersToGlyphBuffer + lastPreEditCharacter ) + ( numberOfGlyphsLastCharacter > 1u ? numberOfGlyphsLastCharacter - 1u : 0u );
867 GlyphRun underlineRun;
868 underlineRun.glyphIndex = glyphStart;
869 underlineRun.numberOfGlyphs = 1u + glyphEnd - glyphStart;
871 // TODO: At the moment the underline runs are only for pre-edit.
872 mVisualModel->mUnderlineRuns.PushBack( underlineRun );
875 // The estimated number of lines. Used to avoid reallocations when layouting.
876 mTextUpdateInfo.mEstimatedNumberOfLines = std::max( mVisualModel->mLines.Count(), mLogicalModel->mParagraphInfo.Count() );
878 // Set the previous number of characters for the next time the text is updated.
879 mTextUpdateInfo.mPreviousNumberOfCharacters = numberOfCharacters;
884 void Controller::Impl::RetrieveDefaultInputStyle( InputStyle& inputStyle )
886 // Sets the default text's color.
887 inputStyle.textColor = mTextColor;
888 inputStyle.isDefaultColor = true;
890 inputStyle.familyName.clear();
891 inputStyle.weight = TextAbstraction::FontWeight::NORMAL;
892 inputStyle.width = TextAbstraction::FontWidth::NORMAL;
893 inputStyle.slant = TextAbstraction::FontSlant::NORMAL;
894 inputStyle.size = 0.f;
896 inputStyle.familyDefined = false;
897 inputStyle.weightDefined = false;
898 inputStyle.widthDefined = false;
899 inputStyle.slantDefined = false;
900 inputStyle.sizeDefined = false;
902 // Sets the default font's family name, weight, width, slant and size.
905 if( mFontDefaults->familyDefined )
907 inputStyle.familyName = mFontDefaults->mFontDescription.family;
908 inputStyle.familyDefined = true;
911 if( mFontDefaults->weightDefined )
913 inputStyle.weight = mFontDefaults->mFontDescription.weight;
914 inputStyle.weightDefined = true;
917 if( mFontDefaults->widthDefined )
919 inputStyle.width = mFontDefaults->mFontDescription.width;
920 inputStyle.widthDefined = true;
923 if( mFontDefaults->slantDefined )
925 inputStyle.slant = mFontDefaults->mFontDescription.slant;
926 inputStyle.slantDefined = true;
929 if( mFontDefaults->sizeDefined )
931 inputStyle.size = mFontDefaults->mDefaultPointSize;
932 inputStyle.sizeDefined = true;
937 float Controller::Impl::GetDefaultFontLineHeight()
939 FontId defaultFontId = 0u;
940 if( NULL == mFontDefaults )
942 TextAbstraction::FontDescription fontDescription;
943 defaultFontId = mFontClient.GetFontId( fontDescription );
947 defaultFontId = mFontDefaults->GetFontId( mFontClient );
950 Text::FontMetrics fontMetrics;
951 mMetrics->GetFontMetrics( defaultFontId, fontMetrics );
953 return( fontMetrics.ascender - fontMetrics.descender );
956 void Controller::Impl::OnCursorKeyEvent( const Event& event )
958 if( NULL == mEventData )
960 // Nothing to do if there is no text input.
964 int keyCode = event.p1.mInt;
966 if( Dali::DALI_KEY_CURSOR_LEFT == keyCode )
968 if( mEventData->mPrimaryCursorPosition > 0u )
970 mEventData->mPrimaryCursorPosition = CalculateNewCursorIndex( mEventData->mPrimaryCursorPosition - 1u );
973 else if( Dali::DALI_KEY_CURSOR_RIGHT == keyCode )
975 if( mLogicalModel->mText.Count() > mEventData->mPrimaryCursorPosition )
977 mEventData->mPrimaryCursorPosition = CalculateNewCursorIndex( mEventData->mPrimaryCursorPosition );
980 else if( Dali::DALI_KEY_CURSOR_UP == keyCode )
984 else if( Dali::DALI_KEY_CURSOR_DOWN == keyCode )
989 mEventData->mUpdateCursorPosition = true;
990 mEventData->mUpdateInputStyle = true;
991 mEventData->mScrollAfterUpdatePosition = true;
994 void Controller::Impl::OnTapEvent( const Event& event )
996 if( NULL != mEventData )
998 const unsigned int tapCount = event.p1.mUint;
1000 if( 1u == tapCount )
1002 if( IsShowingRealText() )
1004 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
1005 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
1007 mEventData->mPrimaryCursorPosition = GetClosestCursorIndex( xPosition,
1010 // When the cursor position is changing, delay cursor blinking
1011 mEventData->mDecorator->DelayCursorBlink();
1015 mEventData->mPrimaryCursorPosition = 0u;
1018 mEventData->mUpdateCursorPosition = true;
1019 mEventData->mScrollAfterUpdatePosition = true;
1020 mEventData->mUpdateInputStyle = true;
1022 // Notify the cursor position to the imf manager.
1023 if( mEventData->mImfManager )
1025 mEventData->mImfManager.SetCursorPosition( mEventData->mPrimaryCursorPosition );
1026 mEventData->mImfManager.NotifyCursorPosition();
1032 void Controller::Impl::OnPanEvent( const Event& event )
1034 if( NULL == mEventData )
1036 // Nothing to do if there is no text input.
1040 int state = event.p1.mInt;
1042 if( Gesture::Started == state ||
1043 Gesture::Continuing == state )
1045 const Vector2& actualSize = mVisualModel->GetLayoutSize();
1046 const Vector2 currentScroll = mEventData->mScrollPosition;
1048 if( mEventData->mHorizontalScrollingEnabled )
1050 const float displacementX = event.p2.mFloat;
1051 mEventData->mScrollPosition.x += displacementX;
1053 ClampHorizontalScroll( actualSize );
1056 if( mEventData->mVerticalScrollingEnabled )
1058 const float displacementY = event.p3.mFloat;
1059 mEventData->mScrollPosition.y += displacementY;
1061 ClampVerticalScroll( actualSize );
1064 if( mEventData->mDecorator )
1066 mEventData->mDecorator->UpdatePositions( mEventData->mScrollPosition - currentScroll );
1071 void Controller::Impl::OnLongPressEvent( const Event& event )
1073 DALI_LOG_INFO( gLogFilter, Debug::General, "Controller::OnLongPressEvent\n" );
1075 if( EventData::EDITING == mEventData->mState )
1077 ChangeState ( EventData::EDITING_WITH_POPUP );
1078 mEventData->mDecoratorUpdated = true;
1082 void Controller::Impl::OnHandleEvent( const Event& event )
1084 if( NULL == mEventData )
1086 // Nothing to do if there is no text input.
1090 const unsigned int state = event.p1.mUint;
1091 const bool handleStopScrolling = ( HANDLE_STOP_SCROLLING == state );
1093 if( HANDLE_PRESSED == state )
1095 // The event.p2 and event.p3 are in decorator coords. Need to transforms to text coords.
1096 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
1097 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
1099 const CharacterIndex handleNewPosition = GetClosestCursorIndex( xPosition, yPosition );
1101 if( Event::GRAB_HANDLE_EVENT == event.type )
1103 ChangeState ( EventData::GRAB_HANDLE_PANNING );
1105 if( handleNewPosition != mEventData->mPrimaryCursorPosition )
1107 mEventData->mPrimaryCursorPosition = handleNewPosition;
1108 mEventData->mUpdateCursorPosition = true;
1111 else if( Event::LEFT_SELECTION_HANDLE_EVENT == event.type )
1113 ChangeState ( EventData::SELECTION_HANDLE_PANNING );
1115 if( ( handleNewPosition != mEventData->mLeftSelectionPosition ) &&
1116 ( handleNewPosition != mEventData->mRightSelectionPosition ) )
1118 mEventData->mLeftSelectionPosition = handleNewPosition;
1120 mEventData->mUpdateLeftSelectionPosition = true;
1123 else if( Event::RIGHT_SELECTION_HANDLE_EVENT == event.type )
1125 ChangeState ( EventData::SELECTION_HANDLE_PANNING );
1127 if( ( handleNewPosition != mEventData->mRightSelectionPosition ) &&
1128 ( handleNewPosition != mEventData->mLeftSelectionPosition ) )
1130 mEventData->mRightSelectionPosition = handleNewPosition;
1132 mEventData->mUpdateRightSelectionPosition = true;
1135 } // end ( HANDLE_PRESSED == state )
1136 else if( ( HANDLE_RELEASED == state ) ||
1137 handleStopScrolling )
1139 CharacterIndex handlePosition = 0u;
1140 if( handleStopScrolling )
1142 // The event.p2 and event.p3 are in decorator coords. Need to transforms to text coords.
1143 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
1144 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
1146 handlePosition = GetClosestCursorIndex( xPosition, yPosition );
1149 if( Event::GRAB_HANDLE_EVENT == event.type )
1151 mEventData->mUpdateCursorPosition = true;
1152 mEventData->mUpdateInputStyle = true;
1154 if( !IsClipboardEmpty() )
1156 ChangeState( EventData::EDITING_WITH_PASTE_POPUP ); // Moving grabhandle will show Paste Popup
1159 if( handleStopScrolling )
1161 mEventData->mScrollAfterUpdatePosition = mEventData->mPrimaryCursorPosition != handlePosition;
1162 mEventData->mPrimaryCursorPosition = handlePosition;
1165 else if( Event::LEFT_SELECTION_HANDLE_EVENT == event.type )
1167 ChangeState( EventData::SELECTING );
1169 if( handleStopScrolling )
1171 mEventData->mUpdateLeftSelectionPosition = ( mEventData->mRightSelectionPosition != handlePosition );
1172 mEventData->mScrollAfterUpdatePosition = mEventData->mUpdateLeftSelectionPosition;
1174 if( mEventData->mUpdateLeftSelectionPosition )
1176 mEventData->mLeftSelectionPosition = handlePosition;
1180 else if( Event::RIGHT_SELECTION_HANDLE_EVENT == event.type )
1182 ChangeState( EventData::SELECTING );
1184 if( handleStopScrolling )
1186 mEventData->mUpdateRightSelectionPosition = ( mEventData->mLeftSelectionPosition != handlePosition );
1187 mEventData->mScrollAfterUpdatePosition = mEventData->mUpdateRightSelectionPosition;
1188 if( mEventData->mUpdateRightSelectionPosition )
1190 mEventData->mRightSelectionPosition = handlePosition;
1195 mEventData->mDecoratorUpdated = true;
1196 } // end ( ( HANDLE_RELEASED == state ) || ( HANDLE_STOP_SCROLLING == state ) )
1197 else if( HANDLE_SCROLLING == state )
1199 const float xSpeed = event.p2.mFloat;
1200 const Vector2& actualSize = mVisualModel->GetLayoutSize();
1201 const Vector2 currentScrollPosition = mEventData->mScrollPosition;
1203 mEventData->mScrollPosition.x += xSpeed;
1205 ClampHorizontalScroll( actualSize );
1207 bool endOfScroll = false;
1208 if( Vector2::ZERO == ( currentScrollPosition - mEventData->mScrollPosition ) )
1210 // Notify the decorator there is no more text to scroll.
1211 // The decorator won't send more scroll events.
1212 mEventData->mDecorator->NotifyEndOfScroll();
1213 // Still need to set the position of the handle.
1217 // Set the position of the handle.
1218 const bool scrollRightDirection = xSpeed > 0.f;
1219 const bool leftSelectionHandleEvent = Event::LEFT_SELECTION_HANDLE_EVENT == event.type;
1220 const bool rightSelectionHandleEvent = Event::RIGHT_SELECTION_HANDLE_EVENT == event.type;
1222 if( Event::GRAB_HANDLE_EVENT == event.type )
1224 ChangeState( EventData::GRAB_HANDLE_PANNING );
1226 Vector2 position = mEventData->mDecorator->GetPosition( GRAB_HANDLE );
1228 // Position the grag handle close to either the left or right edge.
1229 position.x = scrollRightDirection ? 0.f : mVisualModel->mControlSize.width;
1231 // Get the new handle position.
1232 // The grab handle's position is in decorator coords. Need to transforms to text coords.
1233 const CharacterIndex handlePosition = GetClosestCursorIndex( position.x - mEventData->mScrollPosition.x - mAlignmentOffset.x,
1234 position.y - mEventData->mScrollPosition.y - mAlignmentOffset.y );
1236 mEventData->mUpdateCursorPosition = mEventData->mPrimaryCursorPosition != handlePosition;
1237 mEventData->mScrollAfterUpdatePosition = mEventData->mUpdateCursorPosition;
1238 mEventData->mPrimaryCursorPosition = handlePosition;
1239 mEventData->mUpdateInputStyle = mEventData->mUpdateCursorPosition;
1241 else if( leftSelectionHandleEvent || rightSelectionHandleEvent )
1243 // TODO: This is recalculating the selection box every time the text is scrolled with the selection handles.
1244 // Think if something can be done to save power.
1246 ChangeState( EventData::SELECTION_HANDLE_PANNING );
1248 Vector2 position = mEventData->mDecorator->GetPosition( leftSelectionHandleEvent ? Text::LEFT_SELECTION_HANDLE : Text::RIGHT_SELECTION_HANDLE );
1250 // Position the selection handle close to either the left or right edge.
1251 position.x = scrollRightDirection ? 0.f : mVisualModel->mControlSize.width;
1253 // Get the new handle position.
1254 // The selection handle's position is in decorator coords. Need to transforms to text coords.
1255 const CharacterIndex handlePosition = GetClosestCursorIndex( position.x - mEventData->mScrollPosition.x - mAlignmentOffset.x,
1256 position.y - mEventData->mScrollPosition.y - mAlignmentOffset.y );
1258 if( leftSelectionHandleEvent )
1260 const bool differentHandles = ( mEventData->mLeftSelectionPosition != handlePosition ) && ( mEventData->mRightSelectionPosition != handlePosition );
1261 mEventData->mUpdateLeftSelectionPosition = endOfScroll || differentHandles;
1262 if( differentHandles )
1264 mEventData->mLeftSelectionPosition = handlePosition;
1269 const bool differentHandles = ( mEventData->mRightSelectionPosition != handlePosition ) && ( mEventData->mLeftSelectionPosition != handlePosition );
1270 mEventData->mUpdateRightSelectionPosition = endOfScroll || differentHandles;
1271 if( differentHandles )
1273 mEventData->mRightSelectionPosition = handlePosition;
1277 if( mEventData->mUpdateLeftSelectionPosition || mEventData->mUpdateRightSelectionPosition )
1279 RepositionSelectionHandles();
1281 mEventData->mScrollAfterUpdatePosition = true;
1284 mEventData->mDecoratorUpdated = true;
1285 } // end ( HANDLE_SCROLLING == state )
1288 void Controller::Impl::OnSelectEvent( const Event& event )
1290 if( NULL == mEventData )
1292 // Nothing to do if there is no text.
1296 if( mEventData->mSelectionEnabled )
1298 // The event.p2 and event.p3 are in decorator coords. Need to transforms to text coords.
1299 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
1300 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
1302 // Calculates the logical position from the x,y coords.
1303 RepositionSelectionHandles( xPosition,
1306 mEventData->mUpdateLeftSelectionPosition = true;
1307 mEventData->mUpdateRightSelectionPosition = true;
1308 mEventData->mUpdateCursorPosition = false;
1310 mEventData->mScrollAfterUpdatePosition = ( mEventData->mLeftSelectionPosition != mEventData->mRightSelectionPosition );
1314 void Controller::Impl::OnSelectAllEvent()
1316 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "OnSelectAllEvent mEventData->mSelectionEnabled%s \n", mEventData->mSelectionEnabled?"true":"false");
1318 if( NULL == mEventData )
1320 // Nothing to do if there is no text.
1324 if( mEventData->mSelectionEnabled )
1326 mEventData->mLeftSelectionPosition = 0u;
1327 mEventData->mRightSelectionPosition = mLogicalModel->mText.Count();
1329 mEventData->mScrollAfterUpdatePosition = true;
1330 mEventData->mUpdateLeftSelectionPosition = true;
1331 mEventData->mUpdateRightSelectionPosition = true;
1335 void Controller::Impl::RetrieveSelection( std::string& selectedText, bool deleteAfterRetrieval )
1337 if( mEventData->mLeftSelectionPosition == mEventData->mRightSelectionPosition )
1339 // Nothing to select if handles are in the same place.
1340 selectedText.clear();
1344 const bool handlesCrossed = mEventData->mLeftSelectionPosition > mEventData->mRightSelectionPosition;
1346 //Get start and end position of selection
1347 const CharacterIndex startOfSelectedText = handlesCrossed ? mEventData->mRightSelectionPosition : mEventData->mLeftSelectionPosition;
1348 const Length lengthOfSelectedText = ( handlesCrossed ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition ) - startOfSelectedText;
1350 Vector<Character>& utf32Characters = mLogicalModel->mText;
1351 const Length numberOfCharacters = utf32Characters.Count();
1353 // Validate the start and end selection points
1354 if( ( startOfSelectedText + lengthOfSelectedText ) <= numberOfCharacters )
1356 //Get text as a UTF8 string
1357 Utf32ToUtf8( &utf32Characters[startOfSelectedText], lengthOfSelectedText, selectedText );
1359 if( deleteAfterRetrieval ) // Only delete text if copied successfully
1361 // Set as input style the style of the first deleted character.
1362 mLogicalModel->RetrieveStyle( startOfSelectedText, mEventData->mInputStyle );
1364 mLogicalModel->UpdateTextStyleRuns( startOfSelectedText, -static_cast<int>( lengthOfSelectedText ) );
1366 // Mark the paragraphs to be updated.
1367 mTextUpdateInfo.mCharacterIndex = startOfSelectedText;
1368 mTextUpdateInfo.mNumberOfCharactersToRemove = lengthOfSelectedText;
1370 // Delete text between handles
1371 Vector<Character>::Iterator first = utf32Characters.Begin() + startOfSelectedText;
1372 Vector<Character>::Iterator last = first + lengthOfSelectedText;
1373 utf32Characters.Erase( first, last );
1375 // Scroll after delete.
1376 mEventData->mPrimaryCursorPosition = handlesCrossed ? mEventData->mRightSelectionPosition : mEventData->mLeftSelectionPosition;
1378 mEventData->mDecoratorUpdated = true;
1382 void Controller::Impl::ShowClipboard()
1386 mClipboard.ShowClipboard();
1390 void Controller::Impl::HideClipboard()
1392 if( mClipboard && mClipboardHideEnabled )
1394 mClipboard.HideClipboard();
1398 void Controller::Impl::SetClipboardHideEnable(bool enable)
1400 mClipboardHideEnabled = enable;
1403 bool Controller::Impl::CopyStringToClipboard( std::string& source )
1405 //Send string to clipboard
1406 return ( mClipboard && mClipboard.SetItem( source ) );
1409 void Controller::Impl::SendSelectionToClipboard( bool deleteAfterSending )
1411 std::string selectedText;
1412 RetrieveSelection( selectedText, deleteAfterSending );
1413 CopyStringToClipboard( selectedText );
1414 ChangeState( EventData::EDITING );
1417 void Controller::Impl::GetTextFromClipboard( unsigned int itemIndex, std::string& retrievedString )
1421 retrievedString = mClipboard.GetItem( itemIndex );
1425 void Controller::Impl::RepositionSelectionHandles()
1427 CharacterIndex selectionStart = mEventData->mLeftSelectionPosition;
1428 CharacterIndex selectionEnd = mEventData->mRightSelectionPosition;
1430 if( selectionStart == selectionEnd )
1432 // Nothing to select if handles are in the same place.
1436 mEventData->mDecorator->ClearHighlights();
1438 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
1439 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
1440 const GlyphInfo* const glyphsBuffer = mVisualModel->mGlyphs.Begin();
1441 const Vector2* const positionsBuffer = mVisualModel->mGlyphPositions.Begin();
1442 const Length* const charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
1443 const CharacterIndex* const glyphToCharacterBuffer = mVisualModel->mGlyphsToCharacters.Begin();
1444 const CharacterDirection* const modelCharacterDirectionsBuffer = ( 0u != mLogicalModel->mCharacterDirections.Count() ) ? mLogicalModel->mCharacterDirections.Begin() : NULL;
1446 // TODO: Better algorithm to create the highlight box.
1447 // TODO: Multi-line.
1449 // Get the height of the line.
1450 const Vector<LineRun>& lines = mVisualModel->mLines;
1451 const LineRun& firstLine = *lines.Begin();
1452 const float height = firstLine.ascender + -firstLine.descender;
1454 const bool isLastCharacter = selectionEnd >= mLogicalModel->mText.Count();
1455 const bool startDirection = ( ( NULL == modelCharacterDirectionsBuffer ) ? false : *( modelCharacterDirectionsBuffer + selectionStart ) );
1456 const bool endDirection = ( ( NULL == modelCharacterDirectionsBuffer ) ? false : *( modelCharacterDirectionsBuffer + ( selectionEnd - ( isLastCharacter ? 1u : 0u ) ) ) );
1458 // Swap the indices if the start is greater than the end.
1459 const bool indicesSwapped = selectionStart > selectionEnd;
1461 // Tell the decorator to flip the selection handles if needed.
1462 mEventData->mDecorator->SetSelectionHandleFlipState( indicesSwapped, startDirection, endDirection );
1464 if( indicesSwapped )
1466 std::swap( selectionStart, selectionEnd );
1469 // Get the indices to the first and last selected glyphs.
1470 const CharacterIndex selectionEndMinusOne = selectionEnd - 1u;
1471 const GlyphIndex glyphStart = *( charactersToGlyphBuffer + selectionStart );
1472 const Length numberOfGlyphs = *( glyphsPerCharacterBuffer + selectionEndMinusOne );
1473 const GlyphIndex glyphEnd = *( charactersToGlyphBuffer + selectionEndMinusOne ) + ( ( numberOfGlyphs > 0 ) ? numberOfGlyphs - 1u : 0u );
1475 // Check if the first glyph is a ligature that must be broken like Latin ff, fi, or Arabic ﻻ, etc which needs special code.
1476 const Length numberOfCharactersStart = *( charactersPerGlyphBuffer + glyphStart );
1477 bool splitStartGlyph = ( numberOfCharactersStart > 1u ) && HasLigatureMustBreak( mLogicalModel->GetScript( selectionStart ) );
1479 // Check if the last glyph is a ligature that must be broken like Latin ff, fi, or Arabic ﻻ, etc which needs special code.
1480 const Length numberOfCharactersEnd = *( charactersPerGlyphBuffer + glyphEnd );
1481 bool splitEndGlyph = ( glyphStart != glyphEnd ) && ( numberOfCharactersEnd > 1u ) && HasLigatureMustBreak( mLogicalModel->GetScript( selectionEndMinusOne ) );
1483 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
1485 // Traverse the glyphs.
1486 for( GlyphIndex index = glyphStart; index <= glyphEnd; ++index )
1488 const GlyphInfo& glyph = *( glyphsBuffer + index );
1489 const Vector2& position = *( positionsBuffer + index );
1491 if( splitStartGlyph )
1493 // 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.
1495 const float glyphAdvance = glyph.advance / static_cast<float>( numberOfCharactersStart );
1496 const CharacterIndex interGlyphIndex = selectionStart - *( glyphToCharacterBuffer + glyphStart );
1497 // Get the direction of the character.
1498 CharacterDirection isCurrentRightToLeft = false;
1499 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
1501 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + selectionStart );
1504 // The end point could be in the middle of the ligature.
1505 // Calculate the number of characters selected.
1506 const Length numberOfCharacters = ( glyphStart == glyphEnd ) ? ( selectionEnd - selectionStart ) : ( numberOfCharactersStart - interGlyphIndex );
1508 const float xPosition = position.x - glyph.xBearing + offset.x + glyphAdvance * static_cast<float>( isCurrentRightToLeft ? ( numberOfCharactersStart - interGlyphIndex - numberOfCharacters ) : interGlyphIndex );
1510 mEventData->mDecorator->AddHighlight( xPosition,
1512 xPosition + static_cast<float>( numberOfCharacters ) * glyphAdvance,
1513 offset.y + height );
1515 splitStartGlyph = false;
1519 if( splitEndGlyph && ( index == glyphEnd ) )
1521 // 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.
1523 const float glyphAdvance = glyph.advance / static_cast<float>( numberOfCharactersEnd );
1524 const CharacterIndex interGlyphIndex = selectionEnd - *( glyphToCharacterBuffer + glyphEnd );
1525 // Get the direction of the character.
1526 CharacterDirection isCurrentRightToLeft = false;
1527 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
1529 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + selectionEnd );
1532 const Length numberOfCharacters = numberOfCharactersEnd - interGlyphIndex;
1534 const float xPosition = position.x - glyph.xBearing + offset.x + ( isCurrentRightToLeft ? ( glyphAdvance * static_cast<float>( numberOfCharacters ) ) : 0.f );
1535 mEventData->mDecorator->AddHighlight( xPosition,
1537 xPosition + static_cast<float>( interGlyphIndex ) * glyphAdvance,
1538 offset.y + height );
1540 splitEndGlyph = false;
1544 const float xPosition = position.x - glyph.xBearing + offset.x;
1545 mEventData->mDecorator->AddHighlight( xPosition,
1547 xPosition + glyph.advance,
1548 offset.y + height );
1551 CursorInfo primaryCursorInfo;
1552 GetCursorPosition( mEventData->mLeftSelectionPosition,
1553 primaryCursorInfo );
1555 CursorInfo secondaryCursorInfo;
1556 GetCursorPosition( mEventData->mRightSelectionPosition,
1557 secondaryCursorInfo );
1559 const Vector2 primaryPosition = primaryCursorInfo.primaryPosition + offset;
1560 const Vector2 secondaryPosition = secondaryCursorInfo.primaryPosition + offset;
1562 mEventData->mDecorator->SetPosition( LEFT_SELECTION_HANDLE,
1564 primaryCursorInfo.lineOffset + offset.y,
1565 primaryCursorInfo.lineHeight );
1567 mEventData->mDecorator->SetPosition( RIGHT_SELECTION_HANDLE,
1568 secondaryPosition.x,
1569 secondaryCursorInfo.lineOffset + offset.y,
1570 secondaryCursorInfo.lineHeight );
1572 // Cursor to be positioned at end of selection so if selection interrupted and edit mode restarted the cursor will be at end of selection
1573 mEventData->mPrimaryCursorPosition = ( indicesSwapped ) ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition;
1575 // Set the flag to update the decorator.
1576 mEventData->mDecoratorUpdated = true;
1579 void Controller::Impl::RepositionSelectionHandles( float visualX, float visualY )
1581 if( NULL == mEventData )
1583 // Nothing to do if there is no text input.
1587 if( IsShowingPlaceholderText() )
1589 // Nothing to do if there is the place-holder text.
1593 const Length numberOfGlyphs = mVisualModel->mGlyphs.Count();
1594 const Length numberOfLines = mVisualModel->mLines.Count();
1595 if( ( 0 == numberOfGlyphs ) ||
1596 ( 0 == numberOfLines ) )
1598 // Nothing to do if there is no text.
1602 // Find which word was selected
1603 CharacterIndex selectionStart( 0 );
1604 CharacterIndex selectionEnd( 0 );
1605 FindSelectionIndices( visualX, visualY, selectionStart, selectionEnd );
1606 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "%p selectionStart %d selectionEnd %d\n", this, selectionStart, selectionEnd );
1608 if( selectionStart == selectionEnd )
1610 ChangeState( EventData::EDITING );
1611 // Nothing to select. i.e. a white space, out of bounds
1615 mEventData->mLeftSelectionPosition = selectionStart;
1616 mEventData->mRightSelectionPosition = selectionEnd;
1619 void Controller::Impl::SetPopupButtons()
1622 * Sets the Popup buttons to be shown depending on State.
1624 * If SELECTING : CUT & COPY + ( PASTE & CLIPBOARD if content available to paste )
1626 * If EDITING_WITH_POPUP : SELECT & SELECT_ALL
1629 TextSelectionPopup::Buttons buttonsToShow = TextSelectionPopup::NONE;
1631 if( EventData::SELECTING == mEventData->mState )
1633 buttonsToShow = TextSelectionPopup::Buttons( TextSelectionPopup::CUT | TextSelectionPopup::COPY );
1635 if( !IsClipboardEmpty() )
1637 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
1638 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::CLIPBOARD ) );
1641 if( !mEventData->mAllTextSelected )
1643 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::SELECT_ALL ) );
1646 else if( EventData::EDITING_WITH_POPUP == mEventData->mState )
1648 if( mLogicalModel->mText.Count() && !IsShowingPlaceholderText() )
1650 buttonsToShow = TextSelectionPopup::Buttons( TextSelectionPopup::SELECT | TextSelectionPopup::SELECT_ALL );
1653 if( !IsClipboardEmpty() )
1655 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
1656 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::CLIPBOARD ) );
1659 else if( EventData::EDITING_WITH_PASTE_POPUP == mEventData->mState )
1661 if ( !IsClipboardEmpty() )
1663 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
1664 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::CLIPBOARD ) );
1668 mEventData->mDecorator->SetEnabledPopupButtons( buttonsToShow );
1671 void Controller::Impl::ChangeState( EventData::State newState )
1673 if( NULL == mEventData )
1675 // Nothing to do if there is no text input.
1679 DALI_LOG_INFO( gLogFilter, Debug::General, "ChangeState state:%d newstate:%d\n", mEventData->mState, newState );
1681 if( mEventData->mState != newState )
1683 mEventData->mState = newState;
1685 if( EventData::INACTIVE == mEventData->mState )
1687 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
1688 mEventData->mDecorator->StopCursorBlink();
1689 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1690 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1691 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1692 mEventData->mDecorator->SetPopupActive( false );
1693 mEventData->mDecoratorUpdated = true;
1696 else if( EventData::INTERRUPTED == mEventData->mState)
1698 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1699 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1700 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1701 mEventData->mDecorator->SetPopupActive( false );
1702 mEventData->mDecoratorUpdated = true;
1705 else if( EventData::SELECTING == mEventData->mState )
1707 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
1708 mEventData->mDecorator->StopCursorBlink();
1709 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1710 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true );
1711 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true );
1712 if( mEventData->mGrabHandlePopupEnabled )
1715 mEventData->mDecorator->SetPopupActive( true );
1717 mEventData->mDecoratorUpdated = true;
1719 else if( EventData::EDITING == mEventData->mState )
1721 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1722 if( mEventData->mCursorBlinkEnabled )
1724 mEventData->mDecorator->StartCursorBlink();
1726 // Grab handle is not shown until a tap is received whilst EDITING
1727 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1728 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1729 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1730 if( mEventData->mGrabHandlePopupEnabled )
1732 mEventData->mDecorator->SetPopupActive( false );
1734 mEventData->mDecoratorUpdated = true;
1737 else if( EventData::EDITING_WITH_POPUP == mEventData->mState )
1739 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "EDITING_WITH_POPUP \n", newState );
1741 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1742 if( mEventData->mCursorBlinkEnabled )
1744 mEventData->mDecorator->StartCursorBlink();
1746 if( mEventData->mSelectionEnabled )
1748 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1749 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1753 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
1755 if( mEventData->mGrabHandlePopupEnabled )
1758 mEventData->mDecorator->SetPopupActive( true );
1761 mEventData->mDecoratorUpdated = true;
1763 else if( EventData::EDITING_WITH_GRAB_HANDLE == mEventData->mState )
1765 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "EDITING_WITH_GRAB_HANDLE \n", newState );
1767 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1768 if( mEventData->mCursorBlinkEnabled )
1770 mEventData->mDecorator->StartCursorBlink();
1772 // Grab handle is not shown until a tap is received whilst EDITING
1773 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
1774 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1775 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1776 if( mEventData->mGrabHandlePopupEnabled )
1778 mEventData->mDecorator->SetPopupActive( false );
1780 mEventData->mDecoratorUpdated = true;
1783 else if( EventData::SELECTION_HANDLE_PANNING == mEventData->mState )
1785 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
1786 mEventData->mDecorator->StopCursorBlink();
1787 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1788 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true );
1789 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true );
1790 if( mEventData->mGrabHandlePopupEnabled )
1792 mEventData->mDecorator->SetPopupActive( false );
1794 mEventData->mDecoratorUpdated = true;
1796 else if( EventData::GRAB_HANDLE_PANNING == mEventData->mState )
1798 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "GRAB_HANDLE_PANNING \n", newState );
1800 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1801 if( mEventData->mCursorBlinkEnabled )
1803 mEventData->mDecorator->StartCursorBlink();
1805 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
1806 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1807 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1808 if( mEventData->mGrabHandlePopupEnabled )
1810 mEventData->mDecorator->SetPopupActive( false );
1812 mEventData->mDecoratorUpdated = true;
1814 else if( EventData::EDITING_WITH_PASTE_POPUP == mEventData->mState )
1816 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "EDITING_WITH_PASTE_POPUP \n", newState );
1818 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1819 if( mEventData->mCursorBlinkEnabled )
1821 mEventData->mDecorator->StartCursorBlink();
1824 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
1825 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1826 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1828 if( mEventData->mGrabHandlePopupEnabled )
1831 mEventData->mDecorator->SetPopupActive( true );
1834 mEventData->mDecoratorUpdated = true;
1839 LineIndex Controller::Impl::GetClosestLine( float y ) const
1841 float totalHeight = 0.f;
1842 LineIndex lineIndex = 0u;
1844 const Vector<LineRun>& lines = mVisualModel->mLines;
1845 for( LineIndex endLine = lines.Count();
1846 lineIndex < endLine;
1849 const LineRun& lineRun = lines[lineIndex];
1850 totalHeight += lineRun.ascender + -lineRun.descender;
1851 if( y < totalHeight )
1857 if( lineIndex == 0 )
1865 void Controller::Impl::FindSelectionIndices( float visualX, float visualY, CharacterIndex& startIndex, CharacterIndex& endIndex )
1867 CharacterIndex hitCharacter = GetClosestCursorIndex( visualX, visualY );
1868 DALI_ASSERT_DEBUG( hitCharacter <= mLogicalModel->mText.Count() && "GetClosestCursorIndex returned out of bounds index" );
1870 if( mLogicalModel->mText.Count() == 0 )
1872 return; // if model empty
1875 if( hitCharacter >= mLogicalModel->mText.Count() )
1877 // Closest hit character is the last character.
1878 if( hitCharacter == mLogicalModel->mText.Count() )
1880 hitCharacter--; //Hit character index set to last character in logical model
1884 // hitCharacter is out of bounds
1889 startIndex = hitCharacter;
1890 endIndex = hitCharacter;
1891 bool isHitCharacterWhitespace = TextAbstraction::IsWhiteSpace( mLogicalModel->mText[hitCharacter] );
1893 // Find the start and end of the text
1894 for( startIndex = hitCharacter; startIndex > 0; --startIndex )
1896 if( isHitCharacterWhitespace != TextAbstraction::IsWhiteSpace( mLogicalModel->mText[ startIndex-1 ] ) )
1901 const CharacterIndex pastTheEnd = mLogicalModel->mText.Count();
1902 for( endIndex = hitCharacter + 1u; endIndex < pastTheEnd; ++endIndex )
1904 if( isHitCharacterWhitespace != TextAbstraction::IsWhiteSpace( mLogicalModel->mText[ endIndex ] ) )
1911 CharacterIndex Controller::Impl::GetClosestCursorIndex( float visualX,
1914 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "GetClosestCursorIndex %p closest visualX %f visualY %f\n", this, visualX, visualY );
1916 if( NULL == mEventData )
1918 // Nothing to do if there is no text input.
1922 CharacterIndex logicalIndex = 0u;
1924 const Length numberOfGlyphs = mVisualModel->mGlyphs.Count();
1925 const Length numberOfLines = mVisualModel->mLines.Count();
1926 if( ( 0 == numberOfGlyphs ) ||
1927 ( 0 == numberOfLines ) )
1929 return logicalIndex;
1932 // Find which line is closest
1933 const LineIndex lineIndex = GetClosestLine( visualY );
1934 const LineRun& line = mVisualModel->mLines[lineIndex];
1936 // Get the positions of the glyphs.
1937 const Vector<Vector2>& positions = mVisualModel->mGlyphPositions;
1938 const Vector2* const positionsBuffer = positions.Begin();
1940 // Get the character to glyph conversion table.
1941 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
1943 // Get the glyphs per character table.
1944 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
1946 // Get the glyph's info buffer.
1947 const GlyphInfo* const glyphInfoBuffer = mVisualModel->mGlyphs.Begin();
1949 const CharacterIndex startCharacter = line.characterRun.characterIndex;
1950 const CharacterIndex endCharacter = line.characterRun.characterIndex + line.characterRun.numberOfCharacters;
1951 DALI_ASSERT_DEBUG( endCharacter <= mLogicalModel->mText.Count() && "Invalid line info" );
1953 // Whether there is a hit on a glyph.
1954 bool matched = false;
1956 // Traverses glyphs in visual order. To do that use the visual to logical conversion table.
1957 CharacterIndex visualIndex = startCharacter;
1958 Length numberOfCharacters = 0u;
1959 for( ; !matched && ( visualIndex < endCharacter ); ++visualIndex )
1961 // The character in logical order.
1962 const CharacterIndex characterLogicalOrderIndex = mLogicalModel->GetLogicalCharacterIndex( visualIndex );
1964 // Get the script of the character.
1965 const Script script = mLogicalModel->GetScript( characterLogicalOrderIndex );
1967 // The number of glyphs for that character
1968 const Length numberOfGlyphs = *( glyphsPerCharacterBuffer + characterLogicalOrderIndex );
1969 ++numberOfCharacters;
1972 if( 0u != numberOfGlyphs )
1974 // Get the first character/glyph of the group of glyphs.
1975 const CharacterIndex firstVisualCharacterIndex = 1u + visualIndex - numberOfCharacters;
1976 const CharacterIndex firstLogicalCharacterIndex = mLogicalModel->GetLogicalCharacterIndex( firstVisualCharacterIndex );
1977 const GlyphIndex firstLogicalGlyphIndex = *( charactersToGlyphBuffer + firstLogicalCharacterIndex );
1979 // Get the metrics for the group of glyphs.
1980 GlyphMetrics glyphMetrics;
1981 GetGlyphsMetrics( firstLogicalGlyphIndex,
1987 // Get the position of the first glyph.
1988 const Vector2& position = *( positionsBuffer + firstLogicalGlyphIndex );
1990 // Whether the glyph can be split, like Latin ligatures fi, ff or Arabic ﻻ.
1991 const bool isInterglyphIndex = ( numberOfCharacters > numberOfGlyphs ) && HasLigatureMustBreak( script );
1992 const Length numberOfBlocks = isInterglyphIndex ? numberOfCharacters : 1u;
1993 const float glyphAdvance = glyphMetrics.advance / static_cast<float>( numberOfBlocks );
1995 GlyphIndex index = 0u;
1996 for( ; !matched && ( index < numberOfBlocks ); ++index )
1998 // Find the mid-point of the area containing the glyph
1999 const float glyphCenter = -glyphMetrics.xBearing + position.x + ( static_cast<float>( index ) + 0.5f ) * glyphAdvance;
2001 if( visualX < glyphCenter )
2010 visualIndex = firstVisualCharacterIndex + index;
2014 numberOfCharacters = 0u;
2019 // Return the logical position of the cursor in characters.
2023 visualIndex = endCharacter;
2026 logicalIndex = mLogicalModel->GetLogicalCursorIndex( visualIndex );
2027 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "%p closest visualIndex %d logicalIndex %d\n", this, visualIndex, logicalIndex );
2029 DALI_ASSERT_DEBUG( ( logicalIndex <= mLogicalModel->mText.Count() && logicalIndex >= 0 ) && "GetClosestCursorIndex - Out of bounds index" );
2031 return logicalIndex;
2034 void Controller::Impl::GetCursorPosition( CharacterIndex logical,
2035 CursorInfo& cursorInfo )
2037 // TODO: Check for multiline with \n, etc...
2039 const Length numberOfCharacters = mLogicalModel->mText.Count();
2040 if( !IsShowingRealText() )
2042 // Do not want to use the place-holder text to set the cursor position.
2044 // Use the line's height of the font's family set to set the cursor's size.
2045 // If there is no font's family set, use the default font.
2046 // Use the current alignment to place the cursor at the beginning, center or end of the box.
2048 cursorInfo.lineOffset = 0.f;
2049 cursorInfo.lineHeight = GetDefaultFontLineHeight();
2050 cursorInfo.primaryCursorHeight = cursorInfo.lineHeight;
2052 switch( mLayoutEngine.GetHorizontalAlignment() )
2054 case LayoutEngine::HORIZONTAL_ALIGN_BEGIN:
2056 cursorInfo.primaryPosition.x = 0.f;
2059 case LayoutEngine::HORIZONTAL_ALIGN_CENTER:
2061 cursorInfo.primaryPosition.x = floorf( 0.5f * mVisualModel->mControlSize.width );
2064 case LayoutEngine::HORIZONTAL_ALIGN_END:
2066 cursorInfo.primaryPosition.x = mVisualModel->mControlSize.width - mEventData->mDecorator->GetCursorWidth();
2071 switch( mLayoutEngine.GetVerticalAlignment() )
2073 case LayoutEngine::VERTICAL_ALIGN_TOP:
2075 cursorInfo.primaryPosition.y = 0.f;
2078 case LayoutEngine::VERTICAL_ALIGN_CENTER:
2080 cursorInfo.primaryPosition.y = floorf( 0.5f * ( mVisualModel->mControlSize.height - cursorInfo.lineHeight ) );
2083 case LayoutEngine::VERTICAL_ALIGN_BOTTOM:
2085 cursorInfo.primaryPosition.y = mVisualModel->mControlSize.height - cursorInfo.lineHeight;
2090 // Nothing else to do.
2094 // Check if the logical position is the first or the last one of the text.
2095 const bool isFirstPosition = 0u == logical;
2096 const bool isLastPosition = numberOfCharacters == logical;
2098 // 'logical' is the logical 'cursor' index.
2099 // Get the next and current logical 'character' index.
2100 const CharacterIndex nextCharacterIndex = logical;
2101 const CharacterIndex characterIndex = isFirstPosition ? logical : logical - 1u;
2103 // Get the direction of the character and the next one.
2104 const CharacterDirection* const modelCharacterDirectionsBuffer = ( 0u != mLogicalModel->mCharacterDirections.Count() ) ? mLogicalModel->mCharacterDirections.Begin() : NULL;
2106 CharacterDirection isCurrentRightToLeft = false;
2107 CharacterDirection isNextRightToLeft = false;
2108 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
2110 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + characterIndex );
2111 isNextRightToLeft = *( modelCharacterDirectionsBuffer + nextCharacterIndex );
2114 // Get the line where the character is laid-out.
2115 const LineRun* const modelLines = mVisualModel->mLines.Begin();
2117 const LineIndex lineIndex = mVisualModel->GetLineOfCharacter( characterIndex );
2118 const LineRun& line = *( modelLines + lineIndex );
2120 // Get the paragraph's direction.
2121 const CharacterDirection isRightToLeftParagraph = line.direction;
2123 // Check whether there is an alternative position:
2125 cursorInfo.isSecondaryCursor = ( !isLastPosition && ( isCurrentRightToLeft != isNextRightToLeft ) ) ||
2126 ( isLastPosition && ( isRightToLeftParagraph != isCurrentRightToLeft ) );
2128 // Set the line offset and height.
2129 cursorInfo.lineOffset = 0.f;
2130 cursorInfo.lineHeight = line.ascender + -line.descender;
2132 // Calculate the primary cursor.
2134 CharacterIndex index = characterIndex;
2135 if( cursorInfo.isSecondaryCursor )
2137 // If there is a secondary position, the primary cursor may be in a different place than the logical index.
2139 if( isLastPosition )
2141 // The position of the cursor after the last character needs special
2142 // care depending on its direction and the direction of the paragraph.
2144 // Need to find the first character after the last character with the paragraph's direction.
2145 // i.e l0 l1 l2 r0 r1 should find r0.
2147 // TODO: check for more than one line!
2148 index = isRightToLeftParagraph ? line.characterRun.characterIndex : line.characterRun.characterIndex + line.characterRun.numberOfCharacters - 1u;
2149 index = mLogicalModel->GetLogicalCharacterIndex( index );
2153 index = ( isRightToLeftParagraph == isCurrentRightToLeft ) ? characterIndex : nextCharacterIndex;
2157 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
2158 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
2159 const Length* const charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
2160 const CharacterIndex* const glyphsToCharactersBuffer = mVisualModel->mGlyphsToCharacters.Begin();
2161 const Vector2* const glyphPositionsBuffer = mVisualModel->mGlyphPositions.Begin();
2162 const GlyphInfo* const glyphInfoBuffer = mVisualModel->mGlyphs.Begin();
2164 // Convert the cursor position into the glyph position.
2165 const GlyphIndex primaryGlyphIndex = *( charactersToGlyphBuffer + index );
2166 const Length primaryNumberOfGlyphs = *( glyphsPerCharacterBuffer + index );
2167 const Length primaryNumberOfCharacters = *( charactersPerGlyphBuffer + primaryGlyphIndex );
2169 // Get the metrics for the group of glyphs.
2170 GlyphMetrics glyphMetrics;
2171 GetGlyphsMetrics( primaryGlyphIndex,
2172 primaryNumberOfGlyphs,
2177 // Whether to add the glyph's advance to the cursor position.
2178 // i.e if the paragraph is left to right and the logical cursor is zero, the position is the position of the first glyph and the advance is not added,
2179 // if the logical cursor is one, the position is the position of the first glyph and the advance is added.
2180 // A 'truth table' was build and an online Karnaugh map tool was used to simplify the logic.
2201 // Where F -> isFirstPosition
2202 // L -> isLastPosition
2203 // C -> isCurrentRightToLeft
2204 // P -> isRightToLeftParagraph
2205 // A -> Whether to add the glyph's advance.
2207 const bool addGlyphAdvance = ( ( isLastPosition && !isRightToLeftParagraph ) ||
2208 ( isFirstPosition && isRightToLeftParagraph ) ||
2209 ( !isFirstPosition && !isLastPosition && !isCurrentRightToLeft ) );
2211 float glyphAdvance = addGlyphAdvance ? glyphMetrics.advance : 0.f;
2213 if( !isLastPosition &&
2214 ( primaryNumberOfCharacters > 1u ) )
2216 const CharacterIndex firstIndex = *( glyphsToCharactersBuffer + primaryGlyphIndex );
2218 bool isCurrentRightToLeft = false;
2219 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
2221 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + index );
2224 Length numberOfGlyphAdvance = ( isFirstPosition ? 0u : 1u ) + characterIndex - firstIndex;
2225 if( isCurrentRightToLeft )
2227 numberOfGlyphAdvance = primaryNumberOfCharacters - numberOfGlyphAdvance;
2230 glyphAdvance = static_cast<float>( numberOfGlyphAdvance ) * glyphMetrics.advance / static_cast<float>( primaryNumberOfCharacters );
2233 // Get the glyph position and x bearing.
2234 const Vector2& primaryPosition = *( glyphPositionsBuffer + primaryGlyphIndex );
2236 // Set the primary cursor's height.
2237 cursorInfo.primaryCursorHeight = cursorInfo.isSecondaryCursor ? 0.5f * glyphMetrics.fontHeight : glyphMetrics.fontHeight;
2239 // Set the primary cursor's position.
2240 cursorInfo.primaryPosition.x = -glyphMetrics.xBearing + primaryPosition.x + glyphAdvance;
2241 cursorInfo.primaryPosition.y = line.ascender - glyphMetrics.ascender;
2243 // Calculate the secondary cursor.
2245 if( cursorInfo.isSecondaryCursor )
2247 // Set the secondary cursor's height.
2248 cursorInfo.secondaryCursorHeight = 0.5f * glyphMetrics.fontHeight;
2250 CharacterIndex index = characterIndex;
2251 if( !isLastPosition )
2253 index = ( isRightToLeftParagraph == isCurrentRightToLeft ) ? nextCharacterIndex : characterIndex;
2256 const GlyphIndex secondaryGlyphIndex = *( charactersToGlyphBuffer + index );
2257 const Length secondaryNumberOfGlyphs = *( glyphsPerCharacterBuffer + index );
2259 const Vector2& secondaryPosition = *( glyphPositionsBuffer + secondaryGlyphIndex );
2261 GetGlyphsMetrics( secondaryGlyphIndex,
2262 secondaryNumberOfGlyphs,
2267 // Set the secondary cursor's position.
2268 cursorInfo.secondaryPosition.x = -glyphMetrics.xBearing + secondaryPosition.x + ( isCurrentRightToLeft ? 0.f : glyphMetrics.advance );
2269 cursorInfo.secondaryPosition.y = cursorInfo.lineHeight - cursorInfo.secondaryCursorHeight - line.descender - ( glyphMetrics.fontHeight - glyphMetrics.ascender );
2272 if( LayoutEngine::MULTI_LINE_BOX == mLayoutEngine.GetLayout() )
2274 // If the text is editable and multi-line, the cursor position after a white space shouldn't exceed the boundaries of the text control.
2276 // Note the white spaces laid-out at the end of the line might exceed the boundaries of the control.
2277 // The reason is a wrapped line must not start with a white space so they are laid-out at the end of the line.
2279 if( 0.f > cursorInfo.primaryPosition.x )
2281 cursorInfo.primaryPosition.x = 0.f;
2284 const float edgeWidth = mVisualModel->mControlSize.width - static_cast<float>( mEventData->mDecorator->GetCursorWidth() );
2285 if( cursorInfo.primaryPosition.x > edgeWidth )
2287 cursorInfo.primaryPosition.x = edgeWidth;
2292 CharacterIndex Controller::Impl::CalculateNewCursorIndex( CharacterIndex index ) const
2294 if( NULL == mEventData )
2296 // Nothing to do if there is no text input.
2300 CharacterIndex cursorIndex = mEventData->mPrimaryCursorPosition;
2302 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
2303 const Length* const charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
2305 GlyphIndex glyphIndex = *( charactersToGlyphBuffer + index );
2306 Length numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
2308 if( numberOfCharacters > 1u )
2310 const Script script = mLogicalModel->GetScript( index );
2311 if( HasLigatureMustBreak( script ) )
2313 // Prevents to jump the whole Latin ligatures like fi, ff, or Arabic ﻻ, ...
2314 numberOfCharacters = 1u;
2319 while( 0u == numberOfCharacters )
2322 numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
2326 if( index < mEventData->mPrimaryCursorPosition )
2328 cursorIndex -= numberOfCharacters;
2332 cursorIndex += numberOfCharacters;
2338 void Controller::Impl::UpdateCursorPosition( const CursorInfo& cursorInfo )
2340 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "-->Controller::UpdateCursorPosition %p\n", this );
2341 if( NULL == mEventData )
2343 // Nothing to do if there is no text input.
2344 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition no event data\n" );
2348 const Vector2 offset = mEventData->mScrollPosition + ( IsShowingRealText() ? mAlignmentOffset : Vector2::ZERO );
2349 const Vector2 cursorPosition = cursorInfo.primaryPosition + offset;
2351 // Sets the cursor position.
2352 mEventData->mDecorator->SetPosition( PRIMARY_CURSOR,
2355 cursorInfo.primaryCursorHeight,
2356 cursorInfo.lineHeight );
2357 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Primary cursor position: %f,%f\n", cursorPosition.x, cursorPosition.y );
2359 // Sets the grab handle position.
2360 mEventData->mDecorator->SetPosition( GRAB_HANDLE,
2362 cursorInfo.lineOffset + offset.y,
2363 cursorInfo.lineHeight );
2365 if( cursorInfo.isSecondaryCursor )
2367 mEventData->mDecorator->SetPosition( SECONDARY_CURSOR,
2368 cursorInfo.secondaryPosition.x + offset.x,
2369 cursorInfo.secondaryPosition.y + offset.y,
2370 cursorInfo.secondaryCursorHeight,
2371 cursorInfo.lineHeight );
2372 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Secondary cursor position: %f,%f\n", cursorInfo.secondaryPosition.x + offset.x, cursorInfo.secondaryPosition.y + offset.y );
2375 // Set which cursors are active according the state.
2376 if( EventData::IsEditingState( mEventData->mState ) || ( EventData::GRAB_HANDLE_PANNING == mEventData->mState ) )
2378 if( cursorInfo.isSecondaryCursor )
2380 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_BOTH );
2384 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
2389 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
2392 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition\n" );
2395 void Controller::Impl::UpdateSelectionHandle( HandleType handleType,
2396 const CursorInfo& cursorInfo )
2398 if( ( LEFT_SELECTION_HANDLE != handleType ) &&
2399 ( RIGHT_SELECTION_HANDLE != handleType ) )
2404 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
2405 const Vector2 cursorPosition = cursorInfo.primaryPosition + offset;
2407 // Sets the handle's position.
2408 mEventData->mDecorator->SetPosition( handleType,
2410 cursorInfo.lineOffset + offset.y,
2411 cursorInfo.lineHeight );
2413 // If selection handle at start of the text and other at end of the text then all text is selected.
2414 const CharacterIndex startOfSelection = std::min( mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition );
2415 const CharacterIndex endOfSelection = std::max ( mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition );
2416 mEventData->mAllTextSelected = ( startOfSelection == 0 ) && ( endOfSelection == mLogicalModel->mText.Count() );
2419 void Controller::Impl::ClampHorizontalScroll( const Vector2& actualSize )
2421 // Clamp between -space & 0 (and the text alignment).
2423 if( actualSize.width > mVisualModel->mControlSize.width )
2425 const float space = ( actualSize.width - mVisualModel->mControlSize.width ) + mAlignmentOffset.x;
2426 mEventData->mScrollPosition.x = ( mEventData->mScrollPosition.x < -space ) ? -space : mEventData->mScrollPosition.x;
2427 mEventData->mScrollPosition.x = ( mEventData->mScrollPosition.x > -mAlignmentOffset.x ) ? -mAlignmentOffset.x : mEventData->mScrollPosition.x;
2429 mEventData->mDecoratorUpdated = true;
2433 mEventData->mScrollPosition.x = 0.f;
2437 void Controller::Impl::ClampVerticalScroll( const Vector2& actualSize )
2439 // Clamp between -space & 0 (and the text alignment).
2440 if( actualSize.height > mVisualModel->mControlSize.height )
2442 const float space = ( actualSize.height - mVisualModel->mControlSize.height ) + mAlignmentOffset.y;
2443 mEventData->mScrollPosition.y = ( mEventData->mScrollPosition.y < -space ) ? -space : mEventData->mScrollPosition.y;
2444 mEventData->mScrollPosition.y = ( mEventData->mScrollPosition.y > -mAlignmentOffset.y ) ? -mAlignmentOffset.y : mEventData->mScrollPosition.y;
2446 mEventData->mDecoratorUpdated = true;
2450 mEventData->mScrollPosition.y = 0.f;
2454 void Controller::Impl::ScrollToMakePositionVisible( const Vector2& position )
2456 const float cursorWidth = mEventData->mDecorator ? mEventData->mDecorator->GetCursorWidth() : 0.f;
2458 // position is in actor's coords.
2459 const float positionEnd = position.x + cursorWidth;
2461 // Transform the position to decorator coords.
2462 const float alignment = IsShowingRealText() ? mAlignmentOffset.x : 0.f;
2463 const float offset = mEventData->mScrollPosition.x + alignment;
2464 const float decoratorPositionBegin = position.x + offset;
2465 const float decoratorPositionEnd = positionEnd + offset;
2467 if( decoratorPositionBegin < 0.f )
2469 mEventData->mScrollPosition.x = -position.x - alignment;
2471 else if( decoratorPositionEnd > mVisualModel->mControlSize.width )
2473 mEventData->mScrollPosition.x = mVisualModel->mControlSize.width - positionEnd - alignment;
2477 void Controller::Impl::ScrollTextToMatchCursor( const CursorInfo& cursorInfo )
2479 // Get the current cursor position in decorator coords.
2480 const Vector2& currentCursorPosition = mEventData->mDecorator->GetPosition( PRIMARY_CURSOR );
2482 // Calculate the offset to match the cursor position before the character was deleted.
2483 mEventData->mScrollPosition.x = currentCursorPosition.x - cursorInfo.primaryPosition.x - mAlignmentOffset.x;
2485 ClampHorizontalScroll( mVisualModel->GetLayoutSize() );
2487 // Makes the new cursor position visible if needed.
2488 ScrollToMakePositionVisible( cursorInfo.primaryPosition );
2491 void Controller::Impl::RequestRelayout()
2493 mControlInterface.RequestTextRelayout();
2498 } // namespace Toolkit