2 * Copyright (c) 2015 Samsung Electronics Co., Ltd.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
19 #include <dali-toolkit/internal/text/text-controller-impl.h>
22 #include <dali/public-api/adaptor-framework/key.h>
23 #include <dali/integration-api/debug.h>
26 #include <dali-toolkit/internal/text/bidirectional-support.h>
27 #include <dali-toolkit/internal/text/character-set-conversion.h>
28 #include <dali-toolkit/internal/text/layouts/layout-parameters.h>
29 #include <dali-toolkit/internal/text/multi-language-support.h>
30 #include <dali-toolkit/internal/text/script-run.h>
31 #include <dali-toolkit/internal/text/segmentation.h>
32 #include <dali-toolkit/internal/text/shaper.h>
33 #include <dali-toolkit/internal/text/text-io.h>
34 #include <dali-toolkit/internal/text/text-view.h>
39 #if defined(DEBUG_ENABLED)
40 Debug::Filter* gLogFilter = Debug::Filter::New(Debug::Concise, true, "LOG_TEXT_CONTROLS");
44 * @brief Some characters can be shaped in more than one glyph.
45 * This struct is used to retrieve metrics from these group of glyphs.
59 float fontHeight; ///< The font's height of that glyphs.
60 float advance; ///< The sum of all the advances of all the glyphs.
61 float ascender; ///< The font's ascender.
62 float xBearing; ///< The x bearing of the first glyph.
65 const std::string EMPTY_STRING("");
79 * @brief Get some glyph's metrics of a group of glyphs formed as a result of shaping one character.
81 * @param[in] glyphIndex The index to the first glyph.
82 * @param[in] numberOfGlyphs The number of glyphs.
83 * @param[out] glyphMetrics Some glyph metrics (font height, advance, ascender and x bearing).
84 * @param[in] visualModel The visual model.
85 * @param[in] fontClient The font client.
87 void GetGlyphsMetrics( GlyphIndex glyphIndex,
88 Length numberOfGlyphs,
89 GlyphMetrics& glyphMetrics,
90 VisualModelPtr visualModel,
91 TextAbstraction::FontClient& fontClient )
93 const GlyphInfo* glyphsBuffer = visualModel->mGlyphs.Begin();
95 const GlyphInfo& firstGlyph = *( glyphsBuffer + glyphIndex );
97 Text::FontMetrics fontMetrics;
98 fontClient.GetFontMetrics( firstGlyph.fontId, fontMetrics );
100 glyphMetrics.fontHeight = fontMetrics.height;
101 glyphMetrics.advance = firstGlyph.advance;
102 glyphMetrics.ascender = fontMetrics.ascender;
103 glyphMetrics.xBearing = firstGlyph.xBearing;
105 for( unsigned int i = 1u; i < numberOfGlyphs; ++i )
107 const GlyphInfo& glyphInfo = *( glyphsBuffer + glyphIndex + i );
109 glyphMetrics.advance += glyphInfo.advance;
113 EventData::EventData( DecoratorPtr decorator )
114 : mDecorator( decorator ),
115 mPlaceholderTextActive(),
116 mPlaceholderTextInactive(),
117 mPlaceholderTextColor( 0.8f, 0.8f, 0.8f, 0.8f ),
121 mPrimaryCursorPosition( 0u ),
122 mLeftSelectionPosition( 0u ),
123 mRightSelectionPosition( 0u ),
124 mPreEditStartPosition( 0u ),
125 mPreEditLength( 0u ),
126 mIsShowingPlaceholderText( false ),
127 mPreEditFlag( false ),
128 mDecoratorUpdated( false ),
129 mCursorBlinkEnabled( true ),
130 mGrabHandleEnabled( true ),
131 mGrabHandlePopupEnabled( true ),
132 mSelectionEnabled( true ),
133 mHorizontalScrollingEnabled( true ),
134 mVerticalScrollingEnabled( false ),
135 mUpdateCursorPosition( false ),
136 mUpdateLeftSelectionPosition( false ),
137 mUpdateRightSelectionPosition( false ),
138 mScrollAfterUpdatePosition( false ),
139 mScrollAfterDelete( false ),
140 mAllTextSelected( false )
143 EventData::~EventData()
146 bool Controller::Impl::ProcessInputEvents()
148 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "-->Controller::ProcessInputEvents\n" );
149 if( NULL == mEventData )
151 // Nothing to do if there is no text input.
152 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::ProcessInputEvents no event data\n" );
156 if( mEventData->mDecorator )
158 for( std::vector<Event>::iterator iter = mEventData->mEventQueue.begin();
159 iter != mEventData->mEventQueue.end();
164 case Event::CURSOR_KEY_EVENT:
166 OnCursorKeyEvent( *iter );
169 case Event::TAP_EVENT:
174 case Event::LONG_PRESS_EVENT:
176 OnLongPressEvent( *iter );
179 case Event::PAN_EVENT:
184 case Event::GRAB_HANDLE_EVENT:
185 case Event::LEFT_SELECTION_HANDLE_EVENT:
186 case Event::RIGHT_SELECTION_HANDLE_EVENT: // Fall through
188 OnHandleEvent( *iter );
193 OnSelectEvent( *iter );
196 case Event::SELECT_ALL:
205 // The cursor must also be repositioned after inserts into the model
206 if( mEventData->mUpdateCursorPosition )
208 // Updates the cursor position and scrolls the text to make it visible.
210 UpdateCursorPosition();
212 if( mEventData->mScrollAfterUpdatePosition )
214 const Vector2& primaryCursorPosition = mEventData->mDecorator->GetPosition( PRIMARY_CURSOR );
216 ScrollToMakePositionVisible( primaryCursorPosition );
217 mEventData->mScrollAfterUpdatePosition = false;
220 mEventData->mDecoratorUpdated = true;
221 mEventData->mUpdateCursorPosition = false;
223 else if( mEventData->mScrollAfterDelete )
225 ScrollTextToMatchCursor();
226 mEventData->mDecoratorUpdated = true;
227 mEventData->mScrollAfterDelete = false;
231 bool leftScroll = false;
232 bool rightScroll = false;
234 if( mEventData->mUpdateLeftSelectionPosition )
236 UpdateSelectionHandle( LEFT_SELECTION_HANDLE );
238 if( mEventData->mScrollAfterUpdatePosition )
240 const Vector2& leftHandlePosition = mEventData->mDecorator->GetPosition( LEFT_SELECTION_HANDLE );
242 ScrollToMakePositionVisible( leftHandlePosition );
247 mEventData->mDecoratorUpdated = true;
248 mEventData->mUpdateLeftSelectionPosition = false;
251 if( mEventData->mUpdateRightSelectionPosition )
253 UpdateSelectionHandle( RIGHT_SELECTION_HANDLE );
255 if( mEventData->mScrollAfterUpdatePosition )
257 const Vector2& rightHandlePosition = mEventData->mDecorator->GetPosition( RIGHT_SELECTION_HANDLE );
259 ScrollToMakePositionVisible( rightHandlePosition );
264 mEventData->mDecoratorUpdated = true;
265 mEventData->mUpdateRightSelectionPosition = false;
268 if( leftScroll || rightScroll )
270 mEventData->mScrollAfterUpdatePosition = false;
274 mEventData->mEventQueue.clear();
276 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::ProcessInputEvents\n" );
278 const bool decoratorUpdated = mEventData->mDecoratorUpdated;
279 mEventData->mDecoratorUpdated = false;
281 return decoratorUpdated;
284 void Controller::Impl::UpdateModel( OperationsMask operationsRequired )
286 // Calculate the operations to be done.
287 const OperationsMask operations = static_cast<OperationsMask>( mOperationsPending & operationsRequired );
289 Vector<Character>& utf32Characters = mLogicalModel->mText;
291 const Length numberOfCharacters = utf32Characters.Count();
293 Vector<LineBreakInfo>& lineBreakInfo = mLogicalModel->mLineBreakInfo;
294 if( GET_LINE_BREAKS & operations )
296 // Retrieves the line break info. The line break info is used to split the text in 'paragraphs' to
297 // calculate the bidirectional info for each 'paragraph'.
298 // It's also used to layout the text (where it should be a new line) or to shape the text (text in different lines
299 // is not shaped together).
300 lineBreakInfo.Resize( numberOfCharacters, TextAbstraction::LINE_NO_BREAK );
302 SetLineBreakInfo( utf32Characters,
306 Vector<WordBreakInfo>& wordBreakInfo = mLogicalModel->mWordBreakInfo;
307 if( GET_WORD_BREAKS & operations )
309 // Retrieves the word break info. The word break info is used to layout the text (where to wrap the text in lines).
310 wordBreakInfo.Resize( numberOfCharacters, TextAbstraction::WORD_NO_BREAK );
312 SetWordBreakInfo( utf32Characters,
316 const bool getScripts = GET_SCRIPTS & operations;
317 const bool validateFonts = VALIDATE_FONTS & operations;
319 Vector<ScriptRun>& scripts = mLogicalModel->mScriptRuns;
320 Vector<FontRun>& validFonts = mLogicalModel->mFontRuns;
322 if( getScripts || validateFonts )
324 // Validates the fonts assigned by the application or assigns default ones.
325 // It makes sure all the characters are going to be rendered by the correct font.
326 MultilanguageSupport multilanguageSupport = MultilanguageSupport::Get();
330 // Retrieves the scripts used in the text.
331 multilanguageSupport.SetScripts( utf32Characters,
338 if( 0u == validFonts.Count() )
340 // Copy the requested font defaults received via the property system.
341 // These may not be valid i.e. may not contain glyphs for the necessary scripts.
342 GetDefaultFonts( validFonts, numberOfCharacters );
345 // Validates the fonts. If there is a character with no assigned font it sets a default one.
346 // After this call, fonts are validated.
347 multilanguageSupport.ValidateFonts( utf32Characters,
353 Vector<Character> mirroredUtf32Characters;
354 bool textMirrored = false;
355 if( BIDI_INFO & operations )
357 // Count the number of LINE_NO_BREAK to reserve some space for the vector of paragraph's
358 // bidirectional info.
360 Length numberOfParagraphs = 0u;
362 const TextAbstraction::LineBreakInfo* lineBreakInfoBuffer = lineBreakInfo.Begin();
363 for( Length index = 0u; index < numberOfCharacters; ++index )
365 if( TextAbstraction::LINE_NO_BREAK == *( lineBreakInfoBuffer + index ) )
367 ++numberOfParagraphs;
371 Vector<BidirectionalParagraphInfoRun>& bidirectionalInfo = mLogicalModel->mBidirectionalParagraphInfo;
372 bidirectionalInfo.Reserve( numberOfParagraphs );
374 // Calculates the bidirectional info for the whole paragraph if it contains right to left scripts.
375 SetBidirectionalInfo( utf32Characters,
380 if( 0u != bidirectionalInfo.Count() )
382 // This paragraph has right to left text. Some characters may need to be mirrored.
383 // TODO: consider if the mirrored string can be stored as well.
385 textMirrored = GetMirroredText( utf32Characters, mirroredUtf32Characters );
387 // Only set the character directions if there is right to left characters.
388 Vector<CharacterDirection>& directions = mLogicalModel->mCharacterDirections;
389 directions.Resize( numberOfCharacters );
391 GetCharactersDirection( bidirectionalInfo,
396 // There is no right to left characters. Clear the directions vector.
397 mLogicalModel->mCharacterDirections.Clear();
401 Vector<GlyphInfo>& glyphs = mVisualModel->mGlyphs;
402 Vector<CharacterIndex>& glyphsToCharactersMap = mVisualModel->mGlyphsToCharacters;
403 Vector<Length>& charactersPerGlyph = mVisualModel->mCharactersPerGlyph;
404 if( SHAPE_TEXT & operations )
406 const Vector<Character>& textToShape = textMirrored ? mirroredUtf32Characters : utf32Characters;
408 ShapeText( textToShape,
413 glyphsToCharactersMap,
414 charactersPerGlyph );
416 // Create the 'number of glyphs' per character and the glyph to character conversion tables.
417 mVisualModel->CreateGlyphsPerCharacterTable( numberOfCharacters );
418 mVisualModel->CreateCharacterToGlyphTable( numberOfCharacters );
421 const Length numberOfGlyphs = glyphs.Count();
423 if( GET_GLYPH_METRICS & operations )
425 mFontClient.GetGlyphMetrics( glyphs.Begin(), numberOfGlyphs );
429 void Controller::Impl::GetDefaultFonts( Vector<FontRun>& fonts, Length numberOfCharacters )
434 fontRun.characterRun.characterIndex = 0;
435 fontRun.characterRun.numberOfCharacters = numberOfCharacters;
436 fontRun.fontId = mFontDefaults->GetFontId( mFontClient );
437 fontRun.isDefault = true;
439 fonts.PushBack( fontRun );
443 float Controller::Impl::GetDefaultFontLineHeight()
445 FontId defaultFontId = 0u;
446 if( NULL == mFontDefaults )
448 defaultFontId = mFontClient.GetFontId( EMPTY_STRING,
453 defaultFontId = mFontDefaults->GetFontId( mFontClient );
456 Text::FontMetrics fontMetrics;
457 mFontClient.GetFontMetrics( defaultFontId, fontMetrics );
459 return( fontMetrics.ascender - fontMetrics.descender );
462 void Controller::Impl::OnCursorKeyEvent( const Event& event )
464 if( NULL == mEventData )
466 // Nothing to do if there is no text input.
470 int keyCode = event.p1.mInt;
472 if( Dali::DALI_KEY_CURSOR_LEFT == keyCode )
474 if( mEventData->mPrimaryCursorPosition > 0u )
476 mEventData->mPrimaryCursorPosition = CalculateNewCursorIndex( mEventData->mPrimaryCursorPosition - 1u );
479 else if( Dali::DALI_KEY_CURSOR_RIGHT == keyCode )
481 if( mLogicalModel->mText.Count() > mEventData->mPrimaryCursorPosition )
483 mEventData->mPrimaryCursorPosition = CalculateNewCursorIndex( mEventData->mPrimaryCursorPosition );
486 else if( Dali::DALI_KEY_CURSOR_UP == keyCode )
490 else if( Dali::DALI_KEY_CURSOR_DOWN == keyCode )
495 mEventData->mUpdateCursorPosition = true;
496 mEventData->mScrollAfterUpdatePosition = true;
499 void Controller::Impl::OnTapEvent( const Event& event )
501 if( NULL != mEventData )
503 const unsigned int tapCount = event.p1.mUint;
507 if( ! IsShowingPlaceholderText() )
509 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
510 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
512 mEventData->mPrimaryCursorPosition = GetClosestCursorIndex( xPosition,
517 mEventData->mPrimaryCursorPosition = 0u;
520 mEventData->mUpdateCursorPosition = true;
521 mEventData->mScrollAfterUpdatePosition = true;
526 void Controller::Impl::OnPanEvent( const Event& event )
528 if( NULL == mEventData )
530 // Nothing to do if there is no text input.
534 int state = event.p1.mInt;
536 if( Gesture::Started == state ||
537 Gesture::Continuing == state )
539 const Vector2& actualSize = mVisualModel->GetActualSize();
540 const Vector2 currentScroll = mEventData->mScrollPosition;
542 if( mEventData->mHorizontalScrollingEnabled )
544 const float displacementX = event.p2.mFloat;
545 mEventData->mScrollPosition.x += displacementX;
547 ClampHorizontalScroll( actualSize );
550 if( mEventData->mVerticalScrollingEnabled )
552 const float displacementY = event.p3.mFloat;
553 mEventData->mScrollPosition.y += displacementY;
555 ClampVerticalScroll( actualSize );
558 if( mEventData->mDecorator )
560 mEventData->mDecorator->UpdatePositions( mEventData->mScrollPosition - currentScroll );
565 void Controller::Impl::OnLongPressEvent( const Event& event )
567 if ( EventData::EDITING == mEventData->mState )
569 ChangeState ( EventData::EDITING_WITH_POPUP );
570 mEventData->mDecoratorUpdated = true;
574 void Controller::Impl::OnHandleEvent( const Event& event )
576 if( NULL == mEventData )
578 // Nothing to do if there is no text input.
582 const unsigned int state = event.p1.mUint;
583 const bool handleStopScrolling = ( HANDLE_STOP_SCROLLING == state );
585 if( HANDLE_PRESSED == state )
587 // The event.p2 and event.p3 are in decorator coords. Need to transforms to text coords.
588 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
589 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
591 const CharacterIndex handleNewPosition = GetClosestCursorIndex( xPosition, yPosition );
593 if( Event::GRAB_HANDLE_EVENT == event.type )
595 ChangeState ( EventData::GRAB_HANDLE_PANNING );
597 if( handleNewPosition != mEventData->mPrimaryCursorPosition )
599 mEventData->mPrimaryCursorPosition = handleNewPosition;
600 mEventData->mUpdateCursorPosition = true;
603 else if( Event::LEFT_SELECTION_HANDLE_EVENT == event.type )
605 ChangeState ( EventData::SELECTION_HANDLE_PANNING );
607 if( ( handleNewPosition != mEventData->mLeftSelectionPosition ) &&
608 ( handleNewPosition != mEventData->mRightSelectionPosition ) )
610 mEventData->mLeftSelectionPosition = handleNewPosition;
612 RepositionSelectionHandles( mEventData->mLeftSelectionPosition,
613 mEventData->mRightSelectionPosition );
615 mEventData->mUpdateLeftSelectionPosition = true;
618 else if( Event::RIGHT_SELECTION_HANDLE_EVENT == event.type )
620 ChangeState ( EventData::SELECTION_HANDLE_PANNING );
622 if( ( handleNewPosition != mEventData->mRightSelectionPosition ) &&
623 ( handleNewPosition != mEventData->mLeftSelectionPosition ) )
625 mEventData->mRightSelectionPosition = handleNewPosition;
627 RepositionSelectionHandles( mEventData->mLeftSelectionPosition,
628 mEventData->mRightSelectionPosition );
630 mEventData->mUpdateRightSelectionPosition = true;
633 } // end ( HANDLE_PRESSED == state )
634 else if( ( HANDLE_RELEASED == state ) ||
635 handleStopScrolling )
637 CharacterIndex handlePosition = 0u;
638 if( handleStopScrolling )
640 // The event.p2 and event.p3 are in decorator coords. Need to transforms to text coords.
641 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
642 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
644 handlePosition = GetClosestCursorIndex( xPosition, yPosition );
647 if( Event::GRAB_HANDLE_EVENT == event.type )
649 mEventData->mUpdateCursorPosition = true;
651 ChangeState( EventData::EDITING_WITH_POPUP );
653 if( handleStopScrolling )
655 mEventData->mScrollAfterUpdatePosition = mEventData->mPrimaryCursorPosition != handlePosition;
656 mEventData->mPrimaryCursorPosition = handlePosition;
659 else if( Event::LEFT_SELECTION_HANDLE_EVENT == event.type )
661 ChangeState( EventData::SELECTING );
663 if( handleStopScrolling )
665 mEventData->mUpdateLeftSelectionPosition = ( mEventData->mRightSelectionPosition != handlePosition );
666 mEventData->mScrollAfterUpdatePosition = mEventData->mUpdateLeftSelectionPosition;
668 if( mEventData->mUpdateLeftSelectionPosition )
670 mEventData->mLeftSelectionPosition = handlePosition;
672 RepositionSelectionHandles( mEventData->mLeftSelectionPosition,
673 mEventData->mRightSelectionPosition );
677 else if( Event::RIGHT_SELECTION_HANDLE_EVENT == event.type )
679 ChangeState( EventData::SELECTING );
681 if( handleStopScrolling )
683 mEventData->mUpdateRightSelectionPosition = ( mEventData->mLeftSelectionPosition != handlePosition );
684 mEventData->mScrollAfterUpdatePosition = mEventData->mUpdateRightSelectionPosition;
685 if( mEventData->mUpdateRightSelectionPosition )
687 mEventData->mRightSelectionPosition = handlePosition;
688 RepositionSelectionHandles( mEventData->mLeftSelectionPosition,
689 mEventData->mRightSelectionPosition );
694 mEventData->mDecoratorUpdated = true;
695 } // end ( ( HANDLE_RELEASED == state ) || ( HANDLE_STOP_SCROLLING == state ) )
696 else if( HANDLE_SCROLLING == state )
698 const float xSpeed = event.p2.mFloat;
699 const Vector2& actualSize = mVisualModel->GetActualSize();
700 const Vector2 currentScrollPosition = mEventData->mScrollPosition;
702 mEventData->mScrollPosition.x += xSpeed;
704 ClampHorizontalScroll( actualSize );
706 bool endOfScroll = false;
707 if( Vector2::ZERO == ( currentScrollPosition - mEventData->mScrollPosition ) )
709 // Notify the decorator there is no more text to scroll.
710 // The decorator won't send more scroll events.
711 mEventData->mDecorator->NotifyEndOfScroll();
712 // Still need to set the position of the handle.
716 // Set the position of the handle.
717 const bool scrollRightDirection = xSpeed > 0.f;
718 const bool leftSelectionHandleEvent = Event::LEFT_SELECTION_HANDLE_EVENT == event.type;
719 const bool rightSelectionHandleEvent = Event::RIGHT_SELECTION_HANDLE_EVENT == event.type;
721 if( Event::GRAB_HANDLE_EVENT == event.type )
723 ChangeState( EventData::GRAB_HANDLE_PANNING );
725 Vector2 position = mEventData->mDecorator->GetPosition( GRAB_HANDLE );
727 // Position the grag handle close to either the left or right edge.
728 position.x = scrollRightDirection ? 0.f : mVisualModel->mControlSize.width;
730 // Get the new handle position.
731 // The grab handle's position is in decorator coords. Need to transforms to text coords.
732 const CharacterIndex handlePosition = GetClosestCursorIndex( position.x - mEventData->mScrollPosition.x - mAlignmentOffset.x,
733 position.y - mEventData->mScrollPosition.y - mAlignmentOffset.y );
735 mEventData->mUpdateCursorPosition = mEventData->mPrimaryCursorPosition != handlePosition;
736 mEventData->mScrollAfterUpdatePosition = mEventData->mUpdateCursorPosition;
737 mEventData->mPrimaryCursorPosition = handlePosition;
739 else if( leftSelectionHandleEvent || rightSelectionHandleEvent )
741 // TODO: This is recalculating the selection box every time the text is scrolled with the selection handles.
742 // Think if something can be done to save power.
744 ChangeState( EventData::SELECTION_HANDLE_PANNING );
746 Vector2 position = mEventData->mDecorator->GetPosition( leftSelectionHandleEvent ? Text::LEFT_SELECTION_HANDLE : Text::RIGHT_SELECTION_HANDLE );
748 // Position the selection handle close to either the left or right edge.
749 position.x = scrollRightDirection ? 0.f : mVisualModel->mControlSize.width;
751 // Get the new handle position.
752 // The selection handle's position is in decorator coords. Need to transforms to text coords.
753 const CharacterIndex handlePosition = GetClosestCursorIndex( position.x - mEventData->mScrollPosition.x - mAlignmentOffset.x,
754 position.y - mEventData->mScrollPosition.y - mAlignmentOffset.y );
756 if( leftSelectionHandleEvent )
758 const bool differentHandles = ( mEventData->mLeftSelectionPosition != handlePosition ) && ( mEventData->mRightSelectionPosition != handlePosition );
759 mEventData->mUpdateLeftSelectionPosition = endOfScroll || differentHandles;
760 if( differentHandles )
762 mEventData->mLeftSelectionPosition = handlePosition;
767 const bool differentHandles = ( mEventData->mRightSelectionPosition != handlePosition ) && ( mEventData->mLeftSelectionPosition != handlePosition );
768 mEventData->mUpdateRightSelectionPosition = endOfScroll || differentHandles;
769 if( differentHandles )
771 mEventData->mRightSelectionPosition = handlePosition;
775 if( mEventData->mUpdateLeftSelectionPosition || mEventData->mUpdateRightSelectionPosition )
777 RepositionSelectionHandles( mEventData->mLeftSelectionPosition,
778 mEventData->mRightSelectionPosition );
780 mEventData->mScrollAfterUpdatePosition = true;
783 mEventData->mDecoratorUpdated = true;
784 } // end ( HANDLE_SCROLLING == state )
787 void Controller::Impl::OnSelectEvent( const Event& event )
789 if( NULL == mEventData )
791 // Nothing to do if there is no text.
795 if( mEventData->mSelectionEnabled )
797 // The event.p2 and event.p3 are in decorator coords. Need to transforms to text coords.
798 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
799 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
801 const CharacterIndex leftPosition = mEventData->mLeftSelectionPosition;
802 const CharacterIndex rightPosition = mEventData->mRightSelectionPosition;
804 RepositionSelectionHandles( xPosition,
807 mEventData->mUpdateLeftSelectionPosition = leftPosition != mEventData->mLeftSelectionPosition;
808 mEventData->mUpdateRightSelectionPosition = rightPosition != mEventData->mRightSelectionPosition;
810 mEventData->mScrollAfterUpdatePosition = ( ( mEventData->mUpdateLeftSelectionPosition || mEventData->mUpdateRightSelectionPosition ) &&
811 ( mEventData->mLeftSelectionPosition != mEventData->mRightSelectionPosition ) );
815 void Controller::Impl::OnSelectAllEvent()
817 if( NULL == mEventData )
819 // Nothing to do if there is no text.
823 if( mEventData->mSelectionEnabled )
825 RepositionSelectionHandles( 0u,
826 mLogicalModel->mText.Count() );
828 mEventData->mScrollAfterUpdatePosition = true;
829 mEventData->mUpdateLeftSelectionPosition = true;
830 mEventData->mUpdateRightSelectionPosition = true;
834 void Controller::Impl::RetrieveSelection( std::string& selectedText, bool deleteAfterRetreival )
836 if( mEventData->mLeftSelectionPosition == mEventData->mRightSelectionPosition )
838 // Nothing to select if handles are in the same place.
843 //Get start and end position of selection
844 uint32_t startOfSelectedText = mEventData->mLeftSelectionPosition;
845 uint32_t lengthOfSelectedText = mEventData->mRightSelectionPosition - startOfSelectedText;
847 // Validate the start and end selection points
848 if( ( startOfSelectedText >= 0 ) && ( ( startOfSelectedText + lengthOfSelectedText ) <= mLogicalModel->mText.Count() ) )
850 //Get text as a UTF8 string
851 Vector<Character>& utf32Characters = mLogicalModel->mText;
853 Utf32ToUtf8( &utf32Characters[startOfSelectedText], lengthOfSelectedText, selectedText );
855 if ( deleteAfterRetreival ) // Only delete text if copied successfully
857 // Delete text between handles
858 Vector<Character>& currentText = mLogicalModel->mText;
860 Vector<Character>::Iterator first = currentText.Begin() + startOfSelectedText;
861 Vector<Character>::Iterator last = first + lengthOfSelectedText;
862 currentText.Erase( first, last );
864 mEventData->mPrimaryCursorPosition = mEventData->mLeftSelectionPosition;
865 mEventData->mScrollAfterDelete = true;
866 mEventData->mDecoratorUpdated = true;
870 void Controller::Impl::ShowClipboard()
874 mClipboard.ShowClipboard();
878 void Controller::Impl::HideClipboard()
882 mClipboard.HideClipboard();
886 bool Controller::Impl::CopyStringToClipboard( std::string& source )
888 //Send string to clipboard
889 return ( mClipboard && mClipboard.SetItem( source ) );
892 void Controller::Impl::SendSelectionToClipboard( bool deleteAfterSending )
894 std::string selectedText;
895 RetrieveSelection( selectedText, deleteAfterSending );
896 CopyStringToClipboard( selectedText );
897 ChangeState( EventData::EDITING );
900 void Controller::Impl::GetTextFromClipboard( unsigned int itemIndex, std::string& retreivedString )
904 retreivedString = mClipboard.GetItem( itemIndex );
908 void Controller::Impl::RepositionSelectionHandles( CharacterIndex selectionStart, CharacterIndex selectionEnd )
910 if( selectionStart == selectionEnd )
912 // Nothing to select if handles are in the same place.
916 mEventData->mDecorator->ClearHighlights();
918 mEventData->mLeftSelectionPosition = selectionStart;
919 mEventData->mRightSelectionPosition = selectionEnd;
921 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
922 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
923 const GlyphInfo* const glyphsBuffer = mVisualModel->mGlyphs.Begin();
924 const Vector2* const positionsBuffer = mVisualModel->mGlyphPositions.Begin();
925 const Length* const charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
926 const CharacterIndex* const glyphToCharacterBuffer = mVisualModel->mGlyphsToCharacters.Begin();
927 const CharacterDirection* const modelCharacterDirectionsBuffer = ( 0u != mLogicalModel->mCharacterDirections.Count() ) ? mLogicalModel->mCharacterDirections.Begin() : NULL;
929 // TODO: Better algorithm to create the highlight box.
932 // Get the height of the line.
933 const Vector<LineRun>& lines = mVisualModel->mLines;
934 const LineRun& firstLine = *lines.Begin();
935 const float height = firstLine.ascender + -firstLine.descender;
937 // Swap the indices if the start is greater than the end.
938 const bool indicesSwapped = ( selectionStart > selectionEnd );
941 std::swap( selectionStart, selectionEnd );
944 // Get the indices to the first and last selected glyphs.
945 const CharacterIndex selectionEndMinusOne = selectionEnd - 1u;
946 const GlyphIndex glyphStart = *( charactersToGlyphBuffer + selectionStart );
947 const Length numberOfGlyphs = *( glyphsPerCharacterBuffer + selectionEndMinusOne );
948 const GlyphIndex glyphEnd = *( charactersToGlyphBuffer + selectionEndMinusOne ) + ( ( numberOfGlyphs > 0 ) ? numberOfGlyphs - 1u : 0u );
950 // Check if the first glyph is a ligature that must be broken like Latin ff, fi, or Arabic ï»», etc which needs special code.
951 const Length numberOfCharactersStart = *( charactersPerGlyphBuffer + glyphStart );
952 bool splitStartGlyph = ( numberOfCharactersStart > 1u ) && HasLigatureMustBreak( mLogicalModel->GetScript( selectionStart ) );
954 // Check if the last glyph is a ligature that must be broken like Latin ff, fi, or Arabic ï»», etc which needs special code.
955 const Length numberOfCharactersEnd = *( charactersPerGlyphBuffer + glyphEnd );
956 bool splitEndGlyph = ( glyphStart != glyphEnd ) && ( numberOfCharactersEnd > 1u ) && HasLigatureMustBreak( mLogicalModel->GetScript( selectionEndMinusOne ) );
958 // Tell the decorator to swap the selection handles if needed.
959 mEventData->mDecorator->SwapSelectionHandlesEnabled( firstLine.direction != indicesSwapped );
961 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
963 // Traverse the glyphs.
964 for( GlyphIndex index = glyphStart; index <= glyphEnd; ++index )
966 const GlyphInfo& glyph = *( glyphsBuffer + index );
967 const Vector2& position = *( positionsBuffer + index );
969 if( splitStartGlyph )
971 // 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.
973 const float glyphAdvance = glyph.advance / static_cast<float>( numberOfCharactersStart );
974 const CharacterIndex interGlyphIndex = selectionStart - *( glyphToCharacterBuffer + glyphStart );
975 // Get the direction of the character.
976 CharacterDirection isCurrentRightToLeft = false;
977 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
979 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + selectionStart );
982 // The end point could be in the middle of the ligature.
983 // Calculate the number of characters selected.
984 const Length numberOfCharacters = ( glyphStart == glyphEnd ) ? ( selectionEnd - selectionStart ) : ( numberOfCharactersStart - interGlyphIndex );
986 const float xPosition = position.x - glyph.xBearing + offset.x + glyphAdvance * static_cast<float>( isCurrentRightToLeft ? ( numberOfCharactersStart - interGlyphIndex - numberOfCharacters ) : interGlyphIndex );
988 mEventData->mDecorator->AddHighlight( xPosition,
990 xPosition + static_cast<float>( numberOfCharacters ) * glyphAdvance,
993 splitStartGlyph = false;
997 if( splitEndGlyph && ( index == glyphEnd ) )
999 // 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.
1001 const float glyphAdvance = glyph.advance / static_cast<float>( numberOfCharactersEnd );
1002 const CharacterIndex interGlyphIndex = selectionEnd - *( glyphToCharacterBuffer + glyphEnd );
1003 // Get the direction of the character.
1004 CharacterDirection isCurrentRightToLeft = false;
1005 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
1007 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + selectionEnd );
1010 const Length numberOfCharacters = numberOfCharactersEnd - interGlyphIndex;
1012 const float xPosition = position.x - glyph.xBearing + offset.x + ( isCurrentRightToLeft ? ( glyphAdvance * static_cast<float>( numberOfCharacters ) ) : 0.f );
1013 mEventData->mDecorator->AddHighlight( xPosition,
1015 xPosition + static_cast<float>( interGlyphIndex ) * glyphAdvance,
1018 splitEndGlyph = false;
1022 const float xPosition = position.x - glyph.xBearing + offset.x;
1023 mEventData->mDecorator->AddHighlight( xPosition, offset.y, xPosition + glyph.advance, height );
1026 CursorInfo primaryCursorInfo;
1027 GetCursorPosition( mEventData->mLeftSelectionPosition,
1028 primaryCursorInfo );
1030 CursorInfo secondaryCursorInfo;
1031 GetCursorPosition( mEventData->mRightSelectionPosition,
1032 secondaryCursorInfo );
1034 const Vector2 primaryPosition = primaryCursorInfo.primaryPosition + offset;
1035 const Vector2 secondaryPosition = secondaryCursorInfo.primaryPosition + offset;
1037 mEventData->mDecorator->SetPosition( LEFT_SELECTION_HANDLE, primaryPosition.x, primaryPosition.y, primaryCursorInfo.lineHeight );
1039 mEventData->mDecorator->SetPosition( RIGHT_SELECTION_HANDLE, secondaryPosition.x, secondaryPosition.y, secondaryCursorInfo.lineHeight );
1041 // Set the flag to update the decorator.
1042 mEventData->mDecoratorUpdated = true;
1045 void Controller::Impl::RepositionSelectionHandles( float visualX, float visualY )
1047 if( NULL == mEventData )
1049 // Nothing to do if there is no text input.
1053 if( IsShowingPlaceholderText() )
1055 // Nothing to do if there is the place-holder text.
1059 const Length numberOfGlyphs = mVisualModel->mGlyphs.Count();
1060 const Length numberOfLines = mVisualModel->mLines.Count();
1061 if( 0 == numberOfGlyphs ||
1062 0 == numberOfLines )
1064 // Nothing to do if there is no text.
1068 // Find which word was selected
1069 CharacterIndex selectionStart( 0 );
1070 CharacterIndex selectionEnd( 0 );
1071 FindSelectionIndices( visualX, visualY, selectionStart, selectionEnd );
1072 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "%p selectionStart %d selectionEnd %d\n", this, selectionStart, selectionEnd );
1074 if( selectionStart == selectionEnd )
1076 ChangeState( EventData::EDITING );
1077 // Nothing to select. i.e. a white space, out of bounds
1081 RepositionSelectionHandles( selectionStart, selectionEnd );
1084 void Controller::Impl::SetPopupButtons()
1087 * Sets the Popup buttons to be shown depending on State.
1089 * If SELECTING : CUT & COPY + ( PASTE & CLIPBOARD if content available to paste )
1091 * If EDITING_WITH_POPUP : SELECT & SELECT_ALL
1094 TextSelectionPopup::Buttons buttonsToShow = TextSelectionPopup::NONE;
1096 if ( ( EventData::SELECTING == mEventData->mState ) || ( EventData::SELECTION_CHANGED == mEventData->mState ) )
1098 buttonsToShow = TextSelectionPopup::Buttons( TextSelectionPopup::CUT | TextSelectionPopup::COPY );
1100 if ( !IsClipboardEmpty() )
1102 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
1103 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::CLIPBOARD ) );
1106 if ( !mEventData->mAllTextSelected )
1108 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::SELECT_ALL ) );
1111 else if ( EventData::EDITING_WITH_POPUP == mEventData->mState )
1113 if ( mLogicalModel->mText.Count() && !IsShowingPlaceholderText())
1115 buttonsToShow = TextSelectionPopup::Buttons( TextSelectionPopup::SELECT | TextSelectionPopup::SELECT_ALL );
1118 if ( !IsClipboardEmpty() )
1120 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
1121 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::CLIPBOARD ) );
1125 mEventData->mDecorator->SetEnabledPopupButtons( buttonsToShow );
1128 void Controller::Impl::ChangeState( EventData::State newState )
1130 if( NULL == mEventData )
1132 // Nothing to do if there is no text input.
1136 if( mEventData->mState != newState )
1138 mEventData->mState = newState;
1140 if( EventData::INACTIVE == mEventData->mState )
1142 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
1143 mEventData->mDecorator->StopCursorBlink();
1144 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1145 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1146 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1147 mEventData->mDecorator->SetPopupActive( false );
1148 mEventData->mDecoratorUpdated = true;
1151 else if ( EventData::INTERRUPTED == mEventData->mState)
1153 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1154 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1155 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1156 mEventData->mDecorator->SetPopupActive( false );
1157 mEventData->mDecoratorUpdated = true;
1160 else if ( EventData::SELECTING == mEventData->mState )
1162 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
1163 mEventData->mDecorator->StopCursorBlink();
1164 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1165 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true );
1166 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true );
1167 if( mEventData->mGrabHandlePopupEnabled )
1170 mEventData->mDecorator->SetPopupActive( true );
1172 mEventData->mDecoratorUpdated = true;
1174 else if ( EventData::SELECTION_CHANGED == mEventData->mState )
1176 if( mEventData->mGrabHandlePopupEnabled )
1179 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
1180 mEventData->mDecorator->SetPopupActive( true );
1182 mEventData->mDecoratorUpdated = true;
1184 else if( EventData::EDITING == mEventData->mState )
1186 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1187 if( mEventData->mCursorBlinkEnabled )
1189 mEventData->mDecorator->StartCursorBlink();
1191 // Grab handle is not shown until a tap is received whilst EDITING
1192 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1193 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1194 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1195 if( mEventData->mGrabHandlePopupEnabled )
1197 mEventData->mDecorator->SetPopupActive( false );
1199 mEventData->mDecoratorUpdated = true;
1202 else if( EventData::EDITING_WITH_POPUP == mEventData->mState )
1204 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1205 if( mEventData->mCursorBlinkEnabled )
1207 mEventData->mDecorator->StartCursorBlink();
1209 if( mEventData->mSelectionEnabled )
1211 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1212 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1216 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
1218 if( mEventData->mGrabHandlePopupEnabled )
1221 mEventData->mDecorator->SetPopupActive( true );
1224 mEventData->mDecoratorUpdated = true;
1226 else if( EventData::EDITING_WITH_GRAB_HANDLE == mEventData->mState )
1228 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1229 if( mEventData->mCursorBlinkEnabled )
1231 mEventData->mDecorator->StartCursorBlink();
1233 // Grab handle is not shown until a tap is received whilst EDITING
1234 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
1235 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1236 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1237 if( mEventData->mGrabHandlePopupEnabled )
1239 mEventData->mDecorator->SetPopupActive( false );
1241 mEventData->mDecoratorUpdated = true;
1244 else if ( EventData::SELECTION_HANDLE_PANNING == mEventData->mState )
1246 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
1247 mEventData->mDecorator->StopCursorBlink();
1248 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1249 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true );
1250 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true );
1251 if( mEventData->mGrabHandlePopupEnabled )
1253 mEventData->mDecorator->SetPopupActive( false );
1255 mEventData->mDecoratorUpdated = true;
1257 else if ( EventData::GRAB_HANDLE_PANNING == mEventData->mState )
1259 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1260 if( mEventData->mCursorBlinkEnabled )
1262 mEventData->mDecorator->StartCursorBlink();
1264 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
1265 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1266 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1267 if( mEventData->mGrabHandlePopupEnabled )
1269 mEventData->mDecorator->SetPopupActive( false );
1271 mEventData->mDecoratorUpdated = true;
1276 LineIndex Controller::Impl::GetClosestLine( float y ) const
1278 float totalHeight = 0.f;
1279 LineIndex lineIndex = 0u;
1281 const Vector<LineRun>& lines = mVisualModel->mLines;
1282 for( LineIndex endLine = lines.Count();
1283 lineIndex < endLine;
1286 const LineRun& lineRun = lines[lineIndex];
1287 totalHeight += lineRun.ascender + -lineRun.descender;
1288 if( y < totalHeight )
1294 if( lineIndex == 0 )
1302 void Controller::Impl::FindSelectionIndices( float visualX, float visualY, CharacterIndex& startIndex, CharacterIndex& endIndex )
1304 CharacterIndex hitCharacter = GetClosestCursorIndex( visualX, visualY );
1305 if( hitCharacter >= mLogicalModel->mText.Count() )
1307 // Selection out of bounds.
1311 startIndex = hitCharacter;
1312 endIndex = hitCharacter;
1314 if( !TextAbstraction::IsWhiteSpace( mLogicalModel->mText[hitCharacter] ) )
1316 // Find the start and end of the text
1317 for( startIndex = hitCharacter; startIndex > 0; --startIndex )
1319 Character charCode = mLogicalModel->mText[ startIndex-1 ];
1320 if( TextAbstraction::IsWhiteSpace( charCode ) )
1325 const CharacterIndex pastTheEnd = mLogicalModel->mText.Count();
1326 for( endIndex = hitCharacter + 1u; endIndex < pastTheEnd; ++endIndex )
1328 Character charCode = mLogicalModel->mText[ endIndex ];
1329 if( TextAbstraction::IsWhiteSpace( charCode ) )
1337 CharacterIndex Controller::Impl::GetClosestCursorIndex( float visualX,
1340 if( NULL == mEventData )
1342 // Nothing to do if there is no text input.
1346 CharacterIndex logicalIndex = 0u;
1348 const Length numberOfGlyphs = mVisualModel->mGlyphs.Count();
1349 const Length numberOfLines = mVisualModel->mLines.Count();
1350 if( 0 == numberOfGlyphs ||
1351 0 == numberOfLines )
1353 return logicalIndex;
1356 // Find which line is closest
1357 const LineIndex lineIndex = GetClosestLine( visualY );
1358 const LineRun& line = mVisualModel->mLines[lineIndex];
1360 // Get the positions of the glyphs.
1361 const Vector<Vector2>& positions = mVisualModel->mGlyphPositions;
1362 const Vector2* const positionsBuffer = positions.Begin();
1364 // Get the visual to logical conversion tables.
1365 const CharacterIndex* const visualToLogicalBuffer = ( 0u != mLogicalModel->mVisualToLogicalMap.Count() ) ? mLogicalModel->mVisualToLogicalMap.Begin() : NULL;
1366 const CharacterIndex* const visualToLogicalCursorBuffer = mLogicalModel->mVisualToLogicalCursorMap.Begin();
1368 // Get the character to glyph conversion table.
1369 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
1371 // Get the glyphs per character table.
1372 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
1373 const Length* const charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
1375 // If the vector is void, there is no right to left characters.
1376 const bool hasRightToLeftCharacters = NULL != visualToLogicalBuffer;
1378 const CharacterIndex startCharacter = line.characterRun.characterIndex;
1379 const CharacterIndex endCharacter = line.characterRun.characterIndex + line.characterRun.numberOfCharacters;
1380 DALI_ASSERT_DEBUG( endCharacter <= mLogicalModel->mText.Count() && "Invalid line info" );
1382 // Whether there is a hit on a glyph.
1383 bool matched = false;
1385 // Traverses glyphs in visual order. To do that use the visual to logical conversion table.
1386 CharacterIndex visualIndex = startCharacter;
1387 for( ; !matched && ( visualIndex < endCharacter ); ++visualIndex )
1389 // The character in logical order.
1390 const CharacterIndex characterLogicalOrderIndex = hasRightToLeftCharacters ? *( visualToLogicalBuffer + visualIndex ) : visualIndex;
1392 // Get the script of the character.
1393 const Script script = mLogicalModel->GetScript( characterLogicalOrderIndex );
1395 // The first glyph for that character in logical order.
1396 const GlyphIndex glyphLogicalOrderIndex = *( charactersToGlyphBuffer + characterLogicalOrderIndex );
1397 // The number of glyphs for that character
1398 const Length numberOfGlyphs = *( glyphsPerCharacterBuffer + characterLogicalOrderIndex );
1400 // Get the metrics for the group of glyphs.
1401 GlyphMetrics glyphMetrics;
1402 GetGlyphsMetrics( glyphLogicalOrderIndex,
1408 const Vector2& position = *( positionsBuffer + glyphLogicalOrderIndex );
1410 // Prevents to jump the whole Latin ligatures like fi, ff, or Arabic ï»»...
1411 const Length numberOfCharactersInLigature = HasLigatureMustBreak( script ) ? *( charactersPerGlyphBuffer + glyphLogicalOrderIndex ) : 1u;
1412 const float glyphAdvance = glyphMetrics.advance / static_cast<float>( numberOfCharactersInLigature );
1414 for( GlyphIndex index = 0u; !matched && ( index < numberOfCharactersInLigature ); ++index )
1416 // Find the mid-point of the area containing the glyph
1417 const float glyphCenter = -glyphMetrics.xBearing + position.x + ( static_cast<float>( index ) + 0.5f ) * glyphAdvance;
1419 if( visualX < glyphCenter )
1421 visualIndex += index;
1433 // Return the logical position of the cursor in characters.
1437 visualIndex = endCharacter;
1440 logicalIndex = hasRightToLeftCharacters ? *( visualToLogicalCursorBuffer + visualIndex ) : visualIndex;
1441 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "%p closest visualIndex %d logicalIndex %d\n", this, visualIndex, logicalIndex );
1443 return logicalIndex;
1446 void Controller::Impl::GetCursorPosition( CharacterIndex logical,
1447 CursorInfo& cursorInfo )
1449 // TODO: Check for multiline with \n, etc...
1451 // Check if the logical position is the first or the last one of the text.
1452 const bool isFirstPosition = 0u == logical;
1453 const bool isLastPosition = mLogicalModel->mText.Count() == logical;
1455 if( isFirstPosition && isLastPosition )
1457 // There is zero characters. Get the default font's line height.
1458 cursorInfo.lineHeight = GetDefaultFontLineHeight();
1459 cursorInfo.primaryCursorHeight = cursorInfo.lineHeight;
1461 cursorInfo.primaryPosition.x = 1.f;
1462 cursorInfo.primaryPosition.y = 0.f;
1464 // Nothing else to do.
1468 // 'logical' is the logical 'cursor' index.
1469 // Get the next and current logical 'character' index.
1470 const CharacterIndex nextCharacterIndex = logical;
1471 const CharacterIndex characterIndex = isFirstPosition ? logical : logical - 1u;
1473 // Get the direction of the character and the next one.
1474 const CharacterDirection* const modelCharacterDirectionsBuffer = ( 0u != mLogicalModel->mCharacterDirections.Count() ) ? mLogicalModel->mCharacterDirections.Begin() : NULL;
1476 CharacterDirection isCurrentRightToLeft = false;
1477 CharacterDirection isNextRightToLeft = false;
1478 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
1480 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + characterIndex );
1481 isNextRightToLeft = *( modelCharacterDirectionsBuffer + nextCharacterIndex );
1484 // Get the line where the character is laid-out.
1485 const LineRun* const modelLines = mVisualModel->mLines.Begin();
1487 const LineIndex lineIndex = mVisualModel->GetLineOfCharacter( characterIndex );
1488 const LineRun& line = *( modelLines + lineIndex );
1490 // Get the paragraph's direction.
1491 const CharacterDirection isRightToLeftParagraph = line.direction;
1493 // Check whether there is an alternative position:
1495 cursorInfo.isSecondaryCursor = ( !isLastPosition && ( isCurrentRightToLeft != isNextRightToLeft ) ) ||
1496 ( isLastPosition && ( isRightToLeftParagraph != isCurrentRightToLeft ) );
1498 // Set the line height.
1499 cursorInfo.lineHeight = line.ascender + -line.descender;
1501 // Calculate the primary cursor.
1503 CharacterIndex index = characterIndex;
1504 if( cursorInfo.isSecondaryCursor )
1506 // If there is a secondary position, the primary cursor may be in a different place than the logical index.
1508 if( isLastPosition )
1510 // The position of the cursor after the last character needs special
1511 // care depending on its direction and the direction of the paragraph.
1513 // Need to find the first character after the last character with the paragraph's direction.
1514 // i.e l0 l1 l2 r0 r1 should find r0.
1516 // TODO: check for more than one line!
1517 index = isRightToLeftParagraph ? line.characterRun.characterIndex : line.characterRun.characterIndex + line.characterRun.numberOfCharacters - 1u;
1518 index = mLogicalModel->GetLogicalCharacterIndex( index );
1522 index = ( isRightToLeftParagraph == isCurrentRightToLeft ) ? characterIndex : nextCharacterIndex;
1526 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
1527 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
1528 const Length* const charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
1529 const CharacterIndex* const glyphsToCharactersBuffer = mVisualModel->mGlyphsToCharacters.Begin();
1530 const Vector2* const glyphPositionsBuffer = mVisualModel->mGlyphPositions.Begin();
1532 // Convert the cursor position into the glyph position.
1533 const GlyphIndex primaryGlyphIndex = *( charactersToGlyphBuffer + index );
1534 const Length primaryNumberOfGlyphs = *( glyphsPerCharacterBuffer + index );
1535 const Length primaryNumberOfCharacters = *( charactersPerGlyphBuffer + primaryGlyphIndex );
1537 // Get the metrics for the group of glyphs.
1538 GlyphMetrics glyphMetrics;
1539 GetGlyphsMetrics( primaryGlyphIndex,
1540 primaryNumberOfGlyphs,
1545 // Whether to add the glyph's advance to the cursor position.
1546 // 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,
1547 // if the logical cursor is one, the position is the position of the first glyph and the advance is added.
1548 // A 'truth table' was build and an online Karnaugh map tool was used to simplify the logic.
1569 // Where F -> isFirstPosition
1570 // L -> isLastPosition
1571 // C -> isCurrentRightToLeft
1572 // P -> isRightToLeftParagraph
1573 // A -> Whether to add the glyph's advance.
1575 const bool addGlyphAdvance = ( ( isLastPosition && !isRightToLeftParagraph ) ||
1576 ( isFirstPosition && isRightToLeftParagraph ) ||
1577 ( !isFirstPosition && !isLastPosition && !isCurrentRightToLeft ) );
1579 float glyphAdvance = addGlyphAdvance ? glyphMetrics.advance : 0.f;
1581 if( !isLastPosition &&
1582 ( primaryNumberOfCharacters > 1u ) )
1584 const CharacterIndex firstIndex = *( glyphsToCharactersBuffer + primaryGlyphIndex );
1586 bool isCurrentRightToLeft = false;
1587 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
1589 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + index );
1592 Length numberOfGlyphAdvance = ( isFirstPosition ? 0u : 1u ) + characterIndex - firstIndex;
1593 if( isCurrentRightToLeft )
1595 numberOfGlyphAdvance = primaryNumberOfCharacters - numberOfGlyphAdvance;
1598 glyphAdvance = static_cast<float>( numberOfGlyphAdvance ) * glyphMetrics.advance / static_cast<float>( primaryNumberOfCharacters );
1601 // Get the glyph position and x bearing.
1602 const Vector2& primaryPosition = *( glyphPositionsBuffer + primaryGlyphIndex );
1604 // Set the primary cursor's height.
1605 cursorInfo.primaryCursorHeight = cursorInfo.isSecondaryCursor ? 0.5f * glyphMetrics.fontHeight : glyphMetrics.fontHeight;
1607 // Set the primary cursor's position.
1608 cursorInfo.primaryPosition.x = -glyphMetrics.xBearing + primaryPosition.x + glyphAdvance;
1609 cursorInfo.primaryPosition.y = line.ascender - glyphMetrics.ascender;
1611 // Calculate the secondary cursor.
1613 if( cursorInfo.isSecondaryCursor )
1615 // Set the secondary cursor's height.
1616 cursorInfo.secondaryCursorHeight = 0.5f * glyphMetrics.fontHeight;
1618 CharacterIndex index = characterIndex;
1619 if( !isLastPosition )
1621 index = ( isRightToLeftParagraph == isCurrentRightToLeft ) ? nextCharacterIndex : characterIndex;
1624 const GlyphIndex secondaryGlyphIndex = *( charactersToGlyphBuffer + index );
1625 const Length secondaryNumberOfGlyphs = *( glyphsPerCharacterBuffer + index );
1627 const Vector2& secondaryPosition = *( glyphPositionsBuffer + secondaryGlyphIndex );
1629 GetGlyphsMetrics( secondaryGlyphIndex,
1630 secondaryNumberOfGlyphs,
1635 // Set the secondary cursor's position.
1636 cursorInfo.secondaryPosition.x = -glyphMetrics.xBearing + secondaryPosition.x + ( isCurrentRightToLeft ? 0.f : glyphMetrics.advance );
1637 cursorInfo.secondaryPosition.y = cursorInfo.lineHeight - cursorInfo.secondaryCursorHeight - line.descender - ( glyphMetrics.fontHeight - glyphMetrics.ascender );
1641 CharacterIndex Controller::Impl::CalculateNewCursorIndex( CharacterIndex index ) const
1643 if( NULL == mEventData )
1645 // Nothing to do if there is no text input.
1649 CharacterIndex cursorIndex = mEventData->mPrimaryCursorPosition;
1651 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
1652 const Length* const charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
1654 GlyphIndex glyphIndex = *( charactersToGlyphBuffer + index );
1655 Length numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
1657 if( numberOfCharacters > 1u )
1659 const Script script = mLogicalModel->GetScript( index );
1660 if( HasLigatureMustBreak( script ) )
1662 // Prevents to jump the whole Latin ligatures like fi, ff, or Arabic ï»», ...
1663 numberOfCharacters = 1u;
1668 while( 0u == numberOfCharacters )
1671 numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
1675 if( index < mEventData->mPrimaryCursorPosition )
1677 cursorIndex -= numberOfCharacters;
1681 cursorIndex += numberOfCharacters;
1687 void Controller::Impl::UpdateCursorPosition()
1689 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "-->Controller::UpdateCursorPosition %p\n", this );
1690 if( NULL == mEventData )
1692 // Nothing to do if there is no text input.
1693 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition no event data\n" );
1697 if( IsShowingPlaceholderText() )
1699 // Do not want to use the place-holder text to set the cursor position.
1701 // Use the line's height of the font's family set to set the cursor's size.
1702 // If there is no font's family set, use the default font.
1703 // Use the current alignment to place the cursor at the beginning, center or end of the box.
1705 float lineHeight = 0.f;
1707 FontId defaultFontId = 0u;
1708 if( NULL == mFontDefaults )
1710 defaultFontId = mFontClient.GetFontId( EMPTY_STRING,
1715 defaultFontId = mFontDefaults->GetFontId( mFontClient );
1718 Text::FontMetrics fontMetrics;
1719 mFontClient.GetFontMetrics( defaultFontId, fontMetrics );
1721 lineHeight = fontMetrics.ascender - fontMetrics.descender;
1724 Vector2 cursorPosition;
1726 switch( mLayoutEngine.GetHorizontalAlignment() )
1728 case LayoutEngine::HORIZONTAL_ALIGN_BEGIN:
1730 cursorPosition.x = 1.f;
1733 case LayoutEngine::HORIZONTAL_ALIGN_CENTER:
1735 cursorPosition.x = floor( 0.5f * mVisualModel->mControlSize.width );
1738 case LayoutEngine::HORIZONTAL_ALIGN_END:
1740 cursorPosition.x = mVisualModel->mControlSize.width;
1745 switch( mLayoutEngine.GetVerticalAlignment() )
1747 case LayoutEngine::VERTICAL_ALIGN_TOP:
1749 cursorPosition.y = 0.f;
1752 case LayoutEngine::VERTICAL_ALIGN_CENTER:
1754 cursorPosition.y = floorf( 0.5f * ( mVisualModel->mControlSize.height - lineHeight ) );
1757 case LayoutEngine::VERTICAL_ALIGN_BOTTOM:
1759 cursorPosition.y = mVisualModel->mControlSize.height - lineHeight;
1764 mEventData->mDecorator->SetPosition( PRIMARY_CURSOR,
1772 CursorInfo cursorInfo;
1773 GetCursorPosition( mEventData->mPrimaryCursorPosition,
1776 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
1777 const Vector2 cursorPosition = cursorInfo.primaryPosition + offset;
1779 // Sets the cursor position.
1780 mEventData->mDecorator->SetPosition( PRIMARY_CURSOR,
1783 cursorInfo.primaryCursorHeight,
1784 cursorInfo.lineHeight );
1785 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Primary cursor position: %f,%f\n", cursorPosition.x, cursorPosition.y );
1787 // Sets the grab handle position.
1788 mEventData->mDecorator->SetPosition( GRAB_HANDLE,
1791 cursorInfo.lineHeight );
1793 if( cursorInfo.isSecondaryCursor )
1795 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_BOTH );
1796 mEventData->mDecorator->SetPosition( SECONDARY_CURSOR,
1797 cursorInfo.secondaryPosition.x + offset.x,
1798 cursorInfo.secondaryPosition.y + offset.y,
1799 cursorInfo.secondaryCursorHeight,
1800 cursorInfo.lineHeight );
1801 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Secondary cursor position: %f,%f\n", cursorInfo.secondaryPosition.x + offset.x, cursorInfo.secondaryPosition.y + offset.y );
1805 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1808 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition\n" );
1811 void Controller::Impl::UpdateSelectionHandle( HandleType handleType )
1813 if( ( LEFT_SELECTION_HANDLE != handleType ) &&
1814 ( RIGHT_SELECTION_HANDLE != handleType ) )
1819 const bool leftSelectionHandle = LEFT_SELECTION_HANDLE == handleType;
1820 const CharacterIndex index = leftSelectionHandle ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition;
1822 CursorInfo cursorInfo;
1823 GetCursorPosition( index,
1826 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
1827 const Vector2 cursorPosition = cursorInfo.primaryPosition + offset;
1829 // Sets the grab handle position.
1830 mEventData->mDecorator->SetPosition( handleType,
1833 cursorInfo.lineHeight );
1835 // If selection handle at start of the text and other at end of the text then all text is selected.
1836 const CharacterIndex startOfSelection = std::min( mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition );
1837 const CharacterIndex endOfSelection = std::max ( mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition );
1838 mEventData->mAllTextSelected = ( startOfSelection == 0 ) && ( endOfSelection == mLogicalModel->mText.Count() );
1841 void Controller::Impl::ClampHorizontalScroll( const Vector2& actualSize )
1843 // Clamp between -space & 0 (and the text alignment).
1844 if( actualSize.width > mVisualModel->mControlSize.width )
1846 const float space = ( actualSize.width - mVisualModel->mControlSize.width ) + mAlignmentOffset.x;
1847 mEventData->mScrollPosition.x = ( mEventData->mScrollPosition.x < -space ) ? -space : mEventData->mScrollPosition.x;
1848 mEventData->mScrollPosition.x = ( mEventData->mScrollPosition.x > -mAlignmentOffset.x ) ? -mAlignmentOffset.x : mEventData->mScrollPosition.x;
1850 mEventData->mDecoratorUpdated = true;
1854 mEventData->mScrollPosition.x = 0.f;
1858 void Controller::Impl::ClampVerticalScroll( const Vector2& actualSize )
1860 // Clamp between -space & 0 (and the text alignment).
1861 if( actualSize.height > mVisualModel->mControlSize.height )
1863 const float space = ( actualSize.height - mVisualModel->mControlSize.height ) + mAlignmentOffset.y;
1864 mEventData->mScrollPosition.y = ( mEventData->mScrollPosition.y < -space ) ? -space : mEventData->mScrollPosition.y;
1865 mEventData->mScrollPosition.y = ( mEventData->mScrollPosition.y > -mAlignmentOffset.y ) ? -mAlignmentOffset.y : mEventData->mScrollPosition.y;
1867 mEventData->mDecoratorUpdated = true;
1871 mEventData->mScrollPosition.y = 0.f;
1875 void Controller::Impl::ScrollToMakePositionVisible( const Vector2& position )
1878 bool updateDecorator = false;
1879 if( position.x < 0.f )
1881 offset.x = -position.x;
1882 mEventData->mScrollPosition.x += offset.x;
1883 updateDecorator = true;
1885 else if( position.x > mVisualModel->mControlSize.width )
1887 offset.x = mVisualModel->mControlSize.width - position.x;
1888 mEventData->mScrollPosition.x += offset.x;
1889 updateDecorator = true;
1892 if( updateDecorator && mEventData->mDecorator )
1894 mEventData->mDecorator->UpdatePositions( offset );
1897 // TODO : calculate the vertical scroll.
1900 void Controller::Impl::ScrollTextToMatchCursor()
1902 // Get the current cursor position in decorator coords.
1903 const Vector2& currentCursorPosition = mEventData->mDecorator->GetPosition( PRIMARY_CURSOR );
1905 // Calculate the new cursor position.
1906 CursorInfo cursorInfo;
1907 GetCursorPosition( mEventData->mPrimaryCursorPosition,
1910 // Calculate the offset to match the cursor position before the character was deleted.
1911 mEventData->mScrollPosition.x = currentCursorPosition.x - cursorInfo.primaryPosition.x - mAlignmentOffset.x;
1913 ClampHorizontalScroll( mVisualModel->GetActualSize() );
1915 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
1916 const Vector2 cursorPosition = cursorInfo.primaryPosition + offset;
1918 // Sets the cursor position.
1919 mEventData->mDecorator->SetPosition( PRIMARY_CURSOR,
1922 cursorInfo.primaryCursorHeight,
1923 cursorInfo.lineHeight );
1925 // Sets the grab handle position.
1926 mEventData->mDecorator->SetPosition( GRAB_HANDLE,
1929 cursorInfo.lineHeight );
1931 if( cursorInfo.isSecondaryCursor )
1933 mEventData->mDecorator->SetPosition( SECONDARY_CURSOR,
1934 cursorInfo.secondaryPosition.x + offset.x,
1935 cursorInfo.secondaryPosition.y + offset.y,
1936 cursorInfo.secondaryCursorHeight,
1937 cursorInfo.lineHeight );
1938 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Secondary cursor position: %f,%f\n", cursorInfo.secondaryPosition.x + offset.x, cursorInfo.secondaryPosition.y + offset.y );
1941 // Set which cursors are active according the state.
1942 if( ( EventData::EDITING == mEventData->mState ) ||
1943 ( EventData::EDITING_WITH_POPUP == mEventData->mState ) ||
1944 ( EventData::GRAB_HANDLE_PANNING == mEventData->mState ) )
1946 if( cursorInfo.isSecondaryCursor )
1948 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_BOTH );
1952 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1957 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
1961 void Controller::Impl::RequestRelayout()
1963 mControlInterface.RequestTextRelayout();
1968 } // namespace Toolkit