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( true ),
132 mSelectionEnabled( false ),
133 mHorizontalScrollingEnabled( true ),
134 mVerticalScrollingEnabled( false ),
135 mUpdateCursorPosition( false ),
136 mUpdateLeftSelectionPosition( false ),
137 mUpdateRightSelectionPosition( false ),
138 mScrollAfterUpdateCursorPosition( false ),
139 mScrollAfterDelete( false )
142 EventData::~EventData()
145 bool Controller::Impl::ProcessInputEvents()
147 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "-->Controller::ProcessInputEvents\n" );
148 if( NULL == mEventData )
150 // Nothing to do if there is no text input.
151 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::ProcessInputEvents no event data\n" );
155 if( mEventData->mDecorator )
157 for( std::vector<Event>::iterator iter = mEventData->mEventQueue.begin();
158 iter != mEventData->mEventQueue.end();
163 case Event::CURSOR_KEY_EVENT:
165 OnCursorKeyEvent( *iter );
168 case Event::TAP_EVENT:
173 case Event::PAN_EVENT:
178 case Event::GRAB_HANDLE_EVENT:
179 case Event::LEFT_SELECTION_HANDLE_EVENT:
180 case Event::RIGHT_SELECTION_HANDLE_EVENT: // Fall through
182 OnHandleEvent( *iter );
189 // The cursor must also be repositioned after inserts into the model
190 if( mEventData->mUpdateCursorPosition )
192 // Updates the cursor position and scrolls the text to make it visible.
194 UpdateCursorPosition();
196 if( mEventData->mScrollAfterUpdateCursorPosition )
198 ScrollToMakeCursorVisible();
199 mEventData->mScrollAfterUpdateCursorPosition = false;
202 mEventData->mDecoratorUpdated = true;
203 mEventData->mUpdateCursorPosition = false;
205 else if( mEventData->mScrollAfterDelete )
207 ScrollTextToMatchCursor();
208 mEventData->mDecoratorUpdated = true;
209 mEventData->mScrollAfterDelete = false;
211 else if( mEventData->mUpdateLeftSelectionPosition )
213 UpdateSelectionHandle( LEFT_SELECTION_HANDLE );
215 if( mEventData->mScrollAfterUpdateCursorPosition )
217 ScrollToMakeCursorVisible();
218 mEventData->mScrollAfterUpdateCursorPosition = false;
221 mEventData->mDecoratorUpdated = true;
222 mEventData->mUpdateLeftSelectionPosition = false;
224 else if( mEventData->mUpdateRightSelectionPosition )
226 UpdateSelectionHandle( RIGHT_SELECTION_HANDLE );
228 if( mEventData->mScrollAfterUpdateCursorPosition )
230 ScrollToMakeCursorVisible();
231 mEventData->mScrollAfterUpdateCursorPosition = false;
234 mEventData->mDecoratorUpdated = true;
235 mEventData->mUpdateRightSelectionPosition = false;
238 mEventData->mEventQueue.clear();
240 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::ProcessInputEvents\n" );
242 bool decoratorUpdated = mEventData->mDecoratorUpdated;
243 mEventData->mDecoratorUpdated = false;
244 return decoratorUpdated;
247 void Controller::Impl::UpdateModel( OperationsMask operationsRequired )
249 // Calculate the operations to be done.
250 const OperationsMask operations = static_cast<OperationsMask>( mOperationsPending & operationsRequired );
252 Vector<Character>& utf32Characters = mLogicalModel->mText;
254 const Length numberOfCharacters = utf32Characters.Count();
256 Vector<LineBreakInfo>& lineBreakInfo = mLogicalModel->mLineBreakInfo;
257 if( GET_LINE_BREAKS & operations )
259 // Retrieves the line break info. The line break info is used to split the text in 'paragraphs' to
260 // calculate the bidirectional info for each 'paragraph'.
261 // It's also used to layout the text (where it should be a new line) or to shape the text (text in different lines
262 // is not shaped together).
263 lineBreakInfo.Resize( numberOfCharacters, TextAbstraction::LINE_NO_BREAK );
265 SetLineBreakInfo( utf32Characters,
269 Vector<WordBreakInfo>& wordBreakInfo = mLogicalModel->mWordBreakInfo;
270 if( GET_WORD_BREAKS & operations )
272 // Retrieves the word break info. The word break info is used to layout the text (where to wrap the text in lines).
273 wordBreakInfo.Resize( numberOfCharacters, TextAbstraction::WORD_NO_BREAK );
275 SetWordBreakInfo( utf32Characters,
279 const bool getScripts = GET_SCRIPTS & operations;
280 const bool validateFonts = VALIDATE_FONTS & operations;
282 Vector<ScriptRun>& scripts = mLogicalModel->mScriptRuns;
283 Vector<FontRun>& validFonts = mLogicalModel->mFontRuns;
285 if( getScripts || validateFonts )
287 // Validates the fonts assigned by the application or assigns default ones.
288 // It makes sure all the characters are going to be rendered by the correct font.
289 MultilanguageSupport multilanguageSupport = MultilanguageSupport::Get();
293 // Retrieves the scripts used in the text.
294 multilanguageSupport.SetScripts( utf32Characters,
301 if( 0u == validFonts.Count() )
303 // Copy the requested font defaults received via the property system.
304 // These may not be valid i.e. may not contain glyphs for the necessary scripts.
305 GetDefaultFonts( validFonts, numberOfCharacters );
308 // Validates the fonts. If there is a character with no assigned font it sets a default one.
309 // After this call, fonts are validated.
310 multilanguageSupport.ValidateFonts( utf32Characters,
316 Vector<Character> mirroredUtf32Characters;
317 bool textMirrored = false;
318 if( BIDI_INFO & operations )
320 // Count the number of LINE_NO_BREAK to reserve some space for the vector of paragraph's
321 // bidirectional info.
323 Length numberOfParagraphs = 0u;
325 const TextAbstraction::LineBreakInfo* lineBreakInfoBuffer = lineBreakInfo.Begin();
326 for( Length index = 0u; index < numberOfCharacters; ++index )
328 if( TextAbstraction::LINE_NO_BREAK == *( lineBreakInfoBuffer + index ) )
330 ++numberOfParagraphs;
334 Vector<BidirectionalParagraphInfoRun>& bidirectionalInfo = mLogicalModel->mBidirectionalParagraphInfo;
335 bidirectionalInfo.Reserve( numberOfParagraphs );
337 // Calculates the bidirectional info for the whole paragraph if it contains right to left scripts.
338 SetBidirectionalInfo( utf32Characters,
343 if( 0u != bidirectionalInfo.Count() )
345 // This paragraph has right to left text. Some characters may need to be mirrored.
346 // TODO: consider if the mirrored string can be stored as well.
348 textMirrored = GetMirroredText( utf32Characters, mirroredUtf32Characters );
350 // Only set the character directions if there is right to left characters.
351 Vector<CharacterDirection>& directions = mLogicalModel->mCharacterDirections;
352 directions.Resize( numberOfCharacters );
354 GetCharactersDirection( bidirectionalInfo,
359 // There is no right to left characters. Clear the directions vector.
360 mLogicalModel->mCharacterDirections.Clear();
365 Vector<GlyphInfo>& glyphs = mVisualModel->mGlyphs;
366 Vector<CharacterIndex>& glyphsToCharactersMap = mVisualModel->mGlyphsToCharacters;
367 Vector<Length>& charactersPerGlyph = mVisualModel->mCharactersPerGlyph;
368 if( SHAPE_TEXT & operations )
370 const Vector<Character>& textToShape = textMirrored ? mirroredUtf32Characters : utf32Characters;
372 ShapeText( textToShape,
377 glyphsToCharactersMap,
378 charactersPerGlyph );
380 // Create the 'number of glyphs' per character and the glyph to character conversion tables.
381 mVisualModel->CreateGlyphsPerCharacterTable( numberOfCharacters );
382 mVisualModel->CreateCharacterToGlyphTable( numberOfCharacters );
385 const Length numberOfGlyphs = glyphs.Count();
387 if( GET_GLYPH_METRICS & operations )
389 mFontClient.GetGlyphMetrics( glyphs.Begin(), numberOfGlyphs );
393 void Controller::Impl::GetDefaultFonts( Vector<FontRun>& fonts, Length numberOfCharacters )
398 fontRun.characterRun.characterIndex = 0;
399 fontRun.characterRun.numberOfCharacters = numberOfCharacters;
400 fontRun.fontId = mFontDefaults->GetFontId( mFontClient );
401 fontRun.isDefault = true;
403 fonts.PushBack( fontRun );
407 void Controller::Impl::OnCursorKeyEvent( const Event& event )
409 if( NULL == mEventData )
411 // Nothing to do if there is no text input.
415 int keyCode = event.p1.mInt;
417 if( Dali::DALI_KEY_CURSOR_LEFT == keyCode )
419 if( mEventData->mPrimaryCursorPosition > 0u )
421 mEventData->mPrimaryCursorPosition = CalculateNewCursorIndex( mEventData->mPrimaryCursorPosition - 1u );
424 else if( Dali::DALI_KEY_CURSOR_RIGHT == keyCode )
426 if( mLogicalModel->GetNumberOfCharacters() > mEventData->mPrimaryCursorPosition )
428 mEventData->mPrimaryCursorPosition = CalculateNewCursorIndex( mEventData->mPrimaryCursorPosition );
431 else if( Dali::DALI_KEY_CURSOR_UP == keyCode )
435 else if( Dali::DALI_KEY_CURSOR_DOWN == keyCode )
440 mEventData->mUpdateCursorPosition = true;
441 mEventData->mScrollAfterUpdateCursorPosition = true;
444 void Controller::Impl::OnTapEvent( const Event& event )
446 if( NULL != mEventData )
448 const unsigned int tapCount = event.p1.mUint;
452 if( ! IsShowingPlaceholderText() )
454 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
455 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
457 mEventData->mPrimaryCursorPosition = GetClosestCursorIndex( xPosition,
462 mEventData->mPrimaryCursorPosition = 0u;
465 mEventData->mUpdateCursorPosition = true;
466 mEventData->mScrollAfterUpdateCursorPosition = true;
468 else if( mEventData->mSelectionEnabled &&
471 RepositionSelectionHandles( event.p2.mFloat, event.p3.mFloat );
476 void Controller::Impl::OnPanEvent( const Event& event )
478 if( NULL == mEventData )
480 // Nothing to do if there is no text input.
484 int state = event.p1.mInt;
486 if( Gesture::Started == state ||
487 Gesture::Continuing == state )
489 const Vector2& actualSize = mVisualModel->GetActualSize();
490 const Vector2 currentScroll = mEventData->mScrollPosition;
492 if( mEventData->mHorizontalScrollingEnabled )
494 const float displacementX = event.p2.mFloat;
495 mEventData->mScrollPosition.x += displacementX;
497 ClampHorizontalScroll( actualSize );
500 if( mEventData->mVerticalScrollingEnabled )
502 const float displacementY = event.p3.mFloat;
503 mEventData->mScrollPosition.y += displacementY;
505 ClampVerticalScroll( actualSize );
508 if( mEventData->mDecorator )
510 mEventData->mDecorator->UpdatePositions( mEventData->mScrollPosition - currentScroll );
515 void Controller::Impl::OnHandleEvent( const Event& event )
517 if( NULL == mEventData )
519 // Nothing to do if there is no text input.
523 const unsigned int state = event.p1.mUint;
525 if( HANDLE_PRESSED == state )
527 // The event.p2 and event.p3 are in decorator coords. Need to transforms to text coords.
528 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
529 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
531 const CharacterIndex handleNewPosition = GetClosestCursorIndex( xPosition, yPosition );
533 if( Event::GRAB_HANDLE_EVENT == event.type )
535 ChangeState ( EventData::GRAB_HANDLE_PANNING );
537 if( handleNewPosition != mEventData->mPrimaryCursorPosition )
539 mEventData->mPrimaryCursorPosition = handleNewPosition;
540 mEventData->mUpdateCursorPosition = true;
543 else if( Event::LEFT_SELECTION_HANDLE_EVENT == event.type )
545 ChangeState ( EventData::SELECTION_HANDLE_PANNING );
547 if( handleNewPosition != mEventData->mLeftSelectionPosition )
549 mEventData->mLeftSelectionPosition = handleNewPosition;
550 mEventData->mUpdateLeftSelectionPosition = true;
553 else if( Event::RIGHT_SELECTION_HANDLE_EVENT == event.type )
555 ChangeState ( EventData::SELECTION_HANDLE_PANNING );
557 if( handleNewPosition != mEventData->mRightSelectionPosition )
559 mEventData->mRightSelectionPosition = handleNewPosition;
560 mEventData->mUpdateRightSelectionPosition = true;
564 else if( ( HANDLE_RELEASED == state ) ||
565 ( HANDLE_STOP_SCROLLING == state ) )
567 if( Event::GRAB_HANDLE_EVENT == event.type )
569 mEventData->mUpdateCursorPosition = true;
571 ChangeState( EventData::EDITING_WITH_POPUP );
573 if( HANDLE_STOP_SCROLLING == state )
575 // The event.p2 and event.p3 are in decorator coords. Need to transforms to text coords.
576 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
577 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
579 mEventData->mPrimaryCursorPosition = GetClosestCursorIndex( xPosition, yPosition );
581 mEventData->mScrollAfterUpdateCursorPosition = true;
584 else if( Event::LEFT_SELECTION_HANDLE_EVENT == event.type || Event::RIGHT_SELECTION_HANDLE_EVENT )
586 ChangeState( EventData::SELECTING );
588 mEventData->mDecoratorUpdated = true;
590 else if( HANDLE_SCROLLING == state )
592 const float xSpeed = event.p2.mFloat;
593 const Vector2& actualSize = mVisualModel->GetActualSize();
595 mEventData->mScrollPosition.x += xSpeed;
597 ClampHorizontalScroll( actualSize );
599 if( Event::GRAB_HANDLE_EVENT == event.type )
601 ChangeState( EventData::GRAB_HANDLE_PANNING );
603 else if( Event::LEFT_SELECTION_HANDLE_EVENT == event.type || Event::RIGHT_SELECTION_HANDLE_EVENT )
605 ChangeState( EventData::SELECTION_HANDLE_PANNING );
608 mEventData->mDecoratorUpdated = true;
612 void Controller::Impl::RepositionSelectionHandles( float visualX, float visualY )
614 if( NULL == mEventData )
616 // Nothing to do if there is no text input.
620 // TODO - Find which word was selected
622 const Vector<GlyphInfo>& glyphs = mVisualModel->mGlyphs;
623 const Vector<Vector2>::SizeType glyphCount = glyphs.Count();
625 const Vector<Vector2>& positions = mVisualModel->mGlyphPositions;
626 const Vector<Vector2>::SizeType positionCount = positions.Count();
628 // Guard against glyphs which did not fit inside the layout
629 const Vector<Vector2>::SizeType count = (positionCount < glyphCount) ? positionCount : glyphCount;
633 float primaryX = positions[0].x + mEventData->mScrollPosition.x;
634 float secondaryX = positions[count-1].x + glyphs[count-1].width + mEventData->mScrollPosition.x;
636 // TODO - multi-line selection
637 const Vector<LineRun>& lines = mVisualModel->mLines;
638 float height = lines.Count() ? lines[0].ascender + -lines[0].descender : 0.0f;
640 mEventData->mDecorator->SetPosition( LEFT_SELECTION_HANDLE, primaryX, mEventData->mScrollPosition.y, height );
641 mEventData->mDecorator->SetPosition( RIGHT_SELECTION_HANDLE, secondaryX, mEventData->mScrollPosition.y, height );
643 mEventData->mDecorator->ClearHighlights();
644 mEventData->mDecorator->AddHighlight( primaryX, mEventData->mScrollPosition.y, secondaryX, height + mEventData->mScrollPosition.y );
648 void Controller::Impl::ChangeState( EventData::State newState )
650 if( NULL == mEventData )
652 // Nothing to do if there is no text input.
656 if( mEventData->mState != newState )
658 mEventData->mState = newState;
660 if( EventData::INACTIVE == mEventData->mState )
662 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
663 mEventData->mDecorator->StopCursorBlink();
664 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
665 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
666 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
667 mEventData->mDecorator->SetPopupActive( false );
668 mEventData->mDecoratorUpdated = true;
670 else if ( EventData::SELECTING == mEventData->mState )
672 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
673 mEventData->mDecorator->StopCursorBlink();
674 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
675 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true );
676 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true );
677 if( mEventData->mGrabHandlePopupEnabled )
679 mEventData->mDecorator->SetPopupActive( true );
681 mEventData->mDecoratorUpdated = true;
683 else if( EventData::EDITING == mEventData->mState )
685 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
686 if( mEventData->mCursorBlinkEnabled )
688 mEventData->mDecorator->StartCursorBlink();
690 // Grab handle is not shown until a tap is received whilst EDITING
691 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
692 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
693 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
694 if( mEventData->mGrabHandlePopupEnabled )
696 mEventData->mDecorator->SetPopupActive( false );
698 mEventData->mDecoratorUpdated = true;
700 else if( EventData::EDITING_WITH_POPUP == mEventData->mState )
702 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
703 if( mEventData->mCursorBlinkEnabled )
705 mEventData->mDecorator->StartCursorBlink();
707 if( mEventData->mSelectionEnabled )
709 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
710 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
714 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
716 if( mEventData->mGrabHandlePopupEnabled )
718 mEventData->mDecorator->SetPopupActive( true );
720 mEventData->mDecoratorUpdated = true;
722 else if ( EventData::SELECTION_HANDLE_PANNING == mEventData->mState )
724 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
725 mEventData->mDecorator->StopCursorBlink();
726 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
727 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true );
728 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true );
729 if( mEventData->mGrabHandlePopupEnabled )
731 mEventData->mDecorator->SetPopupActive( false );
733 mEventData->mDecoratorUpdated = true;
735 else if ( EventData::GRAB_HANDLE_PANNING == mEventData->mState )
737 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
738 if( mEventData->mCursorBlinkEnabled )
740 mEventData->mDecorator->StartCursorBlink();
742 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
743 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
744 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
745 if( mEventData->mGrabHandlePopupEnabled )
747 mEventData->mDecorator->SetPopupActive( false );
749 mEventData->mDecoratorUpdated = true;
754 LineIndex Controller::Impl::GetClosestLine( float y ) const
756 float totalHeight = 0.f;
757 LineIndex lineIndex = 0u;
759 const Vector<LineRun>& lines = mVisualModel->mLines;
760 for( LineIndex endLine = lines.Count();
764 const LineRun& lineRun = lines[lineIndex];
765 totalHeight += lineRun.ascender + -lineRun.descender;
766 if( y < totalHeight )
775 CharacterIndex Controller::Impl::GetClosestCursorIndex( float visualX,
778 if( NULL == mEventData )
780 // Nothing to do if there is no text input.
784 CharacterIndex logicalIndex = 0u;
786 const Length numberOfGlyphs = mVisualModel->mGlyphs.Count();
787 const Length numberOfLines = mVisualModel->mLines.Count();
788 if( 0 == numberOfGlyphs ||
794 // Find which line is closest
795 const LineIndex lineIndex = GetClosestLine( visualY );
796 const LineRun& line = mVisualModel->mLines[lineIndex];
798 // Get the positions of the glyphs.
799 const Vector<Vector2>& positions = mVisualModel->mGlyphPositions;
800 const Vector2* const positionsBuffer = positions.Begin();
802 // Get the visual to logical conversion tables.
803 const CharacterIndex* const visualToLogicalBuffer = ( 0u != mLogicalModel->mVisualToLogicalMap.Count() ) ? mLogicalModel->mVisualToLogicalMap.Begin() : NULL;
804 const CharacterIndex* const visualToLogicalCursorBuffer = mLogicalModel->mVisualToLogicalCursorMap.Begin();
806 // Get the character to glyph conversion table.
807 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
809 // Get the glyphs per character table.
810 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
812 // If the vector is void, there is no right to left characters.
813 const bool hasRightToLeftCharacters = NULL != visualToLogicalBuffer;
815 const CharacterIndex startCharacter = line.characterRun.characterIndex;
816 const CharacterIndex endCharacter = line.characterRun.characterIndex + line.characterRun.numberOfCharacters;
817 DALI_ASSERT_DEBUG( endCharacter <= mLogicalModel->mText.Count() && "Invalid line info" );
819 // Whether there is a hit on a glyph.
820 bool matched = false;
822 // Traverses glyphs in visual order. To do that use the visual to logical conversion table.
823 CharacterIndex visualIndex = startCharacter;
824 for( ; !matched && ( visualIndex < endCharacter ); ++visualIndex )
826 // The character in logical order.
827 const CharacterIndex characterLogicalOrderIndex = hasRightToLeftCharacters ? *( visualToLogicalBuffer + visualIndex ) : visualIndex;
829 // The first glyph for that character in logical order.
830 const GlyphIndex glyphLogicalOrderIndex = *( charactersToGlyphBuffer + characterLogicalOrderIndex );
832 // The number of glyphs for that character
833 const Length numberOfGlyphs = *( glyphsPerCharacterBuffer + characterLogicalOrderIndex );
835 // Get the metrics for the group of glyphs.
836 GlyphMetrics glyphMetrics;
837 GetGlyphsMetrics( glyphLogicalOrderIndex,
843 const Vector2& position = *( positionsBuffer + glyphLogicalOrderIndex );
845 const float glyphX = -glyphMetrics.xBearing + position.x + 0.5f * glyphMetrics.advance;
847 if( visualX < glyphX )
854 // Return the logical position of the cursor in characters.
858 visualIndex = endCharacter;
861 return hasRightToLeftCharacters ? *( visualToLogicalCursorBuffer + visualIndex ) : visualIndex;
864 void Controller::Impl::GetCursorPosition( CharacterIndex logical,
865 CursorInfo& cursorInfo )
867 // TODO: Check for multiline with \n, etc...
869 // Check if the logical position is the first or the last one of the text.
870 const bool isFirstPosition = 0u == logical;
871 const bool isLastPosition = mLogicalModel->GetNumberOfCharacters() == logical;
873 if( isFirstPosition && isLastPosition )
875 // There is zero characters. Get the default font.
877 FontId defaultFontId = 0u;
878 if( NULL == mFontDefaults )
880 defaultFontId = mFontClient.GetFontId( EMPTY_STRING,
885 defaultFontId = mFontDefaults->GetFontId( mFontClient );
888 Text::FontMetrics fontMetrics;
889 mFontClient.GetFontMetrics( defaultFontId, fontMetrics );
891 cursorInfo.lineHeight = fontMetrics.ascender - fontMetrics.descender;
892 cursorInfo.primaryCursorHeight = cursorInfo.lineHeight;
894 cursorInfo.primaryPosition.x = 0.f;
895 cursorInfo.primaryPosition.y = 0.f;
897 // Nothing else to do.
901 // Get the previous logical index.
902 const CharacterIndex previousLogical = isFirstPosition ? 0u : logical - 1u;
904 // Decrease the logical index if it's the last one.
910 // Get the direction of the character and the previous one.
911 const CharacterDirection* const modelCharacterDirectionsBuffer = ( 0u != mLogicalModel->mCharacterDirections.Count() ) ? mLogicalModel->mCharacterDirections.Begin() : NULL;
913 CharacterDirection isCurrentRightToLeft = false;
914 CharacterDirection isPreviousRightToLeft = false;
915 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
917 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + logical );
918 isPreviousRightToLeft = *( modelCharacterDirectionsBuffer + previousLogical );
921 // Get the line where the character is laid-out.
922 const LineRun* modelLines = mVisualModel->mLines.Begin();
924 const LineIndex lineIndex = mVisualModel->GetLineOfCharacter( logical );
925 const LineRun& line = *( modelLines + lineIndex );
927 // Get the paragraph's direction.
928 const CharacterDirection isRightToLeftParagraph = line.direction;
930 // Check whether there is an alternative position:
932 cursorInfo.isSecondaryCursor = ( isCurrentRightToLeft != isPreviousRightToLeft ) ||
933 ( isLastPosition && ( isRightToLeftParagraph != isCurrentRightToLeft ) );
935 // Set the line height.
936 cursorInfo.lineHeight = line.ascender + -line.descender;
938 // Convert the cursor position into the glyph position.
939 CharacterIndex characterIndex = logical;
940 if( cursorInfo.isSecondaryCursor &&
941 ( isRightToLeftParagraph != isCurrentRightToLeft ) )
943 characterIndex = previousLogical;
946 const GlyphIndex currentGlyphIndex = *( mVisualModel->mCharactersToGlyph.Begin() + characterIndex );
947 const Length numberOfGlyphs = *( mVisualModel->mGlyphsPerCharacter.Begin() + characterIndex );
948 const Length numberOfCharacters = *( mVisualModel->mCharactersPerGlyph.Begin() +currentGlyphIndex );
950 // Get the metrics for the group of glyphs.
951 GlyphMetrics glyphMetrics;
952 GetGlyphsMetrics( currentGlyphIndex,
958 float interGlyphAdvance = 0.f;
959 if( !isLastPosition &&
960 ( numberOfCharacters > 1u ) )
962 const CharacterIndex firstIndex = *( mVisualModel->mGlyphsToCharacters.Begin() + currentGlyphIndex );
963 interGlyphAdvance = static_cast<float>( characterIndex - firstIndex ) * glyphMetrics.advance / static_cast<float>( numberOfCharacters );
966 // Get the glyph position and x bearing.
967 const Vector2& currentPosition = *( mVisualModel->mGlyphPositions.Begin() + currentGlyphIndex );
969 // Set the cursor's height.
970 cursorInfo.primaryCursorHeight = glyphMetrics.fontHeight;
973 cursorInfo.primaryPosition.x = -glyphMetrics.xBearing + currentPosition.x + ( isCurrentRightToLeft ? glyphMetrics.advance : interGlyphAdvance );
974 cursorInfo.primaryPosition.y = line.ascender - glyphMetrics.ascender;
978 // The position of the cursor after the last character needs special
979 // care depending on its direction and the direction of the paragraph.
981 if( cursorInfo.isSecondaryCursor )
983 // Need to find the first character after the last character with the paragraph's direction.
984 // i.e l0 l1 l2 r0 r1 should find r0.
986 // TODO: check for more than one line!
987 characterIndex = isRightToLeftParagraph ? line.characterRun.characterIndex : line.characterRun.characterIndex + line.characterRun.numberOfCharacters - 1u;
988 characterIndex = mLogicalModel->GetLogicalCharacterIndex( characterIndex );
990 const GlyphIndex glyphIndex = *( mVisualModel->mCharactersToGlyph.Begin() + characterIndex );
991 const Length numberOfGlyphs = *( mVisualModel->mGlyphsPerCharacter.Begin() + characterIndex );
993 const Vector2& position = *( mVisualModel->mGlyphPositions.Begin() + glyphIndex );
995 // Get the metrics for the group of glyphs.
996 GlyphMetrics glyphMetrics;
997 GetGlyphsMetrics( glyphIndex,
1003 cursorInfo.primaryPosition.x = -glyphMetrics.xBearing + position.x + ( isRightToLeftParagraph ? 0.f : glyphMetrics.advance );
1005 cursorInfo.primaryPosition.y = line.ascender - glyphMetrics.ascender;
1009 if( !isCurrentRightToLeft )
1011 cursorInfo.primaryPosition.x += glyphMetrics.advance;
1015 cursorInfo.primaryPosition.x -= glyphMetrics.advance;
1020 // Set the alternative cursor position.
1021 if( cursorInfo.isSecondaryCursor )
1023 // Convert the cursor position into the glyph position.
1024 const CharacterIndex previousCharacterIndex = ( ( isRightToLeftParagraph != isCurrentRightToLeft ) ? logical : previousLogical );
1025 const GlyphIndex previousGlyphIndex = *( mVisualModel->mCharactersToGlyph.Begin() + previousCharacterIndex );
1026 const Length numberOfGlyphs = *( mVisualModel->mGlyphsPerCharacter.Begin() + previousCharacterIndex );
1028 // Get the glyph position.
1029 const Vector2& previousPosition = *( mVisualModel->mGlyphPositions.Begin() + previousGlyphIndex );
1031 // Get the metrics for the group of glyphs.
1032 GlyphMetrics glyphMetrics;
1033 GetGlyphsMetrics( previousGlyphIndex,
1039 // Set the cursor position and height.
1040 cursorInfo.secondaryPosition.x = -glyphMetrics.xBearing + previousPosition.x + ( ( ( isLastPosition && !isCurrentRightToLeft ) ||
1041 ( !isLastPosition && isCurrentRightToLeft ) ) ? glyphMetrics.advance : 0.f );
1043 cursorInfo.secondaryCursorHeight = 0.5f * glyphMetrics.fontHeight;
1045 cursorInfo.secondaryPosition.y = cursorInfo.lineHeight - cursorInfo.secondaryCursorHeight - line.descender - ( glyphMetrics.fontHeight - glyphMetrics.ascender );
1047 // Update the primary cursor height as well.
1048 cursorInfo.primaryCursorHeight *= 0.5f;
1052 CharacterIndex Controller::Impl::CalculateNewCursorIndex( CharacterIndex index ) const
1054 if( NULL == mEventData )
1056 // Nothing to do if there is no text input.
1060 CharacterIndex cursorIndex = mEventData->mPrimaryCursorPosition;
1062 const Script script = mLogicalModel->GetScript( index );
1063 const GlyphIndex* charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
1064 const Length* charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
1066 Length numberOfCharacters = 0u;
1067 if( TextAbstraction::LATIN == script )
1069 // Prevents to jump the whole Latin ligatures like fi, ff, ...
1070 numberOfCharacters = 1u;
1074 GlyphIndex glyphIndex = *( charactersToGlyphBuffer + index );
1075 numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
1077 while( 0u == numberOfCharacters )
1079 numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
1084 if( index < mEventData->mPrimaryCursorPosition )
1086 cursorIndex -= numberOfCharacters;
1090 cursorIndex += numberOfCharacters;
1096 void Controller::Impl::UpdateCursorPosition()
1098 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "-->Controller::UpdateCursorPosition %p\n", this );
1099 if( NULL == mEventData )
1101 // Nothing to do if there is no text input.
1102 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition no event data\n" );
1106 if( IsShowingPlaceholderText() )
1108 // Do not want to use the place-holder text to set the cursor position.
1110 // Use the line's height of the font's family set to set the cursor's size.
1111 // If there is no font's family set, use the default font.
1112 // Use the current alignment to place the cursor at the beginning, center or end of the box.
1114 float lineHeight = 0.f;
1116 FontId defaultFontId = 0u;
1117 if( NULL == mFontDefaults )
1119 defaultFontId = mFontClient.GetFontId( EMPTY_STRING,
1124 defaultFontId = mFontDefaults->GetFontId( mFontClient );
1127 Text::FontMetrics fontMetrics;
1128 mFontClient.GetFontMetrics( defaultFontId, fontMetrics );
1130 lineHeight = fontMetrics.ascender - fontMetrics.descender;
1133 Vector2 cursorPosition;
1135 switch( mLayoutEngine.GetHorizontalAlignment() )
1137 case LayoutEngine::HORIZONTAL_ALIGN_BEGIN:
1139 cursorPosition.x = 1.f;
1142 case LayoutEngine::HORIZONTAL_ALIGN_CENTER:
1144 cursorPosition.x = floor( 0.5f * mControlSize.width );
1147 case LayoutEngine::HORIZONTAL_ALIGN_END:
1149 cursorPosition.x = mControlSize.width;
1154 switch( mLayoutEngine.GetVerticalAlignment() )
1156 case LayoutEngine::VERTICAL_ALIGN_TOP:
1158 cursorPosition.y = 0.f;
1161 case LayoutEngine::VERTICAL_ALIGN_CENTER:
1163 cursorPosition.y = floorf( 0.5f * ( mControlSize.height - lineHeight ) );
1166 case LayoutEngine::VERTICAL_ALIGN_BOTTOM:
1168 cursorPosition.y = mControlSize.height - lineHeight;
1173 mEventData->mDecorator->SetPosition( PRIMARY_CURSOR,
1181 CursorInfo cursorInfo;
1182 GetCursorPosition( mEventData->mPrimaryCursorPosition,
1185 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
1186 const Vector2 cursorPosition = cursorInfo.primaryPosition + offset;
1188 // Sets the cursor position.
1189 mEventData->mDecorator->SetPosition( PRIMARY_CURSOR,
1192 cursorInfo.primaryCursorHeight,
1193 cursorInfo.lineHeight );
1194 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Primary cursor position: %f,%f\n", cursorPosition.x, cursorPosition.y );
1196 // Sets the grab handle position.
1197 mEventData->mDecorator->SetPosition( GRAB_HANDLE,
1200 cursorInfo.lineHeight );
1202 if( cursorInfo.isSecondaryCursor )
1204 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_BOTH );
1205 mEventData->mDecorator->SetPosition( SECONDARY_CURSOR,
1206 cursorInfo.secondaryPosition.x + offset.x,
1207 cursorInfo.secondaryPosition.y + offset.y,
1208 cursorInfo.secondaryCursorHeight,
1209 cursorInfo.lineHeight );
1210 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Secondary cursor position: %f,%f\n", cursorInfo.secondaryPosition.x + offset.x, cursorInfo.secondaryPosition.y + offset.y );
1214 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1217 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition\n" );
1220 void Controller::Impl::UpdateSelectionHandle( HandleType handleType )
1222 if( ( LEFT_SELECTION_HANDLE != handleType ) &&
1223 ( RIGHT_SELECTION_HANDLE != handleType ) )
1228 const bool leftSelectionHandle = LEFT_SELECTION_HANDLE == handleType;
1229 const CharacterIndex index = leftSelectionHandle ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition;
1231 CursorInfo cursorInfo;
1232 GetCursorPosition( index,
1235 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
1236 const Vector2 cursorPosition = cursorInfo.primaryPosition + offset;
1238 // Sets the grab handle position.
1239 mEventData->mDecorator->SetPosition( handleType,
1242 cursorInfo.lineHeight );
1245 void Controller::Impl::ClampHorizontalScroll( const Vector2& actualSize )
1247 // Clamp between -space & 0 (and the text alignment).
1248 if( actualSize.width > mControlSize.width )
1250 const float space = ( actualSize.width - mControlSize.width ) + mAlignmentOffset.x;
1251 mEventData->mScrollPosition.x = ( mEventData->mScrollPosition.x < -space ) ? -space : mEventData->mScrollPosition.x;
1252 mEventData->mScrollPosition.x = ( mEventData->mScrollPosition.x > -mAlignmentOffset.x ) ? -mAlignmentOffset.x : mEventData->mScrollPosition.x;
1254 mEventData->mDecoratorUpdated = true;
1258 mEventData->mScrollPosition.x = 0.f;
1262 void Controller::Impl::ClampVerticalScroll( const Vector2& actualSize )
1264 // Clamp between -space & 0 (and the text alignment).
1265 if( actualSize.height > mControlSize.height )
1267 const float space = ( actualSize.height - mControlSize.height ) + mAlignmentOffset.y;
1268 mEventData->mScrollPosition.y = ( mEventData->mScrollPosition.y < -space ) ? -space : mEventData->mScrollPosition.y;
1269 mEventData->mScrollPosition.y = ( mEventData->mScrollPosition.y > -mAlignmentOffset.y ) ? -mAlignmentOffset.y : mEventData->mScrollPosition.y;
1271 mEventData->mDecoratorUpdated = true;
1275 mEventData->mScrollPosition.y = 0.f;
1279 void Controller::Impl::ScrollToMakeCursorVisible()
1281 if( NULL == mEventData )
1283 // Nothing to do if there is no text input.
1287 const Vector2& primaryCursorPosition = mEventData->mDecorator->GetPosition( PRIMARY_CURSOR );
1290 bool updateDecorator = false;
1291 if( primaryCursorPosition.x < 0.f )
1293 offset.x = -primaryCursorPosition.x;
1294 mEventData->mScrollPosition.x += offset.x;
1295 updateDecorator = true;
1297 else if( primaryCursorPosition.x > mControlSize.width )
1299 offset.x = mControlSize.width - primaryCursorPosition.x;
1300 mEventData->mScrollPosition.x += offset.x;
1301 updateDecorator = true;
1304 if( updateDecorator && mEventData->mDecorator )
1306 mEventData->mDecorator->UpdatePositions( offset );
1309 // TODO : calculate the vertical scroll.
1312 void Controller::Impl::ScrollTextToMatchCursor()
1314 // Get the current cursor position in decorator coords.
1315 const Vector2& currentCursorPosition = mEventData->mDecorator->GetPosition( PRIMARY_CURSOR );
1317 // Calculate the new cursor position.
1318 CursorInfo cursorInfo;
1319 GetCursorPosition( mEventData->mPrimaryCursorPosition,
1322 // Calculate the offset to match the cursor position before the character was deleted.
1323 mEventData->mScrollPosition.x = currentCursorPosition.x - cursorInfo.primaryPosition.x - mAlignmentOffset.x;
1325 ClampHorizontalScroll( mVisualModel->GetActualSize() );
1326 bool updateCursorPosition = true;
1328 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
1329 const Vector2 cursorPosition = cursorInfo.primaryPosition + offset;
1331 if( updateCursorPosition )
1333 // Sets the cursor position.
1334 mEventData->mDecorator->SetPosition( PRIMARY_CURSOR,
1337 cursorInfo.primaryCursorHeight,
1338 cursorInfo.lineHeight );
1340 // Sets the grab handle position.
1341 mEventData->mDecorator->SetPosition( GRAB_HANDLE,
1344 cursorInfo.lineHeight );
1346 if( cursorInfo.isSecondaryCursor )
1348 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_BOTH );
1349 mEventData->mDecorator->SetPosition( SECONDARY_CURSOR,
1350 cursorInfo.secondaryPosition.x + offset.x,
1351 cursorInfo.secondaryPosition.y + offset.y,
1352 cursorInfo.secondaryCursorHeight,
1353 cursorInfo.lineHeight );
1354 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Secondary cursor position: %f,%f\n", cursorInfo.secondaryPosition.x + offset.x, cursorInfo.secondaryPosition.y + offset.y );
1358 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1363 void Controller::Impl::RequestRelayout()
1365 mControlInterface.RequestTextRelayout();
1370 } // namespace Toolkit