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 )
780 CharacterIndex Controller::Impl::GetClosestCursorIndex( float visualX,
783 if( NULL == mEventData )
785 // Nothing to do if there is no text input.
789 CharacterIndex logicalIndex = 0u;
791 const Length numberOfGlyphs = mVisualModel->mGlyphs.Count();
792 const Length numberOfLines = mVisualModel->mLines.Count();
793 if( 0 == numberOfGlyphs ||
799 // Find which line is closest
800 const LineIndex lineIndex = GetClosestLine( visualY );
801 const LineRun& line = mVisualModel->mLines[lineIndex];
803 // Get the positions of the glyphs.
804 const Vector<Vector2>& positions = mVisualModel->mGlyphPositions;
805 const Vector2* const positionsBuffer = positions.Begin();
807 // Get the visual to logical conversion tables.
808 const CharacterIndex* const visualToLogicalBuffer = ( 0u != mLogicalModel->mVisualToLogicalMap.Count() ) ? mLogicalModel->mVisualToLogicalMap.Begin() : NULL;
809 const CharacterIndex* const visualToLogicalCursorBuffer = mLogicalModel->mVisualToLogicalCursorMap.Begin();
811 // Get the character to glyph conversion table.
812 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
814 // Get the glyphs per character table.
815 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
817 // If the vector is void, there is no right to left characters.
818 const bool hasRightToLeftCharacters = NULL != visualToLogicalBuffer;
820 const CharacterIndex startCharacter = line.characterRun.characterIndex;
821 const CharacterIndex endCharacter = line.characterRun.characterIndex + line.characterRun.numberOfCharacters;
822 DALI_ASSERT_DEBUG( endCharacter <= mLogicalModel->mText.Count() && "Invalid line info" );
824 // Whether there is a hit on a glyph.
825 bool matched = false;
827 // Traverses glyphs in visual order. To do that use the visual to logical conversion table.
828 CharacterIndex visualIndex = startCharacter;
829 for( ; !matched && ( visualIndex < endCharacter ); ++visualIndex )
831 // The character in logical order.
832 const CharacterIndex characterLogicalOrderIndex = hasRightToLeftCharacters ? *( visualToLogicalBuffer + visualIndex ) : visualIndex;
834 // The first glyph for that character in logical order.
835 const GlyphIndex glyphLogicalOrderIndex = *( charactersToGlyphBuffer + characterLogicalOrderIndex );
837 // The number of glyphs for that character
838 const Length numberOfGlyphs = *( glyphsPerCharacterBuffer + characterLogicalOrderIndex );
840 // Get the metrics for the group of glyphs.
841 GlyphMetrics glyphMetrics;
842 GetGlyphsMetrics( glyphLogicalOrderIndex,
848 const Vector2& position = *( positionsBuffer + glyphLogicalOrderIndex );
850 const float glyphX = -glyphMetrics.xBearing + position.x + 0.5f * glyphMetrics.advance;
852 if( visualX < glyphX )
859 // Return the logical position of the cursor in characters.
863 visualIndex = endCharacter;
866 return hasRightToLeftCharacters ? *( visualToLogicalCursorBuffer + visualIndex ) : visualIndex;
869 void Controller::Impl::GetCursorPosition( CharacterIndex logical,
870 CursorInfo& cursorInfo )
872 // TODO: Check for multiline with \n, etc...
874 // Check if the logical position is the first or the last one of the text.
875 const bool isFirstPosition = 0u == logical;
876 const bool isLastPosition = mLogicalModel->GetNumberOfCharacters() == logical;
878 if( isFirstPosition && isLastPosition )
880 // There is zero characters. Get the default font.
882 FontId defaultFontId = 0u;
883 if( NULL == mFontDefaults )
885 defaultFontId = mFontClient.GetFontId( EMPTY_STRING,
890 defaultFontId = mFontDefaults->GetFontId( mFontClient );
893 Text::FontMetrics fontMetrics;
894 mFontClient.GetFontMetrics( defaultFontId, fontMetrics );
896 cursorInfo.lineHeight = fontMetrics.ascender - fontMetrics.descender;
897 cursorInfo.primaryCursorHeight = cursorInfo.lineHeight;
899 cursorInfo.primaryPosition.x = 0.f;
900 cursorInfo.primaryPosition.y = 0.f;
902 // Nothing else to do.
906 // Get the previous logical index.
907 const CharacterIndex previousLogical = isFirstPosition ? 0u : logical - 1u;
909 // Decrease the logical index if it's the last one.
915 // Get the direction of the character and the previous one.
916 const CharacterDirection* const modelCharacterDirectionsBuffer = ( 0u != mLogicalModel->mCharacterDirections.Count() ) ? mLogicalModel->mCharacterDirections.Begin() : NULL;
918 CharacterDirection isCurrentRightToLeft = false;
919 CharacterDirection isPreviousRightToLeft = false;
920 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
922 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + logical );
923 isPreviousRightToLeft = *( modelCharacterDirectionsBuffer + previousLogical );
926 // Get the line where the character is laid-out.
927 const LineRun* modelLines = mVisualModel->mLines.Begin();
929 const LineIndex lineIndex = mVisualModel->GetLineOfCharacter( logical );
930 const LineRun& line = *( modelLines + lineIndex );
932 // Get the paragraph's direction.
933 const CharacterDirection isRightToLeftParagraph = line.direction;
935 // Check whether there is an alternative position:
937 cursorInfo.isSecondaryCursor = ( isCurrentRightToLeft != isPreviousRightToLeft ) ||
938 ( isLastPosition && ( isRightToLeftParagraph != isCurrentRightToLeft ) );
940 // Set the line height.
941 cursorInfo.lineHeight = line.ascender + -line.descender;
943 // Convert the cursor position into the glyph position.
944 CharacterIndex characterIndex = logical;
945 if( cursorInfo.isSecondaryCursor &&
946 ( isRightToLeftParagraph != isCurrentRightToLeft ) )
948 characterIndex = previousLogical;
951 const GlyphIndex currentGlyphIndex = *( mVisualModel->mCharactersToGlyph.Begin() + characterIndex );
952 const Length numberOfGlyphs = *( mVisualModel->mGlyphsPerCharacter.Begin() + characterIndex );
953 const Length numberOfCharacters = *( mVisualModel->mCharactersPerGlyph.Begin() +currentGlyphIndex );
955 // Get the metrics for the group of glyphs.
956 GlyphMetrics glyphMetrics;
957 GetGlyphsMetrics( currentGlyphIndex,
963 float interGlyphAdvance = 0.f;
964 if( !isLastPosition &&
965 ( numberOfCharacters > 1u ) )
967 const CharacterIndex firstIndex = *( mVisualModel->mGlyphsToCharacters.Begin() + currentGlyphIndex );
968 interGlyphAdvance = static_cast<float>( characterIndex - firstIndex ) * glyphMetrics.advance / static_cast<float>( numberOfCharacters );
971 // Get the glyph position and x bearing.
972 const Vector2& currentPosition = *( mVisualModel->mGlyphPositions.Begin() + currentGlyphIndex );
974 // Set the cursor's height.
975 cursorInfo.primaryCursorHeight = glyphMetrics.fontHeight;
978 cursorInfo.primaryPosition.x = -glyphMetrics.xBearing + currentPosition.x + ( isCurrentRightToLeft ? glyphMetrics.advance : interGlyphAdvance );
979 cursorInfo.primaryPosition.y = line.ascender - glyphMetrics.ascender;
983 // The position of the cursor after the last character needs special
984 // care depending on its direction and the direction of the paragraph.
986 if( cursorInfo.isSecondaryCursor )
988 // Need to find the first character after the last character with the paragraph's direction.
989 // i.e l0 l1 l2 r0 r1 should find r0.
991 // TODO: check for more than one line!
992 characterIndex = isRightToLeftParagraph ? line.characterRun.characterIndex : line.characterRun.characterIndex + line.characterRun.numberOfCharacters - 1u;
993 characterIndex = mLogicalModel->GetLogicalCharacterIndex( characterIndex );
995 const GlyphIndex glyphIndex = *( mVisualModel->mCharactersToGlyph.Begin() + characterIndex );
996 const Length numberOfGlyphs = *( mVisualModel->mGlyphsPerCharacter.Begin() + characterIndex );
998 const Vector2& position = *( mVisualModel->mGlyphPositions.Begin() + glyphIndex );
1000 // Get the metrics for the group of glyphs.
1001 GlyphMetrics glyphMetrics;
1002 GetGlyphsMetrics( glyphIndex,
1008 cursorInfo.primaryPosition.x = -glyphMetrics.xBearing + position.x + ( isRightToLeftParagraph ? 0.f : glyphMetrics.advance );
1010 cursorInfo.primaryPosition.y = line.ascender - glyphMetrics.ascender;
1014 if( !isCurrentRightToLeft )
1016 cursorInfo.primaryPosition.x += glyphMetrics.advance;
1020 cursorInfo.primaryPosition.x -= glyphMetrics.advance;
1025 // Set the alternative cursor position.
1026 if( cursorInfo.isSecondaryCursor )
1028 // Convert the cursor position into the glyph position.
1029 const CharacterIndex previousCharacterIndex = ( ( isRightToLeftParagraph != isCurrentRightToLeft ) ? logical : previousLogical );
1030 const GlyphIndex previousGlyphIndex = *( mVisualModel->mCharactersToGlyph.Begin() + previousCharacterIndex );
1031 const Length numberOfGlyphs = *( mVisualModel->mGlyphsPerCharacter.Begin() + previousCharacterIndex );
1033 // Get the glyph position.
1034 const Vector2& previousPosition = *( mVisualModel->mGlyphPositions.Begin() + previousGlyphIndex );
1036 // Get the metrics for the group of glyphs.
1037 GlyphMetrics glyphMetrics;
1038 GetGlyphsMetrics( previousGlyphIndex,
1044 // Set the cursor position and height.
1045 cursorInfo.secondaryPosition.x = -glyphMetrics.xBearing + previousPosition.x + ( ( ( isLastPosition && !isCurrentRightToLeft ) ||
1046 ( !isLastPosition && isCurrentRightToLeft ) ) ? glyphMetrics.advance : 0.f );
1048 cursorInfo.secondaryCursorHeight = 0.5f * glyphMetrics.fontHeight;
1050 cursorInfo.secondaryPosition.y = cursorInfo.lineHeight - cursorInfo.secondaryCursorHeight - line.descender - ( glyphMetrics.fontHeight - glyphMetrics.ascender );
1052 // Update the primary cursor height as well.
1053 cursorInfo.primaryCursorHeight *= 0.5f;
1057 CharacterIndex Controller::Impl::CalculateNewCursorIndex( CharacterIndex index ) const
1059 if( NULL == mEventData )
1061 // Nothing to do if there is no text input.
1065 CharacterIndex cursorIndex = mEventData->mPrimaryCursorPosition;
1067 const Script script = mLogicalModel->GetScript( index );
1068 const GlyphIndex* charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
1069 const Length* charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
1071 Length numberOfCharacters = 0u;
1072 if( TextAbstraction::LATIN == script )
1074 // Prevents to jump the whole Latin ligatures like fi, ff, ...
1075 numberOfCharacters = 1u;
1079 GlyphIndex glyphIndex = *( charactersToGlyphBuffer + index );
1080 numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
1082 while( 0u == numberOfCharacters )
1084 numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
1089 if( index < mEventData->mPrimaryCursorPosition )
1091 cursorIndex -= numberOfCharacters;
1095 cursorIndex += numberOfCharacters;
1101 void Controller::Impl::UpdateCursorPosition()
1103 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "-->Controller::UpdateCursorPosition %p\n", this );
1104 if( NULL == mEventData )
1106 // Nothing to do if there is no text input.
1107 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition no event data\n" );
1111 if( IsShowingPlaceholderText() )
1113 // Do not want to use the place-holder text to set the cursor position.
1115 // Use the line's height of the font's family set to set the cursor's size.
1116 // If there is no font's family set, use the default font.
1117 // Use the current alignment to place the cursor at the beginning, center or end of the box.
1119 float lineHeight = 0.f;
1121 FontId defaultFontId = 0u;
1122 if( NULL == mFontDefaults )
1124 defaultFontId = mFontClient.GetFontId( EMPTY_STRING,
1129 defaultFontId = mFontDefaults->GetFontId( mFontClient );
1132 Text::FontMetrics fontMetrics;
1133 mFontClient.GetFontMetrics( defaultFontId, fontMetrics );
1135 lineHeight = fontMetrics.ascender - fontMetrics.descender;
1138 Vector2 cursorPosition;
1140 switch( mLayoutEngine.GetHorizontalAlignment() )
1142 case LayoutEngine::HORIZONTAL_ALIGN_BEGIN:
1144 cursorPosition.x = 1.f;
1147 case LayoutEngine::HORIZONTAL_ALIGN_CENTER:
1149 cursorPosition.x = floor( 0.5f * mControlSize.width );
1152 case LayoutEngine::HORIZONTAL_ALIGN_END:
1154 cursorPosition.x = mControlSize.width;
1159 switch( mLayoutEngine.GetVerticalAlignment() )
1161 case LayoutEngine::VERTICAL_ALIGN_TOP:
1163 cursorPosition.y = 0.f;
1166 case LayoutEngine::VERTICAL_ALIGN_CENTER:
1168 cursorPosition.y = floorf( 0.5f * ( mControlSize.height - lineHeight ) );
1171 case LayoutEngine::VERTICAL_ALIGN_BOTTOM:
1173 cursorPosition.y = mControlSize.height - lineHeight;
1178 mEventData->mDecorator->SetPosition( PRIMARY_CURSOR,
1186 CursorInfo cursorInfo;
1187 GetCursorPosition( mEventData->mPrimaryCursorPosition,
1190 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
1191 const Vector2 cursorPosition = cursorInfo.primaryPosition + offset;
1193 // Sets the cursor position.
1194 mEventData->mDecorator->SetPosition( PRIMARY_CURSOR,
1197 cursorInfo.primaryCursorHeight,
1198 cursorInfo.lineHeight );
1199 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Primary cursor position: %f,%f\n", cursorPosition.x, cursorPosition.y );
1201 // Sets the grab handle position.
1202 mEventData->mDecorator->SetPosition( GRAB_HANDLE,
1205 cursorInfo.lineHeight );
1207 if( cursorInfo.isSecondaryCursor )
1209 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_BOTH );
1210 mEventData->mDecorator->SetPosition( SECONDARY_CURSOR,
1211 cursorInfo.secondaryPosition.x + offset.x,
1212 cursorInfo.secondaryPosition.y + offset.y,
1213 cursorInfo.secondaryCursorHeight,
1214 cursorInfo.lineHeight );
1215 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Secondary cursor position: %f,%f\n", cursorInfo.secondaryPosition.x + offset.x, cursorInfo.secondaryPosition.y + offset.y );
1219 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1222 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition\n" );
1225 void Controller::Impl::UpdateSelectionHandle( HandleType handleType )
1227 if( ( LEFT_SELECTION_HANDLE != handleType ) &&
1228 ( RIGHT_SELECTION_HANDLE != handleType ) )
1233 const bool leftSelectionHandle = LEFT_SELECTION_HANDLE == handleType;
1234 const CharacterIndex index = leftSelectionHandle ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition;
1236 CursorInfo cursorInfo;
1237 GetCursorPosition( index,
1240 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
1241 const Vector2 cursorPosition = cursorInfo.primaryPosition + offset;
1243 // Sets the grab handle position.
1244 mEventData->mDecorator->SetPosition( handleType,
1247 cursorInfo.lineHeight );
1250 void Controller::Impl::ClampHorizontalScroll( const Vector2& actualSize )
1252 // Clamp between -space & 0 (and the text alignment).
1253 if( actualSize.width > mControlSize.width )
1255 const float space = ( actualSize.width - mControlSize.width ) + mAlignmentOffset.x;
1256 mEventData->mScrollPosition.x = ( mEventData->mScrollPosition.x < -space ) ? -space : mEventData->mScrollPosition.x;
1257 mEventData->mScrollPosition.x = ( mEventData->mScrollPosition.x > -mAlignmentOffset.x ) ? -mAlignmentOffset.x : mEventData->mScrollPosition.x;
1259 mEventData->mDecoratorUpdated = true;
1263 mEventData->mScrollPosition.x = 0.f;
1267 void Controller::Impl::ClampVerticalScroll( const Vector2& actualSize )
1269 // Clamp between -space & 0 (and the text alignment).
1270 if( actualSize.height > mControlSize.height )
1272 const float space = ( actualSize.height - mControlSize.height ) + mAlignmentOffset.y;
1273 mEventData->mScrollPosition.y = ( mEventData->mScrollPosition.y < -space ) ? -space : mEventData->mScrollPosition.y;
1274 mEventData->mScrollPosition.y = ( mEventData->mScrollPosition.y > -mAlignmentOffset.y ) ? -mAlignmentOffset.y : mEventData->mScrollPosition.y;
1276 mEventData->mDecoratorUpdated = true;
1280 mEventData->mScrollPosition.y = 0.f;
1284 void Controller::Impl::ScrollToMakeCursorVisible()
1286 if( NULL == mEventData )
1288 // Nothing to do if there is no text input.
1292 const Vector2& primaryCursorPosition = mEventData->mDecorator->GetPosition( PRIMARY_CURSOR );
1295 bool updateDecorator = false;
1296 if( primaryCursorPosition.x < 0.f )
1298 offset.x = -primaryCursorPosition.x;
1299 mEventData->mScrollPosition.x += offset.x;
1300 updateDecorator = true;
1302 else if( primaryCursorPosition.x > mControlSize.width )
1304 offset.x = mControlSize.width - primaryCursorPosition.x;
1305 mEventData->mScrollPosition.x += offset.x;
1306 updateDecorator = true;
1309 if( updateDecorator && mEventData->mDecorator )
1311 mEventData->mDecorator->UpdatePositions( offset );
1314 // TODO : calculate the vertical scroll.
1317 void Controller::Impl::ScrollTextToMatchCursor()
1319 // Get the current cursor position in decorator coords.
1320 const Vector2& currentCursorPosition = mEventData->mDecorator->GetPosition( PRIMARY_CURSOR );
1322 // Calculate the new cursor position.
1323 CursorInfo cursorInfo;
1324 GetCursorPosition( mEventData->mPrimaryCursorPosition,
1327 // Calculate the offset to match the cursor position before the character was deleted.
1328 mEventData->mScrollPosition.x = currentCursorPosition.x - cursorInfo.primaryPosition.x - mAlignmentOffset.x;
1330 ClampHorizontalScroll( mVisualModel->GetActualSize() );
1331 bool updateCursorPosition = true;
1333 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
1334 const Vector2 cursorPosition = cursorInfo.primaryPosition + offset;
1336 if( updateCursorPosition )
1338 // Sets the cursor position.
1339 mEventData->mDecorator->SetPosition( PRIMARY_CURSOR,
1342 cursorInfo.primaryCursorHeight,
1343 cursorInfo.lineHeight );
1345 // Sets the grab handle position.
1346 mEventData->mDecorator->SetPosition( GRAB_HANDLE,
1349 cursorInfo.lineHeight );
1351 if( cursorInfo.isSecondaryCursor )
1353 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_BOTH );
1354 mEventData->mDecorator->SetPosition( SECONDARY_CURSOR,
1355 cursorInfo.secondaryPosition.x + offset.x,
1356 cursorInfo.secondaryPosition.y + offset.y,
1357 cursorInfo.secondaryCursorHeight,
1358 cursorInfo.lineHeight );
1359 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Secondary cursor position: %f,%f\n", cursorInfo.secondaryPosition.x + offset.x, cursorInfo.secondaryPosition.y + offset.y );
1363 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1368 void Controller::Impl::RequestRelayout()
1370 mControlInterface.RequestTextRelayout();
1375 } // namespace Toolkit