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/multi-language-support.h>
29 #include <dali-toolkit/internal/text/segmentation.h>
30 #include <dali-toolkit/internal/text/shaper.h>
35 #if defined(DEBUG_ENABLED)
36 Debug::Filter* gLogFilter = Debug::Filter::New(Debug::Concise, true, "LOG_TEXT_CONTROLS");
40 * @brief Some characters can be shaped in more than one glyph.
41 * This struct is used to retrieve metrics from these group of glyphs.
55 float fontHeight; ///< The font's height of that glyphs.
56 float advance; ///< The sum of all the advances of all the glyphs.
57 float ascender; ///< The font's ascender.
58 float xBearing; ///< The x bearing of the first glyph.
61 const std::string EMPTY_STRING("");
75 * @brief Get some glyph's metrics of a group of glyphs formed as a result of shaping one character.
77 * @param[in] glyphIndex The index to the first glyph.
78 * @param[in] numberOfGlyphs The number of glyphs.
79 * @param[out] glyphMetrics Some glyph metrics (font height, advance, ascender and x bearing).
80 * @param[in] visualModel The visual model.
81 * @param[in] fontClient The font client.
83 void GetGlyphsMetrics( GlyphIndex glyphIndex,
84 Length numberOfGlyphs,
85 GlyphMetrics& glyphMetrics,
86 VisualModelPtr visualModel,
87 TextAbstraction::FontClient& fontClient )
89 const GlyphInfo* glyphsBuffer = visualModel->mGlyphs.Begin();
91 const GlyphInfo& firstGlyph = *( glyphsBuffer + glyphIndex );
93 Text::FontMetrics fontMetrics;
94 fontClient.GetFontMetrics( firstGlyph.fontId, fontMetrics );
96 glyphMetrics.fontHeight = fontMetrics.height;
97 glyphMetrics.advance = firstGlyph.advance;
98 glyphMetrics.ascender = fontMetrics.ascender;
99 glyphMetrics.xBearing = firstGlyph.xBearing;
101 for( unsigned int i = 1u; i < numberOfGlyphs; ++i )
103 const GlyphInfo& glyphInfo = *( glyphsBuffer + glyphIndex + i );
105 glyphMetrics.advance += glyphInfo.advance;
109 EventData::EventData( DecoratorPtr decorator )
110 : mDecorator( decorator ),
111 mPlaceholderTextActive(),
112 mPlaceholderTextInactive(),
113 mPlaceholderTextColor( 0.8f, 0.8f, 0.8f, 0.8f ),
117 mPrimaryCursorPosition( 0u ),
118 mLeftSelectionPosition( 0u ),
119 mRightSelectionPosition( 0u ),
120 mPreEditStartPosition( 0u ),
121 mPreEditLength( 0u ),
122 mIsShowingPlaceholderText( false ),
123 mPreEditFlag( false ),
124 mDecoratorUpdated( false ),
125 mCursorBlinkEnabled( true ),
126 mGrabHandleEnabled( true ),
127 mGrabHandlePopupEnabled( true ),
128 mSelectionEnabled( true ),
129 mHorizontalScrollingEnabled( true ),
130 mVerticalScrollingEnabled( false ),
131 mUpdateCursorPosition( false ),
132 mUpdateLeftSelectionPosition( false ),
133 mUpdateRightSelectionPosition( false ),
134 mScrollAfterUpdatePosition( false ),
135 mScrollAfterDelete( false ),
136 mAllTextSelected( false )
139 EventData::~EventData()
142 bool Controller::Impl::ProcessInputEvents()
144 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "-->Controller::ProcessInputEvents\n" );
145 if( NULL == mEventData )
147 // Nothing to do if there is no text input.
148 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::ProcessInputEvents no event data\n" );
152 if( mEventData->mDecorator )
154 for( std::vector<Event>::iterator iter = mEventData->mEventQueue.begin();
155 iter != mEventData->mEventQueue.end();
160 case Event::CURSOR_KEY_EVENT:
162 OnCursorKeyEvent( *iter );
165 case Event::TAP_EVENT:
170 case Event::LONG_PRESS_EVENT:
172 OnLongPressEvent( *iter );
175 case Event::PAN_EVENT:
180 case Event::GRAB_HANDLE_EVENT:
181 case Event::LEFT_SELECTION_HANDLE_EVENT:
182 case Event::RIGHT_SELECTION_HANDLE_EVENT: // Fall through
184 OnHandleEvent( *iter );
189 OnSelectEvent( *iter );
192 case Event::SELECT_ALL:
201 // The cursor must also be repositioned after inserts into the model
202 if( mEventData->mUpdateCursorPosition )
204 // Updates the cursor position and scrolls the text to make it visible.
206 UpdateCursorPosition();
208 if( mEventData->mScrollAfterUpdatePosition )
210 const Vector2& primaryCursorPosition = mEventData->mDecorator->GetPosition( PRIMARY_CURSOR );
212 ScrollToMakePositionVisible( primaryCursorPosition );
213 mEventData->mScrollAfterUpdatePosition = false;
216 mEventData->mDecoratorUpdated = true;
217 mEventData->mUpdateCursorPosition = false;
219 else if( mEventData->mScrollAfterDelete )
221 ScrollTextToMatchCursor();
222 mEventData->mDecoratorUpdated = true;
223 mEventData->mScrollAfterDelete = false;
227 bool leftScroll = false;
228 bool rightScroll = false;
230 if( mEventData->mUpdateLeftSelectionPosition )
232 UpdateSelectionHandle( LEFT_SELECTION_HANDLE );
234 if( mEventData->mScrollAfterUpdatePosition )
236 const Vector2& leftHandlePosition = mEventData->mDecorator->GetPosition( LEFT_SELECTION_HANDLE );
238 ScrollToMakePositionVisible( leftHandlePosition );
243 mEventData->mDecoratorUpdated = true;
244 mEventData->mUpdateLeftSelectionPosition = false;
247 if( mEventData->mUpdateRightSelectionPosition )
249 UpdateSelectionHandle( RIGHT_SELECTION_HANDLE );
251 if( mEventData->mScrollAfterUpdatePosition )
253 const Vector2& rightHandlePosition = mEventData->mDecorator->GetPosition( RIGHT_SELECTION_HANDLE );
255 ScrollToMakePositionVisible( rightHandlePosition );
260 mEventData->mDecoratorUpdated = true;
261 mEventData->mUpdateRightSelectionPosition = false;
264 if( leftScroll || rightScroll )
266 mEventData->mScrollAfterUpdatePosition = false;
270 mEventData->mEventQueue.clear();
272 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::ProcessInputEvents\n" );
274 const bool decoratorUpdated = mEventData->mDecoratorUpdated;
275 mEventData->mDecoratorUpdated = false;
277 return decoratorUpdated;
280 void Controller::Impl::UpdateModel( OperationsMask operationsRequired )
282 // Calculate the operations to be done.
283 const OperationsMask operations = static_cast<OperationsMask>( mOperationsPending & operationsRequired );
285 Vector<Character>& utf32Characters = mLogicalModel->mText;
287 const Length numberOfCharacters = utf32Characters.Count();
289 Vector<LineBreakInfo>& lineBreakInfo = mLogicalModel->mLineBreakInfo;
290 if( GET_LINE_BREAKS & operations )
292 // Retrieves the line break info. The line break info is used to split the text in 'paragraphs' to
293 // calculate the bidirectional info for each 'paragraph'.
294 // It's also used to layout the text (where it should be a new line) or to shape the text (text in different lines
295 // is not shaped together).
296 lineBreakInfo.Resize( numberOfCharacters, TextAbstraction::LINE_NO_BREAK );
298 SetLineBreakInfo( utf32Characters,
302 Vector<WordBreakInfo>& wordBreakInfo = mLogicalModel->mWordBreakInfo;
303 if( GET_WORD_BREAKS & operations )
305 // Retrieves the word break info. The word break info is used to layout the text (where to wrap the text in lines).
306 wordBreakInfo.Resize( numberOfCharacters, TextAbstraction::WORD_NO_BREAK );
308 SetWordBreakInfo( utf32Characters,
312 const bool getScripts = GET_SCRIPTS & operations;
313 const bool validateFonts = VALIDATE_FONTS & operations;
315 Vector<ScriptRun>& scripts = mLogicalModel->mScriptRuns;
316 Vector<FontRun>& validFonts = mLogicalModel->mFontRuns;
318 if( getScripts || validateFonts )
320 // Validates the fonts assigned by the application or assigns default ones.
321 // It makes sure all the characters are going to be rendered by the correct font.
322 MultilanguageSupport multilanguageSupport = MultilanguageSupport::Get();
326 // Retrieves the scripts used in the text.
327 multilanguageSupport.SetScripts( utf32Characters,
333 if( 0u == validFonts.Count() )
335 // Copy the requested font defaults received via the property system.
336 // These may not be valid i.e. may not contain glyphs for the necessary scripts.
337 GetDefaultFonts( validFonts, numberOfCharacters );
340 // Validates the fonts. If there is a character with no assigned font it sets a default one.
341 // After this call, fonts are validated.
342 multilanguageSupport.ValidateFonts( utf32Characters,
348 Vector<Character> mirroredUtf32Characters;
349 bool textMirrored = false;
350 Length numberOfParagraphs = 0u;
351 if( BIDI_INFO & operations )
353 // Count the number of LINE_NO_BREAK to reserve some space for the vector of paragraph's
354 // bidirectional info.
356 const TextAbstraction::LineBreakInfo* lineBreakInfoBuffer = lineBreakInfo.Begin();
357 for( Length index = 0u; index < numberOfCharacters; ++index )
359 if( TextAbstraction::LINE_NO_BREAK == *( lineBreakInfoBuffer + index ) )
361 ++numberOfParagraphs;
365 Vector<BidirectionalParagraphInfoRun>& bidirectionalInfo = mLogicalModel->mBidirectionalParagraphInfo;
366 bidirectionalInfo.Reserve( numberOfParagraphs );
368 // Calculates the bidirectional info for the whole paragraph if it contains right to left scripts.
369 SetBidirectionalInfo( utf32Characters,
374 if( 0u != bidirectionalInfo.Count() )
376 // This paragraph has right to left text. Some characters may need to be mirrored.
377 // TODO: consider if the mirrored string can be stored as well.
379 textMirrored = GetMirroredText( utf32Characters,
380 mirroredUtf32Characters,
383 // Only set the character directions if there is right to left characters.
384 Vector<CharacterDirection>& directions = mLogicalModel->mCharacterDirections;
385 directions.Resize( numberOfCharacters );
387 GetCharactersDirection( bidirectionalInfo,
392 // There is no right to left characters. Clear the directions vector.
393 mLogicalModel->mCharacterDirections.Clear();
397 Vector<GlyphInfo>& glyphs = mVisualModel->mGlyphs;
398 Vector<CharacterIndex>& glyphsToCharactersMap = mVisualModel->mGlyphsToCharacters;
399 Vector<Length>& charactersPerGlyph = mVisualModel->mCharactersPerGlyph;
400 Vector<GlyphIndex> newParagraphGlyphs;
401 newParagraphGlyphs.Reserve( numberOfParagraphs );
403 if( SHAPE_TEXT & operations )
405 const Vector<Character>& textToShape = textMirrored ? mirroredUtf32Characters : utf32Characters;
407 ShapeText( textToShape,
412 glyphsToCharactersMap,
414 newParagraphGlyphs );
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 GlyphInfo* glyphsBuffer = glyphs.Begin();
426 mFontClient.GetGlyphMetrics( glyphsBuffer, numberOfGlyphs );
428 // Update the width and advance of all new paragraph characters.
429 for( Vector<GlyphIndex>::ConstIterator it = newParagraphGlyphs.Begin(), endIt = newParagraphGlyphs.End(); it != endIt; ++it )
431 const GlyphIndex index = *it;
432 GlyphInfo& glyph = *( glyphsBuffer + index );
434 glyph.xBearing = 0.f;
441 mEventData->mPreEditFlag &&
442 ( 0u != mVisualModel->mCharactersToGlyph.Count() ) )
444 // Add the underline for the pre-edit text.
445 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
446 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
448 const GlyphIndex glyphStart = *( charactersToGlyphBuffer + mEventData->mPreEditStartPosition );
449 const CharacterIndex lastPreEditCharacter = mEventData->mPreEditStartPosition + ( ( mEventData->mPreEditLength > 0u ) ? mEventData->mPreEditLength - 1u : 0u );
450 const Length numberOfGlyphsLastCharacter = *( glyphsPerCharacterBuffer + lastPreEditCharacter );
451 const GlyphIndex glyphEnd = *( charactersToGlyphBuffer + lastPreEditCharacter ) + ( numberOfGlyphsLastCharacter > 1u ? numberOfGlyphsLastCharacter - 1u : 0u );
453 GlyphRun underlineRun;
454 underlineRun.glyphIndex = glyphStart;
455 underlineRun.numberOfGlyphs = 1u + glyphEnd - glyphStart;
457 // TODO: At the moment the underline runs are only for pre-edit.
458 mVisualModel->mUnderlineRuns.PushBack( underlineRun );
462 void Controller::Impl::GetDefaultFonts( Vector<FontRun>& fonts, Length numberOfCharacters )
467 fontRun.characterRun.characterIndex = 0;
468 fontRun.characterRun.numberOfCharacters = numberOfCharacters;
469 fontRun.fontId = mFontDefaults->GetFontId( mFontClient );
470 fontRun.isDefault = true;
472 fonts.PushBack( fontRun );
476 float Controller::Impl::GetDefaultFontLineHeight()
478 FontId defaultFontId = 0u;
479 if( NULL == mFontDefaults )
481 defaultFontId = mFontClient.GetFontId( EMPTY_STRING,
486 defaultFontId = mFontDefaults->GetFontId( mFontClient );
489 Text::FontMetrics fontMetrics;
490 mFontClient.GetFontMetrics( defaultFontId, fontMetrics );
492 return( fontMetrics.ascender - fontMetrics.descender );
495 void Controller::Impl::OnCursorKeyEvent( const Event& event )
497 if( NULL == mEventData )
499 // Nothing to do if there is no text input.
503 int keyCode = event.p1.mInt;
505 if( Dali::DALI_KEY_CURSOR_LEFT == keyCode )
507 if( mEventData->mPrimaryCursorPosition > 0u )
509 mEventData->mPrimaryCursorPosition = CalculateNewCursorIndex( mEventData->mPrimaryCursorPosition - 1u );
512 else if( Dali::DALI_KEY_CURSOR_RIGHT == keyCode )
514 if( mLogicalModel->mText.Count() > mEventData->mPrimaryCursorPosition )
516 mEventData->mPrimaryCursorPosition = CalculateNewCursorIndex( mEventData->mPrimaryCursorPosition );
519 else if( Dali::DALI_KEY_CURSOR_UP == keyCode )
523 else if( Dali::DALI_KEY_CURSOR_DOWN == keyCode )
528 mEventData->mUpdateCursorPosition = true;
529 mEventData->mScrollAfterUpdatePosition = true;
532 void Controller::Impl::OnTapEvent( const Event& event )
534 if( NULL != mEventData )
536 const unsigned int tapCount = event.p1.mUint;
540 if( ! IsShowingPlaceholderText() )
542 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
543 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
545 mEventData->mPrimaryCursorPosition = GetClosestCursorIndex( xPosition,
550 mEventData->mPrimaryCursorPosition = 0u;
553 mEventData->mUpdateCursorPosition = true;
554 mEventData->mScrollAfterUpdatePosition = true;
559 void Controller::Impl::OnPanEvent( const Event& event )
561 if( NULL == mEventData )
563 // Nothing to do if there is no text input.
567 int state = event.p1.mInt;
569 if( Gesture::Started == state ||
570 Gesture::Continuing == state )
572 const Vector2& actualSize = mVisualModel->GetActualSize();
573 const Vector2 currentScroll = mEventData->mScrollPosition;
575 if( mEventData->mHorizontalScrollingEnabled )
577 const float displacementX = event.p2.mFloat;
578 mEventData->mScrollPosition.x += displacementX;
580 ClampHorizontalScroll( actualSize );
583 if( mEventData->mVerticalScrollingEnabled )
585 const float displacementY = event.p3.mFloat;
586 mEventData->mScrollPosition.y += displacementY;
588 ClampVerticalScroll( actualSize );
591 if( mEventData->mDecorator )
593 mEventData->mDecorator->UpdatePositions( mEventData->mScrollPosition - currentScroll );
598 void Controller::Impl::OnLongPressEvent( const Event& event )
600 if ( EventData::EDITING == mEventData->mState )
602 ChangeState ( EventData::EDITING_WITH_POPUP );
603 mEventData->mDecoratorUpdated = true;
607 void Controller::Impl::OnHandleEvent( const Event& event )
609 if( NULL == mEventData )
611 // Nothing to do if there is no text input.
615 const unsigned int state = event.p1.mUint;
616 const bool handleStopScrolling = ( HANDLE_STOP_SCROLLING == state );
618 if( HANDLE_PRESSED == state )
620 // The event.p2 and event.p3 are in decorator coords. Need to transforms to text coords.
621 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
622 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
624 const CharacterIndex handleNewPosition = GetClosestCursorIndex( xPosition, yPosition );
626 if( Event::GRAB_HANDLE_EVENT == event.type )
628 ChangeState ( EventData::GRAB_HANDLE_PANNING );
630 if( handleNewPosition != mEventData->mPrimaryCursorPosition )
632 mEventData->mPrimaryCursorPosition = handleNewPosition;
633 mEventData->mUpdateCursorPosition = true;
636 else if( Event::LEFT_SELECTION_HANDLE_EVENT == event.type )
638 ChangeState ( EventData::SELECTION_HANDLE_PANNING );
640 if( ( handleNewPosition != mEventData->mLeftSelectionPosition ) &&
641 ( handleNewPosition != mEventData->mRightSelectionPosition ) )
643 mEventData->mLeftSelectionPosition = handleNewPosition;
645 RepositionSelectionHandles( mEventData->mLeftSelectionPosition,
646 mEventData->mRightSelectionPosition );
648 mEventData->mUpdateLeftSelectionPosition = true;
651 else if( Event::RIGHT_SELECTION_HANDLE_EVENT == event.type )
653 ChangeState ( EventData::SELECTION_HANDLE_PANNING );
655 if( ( handleNewPosition != mEventData->mRightSelectionPosition ) &&
656 ( handleNewPosition != mEventData->mLeftSelectionPosition ) )
658 mEventData->mRightSelectionPosition = handleNewPosition;
660 RepositionSelectionHandles( mEventData->mLeftSelectionPosition,
661 mEventData->mRightSelectionPosition );
663 mEventData->mUpdateRightSelectionPosition = true;
666 } // end ( HANDLE_PRESSED == state )
667 else if( ( HANDLE_RELEASED == state ) ||
668 handleStopScrolling )
670 CharacterIndex handlePosition = 0u;
671 if( handleStopScrolling )
673 // The event.p2 and event.p3 are in decorator coords. Need to transforms to text coords.
674 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
675 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
677 handlePosition = GetClosestCursorIndex( xPosition, yPosition );
680 if( Event::GRAB_HANDLE_EVENT == event.type )
682 mEventData->mUpdateCursorPosition = true;
684 ChangeState( EventData::EDITING_WITH_POPUP );
686 if( handleStopScrolling )
688 mEventData->mScrollAfterUpdatePosition = mEventData->mPrimaryCursorPosition != handlePosition;
689 mEventData->mPrimaryCursorPosition = handlePosition;
692 else if( Event::LEFT_SELECTION_HANDLE_EVENT == event.type )
694 ChangeState( EventData::SELECTING );
696 if( handleStopScrolling )
698 mEventData->mUpdateLeftSelectionPosition = ( mEventData->mRightSelectionPosition != handlePosition );
699 mEventData->mScrollAfterUpdatePosition = mEventData->mUpdateLeftSelectionPosition;
701 if( mEventData->mUpdateLeftSelectionPosition )
703 mEventData->mLeftSelectionPosition = handlePosition;
705 RepositionSelectionHandles( mEventData->mLeftSelectionPosition,
706 mEventData->mRightSelectionPosition );
710 else if( Event::RIGHT_SELECTION_HANDLE_EVENT == event.type )
712 ChangeState( EventData::SELECTING );
714 if( handleStopScrolling )
716 mEventData->mUpdateRightSelectionPosition = ( mEventData->mLeftSelectionPosition != handlePosition );
717 mEventData->mScrollAfterUpdatePosition = mEventData->mUpdateRightSelectionPosition;
718 if( mEventData->mUpdateRightSelectionPosition )
720 mEventData->mRightSelectionPosition = handlePosition;
721 RepositionSelectionHandles( mEventData->mLeftSelectionPosition,
722 mEventData->mRightSelectionPosition );
727 mEventData->mDecoratorUpdated = true;
728 } // end ( ( HANDLE_RELEASED == state ) || ( HANDLE_STOP_SCROLLING == state ) )
729 else if( HANDLE_SCROLLING == state )
731 const float xSpeed = event.p2.mFloat;
732 const Vector2& actualSize = mVisualModel->GetActualSize();
733 const Vector2 currentScrollPosition = mEventData->mScrollPosition;
735 mEventData->mScrollPosition.x += xSpeed;
737 ClampHorizontalScroll( actualSize );
739 bool endOfScroll = false;
740 if( Vector2::ZERO == ( currentScrollPosition - mEventData->mScrollPosition ) )
742 // Notify the decorator there is no more text to scroll.
743 // The decorator won't send more scroll events.
744 mEventData->mDecorator->NotifyEndOfScroll();
745 // Still need to set the position of the handle.
749 // Set the position of the handle.
750 const bool scrollRightDirection = xSpeed > 0.f;
751 const bool leftSelectionHandleEvent = Event::LEFT_SELECTION_HANDLE_EVENT == event.type;
752 const bool rightSelectionHandleEvent = Event::RIGHT_SELECTION_HANDLE_EVENT == event.type;
754 if( Event::GRAB_HANDLE_EVENT == event.type )
756 ChangeState( EventData::GRAB_HANDLE_PANNING );
758 Vector2 position = mEventData->mDecorator->GetPosition( GRAB_HANDLE );
760 // Position the grag handle close to either the left or right edge.
761 position.x = scrollRightDirection ? 0.f : mVisualModel->mControlSize.width;
763 // Get the new handle position.
764 // The grab handle's position is in decorator coords. Need to transforms to text coords.
765 const CharacterIndex handlePosition = GetClosestCursorIndex( position.x - mEventData->mScrollPosition.x - mAlignmentOffset.x,
766 position.y - mEventData->mScrollPosition.y - mAlignmentOffset.y );
768 mEventData->mUpdateCursorPosition = mEventData->mPrimaryCursorPosition != handlePosition;
769 mEventData->mScrollAfterUpdatePosition = mEventData->mUpdateCursorPosition;
770 mEventData->mPrimaryCursorPosition = handlePosition;
772 else if( leftSelectionHandleEvent || rightSelectionHandleEvent )
774 // TODO: This is recalculating the selection box every time the text is scrolled with the selection handles.
775 // Think if something can be done to save power.
777 ChangeState( EventData::SELECTION_HANDLE_PANNING );
779 Vector2 position = mEventData->mDecorator->GetPosition( leftSelectionHandleEvent ? Text::LEFT_SELECTION_HANDLE : Text::RIGHT_SELECTION_HANDLE );
781 // Position the selection handle close to either the left or right edge.
782 position.x = scrollRightDirection ? 0.f : mVisualModel->mControlSize.width;
784 // Get the new handle position.
785 // The selection handle's position is in decorator coords. Need to transforms to text coords.
786 const CharacterIndex handlePosition = GetClosestCursorIndex( position.x - mEventData->mScrollPosition.x - mAlignmentOffset.x,
787 position.y - mEventData->mScrollPosition.y - mAlignmentOffset.y );
789 if( leftSelectionHandleEvent )
791 const bool differentHandles = ( mEventData->mLeftSelectionPosition != handlePosition ) && ( mEventData->mRightSelectionPosition != handlePosition );
792 mEventData->mUpdateLeftSelectionPosition = endOfScroll || differentHandles;
793 if( differentHandles )
795 mEventData->mLeftSelectionPosition = handlePosition;
800 const bool differentHandles = ( mEventData->mRightSelectionPosition != handlePosition ) && ( mEventData->mLeftSelectionPosition != handlePosition );
801 mEventData->mUpdateRightSelectionPosition = endOfScroll || differentHandles;
802 if( differentHandles )
804 mEventData->mRightSelectionPosition = handlePosition;
808 if( mEventData->mUpdateLeftSelectionPosition || mEventData->mUpdateRightSelectionPosition )
810 RepositionSelectionHandles( mEventData->mLeftSelectionPosition,
811 mEventData->mRightSelectionPosition );
813 mEventData->mScrollAfterUpdatePosition = true;
816 mEventData->mDecoratorUpdated = true;
817 } // end ( HANDLE_SCROLLING == state )
820 void Controller::Impl::OnSelectEvent( const Event& event )
822 if( NULL == mEventData )
824 // Nothing to do if there is no text.
828 if( mEventData->mSelectionEnabled )
830 // The event.p2 and event.p3 are in decorator coords. Need to transforms to text coords.
831 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
832 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
834 const CharacterIndex leftPosition = mEventData->mLeftSelectionPosition;
835 const CharacterIndex rightPosition = mEventData->mRightSelectionPosition;
837 RepositionSelectionHandles( xPosition,
840 mEventData->mUpdateLeftSelectionPosition = leftPosition != mEventData->mLeftSelectionPosition;
841 mEventData->mUpdateRightSelectionPosition = rightPosition != mEventData->mRightSelectionPosition;
843 mEventData->mScrollAfterUpdatePosition = ( ( mEventData->mUpdateLeftSelectionPosition || mEventData->mUpdateRightSelectionPosition ) &&
844 ( mEventData->mLeftSelectionPosition != mEventData->mRightSelectionPosition ) );
848 void Controller::Impl::OnSelectAllEvent()
850 if( NULL == mEventData )
852 // Nothing to do if there is no text.
856 if( mEventData->mSelectionEnabled )
858 RepositionSelectionHandles( 0u,
859 mLogicalModel->mText.Count() );
861 mEventData->mScrollAfterUpdatePosition = true;
862 mEventData->mUpdateLeftSelectionPosition = true;
863 mEventData->mUpdateRightSelectionPosition = true;
867 void Controller::Impl::RetrieveSelection( std::string& selectedText, bool deleteAfterRetreival )
869 if( mEventData->mLeftSelectionPosition == mEventData->mRightSelectionPosition )
871 // Nothing to select if handles are in the same place.
876 //Get start and end position of selection
877 uint32_t startOfSelectedText = mEventData->mLeftSelectionPosition;
878 uint32_t lengthOfSelectedText = mEventData->mRightSelectionPosition - startOfSelectedText;
880 // Validate the start and end selection points
881 if( ( startOfSelectedText >= 0 ) && ( ( startOfSelectedText + lengthOfSelectedText ) <= mLogicalModel->mText.Count() ) )
883 //Get text as a UTF8 string
884 Vector<Character>& utf32Characters = mLogicalModel->mText;
886 Utf32ToUtf8( &utf32Characters[startOfSelectedText], lengthOfSelectedText, selectedText );
888 if ( deleteAfterRetreival ) // Only delete text if copied successfully
890 // Delete text between handles
891 Vector<Character>& currentText = mLogicalModel->mText;
893 Vector<Character>::Iterator first = currentText.Begin() + startOfSelectedText;
894 Vector<Character>::Iterator last = first + lengthOfSelectedText;
895 currentText.Erase( first, last );
897 mEventData->mPrimaryCursorPosition = mEventData->mLeftSelectionPosition;
898 mEventData->mScrollAfterDelete = true;
899 mEventData->mDecoratorUpdated = true;
903 void Controller::Impl::ShowClipboard()
907 mClipboard.ShowClipboard();
911 void Controller::Impl::HideClipboard()
915 mClipboard.HideClipboard();
919 bool Controller::Impl::CopyStringToClipboard( std::string& source )
921 //Send string to clipboard
922 return ( mClipboard && mClipboard.SetItem( source ) );
925 void Controller::Impl::SendSelectionToClipboard( bool deleteAfterSending )
927 std::string selectedText;
928 RetrieveSelection( selectedText, deleteAfterSending );
929 CopyStringToClipboard( selectedText );
930 ChangeState( EventData::EDITING );
933 void Controller::Impl::GetTextFromClipboard( unsigned int itemIndex, std::string& retreivedString )
937 retreivedString = mClipboard.GetItem( itemIndex );
941 void Controller::Impl::RepositionSelectionHandles( CharacterIndex selectionStart, CharacterIndex selectionEnd )
943 if( selectionStart == selectionEnd )
945 // Nothing to select if handles are in the same place.
949 mEventData->mDecorator->ClearHighlights();
951 mEventData->mLeftSelectionPosition = selectionStart;
952 mEventData->mRightSelectionPosition = selectionEnd;
954 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
955 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
956 const GlyphInfo* const glyphsBuffer = mVisualModel->mGlyphs.Begin();
957 const Vector2* const positionsBuffer = mVisualModel->mGlyphPositions.Begin();
958 const Length* const charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
959 const CharacterIndex* const glyphToCharacterBuffer = mVisualModel->mGlyphsToCharacters.Begin();
960 const CharacterDirection* const modelCharacterDirectionsBuffer = ( 0u != mLogicalModel->mCharacterDirections.Count() ) ? mLogicalModel->mCharacterDirections.Begin() : NULL;
962 // TODO: Better algorithm to create the highlight box.
965 // Get the height of the line.
966 const Vector<LineRun>& lines = mVisualModel->mLines;
967 const LineRun& firstLine = *lines.Begin();
968 const float height = firstLine.ascender + -firstLine.descender;
970 // Swap the indices if the start is greater than the end.
971 const bool indicesSwapped = ( selectionStart > selectionEnd );
974 std::swap( selectionStart, selectionEnd );
977 // Get the indices to the first and last selected glyphs.
978 const CharacterIndex selectionEndMinusOne = selectionEnd - 1u;
979 const GlyphIndex glyphStart = *( charactersToGlyphBuffer + selectionStart );
980 const Length numberOfGlyphs = *( glyphsPerCharacterBuffer + selectionEndMinusOne );
981 const GlyphIndex glyphEnd = *( charactersToGlyphBuffer + selectionEndMinusOne ) + ( ( numberOfGlyphs > 0 ) ? numberOfGlyphs - 1u : 0u );
983 // Check if the first glyph is a ligature that must be broken like Latin ff, fi, or Arabic ï»», etc which needs special code.
984 const Length numberOfCharactersStart = *( charactersPerGlyphBuffer + glyphStart );
985 bool splitStartGlyph = ( numberOfCharactersStart > 1u ) && HasLigatureMustBreak( mLogicalModel->GetScript( selectionStart ) );
987 // Check if the last glyph is a ligature that must be broken like Latin ff, fi, or Arabic ï»», etc which needs special code.
988 const Length numberOfCharactersEnd = *( charactersPerGlyphBuffer + glyphEnd );
989 bool splitEndGlyph = ( glyphStart != glyphEnd ) && ( numberOfCharactersEnd > 1u ) && HasLigatureMustBreak( mLogicalModel->GetScript( selectionEndMinusOne ) );
991 // Tell the decorator to swap the selection handles if needed.
992 mEventData->mDecorator->SwapSelectionHandlesEnabled( firstLine.direction != indicesSwapped );
994 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
996 // Traverse the glyphs.
997 for( GlyphIndex index = glyphStart; index <= glyphEnd; ++index )
999 const GlyphInfo& glyph = *( glyphsBuffer + index );
1000 const Vector2& position = *( positionsBuffer + index );
1002 if( splitStartGlyph )
1004 // 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.
1006 const float glyphAdvance = glyph.advance / static_cast<float>( numberOfCharactersStart );
1007 const CharacterIndex interGlyphIndex = selectionStart - *( glyphToCharacterBuffer + glyphStart );
1008 // Get the direction of the character.
1009 CharacterDirection isCurrentRightToLeft = false;
1010 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
1012 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + selectionStart );
1015 // The end point could be in the middle of the ligature.
1016 // Calculate the number of characters selected.
1017 const Length numberOfCharacters = ( glyphStart == glyphEnd ) ? ( selectionEnd - selectionStart ) : ( numberOfCharactersStart - interGlyphIndex );
1019 const float xPosition = position.x - glyph.xBearing + offset.x + glyphAdvance * static_cast<float>( isCurrentRightToLeft ? ( numberOfCharactersStart - interGlyphIndex - numberOfCharacters ) : interGlyphIndex );
1021 mEventData->mDecorator->AddHighlight( xPosition,
1023 xPosition + static_cast<float>( numberOfCharacters ) * glyphAdvance,
1024 offset.y + height );
1026 splitStartGlyph = false;
1030 if( splitEndGlyph && ( index == glyphEnd ) )
1032 // 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.
1034 const float glyphAdvance = glyph.advance / static_cast<float>( numberOfCharactersEnd );
1035 const CharacterIndex interGlyphIndex = selectionEnd - *( glyphToCharacterBuffer + glyphEnd );
1036 // Get the direction of the character.
1037 CharacterDirection isCurrentRightToLeft = false;
1038 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
1040 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + selectionEnd );
1043 const Length numberOfCharacters = numberOfCharactersEnd - interGlyphIndex;
1045 const float xPosition = position.x - glyph.xBearing + offset.x + ( isCurrentRightToLeft ? ( glyphAdvance * static_cast<float>( numberOfCharacters ) ) : 0.f );
1046 mEventData->mDecorator->AddHighlight( xPosition,
1048 xPosition + static_cast<float>( interGlyphIndex ) * glyphAdvance,
1049 offset.y + height );
1051 splitEndGlyph = false;
1055 const float xPosition = position.x - glyph.xBearing + offset.x;
1056 mEventData->mDecorator->AddHighlight( xPosition,
1058 xPosition + glyph.advance,
1059 offset.y + height );
1062 CursorInfo primaryCursorInfo;
1063 GetCursorPosition( mEventData->mLeftSelectionPosition,
1064 primaryCursorInfo );
1066 CursorInfo secondaryCursorInfo;
1067 GetCursorPosition( mEventData->mRightSelectionPosition,
1068 secondaryCursorInfo );
1070 const Vector2 primaryPosition = primaryCursorInfo.primaryPosition + offset;
1071 const Vector2 secondaryPosition = secondaryCursorInfo.primaryPosition + offset;
1073 mEventData->mDecorator->SetPosition( LEFT_SELECTION_HANDLE, primaryPosition.x, primaryPosition.y, primaryCursorInfo.lineHeight );
1075 mEventData->mDecorator->SetPosition( RIGHT_SELECTION_HANDLE, secondaryPosition.x, secondaryPosition.y, secondaryCursorInfo.lineHeight );
1077 // Cursor to be positioned at end of selection so if selection interrupted and edit mode restarted the cursor will be at end of selection
1078 mEventData->mPrimaryCursorPosition = (indicesSwapped)?mEventData->mLeftSelectionPosition:mEventData->mRightSelectionPosition;
1080 // Set the flag to update the decorator.
1081 mEventData->mDecoratorUpdated = true;
1084 void Controller::Impl::RepositionSelectionHandles( float visualX, float visualY )
1086 if( NULL == mEventData )
1088 // Nothing to do if there is no text input.
1092 if( IsShowingPlaceholderText() )
1094 // Nothing to do if there is the place-holder text.
1098 const Length numberOfGlyphs = mVisualModel->mGlyphs.Count();
1099 const Length numberOfLines = mVisualModel->mLines.Count();
1100 if( 0 == numberOfGlyphs ||
1101 0 == numberOfLines )
1103 // Nothing to do if there is no text.
1107 // Find which word was selected
1108 CharacterIndex selectionStart( 0 );
1109 CharacterIndex selectionEnd( 0 );
1110 FindSelectionIndices( visualX, visualY, selectionStart, selectionEnd );
1111 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "%p selectionStart %d selectionEnd %d\n", this, selectionStart, selectionEnd );
1113 if( selectionStart == selectionEnd )
1115 ChangeState( EventData::EDITING );
1116 // Nothing to select. i.e. a white space, out of bounds
1120 RepositionSelectionHandles( selectionStart, selectionEnd );
1123 void Controller::Impl::SetPopupButtons()
1126 * Sets the Popup buttons to be shown depending on State.
1128 * If SELECTING : CUT & COPY + ( PASTE & CLIPBOARD if content available to paste )
1130 * If EDITING_WITH_POPUP : SELECT & SELECT_ALL
1133 TextSelectionPopup::Buttons buttonsToShow = TextSelectionPopup::NONE;
1135 if ( ( EventData::SELECTING == mEventData->mState ) || ( EventData::SELECTION_CHANGED == mEventData->mState ) )
1137 buttonsToShow = TextSelectionPopup::Buttons( TextSelectionPopup::CUT | TextSelectionPopup::COPY );
1139 if ( !IsClipboardEmpty() )
1141 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
1142 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::CLIPBOARD ) );
1145 if ( !mEventData->mAllTextSelected )
1147 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::SELECT_ALL ) );
1150 else if ( EventData::EDITING_WITH_POPUP == mEventData->mState )
1152 if ( mLogicalModel->mText.Count() && !IsShowingPlaceholderText())
1154 buttonsToShow = TextSelectionPopup::Buttons( TextSelectionPopup::SELECT | TextSelectionPopup::SELECT_ALL );
1157 if ( !IsClipboardEmpty() )
1159 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
1160 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::CLIPBOARD ) );
1164 mEventData->mDecorator->SetEnabledPopupButtons( buttonsToShow );
1167 void Controller::Impl::ChangeState( EventData::State newState )
1169 if( NULL == mEventData )
1171 // Nothing to do if there is no text input.
1175 if( mEventData->mState != newState )
1177 mEventData->mState = newState;
1179 if( EventData::INACTIVE == mEventData->mState )
1181 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
1182 mEventData->mDecorator->StopCursorBlink();
1183 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1184 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1185 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1186 mEventData->mDecorator->SetPopupActive( false );
1187 mEventData->mDecoratorUpdated = true;
1190 else if ( EventData::INTERRUPTED == mEventData->mState)
1192 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1193 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1194 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1195 mEventData->mDecorator->SetPopupActive( false );
1196 mEventData->mDecoratorUpdated = true;
1199 else if ( EventData::SELECTING == mEventData->mState )
1201 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
1202 mEventData->mDecorator->StopCursorBlink();
1203 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1204 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true );
1205 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true );
1206 if( mEventData->mGrabHandlePopupEnabled )
1209 mEventData->mDecorator->SetPopupActive( true );
1211 mEventData->mDecoratorUpdated = true;
1213 else if ( EventData::SELECTION_CHANGED == mEventData->mState )
1215 if( mEventData->mGrabHandlePopupEnabled )
1218 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
1219 mEventData->mDecorator->SetPopupActive( true );
1221 mEventData->mDecoratorUpdated = true;
1223 else if( EventData::EDITING == mEventData->mState )
1225 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1226 if( mEventData->mCursorBlinkEnabled )
1228 mEventData->mDecorator->StartCursorBlink();
1230 // Grab handle is not shown until a tap is received whilst EDITING
1231 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1232 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1233 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1234 if( mEventData->mGrabHandlePopupEnabled )
1236 mEventData->mDecorator->SetPopupActive( false );
1238 mEventData->mDecoratorUpdated = true;
1241 else if( EventData::EDITING_WITH_POPUP == mEventData->mState )
1243 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1244 if( mEventData->mCursorBlinkEnabled )
1246 mEventData->mDecorator->StartCursorBlink();
1248 if( mEventData->mSelectionEnabled )
1250 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1251 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1255 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
1257 if( mEventData->mGrabHandlePopupEnabled )
1260 mEventData->mDecorator->SetPopupActive( true );
1263 mEventData->mDecoratorUpdated = true;
1265 else if( EventData::EDITING_WITH_GRAB_HANDLE == mEventData->mState )
1267 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1268 if( mEventData->mCursorBlinkEnabled )
1270 mEventData->mDecorator->StartCursorBlink();
1272 // Grab handle is not shown until a tap is received whilst EDITING
1273 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
1274 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1275 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1276 if( mEventData->mGrabHandlePopupEnabled )
1278 mEventData->mDecorator->SetPopupActive( false );
1280 mEventData->mDecoratorUpdated = true;
1283 else if ( EventData::SELECTION_HANDLE_PANNING == mEventData->mState )
1285 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
1286 mEventData->mDecorator->StopCursorBlink();
1287 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1288 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true );
1289 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true );
1290 if( mEventData->mGrabHandlePopupEnabled )
1292 mEventData->mDecorator->SetPopupActive( false );
1294 mEventData->mDecoratorUpdated = true;
1296 else if ( EventData::GRAB_HANDLE_PANNING == mEventData->mState )
1298 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1299 if( mEventData->mCursorBlinkEnabled )
1301 mEventData->mDecorator->StartCursorBlink();
1303 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
1304 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1305 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1306 if( mEventData->mGrabHandlePopupEnabled )
1308 mEventData->mDecorator->SetPopupActive( false );
1310 mEventData->mDecoratorUpdated = true;
1315 LineIndex Controller::Impl::GetClosestLine( float y ) const
1317 float totalHeight = 0.f;
1318 LineIndex lineIndex = 0u;
1320 const Vector<LineRun>& lines = mVisualModel->mLines;
1321 for( LineIndex endLine = lines.Count();
1322 lineIndex < endLine;
1325 const LineRun& lineRun = lines[lineIndex];
1326 totalHeight += lineRun.ascender + -lineRun.descender;
1327 if( y < totalHeight )
1333 if( lineIndex == 0 )
1341 void Controller::Impl::FindSelectionIndices( float visualX, float visualY, CharacterIndex& startIndex, CharacterIndex& endIndex )
1343 CharacterIndex hitCharacter = GetClosestCursorIndex( visualX, visualY );
1344 if( hitCharacter >= mLogicalModel->mText.Count() )
1346 // Selection out of bounds.
1350 startIndex = hitCharacter;
1351 endIndex = hitCharacter;
1353 if( !TextAbstraction::IsWhiteSpace( mLogicalModel->mText[hitCharacter] ) )
1355 // Find the start and end of the text
1356 for( startIndex = hitCharacter; startIndex > 0; --startIndex )
1358 Character charCode = mLogicalModel->mText[ startIndex-1 ];
1359 if( TextAbstraction::IsWhiteSpace( charCode ) )
1364 const CharacterIndex pastTheEnd = mLogicalModel->mText.Count();
1365 for( endIndex = hitCharacter + 1u; endIndex < pastTheEnd; ++endIndex )
1367 Character charCode = mLogicalModel->mText[ endIndex ];
1368 if( TextAbstraction::IsWhiteSpace( charCode ) )
1376 CharacterIndex Controller::Impl::GetClosestCursorIndex( float visualX,
1379 if( NULL == mEventData )
1381 // Nothing to do if there is no text input.
1385 CharacterIndex logicalIndex = 0u;
1387 const Length numberOfGlyphs = mVisualModel->mGlyphs.Count();
1388 const Length numberOfLines = mVisualModel->mLines.Count();
1389 if( 0 == numberOfGlyphs ||
1390 0 == numberOfLines )
1392 return logicalIndex;
1395 // Find which line is closest
1396 const LineIndex lineIndex = GetClosestLine( visualY );
1397 const LineRun& line = mVisualModel->mLines[lineIndex];
1399 // Get the positions of the glyphs.
1400 const Vector<Vector2>& positions = mVisualModel->mGlyphPositions;
1401 const Vector2* const positionsBuffer = positions.Begin();
1403 // Get the visual to logical conversion tables.
1404 const CharacterIndex* const visualToLogicalBuffer = ( 0u != mLogicalModel->mVisualToLogicalMap.Count() ) ? mLogicalModel->mVisualToLogicalMap.Begin() : NULL;
1405 const CharacterIndex* const visualToLogicalCursorBuffer = mLogicalModel->mVisualToLogicalCursorMap.Begin();
1407 // Get the character to glyph conversion table.
1408 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
1410 // Get the glyphs per character table.
1411 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
1412 const Length* const charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
1414 // If the vector is void, there is no right to left characters.
1415 const bool hasRightToLeftCharacters = NULL != visualToLogicalBuffer;
1417 const CharacterIndex startCharacter = line.characterRun.characterIndex;
1418 const CharacterIndex endCharacter = line.characterRun.characterIndex + line.characterRun.numberOfCharacters;
1419 DALI_ASSERT_DEBUG( endCharacter <= mLogicalModel->mText.Count() && "Invalid line info" );
1421 // Whether there is a hit on a glyph.
1422 bool matched = false;
1424 // Traverses glyphs in visual order. To do that use the visual to logical conversion table.
1425 CharacterIndex visualIndex = startCharacter;
1426 for( ; !matched && ( visualIndex < endCharacter ); ++visualIndex )
1428 // The character in logical order.
1429 const CharacterIndex characterLogicalOrderIndex = hasRightToLeftCharacters ? *( visualToLogicalBuffer + visualIndex ) : visualIndex;
1431 // Get the script of the character.
1432 const Script script = mLogicalModel->GetScript( characterLogicalOrderIndex );
1434 // The first glyph for that character in logical order.
1435 const GlyphIndex glyphLogicalOrderIndex = *( charactersToGlyphBuffer + characterLogicalOrderIndex );
1436 // The number of glyphs for that character
1437 const Length numberOfGlyphs = *( glyphsPerCharacterBuffer + characterLogicalOrderIndex );
1439 // Get the metrics for the group of glyphs.
1440 GlyphMetrics glyphMetrics;
1441 GetGlyphsMetrics( glyphLogicalOrderIndex,
1447 const Vector2& position = *( positionsBuffer + glyphLogicalOrderIndex );
1449 // Prevents to jump the whole Latin ligatures like fi, ff, or Arabic ï»»...
1450 const Length numberOfCharactersInLigature = HasLigatureMustBreak( script ) ? *( charactersPerGlyphBuffer + glyphLogicalOrderIndex ) : 1u;
1451 const float glyphAdvance = glyphMetrics.advance / static_cast<float>( numberOfCharactersInLigature );
1453 for( GlyphIndex index = 0u; !matched && ( index < numberOfCharactersInLigature ); ++index )
1455 // Find the mid-point of the area containing the glyph
1456 const float glyphCenter = -glyphMetrics.xBearing + position.x + ( static_cast<float>( index ) + 0.5f ) * glyphAdvance;
1458 if( visualX < glyphCenter )
1460 visualIndex += index;
1472 // Return the logical position of the cursor in characters.
1476 visualIndex = endCharacter;
1479 logicalIndex = hasRightToLeftCharacters ? *( visualToLogicalCursorBuffer + visualIndex ) : visualIndex;
1480 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "%p closest visualIndex %d logicalIndex %d\n", this, visualIndex, logicalIndex );
1482 return logicalIndex;
1485 void Controller::Impl::GetCursorPosition( CharacterIndex logical,
1486 CursorInfo& cursorInfo )
1488 // TODO: Check for multiline with \n, etc...
1490 // Check if the logical position is the first or the last one of the text.
1491 const bool isFirstPosition = 0u == logical;
1492 const bool isLastPosition = mLogicalModel->mText.Count() == logical;
1494 if( isFirstPosition && isLastPosition )
1496 // There is zero characters. Get the default font's line height.
1497 cursorInfo.lineHeight = GetDefaultFontLineHeight();
1498 cursorInfo.primaryCursorHeight = cursorInfo.lineHeight;
1500 cursorInfo.primaryPosition.x = mEventData->mDecorator->GetCursorWidth();
1501 cursorInfo.primaryPosition.y = 0.f;
1503 // Nothing else to do.
1507 // 'logical' is the logical 'cursor' index.
1508 // Get the next and current logical 'character' index.
1509 const CharacterIndex nextCharacterIndex = logical;
1510 const CharacterIndex characterIndex = isFirstPosition ? logical : logical - 1u;
1512 // Get the direction of the character and the next one.
1513 const CharacterDirection* const modelCharacterDirectionsBuffer = ( 0u != mLogicalModel->mCharacterDirections.Count() ) ? mLogicalModel->mCharacterDirections.Begin() : NULL;
1515 CharacterDirection isCurrentRightToLeft = false;
1516 CharacterDirection isNextRightToLeft = false;
1517 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
1519 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + characterIndex );
1520 isNextRightToLeft = *( modelCharacterDirectionsBuffer + nextCharacterIndex );
1523 // Get the line where the character is laid-out.
1524 const LineRun* const modelLines = mVisualModel->mLines.Begin();
1526 const LineIndex lineIndex = mVisualModel->GetLineOfCharacter( characterIndex );
1527 const LineRun& line = *( modelLines + lineIndex );
1529 // Get the paragraph's direction.
1530 const CharacterDirection isRightToLeftParagraph = line.direction;
1532 // Check whether there is an alternative position:
1534 cursorInfo.isSecondaryCursor = ( !isLastPosition && ( isCurrentRightToLeft != isNextRightToLeft ) ) ||
1535 ( isLastPosition && ( isRightToLeftParagraph != isCurrentRightToLeft ) );
1537 // Set the line height.
1538 cursorInfo.lineHeight = line.ascender + -line.descender;
1540 // Calculate the primary cursor.
1542 CharacterIndex index = characterIndex;
1543 if( cursorInfo.isSecondaryCursor )
1545 // If there is a secondary position, the primary cursor may be in a different place than the logical index.
1547 if( isLastPosition )
1549 // The position of the cursor after the last character needs special
1550 // care depending on its direction and the direction of the paragraph.
1552 // Need to find the first character after the last character with the paragraph's direction.
1553 // i.e l0 l1 l2 r0 r1 should find r0.
1555 // TODO: check for more than one line!
1556 index = isRightToLeftParagraph ? line.characterRun.characterIndex : line.characterRun.characterIndex + line.characterRun.numberOfCharacters - 1u;
1557 index = mLogicalModel->GetLogicalCharacterIndex( index );
1561 index = ( isRightToLeftParagraph == isCurrentRightToLeft ) ? characterIndex : nextCharacterIndex;
1565 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
1566 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
1567 const Length* const charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
1568 const CharacterIndex* const glyphsToCharactersBuffer = mVisualModel->mGlyphsToCharacters.Begin();
1569 const Vector2* const glyphPositionsBuffer = mVisualModel->mGlyphPositions.Begin();
1571 // Convert the cursor position into the glyph position.
1572 const GlyphIndex primaryGlyphIndex = *( charactersToGlyphBuffer + index );
1573 const Length primaryNumberOfGlyphs = *( glyphsPerCharacterBuffer + index );
1574 const Length primaryNumberOfCharacters = *( charactersPerGlyphBuffer + primaryGlyphIndex );
1576 // Get the metrics for the group of glyphs.
1577 GlyphMetrics glyphMetrics;
1578 GetGlyphsMetrics( primaryGlyphIndex,
1579 primaryNumberOfGlyphs,
1584 // Whether to add the glyph's advance to the cursor position.
1585 // 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,
1586 // if the logical cursor is one, the position is the position of the first glyph and the advance is added.
1587 // A 'truth table' was build and an online Karnaugh map tool was used to simplify the logic.
1608 // Where F -> isFirstPosition
1609 // L -> isLastPosition
1610 // C -> isCurrentRightToLeft
1611 // P -> isRightToLeftParagraph
1612 // A -> Whether to add the glyph's advance.
1614 const bool addGlyphAdvance = ( ( isLastPosition && !isRightToLeftParagraph ) ||
1615 ( isFirstPosition && isRightToLeftParagraph ) ||
1616 ( !isFirstPosition && !isLastPosition && !isCurrentRightToLeft ) );
1618 float glyphAdvance = addGlyphAdvance ? glyphMetrics.advance : 0.f;
1620 if( !isLastPosition &&
1621 ( primaryNumberOfCharacters > 1u ) )
1623 const CharacterIndex firstIndex = *( glyphsToCharactersBuffer + primaryGlyphIndex );
1625 bool isCurrentRightToLeft = false;
1626 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
1628 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + index );
1631 Length numberOfGlyphAdvance = ( isFirstPosition ? 0u : 1u ) + characterIndex - firstIndex;
1632 if( isCurrentRightToLeft )
1634 numberOfGlyphAdvance = primaryNumberOfCharacters - numberOfGlyphAdvance;
1637 glyphAdvance = static_cast<float>( numberOfGlyphAdvance ) * glyphMetrics.advance / static_cast<float>( primaryNumberOfCharacters );
1640 // Get the glyph position and x bearing.
1641 const Vector2& primaryPosition = *( glyphPositionsBuffer + primaryGlyphIndex );
1643 // Set the primary cursor's height.
1644 cursorInfo.primaryCursorHeight = cursorInfo.isSecondaryCursor ? 0.5f * glyphMetrics.fontHeight : glyphMetrics.fontHeight;
1646 // Set the primary cursor's position.
1647 cursorInfo.primaryPosition.x = -glyphMetrics.xBearing + primaryPosition.x + glyphAdvance;
1648 cursorInfo.primaryPosition.y = line.ascender - glyphMetrics.ascender;
1650 // Calculate the secondary cursor.
1652 if( cursorInfo.isSecondaryCursor )
1654 // Set the secondary cursor's height.
1655 cursorInfo.secondaryCursorHeight = 0.5f * glyphMetrics.fontHeight;
1657 CharacterIndex index = characterIndex;
1658 if( !isLastPosition )
1660 index = ( isRightToLeftParagraph == isCurrentRightToLeft ) ? nextCharacterIndex : characterIndex;
1663 const GlyphIndex secondaryGlyphIndex = *( charactersToGlyphBuffer + index );
1664 const Length secondaryNumberOfGlyphs = *( glyphsPerCharacterBuffer + index );
1666 const Vector2& secondaryPosition = *( glyphPositionsBuffer + secondaryGlyphIndex );
1668 GetGlyphsMetrics( secondaryGlyphIndex,
1669 secondaryNumberOfGlyphs,
1674 // Set the secondary cursor's position.
1675 cursorInfo.secondaryPosition.x = -glyphMetrics.xBearing + secondaryPosition.x + ( isCurrentRightToLeft ? 0.f : glyphMetrics.advance );
1676 cursorInfo.secondaryPosition.y = cursorInfo.lineHeight - cursorInfo.secondaryCursorHeight - line.descender - ( glyphMetrics.fontHeight - glyphMetrics.ascender );
1680 CharacterIndex Controller::Impl::CalculateNewCursorIndex( CharacterIndex index ) const
1682 if( NULL == mEventData )
1684 // Nothing to do if there is no text input.
1688 CharacterIndex cursorIndex = mEventData->mPrimaryCursorPosition;
1690 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
1691 const Length* const charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
1693 GlyphIndex glyphIndex = *( charactersToGlyphBuffer + index );
1694 Length numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
1696 if( numberOfCharacters > 1u )
1698 const Script script = mLogicalModel->GetScript( index );
1699 if( HasLigatureMustBreak( script ) )
1701 // Prevents to jump the whole Latin ligatures like fi, ff, or Arabic ï»», ...
1702 numberOfCharacters = 1u;
1707 while( 0u == numberOfCharacters )
1710 numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
1714 if( index < mEventData->mPrimaryCursorPosition )
1716 cursorIndex -= numberOfCharacters;
1720 cursorIndex += numberOfCharacters;
1726 void Controller::Impl::UpdateCursorPosition()
1728 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "-->Controller::UpdateCursorPosition %p\n", this );
1729 if( NULL == mEventData )
1731 // Nothing to do if there is no text input.
1732 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition no event data\n" );
1736 if( IsShowingPlaceholderText() || ( 0u == mLogicalModel->mText.Count() ) )
1738 // Do not want to use the place-holder text to set the cursor position.
1740 // Use the line's height of the font's family set to set the cursor's size.
1741 // If there is no font's family set, use the default font.
1742 // Use the current alignment to place the cursor at the beginning, center or end of the box.
1744 float lineHeight = 0.f;
1746 FontId defaultFontId = 0u;
1747 if( NULL == mFontDefaults )
1749 defaultFontId = mFontClient.GetFontId( EMPTY_STRING,
1754 defaultFontId = mFontDefaults->GetFontId( mFontClient );
1757 Text::FontMetrics fontMetrics;
1758 mFontClient.GetFontMetrics( defaultFontId, fontMetrics );
1760 lineHeight = fontMetrics.ascender - fontMetrics.descender;
1763 Vector2 cursorPosition;
1765 switch( mLayoutEngine.GetHorizontalAlignment() )
1767 case LayoutEngine::HORIZONTAL_ALIGN_BEGIN:
1769 cursorPosition.x = mEventData->mDecorator->GetCursorWidth();
1772 case LayoutEngine::HORIZONTAL_ALIGN_CENTER:
1774 cursorPosition.x = floor( 0.5f * mVisualModel->mControlSize.width );
1777 case LayoutEngine::HORIZONTAL_ALIGN_END:
1779 cursorPosition.x = mVisualModel->mControlSize.width;
1784 switch( mLayoutEngine.GetVerticalAlignment() )
1786 case LayoutEngine::VERTICAL_ALIGN_TOP:
1788 cursorPosition.y = 0.f;
1791 case LayoutEngine::VERTICAL_ALIGN_CENTER:
1793 cursorPosition.y = floorf( 0.5f * ( mVisualModel->mControlSize.height - lineHeight ) );
1796 case LayoutEngine::VERTICAL_ALIGN_BOTTOM:
1798 cursorPosition.y = mVisualModel->mControlSize.height - lineHeight;
1803 mEventData->mDecorator->SetPosition( PRIMARY_CURSOR,
1811 CursorInfo cursorInfo;
1812 GetCursorPosition( mEventData->mPrimaryCursorPosition,
1815 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
1816 const Vector2 cursorPosition = cursorInfo.primaryPosition + offset;
1818 // Sets the cursor position.
1819 mEventData->mDecorator->SetPosition( PRIMARY_CURSOR,
1822 cursorInfo.primaryCursorHeight,
1823 cursorInfo.lineHeight );
1824 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Primary cursor position: %f,%f\n", cursorPosition.x, cursorPosition.y );
1826 // Sets the grab handle position.
1827 mEventData->mDecorator->SetPosition( GRAB_HANDLE,
1830 cursorInfo.lineHeight );
1832 if( cursorInfo.isSecondaryCursor )
1834 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_BOTH );
1835 mEventData->mDecorator->SetPosition( SECONDARY_CURSOR,
1836 cursorInfo.secondaryPosition.x + offset.x,
1837 cursorInfo.secondaryPosition.y + offset.y,
1838 cursorInfo.secondaryCursorHeight,
1839 cursorInfo.lineHeight );
1840 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Secondary cursor position: %f,%f\n", cursorInfo.secondaryPosition.x + offset.x, cursorInfo.secondaryPosition.y + offset.y );
1844 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1847 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition\n" );
1850 void Controller::Impl::UpdateSelectionHandle( HandleType handleType )
1852 if( ( LEFT_SELECTION_HANDLE != handleType ) &&
1853 ( RIGHT_SELECTION_HANDLE != handleType ) )
1858 const bool leftSelectionHandle = LEFT_SELECTION_HANDLE == handleType;
1859 const CharacterIndex index = leftSelectionHandle ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition;
1861 CursorInfo cursorInfo;
1862 GetCursorPosition( index,
1865 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
1866 const Vector2 cursorPosition = cursorInfo.primaryPosition + offset;
1868 // Sets the grab handle position.
1869 mEventData->mDecorator->SetPosition( handleType,
1872 cursorInfo.lineHeight );
1874 // If selection handle at start of the text and other at end of the text then all text is selected.
1875 const CharacterIndex startOfSelection = std::min( mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition );
1876 const CharacterIndex endOfSelection = std::max ( mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition );
1877 mEventData->mAllTextSelected = ( startOfSelection == 0 ) && ( endOfSelection == mLogicalModel->mText.Count() );
1880 void Controller::Impl::ClampHorizontalScroll( const Vector2& actualSize )
1882 // Clamp between -space & 0 (and the text alignment).
1883 if( actualSize.width > mVisualModel->mControlSize.width )
1885 const float space = ( actualSize.width - mVisualModel->mControlSize.width ) + mAlignmentOffset.x;
1886 mEventData->mScrollPosition.x = ( mEventData->mScrollPosition.x < -space ) ? -space : mEventData->mScrollPosition.x;
1887 mEventData->mScrollPosition.x = ( mEventData->mScrollPosition.x > -mAlignmentOffset.x ) ? -mAlignmentOffset.x : mEventData->mScrollPosition.x;
1889 mEventData->mDecoratorUpdated = true;
1893 mEventData->mScrollPosition.x = 0.f;
1897 void Controller::Impl::ClampVerticalScroll( const Vector2& actualSize )
1899 // Clamp between -space & 0 (and the text alignment).
1900 if( actualSize.height > mVisualModel->mControlSize.height )
1902 const float space = ( actualSize.height - mVisualModel->mControlSize.height ) + mAlignmentOffset.y;
1903 mEventData->mScrollPosition.y = ( mEventData->mScrollPosition.y < -space ) ? -space : mEventData->mScrollPosition.y;
1904 mEventData->mScrollPosition.y = ( mEventData->mScrollPosition.y > -mAlignmentOffset.y ) ? -mAlignmentOffset.y : mEventData->mScrollPosition.y;
1906 mEventData->mDecoratorUpdated = true;
1910 mEventData->mScrollPosition.y = 0.f;
1914 void Controller::Impl::ScrollToMakePositionVisible( const Vector2& position )
1917 bool updateDecorator = false;
1918 if( position.x < 0.f )
1920 offset.x = -position.x;
1921 mEventData->mScrollPosition.x += offset.x;
1922 updateDecorator = true;
1924 else if( position.x > mVisualModel->mControlSize.width )
1926 offset.x = mVisualModel->mControlSize.width - position.x;
1927 mEventData->mScrollPosition.x += offset.x;
1928 updateDecorator = true;
1931 if( updateDecorator && mEventData->mDecorator )
1933 mEventData->mDecorator->UpdatePositions( offset );
1936 // TODO : calculate the vertical scroll.
1939 void Controller::Impl::ScrollTextToMatchCursor()
1941 // Get the current cursor position in decorator coords.
1942 const Vector2& currentCursorPosition = mEventData->mDecorator->GetPosition( PRIMARY_CURSOR );
1944 // Calculate the new cursor position.
1945 CursorInfo cursorInfo;
1946 GetCursorPosition( mEventData->mPrimaryCursorPosition,
1949 // Calculate the offset to match the cursor position before the character was deleted.
1950 mEventData->mScrollPosition.x = currentCursorPosition.x - cursorInfo.primaryPosition.x - mAlignmentOffset.x;
1952 ClampHorizontalScroll( mVisualModel->GetActualSize() );
1954 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
1955 const Vector2 cursorPosition = cursorInfo.primaryPosition + offset;
1957 // Sets the cursor position.
1958 mEventData->mDecorator->SetPosition( PRIMARY_CURSOR,
1961 cursorInfo.primaryCursorHeight,
1962 cursorInfo.lineHeight );
1964 // Sets the grab handle position.
1965 mEventData->mDecorator->SetPosition( GRAB_HANDLE,
1968 cursorInfo.lineHeight );
1970 if( cursorInfo.isSecondaryCursor )
1972 mEventData->mDecorator->SetPosition( SECONDARY_CURSOR,
1973 cursorInfo.secondaryPosition.x + offset.x,
1974 cursorInfo.secondaryPosition.y + offset.y,
1975 cursorInfo.secondaryCursorHeight,
1976 cursorInfo.lineHeight );
1977 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Secondary cursor position: %f,%f\n", cursorInfo.secondaryPosition.x + offset.x, cursorInfo.secondaryPosition.y + offset.y );
1980 // Set which cursors are active according the state.
1981 if( ( EventData::EDITING == mEventData->mState ) ||
1982 ( EventData::EDITING_WITH_POPUP == mEventData->mState ) ||
1983 ( EventData::EDITING_WITH_GRAB_HANDLE == mEventData->mState ) ||
1984 ( EventData::GRAB_HANDLE_PANNING == mEventData->mState ) )
1986 if( cursorInfo.isSecondaryCursor )
1988 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_BOTH );
1992 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1997 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
2001 void Controller::Impl::RequestRelayout()
2003 mControlInterface.RequestTextRelayout();
2008 } // namespace Toolkit