2 * Copyright (c) 2015 Samsung Electronics Co., Ltd.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
19 #include <dali-toolkit/internal/text/text-controller-impl.h>
22 #include <dali/public-api/adaptor-framework/key.h>
23 #include <dali/integration-api/debug.h>
26 #include <dali-toolkit/internal/text/bidirectional-support.h>
27 #include <dali-toolkit/internal/text/character-set-conversion.h>
28 #include <dali-toolkit/internal/text/color-segmentation.h>
29 #include <dali-toolkit/internal/text/glyph-metrics-helper.h>
30 #include <dali-toolkit/internal/text/multi-language-support.h>
31 #include <dali-toolkit/internal/text/segmentation.h>
32 #include <dali-toolkit/internal/text/shaper.h>
33 #include <dali-toolkit/internal/text/text-run-container.h>
38 #if defined(DEBUG_ENABLED)
39 Debug::Filter* gLogFilter = Debug::Filter::New(Debug::NoLogging, true, "LOG_TEXT_CONTROLS");
53 EventData::EventData( DecoratorPtr decorator )
54 : mDecorator( decorator ),
56 mPlaceholderTextActive(),
57 mPlaceholderTextInactive(),
58 mPlaceholderTextColor( 0.8f, 0.8f, 0.8f, 0.8f ),
62 mPrimaryCursorPosition( 0u ),
63 mLeftSelectionPosition( 0u ),
64 mRightSelectionPosition( 0u ),
65 mPreEditStartPosition( 0u ),
67 mIsShowingPlaceholderText( false ),
68 mPreEditFlag( false ),
69 mDecoratorUpdated( false ),
70 mCursorBlinkEnabled( true ),
71 mGrabHandleEnabled( true ),
72 mGrabHandlePopupEnabled( true ),
73 mSelectionEnabled( true ),
74 mHorizontalScrollingEnabled( true ),
75 mVerticalScrollingEnabled( false ),
76 mUpdateCursorPosition( false ),
77 mUpdateLeftSelectionPosition( false ),
78 mUpdateRightSelectionPosition( false ),
79 mScrollAfterUpdatePosition( false ),
80 mScrollAfterDelete( false ),
81 mAllTextSelected( false ),
82 mUpdateInputStyle( false )
84 mImfManager = ImfManager::Get();
87 EventData::~EventData()
90 bool Controller::Impl::ProcessInputEvents()
92 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "-->Controller::ProcessInputEvents\n" );
93 if( NULL == mEventData )
95 // Nothing to do if there is no text input.
96 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::ProcessInputEvents no event data\n" );
100 if( mEventData->mDecorator )
102 for( std::vector<Event>::iterator iter = mEventData->mEventQueue.begin();
103 iter != mEventData->mEventQueue.end();
108 case Event::CURSOR_KEY_EVENT:
110 OnCursorKeyEvent( *iter );
113 case Event::TAP_EVENT:
118 case Event::LONG_PRESS_EVENT:
120 OnLongPressEvent( *iter );
123 case Event::PAN_EVENT:
128 case Event::GRAB_HANDLE_EVENT:
129 case Event::LEFT_SELECTION_HANDLE_EVENT:
130 case Event::RIGHT_SELECTION_HANDLE_EVENT: // Fall through
132 OnHandleEvent( *iter );
137 OnSelectEvent( *iter );
140 case Event::SELECT_ALL:
149 // The cursor must also be repositioned after inserts into the model
150 if( mEventData->mUpdateCursorPosition )
152 // Updates the cursor position and scrolls the text to make it visible.
153 CursorInfo cursorInfo;
154 GetCursorPosition( mEventData->mPrimaryCursorPosition,
157 if( mEventData->mScrollAfterUpdatePosition )
159 ScrollToMakePositionVisible( cursorInfo.primaryPosition );
160 mEventData->mScrollAfterUpdatePosition = false;
162 else if( mEventData->mScrollAfterDelete )
164 ScrollTextToMatchCursor( cursorInfo );
165 mEventData->mScrollAfterDelete = false;
168 UpdateCursorPosition( cursorInfo );
170 mEventData->mDecoratorUpdated = true;
171 mEventData->mUpdateCursorPosition = false;
175 bool leftScroll = false;
176 bool rightScroll = false;
178 CursorInfo leftHandleInfo;
179 CursorInfo rightHandleInfo;
181 if( mEventData->mUpdateLeftSelectionPosition )
183 GetCursorPosition( mEventData->mLeftSelectionPosition,
186 if( mEventData->mScrollAfterUpdatePosition )
188 ScrollToMakePositionVisible( leftHandleInfo.primaryPosition );
193 if( mEventData->mUpdateRightSelectionPosition )
195 GetCursorPosition( mEventData->mRightSelectionPosition,
198 if( mEventData->mScrollAfterUpdatePosition )
200 ScrollToMakePositionVisible( rightHandleInfo.primaryPosition );
205 if( mEventData->mUpdateLeftSelectionPosition )
207 UpdateSelectionHandle( LEFT_SELECTION_HANDLE,
211 mEventData->mDecoratorUpdated = true;
214 if( mEventData->mUpdateRightSelectionPosition )
216 UpdateSelectionHandle( RIGHT_SELECTION_HANDLE,
220 mEventData->mDecoratorUpdated = true;
223 if( mEventData->mUpdateLeftSelectionPosition || mEventData->mUpdateRightSelectionPosition )
225 RepositionSelectionHandles();
227 mEventData->mUpdateLeftSelectionPosition = false;
228 mEventData->mUpdateRightSelectionPosition = false;
231 if( leftScroll || rightScroll )
233 mEventData->mScrollAfterUpdatePosition = false;
237 if( mEventData->mUpdateInputStyle )
239 // Set the default style first.
240 RetrieveDefaultInputStyle( mEventData->mInputStyle );
242 // Get the character index from the cursor index.
243 const CharacterIndex styleIndex = ( mEventData->mPrimaryCursorPosition > 0u ) ? mEventData->mPrimaryCursorPosition - 1u : 0u;
245 // Retrieve the style from the style runs stored in the logical model.
246 mLogicalModel->RetrieveStyle( styleIndex, mEventData->mInputStyle );
248 mEventData->mUpdateInputStyle = false;
251 mEventData->mEventQueue.clear();
253 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::ProcessInputEvents\n" );
255 const bool decoratorUpdated = mEventData->mDecoratorUpdated;
256 mEventData->mDecoratorUpdated = false;
258 return decoratorUpdated;
261 void Controller::Impl::CalculateTextUpdateIndices( Length& numberOfCharacters )
263 mTextUpdateInfo.mParagraphCharacterIndex = 0u;
264 mTextUpdateInfo.mStartGlyphIndex = 0u;
265 mTextUpdateInfo.mStartLineIndex = 0u;
266 numberOfCharacters = 0u;
268 const Length numberOfParagraphs = mLogicalModel->mParagraphInfo.Count();
269 if( 0u == numberOfParagraphs )
271 mTextUpdateInfo.mParagraphCharacterIndex = 0u;
272 numberOfCharacters = 0u;
274 mTextUpdateInfo.mRequestedNumberOfCharacters = mTextUpdateInfo.mNumberOfCharactersToAdd - mTextUpdateInfo.mNumberOfCharactersToRemove;
276 // Nothing else to do if there are no paragraphs.
280 // Find the paragraphs to be updated.
281 Vector<ParagraphRunIndex> paragraphsToBeUpdated;
282 if( mTextUpdateInfo.mCharacterIndex >= mTextUpdateInfo.mPreviousNumberOfCharacters )
284 // Text is being added at the end of the current text.
285 if( mTextUpdateInfo.mIsLastCharacterNewParagraph )
287 // Text is being added in a new paragraph after the last character of the text.
288 mTextUpdateInfo.mParagraphCharacterIndex = mTextUpdateInfo.mPreviousNumberOfCharacters;
289 numberOfCharacters = 0u;
290 mTextUpdateInfo.mRequestedNumberOfCharacters = mTextUpdateInfo.mNumberOfCharactersToAdd - mTextUpdateInfo.mNumberOfCharactersToRemove;
292 mTextUpdateInfo.mStartGlyphIndex = mVisualModel->mGlyphs.Count();
293 mTextUpdateInfo.mStartLineIndex = mVisualModel->mLines.Count() - 1u;
295 // Nothing else to do;
299 paragraphsToBeUpdated.PushBack( numberOfParagraphs - 1u );
303 CharacterIndex lastIndex = 0u;
304 if( mTextUpdateInfo.mFullRelayoutNeeded )
306 lastIndex = mTextUpdateInfo.mPreviousNumberOfCharacters;
310 lastIndex = ( mTextUpdateInfo.mNumberOfCharactersToRemove > 0u ) ? mTextUpdateInfo.mNumberOfCharactersToRemove : 1u;
312 mLogicalModel->FindParagraphs( mTextUpdateInfo.mCharacterIndex,
314 paragraphsToBeUpdated );
317 if( 0u != paragraphsToBeUpdated.Count() )
319 const ParagraphRunIndex firstParagraphIndex = *( paragraphsToBeUpdated.Begin() );
320 const ParagraphRun& firstParagraph = *( mLogicalModel->mParagraphInfo.Begin() + firstParagraphIndex );
321 mTextUpdateInfo.mParagraphCharacterIndex = firstParagraph.characterRun.characterIndex;
323 ParagraphRunIndex lastParagraphIndex = *( paragraphsToBeUpdated.End() - 1u );
324 const ParagraphRun& lastParagraph = *( mLogicalModel->mParagraphInfo.Begin() + lastParagraphIndex );
326 if( ( mTextUpdateInfo.mNumberOfCharactersToRemove > 0u ) && // Some character are removed.
327 ( lastParagraphIndex < numberOfParagraphs - 1u ) && // There is a next paragraph.
328 ( ( lastParagraph.characterRun.characterIndex + lastParagraph.characterRun.numberOfCharacters ) == // The last removed character is the new paragraph character.
329 ( mTextUpdateInfo.mCharacterIndex + mTextUpdateInfo.mNumberOfCharactersToRemove ) ) )
331 // The new paragraph character of the last updated paragraph has been removed so is going to be merged with the next one.
332 const ParagraphRun& lastParagraph = *( mLogicalModel->mParagraphInfo.Begin() + lastParagraphIndex + 1u );
334 numberOfCharacters = lastParagraph.characterRun.characterIndex + lastParagraph.characterRun.numberOfCharacters - mTextUpdateInfo.mParagraphCharacterIndex;
338 numberOfCharacters = lastParagraph.characterRun.characterIndex + lastParagraph.characterRun.numberOfCharacters - mTextUpdateInfo.mParagraphCharacterIndex;
342 mTextUpdateInfo.mRequestedNumberOfCharacters = numberOfCharacters + mTextUpdateInfo.mNumberOfCharactersToAdd - mTextUpdateInfo.mNumberOfCharactersToRemove;
343 mTextUpdateInfo.mStartGlyphIndex = *( mVisualModel->mCharactersToGlyph.Begin() + mTextUpdateInfo.mParagraphCharacterIndex );
346 void Controller::Impl::ClearFullModelData( OperationsMask operations )
348 if( GET_LINE_BREAKS & operations )
350 mLogicalModel->mLineBreakInfo.Clear();
351 mLogicalModel->mParagraphInfo.Clear();
354 if( GET_WORD_BREAKS & operations )
356 mLogicalModel->mLineBreakInfo.Clear();
359 if( GET_SCRIPTS & operations )
361 mLogicalModel->mScriptRuns.Clear();
364 if( VALIDATE_FONTS & operations )
366 mLogicalModel->mFontRuns.Clear();
369 if( 0u != mLogicalModel->mBidirectionalParagraphInfo.Count() )
371 if( BIDI_INFO & operations )
373 mLogicalModel->mBidirectionalParagraphInfo.Clear();
374 mLogicalModel->mCharacterDirections.Clear();
377 if( REORDER & operations )
379 // Free the allocated memory used to store the conversion table in the bidirectional line info run.
380 for( Vector<BidirectionalLineInfoRun>::Iterator it = mLogicalModel->mBidirectionalLineInfo.Begin(),
381 endIt = mLogicalModel->mBidirectionalLineInfo.End();
385 BidirectionalLineInfoRun& bidiLineInfo = *it;
387 free( bidiLineInfo.visualToLogicalMap );
388 bidiLineInfo.visualToLogicalMap = NULL;
390 mLogicalModel->mBidirectionalLineInfo.Clear();
394 if( SHAPE_TEXT & operations )
396 mVisualModel->mGlyphs.Clear();
397 mVisualModel->mGlyphsToCharacters.Clear();
398 mVisualModel->mCharactersToGlyph.Clear();
399 mVisualModel->mCharactersPerGlyph.Clear();
400 mVisualModel->mGlyphsPerCharacter.Clear();
401 mVisualModel->mGlyphPositions.Clear();
404 if( LAYOUT & operations )
406 mVisualModel->mLines.Clear();
409 if( COLOR & operations )
411 mVisualModel->mColorIndices.Clear();
415 void Controller::Impl::ClearCharacterModelData( CharacterIndex startIndex, CharacterIndex endIndex, OperationsMask operations )
417 const CharacterIndex endIndexPlusOne = endIndex + 1u;
419 if( GET_LINE_BREAKS & operations )
421 // Clear the line break info.
422 LineBreakInfo* lineBreakInfoBuffer = mLogicalModel->mLineBreakInfo.Begin();
424 mLogicalModel->mLineBreakInfo.Erase( lineBreakInfoBuffer + startIndex,
425 lineBreakInfoBuffer + endIndexPlusOne );
427 // Clear the paragraphs.
428 ClearCharacterRuns( startIndex,
430 mLogicalModel->mParagraphInfo );
433 if( GET_WORD_BREAKS & operations )
435 // Clear the word break info.
436 WordBreakInfo* wordBreakInfoBuffer = mLogicalModel->mWordBreakInfo.Begin();
438 mLogicalModel->mWordBreakInfo.Erase( wordBreakInfoBuffer + startIndex,
439 wordBreakInfoBuffer + endIndexPlusOne );
442 if( GET_SCRIPTS & operations )
444 // Clear the scripts.
445 ClearCharacterRuns( startIndex,
447 mLogicalModel->mScriptRuns );
450 if( VALIDATE_FONTS & operations )
453 ClearCharacterRuns( startIndex,
455 mLogicalModel->mFontRuns );
458 if( 0u != mLogicalModel->mBidirectionalParagraphInfo.Count() )
460 if( BIDI_INFO & operations )
462 // Clear the bidirectional paragraph info.
463 ClearCharacterRuns( startIndex,
465 mLogicalModel->mBidirectionalParagraphInfo );
467 // Clear the character's directions.
468 CharacterDirection* characterDirectionsBuffer = mLogicalModel->mCharacterDirections.Begin();
470 mLogicalModel->mCharacterDirections.Erase( characterDirectionsBuffer + startIndex,
471 characterDirectionsBuffer + endIndexPlusOne );
474 if( REORDER & operations )
476 uint32_t startRemoveIndex = mLogicalModel->mBidirectionalLineInfo.Count();
477 uint32_t endRemoveIndex = startRemoveIndex;
478 ClearCharacterRuns( startIndex,
480 mLogicalModel->mBidirectionalLineInfo,
484 BidirectionalLineInfoRun* bidirectionalLineInfoBuffer = mLogicalModel->mBidirectionalLineInfo.Begin();
486 // Free the allocated memory used to store the conversion table in the bidirectional line info run.
487 for( Vector<BidirectionalLineInfoRun>::Iterator it = bidirectionalLineInfoBuffer + startRemoveIndex,
488 endIt = bidirectionalLineInfoBuffer + endRemoveIndex;
492 BidirectionalLineInfoRun& bidiLineInfo = *it;
494 free( bidiLineInfo.visualToLogicalMap );
495 bidiLineInfo.visualToLogicalMap = NULL;
498 mLogicalModel->mBidirectionalLineInfo.Erase( bidirectionalLineInfoBuffer + startRemoveIndex,
499 bidirectionalLineInfoBuffer + endRemoveIndex );
504 void Controller::Impl::ClearGlyphModelData( CharacterIndex startIndex, CharacterIndex endIndex, OperationsMask operations )
506 const CharacterIndex endIndexPlusOne = endIndex + 1u;
507 const Length numberOfCharactersRemoved = endIndexPlusOne - startIndex;
509 // Convert the character index to glyph index before deleting the character to glyph and the glyphs per character buffers.
510 GlyphIndex* charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
511 Length* glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
513 const GlyphIndex endGlyphIndexPlusOne = *( charactersToGlyphBuffer + endIndex ) + *( glyphsPerCharacterBuffer + endIndex );
514 const Length numberOfGlyphsRemoved = endGlyphIndexPlusOne - mTextUpdateInfo.mStartGlyphIndex;
516 if( SHAPE_TEXT & operations )
518 // Update the character to glyph indices.
519 for( Vector<GlyphIndex>::Iterator it = charactersToGlyphBuffer + endIndexPlusOne,
520 endIt = charactersToGlyphBuffer + mVisualModel->mCharactersToGlyph.Count();
524 CharacterIndex& index = *it;
525 index -= numberOfGlyphsRemoved;
528 // Clear the character to glyph conversion table.
529 mVisualModel->mCharactersToGlyph.Erase( charactersToGlyphBuffer + startIndex,
530 charactersToGlyphBuffer + endIndexPlusOne );
532 // Clear the glyphs per character table.
533 mVisualModel->mGlyphsPerCharacter.Erase( glyphsPerCharacterBuffer + startIndex,
534 glyphsPerCharacterBuffer + endIndexPlusOne );
536 // Clear the glyphs buffer.
537 GlyphInfo* glyphsBuffer = mVisualModel->mGlyphs.Begin();
538 mVisualModel->mGlyphs.Erase( glyphsBuffer + mTextUpdateInfo.mStartGlyphIndex,
539 glyphsBuffer + endGlyphIndexPlusOne );
541 CharacterIndex* glyphsToCharactersBuffer = mVisualModel->mGlyphsToCharacters.Begin();
543 // Update the glyph to character indices.
544 for( Vector<CharacterIndex>::Iterator it = glyphsToCharactersBuffer + endGlyphIndexPlusOne,
545 endIt = glyphsToCharactersBuffer + mVisualModel->mGlyphsToCharacters.Count();
549 CharacterIndex& index = *it;
550 index -= numberOfCharactersRemoved;
553 // Clear the glyphs to characters buffer.
554 mVisualModel->mGlyphsToCharacters.Erase( glyphsToCharactersBuffer + mTextUpdateInfo.mStartGlyphIndex,
555 glyphsToCharactersBuffer + endGlyphIndexPlusOne );
557 // Clear the characters per glyph buffer.
558 Length* charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
559 mVisualModel->mCharactersPerGlyph.Erase( charactersPerGlyphBuffer + mTextUpdateInfo.mStartGlyphIndex,
560 charactersPerGlyphBuffer + endGlyphIndexPlusOne );
562 // Clear the positions buffer.
563 Vector2* positionsBuffer = mVisualModel->mGlyphPositions.Begin();
564 mVisualModel->mGlyphPositions.Erase( positionsBuffer + mTextUpdateInfo.mStartGlyphIndex,
565 positionsBuffer + endGlyphIndexPlusOne );
568 if( LAYOUT & operations )
571 uint32_t startRemoveIndex = mVisualModel->mLines.Count();
572 uint32_t endRemoveIndex = startRemoveIndex;
573 ClearCharacterRuns( startIndex,
575 mVisualModel->mLines,
579 // Will update the glyph runs.
580 startRemoveIndex = mVisualModel->mLines.Count();
581 endRemoveIndex = startRemoveIndex;
582 ClearGlyphRuns( mTextUpdateInfo.mStartGlyphIndex,
583 endGlyphIndexPlusOne - 1u,
584 mVisualModel->mLines,
588 // Set the line index from where to insert the new laid-out lines.
589 mTextUpdateInfo.mStartLineIndex = startRemoveIndex;
591 LineRun* linesBuffer = mVisualModel->mLines.Begin();
592 mVisualModel->mLines.Erase( linesBuffer + startRemoveIndex,
593 linesBuffer + endRemoveIndex );
596 if( COLOR & operations )
598 if( 0u != mVisualModel->mColorIndices.Count() )
600 ColorIndex* colorIndexBuffer = mVisualModel->mColorIndices.Begin();
601 mVisualModel->mColorIndices.Erase( colorIndexBuffer + mTextUpdateInfo.mStartGlyphIndex,
602 colorIndexBuffer + endGlyphIndexPlusOne );
607 void Controller::Impl::ClearModelData( CharacterIndex startIndex, CharacterIndex endIndex, OperationsMask operations )
609 if( mTextUpdateInfo.mClearAll ||
610 ( ( 0u == startIndex ) &&
611 ( mTextUpdateInfo.mPreviousNumberOfCharacters == endIndex + 1u ) ) )
613 ClearFullModelData( operations );
617 // Clear the model data related with characters.
618 ClearCharacterModelData( startIndex, endIndex, operations );
620 // Clear the model data related with glyphs.
621 ClearGlyphModelData( startIndex, endIndex, operations );
624 // The estimated number of lines. Used to avoid reallocations when layouting.
625 mTextUpdateInfo.mEstimatedNumberOfLines = std::max( mVisualModel->mLines.Count(), mLogicalModel->mParagraphInfo.Count() );
627 mVisualModel->ClearCaches();
630 bool Controller::Impl::UpdateModel( OperationsMask operationsRequired )
632 DALI_LOG_INFO( gLogFilter, Debug::General, "Controller::UpdateModel\n" );
634 // Calculate the operations to be done.
635 const OperationsMask operations = static_cast<OperationsMask>( mOperationsPending & operationsRequired );
637 if( NO_OPERATION == operations )
639 // Nothing to do if no operations are pending and required.
643 Vector<Character>& utf32Characters = mLogicalModel->mText;
645 const Length numberOfCharacters = utf32Characters.Count();
647 // Index to the first character of the first paragraph to be updated.
648 CharacterIndex startIndex = 0u;
649 // Number of characters of the paragraphs to be removed.
650 Length paragraphCharacters = 0u;
652 CalculateTextUpdateIndices( paragraphCharacters );
653 startIndex = mTextUpdateInfo.mParagraphCharacterIndex;
655 if( mTextUpdateInfo.mClearAll ||
656 ( 0u != paragraphCharacters ) )
658 ClearModelData( startIndex, startIndex + ( ( paragraphCharacters > 0u ) ? paragraphCharacters - 1u : 0u ), operationsRequired );
661 mTextUpdateInfo.mClearAll = false;
663 // Whether the model is updated.
664 bool updated = false;
666 Vector<LineBreakInfo>& lineBreakInfo = mLogicalModel->mLineBreakInfo;
667 const Length requestedNumberOfCharacters = mTextUpdateInfo.mRequestedNumberOfCharacters;
669 if( GET_LINE_BREAKS & operations )
671 // Retrieves the line break info. The line break info is used to split the text in 'paragraphs' to
672 // calculate the bidirectional info for each 'paragraph'.
673 // It's also used to layout the text (where it should be a new line) or to shape the text (text in different lines
674 // is not shaped together).
675 lineBreakInfo.Resize( numberOfCharacters, TextAbstraction::LINE_NO_BREAK );
677 SetLineBreakInfo( utf32Characters,
679 requestedNumberOfCharacters,
682 // Create the paragraph info.
683 mLogicalModel->CreateParagraphInfo( startIndex,
684 requestedNumberOfCharacters );
688 Vector<WordBreakInfo>& wordBreakInfo = mLogicalModel->mWordBreakInfo;
689 if( GET_WORD_BREAKS & operations )
691 // Retrieves the word break info. The word break info is used to layout the text (where to wrap the text in lines).
692 wordBreakInfo.Resize( numberOfCharacters, TextAbstraction::WORD_NO_BREAK );
694 SetWordBreakInfo( utf32Characters,
696 requestedNumberOfCharacters,
701 const bool getScripts = GET_SCRIPTS & operations;
702 const bool validateFonts = VALIDATE_FONTS & operations;
704 Vector<ScriptRun>& scripts = mLogicalModel->mScriptRuns;
705 Vector<FontRun>& validFonts = mLogicalModel->mFontRuns;
707 if( getScripts || validateFonts )
709 // Validates the fonts assigned by the application or assigns default ones.
710 // It makes sure all the characters are going to be rendered by the correct font.
711 MultilanguageSupport multilanguageSupport = MultilanguageSupport::Get();
715 // Retrieves the scripts used in the text.
716 multilanguageSupport.SetScripts( utf32Characters,
718 requestedNumberOfCharacters,
724 // Validate the fonts set through the mark-up string.
725 Vector<FontDescriptionRun>& fontDescriptionRuns = mLogicalModel->mFontDescriptionRuns;
727 // Get the default font id.
728 const FontId defaultFontId = ( NULL == mFontDefaults ) ? 0u : mFontDefaults->GetFontId( mFontClient );
730 // Validates the fonts. If there is a character with no assigned font it sets a default one.
731 // After this call, fonts are validated.
732 multilanguageSupport.ValidateFonts( utf32Characters,
737 requestedNumberOfCharacters,
743 Vector<Character> mirroredUtf32Characters;
744 bool textMirrored = false;
745 const Length numberOfParagraphs = mLogicalModel->mParagraphInfo.Count();
746 if( BIDI_INFO & operations )
748 Vector<BidirectionalParagraphInfoRun>& bidirectionalInfo = mLogicalModel->mBidirectionalParagraphInfo;
749 bidirectionalInfo.Reserve( numberOfParagraphs );
751 // Calculates the bidirectional info for the whole paragraph if it contains right to left scripts.
752 SetBidirectionalInfo( utf32Characters,
756 requestedNumberOfCharacters,
759 if( 0u != bidirectionalInfo.Count() )
761 // Only set the character directions if there is right to left characters.
762 Vector<CharacterDirection>& directions = mLogicalModel->mCharacterDirections;
763 GetCharactersDirection( bidirectionalInfo,
766 requestedNumberOfCharacters,
769 // This paragraph has right to left text. Some characters may need to be mirrored.
770 // TODO: consider if the mirrored string can be stored as well.
772 textMirrored = GetMirroredText( utf32Characters,
776 requestedNumberOfCharacters,
777 mirroredUtf32Characters );
781 // There is no right to left characters. Clear the directions vector.
782 mLogicalModel->mCharacterDirections.Clear();
787 Vector<GlyphInfo>& glyphs = mVisualModel->mGlyphs;
788 Vector<CharacterIndex>& glyphsToCharactersMap = mVisualModel->mGlyphsToCharacters;
789 Vector<Length>& charactersPerGlyph = mVisualModel->mCharactersPerGlyph;
790 Vector<GlyphIndex> newParagraphGlyphs;
791 newParagraphGlyphs.Reserve( numberOfParagraphs );
793 const Length currentNumberOfGlyphs = glyphs.Count();
794 if( SHAPE_TEXT & operations )
796 const Vector<Character>& textToShape = textMirrored ? mirroredUtf32Characters : utf32Characters;
798 ShapeText( textToShape,
803 mTextUpdateInfo.mStartGlyphIndex,
804 requestedNumberOfCharacters,
806 glyphsToCharactersMap,
808 newParagraphGlyphs );
810 // Create the 'number of glyphs' per character and the glyph to character conversion tables.
811 mVisualModel->CreateGlyphsPerCharacterTable( startIndex, mTextUpdateInfo.mStartGlyphIndex, requestedNumberOfCharacters );
812 mVisualModel->CreateCharacterToGlyphTable( startIndex, mTextUpdateInfo.mStartGlyphIndex, requestedNumberOfCharacters );
816 const Length numberOfGlyphs = glyphs.Count() - currentNumberOfGlyphs;
818 if( GET_GLYPH_METRICS & operations )
820 GlyphInfo* glyphsBuffer = glyphs.Begin();
821 mMetrics->GetGlyphMetrics( glyphsBuffer + mTextUpdateInfo.mStartGlyphIndex, numberOfGlyphs );
823 // Update the width and advance of all new paragraph characters.
824 for( Vector<GlyphIndex>::ConstIterator it = newParagraphGlyphs.Begin(), endIt = newParagraphGlyphs.End(); it != endIt; ++it )
826 const GlyphIndex index = *it;
827 GlyphInfo& glyph = *( glyphsBuffer + index );
829 glyph.xBearing = 0.f;
836 if( COLOR & operationsRequired )
838 // Set the color runs in glyphs.
839 SetColorSegmentationInfo( mLogicalModel->mColorRuns,
840 mVisualModel->mCharactersToGlyph,
841 mVisualModel->mGlyphsPerCharacter,
843 mTextUpdateInfo.mStartGlyphIndex,
844 requestedNumberOfCharacters,
845 mVisualModel->mColors,
846 mVisualModel->mColorIndices );
851 if( ( NULL != mEventData ) &&
852 mEventData->mPreEditFlag &&
853 ( 0u != mVisualModel->mCharactersToGlyph.Count() ) )
855 // Add the underline for the pre-edit text.
856 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
857 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
859 const GlyphIndex glyphStart = *( charactersToGlyphBuffer + mEventData->mPreEditStartPosition );
860 const CharacterIndex lastPreEditCharacter = mEventData->mPreEditStartPosition + ( ( mEventData->mPreEditLength > 0u ) ? mEventData->mPreEditLength - 1u : 0u );
861 const Length numberOfGlyphsLastCharacter = *( glyphsPerCharacterBuffer + lastPreEditCharacter );
862 const GlyphIndex glyphEnd = *( charactersToGlyphBuffer + lastPreEditCharacter ) + ( numberOfGlyphsLastCharacter > 1u ? numberOfGlyphsLastCharacter - 1u : 0u );
864 GlyphRun underlineRun;
865 underlineRun.glyphIndex = glyphStart;
866 underlineRun.numberOfGlyphs = 1u + glyphEnd - glyphStart;
868 // TODO: At the moment the underline runs are only for pre-edit.
869 mVisualModel->mUnderlineRuns.PushBack( underlineRun );
872 // The estimated number of lines. Used to avoid reallocations when layouting.
873 mTextUpdateInfo.mEstimatedNumberOfLines = std::max( mVisualModel->mLines.Count(), mLogicalModel->mParagraphInfo.Count() );
875 // Set the previous number of characters for the next time the text is updated.
876 mTextUpdateInfo.mPreviousNumberOfCharacters = numberOfCharacters;
881 void Controller::Impl::RetrieveDefaultInputStyle( InputStyle& inputStyle )
883 // Sets the default text's color.
884 inputStyle.textColor = mTextColor;
885 inputStyle.isDefaultColor = true;
887 inputStyle.familyName.clear();
888 inputStyle.weight = TextAbstraction::FontWeight::NORMAL;
889 inputStyle.width = TextAbstraction::FontWidth::NORMAL;
890 inputStyle.slant = TextAbstraction::FontSlant::NORMAL;
891 inputStyle.size = 0.f;
893 inputStyle.familyDefined = false;
894 inputStyle.weightDefined = false;
895 inputStyle.widthDefined = false;
896 inputStyle.slantDefined = false;
897 inputStyle.sizeDefined = false;
899 // Sets the default font's family name, weight, width, slant and size.
902 if( mFontDefaults->familyDefined )
904 inputStyle.familyName = mFontDefaults->mFontDescription.family;
905 inputStyle.familyDefined = true;
908 if( mFontDefaults->weightDefined )
910 inputStyle.weight = mFontDefaults->mFontDescription.weight;
911 inputStyle.weightDefined = true;
914 if( mFontDefaults->widthDefined )
916 inputStyle.width = mFontDefaults->mFontDescription.width;
917 inputStyle.widthDefined = true;
920 if( mFontDefaults->slantDefined )
922 inputStyle.slant = mFontDefaults->mFontDescription.slant;
923 inputStyle.slantDefined = true;
926 if( mFontDefaults->sizeDefined )
928 inputStyle.size = mFontDefaults->mDefaultPointSize;
929 inputStyle.sizeDefined = true;
934 float Controller::Impl::GetDefaultFontLineHeight()
936 FontId defaultFontId = 0u;
937 if( NULL == mFontDefaults )
939 TextAbstraction::FontDescription fontDescription;
940 defaultFontId = mFontClient.GetFontId( fontDescription );
944 defaultFontId = mFontDefaults->GetFontId( mFontClient );
947 Text::FontMetrics fontMetrics;
948 mMetrics->GetFontMetrics( defaultFontId, fontMetrics );
950 return( fontMetrics.ascender - fontMetrics.descender );
953 void Controller::Impl::OnCursorKeyEvent( const Event& event )
955 if( NULL == mEventData )
957 // Nothing to do if there is no text input.
961 int keyCode = event.p1.mInt;
963 if( Dali::DALI_KEY_CURSOR_LEFT == keyCode )
965 if( mEventData->mPrimaryCursorPosition > 0u )
967 mEventData->mPrimaryCursorPosition = CalculateNewCursorIndex( mEventData->mPrimaryCursorPosition - 1u );
970 else if( Dali::DALI_KEY_CURSOR_RIGHT == keyCode )
972 if( mLogicalModel->mText.Count() > mEventData->mPrimaryCursorPosition )
974 mEventData->mPrimaryCursorPosition = CalculateNewCursorIndex( mEventData->mPrimaryCursorPosition );
977 else if( Dali::DALI_KEY_CURSOR_UP == keyCode )
981 else if( Dali::DALI_KEY_CURSOR_DOWN == keyCode )
986 mEventData->mUpdateCursorPosition = true;
987 mEventData->mUpdateInputStyle = true;
988 mEventData->mScrollAfterUpdatePosition = true;
991 void Controller::Impl::OnTapEvent( const Event& event )
993 if( NULL != mEventData )
995 const unsigned int tapCount = event.p1.mUint;
999 if( IsShowingRealText() )
1001 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
1002 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
1004 mEventData->mPrimaryCursorPosition = GetClosestCursorIndex( xPosition,
1007 // When the cursor position is changing, delay cursor blinking
1008 mEventData->mDecorator->DelayCursorBlink();
1012 mEventData->mPrimaryCursorPosition = 0u;
1015 mEventData->mUpdateCursorPosition = true;
1016 mEventData->mScrollAfterUpdatePosition = true;
1017 mEventData->mUpdateInputStyle = true;
1019 // Notify the cursor position to the imf manager.
1020 if( mEventData->mImfManager )
1022 mEventData->mImfManager.SetCursorPosition( mEventData->mPrimaryCursorPosition );
1023 mEventData->mImfManager.NotifyCursorPosition();
1029 void Controller::Impl::OnPanEvent( const Event& event )
1031 if( NULL == mEventData )
1033 // Nothing to do if there is no text input.
1037 int state = event.p1.mInt;
1039 if( Gesture::Started == state ||
1040 Gesture::Continuing == state )
1042 const Vector2& actualSize = mVisualModel->GetLayoutSize();
1043 const Vector2 currentScroll = mEventData->mScrollPosition;
1045 if( mEventData->mHorizontalScrollingEnabled )
1047 const float displacementX = event.p2.mFloat;
1048 mEventData->mScrollPosition.x += displacementX;
1050 ClampHorizontalScroll( actualSize );
1053 if( mEventData->mVerticalScrollingEnabled )
1055 const float displacementY = event.p3.mFloat;
1056 mEventData->mScrollPosition.y += displacementY;
1058 ClampVerticalScroll( actualSize );
1061 if( mEventData->mDecorator )
1063 mEventData->mDecorator->UpdatePositions( mEventData->mScrollPosition - currentScroll );
1068 void Controller::Impl::OnLongPressEvent( const Event& event )
1070 DALI_LOG_INFO( gLogFilter, Debug::General, "Controller::OnLongPressEvent\n" );
1072 if( EventData::EDITING == mEventData->mState )
1074 ChangeState ( EventData::EDITING_WITH_POPUP );
1075 mEventData->mDecoratorUpdated = true;
1079 void Controller::Impl::OnHandleEvent( const Event& event )
1081 if( NULL == mEventData )
1083 // Nothing to do if there is no text input.
1087 const unsigned int state = event.p1.mUint;
1088 const bool handleStopScrolling = ( HANDLE_STOP_SCROLLING == state );
1090 if( HANDLE_PRESSED == state )
1092 // The event.p2 and event.p3 are in decorator coords. Need to transforms to text coords.
1093 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
1094 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
1096 const CharacterIndex handleNewPosition = GetClosestCursorIndex( xPosition, yPosition );
1098 if( Event::GRAB_HANDLE_EVENT == event.type )
1100 ChangeState ( EventData::GRAB_HANDLE_PANNING );
1102 if( handleNewPosition != mEventData->mPrimaryCursorPosition )
1104 mEventData->mPrimaryCursorPosition = handleNewPosition;
1105 mEventData->mUpdateCursorPosition = true;
1108 else if( Event::LEFT_SELECTION_HANDLE_EVENT == event.type )
1110 ChangeState ( EventData::SELECTION_HANDLE_PANNING );
1112 if( ( handleNewPosition != mEventData->mLeftSelectionPosition ) &&
1113 ( handleNewPosition != mEventData->mRightSelectionPosition ) )
1115 mEventData->mLeftSelectionPosition = handleNewPosition;
1117 mEventData->mUpdateLeftSelectionPosition = true;
1120 else if( Event::RIGHT_SELECTION_HANDLE_EVENT == event.type )
1122 ChangeState ( EventData::SELECTION_HANDLE_PANNING );
1124 if( ( handleNewPosition != mEventData->mRightSelectionPosition ) &&
1125 ( handleNewPosition != mEventData->mLeftSelectionPosition ) )
1127 mEventData->mRightSelectionPosition = handleNewPosition;
1129 mEventData->mUpdateRightSelectionPosition = true;
1132 } // end ( HANDLE_PRESSED == state )
1133 else if( ( HANDLE_RELEASED == state ) ||
1134 handleStopScrolling )
1136 CharacterIndex handlePosition = 0u;
1137 if( handleStopScrolling )
1139 // The event.p2 and event.p3 are in decorator coords. Need to transforms to text coords.
1140 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
1141 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
1143 handlePosition = GetClosestCursorIndex( xPosition, yPosition );
1146 if( Event::GRAB_HANDLE_EVENT == event.type )
1148 mEventData->mUpdateCursorPosition = true;
1149 mEventData->mUpdateInputStyle = true;
1151 if( !IsClipboardEmpty() )
1153 ChangeState( EventData::EDITING_WITH_PASTE_POPUP ); // Moving grabhandle will show Paste Popup
1156 if( handleStopScrolling )
1158 mEventData->mScrollAfterUpdatePosition = mEventData->mPrimaryCursorPosition != handlePosition;
1159 mEventData->mPrimaryCursorPosition = handlePosition;
1162 else if( Event::LEFT_SELECTION_HANDLE_EVENT == event.type )
1164 ChangeState( EventData::SELECTING );
1166 if( handleStopScrolling )
1168 mEventData->mUpdateLeftSelectionPosition = ( mEventData->mRightSelectionPosition != handlePosition );
1169 mEventData->mScrollAfterUpdatePosition = mEventData->mUpdateLeftSelectionPosition;
1171 if( mEventData->mUpdateLeftSelectionPosition )
1173 mEventData->mLeftSelectionPosition = handlePosition;
1177 else if( Event::RIGHT_SELECTION_HANDLE_EVENT == event.type )
1179 ChangeState( EventData::SELECTING );
1181 if( handleStopScrolling )
1183 mEventData->mUpdateRightSelectionPosition = ( mEventData->mLeftSelectionPosition != handlePosition );
1184 mEventData->mScrollAfterUpdatePosition = mEventData->mUpdateRightSelectionPosition;
1185 if( mEventData->mUpdateRightSelectionPosition )
1187 mEventData->mRightSelectionPosition = handlePosition;
1192 mEventData->mDecoratorUpdated = true;
1193 } // end ( ( HANDLE_RELEASED == state ) || ( HANDLE_STOP_SCROLLING == state ) )
1194 else if( HANDLE_SCROLLING == state )
1196 const float xSpeed = event.p2.mFloat;
1197 const Vector2& actualSize = mVisualModel->GetLayoutSize();
1198 const Vector2 currentScrollPosition = mEventData->mScrollPosition;
1200 mEventData->mScrollPosition.x += xSpeed;
1202 ClampHorizontalScroll( actualSize );
1204 bool endOfScroll = false;
1205 if( Vector2::ZERO == ( currentScrollPosition - mEventData->mScrollPosition ) )
1207 // Notify the decorator there is no more text to scroll.
1208 // The decorator won't send more scroll events.
1209 mEventData->mDecorator->NotifyEndOfScroll();
1210 // Still need to set the position of the handle.
1214 // Set the position of the handle.
1215 const bool scrollRightDirection = xSpeed > 0.f;
1216 const bool leftSelectionHandleEvent = Event::LEFT_SELECTION_HANDLE_EVENT == event.type;
1217 const bool rightSelectionHandleEvent = Event::RIGHT_SELECTION_HANDLE_EVENT == event.type;
1219 if( Event::GRAB_HANDLE_EVENT == event.type )
1221 ChangeState( EventData::GRAB_HANDLE_PANNING );
1223 Vector2 position = mEventData->mDecorator->GetPosition( GRAB_HANDLE );
1225 // Position the grag handle close to either the left or right edge.
1226 position.x = scrollRightDirection ? 0.f : mVisualModel->mControlSize.width;
1228 // Get the new handle position.
1229 // The grab handle's position is in decorator coords. Need to transforms to text coords.
1230 const CharacterIndex handlePosition = GetClosestCursorIndex( position.x - mEventData->mScrollPosition.x - mAlignmentOffset.x,
1231 position.y - mEventData->mScrollPosition.y - mAlignmentOffset.y );
1233 mEventData->mUpdateCursorPosition = mEventData->mPrimaryCursorPosition != handlePosition;
1234 mEventData->mScrollAfterUpdatePosition = mEventData->mUpdateCursorPosition;
1235 mEventData->mPrimaryCursorPosition = handlePosition;
1236 mEventData->mUpdateInputStyle = mEventData->mUpdateCursorPosition;
1238 else if( leftSelectionHandleEvent || rightSelectionHandleEvent )
1240 // TODO: This is recalculating the selection box every time the text is scrolled with the selection handles.
1241 // Think if something can be done to save power.
1243 ChangeState( EventData::SELECTION_HANDLE_PANNING );
1245 Vector2 position = mEventData->mDecorator->GetPosition( leftSelectionHandleEvent ? Text::LEFT_SELECTION_HANDLE : Text::RIGHT_SELECTION_HANDLE );
1247 // Position the selection handle close to either the left or right edge.
1248 position.x = scrollRightDirection ? 0.f : mVisualModel->mControlSize.width;
1250 // Get the new handle position.
1251 // The selection handle's position is in decorator coords. Need to transforms to text coords.
1252 const CharacterIndex handlePosition = GetClosestCursorIndex( position.x - mEventData->mScrollPosition.x - mAlignmentOffset.x,
1253 position.y - mEventData->mScrollPosition.y - mAlignmentOffset.y );
1255 if( leftSelectionHandleEvent )
1257 const bool differentHandles = ( mEventData->mLeftSelectionPosition != handlePosition ) && ( mEventData->mRightSelectionPosition != handlePosition );
1258 mEventData->mUpdateLeftSelectionPosition = endOfScroll || differentHandles;
1259 if( differentHandles )
1261 mEventData->mLeftSelectionPosition = handlePosition;
1266 const bool differentHandles = ( mEventData->mRightSelectionPosition != handlePosition ) && ( mEventData->mLeftSelectionPosition != handlePosition );
1267 mEventData->mUpdateRightSelectionPosition = endOfScroll || differentHandles;
1268 if( differentHandles )
1270 mEventData->mRightSelectionPosition = handlePosition;
1274 if( mEventData->mUpdateLeftSelectionPosition || mEventData->mUpdateRightSelectionPosition )
1276 RepositionSelectionHandles();
1278 mEventData->mScrollAfterUpdatePosition = true;
1281 mEventData->mDecoratorUpdated = true;
1282 } // end ( HANDLE_SCROLLING == state )
1285 void Controller::Impl::OnSelectEvent( const Event& event )
1287 if( NULL == mEventData )
1289 // Nothing to do if there is no text.
1293 if( mEventData->mSelectionEnabled )
1295 // The event.p2 and event.p3 are in decorator coords. Need to transforms to text coords.
1296 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
1297 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
1299 // Calculates the logical position from the x,y coords.
1300 RepositionSelectionHandles( xPosition,
1303 mEventData->mUpdateLeftSelectionPosition = true;
1304 mEventData->mUpdateRightSelectionPosition = true;
1306 mEventData->mScrollAfterUpdatePosition = ( mEventData->mLeftSelectionPosition != mEventData->mRightSelectionPosition );
1310 void Controller::Impl::OnSelectAllEvent()
1312 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "OnSelectAllEvent mEventData->mSelectionEnabled%s \n", mEventData->mSelectionEnabled?"true":"false");
1314 if( NULL == mEventData )
1316 // Nothing to do if there is no text.
1320 if( mEventData->mSelectionEnabled )
1322 mEventData->mLeftSelectionPosition = 0u;
1323 mEventData->mRightSelectionPosition = mLogicalModel->mText.Count();
1325 mEventData->mScrollAfterUpdatePosition = true;
1326 mEventData->mUpdateLeftSelectionPosition = true;
1327 mEventData->mUpdateRightSelectionPosition = true;
1331 void Controller::Impl::RetrieveSelection( std::string& selectedText, bool deleteAfterRetrieval )
1333 if( mEventData->mLeftSelectionPosition == mEventData->mRightSelectionPosition )
1335 // Nothing to select if handles are in the same place.
1336 selectedText.clear();
1340 const bool handlesCrossed = mEventData->mLeftSelectionPosition > mEventData->mRightSelectionPosition;
1342 //Get start and end position of selection
1343 const CharacterIndex startOfSelectedText = handlesCrossed ? mEventData->mRightSelectionPosition : mEventData->mLeftSelectionPosition;
1344 const Length lengthOfSelectedText = ( handlesCrossed ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition ) - startOfSelectedText;
1346 Vector<Character>& utf32Characters = mLogicalModel->mText;
1347 const Length numberOfCharacters = utf32Characters.Count();
1349 // Validate the start and end selection points
1350 if( ( startOfSelectedText + lengthOfSelectedText ) <= numberOfCharacters )
1352 //Get text as a UTF8 string
1353 Utf32ToUtf8( &utf32Characters[startOfSelectedText], lengthOfSelectedText, selectedText );
1355 if( deleteAfterRetrieval ) // Only delete text if copied successfully
1357 // Set as input style the style of the first deleted character.
1358 mLogicalModel->RetrieveStyle( startOfSelectedText, mEventData->mInputStyle );
1360 mLogicalModel->UpdateTextStyleRuns( startOfSelectedText, -static_cast<int>( lengthOfSelectedText ) );
1362 // Mark the paragraphs to be updated.
1363 mTextUpdateInfo.mCharacterIndex = startOfSelectedText;
1364 mTextUpdateInfo.mNumberOfCharactersToRemove = lengthOfSelectedText;
1366 // Delete text between handles
1367 Vector<Character>::Iterator first = utf32Characters.Begin() + startOfSelectedText;
1368 Vector<Character>::Iterator last = first + lengthOfSelectedText;
1369 utf32Characters.Erase( first, last );
1371 // Scroll after delete.
1372 mEventData->mPrimaryCursorPosition = handlesCrossed ? mEventData->mRightSelectionPosition : mEventData->mLeftSelectionPosition;
1373 mEventData->mScrollAfterDelete = true;
1375 // Udpade the cursor position and the decorator.
1376 // Scroll after the position is updated if is not scrolling after delete.
1377 mEventData->mUpdateCursorPosition = true;
1378 mEventData->mScrollAfterUpdatePosition = !mEventData->mScrollAfterDelete;
1379 mEventData->mDecoratorUpdated = true;
1383 void Controller::Impl::ShowClipboard()
1387 mClipboard.ShowClipboard();
1391 void Controller::Impl::HideClipboard()
1395 mClipboard.HideClipboard();
1399 bool Controller::Impl::CopyStringToClipboard( std::string& source )
1401 //Send string to clipboard
1402 return ( mClipboard && mClipboard.SetItem( source ) );
1405 void Controller::Impl::SendSelectionToClipboard( bool deleteAfterSending )
1407 std::string selectedText;
1408 RetrieveSelection( selectedText, deleteAfterSending );
1409 CopyStringToClipboard( selectedText );
1410 ChangeState( EventData::EDITING );
1413 void Controller::Impl::GetTextFromClipboard( unsigned int itemIndex, std::string& retrievedString )
1417 retrievedString = mClipboard.GetItem( itemIndex );
1421 void Controller::Impl::RepositionSelectionHandles()
1423 CharacterIndex selectionStart = mEventData->mLeftSelectionPosition;
1424 CharacterIndex selectionEnd = mEventData->mRightSelectionPosition;
1426 if( selectionStart == selectionEnd )
1428 // Nothing to select if handles are in the same place.
1432 mEventData->mDecorator->ClearHighlights();
1434 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
1435 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
1436 const GlyphInfo* const glyphsBuffer = mVisualModel->mGlyphs.Begin();
1437 const Vector2* const positionsBuffer = mVisualModel->mGlyphPositions.Begin();
1438 const Length* const charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
1439 const CharacterIndex* const glyphToCharacterBuffer = mVisualModel->mGlyphsToCharacters.Begin();
1440 const CharacterDirection* const modelCharacterDirectionsBuffer = ( 0u != mLogicalModel->mCharacterDirections.Count() ) ? mLogicalModel->mCharacterDirections.Begin() : NULL;
1442 // TODO: Better algorithm to create the highlight box.
1443 // TODO: Multi-line.
1445 // Get the height of the line.
1446 const Vector<LineRun>& lines = mVisualModel->mLines;
1447 const LineRun& firstLine = *lines.Begin();
1448 const float height = firstLine.ascender + -firstLine.descender;
1450 const bool isLastCharacter = selectionEnd >= mLogicalModel->mText.Count();
1451 const bool startDirection = ( ( NULL == modelCharacterDirectionsBuffer ) ? false : *( modelCharacterDirectionsBuffer + selectionStart ) );
1452 const bool endDirection = ( ( NULL == modelCharacterDirectionsBuffer ) ? false : *( modelCharacterDirectionsBuffer + ( selectionEnd - ( isLastCharacter ? 1u : 0u ) ) ) );
1454 // Swap the indices if the start is greater than the end.
1455 const bool indicesSwapped = selectionStart > selectionEnd;
1457 // Tell the decorator to flip the selection handles if needed.
1458 mEventData->mDecorator->SetSelectionHandleFlipState( indicesSwapped, startDirection, endDirection );
1460 if( indicesSwapped )
1462 std::swap( selectionStart, selectionEnd );
1465 // Get the indices to the first and last selected glyphs.
1466 const CharacterIndex selectionEndMinusOne = selectionEnd - 1u;
1467 const GlyphIndex glyphStart = *( charactersToGlyphBuffer + selectionStart );
1468 const Length numberOfGlyphs = *( glyphsPerCharacterBuffer + selectionEndMinusOne );
1469 const GlyphIndex glyphEnd = *( charactersToGlyphBuffer + selectionEndMinusOne ) + ( ( numberOfGlyphs > 0 ) ? numberOfGlyphs - 1u : 0u );
1471 // Check if the first glyph is a ligature that must be broken like Latin ff, fi, or Arabic ﻻ, etc which needs special code.
1472 const Length numberOfCharactersStart = *( charactersPerGlyphBuffer + glyphStart );
1473 bool splitStartGlyph = ( numberOfCharactersStart > 1u ) && HasLigatureMustBreak( mLogicalModel->GetScript( selectionStart ) );
1475 // Check if the last glyph is a ligature that must be broken like Latin ff, fi, or Arabic ﻻ, etc which needs special code.
1476 const Length numberOfCharactersEnd = *( charactersPerGlyphBuffer + glyphEnd );
1477 bool splitEndGlyph = ( glyphStart != glyphEnd ) && ( numberOfCharactersEnd > 1u ) && HasLigatureMustBreak( mLogicalModel->GetScript( selectionEndMinusOne ) );
1479 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
1481 // Traverse the glyphs.
1482 for( GlyphIndex index = glyphStart; index <= glyphEnd; ++index )
1484 const GlyphInfo& glyph = *( glyphsBuffer + index );
1485 const Vector2& position = *( positionsBuffer + index );
1487 if( splitStartGlyph )
1489 // 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.
1491 const float glyphAdvance = glyph.advance / static_cast<float>( numberOfCharactersStart );
1492 const CharacterIndex interGlyphIndex = selectionStart - *( glyphToCharacterBuffer + glyphStart );
1493 // Get the direction of the character.
1494 CharacterDirection isCurrentRightToLeft = false;
1495 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
1497 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + selectionStart );
1500 // The end point could be in the middle of the ligature.
1501 // Calculate the number of characters selected.
1502 const Length numberOfCharacters = ( glyphStart == glyphEnd ) ? ( selectionEnd - selectionStart ) : ( numberOfCharactersStart - interGlyphIndex );
1504 const float xPosition = position.x - glyph.xBearing + offset.x + glyphAdvance * static_cast<float>( isCurrentRightToLeft ? ( numberOfCharactersStart - interGlyphIndex - numberOfCharacters ) : interGlyphIndex );
1506 mEventData->mDecorator->AddHighlight( xPosition,
1508 xPosition + static_cast<float>( numberOfCharacters ) * glyphAdvance,
1509 offset.y + height );
1511 splitStartGlyph = false;
1515 if( splitEndGlyph && ( index == glyphEnd ) )
1517 // 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.
1519 const float glyphAdvance = glyph.advance / static_cast<float>( numberOfCharactersEnd );
1520 const CharacterIndex interGlyphIndex = selectionEnd - *( glyphToCharacterBuffer + glyphEnd );
1521 // Get the direction of the character.
1522 CharacterDirection isCurrentRightToLeft = false;
1523 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
1525 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + selectionEnd );
1528 const Length numberOfCharacters = numberOfCharactersEnd - interGlyphIndex;
1530 const float xPosition = position.x - glyph.xBearing + offset.x + ( isCurrentRightToLeft ? ( glyphAdvance * static_cast<float>( numberOfCharacters ) ) : 0.f );
1531 mEventData->mDecorator->AddHighlight( xPosition,
1533 xPosition + static_cast<float>( interGlyphIndex ) * glyphAdvance,
1534 offset.y + height );
1536 splitEndGlyph = false;
1540 const float xPosition = position.x - glyph.xBearing + offset.x;
1541 mEventData->mDecorator->AddHighlight( xPosition,
1543 xPosition + glyph.advance,
1544 offset.y + height );
1547 CursorInfo primaryCursorInfo;
1548 GetCursorPosition( mEventData->mLeftSelectionPosition,
1549 primaryCursorInfo );
1551 CursorInfo secondaryCursorInfo;
1552 GetCursorPosition( mEventData->mRightSelectionPosition,
1553 secondaryCursorInfo );
1555 const Vector2 primaryPosition = primaryCursorInfo.primaryPosition + offset;
1556 const Vector2 secondaryPosition = secondaryCursorInfo.primaryPosition + offset;
1558 mEventData->mDecorator->SetPosition( LEFT_SELECTION_HANDLE,
1560 primaryCursorInfo.lineOffset + offset.y,
1561 primaryCursorInfo.lineHeight );
1563 mEventData->mDecorator->SetPosition( RIGHT_SELECTION_HANDLE,
1564 secondaryPosition.x,
1565 secondaryCursorInfo.lineOffset + offset.y,
1566 secondaryCursorInfo.lineHeight );
1568 // Cursor to be positioned at end of selection so if selection interrupted and edit mode restarted the cursor will be at end of selection
1569 mEventData->mPrimaryCursorPosition = ( indicesSwapped ) ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition;
1571 // Set the flag to update the decorator.
1572 mEventData->mDecoratorUpdated = true;
1575 void Controller::Impl::RepositionSelectionHandles( float visualX, float visualY )
1577 if( NULL == mEventData )
1579 // Nothing to do if there is no text input.
1583 if( IsShowingPlaceholderText() )
1585 // Nothing to do if there is the place-holder text.
1589 const Length numberOfGlyphs = mVisualModel->mGlyphs.Count();
1590 const Length numberOfLines = mVisualModel->mLines.Count();
1591 if( ( 0 == numberOfGlyphs ) ||
1592 ( 0 == numberOfLines ) )
1594 // Nothing to do if there is no text.
1598 // Find which word was selected
1599 CharacterIndex selectionStart( 0 );
1600 CharacterIndex selectionEnd( 0 );
1601 FindSelectionIndices( visualX, visualY, selectionStart, selectionEnd );
1602 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "%p selectionStart %d selectionEnd %d\n", this, selectionStart, selectionEnd );
1604 if( selectionStart == selectionEnd )
1606 ChangeState( EventData::EDITING );
1607 // Nothing to select. i.e. a white space, out of bounds
1611 mEventData->mLeftSelectionPosition = selectionStart;
1612 mEventData->mRightSelectionPosition = selectionEnd;
1615 void Controller::Impl::SetPopupButtons()
1618 * Sets the Popup buttons to be shown depending on State.
1620 * If SELECTING : CUT & COPY + ( PASTE & CLIPBOARD if content available to paste )
1622 * If EDITING_WITH_POPUP : SELECT & SELECT_ALL
1625 TextSelectionPopup::Buttons buttonsToShow = TextSelectionPopup::NONE;
1627 if( EventData::SELECTING == mEventData->mState )
1629 buttonsToShow = TextSelectionPopup::Buttons( TextSelectionPopup::CUT | TextSelectionPopup::COPY );
1631 if( !IsClipboardEmpty() )
1633 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
1634 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::CLIPBOARD ) );
1637 if( !mEventData->mAllTextSelected )
1639 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::SELECT_ALL ) );
1642 else if( EventData::EDITING_WITH_POPUP == mEventData->mState )
1644 if( mLogicalModel->mText.Count() && !IsShowingPlaceholderText() )
1646 buttonsToShow = TextSelectionPopup::Buttons( TextSelectionPopup::SELECT | TextSelectionPopup::SELECT_ALL );
1649 if( !IsClipboardEmpty() )
1651 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
1652 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::CLIPBOARD ) );
1655 else if( EventData::EDITING_WITH_PASTE_POPUP == mEventData->mState )
1657 if ( !IsClipboardEmpty() )
1659 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
1660 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::CLIPBOARD ) );
1664 mEventData->mDecorator->SetEnabledPopupButtons( buttonsToShow );
1667 void Controller::Impl::ChangeState( EventData::State newState )
1669 if( NULL == mEventData )
1671 // Nothing to do if there is no text input.
1675 DALI_LOG_INFO( gLogFilter, Debug::General, "ChangeState state:%d newstate:%d\n", mEventData->mState, newState );
1677 if( mEventData->mState != newState )
1679 mEventData->mState = newState;
1681 if( EventData::INACTIVE == mEventData->mState )
1683 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
1684 mEventData->mDecorator->StopCursorBlink();
1685 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1686 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1687 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1688 mEventData->mDecorator->SetPopupActive( false );
1689 mEventData->mDecoratorUpdated = true;
1692 else if( EventData::INTERRUPTED == mEventData->mState)
1694 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1695 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1696 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1697 mEventData->mDecorator->SetPopupActive( false );
1698 mEventData->mDecoratorUpdated = true;
1701 else if( EventData::SELECTING == mEventData->mState )
1703 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
1704 mEventData->mDecorator->StopCursorBlink();
1705 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1706 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true );
1707 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true );
1708 if( mEventData->mGrabHandlePopupEnabled )
1711 mEventData->mDecorator->SetPopupActive( true );
1713 mEventData->mDecoratorUpdated = true;
1715 else if( EventData::EDITING == mEventData->mState )
1717 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1718 if( mEventData->mCursorBlinkEnabled )
1720 mEventData->mDecorator->StartCursorBlink();
1722 // Grab handle is not shown until a tap is received whilst EDITING
1723 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1724 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1725 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1726 if( mEventData->mGrabHandlePopupEnabled )
1728 mEventData->mDecorator->SetPopupActive( false );
1730 mEventData->mDecoratorUpdated = true;
1733 else if( EventData::EDITING_WITH_POPUP == mEventData->mState )
1735 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "EDITING_WITH_POPUP \n", newState );
1737 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1738 if( mEventData->mCursorBlinkEnabled )
1740 mEventData->mDecorator->StartCursorBlink();
1742 if( mEventData->mSelectionEnabled )
1744 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1745 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1749 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
1751 if( mEventData->mGrabHandlePopupEnabled )
1754 mEventData->mDecorator->SetPopupActive( true );
1757 mEventData->mDecoratorUpdated = true;
1759 else if( EventData::EDITING_WITH_GRAB_HANDLE == mEventData->mState )
1761 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "EDITING_WITH_GRAB_HANDLE \n", newState );
1763 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1764 if( mEventData->mCursorBlinkEnabled )
1766 mEventData->mDecorator->StartCursorBlink();
1768 // Grab handle is not shown until a tap is received whilst EDITING
1769 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
1770 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1771 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1772 if( mEventData->mGrabHandlePopupEnabled )
1774 mEventData->mDecorator->SetPopupActive( false );
1776 mEventData->mDecoratorUpdated = true;
1779 else if( EventData::SELECTION_HANDLE_PANNING == mEventData->mState )
1781 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
1782 mEventData->mDecorator->StopCursorBlink();
1783 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1784 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true );
1785 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true );
1786 if( mEventData->mGrabHandlePopupEnabled )
1788 mEventData->mDecorator->SetPopupActive( false );
1790 mEventData->mDecoratorUpdated = true;
1792 else if( EventData::GRAB_HANDLE_PANNING == mEventData->mState )
1794 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "GRAB_HANDLE_PANNING \n", newState );
1796 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1797 if( mEventData->mCursorBlinkEnabled )
1799 mEventData->mDecorator->StartCursorBlink();
1801 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
1802 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1803 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1804 if( mEventData->mGrabHandlePopupEnabled )
1806 mEventData->mDecorator->SetPopupActive( false );
1808 mEventData->mDecoratorUpdated = true;
1810 else if( EventData::EDITING_WITH_PASTE_POPUP == mEventData->mState )
1812 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "EDITING_WITH_PASTE_POPUP \n", newState );
1814 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1815 if( mEventData->mCursorBlinkEnabled )
1817 mEventData->mDecorator->StartCursorBlink();
1820 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
1821 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1822 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1824 if( mEventData->mGrabHandlePopupEnabled )
1827 mEventData->mDecorator->SetPopupActive( true );
1830 mEventData->mDecoratorUpdated = true;
1835 LineIndex Controller::Impl::GetClosestLine( float y ) const
1837 float totalHeight = 0.f;
1838 LineIndex lineIndex = 0u;
1840 const Vector<LineRun>& lines = mVisualModel->mLines;
1841 for( LineIndex endLine = lines.Count();
1842 lineIndex < endLine;
1845 const LineRun& lineRun = lines[lineIndex];
1846 totalHeight += lineRun.ascender + -lineRun.descender;
1847 if( y < totalHeight )
1853 if( lineIndex == 0 )
1861 void Controller::Impl::FindSelectionIndices( float visualX, float visualY, CharacterIndex& startIndex, CharacterIndex& endIndex )
1863 CharacterIndex hitCharacter = GetClosestCursorIndex( visualX, visualY );
1864 DALI_ASSERT_DEBUG( hitCharacter <= mLogicalModel->mText.Count() && "GetClosestCursorIndex returned out of bounds index" );
1866 if( mLogicalModel->mText.Count() == 0 )
1868 return; // if model empty
1871 if( hitCharacter >= mLogicalModel->mText.Count() )
1873 // Closest hit character is the last character.
1874 if( hitCharacter == mLogicalModel->mText.Count() )
1876 hitCharacter--; //Hit character index set to last character in logical model
1880 // hitCharacter is out of bounds
1885 startIndex = hitCharacter;
1886 endIndex = hitCharacter;
1887 bool isHitCharacterWhitespace = TextAbstraction::IsWhiteSpace( mLogicalModel->mText[hitCharacter] );
1889 // Find the start and end of the text
1890 for( startIndex = hitCharacter; startIndex > 0; --startIndex )
1892 if( isHitCharacterWhitespace != TextAbstraction::IsWhiteSpace( mLogicalModel->mText[ startIndex-1 ] ) )
1897 const CharacterIndex pastTheEnd = mLogicalModel->mText.Count();
1898 for( endIndex = hitCharacter + 1u; endIndex < pastTheEnd; ++endIndex )
1900 if( isHitCharacterWhitespace != TextAbstraction::IsWhiteSpace( mLogicalModel->mText[ endIndex ] ) )
1907 CharacterIndex Controller::Impl::GetClosestCursorIndex( float visualX,
1910 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "GetClosestCursorIndex %p closest visualX %f visualY %f\n", this, visualX, visualY );
1912 if( NULL == mEventData )
1914 // Nothing to do if there is no text input.
1918 CharacterIndex logicalIndex = 0u;
1920 const Length numberOfGlyphs = mVisualModel->mGlyphs.Count();
1921 const Length numberOfLines = mVisualModel->mLines.Count();
1922 if( ( 0 == numberOfGlyphs ) ||
1923 ( 0 == numberOfLines ) )
1925 return logicalIndex;
1928 // Find which line is closest
1929 const LineIndex lineIndex = GetClosestLine( visualY );
1930 const LineRun& line = mVisualModel->mLines[lineIndex];
1932 // Get the positions of the glyphs.
1933 const Vector<Vector2>& positions = mVisualModel->mGlyphPositions;
1934 const Vector2* const positionsBuffer = positions.Begin();
1936 // Get the character to glyph conversion table.
1937 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
1939 // Get the glyphs per character table.
1940 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
1942 // Get the glyph's info buffer.
1943 const GlyphInfo* const glyphInfoBuffer = mVisualModel->mGlyphs.Begin();
1945 const CharacterIndex startCharacter = line.characterRun.characterIndex;
1946 const CharacterIndex endCharacter = line.characterRun.characterIndex + line.characterRun.numberOfCharacters;
1947 DALI_ASSERT_DEBUG( endCharacter <= mLogicalModel->mText.Count() && "Invalid line info" );
1949 // Whether there is a hit on a glyph.
1950 bool matched = false;
1952 // Traverses glyphs in visual order. To do that use the visual to logical conversion table.
1953 CharacterIndex visualIndex = startCharacter;
1954 Length numberOfCharacters = 0u;
1955 for( ; !matched && ( visualIndex < endCharacter ); ++visualIndex )
1957 // The character in logical order.
1958 const CharacterIndex characterLogicalOrderIndex = mLogicalModel->GetLogicalCharacterIndex( visualIndex );
1960 // Get the script of the character.
1961 const Script script = mLogicalModel->GetScript( characterLogicalOrderIndex );
1963 // The number of glyphs for that character
1964 const Length numberOfGlyphs = *( glyphsPerCharacterBuffer + characterLogicalOrderIndex );
1965 ++numberOfCharacters;
1968 if( 0u != numberOfGlyphs )
1970 // Get the first character/glyph of the group of glyphs.
1971 const CharacterIndex firstVisualCharacterIndex = 1u + visualIndex - numberOfCharacters;
1972 const CharacterIndex firstLogicalCharacterIndex = mLogicalModel->GetLogicalCharacterIndex( firstVisualCharacterIndex );
1973 const GlyphIndex firstLogicalGlyphIndex = *( charactersToGlyphBuffer + firstLogicalCharacterIndex );
1975 // Get the metrics for the group of glyphs.
1976 GlyphMetrics glyphMetrics;
1977 GetGlyphsMetrics( firstLogicalGlyphIndex,
1983 // Get the position of the first glyph.
1984 const Vector2& position = *( positionsBuffer + firstLogicalGlyphIndex );
1986 // Whether the glyph can be split, like Latin ligatures fi, ff or Arabic ﻻ.
1987 const bool isInterglyphIndex = ( numberOfCharacters > numberOfGlyphs ) && HasLigatureMustBreak( script );
1988 const Length numberOfBlocks = isInterglyphIndex ? numberOfCharacters : 1u;
1989 const float glyphAdvance = glyphMetrics.advance / static_cast<float>( numberOfBlocks );
1991 GlyphIndex index = 0u;
1992 for( ; !matched && ( index < numberOfBlocks ); ++index )
1994 // Find the mid-point of the area containing the glyph
1995 const float glyphCenter = -glyphMetrics.xBearing + position.x + ( static_cast<float>( index ) + 0.5f ) * glyphAdvance;
1997 if( visualX < glyphCenter )
2006 visualIndex = firstVisualCharacterIndex + index;
2010 numberOfCharacters = 0u;
2015 // Return the logical position of the cursor in characters.
2019 visualIndex = endCharacter;
2022 logicalIndex = mLogicalModel->GetLogicalCursorIndex( visualIndex );
2023 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "%p closest visualIndex %d logicalIndex %d\n", this, visualIndex, logicalIndex );
2025 DALI_ASSERT_DEBUG( ( logicalIndex <= mLogicalModel->mText.Count() && logicalIndex >= 0 ) && "GetClosestCursorIndex - Out of bounds index" );
2027 return logicalIndex;
2030 void Controller::Impl::GetCursorPosition( CharacterIndex logical,
2031 CursorInfo& cursorInfo )
2033 // TODO: Check for multiline with \n, etc...
2035 const Length numberOfCharacters = mLogicalModel->mText.Count();
2036 if( !IsShowingRealText() )
2038 // Do not want to use the place-holder text to set the cursor position.
2040 // Use the line's height of the font's family set to set the cursor's size.
2041 // If there is no font's family set, use the default font.
2042 // Use the current alignment to place the cursor at the beginning, center or end of the box.
2044 cursorInfo.lineOffset = 0.f;
2045 cursorInfo.lineHeight = GetDefaultFontLineHeight();
2046 cursorInfo.primaryCursorHeight = cursorInfo.lineHeight;
2048 switch( mLayoutEngine.GetHorizontalAlignment() )
2050 case LayoutEngine::HORIZONTAL_ALIGN_BEGIN:
2052 cursorInfo.primaryPosition.x = 0.f;
2055 case LayoutEngine::HORIZONTAL_ALIGN_CENTER:
2057 cursorInfo.primaryPosition.x = floorf( 0.5f * mVisualModel->mControlSize.width );
2060 case LayoutEngine::HORIZONTAL_ALIGN_END:
2062 cursorInfo.primaryPosition.x = mVisualModel->mControlSize.width - mEventData->mDecorator->GetCursorWidth();
2067 switch( mLayoutEngine.GetVerticalAlignment() )
2069 case LayoutEngine::VERTICAL_ALIGN_TOP:
2071 cursorInfo.primaryPosition.y = 0.f;
2074 case LayoutEngine::VERTICAL_ALIGN_CENTER:
2076 cursorInfo.primaryPosition.y = floorf( 0.5f * ( mVisualModel->mControlSize.height - cursorInfo.lineHeight ) );
2079 case LayoutEngine::VERTICAL_ALIGN_BOTTOM:
2081 cursorInfo.primaryPosition.y = mVisualModel->mControlSize.height - cursorInfo.lineHeight;
2086 // Nothing else to do.
2090 // Check if the logical position is the first or the last one of the text.
2091 const bool isFirstPosition = 0u == logical;
2092 const bool isLastPosition = numberOfCharacters == logical;
2094 // 'logical' is the logical 'cursor' index.
2095 // Get the next and current logical 'character' index.
2096 const CharacterIndex nextCharacterIndex = logical;
2097 const CharacterIndex characterIndex = isFirstPosition ? logical : logical - 1u;
2099 // Get the direction of the character and the next one.
2100 const CharacterDirection* const modelCharacterDirectionsBuffer = ( 0u != mLogicalModel->mCharacterDirections.Count() ) ? mLogicalModel->mCharacterDirections.Begin() : NULL;
2102 CharacterDirection isCurrentRightToLeft = false;
2103 CharacterDirection isNextRightToLeft = false;
2104 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
2106 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + characterIndex );
2107 isNextRightToLeft = *( modelCharacterDirectionsBuffer + nextCharacterIndex );
2110 // Get the line where the character is laid-out.
2111 const LineRun* const modelLines = mVisualModel->mLines.Begin();
2113 const LineIndex lineIndex = mVisualModel->GetLineOfCharacter( characterIndex );
2114 const LineRun& line = *( modelLines + lineIndex );
2116 // Get the paragraph's direction.
2117 const CharacterDirection isRightToLeftParagraph = line.direction;
2119 // Check whether there is an alternative position:
2121 cursorInfo.isSecondaryCursor = ( !isLastPosition && ( isCurrentRightToLeft != isNextRightToLeft ) ) ||
2122 ( isLastPosition && ( isRightToLeftParagraph != isCurrentRightToLeft ) );
2124 // Set the line offset and height.
2125 cursorInfo.lineOffset = 0.f;
2126 cursorInfo.lineHeight = line.ascender + -line.descender;
2128 // Calculate the primary cursor.
2130 CharacterIndex index = characterIndex;
2131 if( cursorInfo.isSecondaryCursor )
2133 // If there is a secondary position, the primary cursor may be in a different place than the logical index.
2135 if( isLastPosition )
2137 // The position of the cursor after the last character needs special
2138 // care depending on its direction and the direction of the paragraph.
2140 // Need to find the first character after the last character with the paragraph's direction.
2141 // i.e l0 l1 l2 r0 r1 should find r0.
2143 // TODO: check for more than one line!
2144 index = isRightToLeftParagraph ? line.characterRun.characterIndex : line.characterRun.characterIndex + line.characterRun.numberOfCharacters - 1u;
2145 index = mLogicalModel->GetLogicalCharacterIndex( index );
2149 index = ( isRightToLeftParagraph == isCurrentRightToLeft ) ? characterIndex : nextCharacterIndex;
2153 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
2154 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
2155 const Length* const charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
2156 const CharacterIndex* const glyphsToCharactersBuffer = mVisualModel->mGlyphsToCharacters.Begin();
2157 const Vector2* const glyphPositionsBuffer = mVisualModel->mGlyphPositions.Begin();
2158 const GlyphInfo* const glyphInfoBuffer = mVisualModel->mGlyphs.Begin();
2160 // Convert the cursor position into the glyph position.
2161 const GlyphIndex primaryGlyphIndex = *( charactersToGlyphBuffer + index );
2162 const Length primaryNumberOfGlyphs = *( glyphsPerCharacterBuffer + index );
2163 const Length primaryNumberOfCharacters = *( charactersPerGlyphBuffer + primaryGlyphIndex );
2165 // Get the metrics for the group of glyphs.
2166 GlyphMetrics glyphMetrics;
2167 GetGlyphsMetrics( primaryGlyphIndex,
2168 primaryNumberOfGlyphs,
2173 // Whether to add the glyph's advance to the cursor position.
2174 // 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,
2175 // if the logical cursor is one, the position is the position of the first glyph and the advance is added.
2176 // A 'truth table' was build and an online Karnaugh map tool was used to simplify the logic.
2197 // Where F -> isFirstPosition
2198 // L -> isLastPosition
2199 // C -> isCurrentRightToLeft
2200 // P -> isRightToLeftParagraph
2201 // A -> Whether to add the glyph's advance.
2203 const bool addGlyphAdvance = ( ( isLastPosition && !isRightToLeftParagraph ) ||
2204 ( isFirstPosition && isRightToLeftParagraph ) ||
2205 ( !isFirstPosition && !isLastPosition && !isCurrentRightToLeft ) );
2207 float glyphAdvance = addGlyphAdvance ? glyphMetrics.advance : 0.f;
2209 if( !isLastPosition &&
2210 ( primaryNumberOfCharacters > 1u ) )
2212 const CharacterIndex firstIndex = *( glyphsToCharactersBuffer + primaryGlyphIndex );
2214 bool isCurrentRightToLeft = false;
2215 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
2217 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + index );
2220 Length numberOfGlyphAdvance = ( isFirstPosition ? 0u : 1u ) + characterIndex - firstIndex;
2221 if( isCurrentRightToLeft )
2223 numberOfGlyphAdvance = primaryNumberOfCharacters - numberOfGlyphAdvance;
2226 glyphAdvance = static_cast<float>( numberOfGlyphAdvance ) * glyphMetrics.advance / static_cast<float>( primaryNumberOfCharacters );
2229 // Get the glyph position and x bearing.
2230 const Vector2& primaryPosition = *( glyphPositionsBuffer + primaryGlyphIndex );
2232 // Set the primary cursor's height.
2233 cursorInfo.primaryCursorHeight = cursorInfo.isSecondaryCursor ? 0.5f * glyphMetrics.fontHeight : glyphMetrics.fontHeight;
2235 // Set the primary cursor's position.
2236 cursorInfo.primaryPosition.x = -glyphMetrics.xBearing + primaryPosition.x + glyphAdvance;
2237 cursorInfo.primaryPosition.y = line.ascender - glyphMetrics.ascender;
2239 // Calculate the secondary cursor.
2241 if( cursorInfo.isSecondaryCursor )
2243 // Set the secondary cursor's height.
2244 cursorInfo.secondaryCursorHeight = 0.5f * glyphMetrics.fontHeight;
2246 CharacterIndex index = characterIndex;
2247 if( !isLastPosition )
2249 index = ( isRightToLeftParagraph == isCurrentRightToLeft ) ? nextCharacterIndex : characterIndex;
2252 const GlyphIndex secondaryGlyphIndex = *( charactersToGlyphBuffer + index );
2253 const Length secondaryNumberOfGlyphs = *( glyphsPerCharacterBuffer + index );
2255 const Vector2& secondaryPosition = *( glyphPositionsBuffer + secondaryGlyphIndex );
2257 GetGlyphsMetrics( secondaryGlyphIndex,
2258 secondaryNumberOfGlyphs,
2263 // Set the secondary cursor's position.
2264 cursorInfo.secondaryPosition.x = -glyphMetrics.xBearing + secondaryPosition.x + ( isCurrentRightToLeft ? 0.f : glyphMetrics.advance );
2265 cursorInfo.secondaryPosition.y = cursorInfo.lineHeight - cursorInfo.secondaryCursorHeight - line.descender - ( glyphMetrics.fontHeight - glyphMetrics.ascender );
2268 if( LayoutEngine::MULTI_LINE_BOX == mLayoutEngine.GetLayout() )
2270 // If the text is editable and multi-line, the cursor position after a white space shouldn't exceed the boundaries of the text control.
2272 // Note the white spaces laid-out at the end of the line might exceed the boundaries of the control.
2273 // The reason is a wrapped line must not start with a white space so they are laid-out at the end of the line.
2275 if( 0.f > cursorInfo.primaryPosition.x )
2277 cursorInfo.primaryPosition.x = 0.f;
2280 const float edgeWidth = mVisualModel->mControlSize.width - static_cast<float>( mEventData->mDecorator->GetCursorWidth() );
2281 if( cursorInfo.primaryPosition.x > edgeWidth )
2283 cursorInfo.primaryPosition.x = edgeWidth;
2288 CharacterIndex Controller::Impl::CalculateNewCursorIndex( CharacterIndex index ) const
2290 if( NULL == mEventData )
2292 // Nothing to do if there is no text input.
2296 CharacterIndex cursorIndex = mEventData->mPrimaryCursorPosition;
2298 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
2299 const Length* const charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
2301 GlyphIndex glyphIndex = *( charactersToGlyphBuffer + index );
2302 Length numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
2304 if( numberOfCharacters > 1u )
2306 const Script script = mLogicalModel->GetScript( index );
2307 if( HasLigatureMustBreak( script ) )
2309 // Prevents to jump the whole Latin ligatures like fi, ff, or Arabic ﻻ, ...
2310 numberOfCharacters = 1u;
2315 while( 0u == numberOfCharacters )
2318 numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
2322 if( index < mEventData->mPrimaryCursorPosition )
2324 cursorIndex -= numberOfCharacters;
2328 cursorIndex += numberOfCharacters;
2334 void Controller::Impl::UpdateCursorPosition( const CursorInfo& cursorInfo )
2336 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "-->Controller::UpdateCursorPosition %p\n", this );
2337 if( NULL == mEventData )
2339 // Nothing to do if there is no text input.
2340 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition no event data\n" );
2344 const Vector2 offset = mEventData->mScrollPosition + ( IsShowingRealText() ? mAlignmentOffset : Vector2::ZERO );
2345 const Vector2 cursorPosition = cursorInfo.primaryPosition + offset;
2347 // Sets the cursor position.
2348 mEventData->mDecorator->SetPosition( PRIMARY_CURSOR,
2351 cursorInfo.primaryCursorHeight,
2352 cursorInfo.lineHeight );
2353 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Primary cursor position: %f,%f\n", cursorPosition.x, cursorPosition.y );
2355 // Sets the grab handle position.
2356 mEventData->mDecorator->SetPosition( GRAB_HANDLE,
2358 cursorInfo.lineOffset + offset.y,
2359 cursorInfo.lineHeight );
2361 if( cursorInfo.isSecondaryCursor )
2363 mEventData->mDecorator->SetPosition( SECONDARY_CURSOR,
2364 cursorInfo.secondaryPosition.x + offset.x,
2365 cursorInfo.secondaryPosition.y + offset.y,
2366 cursorInfo.secondaryCursorHeight,
2367 cursorInfo.lineHeight );
2368 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Secondary cursor position: %f,%f\n", cursorInfo.secondaryPosition.x + offset.x, cursorInfo.secondaryPosition.y + offset.y );
2371 // Set which cursors are active according the state.
2372 if( EventData::IsEditingState( mEventData->mState ) || ( EventData::GRAB_HANDLE_PANNING == mEventData->mState ) )
2374 if( cursorInfo.isSecondaryCursor )
2376 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_BOTH );
2380 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
2385 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
2388 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition\n" );
2391 void Controller::Impl::UpdateSelectionHandle( HandleType handleType,
2392 const CursorInfo& cursorInfo )
2394 if( ( LEFT_SELECTION_HANDLE != handleType ) &&
2395 ( RIGHT_SELECTION_HANDLE != handleType ) )
2400 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
2401 const Vector2 cursorPosition = cursorInfo.primaryPosition + offset;
2403 // Sets the handle's position.
2404 mEventData->mDecorator->SetPosition( handleType,
2406 cursorInfo.lineOffset + offset.y,
2407 cursorInfo.lineHeight );
2409 // If selection handle at start of the text and other at end of the text then all text is selected.
2410 const CharacterIndex startOfSelection = std::min( mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition );
2411 const CharacterIndex endOfSelection = std::max ( mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition );
2412 mEventData->mAllTextSelected = ( startOfSelection == 0 ) && ( endOfSelection == mLogicalModel->mText.Count() );
2415 void Controller::Impl::ClampHorizontalScroll( const Vector2& actualSize )
2417 // Clamp between -space & 0 (and the text alignment).
2419 if( actualSize.width > mVisualModel->mControlSize.width )
2421 const float space = ( actualSize.width - mVisualModel->mControlSize.width ) + mAlignmentOffset.x;
2422 mEventData->mScrollPosition.x = ( mEventData->mScrollPosition.x < -space ) ? -space : mEventData->mScrollPosition.x;
2423 mEventData->mScrollPosition.x = ( mEventData->mScrollPosition.x > -mAlignmentOffset.x ) ? -mAlignmentOffset.x : mEventData->mScrollPosition.x;
2425 mEventData->mDecoratorUpdated = true;
2429 mEventData->mScrollPosition.x = 0.f;
2433 void Controller::Impl::ClampVerticalScroll( const Vector2& actualSize )
2435 // Clamp between -space & 0 (and the text alignment).
2436 if( actualSize.height > mVisualModel->mControlSize.height )
2438 const float space = ( actualSize.height - mVisualModel->mControlSize.height ) + mAlignmentOffset.y;
2439 mEventData->mScrollPosition.y = ( mEventData->mScrollPosition.y < -space ) ? -space : mEventData->mScrollPosition.y;
2440 mEventData->mScrollPosition.y = ( mEventData->mScrollPosition.y > -mAlignmentOffset.y ) ? -mAlignmentOffset.y : mEventData->mScrollPosition.y;
2442 mEventData->mDecoratorUpdated = true;
2446 mEventData->mScrollPosition.y = 0.f;
2450 void Controller::Impl::ScrollToMakePositionVisible( const Vector2& position )
2452 // position is in actor's coords.
2453 const float positionEnd = position.x + ( mEventData->mDecorator ? mEventData->mDecorator->GetCursorWidth() : 0.f );
2455 // Transform the position to decorator coords.
2456 const float alignment = IsShowingRealText() ? mAlignmentOffset.x : 0.f;
2457 const float offset = mEventData->mScrollPosition.x + alignment;
2458 const float decoratorPositionBegin = position.x + offset;
2459 const float decoratorPositionEnd = positionEnd + offset;
2461 if( decoratorPositionBegin < 0.f )
2463 mEventData->mScrollPosition.x = -position.x - alignment;
2465 else if( decoratorPositionEnd > mVisualModel->mControlSize.width )
2467 mEventData->mScrollPosition.x = mVisualModel->mControlSize.width - positionEnd - alignment;
2471 void Controller::Impl::ScrollTextToMatchCursor( const CursorInfo& cursorInfo )
2473 // Get the current cursor position in decorator coords.
2474 const Vector2& currentCursorPosition = mEventData->mDecorator->GetPosition( PRIMARY_CURSOR );
2476 // Calculate the offset to match the cursor position before the character was deleted.
2477 mEventData->mScrollPosition.x = currentCursorPosition.x - cursorInfo.primaryPosition.x - mAlignmentOffset.x;
2479 ClampHorizontalScroll( mVisualModel->GetLayoutSize() );
2482 void Controller::Impl::RequestRelayout()
2484 mControlInterface.RequestTextRelayout();
2489 } // namespace Toolkit