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 Length numberOfCharactersToUpdate = 0u;
304 if( mTextUpdateInfo.mFullRelayoutNeeded )
306 numberOfCharactersToUpdate = mTextUpdateInfo.mPreviousNumberOfCharacters;
310 numberOfCharactersToUpdate = ( mTextUpdateInfo.mNumberOfCharactersToRemove > 0u ) ? mTextUpdateInfo.mNumberOfCharactersToRemove : 1u;
312 mLogicalModel->FindParagraphs( mTextUpdateInfo.mCharacterIndex,
313 numberOfCharactersToUpdate,
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( NO_OPERATION != ( GET_LINE_BREAKS & operations ) )
350 mLogicalModel->mLineBreakInfo.Clear();
351 mLogicalModel->mParagraphInfo.Clear();
354 if( NO_OPERATION != ( GET_WORD_BREAKS & operations ) )
356 mLogicalModel->mLineBreakInfo.Clear();
359 if( NO_OPERATION != ( GET_SCRIPTS & operations ) )
361 mLogicalModel->mScriptRuns.Clear();
364 if( NO_OPERATION != ( VALIDATE_FONTS & operations ) )
366 mLogicalModel->mFontRuns.Clear();
369 if( 0u != mLogicalModel->mBidirectionalParagraphInfo.Count() )
371 if( NO_OPERATION != ( BIDI_INFO & operations ) )
373 mLogicalModel->mBidirectionalParagraphInfo.Clear();
374 mLogicalModel->mCharacterDirections.Clear();
377 if( NO_OPERATION != ( 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( NO_OPERATION != ( 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( NO_OPERATION != ( LAYOUT & operations ) )
406 mVisualModel->mLines.Clear();
409 if( NO_OPERATION != ( 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( NO_OPERATION != ( 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( NO_OPERATION != ( 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( NO_OPERATION != ( GET_SCRIPTS & operations ) )
444 // Clear the scripts.
445 ClearCharacterRuns( startIndex,
447 mLogicalModel->mScriptRuns );
450 if( NO_OPERATION != ( VALIDATE_FONTS & operations ) )
453 ClearCharacterRuns( startIndex,
455 mLogicalModel->mFontRuns );
458 if( 0u != mLogicalModel->mBidirectionalParagraphInfo.Count() )
460 if( NO_OPERATION != ( 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( NO_OPERATION != ( 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( NO_OPERATION != ( 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( NO_OPERATION != ( 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( NO_OPERATION != ( 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 ), operations );
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( NO_OPERATION != ( 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( NO_OPERATION != ( 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 = NO_OPERATION != ( GET_SCRIPTS & operations );
702 const bool validateFonts = NO_OPERATION != ( 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( NO_OPERATION != ( 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( NO_OPERATION != ( 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( NO_OPERATION != ( 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( NO_OPERATION != ( COLOR & operations ) )
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()
1393 if( mClipboard && mClipboardHideEnabled )
1395 mClipboard.HideClipboard();
1399 void Controller::Impl::SetClipboardHideEnable(bool enable)
1401 mClipboardHideEnabled = enable;
1404 bool Controller::Impl::CopyStringToClipboard( std::string& source )
1406 //Send string to clipboard
1407 return ( mClipboard && mClipboard.SetItem( source ) );
1410 void Controller::Impl::SendSelectionToClipboard( bool deleteAfterSending )
1412 std::string selectedText;
1413 RetrieveSelection( selectedText, deleteAfterSending );
1414 CopyStringToClipboard( selectedText );
1415 ChangeState( EventData::EDITING );
1418 void Controller::Impl::GetTextFromClipboard( unsigned int itemIndex, std::string& retrievedString )
1422 retrievedString = mClipboard.GetItem( itemIndex );
1426 void Controller::Impl::RepositionSelectionHandles()
1428 CharacterIndex selectionStart = mEventData->mLeftSelectionPosition;
1429 CharacterIndex selectionEnd = mEventData->mRightSelectionPosition;
1431 if( selectionStart == selectionEnd )
1433 // Nothing to select if handles are in the same place.
1437 mEventData->mDecorator->ClearHighlights();
1439 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
1440 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
1441 const GlyphInfo* const glyphsBuffer = mVisualModel->mGlyphs.Begin();
1442 const Vector2* const positionsBuffer = mVisualModel->mGlyphPositions.Begin();
1443 const Length* const charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
1444 const CharacterIndex* const glyphToCharacterBuffer = mVisualModel->mGlyphsToCharacters.Begin();
1445 const CharacterDirection* const modelCharacterDirectionsBuffer = ( 0u != mLogicalModel->mCharacterDirections.Count() ) ? mLogicalModel->mCharacterDirections.Begin() : NULL;
1447 // TODO: Better algorithm to create the highlight box.
1448 // TODO: Multi-line.
1450 // Get the height of the line.
1451 const Vector<LineRun>& lines = mVisualModel->mLines;
1452 const LineRun& firstLine = *lines.Begin();
1453 const float height = firstLine.ascender + -firstLine.descender;
1455 const bool isLastCharacter = selectionEnd >= mLogicalModel->mText.Count();
1456 const bool startDirection = ( ( NULL == modelCharacterDirectionsBuffer ) ? false : *( modelCharacterDirectionsBuffer + selectionStart ) );
1457 const bool endDirection = ( ( NULL == modelCharacterDirectionsBuffer ) ? false : *( modelCharacterDirectionsBuffer + ( selectionEnd - ( isLastCharacter ? 1u : 0u ) ) ) );
1459 // Swap the indices if the start is greater than the end.
1460 const bool indicesSwapped = selectionStart > selectionEnd;
1462 // Tell the decorator to flip the selection handles if needed.
1463 mEventData->mDecorator->SetSelectionHandleFlipState( indicesSwapped, startDirection, endDirection );
1465 if( indicesSwapped )
1467 std::swap( selectionStart, selectionEnd );
1470 // Get the indices to the first and last selected glyphs.
1471 const CharacterIndex selectionEndMinusOne = selectionEnd - 1u;
1472 const GlyphIndex glyphStart = *( charactersToGlyphBuffer + selectionStart );
1473 const Length numberOfGlyphs = *( glyphsPerCharacterBuffer + selectionEndMinusOne );
1474 const GlyphIndex glyphEnd = *( charactersToGlyphBuffer + selectionEndMinusOne ) + ( ( numberOfGlyphs > 0 ) ? numberOfGlyphs - 1u : 0u );
1476 // Check if the first glyph is a ligature that must be broken like Latin ff, fi, or Arabic ﻻ, etc which needs special code.
1477 const Length numberOfCharactersStart = *( charactersPerGlyphBuffer + glyphStart );
1478 bool splitStartGlyph = ( numberOfCharactersStart > 1u ) && HasLigatureMustBreak( mLogicalModel->GetScript( selectionStart ) );
1480 // Check if the last glyph is a ligature that must be broken like Latin ff, fi, or Arabic ﻻ, etc which needs special code.
1481 const Length numberOfCharactersEnd = *( charactersPerGlyphBuffer + glyphEnd );
1482 bool splitEndGlyph = ( glyphStart != glyphEnd ) && ( numberOfCharactersEnd > 1u ) && HasLigatureMustBreak( mLogicalModel->GetScript( selectionEndMinusOne ) );
1484 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
1486 // Traverse the glyphs.
1487 for( GlyphIndex index = glyphStart; index <= glyphEnd; ++index )
1489 const GlyphInfo& glyph = *( glyphsBuffer + index );
1490 const Vector2& position = *( positionsBuffer + index );
1492 if( splitStartGlyph )
1494 // 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.
1496 const float glyphAdvance = glyph.advance / static_cast<float>( numberOfCharactersStart );
1497 const CharacterIndex interGlyphIndex = selectionStart - *( glyphToCharacterBuffer + glyphStart );
1498 // Get the direction of the character.
1499 CharacterDirection isCurrentRightToLeft = false;
1500 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
1502 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + selectionStart );
1505 // The end point could be in the middle of the ligature.
1506 // Calculate the number of characters selected.
1507 const Length numberOfCharacters = ( glyphStart == glyphEnd ) ? ( selectionEnd - selectionStart ) : ( numberOfCharactersStart - interGlyphIndex );
1509 const float xPosition = position.x - glyph.xBearing + offset.x + glyphAdvance * static_cast<float>( isCurrentRightToLeft ? ( numberOfCharactersStart - interGlyphIndex - numberOfCharacters ) : interGlyphIndex );
1511 mEventData->mDecorator->AddHighlight( xPosition,
1513 xPosition + static_cast<float>( numberOfCharacters ) * glyphAdvance,
1514 offset.y + height );
1516 splitStartGlyph = false;
1520 if( splitEndGlyph && ( index == glyphEnd ) )
1522 // 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.
1524 const float glyphAdvance = glyph.advance / static_cast<float>( numberOfCharactersEnd );
1525 const CharacterIndex interGlyphIndex = selectionEnd - *( glyphToCharacterBuffer + glyphEnd );
1526 // Get the direction of the character.
1527 CharacterDirection isCurrentRightToLeft = false;
1528 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
1530 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + selectionEnd );
1533 const Length numberOfCharacters = numberOfCharactersEnd - interGlyphIndex;
1535 const float xPosition = position.x - glyph.xBearing + offset.x + ( isCurrentRightToLeft ? ( glyphAdvance * static_cast<float>( numberOfCharacters ) ) : 0.f );
1536 mEventData->mDecorator->AddHighlight( xPosition,
1538 xPosition + static_cast<float>( interGlyphIndex ) * glyphAdvance,
1539 offset.y + height );
1541 splitEndGlyph = false;
1545 const float xPosition = position.x - glyph.xBearing + offset.x;
1546 mEventData->mDecorator->AddHighlight( xPosition,
1548 xPosition + glyph.advance,
1549 offset.y + height );
1552 CursorInfo primaryCursorInfo;
1553 GetCursorPosition( mEventData->mLeftSelectionPosition,
1554 primaryCursorInfo );
1556 CursorInfo secondaryCursorInfo;
1557 GetCursorPosition( mEventData->mRightSelectionPosition,
1558 secondaryCursorInfo );
1560 const Vector2 primaryPosition = primaryCursorInfo.primaryPosition + offset;
1561 const Vector2 secondaryPosition = secondaryCursorInfo.primaryPosition + offset;
1563 mEventData->mDecorator->SetPosition( LEFT_SELECTION_HANDLE,
1565 primaryCursorInfo.lineOffset + offset.y,
1566 primaryCursorInfo.lineHeight );
1568 mEventData->mDecorator->SetPosition( RIGHT_SELECTION_HANDLE,
1569 secondaryPosition.x,
1570 secondaryCursorInfo.lineOffset + offset.y,
1571 secondaryCursorInfo.lineHeight );
1573 // Cursor to be positioned at end of selection so if selection interrupted and edit mode restarted the cursor will be at end of selection
1574 mEventData->mPrimaryCursorPosition = ( indicesSwapped ) ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition;
1576 // Set the flag to update the decorator.
1577 mEventData->mDecoratorUpdated = true;
1580 void Controller::Impl::RepositionSelectionHandles( float visualX, float visualY )
1582 if( NULL == mEventData )
1584 // Nothing to do if there is no text input.
1588 if( IsShowingPlaceholderText() )
1590 // Nothing to do if there is the place-holder text.
1594 const Length numberOfGlyphs = mVisualModel->mGlyphs.Count();
1595 const Length numberOfLines = mVisualModel->mLines.Count();
1596 if( ( 0 == numberOfGlyphs ) ||
1597 ( 0 == numberOfLines ) )
1599 // Nothing to do if there is no text.
1603 // Find which word was selected
1604 CharacterIndex selectionStart( 0 );
1605 CharacterIndex selectionEnd( 0 );
1606 FindSelectionIndices( visualX, visualY, selectionStart, selectionEnd );
1607 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "%p selectionStart %d selectionEnd %d\n", this, selectionStart, selectionEnd );
1609 if( selectionStart == selectionEnd )
1611 ChangeState( EventData::EDITING );
1612 // Nothing to select. i.e. a white space, out of bounds
1616 mEventData->mLeftSelectionPosition = selectionStart;
1617 mEventData->mRightSelectionPosition = selectionEnd;
1620 void Controller::Impl::SetPopupButtons()
1623 * Sets the Popup buttons to be shown depending on State.
1625 * If SELECTING : CUT & COPY + ( PASTE & CLIPBOARD if content available to paste )
1627 * If EDITING_WITH_POPUP : SELECT & SELECT_ALL
1630 TextSelectionPopup::Buttons buttonsToShow = TextSelectionPopup::NONE;
1632 if( EventData::SELECTING == mEventData->mState )
1634 buttonsToShow = TextSelectionPopup::Buttons( TextSelectionPopup::CUT | TextSelectionPopup::COPY );
1636 if( !IsClipboardEmpty() )
1638 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
1639 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::CLIPBOARD ) );
1642 if( !mEventData->mAllTextSelected )
1644 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::SELECT_ALL ) );
1647 else if( EventData::EDITING_WITH_POPUP == mEventData->mState )
1649 if( mLogicalModel->mText.Count() && !IsShowingPlaceholderText() )
1651 buttonsToShow = TextSelectionPopup::Buttons( TextSelectionPopup::SELECT | TextSelectionPopup::SELECT_ALL );
1654 if( !IsClipboardEmpty() )
1656 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
1657 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::CLIPBOARD ) );
1660 else if( EventData::EDITING_WITH_PASTE_POPUP == mEventData->mState )
1662 if ( !IsClipboardEmpty() )
1664 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
1665 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::CLIPBOARD ) );
1669 mEventData->mDecorator->SetEnabledPopupButtons( buttonsToShow );
1672 void Controller::Impl::ChangeState( EventData::State newState )
1674 if( NULL == mEventData )
1676 // Nothing to do if there is no text input.
1680 DALI_LOG_INFO( gLogFilter, Debug::General, "ChangeState state:%d newstate:%d\n", mEventData->mState, newState );
1682 if( mEventData->mState != newState )
1684 mEventData->mState = newState;
1686 if( EventData::INACTIVE == mEventData->mState )
1688 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
1689 mEventData->mDecorator->StopCursorBlink();
1690 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1691 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1692 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1693 mEventData->mDecorator->SetPopupActive( false );
1694 mEventData->mDecoratorUpdated = true;
1697 else if( EventData::INTERRUPTED == mEventData->mState)
1699 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1700 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1701 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1702 mEventData->mDecorator->SetPopupActive( false );
1703 mEventData->mDecoratorUpdated = true;
1706 else if( EventData::SELECTING == mEventData->mState )
1708 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
1709 mEventData->mDecorator->StopCursorBlink();
1710 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1711 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true );
1712 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true );
1713 if( mEventData->mGrabHandlePopupEnabled )
1716 mEventData->mDecorator->SetPopupActive( true );
1718 mEventData->mDecoratorUpdated = true;
1720 else if( EventData::EDITING == mEventData->mState )
1722 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1723 if( mEventData->mCursorBlinkEnabled )
1725 mEventData->mDecorator->StartCursorBlink();
1727 // Grab handle is not shown until a tap is received whilst EDITING
1728 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1729 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1730 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1731 if( mEventData->mGrabHandlePopupEnabled )
1733 mEventData->mDecorator->SetPopupActive( false );
1735 mEventData->mDecoratorUpdated = true;
1738 else if( EventData::EDITING_WITH_POPUP == mEventData->mState )
1740 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "EDITING_WITH_POPUP \n", newState );
1742 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1743 if( mEventData->mCursorBlinkEnabled )
1745 mEventData->mDecorator->StartCursorBlink();
1747 if( mEventData->mSelectionEnabled )
1749 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1750 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1754 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
1756 if( mEventData->mGrabHandlePopupEnabled )
1759 mEventData->mDecorator->SetPopupActive( true );
1762 mEventData->mDecoratorUpdated = true;
1764 else if( EventData::EDITING_WITH_GRAB_HANDLE == mEventData->mState )
1766 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "EDITING_WITH_GRAB_HANDLE \n", newState );
1768 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1769 if( mEventData->mCursorBlinkEnabled )
1771 mEventData->mDecorator->StartCursorBlink();
1773 // Grab handle is not shown until a tap is received whilst EDITING
1774 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
1775 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1776 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1777 if( mEventData->mGrabHandlePopupEnabled )
1779 mEventData->mDecorator->SetPopupActive( false );
1781 mEventData->mDecoratorUpdated = true;
1784 else if( EventData::SELECTION_HANDLE_PANNING == mEventData->mState )
1786 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
1787 mEventData->mDecorator->StopCursorBlink();
1788 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1789 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true );
1790 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true );
1791 if( mEventData->mGrabHandlePopupEnabled )
1793 mEventData->mDecorator->SetPopupActive( false );
1795 mEventData->mDecoratorUpdated = true;
1797 else if( EventData::GRAB_HANDLE_PANNING == mEventData->mState )
1799 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "GRAB_HANDLE_PANNING \n", newState );
1801 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1802 if( mEventData->mCursorBlinkEnabled )
1804 mEventData->mDecorator->StartCursorBlink();
1806 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
1807 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1808 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1809 if( mEventData->mGrabHandlePopupEnabled )
1811 mEventData->mDecorator->SetPopupActive( false );
1813 mEventData->mDecoratorUpdated = true;
1815 else if( EventData::EDITING_WITH_PASTE_POPUP == mEventData->mState )
1817 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "EDITING_WITH_PASTE_POPUP \n", newState );
1819 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1820 if( mEventData->mCursorBlinkEnabled )
1822 mEventData->mDecorator->StartCursorBlink();
1825 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
1826 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1827 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1829 if( mEventData->mGrabHandlePopupEnabled )
1832 mEventData->mDecorator->SetPopupActive( true );
1835 mEventData->mDecoratorUpdated = true;
1840 LineIndex Controller::Impl::GetClosestLine( float y ) const
1842 float totalHeight = 0.f;
1843 LineIndex lineIndex = 0u;
1845 const Vector<LineRun>& lines = mVisualModel->mLines;
1846 for( LineIndex endLine = lines.Count();
1847 lineIndex < endLine;
1850 const LineRun& lineRun = lines[lineIndex];
1851 totalHeight += lineRun.ascender + -lineRun.descender;
1852 if( y < totalHeight )
1858 if( lineIndex == 0 )
1866 void Controller::Impl::FindSelectionIndices( float visualX, float visualY, CharacterIndex& startIndex, CharacterIndex& endIndex )
1868 CharacterIndex hitCharacter = GetClosestCursorIndex( visualX, visualY );
1869 DALI_ASSERT_DEBUG( hitCharacter <= mLogicalModel->mText.Count() && "GetClosestCursorIndex returned out of bounds index" );
1871 if( mLogicalModel->mText.Count() == 0 )
1873 return; // if model empty
1876 if( hitCharacter >= mLogicalModel->mText.Count() )
1878 // Closest hit character is the last character.
1879 if( hitCharacter == mLogicalModel->mText.Count() )
1881 hitCharacter--; //Hit character index set to last character in logical model
1885 // hitCharacter is out of bounds
1890 startIndex = hitCharacter;
1891 endIndex = hitCharacter;
1892 bool isHitCharacterWhitespace = TextAbstraction::IsWhiteSpace( mLogicalModel->mText[hitCharacter] );
1894 // Find the start and end of the text
1895 for( startIndex = hitCharacter; startIndex > 0; --startIndex )
1897 if( isHitCharacterWhitespace != TextAbstraction::IsWhiteSpace( mLogicalModel->mText[ startIndex-1 ] ) )
1902 const CharacterIndex pastTheEnd = mLogicalModel->mText.Count();
1903 for( endIndex = hitCharacter + 1u; endIndex < pastTheEnd; ++endIndex )
1905 if( isHitCharacterWhitespace != TextAbstraction::IsWhiteSpace( mLogicalModel->mText[ endIndex ] ) )
1912 CharacterIndex Controller::Impl::GetClosestCursorIndex( float visualX,
1915 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "GetClosestCursorIndex %p closest visualX %f visualY %f\n", this, visualX, visualY );
1917 if( NULL == mEventData )
1919 // Nothing to do if there is no text input.
1923 CharacterIndex logicalIndex = 0u;
1925 const Length numberOfGlyphs = mVisualModel->mGlyphs.Count();
1926 const Length numberOfLines = mVisualModel->mLines.Count();
1927 if( ( 0 == numberOfGlyphs ) ||
1928 ( 0 == numberOfLines ) )
1930 return logicalIndex;
1933 // Find which line is closest.
1934 const LineIndex lineIndex = GetClosestLine( visualY );
1935 const LineRun& line = mVisualModel->mLines[lineIndex];
1937 // Get the positions of the glyphs.
1938 const Vector<Vector2>& positions = mVisualModel->mGlyphPositions;
1939 const Vector2* const positionsBuffer = positions.Begin();
1941 // Get the character to glyph conversion table.
1942 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
1944 // Get the glyphs per character table.
1945 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
1947 // Get the glyph's info buffer.
1948 const GlyphInfo* const glyphInfoBuffer = mVisualModel->mGlyphs.Begin();
1950 const CharacterIndex startCharacter = line.characterRun.characterIndex;
1951 const CharacterIndex endCharacter = line.characterRun.characterIndex + line.characterRun.numberOfCharacters;
1952 DALI_ASSERT_DEBUG( endCharacter <= mLogicalModel->mText.Count() && "Invalid line info" );
1954 // Whether this line is a bidirectional line.
1955 const bool bidiLineFetched = mLogicalModel->FetchBidirectionalLineInfo( startCharacter );
1957 // Whether there is a hit on a glyph.
1958 bool matched = false;
1960 // Traverses glyphs in visual order. To do that use the visual to logical conversion table.
1961 CharacterIndex visualIndex = startCharacter;
1962 Length numberOfCharacters = 0u;
1963 for( ; !matched && ( visualIndex < endCharacter ); ++visualIndex )
1965 // The character in logical order.
1966 const CharacterIndex characterLogicalOrderIndex = ( bidiLineFetched ? mLogicalModel->GetLogicalCharacterIndex( visualIndex ) : visualIndex );
1968 // Get the script of the character.
1969 const Script script = mLogicalModel->GetScript( characterLogicalOrderIndex );
1971 // The number of glyphs for that character
1972 const Length numberOfGlyphs = *( glyphsPerCharacterBuffer + characterLogicalOrderIndex );
1973 ++numberOfCharacters;
1976 if( 0u != numberOfGlyphs )
1978 // Get the first character/glyph of the group of glyphs.
1979 const CharacterIndex firstVisualCharacterIndex = 1u + visualIndex - numberOfCharacters;
1980 const CharacterIndex firstLogicalCharacterIndex = ( bidiLineFetched ? mLogicalModel->GetLogicalCharacterIndex( firstVisualCharacterIndex ) : firstVisualCharacterIndex );
1981 const GlyphIndex firstLogicalGlyphIndex = *( charactersToGlyphBuffer + firstLogicalCharacterIndex );
1983 // Get the metrics for the group of glyphs.
1984 GlyphMetrics glyphMetrics;
1985 GetGlyphsMetrics( firstLogicalGlyphIndex,
1991 // Get the position of the first glyph.
1992 const Vector2& position = *( positionsBuffer + firstLogicalGlyphIndex );
1994 // Whether the glyph can be split, like Latin ligatures fi, ff or Arabic ﻻ.
1995 const bool isInterglyphIndex = ( numberOfCharacters > numberOfGlyphs ) && HasLigatureMustBreak( script );
1996 const Length numberOfBlocks = isInterglyphIndex ? numberOfCharacters : 1u;
1997 const float glyphAdvance = glyphMetrics.advance / static_cast<float>( numberOfBlocks );
1999 GlyphIndex index = 0u;
2000 for( ; !matched && ( index < numberOfBlocks ); ++index )
2002 // Find the mid-point of the area containing the glyph
2003 const float glyphCenter = -glyphMetrics.xBearing + position.x + ( static_cast<float>( index ) + 0.5f ) * glyphAdvance;
2005 if( visualX < glyphCenter )
2014 visualIndex = firstVisualCharacterIndex + index;
2018 numberOfCharacters = 0u;
2023 // Return the logical position of the cursor in characters.
2027 visualIndex = endCharacter;
2030 logicalIndex = ( bidiLineFetched ? mLogicalModel->GetLogicalCursorIndex( visualIndex ) : visualIndex );
2031 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "%p closest visualIndex %d logicalIndex %d\n", this, visualIndex, logicalIndex );
2033 DALI_ASSERT_DEBUG( ( logicalIndex <= mLogicalModel->mText.Count() && logicalIndex >= 0 ) && "GetClosestCursorIndex - Out of bounds index" );
2035 return logicalIndex;
2038 void Controller::Impl::GetCursorPosition( CharacterIndex logical,
2039 CursorInfo& cursorInfo )
2041 // TODO: Check for multiline with \n, etc...
2043 const Length numberOfCharacters = mLogicalModel->mText.Count();
2044 if( !IsShowingRealText() )
2046 // Do not want to use the place-holder text to set the cursor position.
2048 // Use the line's height of the font's family set to set the cursor's size.
2049 // If there is no font's family set, use the default font.
2050 // Use the current alignment to place the cursor at the beginning, center or end of the box.
2052 cursorInfo.lineOffset = 0.f;
2053 cursorInfo.lineHeight = GetDefaultFontLineHeight();
2054 cursorInfo.primaryCursorHeight = cursorInfo.lineHeight;
2056 switch( mLayoutEngine.GetHorizontalAlignment() )
2058 case LayoutEngine::HORIZONTAL_ALIGN_BEGIN:
2060 cursorInfo.primaryPosition.x = 0.f;
2063 case LayoutEngine::HORIZONTAL_ALIGN_CENTER:
2065 cursorInfo.primaryPosition.x = floorf( 0.5f * mVisualModel->mControlSize.width );
2068 case LayoutEngine::HORIZONTAL_ALIGN_END:
2070 cursorInfo.primaryPosition.x = mVisualModel->mControlSize.width - mEventData->mDecorator->GetCursorWidth();
2075 switch( mLayoutEngine.GetVerticalAlignment() )
2077 case LayoutEngine::VERTICAL_ALIGN_TOP:
2079 cursorInfo.primaryPosition.y = 0.f;
2082 case LayoutEngine::VERTICAL_ALIGN_CENTER:
2084 cursorInfo.primaryPosition.y = floorf( 0.5f * ( mVisualModel->mControlSize.height - cursorInfo.lineHeight ) );
2087 case LayoutEngine::VERTICAL_ALIGN_BOTTOM:
2089 cursorInfo.primaryPosition.y = mVisualModel->mControlSize.height - cursorInfo.lineHeight;
2094 // Nothing else to do.
2098 // Check if the logical position is the first or the last one of the text.
2099 const bool isFirstPosition = 0u == logical;
2100 const bool isLastPosition = numberOfCharacters == logical;
2102 // 'logical' is the logical 'cursor' index.
2103 // Get the next and current logical 'character' index.
2104 const CharacterIndex nextCharacterIndex = logical;
2105 const CharacterIndex characterIndex = isFirstPosition ? logical : logical - 1u;
2107 // Get the direction of the character and the next one.
2108 const CharacterDirection* const modelCharacterDirectionsBuffer = ( 0u != mLogicalModel->mCharacterDirections.Count() ) ? mLogicalModel->mCharacterDirections.Begin() : NULL;
2110 CharacterDirection isCurrentRightToLeft = false;
2111 CharacterDirection isNextRightToLeft = false;
2112 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
2114 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + characterIndex );
2115 isNextRightToLeft = *( modelCharacterDirectionsBuffer + nextCharacterIndex );
2118 // Get the line where the character is laid-out.
2119 const LineRun* const modelLines = mVisualModel->mLines.Begin();
2121 const LineIndex lineIndex = mVisualModel->GetLineOfCharacter( characterIndex );
2122 const LineRun& line = *( modelLines + lineIndex );
2124 // Get the paragraph's direction.
2125 const CharacterDirection isRightToLeftParagraph = line.direction;
2127 // Check whether there is an alternative position:
2129 cursorInfo.isSecondaryCursor = ( !isLastPosition && ( isCurrentRightToLeft != isNextRightToLeft ) ) ||
2130 ( isLastPosition && ( isRightToLeftParagraph != isCurrentRightToLeft ) );
2132 // Set the line offset and height.
2133 cursorInfo.lineOffset = 0.f;
2134 cursorInfo.lineHeight = line.ascender + -line.descender;
2136 // Calculate the primary cursor.
2138 CharacterIndex index = characterIndex;
2139 if( cursorInfo.isSecondaryCursor )
2141 // If there is a secondary position, the primary cursor may be in a different place than the logical index.
2143 if( isLastPosition )
2145 // The position of the cursor after the last character needs special
2146 // care depending on its direction and the direction of the paragraph.
2148 // Need to find the first character after the last character with the paragraph's direction.
2149 // i.e l0 l1 l2 r0 r1 should find r0.
2151 // TODO: check for more than one line!
2152 index = isRightToLeftParagraph ? line.characterRun.characterIndex : line.characterRun.characterIndex + line.characterRun.numberOfCharacters - 1u;
2153 index = mLogicalModel->GetLogicalCharacterIndex( index );
2157 index = ( isRightToLeftParagraph == isCurrentRightToLeft ) ? characterIndex : nextCharacterIndex;
2161 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
2162 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
2163 const Length* const charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
2164 const CharacterIndex* const glyphsToCharactersBuffer = mVisualModel->mGlyphsToCharacters.Begin();
2165 const Vector2* const glyphPositionsBuffer = mVisualModel->mGlyphPositions.Begin();
2166 const GlyphInfo* const glyphInfoBuffer = mVisualModel->mGlyphs.Begin();
2168 // Convert the cursor position into the glyph position.
2169 const GlyphIndex primaryGlyphIndex = *( charactersToGlyphBuffer + index );
2170 const Length primaryNumberOfGlyphs = *( glyphsPerCharacterBuffer + index );
2171 const Length primaryNumberOfCharacters = *( charactersPerGlyphBuffer + primaryGlyphIndex );
2173 // Get the metrics for the group of glyphs.
2174 GlyphMetrics glyphMetrics;
2175 GetGlyphsMetrics( primaryGlyphIndex,
2176 primaryNumberOfGlyphs,
2181 // Whether to add the glyph's advance to the cursor position.
2182 // 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,
2183 // if the logical cursor is one, the position is the position of the first glyph and the advance is added.
2184 // A 'truth table' was build and an online Karnaugh map tool was used to simplify the logic.
2205 // Where F -> isFirstPosition
2206 // L -> isLastPosition
2207 // C -> isCurrentRightToLeft
2208 // P -> isRightToLeftParagraph
2209 // A -> Whether to add the glyph's advance.
2211 const bool addGlyphAdvance = ( ( isLastPosition && !isRightToLeftParagraph ) ||
2212 ( isFirstPosition && isRightToLeftParagraph ) ||
2213 ( !isFirstPosition && !isLastPosition && !isCurrentRightToLeft ) );
2215 float glyphAdvance = addGlyphAdvance ? glyphMetrics.advance : 0.f;
2217 if( !isLastPosition &&
2218 ( primaryNumberOfCharacters > 1u ) )
2220 const CharacterIndex firstIndex = *( glyphsToCharactersBuffer + primaryGlyphIndex );
2222 bool isCurrentRightToLeft = false;
2223 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
2225 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + index );
2228 Length numberOfGlyphAdvance = ( isFirstPosition ? 0u : 1u ) + characterIndex - firstIndex;
2229 if( isCurrentRightToLeft )
2231 numberOfGlyphAdvance = primaryNumberOfCharacters - numberOfGlyphAdvance;
2234 glyphAdvance = static_cast<float>( numberOfGlyphAdvance ) * glyphMetrics.advance / static_cast<float>( primaryNumberOfCharacters );
2237 // Get the glyph position and x bearing.
2238 const Vector2& primaryPosition = *( glyphPositionsBuffer + primaryGlyphIndex );
2240 // Set the primary cursor's height.
2241 cursorInfo.primaryCursorHeight = cursorInfo.isSecondaryCursor ? 0.5f * glyphMetrics.fontHeight : glyphMetrics.fontHeight;
2243 // Set the primary cursor's position.
2244 cursorInfo.primaryPosition.x = -glyphMetrics.xBearing + primaryPosition.x + glyphAdvance;
2245 cursorInfo.primaryPosition.y = line.ascender - glyphMetrics.ascender;
2247 // Calculate the secondary cursor.
2249 if( cursorInfo.isSecondaryCursor )
2251 // Set the secondary cursor's height.
2252 cursorInfo.secondaryCursorHeight = 0.5f * glyphMetrics.fontHeight;
2254 CharacterIndex index = characterIndex;
2255 if( !isLastPosition )
2257 index = ( isRightToLeftParagraph == isCurrentRightToLeft ) ? nextCharacterIndex : characterIndex;
2260 const GlyphIndex secondaryGlyphIndex = *( charactersToGlyphBuffer + index );
2261 const Length secondaryNumberOfGlyphs = *( glyphsPerCharacterBuffer + index );
2263 const Vector2& secondaryPosition = *( glyphPositionsBuffer + secondaryGlyphIndex );
2265 GetGlyphsMetrics( secondaryGlyphIndex,
2266 secondaryNumberOfGlyphs,
2271 // Set the secondary cursor's position.
2272 cursorInfo.secondaryPosition.x = -glyphMetrics.xBearing + secondaryPosition.x + ( isCurrentRightToLeft ? 0.f : glyphMetrics.advance );
2273 cursorInfo.secondaryPosition.y = cursorInfo.lineHeight - cursorInfo.secondaryCursorHeight - line.descender - ( glyphMetrics.fontHeight - glyphMetrics.ascender );
2276 if( LayoutEngine::MULTI_LINE_BOX == mLayoutEngine.GetLayout() )
2278 // If the text is editable and multi-line, the cursor position after a white space shouldn't exceed the boundaries of the text control.
2280 // Note the white spaces laid-out at the end of the line might exceed the boundaries of the control.
2281 // The reason is a wrapped line must not start with a white space so they are laid-out at the end of the line.
2283 if( 0.f > cursorInfo.primaryPosition.x )
2285 cursorInfo.primaryPosition.x = 0.f;
2288 const float edgeWidth = mVisualModel->mControlSize.width - static_cast<float>( mEventData->mDecorator->GetCursorWidth() );
2289 if( cursorInfo.primaryPosition.x > edgeWidth )
2291 cursorInfo.primaryPosition.x = edgeWidth;
2296 CharacterIndex Controller::Impl::CalculateNewCursorIndex( CharacterIndex index ) const
2298 if( NULL == mEventData )
2300 // Nothing to do if there is no text input.
2304 CharacterIndex cursorIndex = mEventData->mPrimaryCursorPosition;
2306 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
2307 const Length* const charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
2309 GlyphIndex glyphIndex = *( charactersToGlyphBuffer + index );
2310 Length numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
2312 if( numberOfCharacters > 1u )
2314 const Script script = mLogicalModel->GetScript( index );
2315 if( HasLigatureMustBreak( script ) )
2317 // Prevents to jump the whole Latin ligatures like fi, ff, or Arabic ﻻ, ...
2318 numberOfCharacters = 1u;
2323 while( 0u == numberOfCharacters )
2326 numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
2330 if( index < mEventData->mPrimaryCursorPosition )
2332 cursorIndex -= numberOfCharacters;
2336 cursorIndex += numberOfCharacters;
2342 void Controller::Impl::UpdateCursorPosition( const CursorInfo& cursorInfo )
2344 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "-->Controller::UpdateCursorPosition %p\n", this );
2345 if( NULL == mEventData )
2347 // Nothing to do if there is no text input.
2348 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition no event data\n" );
2352 const Vector2 offset = mEventData->mScrollPosition + ( IsShowingRealText() ? mAlignmentOffset : Vector2::ZERO );
2353 const Vector2 cursorPosition = cursorInfo.primaryPosition + offset;
2355 // Sets the cursor position.
2356 mEventData->mDecorator->SetPosition( PRIMARY_CURSOR,
2359 cursorInfo.primaryCursorHeight,
2360 cursorInfo.lineHeight );
2361 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Primary cursor position: %f,%f\n", cursorPosition.x, cursorPosition.y );
2363 // Sets the grab handle position.
2364 mEventData->mDecorator->SetPosition( GRAB_HANDLE,
2366 cursorInfo.lineOffset + offset.y,
2367 cursorInfo.lineHeight );
2369 if( cursorInfo.isSecondaryCursor )
2371 mEventData->mDecorator->SetPosition( SECONDARY_CURSOR,
2372 cursorInfo.secondaryPosition.x + offset.x,
2373 cursorInfo.secondaryPosition.y + offset.y,
2374 cursorInfo.secondaryCursorHeight,
2375 cursorInfo.lineHeight );
2376 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Secondary cursor position: %f,%f\n", cursorInfo.secondaryPosition.x + offset.x, cursorInfo.secondaryPosition.y + offset.y );
2379 // Set which cursors are active according the state.
2380 if( EventData::IsEditingState( mEventData->mState ) || ( EventData::GRAB_HANDLE_PANNING == mEventData->mState ) )
2382 if( cursorInfo.isSecondaryCursor )
2384 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_BOTH );
2388 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
2393 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
2396 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition\n" );
2399 void Controller::Impl::UpdateSelectionHandle( HandleType handleType,
2400 const CursorInfo& cursorInfo )
2402 if( ( LEFT_SELECTION_HANDLE != handleType ) &&
2403 ( RIGHT_SELECTION_HANDLE != handleType ) )
2408 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
2409 const Vector2 cursorPosition = cursorInfo.primaryPosition + offset;
2411 // Sets the handle's position.
2412 mEventData->mDecorator->SetPosition( handleType,
2414 cursorInfo.lineOffset + offset.y,
2415 cursorInfo.lineHeight );
2417 // If selection handle at start of the text and other at end of the text then all text is selected.
2418 const CharacterIndex startOfSelection = std::min( mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition );
2419 const CharacterIndex endOfSelection = std::max ( mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition );
2420 mEventData->mAllTextSelected = ( startOfSelection == 0 ) && ( endOfSelection == mLogicalModel->mText.Count() );
2423 void Controller::Impl::ClampHorizontalScroll( const Vector2& actualSize )
2425 // Clamp between -space & 0 (and the text alignment).
2427 if( actualSize.width > mVisualModel->mControlSize.width )
2429 const float space = ( actualSize.width - mVisualModel->mControlSize.width ) + mAlignmentOffset.x;
2430 mEventData->mScrollPosition.x = ( mEventData->mScrollPosition.x < -space ) ? -space : mEventData->mScrollPosition.x;
2431 mEventData->mScrollPosition.x = ( mEventData->mScrollPosition.x > -mAlignmentOffset.x ) ? -mAlignmentOffset.x : mEventData->mScrollPosition.x;
2433 mEventData->mDecoratorUpdated = true;
2437 mEventData->mScrollPosition.x = 0.f;
2441 void Controller::Impl::ClampVerticalScroll( const Vector2& actualSize )
2443 // Clamp between -space & 0 (and the text alignment).
2444 if( actualSize.height > mVisualModel->mControlSize.height )
2446 const float space = ( actualSize.height - mVisualModel->mControlSize.height ) + mAlignmentOffset.y;
2447 mEventData->mScrollPosition.y = ( mEventData->mScrollPosition.y < -space ) ? -space : mEventData->mScrollPosition.y;
2448 mEventData->mScrollPosition.y = ( mEventData->mScrollPosition.y > -mAlignmentOffset.y ) ? -mAlignmentOffset.y : mEventData->mScrollPosition.y;
2450 mEventData->mDecoratorUpdated = true;
2454 mEventData->mScrollPosition.y = 0.f;
2458 void Controller::Impl::ScrollToMakePositionVisible( const Vector2& position )
2460 // position is in actor's coords.
2461 const float positionEnd = position.x + ( mEventData->mDecorator ? mEventData->mDecorator->GetCursorWidth() : 0.f );
2463 // Transform the position to decorator coords.
2464 const float alignment = IsShowingRealText() ? mAlignmentOffset.x : 0.f;
2465 const float offset = mEventData->mScrollPosition.x + alignment;
2466 const float decoratorPositionBegin = position.x + offset;
2467 const float decoratorPositionEnd = positionEnd + offset;
2469 if( decoratorPositionBegin < 0.f )
2471 mEventData->mScrollPosition.x = -position.x - alignment;
2473 else if( decoratorPositionEnd > mVisualModel->mControlSize.width )
2475 mEventData->mScrollPosition.x = mVisualModel->mControlSize.width - positionEnd - alignment;
2479 void Controller::Impl::ScrollTextToMatchCursor( const CursorInfo& cursorInfo )
2481 // Get the current cursor position in decorator coords.
2482 const Vector2& currentCursorPosition = mEventData->mDecorator->GetPosition( PRIMARY_CURSOR );
2484 // Calculate the offset to match the cursor position before the character was deleted.
2485 mEventData->mScrollPosition.x = currentCursorPosition.x - cursorInfo.primaryPosition.x - mAlignmentOffset.x;
2487 ClampHorizontalScroll( mVisualModel->GetLayoutSize() );
2490 void Controller::Impl::RequestRelayout()
2492 mControlInterface.RequestTextRelayout();
2497 } // namespace Toolkit