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,
337 if( 0u == validFonts.Count() )
339 // Copy the requested font defaults received via the property system.
340 // These may not be valid i.e. may not contain glyphs for the necessary scripts.
341 GetDefaultFonts( validFonts, numberOfCharacters );
344 // Validates the fonts. If there is a character with no assigned font it sets a default one.
345 // After this call, fonts are validated.
346 multilanguageSupport.ValidateFonts( utf32Characters,
352 Vector<Character> mirroredUtf32Characters;
353 bool textMirrored = false;
354 Length numberOfParagraphs = 0u;
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 const TextAbstraction::LineBreakInfo* lineBreakInfoBuffer = lineBreakInfo.Begin();
361 for( Length index = 0u; index < numberOfCharacters; ++index )
363 if( TextAbstraction::LINE_NO_BREAK == *( lineBreakInfoBuffer + index ) )
365 ++numberOfParagraphs;
369 Vector<BidirectionalParagraphInfoRun>& bidirectionalInfo = mLogicalModel->mBidirectionalParagraphInfo;
370 bidirectionalInfo.Reserve( numberOfParagraphs );
372 // Calculates the bidirectional info for the whole paragraph if it contains right to left scripts.
373 SetBidirectionalInfo( utf32Characters,
378 if( 0u != bidirectionalInfo.Count() )
380 // This paragraph has right to left text. Some characters may need to be mirrored.
381 // TODO: consider if the mirrored string can be stored as well.
383 textMirrored = GetMirroredText( utf32Characters,
384 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();
401 Vector<GlyphInfo>& glyphs = mVisualModel->mGlyphs;
402 Vector<CharacterIndex>& glyphsToCharactersMap = mVisualModel->mGlyphsToCharacters;
403 Vector<Length>& charactersPerGlyph = mVisualModel->mCharactersPerGlyph;
404 Vector<GlyphIndex> newParagraphGlyphs;
405 newParagraphGlyphs.Reserve( numberOfParagraphs );
407 if( SHAPE_TEXT & operations )
409 const Vector<Character>& textToShape = textMirrored ? mirroredUtf32Characters : utf32Characters;
411 ShapeText( textToShape,
416 glyphsToCharactersMap,
418 newParagraphGlyphs );
420 // Create the 'number of glyphs' per character and the glyph to character conversion tables.
421 mVisualModel->CreateGlyphsPerCharacterTable( numberOfCharacters );
422 mVisualModel->CreateCharacterToGlyphTable( numberOfCharacters );
425 const Length numberOfGlyphs = glyphs.Count();
427 if( GET_GLYPH_METRICS & operations )
429 GlyphInfo* glyphsBuffer = glyphs.Begin();
430 mFontClient.GetGlyphMetrics( glyphsBuffer, numberOfGlyphs );
432 // Update the width and advance of all new paragraph characters.
433 for( Vector<GlyphIndex>::ConstIterator it = newParagraphGlyphs.Begin(), endIt = newParagraphGlyphs.End(); it != endIt; ++it )
435 const GlyphIndex index = *it;
436 GlyphInfo& glyph = *( glyphsBuffer + index );
438 glyph.xBearing = 0.f;
445 void Controller::Impl::GetDefaultFonts( Vector<FontRun>& fonts, Length numberOfCharacters )
450 fontRun.characterRun.characterIndex = 0;
451 fontRun.characterRun.numberOfCharacters = numberOfCharacters;
452 fontRun.fontId = mFontDefaults->GetFontId( mFontClient );
453 fontRun.isDefault = true;
455 fonts.PushBack( fontRun );
459 float Controller::Impl::GetDefaultFontLineHeight()
461 FontId defaultFontId = 0u;
462 if( NULL == mFontDefaults )
464 defaultFontId = mFontClient.GetFontId( EMPTY_STRING,
469 defaultFontId = mFontDefaults->GetFontId( mFontClient );
472 Text::FontMetrics fontMetrics;
473 mFontClient.GetFontMetrics( defaultFontId, fontMetrics );
475 return( fontMetrics.ascender - fontMetrics.descender );
478 void Controller::Impl::OnCursorKeyEvent( const Event& event )
480 if( NULL == mEventData )
482 // Nothing to do if there is no text input.
486 int keyCode = event.p1.mInt;
488 if( Dali::DALI_KEY_CURSOR_LEFT == keyCode )
490 if( mEventData->mPrimaryCursorPosition > 0u )
492 mEventData->mPrimaryCursorPosition = CalculateNewCursorIndex( mEventData->mPrimaryCursorPosition - 1u );
495 else if( Dali::DALI_KEY_CURSOR_RIGHT == keyCode )
497 if( mLogicalModel->mText.Count() > mEventData->mPrimaryCursorPosition )
499 mEventData->mPrimaryCursorPosition = CalculateNewCursorIndex( mEventData->mPrimaryCursorPosition );
502 else if( Dali::DALI_KEY_CURSOR_UP == keyCode )
506 else if( Dali::DALI_KEY_CURSOR_DOWN == keyCode )
511 mEventData->mUpdateCursorPosition = true;
512 mEventData->mScrollAfterUpdatePosition = true;
515 void Controller::Impl::OnTapEvent( const Event& event )
517 if( NULL != mEventData )
519 const unsigned int tapCount = event.p1.mUint;
523 if( ! IsShowingPlaceholderText() )
525 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
526 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
528 mEventData->mPrimaryCursorPosition = GetClosestCursorIndex( xPosition,
533 mEventData->mPrimaryCursorPosition = 0u;
536 mEventData->mUpdateCursorPosition = true;
537 mEventData->mScrollAfterUpdatePosition = true;
542 void Controller::Impl::OnPanEvent( const Event& event )
544 if( NULL == mEventData )
546 // Nothing to do if there is no text input.
550 int state = event.p1.mInt;
552 if( Gesture::Started == state ||
553 Gesture::Continuing == state )
555 const Vector2& actualSize = mVisualModel->GetActualSize();
556 const Vector2 currentScroll = mEventData->mScrollPosition;
558 if( mEventData->mHorizontalScrollingEnabled )
560 const float displacementX = event.p2.mFloat;
561 mEventData->mScrollPosition.x += displacementX;
563 ClampHorizontalScroll( actualSize );
566 if( mEventData->mVerticalScrollingEnabled )
568 const float displacementY = event.p3.mFloat;
569 mEventData->mScrollPosition.y += displacementY;
571 ClampVerticalScroll( actualSize );
574 if( mEventData->mDecorator )
576 mEventData->mDecorator->UpdatePositions( mEventData->mScrollPosition - currentScroll );
581 void Controller::Impl::OnLongPressEvent( const Event& event )
583 if ( EventData::EDITING == mEventData->mState )
585 ChangeState ( EventData::EDITING_WITH_POPUP );
586 mEventData->mDecoratorUpdated = true;
590 void Controller::Impl::OnHandleEvent( const Event& event )
592 if( NULL == mEventData )
594 // Nothing to do if there is no text input.
598 const unsigned int state = event.p1.mUint;
599 const bool handleStopScrolling = ( HANDLE_STOP_SCROLLING == state );
601 if( HANDLE_PRESSED == state )
603 // The event.p2 and event.p3 are in decorator coords. Need to transforms to text coords.
604 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
605 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
607 const CharacterIndex handleNewPosition = GetClosestCursorIndex( xPosition, yPosition );
609 if( Event::GRAB_HANDLE_EVENT == event.type )
611 ChangeState ( EventData::GRAB_HANDLE_PANNING );
613 if( handleNewPosition != mEventData->mPrimaryCursorPosition )
615 mEventData->mPrimaryCursorPosition = handleNewPosition;
616 mEventData->mUpdateCursorPosition = true;
619 else if( Event::LEFT_SELECTION_HANDLE_EVENT == event.type )
621 ChangeState ( EventData::SELECTION_HANDLE_PANNING );
623 if( ( handleNewPosition != mEventData->mLeftSelectionPosition ) &&
624 ( handleNewPosition != mEventData->mRightSelectionPosition ) )
626 mEventData->mLeftSelectionPosition = handleNewPosition;
628 RepositionSelectionHandles( mEventData->mLeftSelectionPosition,
629 mEventData->mRightSelectionPosition );
631 mEventData->mUpdateLeftSelectionPosition = true;
634 else if( Event::RIGHT_SELECTION_HANDLE_EVENT == event.type )
636 ChangeState ( EventData::SELECTION_HANDLE_PANNING );
638 if( ( handleNewPosition != mEventData->mRightSelectionPosition ) &&
639 ( handleNewPosition != mEventData->mLeftSelectionPosition ) )
641 mEventData->mRightSelectionPosition = handleNewPosition;
643 RepositionSelectionHandles( mEventData->mLeftSelectionPosition,
644 mEventData->mRightSelectionPosition );
646 mEventData->mUpdateRightSelectionPosition = true;
649 } // end ( HANDLE_PRESSED == state )
650 else if( ( HANDLE_RELEASED == state ) ||
651 handleStopScrolling )
653 CharacterIndex handlePosition = 0u;
654 if( handleStopScrolling )
656 // The event.p2 and event.p3 are in decorator coords. Need to transforms to text coords.
657 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
658 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
660 handlePosition = GetClosestCursorIndex( xPosition, yPosition );
663 if( Event::GRAB_HANDLE_EVENT == event.type )
665 mEventData->mUpdateCursorPosition = true;
667 ChangeState( EventData::EDITING_WITH_POPUP );
669 if( handleStopScrolling )
671 mEventData->mScrollAfterUpdatePosition = mEventData->mPrimaryCursorPosition != handlePosition;
672 mEventData->mPrimaryCursorPosition = handlePosition;
675 else if( Event::LEFT_SELECTION_HANDLE_EVENT == event.type )
677 ChangeState( EventData::SELECTING );
679 if( handleStopScrolling )
681 mEventData->mUpdateLeftSelectionPosition = ( mEventData->mRightSelectionPosition != handlePosition );
682 mEventData->mScrollAfterUpdatePosition = mEventData->mUpdateLeftSelectionPosition;
684 if( mEventData->mUpdateLeftSelectionPosition )
686 mEventData->mLeftSelectionPosition = handlePosition;
688 RepositionSelectionHandles( mEventData->mLeftSelectionPosition,
689 mEventData->mRightSelectionPosition );
693 else if( Event::RIGHT_SELECTION_HANDLE_EVENT == event.type )
695 ChangeState( EventData::SELECTING );
697 if( handleStopScrolling )
699 mEventData->mUpdateRightSelectionPosition = ( mEventData->mLeftSelectionPosition != handlePosition );
700 mEventData->mScrollAfterUpdatePosition = mEventData->mUpdateRightSelectionPosition;
701 if( mEventData->mUpdateRightSelectionPosition )
703 mEventData->mRightSelectionPosition = handlePosition;
704 RepositionSelectionHandles( mEventData->mLeftSelectionPosition,
705 mEventData->mRightSelectionPosition );
710 mEventData->mDecoratorUpdated = true;
711 } // end ( ( HANDLE_RELEASED == state ) || ( HANDLE_STOP_SCROLLING == state ) )
712 else if( HANDLE_SCROLLING == state )
714 const float xSpeed = event.p2.mFloat;
715 const Vector2& actualSize = mVisualModel->GetActualSize();
716 const Vector2 currentScrollPosition = mEventData->mScrollPosition;
718 mEventData->mScrollPosition.x += xSpeed;
720 ClampHorizontalScroll( actualSize );
722 bool endOfScroll = false;
723 if( Vector2::ZERO == ( currentScrollPosition - mEventData->mScrollPosition ) )
725 // Notify the decorator there is no more text to scroll.
726 // The decorator won't send more scroll events.
727 mEventData->mDecorator->NotifyEndOfScroll();
728 // Still need to set the position of the handle.
732 // Set the position of the handle.
733 const bool scrollRightDirection = xSpeed > 0.f;
734 const bool leftSelectionHandleEvent = Event::LEFT_SELECTION_HANDLE_EVENT == event.type;
735 const bool rightSelectionHandleEvent = Event::RIGHT_SELECTION_HANDLE_EVENT == event.type;
737 if( Event::GRAB_HANDLE_EVENT == event.type )
739 ChangeState( EventData::GRAB_HANDLE_PANNING );
741 Vector2 position = mEventData->mDecorator->GetPosition( GRAB_HANDLE );
743 // Position the grag handle close to either the left or right edge.
744 position.x = scrollRightDirection ? 0.f : mVisualModel->mControlSize.width;
746 // Get the new handle position.
747 // The grab handle's position is in decorator coords. Need to transforms to text coords.
748 const CharacterIndex handlePosition = GetClosestCursorIndex( position.x - mEventData->mScrollPosition.x - mAlignmentOffset.x,
749 position.y - mEventData->mScrollPosition.y - mAlignmentOffset.y );
751 mEventData->mUpdateCursorPosition = mEventData->mPrimaryCursorPosition != handlePosition;
752 mEventData->mScrollAfterUpdatePosition = mEventData->mUpdateCursorPosition;
753 mEventData->mPrimaryCursorPosition = handlePosition;
755 else if( leftSelectionHandleEvent || rightSelectionHandleEvent )
757 // TODO: This is recalculating the selection box every time the text is scrolled with the selection handles.
758 // Think if something can be done to save power.
760 ChangeState( EventData::SELECTION_HANDLE_PANNING );
762 Vector2 position = mEventData->mDecorator->GetPosition( leftSelectionHandleEvent ? Text::LEFT_SELECTION_HANDLE : Text::RIGHT_SELECTION_HANDLE );
764 // Position the selection handle close to either the left or right edge.
765 position.x = scrollRightDirection ? 0.f : mVisualModel->mControlSize.width;
767 // Get the new handle position.
768 // The selection handle's position is in decorator coords. Need to transforms to text coords.
769 const CharacterIndex handlePosition = GetClosestCursorIndex( position.x - mEventData->mScrollPosition.x - mAlignmentOffset.x,
770 position.y - mEventData->mScrollPosition.y - mAlignmentOffset.y );
772 if( leftSelectionHandleEvent )
774 const bool differentHandles = ( mEventData->mLeftSelectionPosition != handlePosition ) && ( mEventData->mRightSelectionPosition != handlePosition );
775 mEventData->mUpdateLeftSelectionPosition = endOfScroll || differentHandles;
776 if( differentHandles )
778 mEventData->mLeftSelectionPosition = handlePosition;
783 const bool differentHandles = ( mEventData->mRightSelectionPosition != handlePosition ) && ( mEventData->mLeftSelectionPosition != handlePosition );
784 mEventData->mUpdateRightSelectionPosition = endOfScroll || differentHandles;
785 if( differentHandles )
787 mEventData->mRightSelectionPosition = handlePosition;
791 if( mEventData->mUpdateLeftSelectionPosition || mEventData->mUpdateRightSelectionPosition )
793 RepositionSelectionHandles( mEventData->mLeftSelectionPosition,
794 mEventData->mRightSelectionPosition );
796 mEventData->mScrollAfterUpdatePosition = true;
799 mEventData->mDecoratorUpdated = true;
800 } // end ( HANDLE_SCROLLING == state )
803 void Controller::Impl::OnSelectEvent( const Event& event )
805 if( NULL == mEventData )
807 // Nothing to do if there is no text.
811 if( mEventData->mSelectionEnabled )
813 // The event.p2 and event.p3 are in decorator coords. Need to transforms to text coords.
814 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
815 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
817 const CharacterIndex leftPosition = mEventData->mLeftSelectionPosition;
818 const CharacterIndex rightPosition = mEventData->mRightSelectionPosition;
820 RepositionSelectionHandles( xPosition,
823 mEventData->mUpdateLeftSelectionPosition = leftPosition != mEventData->mLeftSelectionPosition;
824 mEventData->mUpdateRightSelectionPosition = rightPosition != mEventData->mRightSelectionPosition;
826 mEventData->mScrollAfterUpdatePosition = ( ( mEventData->mUpdateLeftSelectionPosition || mEventData->mUpdateRightSelectionPosition ) &&
827 ( mEventData->mLeftSelectionPosition != mEventData->mRightSelectionPosition ) );
831 void Controller::Impl::OnSelectAllEvent()
833 if( NULL == mEventData )
835 // Nothing to do if there is no text.
839 if( mEventData->mSelectionEnabled )
841 RepositionSelectionHandles( 0u,
842 mLogicalModel->mText.Count() );
844 mEventData->mScrollAfterUpdatePosition = true;
845 mEventData->mUpdateLeftSelectionPosition = true;
846 mEventData->mUpdateRightSelectionPosition = true;
850 void Controller::Impl::RetrieveSelection( std::string& selectedText, bool deleteAfterRetreival )
852 if( mEventData->mLeftSelectionPosition == mEventData->mRightSelectionPosition )
854 // Nothing to select if handles are in the same place.
859 //Get start and end position of selection
860 uint32_t startOfSelectedText = mEventData->mLeftSelectionPosition;
861 uint32_t lengthOfSelectedText = mEventData->mRightSelectionPosition - startOfSelectedText;
863 // Validate the start and end selection points
864 if( ( startOfSelectedText >= 0 ) && ( ( startOfSelectedText + lengthOfSelectedText ) <= mLogicalModel->mText.Count() ) )
866 //Get text as a UTF8 string
867 Vector<Character>& utf32Characters = mLogicalModel->mText;
869 Utf32ToUtf8( &utf32Characters[startOfSelectedText], lengthOfSelectedText, selectedText );
871 if ( deleteAfterRetreival ) // Only delete text if copied successfully
873 // Delete text between handles
874 Vector<Character>& currentText = mLogicalModel->mText;
876 Vector<Character>::Iterator first = currentText.Begin() + startOfSelectedText;
877 Vector<Character>::Iterator last = first + lengthOfSelectedText;
878 currentText.Erase( first, last );
880 mEventData->mPrimaryCursorPosition = mEventData->mLeftSelectionPosition;
881 mEventData->mScrollAfterDelete = true;
882 mEventData->mDecoratorUpdated = true;
886 void Controller::Impl::ShowClipboard()
890 mClipboard.ShowClipboard();
894 void Controller::Impl::HideClipboard()
898 mClipboard.HideClipboard();
902 bool Controller::Impl::CopyStringToClipboard( std::string& source )
904 //Send string to clipboard
905 return ( mClipboard && mClipboard.SetItem( source ) );
908 void Controller::Impl::SendSelectionToClipboard( bool deleteAfterSending )
910 std::string selectedText;
911 RetrieveSelection( selectedText, deleteAfterSending );
912 CopyStringToClipboard( selectedText );
913 ChangeState( EventData::EDITING );
916 void Controller::Impl::GetTextFromClipboard( unsigned int itemIndex, std::string& retreivedString )
920 retreivedString = mClipboard.GetItem( itemIndex );
924 void Controller::Impl::RepositionSelectionHandles( CharacterIndex selectionStart, CharacterIndex selectionEnd )
926 if( selectionStart == selectionEnd )
928 // Nothing to select if handles are in the same place.
932 mEventData->mDecorator->ClearHighlights();
934 mEventData->mLeftSelectionPosition = selectionStart;
935 mEventData->mRightSelectionPosition = selectionEnd;
937 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
938 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
939 const GlyphInfo* const glyphsBuffer = mVisualModel->mGlyphs.Begin();
940 const Vector2* const positionsBuffer = mVisualModel->mGlyphPositions.Begin();
941 const Length* const charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
942 const CharacterIndex* const glyphToCharacterBuffer = mVisualModel->mGlyphsToCharacters.Begin();
943 const CharacterDirection* const modelCharacterDirectionsBuffer = ( 0u != mLogicalModel->mCharacterDirections.Count() ) ? mLogicalModel->mCharacterDirections.Begin() : NULL;
945 // TODO: Better algorithm to create the highlight box.
948 // Get the height of the line.
949 const Vector<LineRun>& lines = mVisualModel->mLines;
950 const LineRun& firstLine = *lines.Begin();
951 const float height = firstLine.ascender + -firstLine.descender;
953 // Swap the indices if the start is greater than the end.
954 const bool indicesSwapped = ( selectionStart > selectionEnd );
957 std::swap( selectionStart, selectionEnd );
960 // Get the indices to the first and last selected glyphs.
961 const CharacterIndex selectionEndMinusOne = selectionEnd - 1u;
962 const GlyphIndex glyphStart = *( charactersToGlyphBuffer + selectionStart );
963 const Length numberOfGlyphs = *( glyphsPerCharacterBuffer + selectionEndMinusOne );
964 const GlyphIndex glyphEnd = *( charactersToGlyphBuffer + selectionEndMinusOne ) + ( ( numberOfGlyphs > 0 ) ? numberOfGlyphs - 1u : 0u );
966 // Check if the first glyph is a ligature that must be broken like Latin ff, fi, or Arabic ï»», etc which needs special code.
967 const Length numberOfCharactersStart = *( charactersPerGlyphBuffer + glyphStart );
968 bool splitStartGlyph = ( numberOfCharactersStart > 1u ) && HasLigatureMustBreak( mLogicalModel->GetScript( selectionStart ) );
970 // Check if the last glyph is a ligature that must be broken like Latin ff, fi, or Arabic ï»», etc which needs special code.
971 const Length numberOfCharactersEnd = *( charactersPerGlyphBuffer + glyphEnd );
972 bool splitEndGlyph = ( glyphStart != glyphEnd ) && ( numberOfCharactersEnd > 1u ) && HasLigatureMustBreak( mLogicalModel->GetScript( selectionEndMinusOne ) );
974 // Tell the decorator to swap the selection handles if needed.
975 mEventData->mDecorator->SwapSelectionHandlesEnabled( firstLine.direction != indicesSwapped );
977 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
979 // Traverse the glyphs.
980 for( GlyphIndex index = glyphStart; index <= glyphEnd; ++index )
982 const GlyphInfo& glyph = *( glyphsBuffer + index );
983 const Vector2& position = *( positionsBuffer + index );
985 if( splitStartGlyph )
987 // If the first glyph is a ligature that must be broken it may be needed to add only part of the glyph to the highlight box.
989 const float glyphAdvance = glyph.advance / static_cast<float>( numberOfCharactersStart );
990 const CharacterIndex interGlyphIndex = selectionStart - *( glyphToCharacterBuffer + glyphStart );
991 // Get the direction of the character.
992 CharacterDirection isCurrentRightToLeft = false;
993 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
995 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + selectionStart );
998 // The end point could be in the middle of the ligature.
999 // Calculate the number of characters selected.
1000 const Length numberOfCharacters = ( glyphStart == glyphEnd ) ? ( selectionEnd - selectionStart ) : ( numberOfCharactersStart - interGlyphIndex );
1002 const float xPosition = position.x - glyph.xBearing + offset.x + glyphAdvance * static_cast<float>( isCurrentRightToLeft ? ( numberOfCharactersStart - interGlyphIndex - numberOfCharacters ) : interGlyphIndex );
1004 mEventData->mDecorator->AddHighlight( xPosition,
1006 xPosition + static_cast<float>( numberOfCharacters ) * glyphAdvance,
1009 splitStartGlyph = false;
1013 if( splitEndGlyph && ( index == glyphEnd ) )
1015 // Equally, if the last glyph is a ligature that must be broken it may be needed to add only part of the glyph to the highlight box.
1017 const float glyphAdvance = glyph.advance / static_cast<float>( numberOfCharactersEnd );
1018 const CharacterIndex interGlyphIndex = selectionEnd - *( glyphToCharacterBuffer + glyphEnd );
1019 // Get the direction of the character.
1020 CharacterDirection isCurrentRightToLeft = false;
1021 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
1023 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + selectionEnd );
1026 const Length numberOfCharacters = numberOfCharactersEnd - interGlyphIndex;
1028 const float xPosition = position.x - glyph.xBearing + offset.x + ( isCurrentRightToLeft ? ( glyphAdvance * static_cast<float>( numberOfCharacters ) ) : 0.f );
1029 mEventData->mDecorator->AddHighlight( xPosition,
1031 xPosition + static_cast<float>( interGlyphIndex ) * glyphAdvance,
1034 splitEndGlyph = false;
1038 const float xPosition = position.x - glyph.xBearing + offset.x;
1039 mEventData->mDecorator->AddHighlight( xPosition, offset.y, xPosition + glyph.advance, height );
1042 CursorInfo primaryCursorInfo;
1043 GetCursorPosition( mEventData->mLeftSelectionPosition,
1044 primaryCursorInfo );
1046 CursorInfo secondaryCursorInfo;
1047 GetCursorPosition( mEventData->mRightSelectionPosition,
1048 secondaryCursorInfo );
1050 const Vector2 primaryPosition = primaryCursorInfo.primaryPosition + offset;
1051 const Vector2 secondaryPosition = secondaryCursorInfo.primaryPosition + offset;
1053 mEventData->mDecorator->SetPosition( LEFT_SELECTION_HANDLE, primaryPosition.x, primaryPosition.y, primaryCursorInfo.lineHeight );
1055 mEventData->mDecorator->SetPosition( RIGHT_SELECTION_HANDLE, secondaryPosition.x, secondaryPosition.y, secondaryCursorInfo.lineHeight );
1057 // Set the flag to update the decorator.
1058 mEventData->mDecoratorUpdated = true;
1061 void Controller::Impl::RepositionSelectionHandles( float visualX, float visualY )
1063 if( NULL == mEventData )
1065 // Nothing to do if there is no text input.
1069 if( IsShowingPlaceholderText() )
1071 // Nothing to do if there is the place-holder text.
1075 const Length numberOfGlyphs = mVisualModel->mGlyphs.Count();
1076 const Length numberOfLines = mVisualModel->mLines.Count();
1077 if( 0 == numberOfGlyphs ||
1078 0 == numberOfLines )
1080 // Nothing to do if there is no text.
1084 // Find which word was selected
1085 CharacterIndex selectionStart( 0 );
1086 CharacterIndex selectionEnd( 0 );
1087 FindSelectionIndices( visualX, visualY, selectionStart, selectionEnd );
1088 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "%p selectionStart %d selectionEnd %d\n", this, selectionStart, selectionEnd );
1090 if( selectionStart == selectionEnd )
1092 ChangeState( EventData::EDITING );
1093 // Nothing to select. i.e. a white space, out of bounds
1097 RepositionSelectionHandles( selectionStart, selectionEnd );
1100 void Controller::Impl::SetPopupButtons()
1103 * Sets the Popup buttons to be shown depending on State.
1105 * If SELECTING : CUT & COPY + ( PASTE & CLIPBOARD if content available to paste )
1107 * If EDITING_WITH_POPUP : SELECT & SELECT_ALL
1110 TextSelectionPopup::Buttons buttonsToShow = TextSelectionPopup::NONE;
1112 if ( ( EventData::SELECTING == mEventData->mState ) || ( EventData::SELECTION_CHANGED == mEventData->mState ) )
1114 buttonsToShow = TextSelectionPopup::Buttons( TextSelectionPopup::CUT | TextSelectionPopup::COPY );
1116 if ( !IsClipboardEmpty() )
1118 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
1119 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::CLIPBOARD ) );
1122 if ( !mEventData->mAllTextSelected )
1124 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::SELECT_ALL ) );
1127 else if ( EventData::EDITING_WITH_POPUP == mEventData->mState )
1129 if ( mLogicalModel->mText.Count() && !IsShowingPlaceholderText())
1131 buttonsToShow = TextSelectionPopup::Buttons( TextSelectionPopup::SELECT | TextSelectionPopup::SELECT_ALL );
1134 if ( !IsClipboardEmpty() )
1136 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
1137 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::CLIPBOARD ) );
1141 mEventData->mDecorator->SetEnabledPopupButtons( buttonsToShow );
1144 void Controller::Impl::ChangeState( EventData::State newState )
1146 if( NULL == mEventData )
1148 // Nothing to do if there is no text input.
1152 if( mEventData->mState != newState )
1154 mEventData->mState = newState;
1156 if( EventData::INACTIVE == mEventData->mState )
1158 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
1159 mEventData->mDecorator->StopCursorBlink();
1160 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1161 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1162 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1163 mEventData->mDecorator->SetPopupActive( false );
1164 mEventData->mDecoratorUpdated = true;
1167 else if ( EventData::INTERRUPTED == mEventData->mState)
1169 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1170 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1171 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1172 mEventData->mDecorator->SetPopupActive( false );
1173 mEventData->mDecoratorUpdated = true;
1176 else if ( EventData::SELECTING == 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 )
1186 mEventData->mDecorator->SetPopupActive( true );
1188 mEventData->mDecoratorUpdated = true;
1190 else if ( EventData::SELECTION_CHANGED == mEventData->mState )
1192 if( mEventData->mGrabHandlePopupEnabled )
1195 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
1196 mEventData->mDecorator->SetPopupActive( true );
1198 mEventData->mDecoratorUpdated = true;
1200 else if( EventData::EDITING == mEventData->mState )
1202 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1203 if( mEventData->mCursorBlinkEnabled )
1205 mEventData->mDecorator->StartCursorBlink();
1207 // Grab handle is not shown until a tap is received whilst EDITING
1208 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1209 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1210 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1211 if( mEventData->mGrabHandlePopupEnabled )
1213 mEventData->mDecorator->SetPopupActive( false );
1215 mEventData->mDecoratorUpdated = true;
1218 else if( EventData::EDITING_WITH_POPUP == mEventData->mState )
1220 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1221 if( mEventData->mCursorBlinkEnabled )
1223 mEventData->mDecorator->StartCursorBlink();
1225 if( mEventData->mSelectionEnabled )
1227 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1228 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1232 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
1234 if( mEventData->mGrabHandlePopupEnabled )
1237 mEventData->mDecorator->SetPopupActive( true );
1240 mEventData->mDecoratorUpdated = true;
1242 else if( EventData::EDITING_WITH_GRAB_HANDLE == mEventData->mState )
1244 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1245 if( mEventData->mCursorBlinkEnabled )
1247 mEventData->mDecorator->StartCursorBlink();
1249 // Grab handle is not shown until a tap is received whilst EDITING
1250 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
1251 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1252 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1253 if( mEventData->mGrabHandlePopupEnabled )
1255 mEventData->mDecorator->SetPopupActive( false );
1257 mEventData->mDecoratorUpdated = true;
1260 else if ( EventData::SELECTION_HANDLE_PANNING == mEventData->mState )
1262 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
1263 mEventData->mDecorator->StopCursorBlink();
1264 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1265 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true );
1266 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true );
1267 if( mEventData->mGrabHandlePopupEnabled )
1269 mEventData->mDecorator->SetPopupActive( false );
1271 mEventData->mDecoratorUpdated = true;
1273 else if ( EventData::GRAB_HANDLE_PANNING == mEventData->mState )
1275 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1276 if( mEventData->mCursorBlinkEnabled )
1278 mEventData->mDecorator->StartCursorBlink();
1280 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
1281 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1282 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1283 if( mEventData->mGrabHandlePopupEnabled )
1285 mEventData->mDecorator->SetPopupActive( false );
1287 mEventData->mDecoratorUpdated = true;
1292 LineIndex Controller::Impl::GetClosestLine( float y ) const
1294 float totalHeight = 0.f;
1295 LineIndex lineIndex = 0u;
1297 const Vector<LineRun>& lines = mVisualModel->mLines;
1298 for( LineIndex endLine = lines.Count();
1299 lineIndex < endLine;
1302 const LineRun& lineRun = lines[lineIndex];
1303 totalHeight += lineRun.ascender + -lineRun.descender;
1304 if( y < totalHeight )
1310 if( lineIndex == 0 )
1318 void Controller::Impl::FindSelectionIndices( float visualX, float visualY, CharacterIndex& startIndex, CharacterIndex& endIndex )
1320 CharacterIndex hitCharacter = GetClosestCursorIndex( visualX, visualY );
1321 if( hitCharacter >= mLogicalModel->mText.Count() )
1323 // Selection out of bounds.
1327 startIndex = hitCharacter;
1328 endIndex = hitCharacter;
1330 if( !TextAbstraction::IsWhiteSpace( mLogicalModel->mText[hitCharacter] ) )
1332 // Find the start and end of the text
1333 for( startIndex = hitCharacter; startIndex > 0; --startIndex )
1335 Character charCode = mLogicalModel->mText[ startIndex-1 ];
1336 if( TextAbstraction::IsWhiteSpace( charCode ) )
1341 const CharacterIndex pastTheEnd = mLogicalModel->mText.Count();
1342 for( endIndex = hitCharacter + 1u; endIndex < pastTheEnd; ++endIndex )
1344 Character charCode = mLogicalModel->mText[ endIndex ];
1345 if( TextAbstraction::IsWhiteSpace( charCode ) )
1353 CharacterIndex Controller::Impl::GetClosestCursorIndex( float visualX,
1356 if( NULL == mEventData )
1358 // Nothing to do if there is no text input.
1362 CharacterIndex logicalIndex = 0u;
1364 const Length numberOfGlyphs = mVisualModel->mGlyphs.Count();
1365 const Length numberOfLines = mVisualModel->mLines.Count();
1366 if( 0 == numberOfGlyphs ||
1367 0 == numberOfLines )
1369 return logicalIndex;
1372 // Find which line is closest
1373 const LineIndex lineIndex = GetClosestLine( visualY );
1374 const LineRun& line = mVisualModel->mLines[lineIndex];
1376 // Get the positions of the glyphs.
1377 const Vector<Vector2>& positions = mVisualModel->mGlyphPositions;
1378 const Vector2* const positionsBuffer = positions.Begin();
1380 // Get the visual to logical conversion tables.
1381 const CharacterIndex* const visualToLogicalBuffer = ( 0u != mLogicalModel->mVisualToLogicalMap.Count() ) ? mLogicalModel->mVisualToLogicalMap.Begin() : NULL;
1382 const CharacterIndex* const visualToLogicalCursorBuffer = mLogicalModel->mVisualToLogicalCursorMap.Begin();
1384 // Get the character to glyph conversion table.
1385 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
1387 // Get the glyphs per character table.
1388 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
1389 const Length* const charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
1391 // If the vector is void, there is no right to left characters.
1392 const bool hasRightToLeftCharacters = NULL != visualToLogicalBuffer;
1394 const CharacterIndex startCharacter = line.characterRun.characterIndex;
1395 const CharacterIndex endCharacter = line.characterRun.characterIndex + line.characterRun.numberOfCharacters;
1396 DALI_ASSERT_DEBUG( endCharacter <= mLogicalModel->mText.Count() && "Invalid line info" );
1398 // Whether there is a hit on a glyph.
1399 bool matched = false;
1401 // Traverses glyphs in visual order. To do that use the visual to logical conversion table.
1402 CharacterIndex visualIndex = startCharacter;
1403 for( ; !matched && ( visualIndex < endCharacter ); ++visualIndex )
1405 // The character in logical order.
1406 const CharacterIndex characterLogicalOrderIndex = hasRightToLeftCharacters ? *( visualToLogicalBuffer + visualIndex ) : visualIndex;
1408 // Get the script of the character.
1409 const Script script = mLogicalModel->GetScript( characterLogicalOrderIndex );
1411 // The first glyph for that character in logical order.
1412 const GlyphIndex glyphLogicalOrderIndex = *( charactersToGlyphBuffer + characterLogicalOrderIndex );
1413 // The number of glyphs for that character
1414 const Length numberOfGlyphs = *( glyphsPerCharacterBuffer + characterLogicalOrderIndex );
1416 // Get the metrics for the group of glyphs.
1417 GlyphMetrics glyphMetrics;
1418 GetGlyphsMetrics( glyphLogicalOrderIndex,
1424 const Vector2& position = *( positionsBuffer + glyphLogicalOrderIndex );
1426 // Prevents to jump the whole Latin ligatures like fi, ff, or Arabic ï»»...
1427 const Length numberOfCharactersInLigature = HasLigatureMustBreak( script ) ? *( charactersPerGlyphBuffer + glyphLogicalOrderIndex ) : 1u;
1428 const float glyphAdvance = glyphMetrics.advance / static_cast<float>( numberOfCharactersInLigature );
1430 for( GlyphIndex index = 0u; !matched && ( index < numberOfCharactersInLigature ); ++index )
1432 // Find the mid-point of the area containing the glyph
1433 const float glyphCenter = -glyphMetrics.xBearing + position.x + ( static_cast<float>( index ) + 0.5f ) * glyphAdvance;
1435 if( visualX < glyphCenter )
1437 visualIndex += index;
1449 // Return the logical position of the cursor in characters.
1453 visualIndex = endCharacter;
1456 logicalIndex = hasRightToLeftCharacters ? *( visualToLogicalCursorBuffer + visualIndex ) : visualIndex;
1457 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "%p closest visualIndex %d logicalIndex %d\n", this, visualIndex, logicalIndex );
1459 return logicalIndex;
1462 void Controller::Impl::GetCursorPosition( CharacterIndex logical,
1463 CursorInfo& cursorInfo )
1465 // TODO: Check for multiline with \n, etc...
1467 // Check if the logical position is the first or the last one of the text.
1468 const bool isFirstPosition = 0u == logical;
1469 const bool isLastPosition = mLogicalModel->mText.Count() == logical;
1471 if( isFirstPosition && isLastPosition )
1473 // There is zero characters. Get the default font's line height.
1474 cursorInfo.lineHeight = GetDefaultFontLineHeight();
1475 cursorInfo.primaryCursorHeight = cursorInfo.lineHeight;
1477 cursorInfo.primaryPosition.x = 1.f;
1478 cursorInfo.primaryPosition.y = 0.f;
1480 // Nothing else to do.
1484 // 'logical' is the logical 'cursor' index.
1485 // Get the next and current logical 'character' index.
1486 const CharacterIndex nextCharacterIndex = logical;
1487 const CharacterIndex characterIndex = isFirstPosition ? logical : logical - 1u;
1489 // Get the direction of the character and the next one.
1490 const CharacterDirection* const modelCharacterDirectionsBuffer = ( 0u != mLogicalModel->mCharacterDirections.Count() ) ? mLogicalModel->mCharacterDirections.Begin() : NULL;
1492 CharacterDirection isCurrentRightToLeft = false;
1493 CharacterDirection isNextRightToLeft = false;
1494 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
1496 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + characterIndex );
1497 isNextRightToLeft = *( modelCharacterDirectionsBuffer + nextCharacterIndex );
1500 // Get the line where the character is laid-out.
1501 const LineRun* const modelLines = mVisualModel->mLines.Begin();
1503 const LineIndex lineIndex = mVisualModel->GetLineOfCharacter( characterIndex );
1504 const LineRun& line = *( modelLines + lineIndex );
1506 // Get the paragraph's direction.
1507 const CharacterDirection isRightToLeftParagraph = line.direction;
1509 // Check whether there is an alternative position:
1511 cursorInfo.isSecondaryCursor = ( !isLastPosition && ( isCurrentRightToLeft != isNextRightToLeft ) ) ||
1512 ( isLastPosition && ( isRightToLeftParagraph != isCurrentRightToLeft ) );
1514 // Set the line height.
1515 cursorInfo.lineHeight = line.ascender + -line.descender;
1517 // Calculate the primary cursor.
1519 CharacterIndex index = characterIndex;
1520 if( cursorInfo.isSecondaryCursor )
1522 // If there is a secondary position, the primary cursor may be in a different place than the logical index.
1524 if( isLastPosition )
1526 // The position of the cursor after the last character needs special
1527 // care depending on its direction and the direction of the paragraph.
1529 // Need to find the first character after the last character with the paragraph's direction.
1530 // i.e l0 l1 l2 r0 r1 should find r0.
1532 // TODO: check for more than one line!
1533 index = isRightToLeftParagraph ? line.characterRun.characterIndex : line.characterRun.characterIndex + line.characterRun.numberOfCharacters - 1u;
1534 index = mLogicalModel->GetLogicalCharacterIndex( index );
1538 index = ( isRightToLeftParagraph == isCurrentRightToLeft ) ? characterIndex : nextCharacterIndex;
1542 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
1543 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
1544 const Length* const charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
1545 const CharacterIndex* const glyphsToCharactersBuffer = mVisualModel->mGlyphsToCharacters.Begin();
1546 const Vector2* const glyphPositionsBuffer = mVisualModel->mGlyphPositions.Begin();
1548 // Convert the cursor position into the glyph position.
1549 const GlyphIndex primaryGlyphIndex = *( charactersToGlyphBuffer + index );
1550 const Length primaryNumberOfGlyphs = *( glyphsPerCharacterBuffer + index );
1551 const Length primaryNumberOfCharacters = *( charactersPerGlyphBuffer + primaryGlyphIndex );
1553 // Get the metrics for the group of glyphs.
1554 GlyphMetrics glyphMetrics;
1555 GetGlyphsMetrics( primaryGlyphIndex,
1556 primaryNumberOfGlyphs,
1561 // Whether to add the glyph's advance to the cursor position.
1562 // i.e if the paragraph is left to right and the logical cursor is zero, the position is the position of the first glyph and the advance is not added,
1563 // if the logical cursor is one, the position is the position of the first glyph and the advance is added.
1564 // A 'truth table' was build and an online Karnaugh map tool was used to simplify the logic.
1585 // Where F -> isFirstPosition
1586 // L -> isLastPosition
1587 // C -> isCurrentRightToLeft
1588 // P -> isRightToLeftParagraph
1589 // A -> Whether to add the glyph's advance.
1591 const bool addGlyphAdvance = ( ( isLastPosition && !isRightToLeftParagraph ) ||
1592 ( isFirstPosition && isRightToLeftParagraph ) ||
1593 ( !isFirstPosition && !isLastPosition && !isCurrentRightToLeft ) );
1595 float glyphAdvance = addGlyphAdvance ? glyphMetrics.advance : 0.f;
1597 if( !isLastPosition &&
1598 ( primaryNumberOfCharacters > 1u ) )
1600 const CharacterIndex firstIndex = *( glyphsToCharactersBuffer + primaryGlyphIndex );
1602 bool isCurrentRightToLeft = false;
1603 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
1605 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + index );
1608 Length numberOfGlyphAdvance = ( isFirstPosition ? 0u : 1u ) + characterIndex - firstIndex;
1609 if( isCurrentRightToLeft )
1611 numberOfGlyphAdvance = primaryNumberOfCharacters - numberOfGlyphAdvance;
1614 glyphAdvance = static_cast<float>( numberOfGlyphAdvance ) * glyphMetrics.advance / static_cast<float>( primaryNumberOfCharacters );
1617 // Get the glyph position and x bearing.
1618 const Vector2& primaryPosition = *( glyphPositionsBuffer + primaryGlyphIndex );
1620 // Set the primary cursor's height.
1621 cursorInfo.primaryCursorHeight = cursorInfo.isSecondaryCursor ? 0.5f * glyphMetrics.fontHeight : glyphMetrics.fontHeight;
1623 // Set the primary cursor's position.
1624 cursorInfo.primaryPosition.x = -glyphMetrics.xBearing + primaryPosition.x + glyphAdvance;
1625 cursorInfo.primaryPosition.y = line.ascender - glyphMetrics.ascender;
1627 // Calculate the secondary cursor.
1629 if( cursorInfo.isSecondaryCursor )
1631 // Set the secondary cursor's height.
1632 cursorInfo.secondaryCursorHeight = 0.5f * glyphMetrics.fontHeight;
1634 CharacterIndex index = characterIndex;
1635 if( !isLastPosition )
1637 index = ( isRightToLeftParagraph == isCurrentRightToLeft ) ? nextCharacterIndex : characterIndex;
1640 const GlyphIndex secondaryGlyphIndex = *( charactersToGlyphBuffer + index );
1641 const Length secondaryNumberOfGlyphs = *( glyphsPerCharacterBuffer + index );
1643 const Vector2& secondaryPosition = *( glyphPositionsBuffer + secondaryGlyphIndex );
1645 GetGlyphsMetrics( secondaryGlyphIndex,
1646 secondaryNumberOfGlyphs,
1651 // Set the secondary cursor's position.
1652 cursorInfo.secondaryPosition.x = -glyphMetrics.xBearing + secondaryPosition.x + ( isCurrentRightToLeft ? 0.f : glyphMetrics.advance );
1653 cursorInfo.secondaryPosition.y = cursorInfo.lineHeight - cursorInfo.secondaryCursorHeight - line.descender - ( glyphMetrics.fontHeight - glyphMetrics.ascender );
1657 CharacterIndex Controller::Impl::CalculateNewCursorIndex( CharacterIndex index ) const
1659 if( NULL == mEventData )
1661 // Nothing to do if there is no text input.
1665 CharacterIndex cursorIndex = mEventData->mPrimaryCursorPosition;
1667 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
1668 const Length* const charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
1670 GlyphIndex glyphIndex = *( charactersToGlyphBuffer + index );
1671 Length numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
1673 if( numberOfCharacters > 1u )
1675 const Script script = mLogicalModel->GetScript( index );
1676 if( HasLigatureMustBreak( script ) )
1678 // Prevents to jump the whole Latin ligatures like fi, ff, or Arabic ï»», ...
1679 numberOfCharacters = 1u;
1684 while( 0u == numberOfCharacters )
1687 numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
1691 if( index < mEventData->mPrimaryCursorPosition )
1693 cursorIndex -= numberOfCharacters;
1697 cursorIndex += numberOfCharacters;
1703 void Controller::Impl::UpdateCursorPosition()
1705 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "-->Controller::UpdateCursorPosition %p\n", this );
1706 if( NULL == mEventData )
1708 // Nothing to do if there is no text input.
1709 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition no event data\n" );
1713 if( IsShowingPlaceholderText() )
1715 // Do not want to use the place-holder text to set the cursor position.
1717 // Use the line's height of the font's family set to set the cursor's size.
1718 // If there is no font's family set, use the default font.
1719 // Use the current alignment to place the cursor at the beginning, center or end of the box.
1721 float lineHeight = 0.f;
1723 FontId defaultFontId = 0u;
1724 if( NULL == mFontDefaults )
1726 defaultFontId = mFontClient.GetFontId( EMPTY_STRING,
1731 defaultFontId = mFontDefaults->GetFontId( mFontClient );
1734 Text::FontMetrics fontMetrics;
1735 mFontClient.GetFontMetrics( defaultFontId, fontMetrics );
1737 lineHeight = fontMetrics.ascender - fontMetrics.descender;
1740 Vector2 cursorPosition;
1742 switch( mLayoutEngine.GetHorizontalAlignment() )
1744 case LayoutEngine::HORIZONTAL_ALIGN_BEGIN:
1746 cursorPosition.x = 1.f;
1749 case LayoutEngine::HORIZONTAL_ALIGN_CENTER:
1751 cursorPosition.x = floor( 0.5f * mVisualModel->mControlSize.width );
1754 case LayoutEngine::HORIZONTAL_ALIGN_END:
1756 cursorPosition.x = mVisualModel->mControlSize.width;
1761 switch( mLayoutEngine.GetVerticalAlignment() )
1763 case LayoutEngine::VERTICAL_ALIGN_TOP:
1765 cursorPosition.y = 0.f;
1768 case LayoutEngine::VERTICAL_ALIGN_CENTER:
1770 cursorPosition.y = floorf( 0.5f * ( mVisualModel->mControlSize.height - lineHeight ) );
1773 case LayoutEngine::VERTICAL_ALIGN_BOTTOM:
1775 cursorPosition.y = mVisualModel->mControlSize.height - lineHeight;
1780 mEventData->mDecorator->SetPosition( PRIMARY_CURSOR,
1788 CursorInfo cursorInfo;
1789 GetCursorPosition( mEventData->mPrimaryCursorPosition,
1792 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
1793 const Vector2 cursorPosition = cursorInfo.primaryPosition + offset;
1795 // Sets the cursor position.
1796 mEventData->mDecorator->SetPosition( PRIMARY_CURSOR,
1799 cursorInfo.primaryCursorHeight,
1800 cursorInfo.lineHeight );
1801 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Primary cursor position: %f,%f\n", cursorPosition.x, cursorPosition.y );
1803 // Sets the grab handle position.
1804 mEventData->mDecorator->SetPosition( GRAB_HANDLE,
1807 cursorInfo.lineHeight );
1809 if( cursorInfo.isSecondaryCursor )
1811 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_BOTH );
1812 mEventData->mDecorator->SetPosition( SECONDARY_CURSOR,
1813 cursorInfo.secondaryPosition.x + offset.x,
1814 cursorInfo.secondaryPosition.y + offset.y,
1815 cursorInfo.secondaryCursorHeight,
1816 cursorInfo.lineHeight );
1817 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Secondary cursor position: %f,%f\n", cursorInfo.secondaryPosition.x + offset.x, cursorInfo.secondaryPosition.y + offset.y );
1821 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1824 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition\n" );
1827 void Controller::Impl::UpdateSelectionHandle( HandleType handleType )
1829 if( ( LEFT_SELECTION_HANDLE != handleType ) &&
1830 ( RIGHT_SELECTION_HANDLE != handleType ) )
1835 const bool leftSelectionHandle = LEFT_SELECTION_HANDLE == handleType;
1836 const CharacterIndex index = leftSelectionHandle ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition;
1838 CursorInfo cursorInfo;
1839 GetCursorPosition( index,
1842 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
1843 const Vector2 cursorPosition = cursorInfo.primaryPosition + offset;
1845 // Sets the grab handle position.
1846 mEventData->mDecorator->SetPosition( handleType,
1849 cursorInfo.lineHeight );
1851 // If selection handle at start of the text and other at end of the text then all text is selected.
1852 const CharacterIndex startOfSelection = std::min( mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition );
1853 const CharacterIndex endOfSelection = std::max ( mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition );
1854 mEventData->mAllTextSelected = ( startOfSelection == 0 ) && ( endOfSelection == mLogicalModel->mText.Count() );
1857 void Controller::Impl::ClampHorizontalScroll( const Vector2& actualSize )
1859 // Clamp between -space & 0 (and the text alignment).
1860 if( actualSize.width > mVisualModel->mControlSize.width )
1862 const float space = ( actualSize.width - mVisualModel->mControlSize.width ) + mAlignmentOffset.x;
1863 mEventData->mScrollPosition.x = ( mEventData->mScrollPosition.x < -space ) ? -space : mEventData->mScrollPosition.x;
1864 mEventData->mScrollPosition.x = ( mEventData->mScrollPosition.x > -mAlignmentOffset.x ) ? -mAlignmentOffset.x : mEventData->mScrollPosition.x;
1866 mEventData->mDecoratorUpdated = true;
1870 mEventData->mScrollPosition.x = 0.f;
1874 void Controller::Impl::ClampVerticalScroll( const Vector2& actualSize )
1876 // Clamp between -space & 0 (and the text alignment).
1877 if( actualSize.height > mVisualModel->mControlSize.height )
1879 const float space = ( actualSize.height - mVisualModel->mControlSize.height ) + mAlignmentOffset.y;
1880 mEventData->mScrollPosition.y = ( mEventData->mScrollPosition.y < -space ) ? -space : mEventData->mScrollPosition.y;
1881 mEventData->mScrollPosition.y = ( mEventData->mScrollPosition.y > -mAlignmentOffset.y ) ? -mAlignmentOffset.y : mEventData->mScrollPosition.y;
1883 mEventData->mDecoratorUpdated = true;
1887 mEventData->mScrollPosition.y = 0.f;
1891 void Controller::Impl::ScrollToMakePositionVisible( const Vector2& position )
1894 bool updateDecorator = false;
1895 if( position.x < 0.f )
1897 offset.x = -position.x;
1898 mEventData->mScrollPosition.x += offset.x;
1899 updateDecorator = true;
1901 else if( position.x > mVisualModel->mControlSize.width )
1903 offset.x = mVisualModel->mControlSize.width - position.x;
1904 mEventData->mScrollPosition.x += offset.x;
1905 updateDecorator = true;
1908 if( updateDecorator && mEventData->mDecorator )
1910 mEventData->mDecorator->UpdatePositions( offset );
1913 // TODO : calculate the vertical scroll.
1916 void Controller::Impl::ScrollTextToMatchCursor()
1918 // Get the current cursor position in decorator coords.
1919 const Vector2& currentCursorPosition = mEventData->mDecorator->GetPosition( PRIMARY_CURSOR );
1921 // Calculate the new cursor position.
1922 CursorInfo cursorInfo;
1923 GetCursorPosition( mEventData->mPrimaryCursorPosition,
1926 // Calculate the offset to match the cursor position before the character was deleted.
1927 mEventData->mScrollPosition.x = currentCursorPosition.x - cursorInfo.primaryPosition.x - mAlignmentOffset.x;
1929 ClampHorizontalScroll( mVisualModel->GetActualSize() );
1931 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
1932 const Vector2 cursorPosition = cursorInfo.primaryPosition + offset;
1934 // Sets the cursor position.
1935 mEventData->mDecorator->SetPosition( PRIMARY_CURSOR,
1938 cursorInfo.primaryCursorHeight,
1939 cursorInfo.lineHeight );
1941 // Sets the grab handle position.
1942 mEventData->mDecorator->SetPosition( GRAB_HANDLE,
1945 cursorInfo.lineHeight );
1947 if( cursorInfo.isSecondaryCursor )
1949 mEventData->mDecorator->SetPosition( SECONDARY_CURSOR,
1950 cursorInfo.secondaryPosition.x + offset.x,
1951 cursorInfo.secondaryPosition.y + offset.y,
1952 cursorInfo.secondaryCursorHeight,
1953 cursorInfo.lineHeight );
1954 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Secondary cursor position: %f,%f\n", cursorInfo.secondaryPosition.x + offset.x, cursorInfo.secondaryPosition.y + offset.y );
1957 // Set which cursors are active according the state.
1958 if( ( EventData::EDITING == mEventData->mState ) ||
1959 ( EventData::EDITING_WITH_POPUP == mEventData->mState ) ||
1960 ( EventData::GRAB_HANDLE_PANNING == mEventData->mState ) )
1962 if( cursorInfo.isSecondaryCursor )
1964 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_BOTH );
1968 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1973 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
1977 void Controller::Impl::RequestRelayout()
1979 mControlInterface.RequestTextRelayout();
1984 } // namespace Toolkit