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).
84 * @param[in] visualModel The visual model.
85 * @param[in] fontClient The font client.
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( true ),
133 mHorizontalScrollingEnabled( true ),
134 mVerticalScrollingEnabled( false ),
135 mUpdateCursorPosition( false ),
136 mUpdateLeftSelectionPosition( false ),
137 mUpdateRightSelectionPosition( false ),
138 mScrollAfterUpdatePosition( false ),
139 mScrollAfterDelete( false ),
140 mAllTextSelected( false )
143 EventData::~EventData()
146 bool Controller::Impl::ProcessInputEvents()
148 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "-->Controller::ProcessInputEvents\n" );
149 if( NULL == mEventData )
151 // Nothing to do if there is no text input.
152 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::ProcessInputEvents no event data\n" );
156 if( mEventData->mDecorator )
158 for( std::vector<Event>::iterator iter = mEventData->mEventQueue.begin();
159 iter != mEventData->mEventQueue.end();
164 case Event::CURSOR_KEY_EVENT:
166 OnCursorKeyEvent( *iter );
169 case Event::TAP_EVENT:
174 case Event::LONG_PRESS_EVENT:
176 OnLongPressEvent( *iter );
179 case Event::PAN_EVENT:
184 case Event::GRAB_HANDLE_EVENT:
185 case Event::LEFT_SELECTION_HANDLE_EVENT:
186 case Event::RIGHT_SELECTION_HANDLE_EVENT: // Fall through
188 OnHandleEvent( *iter );
193 OnSelectEvent( *iter );
196 case Event::SELECT_ALL:
205 // The cursor must also be repositioned after inserts into the model
206 if( mEventData->mUpdateCursorPosition )
208 // Updates the cursor position and scrolls the text to make it visible.
210 UpdateCursorPosition();
212 if( mEventData->mScrollAfterUpdatePosition )
214 const Vector2& primaryCursorPosition = mEventData->mDecorator->GetPosition( PRIMARY_CURSOR );
216 ScrollToMakePositionVisible( primaryCursorPosition );
217 mEventData->mScrollAfterUpdatePosition = false;
220 mEventData->mDecoratorUpdated = true;
221 mEventData->mUpdateCursorPosition = false;
223 else if( mEventData->mScrollAfterDelete )
225 ScrollTextToMatchCursor();
226 mEventData->mDecoratorUpdated = true;
227 mEventData->mScrollAfterDelete = false;
231 bool leftScroll = false;
232 bool rightScroll = false;
234 if( mEventData->mUpdateLeftSelectionPosition )
236 UpdateSelectionHandle( LEFT_SELECTION_HANDLE );
238 if( mEventData->mScrollAfterUpdatePosition )
240 const Vector2& leftHandlePosition = mEventData->mDecorator->GetPosition( LEFT_SELECTION_HANDLE );
242 ScrollToMakePositionVisible( leftHandlePosition );
247 mEventData->mDecoratorUpdated = true;
248 mEventData->mUpdateLeftSelectionPosition = false;
251 if( mEventData->mUpdateRightSelectionPosition )
253 UpdateSelectionHandle( RIGHT_SELECTION_HANDLE );
255 if( mEventData->mScrollAfterUpdatePosition )
257 const Vector2& rightHandlePosition = mEventData->mDecorator->GetPosition( RIGHT_SELECTION_HANDLE );
259 ScrollToMakePositionVisible( rightHandlePosition );
264 mEventData->mDecoratorUpdated = true;
265 mEventData->mUpdateRightSelectionPosition = false;
268 if( leftScroll || rightScroll )
270 mEventData->mScrollAfterUpdatePosition = false;
274 mEventData->mEventQueue.clear();
276 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::ProcessInputEvents\n" );
278 const bool decoratorUpdated = mEventData->mDecoratorUpdated;
279 mEventData->mDecoratorUpdated = false;
281 return decoratorUpdated;
284 void Controller::Impl::UpdateModel( OperationsMask operationsRequired )
286 // Calculate the operations to be done.
287 const OperationsMask operations = static_cast<OperationsMask>( mOperationsPending & operationsRequired );
289 Vector<Character>& utf32Characters = mLogicalModel->mText;
291 const Length numberOfCharacters = utf32Characters.Count();
293 Vector<LineBreakInfo>& lineBreakInfo = mLogicalModel->mLineBreakInfo;
294 if( GET_LINE_BREAKS & operations )
296 // Retrieves the line break info. The line break info is used to split the text in 'paragraphs' to
297 // calculate the bidirectional info for each 'paragraph'.
298 // It's also used to layout the text (where it should be a new line) or to shape the text (text in different lines
299 // is not shaped together).
300 lineBreakInfo.Resize( numberOfCharacters, TextAbstraction::LINE_NO_BREAK );
302 SetLineBreakInfo( utf32Characters,
306 Vector<WordBreakInfo>& wordBreakInfo = mLogicalModel->mWordBreakInfo;
307 if( GET_WORD_BREAKS & operations )
309 // Retrieves the word break info. The word break info is used to layout the text (where to wrap the text in lines).
310 wordBreakInfo.Resize( numberOfCharacters, TextAbstraction::WORD_NO_BREAK );
312 SetWordBreakInfo( utf32Characters,
316 const bool getScripts = GET_SCRIPTS & operations;
317 const bool validateFonts = VALIDATE_FONTS & operations;
319 Vector<ScriptRun>& scripts = mLogicalModel->mScriptRuns;
320 Vector<FontRun>& validFonts = mLogicalModel->mFontRuns;
322 if( getScripts || validateFonts )
324 // Validates the fonts assigned by the application or assigns default ones.
325 // It makes sure all the characters are going to be rendered by the correct font.
326 MultilanguageSupport multilanguageSupport = MultilanguageSupport::Get();
330 // Retrieves the scripts used in the text.
331 multilanguageSupport.SetScripts( utf32Characters,
338 if( 0u == validFonts.Count() )
340 // Copy the requested font defaults received via the property system.
341 // These may not be valid i.e. may not contain glyphs for the necessary scripts.
342 GetDefaultFonts( validFonts, numberOfCharacters );
345 // Validates the fonts. If there is a character with no assigned font it sets a default one.
346 // After this call, fonts are validated.
347 multilanguageSupport.ValidateFonts( utf32Characters,
353 Vector<Character> mirroredUtf32Characters;
354 bool textMirrored = false;
355 if( BIDI_INFO & operations )
357 // Count the number of LINE_NO_BREAK to reserve some space for the vector of paragraph's
358 // bidirectional info.
360 Length numberOfParagraphs = 0u;
362 const TextAbstraction::LineBreakInfo* lineBreakInfoBuffer = lineBreakInfo.Begin();
363 for( Length index = 0u; index < numberOfCharacters; ++index )
365 if( TextAbstraction::LINE_NO_BREAK == *( lineBreakInfoBuffer + index ) )
367 ++numberOfParagraphs;
371 Vector<BidirectionalParagraphInfoRun>& bidirectionalInfo = mLogicalModel->mBidirectionalParagraphInfo;
372 bidirectionalInfo.Reserve( numberOfParagraphs );
374 // Calculates the bidirectional info for the whole paragraph if it contains right to left scripts.
375 SetBidirectionalInfo( utf32Characters,
380 if( 0u != bidirectionalInfo.Count() )
382 // This paragraph has right to left text. Some characters may need to be mirrored.
383 // TODO: consider if the mirrored string can be stored as well.
385 textMirrored = GetMirroredText( utf32Characters, mirroredUtf32Characters );
387 // Only set the character directions if there is right to left characters.
388 Vector<CharacterDirection>& directions = mLogicalModel->mCharacterDirections;
389 directions.Resize( numberOfCharacters );
391 GetCharactersDirection( bidirectionalInfo,
396 // There is no right to left characters. Clear the directions vector.
397 mLogicalModel->mCharacterDirections.Clear();
402 Vector<GlyphInfo>& glyphs = mVisualModel->mGlyphs;
403 Vector<CharacterIndex>& glyphsToCharactersMap = mVisualModel->mGlyphsToCharacters;
404 Vector<Length>& charactersPerGlyph = mVisualModel->mCharactersPerGlyph;
405 if( SHAPE_TEXT & operations )
407 const Vector<Character>& textToShape = textMirrored ? mirroredUtf32Characters : utf32Characters;
409 ShapeText( textToShape,
414 glyphsToCharactersMap,
415 charactersPerGlyph );
417 // Create the 'number of glyphs' per character and the glyph to character conversion tables.
418 mVisualModel->CreateGlyphsPerCharacterTable( numberOfCharacters );
419 mVisualModel->CreateCharacterToGlyphTable( numberOfCharacters );
422 const Length numberOfGlyphs = glyphs.Count();
424 if( GET_GLYPH_METRICS & operations )
426 mFontClient.GetGlyphMetrics( glyphs.Begin(), numberOfGlyphs );
430 void Controller::Impl::GetDefaultFonts( Vector<FontRun>& fonts, Length numberOfCharacters )
435 fontRun.characterRun.characterIndex = 0;
436 fontRun.characterRun.numberOfCharacters = numberOfCharacters;
437 fontRun.fontId = mFontDefaults->GetFontId( mFontClient );
438 fontRun.isDefault = true;
440 fonts.PushBack( fontRun );
444 float Controller::Impl::GetDefaultFontLineHeight()
446 FontId defaultFontId = 0u;
447 if( NULL == mFontDefaults )
449 defaultFontId = mFontClient.GetFontId( EMPTY_STRING,
454 defaultFontId = mFontDefaults->GetFontId( mFontClient );
457 Text::FontMetrics fontMetrics;
458 mFontClient.GetFontMetrics( defaultFontId, fontMetrics );
460 return( fontMetrics.ascender - fontMetrics.descender );
463 void Controller::Impl::OnCursorKeyEvent( const Event& event )
465 if( NULL == mEventData )
467 // Nothing to do if there is no text input.
471 int keyCode = event.p1.mInt;
473 if( Dali::DALI_KEY_CURSOR_LEFT == keyCode )
475 if( mEventData->mPrimaryCursorPosition > 0u )
477 mEventData->mPrimaryCursorPosition = CalculateNewCursorIndex( mEventData->mPrimaryCursorPosition - 1u );
480 else if( Dali::DALI_KEY_CURSOR_RIGHT == keyCode )
482 if( mLogicalModel->mText.Count() > mEventData->mPrimaryCursorPosition )
484 mEventData->mPrimaryCursorPosition = CalculateNewCursorIndex( mEventData->mPrimaryCursorPosition );
487 else if( Dali::DALI_KEY_CURSOR_UP == keyCode )
491 else if( Dali::DALI_KEY_CURSOR_DOWN == keyCode )
496 mEventData->mUpdateCursorPosition = true;
497 mEventData->mScrollAfterUpdatePosition = true;
500 void Controller::Impl::OnTapEvent( const Event& event )
502 if( NULL != mEventData )
504 const unsigned int tapCount = event.p1.mUint;
508 if( ! IsShowingPlaceholderText() )
510 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
511 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
513 mEventData->mPrimaryCursorPosition = GetClosestCursorIndex( xPosition,
518 mEventData->mPrimaryCursorPosition = 0u;
521 mEventData->mUpdateCursorPosition = true;
522 mEventData->mScrollAfterUpdatePosition = true;
527 void Controller::Impl::OnPanEvent( const Event& event )
529 if( NULL == mEventData )
531 // Nothing to do if there is no text input.
535 int state = event.p1.mInt;
537 if( Gesture::Started == state ||
538 Gesture::Continuing == state )
540 const Vector2& actualSize = mVisualModel->GetActualSize();
541 const Vector2 currentScroll = mEventData->mScrollPosition;
543 if( mEventData->mHorizontalScrollingEnabled )
545 const float displacementX = event.p2.mFloat;
546 mEventData->mScrollPosition.x += displacementX;
548 ClampHorizontalScroll( actualSize );
551 if( mEventData->mVerticalScrollingEnabled )
553 const float displacementY = event.p3.mFloat;
554 mEventData->mScrollPosition.y += displacementY;
556 ClampVerticalScroll( actualSize );
559 if( mEventData->mDecorator )
561 mEventData->mDecorator->UpdatePositions( mEventData->mScrollPosition - currentScroll );
566 void Controller::Impl::OnLongPressEvent( const Event& event )
568 if ( EventData::EDITING == mEventData->mState )
570 ChangeState ( EventData::EDITING_WITH_POPUP );
571 mEventData->mDecoratorUpdated = true;
575 void Controller::Impl::OnHandleEvent( const Event& event )
577 if( NULL == mEventData )
579 // Nothing to do if there is no text input.
583 const unsigned int state = event.p1.mUint;
584 const bool handleStopScrolling = ( HANDLE_STOP_SCROLLING == state );
586 if( HANDLE_PRESSED == state )
588 // The event.p2 and event.p3 are in decorator coords. Need to transforms to text coords.
589 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
590 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
592 const CharacterIndex handleNewPosition = GetClosestCursorIndex( xPosition, yPosition );
594 if( Event::GRAB_HANDLE_EVENT == event.type )
596 ChangeState ( EventData::GRAB_HANDLE_PANNING );
598 if( handleNewPosition != mEventData->mPrimaryCursorPosition )
600 mEventData->mPrimaryCursorPosition = handleNewPosition;
601 mEventData->mUpdateCursorPosition = true;
604 else if( Event::LEFT_SELECTION_HANDLE_EVENT == event.type )
606 ChangeState ( EventData::SELECTION_HANDLE_PANNING );
608 if( ( handleNewPosition != mEventData->mLeftSelectionPosition ) &&
609 ( handleNewPosition != mEventData->mRightSelectionPosition ) )
611 mEventData->mLeftSelectionPosition = handleNewPosition;
613 RepositionSelectionHandles( mEventData->mLeftSelectionPosition,
614 mEventData->mRightSelectionPosition );
616 mEventData->mUpdateLeftSelectionPosition = true;
619 else if( Event::RIGHT_SELECTION_HANDLE_EVENT == event.type )
621 ChangeState ( EventData::SELECTION_HANDLE_PANNING );
623 if( ( handleNewPosition != mEventData->mRightSelectionPosition ) &&
624 ( handleNewPosition != mEventData->mLeftSelectionPosition ) )
626 mEventData->mRightSelectionPosition = handleNewPosition;
628 RepositionSelectionHandles( mEventData->mLeftSelectionPosition,
629 mEventData->mRightSelectionPosition );
631 mEventData->mUpdateRightSelectionPosition = true;
634 } // end ( HANDLE_PRESSED == state )
635 else if( ( HANDLE_RELEASED == state ) ||
636 handleStopScrolling )
638 CharacterIndex handlePosition = 0u;
639 if( handleStopScrolling )
641 // The event.p2 and event.p3 are in decorator coords. Need to transforms to text coords.
642 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
643 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
645 handlePosition = GetClosestCursorIndex( xPosition, yPosition );
648 if( Event::GRAB_HANDLE_EVENT == event.type )
650 mEventData->mUpdateCursorPosition = true;
652 ChangeState( EventData::EDITING_WITH_POPUP );
654 if( handleStopScrolling )
656 mEventData->mScrollAfterUpdatePosition = mEventData->mPrimaryCursorPosition != handlePosition;
657 mEventData->mPrimaryCursorPosition = handlePosition;
660 else if( Event::LEFT_SELECTION_HANDLE_EVENT == event.type )
662 ChangeState( EventData::SELECTING );
664 if( handleStopScrolling )
666 mEventData->mUpdateLeftSelectionPosition = ( mEventData->mRightSelectionPosition != handlePosition );
667 mEventData->mScrollAfterUpdatePosition = mEventData->mUpdateLeftSelectionPosition;
669 if( mEventData->mUpdateLeftSelectionPosition )
671 mEventData->mLeftSelectionPosition = handlePosition;
673 RepositionSelectionHandles( mEventData->mLeftSelectionPosition,
674 mEventData->mRightSelectionPosition );
678 else if( Event::RIGHT_SELECTION_HANDLE_EVENT == event.type )
680 ChangeState( EventData::SELECTING );
682 if( handleStopScrolling )
684 mEventData->mUpdateRightSelectionPosition = ( mEventData->mLeftSelectionPosition != handlePosition );
685 mEventData->mScrollAfterUpdatePosition = mEventData->mUpdateRightSelectionPosition;
686 if( mEventData->mUpdateRightSelectionPosition )
688 mEventData->mRightSelectionPosition = handlePosition;
689 RepositionSelectionHandles( mEventData->mLeftSelectionPosition,
690 mEventData->mRightSelectionPosition );
695 mEventData->mDecoratorUpdated = true;
696 } // end ( ( HANDLE_RELEASED == state ) || ( HANDLE_STOP_SCROLLING == state ) )
697 else if( HANDLE_SCROLLING == state )
699 const float xSpeed = event.p2.mFloat;
700 const Vector2& actualSize = mVisualModel->GetActualSize();
701 const Vector2 currentScrollPosition = mEventData->mScrollPosition;
703 mEventData->mScrollPosition.x += xSpeed;
705 ClampHorizontalScroll( actualSize );
707 bool endOfScroll = false;
708 if( Vector2::ZERO == ( currentScrollPosition - mEventData->mScrollPosition ) )
710 // Notify the decorator there is no more text to scroll.
711 // The decorator won't send more scroll events.
712 mEventData->mDecorator->NotifyEndOfScroll();
713 // Still need to set the position of the handle.
717 // Set the position of the handle.
718 const bool scrollRightDirection = xSpeed > 0.f;
719 const bool leftSelectionHandleEvent = Event::LEFT_SELECTION_HANDLE_EVENT == event.type;
720 const bool rightSelectionHandleEvent = Event::RIGHT_SELECTION_HANDLE_EVENT == event.type;
722 if( Event::GRAB_HANDLE_EVENT == event.type )
724 ChangeState( EventData::GRAB_HANDLE_PANNING );
726 Vector2 position = mEventData->mDecorator->GetPosition( GRAB_HANDLE );
728 // Position the grag handle close to either the left or right edge.
729 position.x = scrollRightDirection ? 0.f : mControlSize.width;
731 // Get the new handle position.
732 // The grab handle's position is in decorator coords. Need to transforms to text coords.
733 const CharacterIndex handlePosition = GetClosestCursorIndex( position.x - mEventData->mScrollPosition.x - mAlignmentOffset.x,
734 position.y - mEventData->mScrollPosition.y - mAlignmentOffset.y );
736 mEventData->mUpdateCursorPosition = mEventData->mPrimaryCursorPosition != handlePosition;
737 mEventData->mScrollAfterUpdatePosition = mEventData->mUpdateCursorPosition;
738 mEventData->mPrimaryCursorPosition = handlePosition;
740 else if( leftSelectionHandleEvent || rightSelectionHandleEvent )
742 // TODO: This is recalculating the selection box every time the text is scrolled with the selection handles.
743 // Think if something can be done to save power.
745 ChangeState( EventData::SELECTION_HANDLE_PANNING );
747 Vector2 position = mEventData->mDecorator->GetPosition( leftSelectionHandleEvent ? Text::LEFT_SELECTION_HANDLE : Text::RIGHT_SELECTION_HANDLE );
749 // Position the selection handle close to either the left or right edge.
750 position.x = scrollRightDirection ? 0.f : mControlSize.width;
752 // Get the new handle position.
753 // The selection handle's position is in decorator coords. Need to transforms to text coords.
754 const CharacterIndex handlePosition = GetClosestCursorIndex( position.x - mEventData->mScrollPosition.x - mAlignmentOffset.x,
755 position.y - mEventData->mScrollPosition.y - mAlignmentOffset.y );
757 if( leftSelectionHandleEvent )
759 const bool differentHandles = ( mEventData->mLeftSelectionPosition != handlePosition ) && ( mEventData->mRightSelectionPosition != handlePosition );
760 mEventData->mUpdateLeftSelectionPosition = endOfScroll || differentHandles;
761 if( differentHandles )
763 mEventData->mLeftSelectionPosition = handlePosition;
768 const bool differentHandles = ( mEventData->mRightSelectionPosition != handlePosition ) && ( mEventData->mLeftSelectionPosition != handlePosition );
769 mEventData->mUpdateRightSelectionPosition = endOfScroll || differentHandles;
770 if( differentHandles )
772 mEventData->mRightSelectionPosition = handlePosition;
776 if( mEventData->mUpdateLeftSelectionPosition || mEventData->mUpdateRightSelectionPosition )
778 RepositionSelectionHandles( mEventData->mLeftSelectionPosition,
779 mEventData->mRightSelectionPosition );
781 mEventData->mScrollAfterUpdatePosition = true;
784 mEventData->mDecoratorUpdated = true;
785 } // end ( HANDLE_SCROLLING == state )
788 void Controller::Impl::OnSelectEvent( const Event& event )
790 if( NULL == mEventData )
792 // Nothing to do if there is no text.
796 if( mEventData->mSelectionEnabled )
798 // The event.p2 and event.p3 are in decorator coords. Need to transforms to text coords.
799 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
800 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
802 const CharacterIndex leftPosition = mEventData->mLeftSelectionPosition;
803 const CharacterIndex rightPosition = mEventData->mRightSelectionPosition;
805 RepositionSelectionHandles( xPosition,
808 mEventData->mUpdateLeftSelectionPosition = leftPosition != mEventData->mLeftSelectionPosition;
809 mEventData->mUpdateRightSelectionPosition = rightPosition != mEventData->mRightSelectionPosition;
811 mEventData->mScrollAfterUpdatePosition = ( ( mEventData->mUpdateLeftSelectionPosition || mEventData->mUpdateRightSelectionPosition ) &&
812 ( mEventData->mLeftSelectionPosition != mEventData->mRightSelectionPosition ) );
816 void Controller::Impl::OnSelectAllEvent()
818 if( NULL == mEventData )
820 // Nothing to do if there is no text.
824 if( mEventData->mSelectionEnabled )
826 RepositionSelectionHandles( 0u,
827 mLogicalModel->mText.Count() );
829 mEventData->mScrollAfterUpdatePosition = true;
830 mEventData->mUpdateLeftSelectionPosition = true;
831 mEventData->mUpdateRightSelectionPosition = true;
835 void Controller::Impl::RetrieveSelection( std::string& selectedText, bool deleteAfterRetreival )
837 if( mEventData->mLeftSelectionPosition == mEventData->mRightSelectionPosition )
839 // Nothing to select if handles are in the same place.
844 //Get start and end position of selection
845 uint32_t startOfSelectedText = mEventData->mLeftSelectionPosition;
846 uint32_t lengthOfSelectedText = mEventData->mRightSelectionPosition - startOfSelectedText;
848 // Validate the start and end selection points
849 if( ( startOfSelectedText >= 0 ) && ( ( startOfSelectedText + lengthOfSelectedText ) <= mLogicalModel->mText.Count() ) )
851 //Get text as a UTF8 string
852 Vector<Character>& utf32Characters = mLogicalModel->mText;
854 Utf32ToUtf8( &utf32Characters[startOfSelectedText], lengthOfSelectedText, selectedText );
856 if ( deleteAfterRetreival ) // Only delete text if copied successfully
858 // Delete text between handles
859 Vector<Character>& currentText = mLogicalModel->mText;
861 Vector<Character>::Iterator first = currentText.Begin() + startOfSelectedText;
862 Vector<Character>::Iterator last = first + lengthOfSelectedText;
863 currentText.Erase( first, last );
865 mEventData->mPrimaryCursorPosition = mEventData->mLeftSelectionPosition;
866 mEventData->mScrollAfterDelete = true;
867 mEventData->mDecoratorUpdated = true;
871 void Controller::Impl::ShowClipboard()
875 mClipboard.ShowClipboard();
879 void Controller::Impl::HideClipboard()
883 mClipboard.HideClipboard();
887 bool Controller::Impl::CopyStringToClipboard( std::string& source )
889 //Send string to clipboard
890 return ( mClipboard && mClipboard.SetItem( source ) );
893 void Controller::Impl::SendSelectionToClipboard( bool deleteAfterSending )
895 std::string selectedText;
896 RetrieveSelection( selectedText, deleteAfterSending );
897 CopyStringToClipboard( selectedText );
898 ChangeState( EventData::EDITING );
901 void Controller::Impl::GetTextFromClipboard( unsigned int itemIndex, std::string& retreivedString )
905 retreivedString = mClipboard.GetItem( itemIndex );
909 void Controller::Impl::RepositionSelectionHandles( CharacterIndex selectionStart, CharacterIndex selectionEnd )
911 if( selectionStart == selectionEnd )
913 // Nothing to select if handles are in the same place.
917 mEventData->mDecorator->ClearHighlights();
919 mEventData->mLeftSelectionPosition = selectionStart;
920 mEventData->mRightSelectionPosition = selectionEnd;
922 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
923 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
924 const GlyphInfo* const glyphsBuffer = mVisualModel->mGlyphs.Begin();
925 const Vector2* const positionsBuffer = mVisualModel->mGlyphPositions.Begin();
927 // TODO: Better algorithm to create the highlight box.
930 const Vector<LineRun>& lines = mVisualModel->mLines;
931 const LineRun& firstLine = *lines.Begin();
932 const float height = firstLine.ascender + -firstLine.descender;
934 const bool indicesSwapped = ( selectionStart > selectionEnd );
937 std::swap( selectionStart, selectionEnd );
940 const GlyphIndex glyphStart = *( charactersToGlyphBuffer + selectionStart );
941 const Length numberOfGlyphs = *( glyphsPerCharacterBuffer + ( selectionEnd - 1u ) );
942 const GlyphIndex glyphEnd = *( charactersToGlyphBuffer + ( selectionEnd - 1u ) ) + ( ( numberOfGlyphs > 0 ) ? numberOfGlyphs - 1u : 0u );
944 mEventData->mDecorator->SwapSelectionHandlesEnabled( firstLine.direction != indicesSwapped );
946 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
948 for( GlyphIndex index = glyphStart; index <= glyphEnd; ++index )
950 // TODO: Fix the LATIN ligatures. i.e ff, fi, etc...
951 const GlyphInfo& glyph = *( glyphsBuffer + index );
952 const Vector2& position = *( positionsBuffer + index );
954 const float xPosition = position.x - glyph.xBearing + offset.x;
955 mEventData->mDecorator->AddHighlight( xPosition, offset.y, xPosition + glyph.advance, height );
958 CursorInfo primaryCursorInfo;
959 GetCursorPosition( mEventData->mLeftSelectionPosition,
962 CursorInfo secondaryCursorInfo;
963 GetCursorPosition( mEventData->mRightSelectionPosition,
964 secondaryCursorInfo );
966 const Vector2 primaryPosition = primaryCursorInfo.primaryPosition + offset;
967 const Vector2 secondaryPosition = secondaryCursorInfo.primaryPosition + offset;
969 mEventData->mDecorator->SetPosition( LEFT_SELECTION_HANDLE, primaryPosition.x, primaryPosition.y, primaryCursorInfo.lineHeight );
971 mEventData->mDecorator->SetPosition( RIGHT_SELECTION_HANDLE, secondaryPosition.x, secondaryPosition.y, secondaryCursorInfo.lineHeight );
973 // Set the flag to update the decorator.
974 mEventData->mDecoratorUpdated = true;
977 void Controller::Impl::RepositionSelectionHandles( float visualX, float visualY )
979 if( NULL == mEventData )
981 // Nothing to do if there is no text input.
985 if( IsShowingPlaceholderText() )
987 // Nothing to do if there is the place-holder text.
991 const Length numberOfGlyphs = mVisualModel->mGlyphs.Count();
992 const Length numberOfLines = mVisualModel->mLines.Count();
993 if( 0 == numberOfGlyphs ||
996 // Nothing to do if there is no text.
1000 // Find which word was selected
1001 CharacterIndex selectionStart( 0 );
1002 CharacterIndex selectionEnd( 0 );
1003 FindSelectionIndices( visualX, visualY, selectionStart, selectionEnd );
1004 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "%p selectionStart %d selectionEnd %d\n", this, selectionStart, selectionEnd );
1006 if( selectionStart == selectionEnd )
1008 ChangeState( EventData::EDITING );
1009 // Nothing to select. i.e. a white space, out of bounds
1013 RepositionSelectionHandles( selectionStart, selectionEnd );
1016 void Controller::Impl::SetPopupButtons()
1019 * Sets the Popup buttons to be shown depending on State.
1021 * If SELECTING : CUT & COPY + ( PASTE & CLIPBOARD if content available to paste )
1023 * If EDITING_WITH_POPUP : SELECT & SELECT_ALL
1026 TextSelectionPopup::Buttons buttonsToShow = TextSelectionPopup::NONE;
1028 if ( ( EventData::SELECTING == mEventData->mState ) || ( EventData::SELECTION_CHANGED == mEventData->mState ) )
1030 buttonsToShow = TextSelectionPopup::Buttons( TextSelectionPopup::CUT | TextSelectionPopup::COPY );
1032 if ( !IsClipboardEmpty() )
1034 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
1035 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::CLIPBOARD ) );
1038 if ( !mEventData->mAllTextSelected )
1040 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::SELECT_ALL ) );
1043 else if ( EventData::EDITING_WITH_POPUP == mEventData->mState )
1045 if ( mLogicalModel->mText.Count() && !IsShowingPlaceholderText())
1047 buttonsToShow = TextSelectionPopup::Buttons( TextSelectionPopup::SELECT | TextSelectionPopup::SELECT_ALL );
1050 if ( !IsClipboardEmpty() )
1052 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
1053 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::CLIPBOARD ) );
1057 mEventData->mDecorator->SetEnabledPopupButtons( buttonsToShow );
1060 void Controller::Impl::ChangeState( EventData::State newState )
1062 if( NULL == mEventData )
1064 // Nothing to do if there is no text input.
1068 if( mEventData->mState != newState )
1070 mEventData->mState = newState;
1072 if( EventData::INACTIVE == mEventData->mState )
1074 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
1075 mEventData->mDecorator->StopCursorBlink();
1076 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1077 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1078 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1079 mEventData->mDecorator->SetPopupActive( false );
1080 mEventData->mDecoratorUpdated = true;
1083 else if ( EventData::INTERRUPTED == mEventData->mState)
1085 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1086 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1087 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1088 mEventData->mDecorator->SetPopupActive( false );
1089 mEventData->mDecoratorUpdated = true;
1092 else if ( EventData::SELECTING == mEventData->mState )
1094 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
1095 mEventData->mDecorator->StopCursorBlink();
1096 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1097 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true );
1098 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true );
1099 if( mEventData->mGrabHandlePopupEnabled )
1102 mEventData->mDecorator->SetPopupActive( true );
1104 mEventData->mDecoratorUpdated = true;
1106 else if ( EventData::SELECTION_CHANGED == mEventData->mState )
1108 if( mEventData->mGrabHandlePopupEnabled )
1111 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
1112 mEventData->mDecorator->SetPopupActive( true );
1114 mEventData->mDecoratorUpdated = true;
1116 else if( EventData::EDITING == mEventData->mState )
1118 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1119 if( mEventData->mCursorBlinkEnabled )
1121 mEventData->mDecorator->StartCursorBlink();
1123 // Grab handle is not shown until a tap is received whilst EDITING
1124 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1125 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1126 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1127 if( mEventData->mGrabHandlePopupEnabled )
1129 mEventData->mDecorator->SetPopupActive( false );
1131 mEventData->mDecoratorUpdated = true;
1134 else if( EventData::EDITING_WITH_POPUP == mEventData->mState )
1136 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1137 if( mEventData->mCursorBlinkEnabled )
1139 mEventData->mDecorator->StartCursorBlink();
1141 if( mEventData->mSelectionEnabled )
1143 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1144 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1148 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
1150 if( mEventData->mGrabHandlePopupEnabled )
1153 mEventData->mDecorator->SetPopupActive( true );
1156 mEventData->mDecoratorUpdated = true;
1158 else if( EventData::EDITING_WITH_GRAB_HANDLE == mEventData->mState )
1160 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1161 if( mEventData->mCursorBlinkEnabled )
1163 mEventData->mDecorator->StartCursorBlink();
1165 // Grab handle is not shown until a tap is received whilst EDITING
1166 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
1167 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1168 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1169 if( mEventData->mGrabHandlePopupEnabled )
1171 mEventData->mDecorator->SetPopupActive( false );
1173 mEventData->mDecoratorUpdated = true;
1176 else if ( EventData::SELECTION_HANDLE_PANNING == mEventData->mState )
1178 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
1179 mEventData->mDecorator->StopCursorBlink();
1180 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1181 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true );
1182 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true );
1183 if( mEventData->mGrabHandlePopupEnabled )
1185 mEventData->mDecorator->SetPopupActive( false );
1187 mEventData->mDecoratorUpdated = true;
1189 else if ( EventData::GRAB_HANDLE_PANNING == mEventData->mState )
1191 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1192 if( mEventData->mCursorBlinkEnabled )
1194 mEventData->mDecorator->StartCursorBlink();
1196 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
1197 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1198 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1199 if( mEventData->mGrabHandlePopupEnabled )
1201 mEventData->mDecorator->SetPopupActive( false );
1203 mEventData->mDecoratorUpdated = true;
1208 LineIndex Controller::Impl::GetClosestLine( float y ) const
1210 float totalHeight = 0.f;
1211 LineIndex lineIndex = 0u;
1213 const Vector<LineRun>& lines = mVisualModel->mLines;
1214 for( LineIndex endLine = lines.Count();
1215 lineIndex < endLine;
1218 const LineRun& lineRun = lines[lineIndex];
1219 totalHeight += lineRun.ascender + -lineRun.descender;
1220 if( y < totalHeight )
1226 if( lineIndex == 0 )
1234 void Controller::Impl::FindSelectionIndices( float visualX, float visualY, CharacterIndex& startIndex, CharacterIndex& endIndex )
1236 CharacterIndex hitCharacter = GetClosestCursorIndex( visualX, visualY );
1237 if( hitCharacter >= mLogicalModel->mText.Count() )
1239 // Selection out of bounds.
1243 startIndex = hitCharacter;
1244 endIndex = hitCharacter;
1246 if( !TextAbstraction::IsWhiteSpace( mLogicalModel->mText[hitCharacter] ) )
1248 // Find the start and end of the text
1249 for( startIndex = hitCharacter; startIndex > 0; --startIndex )
1251 Character charCode = mLogicalModel->mText[ startIndex-1 ];
1252 if( TextAbstraction::IsWhiteSpace( charCode ) )
1257 const CharacterIndex pastTheEnd = mLogicalModel->mText.Count();
1258 for( endIndex = hitCharacter + 1u; endIndex < pastTheEnd; ++endIndex )
1260 Character charCode = mLogicalModel->mText[ endIndex ];
1261 if( TextAbstraction::IsWhiteSpace( charCode ) )
1269 CharacterIndex Controller::Impl::GetClosestCursorIndex( float visualX,
1272 if( NULL == mEventData )
1274 // Nothing to do if there is no text input.
1278 CharacterIndex logicalIndex = 0u;
1280 const Length numberOfGlyphs = mVisualModel->mGlyphs.Count();
1281 const Length numberOfLines = mVisualModel->mLines.Count();
1282 if( 0 == numberOfGlyphs ||
1283 0 == numberOfLines )
1285 return logicalIndex;
1288 // Find which line is closest
1289 const LineIndex lineIndex = GetClosestLine( visualY );
1290 const LineRun& line = mVisualModel->mLines[lineIndex];
1292 // Get the positions of the glyphs.
1293 const Vector<Vector2>& positions = mVisualModel->mGlyphPositions;
1294 const Vector2* const positionsBuffer = positions.Begin();
1296 // Get the visual to logical conversion tables.
1297 const CharacterIndex* const visualToLogicalBuffer = ( 0u != mLogicalModel->mVisualToLogicalMap.Count() ) ? mLogicalModel->mVisualToLogicalMap.Begin() : NULL;
1298 const CharacterIndex* const visualToLogicalCursorBuffer = mLogicalModel->mVisualToLogicalCursorMap.Begin();
1300 // Get the character to glyph conversion table.
1301 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
1303 // Get the glyphs per character table.
1304 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
1305 const Length* const charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
1307 // If the vector is void, there is no right to left characters.
1308 const bool hasRightToLeftCharacters = NULL != visualToLogicalBuffer;
1310 const CharacterIndex startCharacter = line.characterRun.characterIndex;
1311 const CharacterIndex endCharacter = line.characterRun.characterIndex + line.characterRun.numberOfCharacters;
1312 DALI_ASSERT_DEBUG( endCharacter <= mLogicalModel->mText.Count() && "Invalid line info" );
1314 // Whether there is a hit on a glyph.
1315 bool matched = false;
1317 // Traverses glyphs in visual order. To do that use the visual to logical conversion table.
1318 CharacterIndex visualIndex = startCharacter;
1319 for( ; !matched && ( visualIndex < endCharacter ); ++visualIndex )
1321 // The character in logical order.
1322 const CharacterIndex characterLogicalOrderIndex = hasRightToLeftCharacters ? *( visualToLogicalBuffer + visualIndex ) : visualIndex;
1324 // Get the script of the character.
1325 const Script script = mLogicalModel->GetScript( characterLogicalOrderIndex );
1327 // The first glyph for that character in logical order.
1328 const GlyphIndex glyphLogicalOrderIndex = *( charactersToGlyphBuffer + characterLogicalOrderIndex );
1329 // The number of glyphs for that character
1330 const Length numberOfGlyphs = *( glyphsPerCharacterBuffer + characterLogicalOrderIndex );
1332 // Get the metrics for the group of glyphs.
1333 GlyphMetrics glyphMetrics;
1334 GetGlyphsMetrics( glyphLogicalOrderIndex,
1340 const Vector2& position = *( positionsBuffer + glyphLogicalOrderIndex );
1342 // Prevents to jump the whole Latin ligatures like fi, ff, ...
1343 const Length numberOfCharactersInLigature = ( TextAbstraction::LATIN == script ) ? *( charactersPerGlyphBuffer + glyphLogicalOrderIndex ) : 1u;
1344 const float glyphAdvance = glyphMetrics.advance / static_cast<float>( numberOfCharactersInLigature );
1346 for( GlyphIndex index = 0u; !matched && ( index < numberOfCharactersInLigature ); ++index )
1348 // Find the mid-point of the area containing the glyph
1349 const float glyphCenter = -glyphMetrics.xBearing + position.x + ( static_cast<float>( index ) + 0.5f ) * glyphAdvance;
1351 if( visualX < glyphCenter )
1353 visualIndex += index;
1365 // Return the logical position of the cursor in characters.
1369 visualIndex = endCharacter;
1372 logicalIndex = hasRightToLeftCharacters ? *( visualToLogicalCursorBuffer + visualIndex ) : visualIndex;
1373 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "%p closest visualIndex %d logicalIndex %d\n", this, visualIndex, logicalIndex );
1375 return logicalIndex;
1378 void Controller::Impl::GetCursorPosition( CharacterIndex logical,
1379 CursorInfo& cursorInfo )
1381 // TODO: Check for multiline with \n, etc...
1383 // Check if the logical position is the first or the last one of the text.
1384 const bool isFirstPosition = 0u == logical;
1385 const bool isLastPosition = mLogicalModel->mText.Count() == logical;
1387 if( isFirstPosition && isLastPosition )
1389 // There is zero characters. Get the default font's line height.
1390 cursorInfo.lineHeight = GetDefaultFontLineHeight();
1391 cursorInfo.primaryCursorHeight = cursorInfo.lineHeight;
1393 cursorInfo.primaryPosition.x = 1.f;
1394 cursorInfo.primaryPosition.y = 0.f;
1396 // Nothing else to do.
1400 // 'logical' is the logical 'cursor' index.
1401 // Get the next and current logical 'character' index.
1402 const CharacterIndex nextCharacterIndex = logical;
1403 const CharacterIndex characterIndex = isFirstPosition ? logical : logical - 1u;
1405 // Get the direction of the character and the next one.
1406 const CharacterDirection* const modelCharacterDirectionsBuffer = ( 0u != mLogicalModel->mCharacterDirections.Count() ) ? mLogicalModel->mCharacterDirections.Begin() : NULL;
1408 CharacterDirection isCurrentRightToLeft = false;
1409 CharacterDirection isNextRightToLeft = false;
1410 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
1412 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + characterIndex );
1413 isNextRightToLeft = *( modelCharacterDirectionsBuffer + nextCharacterIndex );
1416 // Get the line where the character is laid-out.
1417 const LineRun* modelLines = mVisualModel->mLines.Begin();
1419 const LineIndex lineIndex = mVisualModel->GetLineOfCharacter( characterIndex );
1420 const LineRun& line = *( modelLines + lineIndex );
1422 // Get the paragraph's direction.
1423 const CharacterDirection isRightToLeftParagraph = line.direction;
1425 // Check whether there is an alternative position:
1427 cursorInfo.isSecondaryCursor = ( !isLastPosition && ( isCurrentRightToLeft != isNextRightToLeft ) ) ||
1428 ( isLastPosition && ( isRightToLeftParagraph != isCurrentRightToLeft ) );
1430 // Set the line height.
1431 cursorInfo.lineHeight = line.ascender + -line.descender;
1433 // Calculate the primary cursor.
1435 CharacterIndex index = characterIndex;
1436 if( cursorInfo.isSecondaryCursor )
1438 // If there is a secondary position, the primary cursor may be in a different place than the logical index.
1440 if( isLastPosition )
1442 // The position of the cursor after the last character needs special
1443 // care depending on its direction and the direction of the paragraph.
1445 // Need to find the first character after the last character with the paragraph's direction.
1446 // i.e l0 l1 l2 r0 r1 should find r0.
1448 // TODO: check for more than one line!
1449 index = isRightToLeftParagraph ? line.characterRun.characterIndex : line.characterRun.characterIndex + line.characterRun.numberOfCharacters - 1u;
1450 index = mLogicalModel->GetLogicalCharacterIndex( index );
1454 index = ( isRightToLeftParagraph == isCurrentRightToLeft ) ? characterIndex : nextCharacterIndex;
1458 // Convert the cursor position into the glyph position.
1459 const GlyphIndex primaryGlyphIndex = *( mVisualModel->mCharactersToGlyph.Begin() + index );
1460 const Length primaryNumberOfGlyphs = *( mVisualModel->mGlyphsPerCharacter.Begin() + index );
1461 const Length primaryNumberOfCharacters = *( mVisualModel->mCharactersPerGlyph.Begin() + primaryGlyphIndex );
1463 // Get the metrics for the group of glyphs.
1464 GlyphMetrics glyphMetrics;
1465 GetGlyphsMetrics( primaryGlyphIndex,
1466 primaryNumberOfGlyphs,
1471 float glyphAdvance = 0.f;
1472 if( !isLastPosition &&
1473 ( primaryNumberOfCharacters > 1u ) )
1475 const CharacterIndex firstIndex = *( mVisualModel->mGlyphsToCharacters.Begin() + primaryGlyphIndex );
1476 glyphAdvance = static_cast<float>( 1u + characterIndex - firstIndex ) * glyphMetrics.advance / static_cast<float>( primaryNumberOfCharacters );
1480 glyphAdvance = glyphMetrics.advance;
1483 // Get the glyph position and x bearing.
1484 const Vector2& primaryPosition = *( mVisualModel->mGlyphPositions.Begin() + primaryGlyphIndex );
1486 // Set the primary cursor's height.
1487 cursorInfo.primaryCursorHeight = cursorInfo.isSecondaryCursor ? 0.5f * glyphMetrics.fontHeight : glyphMetrics.fontHeight;
1489 // Set the primary cursor's position.
1490 if( isLastPosition )
1492 cursorInfo.primaryPosition.x = -glyphMetrics.xBearing + primaryPosition.x + ( isRightToLeftParagraph ? 0.f : glyphMetrics.advance );
1496 cursorInfo.primaryPosition.x = -glyphMetrics.xBearing + primaryPosition.x + ( ( ( isFirstPosition && !isCurrentRightToLeft ) || ( !isFirstPosition && isCurrentRightToLeft ) ) ? 0.f : glyphAdvance );
1498 cursorInfo.primaryPosition.y = line.ascender - glyphMetrics.ascender;
1500 // Calculate the secondary cursor.
1502 if( cursorInfo.isSecondaryCursor )
1504 // Set the secondary cursor's height.
1505 cursorInfo.secondaryCursorHeight = 0.5f * glyphMetrics.fontHeight;
1507 CharacterIndex index = characterIndex;
1508 if( !isLastPosition )
1510 index = ( isRightToLeftParagraph == isCurrentRightToLeft ) ? nextCharacterIndex : characterIndex;
1513 const GlyphIndex secondaryGlyphIndex = *( mVisualModel->mCharactersToGlyph.Begin() + index );
1514 const Length secondaryNumberOfGlyphs = *( mVisualModel->mGlyphsPerCharacter.Begin() + index );
1516 const Vector2& secondaryPosition = *( mVisualModel->mGlyphPositions.Begin() + index );
1518 GetGlyphsMetrics( secondaryGlyphIndex,
1519 secondaryNumberOfGlyphs,
1524 // Set the secondary cursor's position.
1525 cursorInfo.secondaryPosition.x = -glyphMetrics.xBearing + secondaryPosition.x + ( isCurrentRightToLeft ? 0.f : glyphMetrics.advance );
1526 cursorInfo.secondaryPosition.y = cursorInfo.lineHeight - cursorInfo.secondaryCursorHeight - line.descender - ( glyphMetrics.fontHeight - glyphMetrics.ascender );
1530 CharacterIndex Controller::Impl::CalculateNewCursorIndex( CharacterIndex index ) const
1532 if( NULL == mEventData )
1534 // Nothing to do if there is no text input.
1538 CharacterIndex cursorIndex = mEventData->mPrimaryCursorPosition;
1540 const Script script = mLogicalModel->GetScript( index );
1541 const GlyphIndex* charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
1542 const Length* charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
1544 Length numberOfCharacters = 0u;
1545 if( TextAbstraction::LATIN == script )
1547 // Prevents to jump the whole Latin ligatures like fi, ff, ...
1548 numberOfCharacters = 1u;
1552 GlyphIndex glyphIndex = *( charactersToGlyphBuffer + index );
1553 numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
1555 while( 0u == numberOfCharacters )
1557 numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
1562 if( index < mEventData->mPrimaryCursorPosition )
1564 cursorIndex -= numberOfCharacters;
1568 cursorIndex += numberOfCharacters;
1574 void Controller::Impl::UpdateCursorPosition()
1576 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "-->Controller::UpdateCursorPosition %p\n", this );
1577 if( NULL == mEventData )
1579 // Nothing to do if there is no text input.
1580 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition no event data\n" );
1584 if( IsShowingPlaceholderText() )
1586 // Do not want to use the place-holder text to set the cursor position.
1588 // Use the line's height of the font's family set to set the cursor's size.
1589 // If there is no font's family set, use the default font.
1590 // Use the current alignment to place the cursor at the beginning, center or end of the box.
1592 float lineHeight = 0.f;
1594 FontId defaultFontId = 0u;
1595 if( NULL == mFontDefaults )
1597 defaultFontId = mFontClient.GetFontId( EMPTY_STRING,
1602 defaultFontId = mFontDefaults->GetFontId( mFontClient );
1605 Text::FontMetrics fontMetrics;
1606 mFontClient.GetFontMetrics( defaultFontId, fontMetrics );
1608 lineHeight = fontMetrics.ascender - fontMetrics.descender;
1611 Vector2 cursorPosition;
1613 switch( mLayoutEngine.GetHorizontalAlignment() )
1615 case LayoutEngine::HORIZONTAL_ALIGN_BEGIN:
1617 cursorPosition.x = 1.f;
1620 case LayoutEngine::HORIZONTAL_ALIGN_CENTER:
1622 cursorPosition.x = floor( 0.5f * mControlSize.width );
1625 case LayoutEngine::HORIZONTAL_ALIGN_END:
1627 cursorPosition.x = mControlSize.width;
1632 switch( mLayoutEngine.GetVerticalAlignment() )
1634 case LayoutEngine::VERTICAL_ALIGN_TOP:
1636 cursorPosition.y = 0.f;
1639 case LayoutEngine::VERTICAL_ALIGN_CENTER:
1641 cursorPosition.y = floorf( 0.5f * ( mControlSize.height - lineHeight ) );
1644 case LayoutEngine::VERTICAL_ALIGN_BOTTOM:
1646 cursorPosition.y = mControlSize.height - lineHeight;
1651 mEventData->mDecorator->SetPosition( PRIMARY_CURSOR,
1659 CursorInfo cursorInfo;
1660 GetCursorPosition( mEventData->mPrimaryCursorPosition,
1663 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
1664 const Vector2 cursorPosition = cursorInfo.primaryPosition + offset;
1666 // Sets the cursor position.
1667 mEventData->mDecorator->SetPosition( PRIMARY_CURSOR,
1670 cursorInfo.primaryCursorHeight,
1671 cursorInfo.lineHeight );
1672 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Primary cursor position: %f,%f\n", cursorPosition.x, cursorPosition.y );
1674 // Sets the grab handle position.
1675 mEventData->mDecorator->SetPosition( GRAB_HANDLE,
1678 cursorInfo.lineHeight );
1680 if( cursorInfo.isSecondaryCursor )
1682 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_BOTH );
1683 mEventData->mDecorator->SetPosition( SECONDARY_CURSOR,
1684 cursorInfo.secondaryPosition.x + offset.x,
1685 cursorInfo.secondaryPosition.y + offset.y,
1686 cursorInfo.secondaryCursorHeight,
1687 cursorInfo.lineHeight );
1688 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Secondary cursor position: %f,%f\n", cursorInfo.secondaryPosition.x + offset.x, cursorInfo.secondaryPosition.y + offset.y );
1692 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1695 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition\n" );
1698 void Controller::Impl::UpdateSelectionHandle( HandleType handleType )
1700 if( ( LEFT_SELECTION_HANDLE != handleType ) &&
1701 ( RIGHT_SELECTION_HANDLE != handleType ) )
1706 const bool leftSelectionHandle = LEFT_SELECTION_HANDLE == handleType;
1707 const CharacterIndex index = leftSelectionHandle ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition;
1709 CursorInfo cursorInfo;
1710 GetCursorPosition( index,
1713 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
1714 const Vector2 cursorPosition = cursorInfo.primaryPosition + offset;
1716 // Sets the grab handle position.
1717 mEventData->mDecorator->SetPosition( handleType,
1720 cursorInfo.lineHeight );
1722 // If selection handle at start of the text and other at end of the text then all text is selected.
1723 const CharacterIndex startOfSelection = std::min( mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition );
1724 const CharacterIndex endOfSelection = std::max ( mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition );
1725 mEventData->mAllTextSelected = ( startOfSelection == 0 ) && ( endOfSelection == mLogicalModel->mText.Count() );
1728 void Controller::Impl::ClampHorizontalScroll( const Vector2& actualSize )
1730 // Clamp between -space & 0 (and the text alignment).
1731 if( actualSize.width > mControlSize.width )
1733 const float space = ( actualSize.width - mControlSize.width ) + mAlignmentOffset.x;
1734 mEventData->mScrollPosition.x = ( mEventData->mScrollPosition.x < -space ) ? -space : mEventData->mScrollPosition.x;
1735 mEventData->mScrollPosition.x = ( mEventData->mScrollPosition.x > -mAlignmentOffset.x ) ? -mAlignmentOffset.x : mEventData->mScrollPosition.x;
1737 mEventData->mDecoratorUpdated = true;
1741 mEventData->mScrollPosition.x = 0.f;
1745 void Controller::Impl::ClampVerticalScroll( const Vector2& actualSize )
1747 // Clamp between -space & 0 (and the text alignment).
1748 if( actualSize.height > mControlSize.height )
1750 const float space = ( actualSize.height - mControlSize.height ) + mAlignmentOffset.y;
1751 mEventData->mScrollPosition.y = ( mEventData->mScrollPosition.y < -space ) ? -space : mEventData->mScrollPosition.y;
1752 mEventData->mScrollPosition.y = ( mEventData->mScrollPosition.y > -mAlignmentOffset.y ) ? -mAlignmentOffset.y : mEventData->mScrollPosition.y;
1754 mEventData->mDecoratorUpdated = true;
1758 mEventData->mScrollPosition.y = 0.f;
1762 void Controller::Impl::ScrollToMakePositionVisible( const Vector2& position )
1765 bool updateDecorator = false;
1766 if( position.x < 0.f )
1768 offset.x = -position.x;
1769 mEventData->mScrollPosition.x += offset.x;
1770 updateDecorator = true;
1772 else if( position.x > mControlSize.width )
1774 offset.x = mControlSize.width - position.x;
1775 mEventData->mScrollPosition.x += offset.x;
1776 updateDecorator = true;
1779 if( updateDecorator && mEventData->mDecorator )
1781 mEventData->mDecorator->UpdatePositions( offset );
1784 // TODO : calculate the vertical scroll.
1787 void Controller::Impl::ScrollTextToMatchCursor()
1789 // Get the current cursor position in decorator coords.
1790 const Vector2& currentCursorPosition = mEventData->mDecorator->GetPosition( PRIMARY_CURSOR );
1792 // Calculate the new cursor position.
1793 CursorInfo cursorInfo;
1794 GetCursorPosition( mEventData->mPrimaryCursorPosition,
1797 // Calculate the offset to match the cursor position before the character was deleted.
1798 mEventData->mScrollPosition.x = currentCursorPosition.x - cursorInfo.primaryPosition.x - mAlignmentOffset.x;
1800 ClampHorizontalScroll( mVisualModel->GetActualSize() );
1802 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
1803 const Vector2 cursorPosition = cursorInfo.primaryPosition + offset;
1805 // Sets the cursor position.
1806 mEventData->mDecorator->SetPosition( PRIMARY_CURSOR,
1809 cursorInfo.primaryCursorHeight,
1810 cursorInfo.lineHeight );
1812 // Sets the grab handle position.
1813 mEventData->mDecorator->SetPosition( GRAB_HANDLE,
1816 cursorInfo.lineHeight );
1818 if( cursorInfo.isSecondaryCursor )
1820 mEventData->mDecorator->SetPosition( SECONDARY_CURSOR,
1821 cursorInfo.secondaryPosition.x + offset.x,
1822 cursorInfo.secondaryPosition.y + offset.y,
1823 cursorInfo.secondaryCursorHeight,
1824 cursorInfo.lineHeight );
1825 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Secondary cursor position: %f,%f\n", cursorInfo.secondaryPosition.x + offset.x, cursorInfo.secondaryPosition.y + offset.y );
1828 // Set which cursors are active according the state.
1829 if( ( EventData::EDITING == mEventData->mState ) ||
1830 ( EventData::EDITING_WITH_POPUP == mEventData->mState ) ||
1831 ( EventData::GRAB_HANDLE_PANNING == mEventData->mState ) )
1833 if( cursorInfo.isSecondaryCursor )
1835 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_BOTH );
1839 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1844 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
1848 void Controller::Impl::RequestRelayout()
1850 mControlInterface.RequestTextRelayout();
1855 } // namespace Toolkit