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/layouts/layout-parameters.h>
29 #include <dali-toolkit/internal/text/multi-language-support.h>
30 #include <dali-toolkit/internal/text/script-run.h>
31 #include <dali-toolkit/internal/text/segmentation.h>
32 #include <dali-toolkit/internal/text/shaper.h>
33 #include <dali-toolkit/internal/text/text-io.h>
34 #include <dali-toolkit/internal/text/text-view.h>
39 #if defined(DEBUG_ENABLED)
40 Debug::Filter* gLogFilter = Debug::Filter::New(Debug::Concise, true, "LOG_TEXT_CONTROLS");
44 * @brief Some characters can be shaped in more than one glyph.
45 * This struct is used to retrieve metrics from these group of glyphs.
59 float fontHeight; ///< The font's height of that glyphs.
60 float advance; ///< The sum of all the advances of all the glyphs.
61 float ascender; ///< The font's ascender.
62 float xBearing; ///< The x bearing of the first glyph.
65 const std::string EMPTY_STRING("");
79 * @brief Get some glyph's metrics of a group of glyphs formed as a result of shaping one character.
81 * @param[in] glyphIndex The index to the first glyph.
82 * @param[in] numberOfGlyphs The number of glyphs.
83 * @param[out] glyphMetrics Some glyph metrics (font height, advance, ascender and x bearing).
87 void GetGlyphsMetrics( GlyphIndex glyphIndex,
88 Length numberOfGlyphs,
89 GlyphMetrics& glyphMetrics,
90 VisualModelPtr visualModel,
91 TextAbstraction::FontClient& fontClient )
93 const GlyphInfo* glyphsBuffer = visualModel->mGlyphs.Begin();
95 const GlyphInfo& firstGlyph = *( glyphsBuffer + glyphIndex );
97 Text::FontMetrics fontMetrics;
98 fontClient.GetFontMetrics( firstGlyph.fontId, fontMetrics );
100 glyphMetrics.fontHeight = fontMetrics.height;
101 glyphMetrics.advance = firstGlyph.advance;
102 glyphMetrics.ascender = fontMetrics.ascender;
103 glyphMetrics.xBearing = firstGlyph.xBearing;
105 for( unsigned int i = 1u; i < numberOfGlyphs; ++i )
107 const GlyphInfo& glyphInfo = *( glyphsBuffer + glyphIndex + i );
109 glyphMetrics.advance += glyphInfo.advance;
113 EventData::EventData( DecoratorPtr decorator )
114 : mDecorator( decorator ),
115 mPlaceholderTextActive(),
116 mPlaceholderTextInactive(),
117 mPlaceholderTextColor( 0.8f, 0.8f, 0.8f, 0.8f ),
121 mPrimaryCursorPosition( 0u ),
122 mLeftSelectionPosition( 0u ),
123 mRightSelectionPosition( 0u ),
124 mPreEditStartPosition( 0u ),
125 mPreEditLength( 0u ),
126 mIsShowingPlaceholderText( false ),
127 mPreEditFlag( false ),
128 mDecoratorUpdated( false ),
129 mCursorBlinkEnabled( true ),
130 mGrabHandleEnabled( true ),
131 mGrabHandlePopupEnabled( false ),
132 mSelectionEnabled( false ),
133 mHorizontalScrollingEnabled( true ),
134 mVerticalScrollingEnabled( false ),
135 mUpdateCursorPosition( false ),
136 mUpdateLeftSelectionPosition( false ),
137 mUpdateRightSelectionPosition( false ),
138 mScrollAfterUpdateCursorPosition( false )
141 EventData::~EventData()
144 bool Controller::Impl::ProcessInputEvents()
146 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "-->Controller::ProcessInputEvents\n" );
147 if( NULL == mEventData )
149 // Nothing to do if there is no text input.
150 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::ProcessInputEvents no event data\n" );
154 if( mEventData->mDecorator )
156 for( std::vector<Event>::iterator iter = mEventData->mEventQueue.begin();
157 iter != mEventData->mEventQueue.end();
162 case Event::CURSOR_KEY_EVENT:
164 OnCursorKeyEvent( *iter );
167 case Event::TAP_EVENT:
172 case Event::PAN_EVENT:
177 case Event::GRAB_HANDLE_EVENT:
178 case Event::LEFT_SELECTION_HANDLE_EVENT:
179 case Event::RIGHT_SELECTION_HANDLE_EVENT: // Fall through
181 OnHandleEvent( *iter );
188 // The cursor must also be repositioned after inserts into the model
189 if( mEventData->mUpdateCursorPosition )
191 // Updates the cursor position and scrolls the text to make it visible.
193 UpdateCursorPosition();
195 if( mEventData->mScrollAfterUpdateCursorPosition )
197 ScrollToMakeCursorVisible();
198 mEventData->mScrollAfterUpdateCursorPosition = false;
201 mEventData->mDecoratorUpdated = true;
202 mEventData->mUpdateCursorPosition = false;
204 else if( mEventData->mUpdateLeftSelectionPosition )
206 UpdateSelectionHandle( LEFT_SELECTION_HANDLE );
208 if( mEventData->mScrollAfterUpdateCursorPosition )
210 ScrollToMakeCursorVisible();
211 mEventData->mScrollAfterUpdateCursorPosition = false;
214 mEventData->mDecoratorUpdated = true;
215 mEventData->mUpdateLeftSelectionPosition = false;
217 else if( mEventData->mUpdateRightSelectionPosition )
219 UpdateSelectionHandle( RIGHT_SELECTION_HANDLE );
221 if( mEventData->mScrollAfterUpdateCursorPosition )
223 ScrollToMakeCursorVisible();
224 mEventData->mScrollAfterUpdateCursorPosition = false;
227 mEventData->mDecoratorUpdated = true;
228 mEventData->mUpdateRightSelectionPosition = false;
231 mEventData->mEventQueue.clear();
233 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::ProcessInputEvents\n" );
235 bool decoratorUpdated = mEventData->mDecoratorUpdated;
236 mEventData->mDecoratorUpdated = false;
237 return decoratorUpdated;
240 void Controller::Impl::UpdateModel( OperationsMask operationsRequired )
242 // Calculate the operations to be done.
243 const OperationsMask operations = static_cast<OperationsMask>( mOperationsPending & operationsRequired );
245 Vector<Character>& utf32Characters = mLogicalModel->mText;
247 const Length numberOfCharacters = utf32Characters.Count();
249 Vector<LineBreakInfo>& lineBreakInfo = mLogicalModel->mLineBreakInfo;
250 if( GET_LINE_BREAKS & operations )
252 // Retrieves the line break info. The line break info is used to split the text in 'paragraphs' to
253 // calculate the bidirectional info for each 'paragraph'.
254 // It's also used to layout the text (where it should be a new line) or to shape the text (text in different lines
255 // is not shaped together).
256 lineBreakInfo.Resize( numberOfCharacters, TextAbstraction::LINE_NO_BREAK );
258 SetLineBreakInfo( utf32Characters,
262 Vector<WordBreakInfo>& wordBreakInfo = mLogicalModel->mWordBreakInfo;
263 if( GET_WORD_BREAKS & operations )
265 // Retrieves the word break info. The word break info is used to layout the text (where to wrap the text in lines).
266 wordBreakInfo.Resize( numberOfCharacters, TextAbstraction::WORD_NO_BREAK );
268 SetWordBreakInfo( utf32Characters,
272 const bool getScripts = GET_SCRIPTS & operations;
273 const bool validateFonts = VALIDATE_FONTS & operations;
275 Vector<ScriptRun>& scripts = mLogicalModel->mScriptRuns;
276 Vector<FontRun>& validFonts = mLogicalModel->mFontRuns;
278 if( getScripts || validateFonts )
280 // Validates the fonts assigned by the application or assigns default ones.
281 // It makes sure all the characters are going to be rendered by the correct font.
282 MultilanguageSupport multilanguageSupport = MultilanguageSupport::Get();
286 // Retrieves the scripts used in the text.
287 multilanguageSupport.SetScripts( utf32Characters,
294 if( 0u == validFonts.Count() )
296 // Copy the requested font defaults received via the property system.
297 // These may not be valid i.e. may not contain glyphs for the necessary scripts.
298 GetDefaultFonts( validFonts, numberOfCharacters );
301 // Validates the fonts. If there is a character with no assigned font it sets a default one.
302 // After this call, fonts are validated.
303 multilanguageSupport.ValidateFonts( utf32Characters,
309 Vector<Character> mirroredUtf32Characters;
310 bool textMirrored = false;
311 if( BIDI_INFO & operations )
313 // Count the number of LINE_NO_BREAK to reserve some space for the vector of paragraph's
314 // bidirectional info.
316 Length numberOfParagraphs = 0u;
318 const TextAbstraction::LineBreakInfo* lineBreakInfoBuffer = lineBreakInfo.Begin();
319 for( Length index = 0u; index < numberOfCharacters; ++index )
321 if( TextAbstraction::LINE_NO_BREAK == *( lineBreakInfoBuffer + index ) )
323 ++numberOfParagraphs;
327 Vector<BidirectionalParagraphInfoRun>& bidirectionalInfo = mLogicalModel->mBidirectionalParagraphInfo;
328 bidirectionalInfo.Reserve( numberOfParagraphs );
330 // Calculates the bidirectional info for the whole paragraph if it contains right to left scripts.
331 SetBidirectionalInfo( utf32Characters,
336 if( 0u != bidirectionalInfo.Count() )
338 // This paragraph has right to left text. Some characters may need to be mirrored.
339 // TODO: consider if the mirrored string can be stored as well.
341 textMirrored = GetMirroredText( utf32Characters, mirroredUtf32Characters );
343 // Only set the character directions if there is right to left characters.
344 Vector<CharacterDirection>& directions = mLogicalModel->mCharacterDirections;
345 directions.Resize( numberOfCharacters );
347 GetCharactersDirection( bidirectionalInfo,
352 // There is no right to left characters. Clear the directions vector.
353 mLogicalModel->mCharacterDirections.Clear();
358 Vector<GlyphInfo>& glyphs = mVisualModel->mGlyphs;
359 Vector<CharacterIndex>& glyphsToCharactersMap = mVisualModel->mGlyphsToCharacters;
360 Vector<Length>& charactersPerGlyph = mVisualModel->mCharactersPerGlyph;
361 if( SHAPE_TEXT & operations )
363 const Vector<Character>& textToShape = textMirrored ? mirroredUtf32Characters : utf32Characters;
365 ShapeText( textToShape,
370 glyphsToCharactersMap,
371 charactersPerGlyph );
373 // Create the 'number of glyphs' per character and the glyph to character conversion tables.
374 mVisualModel->CreateGlyphsPerCharacterTable( numberOfCharacters );
375 mVisualModel->CreateCharacterToGlyphTable( numberOfCharacters );
378 const Length numberOfGlyphs = glyphs.Count();
380 if( GET_GLYPH_METRICS & operations )
382 mFontClient.GetGlyphMetrics( glyphs.Begin(), numberOfGlyphs );
386 void Controller::Impl::GetDefaultFonts( Vector<FontRun>& fonts, Length numberOfCharacters )
391 fontRun.characterRun.characterIndex = 0;
392 fontRun.characterRun.numberOfCharacters = numberOfCharacters;
393 fontRun.fontId = mFontDefaults->GetFontId( mFontClient );
394 fontRun.isDefault = true;
396 fonts.PushBack( fontRun );
400 void Controller::Impl::OnCursorKeyEvent( const Event& event )
402 if( NULL == mEventData )
404 // Nothing to do if there is no text input.
408 int keyCode = event.p1.mInt;
410 if( Dali::DALI_KEY_CURSOR_LEFT == keyCode )
412 if( mEventData->mPrimaryCursorPosition > 0u )
414 mEventData->mPrimaryCursorPosition = CalculateNewCursorIndex( mEventData->mPrimaryCursorPosition - 1u );
417 else if( Dali::DALI_KEY_CURSOR_RIGHT == keyCode )
419 if( mLogicalModel->GetNumberOfCharacters() > mEventData->mPrimaryCursorPosition )
421 mEventData->mPrimaryCursorPosition = CalculateNewCursorIndex( mEventData->mPrimaryCursorPosition );
424 else if( Dali::DALI_KEY_CURSOR_UP == keyCode )
428 else if( Dali::DALI_KEY_CURSOR_DOWN == keyCode )
433 mEventData->mUpdateCursorPosition = true;
434 mEventData->mScrollAfterUpdateCursorPosition = true;
437 void Controller::Impl::OnTapEvent( const Event& event )
439 if( NULL != mEventData )
441 const unsigned int tapCount = event.p1.mUint;
445 if( ! IsShowingPlaceholderText() )
447 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
448 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
450 mEventData->mPrimaryCursorPosition = GetClosestCursorIndex( xPosition,
455 mEventData->mPrimaryCursorPosition = 0u;
458 mEventData->mUpdateCursorPosition = true;
459 mEventData->mScrollAfterUpdateCursorPosition = true;
461 else if( mEventData->mSelectionEnabled &&
464 RepositionSelectionHandles( event.p2.mFloat, event.p3.mFloat );
469 void Controller::Impl::OnPanEvent( const Event& event )
471 if( NULL == mEventData )
473 // Nothing to do if there is no text input.
477 int state = event.p1.mInt;
479 if( Gesture::Started == state ||
480 Gesture::Continuing == state )
482 const Vector2& actualSize = mVisualModel->GetActualSize();
483 const Vector2 currentScroll = mEventData->mScrollPosition;
485 if( mEventData->mHorizontalScrollingEnabled )
487 const float displacementX = event.p2.mFloat;
488 mEventData->mScrollPosition.x += displacementX;
490 ClampHorizontalScroll( actualSize );
493 if( mEventData->mVerticalScrollingEnabled )
495 const float displacementY = event.p3.mFloat;
496 mEventData->mScrollPosition.y += displacementY;
498 ClampVerticalScroll( actualSize );
501 if( mEventData->mDecorator )
503 mEventData->mDecorator->UpdatePositions( mEventData->mScrollPosition - currentScroll );
508 void Controller::Impl::OnHandleEvent( const Event& event )
510 if( NULL == mEventData )
512 // Nothing to do if there is no text input.
516 const unsigned int state = event.p1.mUint;
518 if( HANDLE_PRESSED == state )
520 // The event.p2 and event.p3 are in decorator coords. Need to transforms to text coords.
521 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
522 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
524 const CharacterIndex handleNewPosition = GetClosestCursorIndex( xPosition, yPosition );
526 if( Event::GRAB_HANDLE_EVENT == event.type )
528 ChangeState ( EventData::EDITING );
530 if( handleNewPosition != mEventData->mPrimaryCursorPosition )
532 mEventData->mPrimaryCursorPosition = handleNewPosition;
533 mEventData->mUpdateCursorPosition = true;
536 else if( Event::LEFT_SELECTION_HANDLE_EVENT == event.type )
538 if( handleNewPosition != mEventData->mLeftSelectionPosition )
540 mEventData->mLeftSelectionPosition = handleNewPosition;
541 mEventData->mUpdateLeftSelectionPosition = true;
544 else if( Event::RIGHT_SELECTION_HANDLE_EVENT == event.type )
546 if( handleNewPosition != mEventData->mRightSelectionPosition )
548 mEventData->mRightSelectionPosition = handleNewPosition;
549 mEventData->mUpdateRightSelectionPosition = true;
553 else if( ( HANDLE_RELEASED == state ) ||
554 ( HANDLE_STOP_SCROLLING == state ) )
556 if( mEventData->mGrabHandlePopupEnabled )
558 ChangeState( EventData::EDITING_WITH_POPUP );
560 if( Event::GRAB_HANDLE_EVENT == event.type )
562 mEventData->mUpdateCursorPosition = true;
564 if( HANDLE_STOP_SCROLLING == state )
566 // The event.p2 and event.p3 are in decorator coords. Need to transforms to text coords.
567 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
568 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
570 mEventData->mPrimaryCursorPosition = GetClosestCursorIndex( xPosition, yPosition );
572 mEventData->mScrollAfterUpdateCursorPosition = true;
575 mEventData->mDecoratorUpdated = true;
577 else if( HANDLE_SCROLLING == state )
579 const float xSpeed = event.p2.mFloat;
580 const Vector2& actualSize = mVisualModel->GetActualSize();
582 mEventData->mScrollPosition.x += xSpeed;
584 ClampHorizontalScroll( actualSize );
586 mEventData->mDecoratorUpdated = true;
590 void Controller::Impl::RepositionSelectionHandles( float visualX, float visualY )
592 if( NULL == mEventData )
594 // Nothing to do if there is no text input.
598 // TODO - Find which word was selected
600 const Vector<GlyphInfo>& glyphs = mVisualModel->mGlyphs;
601 const Vector<Vector2>::SizeType glyphCount = glyphs.Count();
603 const Vector<Vector2>& positions = mVisualModel->mGlyphPositions;
604 const Vector<Vector2>::SizeType positionCount = positions.Count();
606 // Guard against glyphs which did not fit inside the layout
607 const Vector<Vector2>::SizeType count = (positionCount < glyphCount) ? positionCount : glyphCount;
611 float primaryX = positions[0].x + mEventData->mScrollPosition.x;
612 float secondaryX = positions[count-1].x + glyphs[count-1].width + mEventData->mScrollPosition.x;
614 // TODO - multi-line selection
615 const Vector<LineRun>& lines = mVisualModel->mLines;
616 float height = lines.Count() ? lines[0].ascender + -lines[0].descender : 0.0f;
618 mEventData->mDecorator->SetPosition( LEFT_SELECTION_HANDLE, primaryX, mEventData->mScrollPosition.y, height );
619 mEventData->mDecorator->SetPosition( RIGHT_SELECTION_HANDLE, secondaryX, mEventData->mScrollPosition.y, height );
621 mEventData->mDecorator->ClearHighlights();
622 mEventData->mDecorator->AddHighlight( primaryX, mEventData->mScrollPosition.y, secondaryX, height + mEventData->mScrollPosition.y );
626 void Controller::Impl::ChangeState( EventData::State newState )
628 if( NULL == mEventData )
630 // Nothing to do if there is no text input.
634 if( mEventData->mState != newState )
636 mEventData->mState = newState;
638 if( EventData::INACTIVE == mEventData->mState )
640 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
641 mEventData->mDecorator->StopCursorBlink();
642 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
643 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
644 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
645 mEventData->mDecorator->SetPopupActive( false );
646 mEventData->mDecoratorUpdated = true;
648 else if ( EventData::SELECTING == mEventData->mState )
650 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
651 mEventData->mDecorator->StopCursorBlink();
652 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
653 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true );
654 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true );
655 mEventData->mDecoratorUpdated = true;
657 else if( EventData::EDITING == mEventData->mState )
659 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
660 if( mEventData->mCursorBlinkEnabled )
662 mEventData->mDecorator->StartCursorBlink();
664 // Grab handle is not shown until a tap is received whilst EDITING
665 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
666 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
667 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
668 mEventData->mDecoratorUpdated = true;
670 else if( EventData::EDITING_WITH_POPUP == mEventData->mState )
672 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
673 if( mEventData->mCursorBlinkEnabled )
675 mEventData->mDecorator->StartCursorBlink();
677 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
678 if( mEventData->mSelectionEnabled )
680 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true );
681 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true );
683 if( mEventData->mGrabHandlePopupEnabled )
685 mEventData->mDecorator->SetPopupActive( true );
687 mEventData->mDecoratorUpdated = true;
692 LineIndex Controller::Impl::GetClosestLine( float y ) const
694 float totalHeight = 0.f;
695 LineIndex lineIndex = 0u;
697 const Vector<LineRun>& lines = mVisualModel->mLines;
698 for( LineIndex endLine = lines.Count();
702 const LineRun& lineRun = lines[lineIndex];
703 totalHeight += lineRun.ascender + -lineRun.descender;
704 if( y < totalHeight )
713 CharacterIndex Controller::Impl::GetClosestCursorIndex( float visualX,
716 if( NULL == mEventData )
718 // Nothing to do if there is no text input.
722 CharacterIndex logicalIndex = 0u;
724 const Length numberOfGlyphs = mVisualModel->mGlyphs.Count();
725 const Length numberOfLines = mVisualModel->mLines.Count();
726 if( 0 == numberOfGlyphs ||
732 // Find which line is closest
733 const LineIndex lineIndex = GetClosestLine( visualY );
734 const LineRun& line = mVisualModel->mLines[lineIndex];
736 // Get the positions of the glyphs.
737 const Vector<Vector2>& positions = mVisualModel->mGlyphPositions;
738 const Vector2* const positionsBuffer = positions.Begin();
740 // Get the visual to logical conversion tables.
741 const CharacterIndex* const visualToLogicalBuffer = ( 0u != mLogicalModel->mVisualToLogicalMap.Count() ) ? mLogicalModel->mVisualToLogicalMap.Begin() : NULL;
742 const CharacterIndex* const visualToLogicalCursorBuffer = mLogicalModel->mVisualToLogicalCursorMap.Begin();
744 // Get the character to glyph conversion table.
745 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
747 // Get the glyphs per character table.
748 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
750 // If the vector is void, there is no right to left characters.
751 const bool hasRightToLeftCharacters = NULL != visualToLogicalBuffer;
753 const CharacterIndex startCharacter = line.characterRun.characterIndex;
754 const CharacterIndex endCharacter = line.characterRun.characterIndex + line.characterRun.numberOfCharacters;
755 DALI_ASSERT_DEBUG( endCharacter <= mLogicalModel->mText.Count() && "Invalid line info" );
757 // Whether there is a hit on a glyph.
758 bool matched = false;
760 // Traverses glyphs in visual order. To do that use the visual to logical conversion table.
761 CharacterIndex visualIndex = startCharacter;
762 for( ; !matched && ( visualIndex < endCharacter ); ++visualIndex )
764 // The character in logical order.
765 const CharacterIndex characterLogicalOrderIndex = hasRightToLeftCharacters ? *( visualToLogicalBuffer + visualIndex ) : visualIndex;
767 // The first glyph for that character in logical order.
768 const GlyphIndex glyphLogicalOrderIndex = *( charactersToGlyphBuffer + characterLogicalOrderIndex );
770 // The number of glyphs for that character
771 const Length numberOfGlyphs = *( glyphsPerCharacterBuffer + characterLogicalOrderIndex );
773 // Get the metrics for the group of glyphs.
774 GlyphMetrics glyphMetrics;
775 GetGlyphsMetrics( glyphLogicalOrderIndex,
781 const Vector2& position = *( positionsBuffer + glyphLogicalOrderIndex );
783 const float glyphX = -glyphMetrics.xBearing + position.x + 0.5f * glyphMetrics.advance;
785 if( visualX < glyphX )
792 // Return the logical position of the cursor in characters.
796 visualIndex = endCharacter;
799 return hasRightToLeftCharacters ? *( visualToLogicalCursorBuffer + visualIndex ) : visualIndex;
802 void Controller::Impl::GetCursorPosition( CharacterIndex logical,
803 CursorInfo& cursorInfo )
805 // TODO: Check for multiline with \n, etc...
807 // Check if the logical position is the first or the last one of the text.
808 const bool isFirstPosition = 0u == logical;
809 const bool isLastPosition = mLogicalModel->GetNumberOfCharacters() == logical;
811 if( isFirstPosition && isLastPosition )
813 // There is zero characters. Get the default font.
815 FontId defaultFontId = 0u;
816 if( NULL == mFontDefaults )
818 defaultFontId = mFontClient.GetFontId( EMPTY_STRING,
823 defaultFontId = mFontDefaults->GetFontId( mFontClient );
826 Text::FontMetrics fontMetrics;
827 mFontClient.GetFontMetrics( defaultFontId, fontMetrics );
829 cursorInfo.lineHeight = fontMetrics.ascender - fontMetrics.descender;
830 cursorInfo.primaryCursorHeight = cursorInfo.lineHeight;
832 cursorInfo.primaryPosition.x = 0.f;
833 cursorInfo.primaryPosition.y = 0.f;
835 // Nothing else to do.
839 // Get the previous logical index.
840 const CharacterIndex previousLogical = isFirstPosition ? 0u : logical - 1u;
842 // Decrease the logical index if it's the last one.
848 // Get the direction of the character and the previous one.
849 const CharacterDirection* const modelCharacterDirectionsBuffer = ( 0u != mLogicalModel->mCharacterDirections.Count() ) ? mLogicalModel->mCharacterDirections.Begin() : NULL;
851 CharacterDirection isCurrentRightToLeft = false;
852 CharacterDirection isPreviousRightToLeft = false;
853 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
855 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + logical );
856 isPreviousRightToLeft = *( modelCharacterDirectionsBuffer + previousLogical );
859 // Get the line where the character is laid-out.
860 const LineRun* modelLines = mVisualModel->mLines.Begin();
862 const LineIndex lineIndex = mVisualModel->GetLineOfCharacter( logical );
863 const LineRun& line = *( modelLines + lineIndex );
865 // Get the paragraph's direction.
866 const CharacterDirection isRightToLeftParagraph = line.direction;
868 // Check whether there is an alternative position:
870 cursorInfo.isSecondaryCursor = ( isCurrentRightToLeft != isPreviousRightToLeft ) ||
871 ( isLastPosition && ( isRightToLeftParagraph != isCurrentRightToLeft ) );
873 // Set the line height.
874 cursorInfo.lineHeight = line.ascender + -line.descender;
876 // Convert the cursor position into the glyph position.
877 CharacterIndex characterIndex = logical;
878 if( cursorInfo.isSecondaryCursor &&
879 ( isRightToLeftParagraph != isCurrentRightToLeft ) )
881 characterIndex = previousLogical;
884 const GlyphIndex currentGlyphIndex = *( mVisualModel->mCharactersToGlyph.Begin() + characterIndex );
885 const Length numberOfGlyphs = *( mVisualModel->mGlyphsPerCharacter.Begin() + characterIndex );
886 const Length numberOfCharacters = *( mVisualModel->mCharactersPerGlyph.Begin() +currentGlyphIndex );
888 // Get the metrics for the group of glyphs.
889 GlyphMetrics glyphMetrics;
890 GetGlyphsMetrics( currentGlyphIndex,
896 float interGlyphAdvance = 0.f;
897 if( !isLastPosition &&
898 ( numberOfCharacters > 1u ) )
900 const CharacterIndex firstIndex = *( mVisualModel->mGlyphsToCharacters.Begin() + currentGlyphIndex );
901 interGlyphAdvance = static_cast<float>( characterIndex - firstIndex ) * glyphMetrics.advance / static_cast<float>( numberOfCharacters );
904 // Get the glyph position and x bearing.
905 const Vector2& currentPosition = *( mVisualModel->mGlyphPositions.Begin() + currentGlyphIndex );
907 // Set the cursor's height.
908 cursorInfo.primaryCursorHeight = glyphMetrics.fontHeight;
911 cursorInfo.primaryPosition.x = -glyphMetrics.xBearing + currentPosition.x + ( isCurrentRightToLeft ? glyphMetrics.advance : interGlyphAdvance );
912 cursorInfo.primaryPosition.y = line.ascender - glyphMetrics.ascender;
916 // The position of the cursor after the last character needs special
917 // care depending on its direction and the direction of the paragraph.
919 if( cursorInfo.isSecondaryCursor )
921 // Need to find the first character after the last character with the paragraph's direction.
922 // i.e l0 l1 l2 r0 r1 should find r0.
924 // TODO: check for more than one line!
925 characterIndex = isRightToLeftParagraph ? line.characterRun.characterIndex : line.characterRun.characterIndex + line.characterRun.numberOfCharacters - 1u;
926 characterIndex = mLogicalModel->GetLogicalCharacterIndex( characterIndex );
928 const GlyphIndex glyphIndex = *( mVisualModel->mCharactersToGlyph.Begin() + characterIndex );
929 const Length numberOfGlyphs = *( mVisualModel->mGlyphsPerCharacter.Begin() + characterIndex );
931 const Vector2& position = *( mVisualModel->mGlyphPositions.Begin() + glyphIndex );
933 // Get the metrics for the group of glyphs.
934 GlyphMetrics glyphMetrics;
935 GetGlyphsMetrics( glyphIndex,
941 cursorInfo.primaryPosition.x = -glyphMetrics.xBearing + position.x + ( isRightToLeftParagraph ? 0.f : glyphMetrics.advance );
943 cursorInfo.primaryPosition.y = line.ascender - glyphMetrics.ascender;
947 if( !isCurrentRightToLeft )
949 cursorInfo.primaryPosition.x += glyphMetrics.advance;
953 cursorInfo.primaryPosition.x -= glyphMetrics.advance;
958 // Set the alternative cursor position.
959 if( cursorInfo.isSecondaryCursor )
961 // Convert the cursor position into the glyph position.
962 const CharacterIndex previousCharacterIndex = ( ( isRightToLeftParagraph != isCurrentRightToLeft ) ? logical : previousLogical );
963 const GlyphIndex previousGlyphIndex = *( mVisualModel->mCharactersToGlyph.Begin() + previousCharacterIndex );
964 const Length numberOfGlyphs = *( mVisualModel->mGlyphsPerCharacter.Begin() + previousCharacterIndex );
966 // Get the glyph position.
967 const Vector2& previousPosition = *( mVisualModel->mGlyphPositions.Begin() + previousGlyphIndex );
969 // Get the metrics for the group of glyphs.
970 GlyphMetrics glyphMetrics;
971 GetGlyphsMetrics( previousGlyphIndex,
977 // Set the cursor position and height.
978 cursorInfo.secondaryPosition.x = -glyphMetrics.xBearing + previousPosition.x + ( ( ( isLastPosition && !isCurrentRightToLeft ) ||
979 ( !isLastPosition && isCurrentRightToLeft ) ) ? glyphMetrics.advance : 0.f );
981 cursorInfo.secondaryCursorHeight = 0.5f * glyphMetrics.fontHeight;
983 cursorInfo.secondaryPosition.y = cursorInfo.lineHeight - cursorInfo.secondaryCursorHeight - line.descender - ( glyphMetrics.fontHeight - glyphMetrics.ascender );
985 // Update the primary cursor height as well.
986 cursorInfo.primaryCursorHeight *= 0.5f;
990 CharacterIndex Controller::Impl::CalculateNewCursorIndex( CharacterIndex index ) const
992 if( NULL == mEventData )
994 // Nothing to do if there is no text input.
998 CharacterIndex cursorIndex = mEventData->mPrimaryCursorPosition;
1000 const Script script = mLogicalModel->GetScript( index );
1001 const GlyphIndex* charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
1002 const Length* charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
1004 Length numberOfCharacters = 0u;
1005 if( TextAbstraction::LATIN == script )
1007 // Prevents to jump the whole Latin ligatures like fi, ff, ...
1008 numberOfCharacters = 1u;
1012 GlyphIndex glyphIndex = *( charactersToGlyphBuffer + index );
1013 numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
1015 while( 0u == numberOfCharacters )
1017 numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
1022 if( index < mEventData->mPrimaryCursorPosition )
1024 cursorIndex -= numberOfCharacters;
1028 cursorIndex += numberOfCharacters;
1034 void Controller::Impl::UpdateCursorPosition()
1036 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "-->Controller::UpdateCursorPosition %p\n", this );
1037 if( NULL == mEventData )
1039 // Nothing to do if there is no text input.
1040 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition no event data\n" );
1044 if( IsShowingPlaceholderText() )
1046 // Do not want to use the place-holder text to set the cursor position.
1048 // Use the line's height of the font's family set to set the cursor's size.
1049 // If there is no font's family set, use the default font.
1050 // Use the current alignment to place the cursor at the beginning, center or end of the box.
1052 float lineHeight = 0.f;
1054 FontId defaultFontId = 0u;
1055 if( NULL == mFontDefaults )
1057 defaultFontId = mFontClient.GetFontId( EMPTY_STRING,
1062 defaultFontId = mFontDefaults->GetFontId( mFontClient );
1065 Text::FontMetrics fontMetrics;
1066 mFontClient.GetFontMetrics( defaultFontId, fontMetrics );
1068 lineHeight = fontMetrics.ascender - fontMetrics.descender;
1071 Vector2 cursorPosition;
1073 switch( mLayoutEngine.GetHorizontalAlignment() )
1075 case LayoutEngine::HORIZONTAL_ALIGN_BEGIN:
1077 cursorPosition.x = 1.f;
1080 case LayoutEngine::HORIZONTAL_ALIGN_CENTER:
1082 cursorPosition.x = floor( 0.5f * mControlSize.width );
1085 case LayoutEngine::HORIZONTAL_ALIGN_END:
1087 cursorPosition.x = mControlSize.width;
1092 mEventData->mDecorator->SetPosition( PRIMARY_CURSOR,
1100 CursorInfo cursorInfo;
1101 GetCursorPosition( mEventData->mPrimaryCursorPosition,
1104 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
1105 const Vector2 cursorPosition = cursorInfo.primaryPosition + offset;
1107 // Sets the cursor position.
1108 mEventData->mDecorator->SetPosition( PRIMARY_CURSOR,
1111 cursorInfo.primaryCursorHeight,
1112 cursorInfo.lineHeight );
1113 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Primary cursor position: %f,%f\n", cursorPosition.x, cursorPosition.y );
1115 // Sets the grab handle position.
1116 mEventData->mDecorator->SetPosition( GRAB_HANDLE,
1119 cursorInfo.lineHeight );
1121 if( cursorInfo.isSecondaryCursor )
1123 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_BOTH );
1124 mEventData->mDecorator->SetPosition( SECONDARY_CURSOR,
1125 cursorInfo.secondaryPosition.x + offset.x,
1126 cursorInfo.secondaryPosition.y + offset.y,
1127 cursorInfo.secondaryCursorHeight,
1128 cursorInfo.lineHeight );
1129 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Secondary cursor position: %f,%f\n", cursorInfo.secondaryPosition.x + offset.x, cursorInfo.secondaryPosition.y + offset.y );
1133 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1136 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition\n" );
1139 void Controller::Impl::UpdateSelectionHandle( HandleType handleType )
1141 if( ( LEFT_SELECTION_HANDLE != handleType ) &&
1142 ( RIGHT_SELECTION_HANDLE != handleType ) )
1147 const bool leftSelectionHandle = LEFT_SELECTION_HANDLE == handleType;
1148 const CharacterIndex index = leftSelectionHandle ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition;
1150 CursorInfo cursorInfo;
1151 GetCursorPosition( index,
1154 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
1155 const Vector2 cursorPosition = cursorInfo.primaryPosition + offset;
1157 // Sets the grab handle position.
1158 mEventData->mDecorator->SetPosition( handleType,
1161 cursorInfo.lineHeight );
1164 void Controller::Impl::ClampHorizontalScroll( const Vector2& actualSize )
1166 // Clamp between -space & 0 (and the text alignment).
1167 if( actualSize.width > mControlSize.width )
1169 const float space = ( actualSize.width - mControlSize.width ) + mAlignmentOffset.x;
1170 mEventData->mScrollPosition.x = ( mEventData->mScrollPosition.x < -space ) ? -space : mEventData->mScrollPosition.x;
1171 mEventData->mScrollPosition.x = ( mEventData->mScrollPosition.x > -mAlignmentOffset.x ) ? -mAlignmentOffset.x : mEventData->mScrollPosition.x;
1173 mEventData->mDecoratorUpdated = true;
1177 mEventData->mScrollPosition.x = 0.f;
1181 void Controller::Impl::ClampVerticalScroll( const Vector2& actualSize )
1183 // Clamp between -space & 0 (and the text alignment).
1184 if( actualSize.height > mControlSize.height )
1186 const float space = ( actualSize.height - mControlSize.height ) + mAlignmentOffset.y;
1187 mEventData->mScrollPosition.y = ( mEventData->mScrollPosition.y < -space ) ? -space : mEventData->mScrollPosition.y;
1188 mEventData->mScrollPosition.y = ( mEventData->mScrollPosition.y > -mAlignmentOffset.y ) ? -mAlignmentOffset.y : mEventData->mScrollPosition.y;
1190 mEventData->mDecoratorUpdated = true;
1194 mEventData->mScrollPosition.y = 0.f;
1198 void Controller::Impl::ScrollToMakeCursorVisible()
1200 if( NULL == mEventData )
1202 // Nothing to do if there is no text input.
1206 const Vector2& primaryCursorPosition = mEventData->mDecorator->GetPosition( PRIMARY_CURSOR );
1209 bool updateDecorator = false;
1210 if( primaryCursorPosition.x < 0.f )
1212 offset.x = -primaryCursorPosition.x;
1213 mEventData->mScrollPosition.x += offset.x;
1214 updateDecorator = true;
1216 else if( primaryCursorPosition.x > mControlSize.width )
1218 offset.x = mControlSize.width - primaryCursorPosition.x;
1219 mEventData->mScrollPosition.x += offset.x;
1220 updateDecorator = true;
1223 if( updateDecorator && mEventData->mDecorator )
1225 mEventData->mDecorator->UpdatePositions( offset );
1228 // TODO : calculate the vertical scroll.
1231 void Controller::Impl::RequestRelayout()
1233 mControlInterface.RequestTextRelayout();
1238 } // namespace Toolkit