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( 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->mLeftSelectionPosition != handlePosition ) && ( 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->mRightSelectionPosition != handlePosition ) && ( mEventData->mLeftSelectionPosition != handlePosition );
685 mEventData->mScrollAfterUpdatePosition = mEventData->mUpdateRightSelectionPosition;
687 if( mEventData->mUpdateRightSelectionPosition )
689 mEventData->mRightSelectionPosition = handlePosition;
690 RepositionSelectionHandles( mEventData->mLeftSelectionPosition,
691 mEventData->mRightSelectionPosition );
696 mEventData->mDecoratorUpdated = true;
697 } // end ( ( HANDLE_RELEASED == state ) || ( HANDLE_STOP_SCROLLING == state ) )
698 else if( HANDLE_SCROLLING == state )
700 const float xSpeed = event.p2.mFloat;
701 const Vector2& actualSize = mVisualModel->GetActualSize();
702 const Vector2 currentScrollPosition = mEventData->mScrollPosition;
704 mEventData->mScrollPosition.x += xSpeed;
706 ClampHorizontalScroll( actualSize );
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();
716 const bool scrollRightDirection = xSpeed > 0.f;
717 const bool leftSelectionHandleEvent = Event::LEFT_SELECTION_HANDLE_EVENT == event.type;
718 const bool rightSelectionHandleEvent = Event::RIGHT_SELECTION_HANDLE_EVENT == event.type;
720 if( Event::GRAB_HANDLE_EVENT == event.type )
722 ChangeState( EventData::GRAB_HANDLE_PANNING );
724 Vector2 position = mEventData->mDecorator->GetPosition( GRAB_HANDLE );
726 // Position the grag handle close to either the left or right edge.
727 position.x = scrollRightDirection ? 0.f : mControlSize.width;
729 // Get the new handle position.
730 // The grab handle's position is in decorator coords. Need to transforms to text coords.
731 const CharacterIndex handlePosition = GetClosestCursorIndex( position.x - mEventData->mScrollPosition.x - mAlignmentOffset.x,
732 position.y - mEventData->mScrollPosition.y - mAlignmentOffset.y );
734 mEventData->mUpdateCursorPosition = mEventData->mPrimaryCursorPosition != handlePosition;
735 mEventData->mScrollAfterUpdatePosition = mEventData->mUpdateCursorPosition;
736 mEventData->mPrimaryCursorPosition = handlePosition;
738 else if( leftSelectionHandleEvent || rightSelectionHandleEvent )
740 // TODO: This is recalculating the selection box every time the text is scrolled with the selection handles.
741 // Think if something can be done to save power.
743 ChangeState( EventData::SELECTION_HANDLE_PANNING );
745 Vector2 position = mEventData->mDecorator->GetPosition( leftSelectionHandleEvent ? Text::LEFT_SELECTION_HANDLE : Text::RIGHT_SELECTION_HANDLE );
747 // Position the selection handle close to either the left or right edge.
748 position.x = scrollRightDirection ? 0.f : mControlSize.width;
750 // Get the new handle position.
751 // The selection handle's position is in decorator coords. Need to transforms to text coords.
752 const CharacterIndex handlePosition = GetClosestCursorIndex( position.x - mEventData->mScrollPosition.x - mAlignmentOffset.x,
753 position.y - mEventData->mScrollPosition.y - mAlignmentOffset.y );
755 if( leftSelectionHandleEvent )
757 mEventData->mUpdateLeftSelectionPosition = handlePosition != mEventData->mLeftSelectionPosition;
758 mEventData->mLeftSelectionPosition = handlePosition;
762 mEventData->mUpdateRightSelectionPosition = handlePosition != mEventData->mRightSelectionPosition;
763 mEventData->mRightSelectionPosition = handlePosition;
766 if( mEventData->mUpdateLeftSelectionPosition || mEventData->mUpdateRightSelectionPosition )
768 RepositionSelectionHandles( mEventData->mLeftSelectionPosition,
769 mEventData->mRightSelectionPosition );
771 mEventData->mScrollAfterUpdatePosition = true;
774 mEventData->mDecoratorUpdated = true;
776 } // end ( HANDLE_SCROLLING == state )
779 void Controller::Impl::OnSelectEvent( const Event& event )
781 if( NULL == mEventData )
783 // Nothing to do if there is no text.
787 if( mEventData->mSelectionEnabled )
789 // The event.p2 and event.p3 are in decorator coords. Need to transforms to text coords.
790 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
791 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
793 const CharacterIndex leftPosition = mEventData->mLeftSelectionPosition;
794 const CharacterIndex rightPosition = mEventData->mRightSelectionPosition;
796 RepositionSelectionHandles( xPosition,
799 mEventData->mUpdateLeftSelectionPosition = leftPosition != mEventData->mLeftSelectionPosition;
800 mEventData->mUpdateRightSelectionPosition = rightPosition != mEventData->mRightSelectionPosition;
802 mEventData->mScrollAfterUpdatePosition = ( ( mEventData->mUpdateLeftSelectionPosition || mEventData->mUpdateRightSelectionPosition ) &&
803 ( mEventData->mLeftSelectionPosition != mEventData->mRightSelectionPosition ) );
807 void Controller::Impl::OnSelectAllEvent()
809 if( NULL == mEventData )
811 // Nothing to do if there is no text.
815 if( mEventData->mSelectionEnabled )
817 RepositionSelectionHandles( 0u,
818 mLogicalModel->mText.Count() );
820 mEventData->mScrollAfterUpdatePosition = true;
821 mEventData->mUpdateLeftSelectionPosition = true;
822 mEventData->mUpdateRightSelectionPosition = true;
826 void Controller::Impl::RetrieveSelection( std::string& selectedText, bool deleteAfterRetreival )
828 if( mEventData->mLeftSelectionPosition == mEventData->mRightSelectionPosition )
830 // Nothing to select if handles are in the same place.
835 //Get start and end position of selection
836 uint32_t startOfSelectedText = mEventData->mLeftSelectionPosition;
837 uint32_t lengthOfSelectedText = mEventData->mRightSelectionPosition - startOfSelectedText;
839 // Validate the start and end selection points
840 if( ( startOfSelectedText >= 0 ) && ( ( startOfSelectedText + lengthOfSelectedText ) <= mLogicalModel->mText.Count() ) )
842 //Get text as a UTF8 string
843 Vector<Character>& utf32Characters = mLogicalModel->mText;
845 Utf32ToUtf8( &utf32Characters[startOfSelectedText], lengthOfSelectedText, selectedText );
847 if ( deleteAfterRetreival ) // Only delete text if copied successfully
849 // Delete text between handles
850 Vector<Character>& currentText = mLogicalModel->mText;
852 Vector<Character>::Iterator first = currentText.Begin() + startOfSelectedText;
853 Vector<Character>::Iterator last = first + lengthOfSelectedText;
854 currentText.Erase( first, last );
856 mEventData->mPrimaryCursorPosition = mEventData->mLeftSelectionPosition;
857 mEventData->mScrollAfterDelete = true;
858 mEventData->mDecoratorUpdated = true;
862 void Controller::Impl::ShowClipboard()
866 mClipboard.ShowClipboard();
870 void Controller::Impl::HideClipboard()
874 mClipboard.HideClipboard();
878 bool Controller::Impl::CopyStringToClipboard( std::string& source )
880 //Send string to clipboard
881 return ( mClipboard && mClipboard.SetItem( source ) );
884 void Controller::Impl::SendSelectionToClipboard( bool deleteAfterSending )
886 std::string selectedText;
887 RetrieveSelection( selectedText, deleteAfterSending );
888 CopyStringToClipboard( selectedText );
889 ChangeState( EventData::EDITING );
892 void Controller::Impl::GetTextFromClipboard( unsigned int itemIndex, std::string& retreivedString )
896 retreivedString = mClipboard.GetItem( itemIndex );
900 void Controller::Impl::RepositionSelectionHandles( CharacterIndex selectionStart, CharacterIndex selectionEnd )
902 if( selectionStart == selectionEnd )
904 // Nothing to select if handles are in the same place.
908 mEventData->mDecorator->ClearHighlights();
910 mEventData->mLeftSelectionPosition = selectionStart;
911 mEventData->mRightSelectionPosition = selectionEnd;
913 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
914 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
915 const GlyphInfo* const glyphsBuffer = mVisualModel->mGlyphs.Begin();
916 const Vector2* const positionsBuffer = mVisualModel->mGlyphPositions.Begin();
918 // TODO: Better algorithm to create the highlight box.
921 const Vector<LineRun>& lines = mVisualModel->mLines;
922 const LineRun& firstLine = *lines.Begin();
923 const float height = firstLine.ascender + -firstLine.descender;
925 const bool indicesSwapped = ( selectionStart > selectionEnd );
928 std::swap( selectionStart, selectionEnd );
931 GlyphIndex glyphStart = *( charactersToGlyphBuffer + selectionStart );
932 GlyphIndex glyphEnd = *( charactersToGlyphBuffer + ( selectionEnd - 1u ) ) + *( glyphsPerCharacterBuffer + ( selectionEnd - 1u ) ) - 1u;
934 mEventData->mDecorator->SwapSelectionHandlesEnabled( firstLine.direction != indicesSwapped );
936 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
938 for( GlyphIndex index = glyphStart; index <= glyphEnd; ++index )
940 const GlyphInfo& glyph = *( glyphsBuffer + index );
941 const Vector2& position = *( positionsBuffer + index );
943 const float xPosition = position.x - glyph.xBearing + offset.x;
944 mEventData->mDecorator->AddHighlight( xPosition, offset.y, xPosition + glyph.advance, height );
947 CursorInfo primaryCursorInfo;
948 GetCursorPosition( mEventData->mLeftSelectionPosition,
951 CursorInfo secondaryCursorInfo;
952 GetCursorPosition( mEventData->mRightSelectionPosition,
953 secondaryCursorInfo );
955 const Vector2 primaryPosition = primaryCursorInfo.primaryPosition + offset;
956 const Vector2 secondaryPosition = secondaryCursorInfo.primaryPosition + offset;
958 mEventData->mDecorator->SetPosition( LEFT_SELECTION_HANDLE, primaryPosition.x, primaryPosition.y, primaryCursorInfo.lineHeight );
960 mEventData->mDecorator->SetPosition( RIGHT_SELECTION_HANDLE, secondaryPosition.x, secondaryPosition.y, secondaryCursorInfo.lineHeight );
962 // Set the flag to update the decorator.
963 mEventData->mDecoratorUpdated = true;
966 void Controller::Impl::RepositionSelectionHandles( float visualX, float visualY )
968 if( NULL == mEventData )
970 // Nothing to do if there is no text input.
974 if( IsShowingPlaceholderText() )
976 // Nothing to do if there is the place-holder text.
980 const Length numberOfGlyphs = mVisualModel->mGlyphs.Count();
981 const Length numberOfLines = mVisualModel->mLines.Count();
982 if( 0 == numberOfGlyphs ||
985 // Nothing to do if there is no text.
989 // Find which word was selected
990 CharacterIndex selectionStart( 0 );
991 CharacterIndex selectionEnd( 0 );
992 FindSelectionIndices( visualX, visualY, selectionStart, selectionEnd );
993 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "%p selectionStart %d selectionEnd %d\n", this, selectionStart, selectionEnd );
995 if( selectionStart == selectionEnd )
997 ChangeState( EventData::EDITING );
998 // Nothing to select. i.e. a white space, out of bounds
1002 RepositionSelectionHandles( selectionStart, selectionEnd );
1005 void Controller::Impl::SetPopupButtons()
1008 * Sets the Popup buttons to be shown depending on State.
1010 * If SELECTING : CUT & COPY + ( PASTE & CLIPBOARD if content available to paste )
1012 * If EDITING_WITH_POPUP : SELECT & SELECT_ALL
1015 TextSelectionPopup::Buttons buttonsToShow = TextSelectionPopup::NONE;
1017 if ( ( EventData::SELECTING == mEventData->mState ) || ( EventData::SELECTION_CHANGED == mEventData->mState ) )
1019 buttonsToShow = TextSelectionPopup::Buttons( TextSelectionPopup::CUT | TextSelectionPopup::COPY );
1021 if ( !IsClipboardEmpty() )
1023 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
1024 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::CLIPBOARD ) );
1027 if ( !mEventData->mAllTextSelected )
1029 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::SELECT_ALL ) );
1032 else if ( EventData::EDITING_WITH_POPUP == mEventData->mState )
1034 if ( mLogicalModel->mText.Count() && !IsShowingPlaceholderText())
1036 buttonsToShow = TextSelectionPopup::Buttons( TextSelectionPopup::SELECT | TextSelectionPopup::SELECT_ALL );
1039 if ( !IsClipboardEmpty() )
1041 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
1042 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::CLIPBOARD ) );
1046 mEventData->mDecorator->SetEnabledPopupButtons( buttonsToShow );
1049 void Controller::Impl::ChangeState( EventData::State newState )
1051 if( NULL == mEventData )
1053 // Nothing to do if there is no text input.
1057 if( mEventData->mState != newState )
1059 mEventData->mState = newState;
1061 if( EventData::INACTIVE == mEventData->mState )
1063 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
1064 mEventData->mDecorator->StopCursorBlink();
1065 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1066 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1067 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1068 mEventData->mDecorator->SetPopupActive( false );
1069 mEventData->mDecoratorUpdated = true;
1072 else if ( EventData::INTERRUPTED == mEventData->mState)
1074 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1075 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1076 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1077 mEventData->mDecorator->SetPopupActive( false );
1078 mEventData->mDecoratorUpdated = true;
1081 else if ( EventData::SELECTING == mEventData->mState )
1083 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
1084 mEventData->mDecorator->StopCursorBlink();
1085 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1086 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true );
1087 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true );
1088 if( mEventData->mGrabHandlePopupEnabled )
1091 mEventData->mDecorator->SetPopupActive( true );
1093 mEventData->mDecoratorUpdated = true;
1095 else if ( EventData::SELECTION_CHANGED == mEventData->mState )
1097 if( mEventData->mGrabHandlePopupEnabled )
1100 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
1101 mEventData->mDecorator->SetPopupActive( true );
1103 mEventData->mDecoratorUpdated = true;
1105 else if( EventData::EDITING == mEventData->mState )
1107 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1108 if( mEventData->mCursorBlinkEnabled )
1110 mEventData->mDecorator->StartCursorBlink();
1112 // Grab handle is not shown until a tap is received whilst EDITING
1113 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1114 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1115 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1116 if( mEventData->mGrabHandlePopupEnabled )
1118 mEventData->mDecorator->SetPopupActive( false );
1120 mEventData->mDecoratorUpdated = true;
1123 else if( EventData::EDITING_WITH_POPUP == mEventData->mState )
1125 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1126 if( mEventData->mCursorBlinkEnabled )
1128 mEventData->mDecorator->StartCursorBlink();
1130 if( mEventData->mSelectionEnabled )
1132 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1133 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1137 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
1139 if( mEventData->mGrabHandlePopupEnabled )
1142 mEventData->mDecorator->SetPopupActive( true );
1145 mEventData->mDecoratorUpdated = true;
1147 else if( EventData::EDITING_WITH_GRAB_HANDLE == mEventData->mState )
1149 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1150 if( mEventData->mCursorBlinkEnabled )
1152 mEventData->mDecorator->StartCursorBlink();
1154 // Grab handle is not shown until a tap is received whilst EDITING
1155 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
1156 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1157 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1158 if( mEventData->mGrabHandlePopupEnabled )
1160 mEventData->mDecorator->SetPopupActive( false );
1162 mEventData->mDecoratorUpdated = true;
1165 else if ( EventData::SELECTION_HANDLE_PANNING == mEventData->mState )
1167 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
1168 mEventData->mDecorator->StopCursorBlink();
1169 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1170 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true );
1171 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true );
1172 if( mEventData->mGrabHandlePopupEnabled )
1174 mEventData->mDecorator->SetPopupActive( false );
1176 mEventData->mDecoratorUpdated = true;
1178 else if ( EventData::GRAB_HANDLE_PANNING == mEventData->mState )
1180 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1181 if( mEventData->mCursorBlinkEnabled )
1183 mEventData->mDecorator->StartCursorBlink();
1185 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
1186 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1187 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1188 if( mEventData->mGrabHandlePopupEnabled )
1190 mEventData->mDecorator->SetPopupActive( false );
1192 mEventData->mDecoratorUpdated = true;
1197 LineIndex Controller::Impl::GetClosestLine( float y ) const
1199 float totalHeight = 0.f;
1200 LineIndex lineIndex = 0u;
1202 const Vector<LineRun>& lines = mVisualModel->mLines;
1203 for( LineIndex endLine = lines.Count();
1204 lineIndex < endLine;
1207 const LineRun& lineRun = lines[lineIndex];
1208 totalHeight += lineRun.ascender + -lineRun.descender;
1209 if( y < totalHeight )
1215 if( lineIndex == 0 )
1223 void Controller::Impl::FindSelectionIndices( float visualX, float visualY, CharacterIndex& startIndex, CharacterIndex& endIndex )
1225 CharacterIndex hitCharacter = GetClosestCursorIndex( visualX, visualY );
1226 if( hitCharacter >= mLogicalModel->mText.Count() )
1228 // Selection out of bounds.
1232 startIndex = hitCharacter;
1233 endIndex = hitCharacter;
1235 if( !TextAbstraction::IsWhiteSpace( mLogicalModel->mText[hitCharacter] ) )
1237 // Find the start and end of the text
1238 for( startIndex = hitCharacter; startIndex > 0; --startIndex )
1240 Character charCode = mLogicalModel->mText[ startIndex-1 ];
1241 if( TextAbstraction::IsWhiteSpace( charCode ) )
1246 const CharacterIndex pastTheEnd = mLogicalModel->mText.Count();
1247 for( endIndex = hitCharacter + 1u; endIndex < pastTheEnd; ++endIndex )
1249 Character charCode = mLogicalModel->mText[ endIndex ];
1250 if( TextAbstraction::IsWhiteSpace( charCode ) )
1258 CharacterIndex Controller::Impl::GetClosestCursorIndex( float visualX,
1261 if( NULL == mEventData )
1263 // Nothing to do if there is no text input.
1267 CharacterIndex logicalIndex = 0u;
1269 const Length numberOfGlyphs = mVisualModel->mGlyphs.Count();
1270 const Length numberOfLines = mVisualModel->mLines.Count();
1271 if( 0 == numberOfGlyphs ||
1272 0 == numberOfLines )
1274 return logicalIndex;
1277 // Find which line is closest
1278 const LineIndex lineIndex = GetClosestLine( visualY );
1279 const LineRun& line = mVisualModel->mLines[lineIndex];
1281 // Get the positions of the glyphs.
1282 const Vector<Vector2>& positions = mVisualModel->mGlyphPositions;
1283 const Vector2* const positionsBuffer = positions.Begin();
1285 // Get the visual to logical conversion tables.
1286 const CharacterIndex* const visualToLogicalBuffer = ( 0u != mLogicalModel->mVisualToLogicalMap.Count() ) ? mLogicalModel->mVisualToLogicalMap.Begin() : NULL;
1287 const CharacterIndex* const visualToLogicalCursorBuffer = mLogicalModel->mVisualToLogicalCursorMap.Begin();
1289 // Get the character to glyph conversion table.
1290 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
1292 // Get the glyphs per character table.
1293 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
1294 const Length* const charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
1296 // If the vector is void, there is no right to left characters.
1297 const bool hasRightToLeftCharacters = NULL != visualToLogicalBuffer;
1299 const CharacterIndex startCharacter = line.characterRun.characterIndex;
1300 const CharacterIndex endCharacter = line.characterRun.characterIndex + line.characterRun.numberOfCharacters;
1301 DALI_ASSERT_DEBUG( endCharacter <= mLogicalModel->mText.Count() && "Invalid line info" );
1303 // Whether there is a hit on a glyph.
1304 bool matched = false;
1306 // Traverses glyphs in visual order. To do that use the visual to logical conversion table.
1307 CharacterIndex visualIndex = startCharacter;
1308 for( ; !matched && ( visualIndex < endCharacter ); ++visualIndex )
1310 // The character in logical order.
1311 const CharacterIndex characterLogicalOrderIndex = hasRightToLeftCharacters ? *( visualToLogicalBuffer + visualIndex ) : visualIndex;
1313 // Get the script of the character.
1314 const Script script = mLogicalModel->GetScript( characterLogicalOrderIndex );
1316 // The first glyph for that character in logical order.
1317 const GlyphIndex glyphLogicalOrderIndex = *( charactersToGlyphBuffer + characterLogicalOrderIndex );
1318 // The number of glyphs for that character
1319 const Length numberOfGlyphs = *( glyphsPerCharacterBuffer + characterLogicalOrderIndex );
1321 // Get the metrics for the group of glyphs.
1322 GlyphMetrics glyphMetrics;
1323 GetGlyphsMetrics( glyphLogicalOrderIndex,
1329 const Vector2& position = *( positionsBuffer + glyphLogicalOrderIndex );
1331 // Prevents to jump the whole Latin ligatures like fi, ff, ...
1332 const Length numberOfCharactersInLigature = ( TextAbstraction::LATIN == script ) ? *( charactersPerGlyphBuffer + glyphLogicalOrderIndex ) : 1u;
1333 const float glyphAdvance = glyphMetrics.advance / static_cast<float>( numberOfCharactersInLigature );
1335 for( GlyphIndex index = 0u; !matched && ( index < numberOfCharactersInLigature ); ++index )
1337 // Find the mid-point of the area containing the glyph
1338 const float glyphCenter = -glyphMetrics.xBearing + position.x + ( static_cast<float>( index ) + 0.5f ) * glyphAdvance;
1340 if( visualX < glyphCenter )
1342 visualIndex += index;
1354 // Return the logical position of the cursor in characters.
1358 visualIndex = endCharacter;
1361 logicalIndex = hasRightToLeftCharacters ? *( visualToLogicalCursorBuffer + visualIndex ) : visualIndex;
1362 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "%p closest visualIndex %d logicalIndex %d\n", this, visualIndex, logicalIndex );
1364 return logicalIndex;
1367 void Controller::Impl::GetCursorPosition( CharacterIndex logical,
1368 CursorInfo& cursorInfo )
1370 // TODO: Check for multiline with \n, etc...
1372 // Check if the logical position is the first or the last one of the text.
1373 const bool isFirstPosition = 0u == logical;
1374 const bool isLastPosition = mLogicalModel->mText.Count() == logical;
1376 if( isFirstPosition && isLastPosition )
1378 // There is zero characters. Get the default font's line height.
1379 cursorInfo.lineHeight = GetDefaultFontLineHeight();
1380 cursorInfo.primaryCursorHeight = cursorInfo.lineHeight;
1382 cursorInfo.primaryPosition.x = 1.f;
1383 cursorInfo.primaryPosition.y = 0.f;
1385 // Nothing else to do.
1389 // Get the previous logical index.
1390 const CharacterIndex previousLogical = isFirstPosition ? 0u : logical - 1u;
1392 // Decrease the logical index if it's the last one.
1393 if( isLastPosition )
1398 // Get the direction of the character and the previous one.
1399 const CharacterDirection* const modelCharacterDirectionsBuffer = ( 0u != mLogicalModel->mCharacterDirections.Count() ) ? mLogicalModel->mCharacterDirections.Begin() : NULL;
1401 CharacterDirection isCurrentRightToLeft = false;
1402 CharacterDirection isPreviousRightToLeft = false;
1403 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
1405 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + logical );
1406 isPreviousRightToLeft = *( modelCharacterDirectionsBuffer + previousLogical );
1409 // Get the line where the character is laid-out.
1410 const LineRun* modelLines = mVisualModel->mLines.Begin();
1412 const LineIndex lineIndex = mVisualModel->GetLineOfCharacter( logical );
1413 const LineRun& line = *( modelLines + lineIndex );
1415 // Get the paragraph's direction.
1416 const CharacterDirection isRightToLeftParagraph = line.direction;
1418 // Check whether there is an alternative position:
1420 cursorInfo.isSecondaryCursor = ( isCurrentRightToLeft != isPreviousRightToLeft ) ||
1421 ( isLastPosition && ( isRightToLeftParagraph != isCurrentRightToLeft ) );
1423 // Set the line height.
1424 cursorInfo.lineHeight = line.ascender + -line.descender;
1426 // Convert the cursor position into the glyph position.
1427 CharacterIndex characterIndex = logical;
1428 if( cursorInfo.isSecondaryCursor &&
1429 ( isRightToLeftParagraph != isCurrentRightToLeft ) )
1431 characterIndex = previousLogical;
1434 const GlyphIndex currentGlyphIndex = *( mVisualModel->mCharactersToGlyph.Begin() + characterIndex );
1435 const Length numberOfGlyphs = *( mVisualModel->mGlyphsPerCharacter.Begin() + characterIndex );
1436 const Length numberOfCharacters = *( mVisualModel->mCharactersPerGlyph.Begin() +currentGlyphIndex );
1438 // Get the metrics for the group of glyphs.
1439 GlyphMetrics glyphMetrics;
1440 GetGlyphsMetrics( currentGlyphIndex,
1446 float interGlyphAdvance = 0.f;
1447 if( !isLastPosition &&
1448 ( numberOfCharacters > 1u ) )
1450 const CharacterIndex firstIndex = *( mVisualModel->mGlyphsToCharacters.Begin() + currentGlyphIndex );
1451 interGlyphAdvance = static_cast<float>( characterIndex - firstIndex ) * glyphMetrics.advance / static_cast<float>( numberOfCharacters );
1454 // Get the glyph position and x bearing.
1455 const Vector2& currentPosition = *( mVisualModel->mGlyphPositions.Begin() + currentGlyphIndex );
1457 // Set the cursor's height.
1458 cursorInfo.primaryCursorHeight = glyphMetrics.fontHeight;
1460 // Set the position.
1461 cursorInfo.primaryPosition.x = -glyphMetrics.xBearing + currentPosition.x + ( isCurrentRightToLeft ? glyphMetrics.advance : interGlyphAdvance );
1462 cursorInfo.primaryPosition.y = line.ascender - glyphMetrics.ascender;
1464 if( isLastPosition )
1466 // The position of the cursor after the last character needs special
1467 // care depending on its direction and the direction of the paragraph.
1469 if( cursorInfo.isSecondaryCursor )
1471 // Need to find the first character after the last character with the paragraph's direction.
1472 // i.e l0 l1 l2 r0 r1 should find r0.
1474 // TODO: check for more than one line!
1475 characterIndex = isRightToLeftParagraph ? line.characterRun.characterIndex : line.characterRun.characterIndex + line.characterRun.numberOfCharacters - 1u;
1476 characterIndex = mLogicalModel->GetLogicalCharacterIndex( characterIndex );
1478 const GlyphIndex glyphIndex = *( mVisualModel->mCharactersToGlyph.Begin() + characterIndex );
1479 const Length numberOfGlyphs = *( mVisualModel->mGlyphsPerCharacter.Begin() + characterIndex );
1481 const Vector2& position = *( mVisualModel->mGlyphPositions.Begin() + glyphIndex );
1483 // Get the metrics for the group of glyphs.
1484 GlyphMetrics glyphMetrics;
1485 GetGlyphsMetrics( glyphIndex,
1491 cursorInfo.primaryPosition.x = -glyphMetrics.xBearing + position.x + ( isRightToLeftParagraph ? 0.f : glyphMetrics.advance );
1493 cursorInfo.primaryPosition.y = line.ascender - glyphMetrics.ascender;
1497 if( !isCurrentRightToLeft )
1499 cursorInfo.primaryPosition.x += glyphMetrics.advance;
1503 cursorInfo.primaryPosition.x -= glyphMetrics.advance;
1508 // Set the alternative cursor position.
1509 if( cursorInfo.isSecondaryCursor )
1511 // Convert the cursor position into the glyph position.
1512 const CharacterIndex previousCharacterIndex = ( ( isRightToLeftParagraph != isCurrentRightToLeft ) ? logical : previousLogical );
1513 const GlyphIndex previousGlyphIndex = *( mVisualModel->mCharactersToGlyph.Begin() + previousCharacterIndex );
1514 const Length numberOfGlyphs = *( mVisualModel->mGlyphsPerCharacter.Begin() + previousCharacterIndex );
1516 // Get the glyph position.
1517 const Vector2& previousPosition = *( mVisualModel->mGlyphPositions.Begin() + previousGlyphIndex );
1519 // Get the metrics for the group of glyphs.
1520 GlyphMetrics glyphMetrics;
1521 GetGlyphsMetrics( previousGlyphIndex,
1527 // Set the cursor position and height.
1528 cursorInfo.secondaryPosition.x = -glyphMetrics.xBearing + previousPosition.x + ( ( ( isLastPosition && !isCurrentRightToLeft ) ||
1529 ( !isLastPosition && isCurrentRightToLeft ) ) ? glyphMetrics.advance : 0.f );
1531 cursorInfo.secondaryCursorHeight = 0.5f * glyphMetrics.fontHeight;
1533 cursorInfo.secondaryPosition.y = cursorInfo.lineHeight - cursorInfo.secondaryCursorHeight - line.descender - ( glyphMetrics.fontHeight - glyphMetrics.ascender );
1535 // Update the primary cursor height as well.
1536 cursorInfo.primaryCursorHeight *= 0.5f;
1540 CharacterIndex Controller::Impl::CalculateNewCursorIndex( CharacterIndex index ) const
1542 if( NULL == mEventData )
1544 // Nothing to do if there is no text input.
1548 CharacterIndex cursorIndex = mEventData->mPrimaryCursorPosition;
1550 const Script script = mLogicalModel->GetScript( index );
1551 const GlyphIndex* charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
1552 const Length* charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
1554 Length numberOfCharacters = 0u;
1555 if( TextAbstraction::LATIN == script )
1557 // Prevents to jump the whole Latin ligatures like fi, ff, ...
1558 numberOfCharacters = 1u;
1562 GlyphIndex glyphIndex = *( charactersToGlyphBuffer + index );
1563 numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
1565 while( 0u == numberOfCharacters )
1567 numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
1572 if( index < mEventData->mPrimaryCursorPosition )
1574 cursorIndex -= numberOfCharacters;
1578 cursorIndex += numberOfCharacters;
1584 void Controller::Impl::UpdateCursorPosition()
1586 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "-->Controller::UpdateCursorPosition %p\n", this );
1587 if( NULL == mEventData )
1589 // Nothing to do if there is no text input.
1590 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition no event data\n" );
1594 if( IsShowingPlaceholderText() )
1596 // Do not want to use the place-holder text to set the cursor position.
1598 // Use the line's height of the font's family set to set the cursor's size.
1599 // If there is no font's family set, use the default font.
1600 // Use the current alignment to place the cursor at the beginning, center or end of the box.
1602 float lineHeight = 0.f;
1604 FontId defaultFontId = 0u;
1605 if( NULL == mFontDefaults )
1607 defaultFontId = mFontClient.GetFontId( EMPTY_STRING,
1612 defaultFontId = mFontDefaults->GetFontId( mFontClient );
1615 Text::FontMetrics fontMetrics;
1616 mFontClient.GetFontMetrics( defaultFontId, fontMetrics );
1618 lineHeight = fontMetrics.ascender - fontMetrics.descender;
1621 Vector2 cursorPosition;
1623 switch( mLayoutEngine.GetHorizontalAlignment() )
1625 case LayoutEngine::HORIZONTAL_ALIGN_BEGIN:
1627 cursorPosition.x = 1.f;
1630 case LayoutEngine::HORIZONTAL_ALIGN_CENTER:
1632 cursorPosition.x = floor( 0.5f * mControlSize.width );
1635 case LayoutEngine::HORIZONTAL_ALIGN_END:
1637 cursorPosition.x = mControlSize.width;
1642 switch( mLayoutEngine.GetVerticalAlignment() )
1644 case LayoutEngine::VERTICAL_ALIGN_TOP:
1646 cursorPosition.y = 0.f;
1649 case LayoutEngine::VERTICAL_ALIGN_CENTER:
1651 cursorPosition.y = floorf( 0.5f * ( mControlSize.height - lineHeight ) );
1654 case LayoutEngine::VERTICAL_ALIGN_BOTTOM:
1656 cursorPosition.y = mControlSize.height - lineHeight;
1661 mEventData->mDecorator->SetPosition( PRIMARY_CURSOR,
1669 CursorInfo cursorInfo;
1670 GetCursorPosition( mEventData->mPrimaryCursorPosition,
1673 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
1674 const Vector2 cursorPosition = cursorInfo.primaryPosition + offset;
1676 // Sets the cursor position.
1677 mEventData->mDecorator->SetPosition( PRIMARY_CURSOR,
1680 cursorInfo.primaryCursorHeight,
1681 cursorInfo.lineHeight );
1682 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Primary cursor position: %f,%f\n", cursorPosition.x, cursorPosition.y );
1684 // Sets the grab handle position.
1685 mEventData->mDecorator->SetPosition( GRAB_HANDLE,
1688 cursorInfo.lineHeight );
1690 if( cursorInfo.isSecondaryCursor )
1692 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_BOTH );
1693 mEventData->mDecorator->SetPosition( SECONDARY_CURSOR,
1694 cursorInfo.secondaryPosition.x + offset.x,
1695 cursorInfo.secondaryPosition.y + offset.y,
1696 cursorInfo.secondaryCursorHeight,
1697 cursorInfo.lineHeight );
1698 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Secondary cursor position: %f,%f\n", cursorInfo.secondaryPosition.x + offset.x, cursorInfo.secondaryPosition.y + offset.y );
1702 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1705 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition\n" );
1708 void Controller::Impl::UpdateSelectionHandle( HandleType handleType )
1710 if( ( LEFT_SELECTION_HANDLE != handleType ) &&
1711 ( RIGHT_SELECTION_HANDLE != handleType ) )
1716 const bool leftSelectionHandle = LEFT_SELECTION_HANDLE == handleType;
1717 const CharacterIndex index = leftSelectionHandle ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition;
1719 CursorInfo cursorInfo;
1720 GetCursorPosition( index,
1723 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
1724 const Vector2 cursorPosition = cursorInfo.primaryPosition + offset;
1726 // Sets the grab handle position.
1727 mEventData->mDecorator->SetPosition( handleType,
1730 cursorInfo.lineHeight );
1732 // If selection handle at start of the text and other at end of the text then all text is selected.
1733 const CharacterIndex startOfSelection = std::min( mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition );
1734 const CharacterIndex endOfSelection = std::max ( mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition );
1735 mEventData->mAllTextSelected = ( startOfSelection == 0 ) && ( endOfSelection == mLogicalModel->mText.Count() );
1738 void Controller::Impl::ClampHorizontalScroll( const Vector2& actualSize )
1740 // Clamp between -space & 0 (and the text alignment).
1741 if( actualSize.width > mControlSize.width )
1743 const float space = ( actualSize.width - mControlSize.width ) + mAlignmentOffset.x;
1744 mEventData->mScrollPosition.x = ( mEventData->mScrollPosition.x < -space ) ? -space : mEventData->mScrollPosition.x;
1745 mEventData->mScrollPosition.x = ( mEventData->mScrollPosition.x > -mAlignmentOffset.x ) ? -mAlignmentOffset.x : mEventData->mScrollPosition.x;
1747 mEventData->mDecoratorUpdated = true;
1751 mEventData->mScrollPosition.x = 0.f;
1755 void Controller::Impl::ClampVerticalScroll( const Vector2& actualSize )
1757 // Clamp between -space & 0 (and the text alignment).
1758 if( actualSize.height > mControlSize.height )
1760 const float space = ( actualSize.height - mControlSize.height ) + mAlignmentOffset.y;
1761 mEventData->mScrollPosition.y = ( mEventData->mScrollPosition.y < -space ) ? -space : mEventData->mScrollPosition.y;
1762 mEventData->mScrollPosition.y = ( mEventData->mScrollPosition.y > -mAlignmentOffset.y ) ? -mAlignmentOffset.y : mEventData->mScrollPosition.y;
1764 mEventData->mDecoratorUpdated = true;
1768 mEventData->mScrollPosition.y = 0.f;
1772 void Controller::Impl::ScrollToMakePositionVisible( const Vector2& position )
1775 bool updateDecorator = false;
1776 if( position.x < 0.f )
1778 offset.x = -position.x;
1779 mEventData->mScrollPosition.x += offset.x;
1780 updateDecorator = true;
1782 else if( position.x > mControlSize.width )
1784 offset.x = mControlSize.width - position.x;
1785 mEventData->mScrollPosition.x += offset.x;
1786 updateDecorator = true;
1789 if( updateDecorator && mEventData->mDecorator )
1791 mEventData->mDecorator->UpdatePositions( offset );
1794 // TODO : calculate the vertical scroll.
1797 void Controller::Impl::ScrollTextToMatchCursor()
1799 // Get the current cursor position in decorator coords.
1800 const Vector2& currentCursorPosition = mEventData->mDecorator->GetPosition( PRIMARY_CURSOR );
1802 // Calculate the new cursor position.
1803 CursorInfo cursorInfo;
1804 GetCursorPosition( mEventData->mPrimaryCursorPosition,
1807 // Calculate the offset to match the cursor position before the character was deleted.
1808 mEventData->mScrollPosition.x = currentCursorPosition.x - cursorInfo.primaryPosition.x - mAlignmentOffset.x;
1810 ClampHorizontalScroll( mVisualModel->GetActualSize() );
1812 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
1813 const Vector2 cursorPosition = cursorInfo.primaryPosition + offset;
1815 // Sets the cursor position.
1816 mEventData->mDecorator->SetPosition( PRIMARY_CURSOR,
1819 cursorInfo.primaryCursorHeight,
1820 cursorInfo.lineHeight );
1822 // Sets the grab handle position.
1823 mEventData->mDecorator->SetPosition( GRAB_HANDLE,
1826 cursorInfo.lineHeight );
1828 if( cursorInfo.isSecondaryCursor )
1830 mEventData->mDecorator->SetPosition( SECONDARY_CURSOR,
1831 cursorInfo.secondaryPosition.x + offset.x,
1832 cursorInfo.secondaryPosition.y + offset.y,
1833 cursorInfo.secondaryCursorHeight,
1834 cursorInfo.lineHeight );
1835 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Secondary cursor position: %f,%f\n", cursorInfo.secondaryPosition.x + offset.x, cursorInfo.secondaryPosition.y + offset.y );
1838 // Set which cursors are active according the state.
1839 if( ( EventData::EDITING == mEventData->mState ) ||
1840 ( EventData::EDITING_WITH_POPUP == mEventData->mState ) ||
1841 ( EventData::GRAB_HANDLE_PANNING == mEventData->mState ) )
1843 if( cursorInfo.isSecondaryCursor )
1845 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_BOTH );
1849 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1854 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
1858 void Controller::Impl::RequestRelayout()
1860 mControlInterface.RequestTextRelayout();
1865 } // namespace Toolkit