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::NoLogging, 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.
73 * @brief Get some glyph's metrics of a group of glyphs formed as a result of shaping one character.
75 * @param[in] glyphIndex The index to the first glyph.
76 * @param[in] numberOfGlyphs The number of glyphs.
77 * @param[out] glyphMetrics Some glyph metrics (font height, advance, ascender and x bearing).
78 * @param[in] visualModel The visual model.
79 * @param[in] metrics Used to access metrics from FontClient.
81 void GetGlyphsMetrics( GlyphIndex glyphIndex,
82 Length numberOfGlyphs,
83 GlyphMetrics& glyphMetrics,
84 VisualModelPtr& visualModel,
87 const GlyphInfo* glyphsBuffer = visualModel->mGlyphs.Begin();
89 const GlyphInfo& firstGlyph = *( glyphsBuffer + glyphIndex );
91 Text::FontMetrics fontMetrics;
92 metrics->GetFontMetrics( firstGlyph.fontId, fontMetrics );
94 glyphMetrics.fontHeight = fontMetrics.height;
95 glyphMetrics.advance = firstGlyph.advance;
96 glyphMetrics.ascender = fontMetrics.ascender;
97 glyphMetrics.xBearing = firstGlyph.xBearing;
99 for( unsigned int i = 1u; i < numberOfGlyphs; ++i )
101 const GlyphInfo& glyphInfo = *( glyphsBuffer + glyphIndex + i );
103 glyphMetrics.advance += glyphInfo.advance;
107 EventData::EventData( DecoratorPtr decorator )
108 : mDecorator( decorator ),
109 mPlaceholderTextActive(),
110 mPlaceholderTextInactive(),
111 mPlaceholderTextColor( 0.8f, 0.8f, 0.8f, 0.8f ),
115 mPrimaryCursorPosition( 0u ),
116 mLeftSelectionPosition( 0u ),
117 mRightSelectionPosition( 0u ),
118 mPreEditStartPosition( 0u ),
119 mPreEditLength( 0u ),
120 mIsShowingPlaceholderText( false ),
121 mPreEditFlag( false ),
122 mDecoratorUpdated( false ),
123 mCursorBlinkEnabled( true ),
124 mGrabHandleEnabled( true ),
125 mGrabHandlePopupEnabled( true ),
126 mSelectionEnabled( true ),
127 mHorizontalScrollingEnabled( true ),
128 mVerticalScrollingEnabled( false ),
129 mUpdateCursorPosition( false ),
130 mUpdateLeftSelectionPosition( false ),
131 mUpdateRightSelectionPosition( false ),
132 mScrollAfterUpdatePosition( false ),
133 mScrollAfterDelete( false ),
134 mAllTextSelected( false )
137 EventData::~EventData()
140 bool Controller::Impl::ProcessInputEvents()
142 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "-->Controller::ProcessInputEvents\n" );
143 if( NULL == mEventData )
145 // Nothing to do if there is no text input.
146 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::ProcessInputEvents no event data\n" );
150 if( mEventData->mDecorator )
152 for( std::vector<Event>::iterator iter = mEventData->mEventQueue.begin();
153 iter != mEventData->mEventQueue.end();
158 case Event::CURSOR_KEY_EVENT:
160 OnCursorKeyEvent( *iter );
163 case Event::TAP_EVENT:
168 case Event::LONG_PRESS_EVENT:
170 OnLongPressEvent( *iter );
173 case Event::PAN_EVENT:
178 case Event::GRAB_HANDLE_EVENT:
179 case Event::LEFT_SELECTION_HANDLE_EVENT:
180 case Event::RIGHT_SELECTION_HANDLE_EVENT: // Fall through
182 OnHandleEvent( *iter );
187 OnSelectEvent( *iter );
190 case Event::SELECT_ALL:
199 // The cursor must also be repositioned after inserts into the model
200 if( mEventData->mUpdateCursorPosition )
202 // Updates the cursor position and scrolls the text to make it visible.
204 UpdateCursorPosition();
206 if( mEventData->mScrollAfterUpdatePosition )
208 const Vector2& primaryCursorPosition = mEventData->mDecorator->GetPosition( PRIMARY_CURSOR );
210 ScrollToMakePositionVisible( primaryCursorPosition );
211 mEventData->mScrollAfterUpdatePosition = false;
214 mEventData->mDecoratorUpdated = true;
215 mEventData->mUpdateCursorPosition = false;
217 else if( mEventData->mScrollAfterDelete )
219 ScrollTextToMatchCursor();
220 mEventData->mDecoratorUpdated = true;
221 mEventData->mScrollAfterDelete = false;
225 bool leftScroll = false;
226 bool rightScroll = false;
228 if( mEventData->mUpdateLeftSelectionPosition )
230 UpdateSelectionHandle( LEFT_SELECTION_HANDLE );
232 if( mEventData->mScrollAfterUpdatePosition )
234 const Vector2& leftHandlePosition = mEventData->mDecorator->GetPosition( LEFT_SELECTION_HANDLE );
236 ScrollToMakePositionVisible( leftHandlePosition );
241 mEventData->mDecoratorUpdated = true;
242 mEventData->mUpdateLeftSelectionPosition = false;
245 if( mEventData->mUpdateRightSelectionPosition )
247 UpdateSelectionHandle( RIGHT_SELECTION_HANDLE );
249 if( mEventData->mScrollAfterUpdatePosition )
251 const Vector2& rightHandlePosition = mEventData->mDecorator->GetPosition( RIGHT_SELECTION_HANDLE );
253 ScrollToMakePositionVisible( rightHandlePosition );
258 mEventData->mDecoratorUpdated = true;
259 mEventData->mUpdateRightSelectionPosition = false;
262 if( leftScroll || rightScroll )
264 mEventData->mScrollAfterUpdatePosition = false;
268 mEventData->mEventQueue.clear();
270 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::ProcessInputEvents\n" );
272 const bool decoratorUpdated = mEventData->mDecoratorUpdated;
273 mEventData->mDecoratorUpdated = false;
275 return decoratorUpdated;
278 void Controller::Impl::UpdateModel( OperationsMask operationsRequired )
280 DALI_LOG_INFO( gLogFilter, Debug::General, "Controller::UpdateModel\n" );
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 mMetrics->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 )
466 DALI_LOG_INFO( gLogFilter, Debug::Concise, "Controller::GetDefaultFonts font family(%s)\n", mFontDefaults->mFontDescription.family.c_str() );
468 fontRun.characterRun.characterIndex = 0;
469 fontRun.characterRun.numberOfCharacters = numberOfCharacters;
470 fontRun.fontId = mFontDefaults->GetFontId( mFontClient );
471 fontRun.isDefault = true;
473 fonts.PushBack( fontRun );
477 float Controller::Impl::GetDefaultFontLineHeight()
479 FontId defaultFontId = 0u;
480 if( NULL == mFontDefaults )
482 TextAbstraction::FontDescription fontDescription;
483 defaultFontId = mFontClient.GetFontId( fontDescription );
487 defaultFontId = mFontDefaults->GetFontId( mFontClient );
490 Text::FontMetrics fontMetrics;
491 mMetrics->GetFontMetrics( defaultFontId, fontMetrics );
493 return( fontMetrics.ascender - fontMetrics.descender );
496 void Controller::Impl::OnCursorKeyEvent( const Event& event )
498 if( NULL == mEventData )
500 // Nothing to do if there is no text input.
504 int keyCode = event.p1.mInt;
506 if( Dali::DALI_KEY_CURSOR_LEFT == keyCode )
508 if( mEventData->mPrimaryCursorPosition > 0u )
510 mEventData->mPrimaryCursorPosition = CalculateNewCursorIndex( mEventData->mPrimaryCursorPosition - 1u );
513 else if( Dali::DALI_KEY_CURSOR_RIGHT == keyCode )
515 if( mLogicalModel->mText.Count() > mEventData->mPrimaryCursorPosition )
517 mEventData->mPrimaryCursorPosition = CalculateNewCursorIndex( mEventData->mPrimaryCursorPosition );
520 else if( Dali::DALI_KEY_CURSOR_UP == keyCode )
524 else if( Dali::DALI_KEY_CURSOR_DOWN == keyCode )
529 mEventData->mUpdateCursorPosition = true;
530 mEventData->mScrollAfterUpdatePosition = true;
533 void Controller::Impl::OnTapEvent( const Event& event )
535 if( NULL != mEventData )
537 const unsigned int tapCount = event.p1.mUint;
541 if( IsShowingRealText() )
543 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
544 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
546 mEventData->mPrimaryCursorPosition = GetClosestCursorIndex( xPosition,
549 // When the cursor position is changing, delay cursor blinking
550 mEventData->mDecorator->DelayCursorBlink();
554 mEventData->mPrimaryCursorPosition = 0u;
557 mEventData->mUpdateCursorPosition = true;
558 mEventData->mScrollAfterUpdatePosition = true;
563 void Controller::Impl::OnPanEvent( const Event& event )
565 if( NULL == mEventData )
567 // Nothing to do if there is no text input.
571 int state = event.p1.mInt;
573 if( Gesture::Started == state ||
574 Gesture::Continuing == state )
576 const Vector2& actualSize = mVisualModel->GetActualSize();
577 const Vector2 currentScroll = mEventData->mScrollPosition;
579 if( mEventData->mHorizontalScrollingEnabled )
581 const float displacementX = event.p2.mFloat;
582 mEventData->mScrollPosition.x += displacementX;
584 ClampHorizontalScroll( actualSize );
587 if( mEventData->mVerticalScrollingEnabled )
589 const float displacementY = event.p3.mFloat;
590 mEventData->mScrollPosition.y += displacementY;
592 ClampVerticalScroll( actualSize );
595 if( mEventData->mDecorator )
597 mEventData->mDecorator->UpdatePositions( mEventData->mScrollPosition - currentScroll );
602 void Controller::Impl::OnLongPressEvent( const Event& event )
604 if ( EventData::EDITING == mEventData->mState )
606 ChangeState ( EventData::EDITING_WITH_POPUP );
607 mEventData->mDecoratorUpdated = true;
611 void Controller::Impl::OnHandleEvent( const Event& event )
613 if( NULL == mEventData )
615 // Nothing to do if there is no text input.
619 const unsigned int state = event.p1.mUint;
620 const bool handleStopScrolling = ( HANDLE_STOP_SCROLLING == state );
622 if( HANDLE_PRESSED == state )
624 // The event.p2 and event.p3 are in decorator coords. Need to transforms to text coords.
625 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
626 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
628 const CharacterIndex handleNewPosition = GetClosestCursorIndex( xPosition, yPosition );
630 if( Event::GRAB_HANDLE_EVENT == event.type )
632 ChangeState ( EventData::GRAB_HANDLE_PANNING );
634 if( handleNewPosition != mEventData->mPrimaryCursorPosition )
636 mEventData->mPrimaryCursorPosition = handleNewPosition;
637 mEventData->mUpdateCursorPosition = true;
640 else if( Event::LEFT_SELECTION_HANDLE_EVENT == event.type )
642 ChangeState ( EventData::SELECTION_HANDLE_PANNING );
644 if( ( handleNewPosition != mEventData->mLeftSelectionPosition ) &&
645 ( handleNewPosition != mEventData->mRightSelectionPosition ) )
647 mEventData->mLeftSelectionPosition = handleNewPosition;
649 RepositionSelectionHandles( mEventData->mLeftSelectionPosition,
650 mEventData->mRightSelectionPosition );
652 mEventData->mUpdateLeftSelectionPosition = true;
655 else if( Event::RIGHT_SELECTION_HANDLE_EVENT == event.type )
657 ChangeState ( EventData::SELECTION_HANDLE_PANNING );
659 if( ( handleNewPosition != mEventData->mRightSelectionPosition ) &&
660 ( handleNewPosition != mEventData->mLeftSelectionPosition ) )
662 mEventData->mRightSelectionPosition = handleNewPosition;
664 RepositionSelectionHandles( mEventData->mLeftSelectionPosition,
665 mEventData->mRightSelectionPosition );
667 mEventData->mUpdateRightSelectionPosition = true;
670 } // end ( HANDLE_PRESSED == state )
671 else if( ( HANDLE_RELEASED == state ) ||
672 handleStopScrolling )
674 CharacterIndex handlePosition = 0u;
675 if( handleStopScrolling )
677 // The event.p2 and event.p3 are in decorator coords. Need to transforms to text coords.
678 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
679 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
681 handlePosition = GetClosestCursorIndex( xPosition, yPosition );
684 if( Event::GRAB_HANDLE_EVENT == event.type )
686 mEventData->mUpdateCursorPosition = true;
688 ChangeState( EventData::EDITING_WITH_POPUP );
690 if( handleStopScrolling )
692 mEventData->mScrollAfterUpdatePosition = mEventData->mPrimaryCursorPosition != handlePosition;
693 mEventData->mPrimaryCursorPosition = handlePosition;
696 else if( Event::LEFT_SELECTION_HANDLE_EVENT == event.type )
698 ChangeState( EventData::SELECTING );
700 if( handleStopScrolling )
702 mEventData->mUpdateLeftSelectionPosition = ( mEventData->mRightSelectionPosition != handlePosition );
703 mEventData->mScrollAfterUpdatePosition = mEventData->mUpdateLeftSelectionPosition;
705 if( mEventData->mUpdateLeftSelectionPosition )
707 mEventData->mLeftSelectionPosition = handlePosition;
709 RepositionSelectionHandles( mEventData->mLeftSelectionPosition,
710 mEventData->mRightSelectionPosition );
714 else if( Event::RIGHT_SELECTION_HANDLE_EVENT == event.type )
716 ChangeState( EventData::SELECTING );
718 if( handleStopScrolling )
720 mEventData->mUpdateRightSelectionPosition = ( mEventData->mLeftSelectionPosition != handlePosition );
721 mEventData->mScrollAfterUpdatePosition = mEventData->mUpdateRightSelectionPosition;
722 if( mEventData->mUpdateRightSelectionPosition )
724 mEventData->mRightSelectionPosition = handlePosition;
725 RepositionSelectionHandles( mEventData->mLeftSelectionPosition,
726 mEventData->mRightSelectionPosition );
731 mEventData->mDecoratorUpdated = true;
732 } // end ( ( HANDLE_RELEASED == state ) || ( HANDLE_STOP_SCROLLING == state ) )
733 else if( HANDLE_SCROLLING == state )
735 const float xSpeed = event.p2.mFloat;
736 const Vector2& actualSize = mVisualModel->GetActualSize();
737 const Vector2 currentScrollPosition = mEventData->mScrollPosition;
739 mEventData->mScrollPosition.x += xSpeed;
741 ClampHorizontalScroll( actualSize );
743 bool endOfScroll = false;
744 if( Vector2::ZERO == ( currentScrollPosition - mEventData->mScrollPosition ) )
746 // Notify the decorator there is no more text to scroll.
747 // The decorator won't send more scroll events.
748 mEventData->mDecorator->NotifyEndOfScroll();
749 // Still need to set the position of the handle.
753 // Set the position of the handle.
754 const bool scrollRightDirection = xSpeed > 0.f;
755 const bool leftSelectionHandleEvent = Event::LEFT_SELECTION_HANDLE_EVENT == event.type;
756 const bool rightSelectionHandleEvent = Event::RIGHT_SELECTION_HANDLE_EVENT == event.type;
758 if( Event::GRAB_HANDLE_EVENT == event.type )
760 ChangeState( EventData::GRAB_HANDLE_PANNING );
762 Vector2 position = mEventData->mDecorator->GetPosition( GRAB_HANDLE );
764 // Position the grag handle close to either the left or right edge.
765 position.x = scrollRightDirection ? 0.f : mVisualModel->mControlSize.width;
767 // Get the new handle position.
768 // The grab handle's position is in decorator coords. Need to transforms to text coords.
769 const CharacterIndex handlePosition = GetClosestCursorIndex( position.x - mEventData->mScrollPosition.x - mAlignmentOffset.x,
770 position.y - mEventData->mScrollPosition.y - mAlignmentOffset.y );
772 mEventData->mUpdateCursorPosition = mEventData->mPrimaryCursorPosition != handlePosition;
773 mEventData->mScrollAfterUpdatePosition = mEventData->mUpdateCursorPosition;
774 mEventData->mPrimaryCursorPosition = handlePosition;
776 else if( leftSelectionHandleEvent || rightSelectionHandleEvent )
778 // TODO: This is recalculating the selection box every time the text is scrolled with the selection handles.
779 // Think if something can be done to save power.
781 ChangeState( EventData::SELECTION_HANDLE_PANNING );
783 Vector2 position = mEventData->mDecorator->GetPosition( leftSelectionHandleEvent ? Text::LEFT_SELECTION_HANDLE : Text::RIGHT_SELECTION_HANDLE );
785 // Position the selection handle close to either the left or right edge.
786 position.x = scrollRightDirection ? 0.f : mVisualModel->mControlSize.width;
788 // Get the new handle position.
789 // The selection handle's position is in decorator coords. Need to transforms to text coords.
790 const CharacterIndex handlePosition = GetClosestCursorIndex( position.x - mEventData->mScrollPosition.x - mAlignmentOffset.x,
791 position.y - mEventData->mScrollPosition.y - mAlignmentOffset.y );
793 if( leftSelectionHandleEvent )
795 const bool differentHandles = ( mEventData->mLeftSelectionPosition != handlePosition ) && ( mEventData->mRightSelectionPosition != handlePosition );
796 mEventData->mUpdateLeftSelectionPosition = endOfScroll || differentHandles;
797 if( differentHandles )
799 mEventData->mLeftSelectionPosition = handlePosition;
804 const bool differentHandles = ( mEventData->mRightSelectionPosition != handlePosition ) && ( mEventData->mLeftSelectionPosition != handlePosition );
805 mEventData->mUpdateRightSelectionPosition = endOfScroll || differentHandles;
806 if( differentHandles )
808 mEventData->mRightSelectionPosition = handlePosition;
812 if( mEventData->mUpdateLeftSelectionPosition || mEventData->mUpdateRightSelectionPosition )
814 RepositionSelectionHandles( mEventData->mLeftSelectionPosition,
815 mEventData->mRightSelectionPosition );
817 mEventData->mScrollAfterUpdatePosition = true;
820 mEventData->mDecoratorUpdated = true;
821 } // end ( HANDLE_SCROLLING == state )
824 void Controller::Impl::OnSelectEvent( const Event& event )
826 if( NULL == mEventData )
828 // Nothing to do if there is no text.
832 if( mEventData->mSelectionEnabled )
834 // The event.p2 and event.p3 are in decorator coords. Need to transforms to text coords.
835 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
836 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
838 const CharacterIndex leftPosition = mEventData->mLeftSelectionPosition;
839 const CharacterIndex rightPosition = mEventData->mRightSelectionPosition;
841 RepositionSelectionHandles( xPosition,
844 mEventData->mUpdateLeftSelectionPosition = leftPosition != mEventData->mLeftSelectionPosition;
845 mEventData->mUpdateRightSelectionPosition = rightPosition != mEventData->mRightSelectionPosition;
847 mEventData->mScrollAfterUpdatePosition = ( ( mEventData->mUpdateLeftSelectionPosition || mEventData->mUpdateRightSelectionPosition ) &&
848 ( mEventData->mLeftSelectionPosition != mEventData->mRightSelectionPosition ) );
852 void Controller::Impl::OnSelectAllEvent()
854 if( NULL == mEventData )
856 // Nothing to do if there is no text.
860 if( mEventData->mSelectionEnabled )
862 RepositionSelectionHandles( 0u,
863 mLogicalModel->mText.Count() );
865 mEventData->mScrollAfterUpdatePosition = true;
866 mEventData->mUpdateLeftSelectionPosition = true;
867 mEventData->mUpdateRightSelectionPosition = true;
871 void Controller::Impl::RetrieveSelection( std::string& selectedText, bool deleteAfterRetreival )
873 if( mEventData->mLeftSelectionPosition == mEventData->mRightSelectionPosition )
875 // Nothing to select if handles are in the same place.
880 const bool handlesCrossed = mEventData->mLeftSelectionPosition > mEventData->mRightSelectionPosition;
882 //Get start and end position of selection
883 uint32_t startOfSelectedText = handlesCrossed ? mEventData->mRightSelectionPosition : mEventData->mLeftSelectionPosition;
884 uint32_t lengthOfSelectedText = ( handlesCrossed ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition ) - startOfSelectedText;
886 // Validate the start and end selection points
887 if( ( startOfSelectedText + lengthOfSelectedText ) <= mLogicalModel->mText.Count() )
889 //Get text as a UTF8 string
890 Vector<Character>& utf32Characters = mLogicalModel->mText;
892 Utf32ToUtf8( &utf32Characters[startOfSelectedText], lengthOfSelectedText, selectedText );
894 if ( deleteAfterRetreival ) // Only delete text if copied successfully
896 // Delete text between handles
897 Vector<Character>& currentText = mLogicalModel->mText;
899 Vector<Character>::Iterator first = currentText.Begin() + startOfSelectedText;
900 Vector<Character>::Iterator last = first + lengthOfSelectedText;
901 currentText.Erase( first, last );
903 // Scroll after delete.
904 mEventData->mPrimaryCursorPosition = handlesCrossed ? mEventData->mRightSelectionPosition : mEventData->mLeftSelectionPosition;
905 mEventData->mScrollAfterDelete = true;
906 mEventData->mDecoratorUpdated = true;
911 void Controller::Impl::ShowClipboard()
915 mClipboard.ShowClipboard();
919 void Controller::Impl::HideClipboard()
923 mClipboard.HideClipboard();
927 bool Controller::Impl::CopyStringToClipboard( std::string& source )
929 //Send string to clipboard
930 return ( mClipboard && mClipboard.SetItem( source ) );
933 void Controller::Impl::SendSelectionToClipboard( bool deleteAfterSending )
935 std::string selectedText;
936 RetrieveSelection( selectedText, deleteAfterSending );
937 CopyStringToClipboard( selectedText );
938 ChangeState( EventData::EDITING );
941 void Controller::Impl::GetTextFromClipboard( unsigned int itemIndex, std::string& retreivedString )
945 retreivedString = mClipboard.GetItem( itemIndex );
949 void Controller::Impl::RepositionSelectionHandles( CharacterIndex selectionStart, CharacterIndex selectionEnd )
951 if( selectionStart == selectionEnd )
953 // Nothing to select if handles are in the same place.
957 mEventData->mDecorator->ClearHighlights();
959 mEventData->mLeftSelectionPosition = selectionStart;
960 mEventData->mRightSelectionPosition = selectionEnd;
962 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
963 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
964 const GlyphInfo* const glyphsBuffer = mVisualModel->mGlyphs.Begin();
965 const Vector2* const positionsBuffer = mVisualModel->mGlyphPositions.Begin();
966 const Length* const charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
967 const CharacterIndex* const glyphToCharacterBuffer = mVisualModel->mGlyphsToCharacters.Begin();
968 const CharacterDirection* const modelCharacterDirectionsBuffer = ( 0u != mLogicalModel->mCharacterDirections.Count() ) ? mLogicalModel->mCharacterDirections.Begin() : NULL;
970 // TODO: Better algorithm to create the highlight box.
973 // Get the height of the line.
974 const Vector<LineRun>& lines = mVisualModel->mLines;
975 const LineRun& firstLine = *lines.Begin();
976 const float height = firstLine.ascender + -firstLine.descender;
978 const bool isLastCharacter = selectionEnd >= mLogicalModel->mText.Count();
979 const bool startDirection = ( ( NULL == modelCharacterDirectionsBuffer ) ? false : *( modelCharacterDirectionsBuffer + selectionStart ) );
980 const bool endDirection = ( ( NULL == modelCharacterDirectionsBuffer ) ? false : *( modelCharacterDirectionsBuffer + ( selectionEnd - ( isLastCharacter ? 1u : 0u ) ) ) );
982 // Swap the indices if the start is greater than the end.
983 const bool indicesSwapped = selectionStart > selectionEnd;
985 // Tell the decorator to flip the selection handles if needed.
986 mEventData->mDecorator->SetSelectionHandleFlipState( indicesSwapped, startDirection, endDirection );
990 std::swap( selectionStart, selectionEnd );
993 // Get the indices to the first and last selected glyphs.
994 const CharacterIndex selectionEndMinusOne = selectionEnd - 1u;
995 const GlyphIndex glyphStart = *( charactersToGlyphBuffer + selectionStart );
996 const Length numberOfGlyphs = *( glyphsPerCharacterBuffer + selectionEndMinusOne );
997 const GlyphIndex glyphEnd = *( charactersToGlyphBuffer + selectionEndMinusOne ) + ( ( numberOfGlyphs > 0 ) ? numberOfGlyphs - 1u : 0u );
999 // Check if the first glyph is a ligature that must be broken like Latin ff, fi, or Arabic ï»», etc which needs special code.
1000 const Length numberOfCharactersStart = *( charactersPerGlyphBuffer + glyphStart );
1001 bool splitStartGlyph = ( numberOfCharactersStart > 1u ) && HasLigatureMustBreak( mLogicalModel->GetScript( selectionStart ) );
1003 // Check if the last glyph is a ligature that must be broken like Latin ff, fi, or Arabic ï»», etc which needs special code.
1004 const Length numberOfCharactersEnd = *( charactersPerGlyphBuffer + glyphEnd );
1005 bool splitEndGlyph = ( glyphStart != glyphEnd ) && ( numberOfCharactersEnd > 1u ) && HasLigatureMustBreak( mLogicalModel->GetScript( selectionEndMinusOne ) );
1007 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
1009 // Traverse the glyphs.
1010 for( GlyphIndex index = glyphStart; index <= glyphEnd; ++index )
1012 const GlyphInfo& glyph = *( glyphsBuffer + index );
1013 const Vector2& position = *( positionsBuffer + index );
1015 if( splitStartGlyph )
1017 // 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.
1019 const float glyphAdvance = glyph.advance / static_cast<float>( numberOfCharactersStart );
1020 const CharacterIndex interGlyphIndex = selectionStart - *( glyphToCharacterBuffer + glyphStart );
1021 // Get the direction of the character.
1022 CharacterDirection isCurrentRightToLeft = false;
1023 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
1025 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + selectionStart );
1028 // The end point could be in the middle of the ligature.
1029 // Calculate the number of characters selected.
1030 const Length numberOfCharacters = ( glyphStart == glyphEnd ) ? ( selectionEnd - selectionStart ) : ( numberOfCharactersStart - interGlyphIndex );
1032 const float xPosition = position.x - glyph.xBearing + offset.x + glyphAdvance * static_cast<float>( isCurrentRightToLeft ? ( numberOfCharactersStart - interGlyphIndex - numberOfCharacters ) : interGlyphIndex );
1034 mEventData->mDecorator->AddHighlight( xPosition,
1036 xPosition + static_cast<float>( numberOfCharacters ) * glyphAdvance,
1037 offset.y + height );
1039 splitStartGlyph = false;
1043 if( splitEndGlyph && ( index == glyphEnd ) )
1045 // 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.
1047 const float glyphAdvance = glyph.advance / static_cast<float>( numberOfCharactersEnd );
1048 const CharacterIndex interGlyphIndex = selectionEnd - *( glyphToCharacterBuffer + glyphEnd );
1049 // Get the direction of the character.
1050 CharacterDirection isCurrentRightToLeft = false;
1051 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
1053 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + selectionEnd );
1056 const Length numberOfCharacters = numberOfCharactersEnd - interGlyphIndex;
1058 const float xPosition = position.x - glyph.xBearing + offset.x + ( isCurrentRightToLeft ? ( glyphAdvance * static_cast<float>( numberOfCharacters ) ) : 0.f );
1059 mEventData->mDecorator->AddHighlight( xPosition,
1061 xPosition + static_cast<float>( interGlyphIndex ) * glyphAdvance,
1062 offset.y + height );
1064 splitEndGlyph = false;
1068 const float xPosition = position.x - glyph.xBearing + offset.x;
1069 mEventData->mDecorator->AddHighlight( xPosition,
1071 xPosition + glyph.advance,
1072 offset.y + height );
1075 CursorInfo primaryCursorInfo;
1076 GetCursorPosition( mEventData->mLeftSelectionPosition,
1077 primaryCursorInfo );
1079 CursorInfo secondaryCursorInfo;
1080 GetCursorPosition( mEventData->mRightSelectionPosition,
1081 secondaryCursorInfo );
1083 const Vector2 primaryPosition = primaryCursorInfo.primaryPosition + offset;
1084 const Vector2 secondaryPosition = secondaryCursorInfo.primaryPosition + offset;
1086 mEventData->mDecorator->SetPosition( LEFT_SELECTION_HANDLE, primaryPosition.x, primaryPosition.y, primaryCursorInfo.lineHeight );
1088 mEventData->mDecorator->SetPosition( RIGHT_SELECTION_HANDLE, secondaryPosition.x, secondaryPosition.y, secondaryCursorInfo.lineHeight );
1090 // Cursor to be positioned at end of selection so if selection interrupted and edit mode restarted the cursor will be at end of selection
1091 mEventData->mPrimaryCursorPosition = ( indicesSwapped ) ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition;
1093 // Set the flag to update the decorator.
1094 mEventData->mDecoratorUpdated = true;
1097 void Controller::Impl::RepositionSelectionHandles( float visualX, float visualY )
1099 if( NULL == mEventData )
1101 // Nothing to do if there is no text input.
1105 if( IsShowingPlaceholderText() )
1107 // Nothing to do if there is the place-holder text.
1111 const Length numberOfGlyphs = mVisualModel->mGlyphs.Count();
1112 const Length numberOfLines = mVisualModel->mLines.Count();
1113 if( 0 == numberOfGlyphs ||
1114 0 == numberOfLines )
1116 // Nothing to do if there is no text.
1120 // Find which word was selected
1121 CharacterIndex selectionStart( 0 );
1122 CharacterIndex selectionEnd( 0 );
1123 FindSelectionIndices( visualX, visualY, selectionStart, selectionEnd );
1124 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "%p selectionStart %d selectionEnd %d\n", this, selectionStart, selectionEnd );
1126 if( selectionStart == selectionEnd )
1128 ChangeState( EventData::EDITING );
1129 // Nothing to select. i.e. a white space, out of bounds
1133 RepositionSelectionHandles( selectionStart, selectionEnd );
1136 void Controller::Impl::SetPopupButtons()
1139 * Sets the Popup buttons to be shown depending on State.
1141 * If SELECTING : CUT & COPY + ( PASTE & CLIPBOARD if content available to paste )
1143 * If EDITING_WITH_POPUP : SELECT & SELECT_ALL
1146 TextSelectionPopup::Buttons buttonsToShow = TextSelectionPopup::NONE;
1148 if ( ( EventData::SELECTING == mEventData->mState ) || ( EventData::SELECTION_CHANGED == mEventData->mState ) )
1150 buttonsToShow = TextSelectionPopup::Buttons( TextSelectionPopup::CUT | TextSelectionPopup::COPY );
1152 if ( !IsClipboardEmpty() )
1154 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
1155 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::CLIPBOARD ) );
1158 if ( !mEventData->mAllTextSelected )
1160 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::SELECT_ALL ) );
1163 else if ( EventData::EDITING_WITH_POPUP == mEventData->mState )
1165 if ( mLogicalModel->mText.Count() && !IsShowingPlaceholderText())
1167 buttonsToShow = TextSelectionPopup::Buttons( TextSelectionPopup::SELECT | TextSelectionPopup::SELECT_ALL );
1170 if ( !IsClipboardEmpty() )
1172 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
1173 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::CLIPBOARD ) );
1177 mEventData->mDecorator->SetEnabledPopupButtons( buttonsToShow );
1180 void Controller::Impl::ChangeState( EventData::State newState )
1182 if( NULL == mEventData )
1184 // Nothing to do if there is no text input.
1188 if( mEventData->mState != newState )
1190 mEventData->mState = newState;
1192 if( EventData::INACTIVE == mEventData->mState )
1194 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
1195 mEventData->mDecorator->StopCursorBlink();
1196 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1197 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1198 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1199 mEventData->mDecorator->SetPopupActive( false );
1200 mEventData->mDecoratorUpdated = true;
1203 else if ( EventData::INTERRUPTED == mEventData->mState)
1205 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1206 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1207 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1208 mEventData->mDecorator->SetPopupActive( false );
1209 mEventData->mDecoratorUpdated = true;
1212 else if ( EventData::SELECTING == mEventData->mState )
1214 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
1215 mEventData->mDecorator->StopCursorBlink();
1216 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1217 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true );
1218 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true );
1219 if( mEventData->mGrabHandlePopupEnabled )
1222 mEventData->mDecorator->SetPopupActive( true );
1224 mEventData->mDecoratorUpdated = true;
1226 else if ( EventData::SELECTION_CHANGED == mEventData->mState )
1228 if( mEventData->mGrabHandlePopupEnabled )
1231 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
1232 mEventData->mDecorator->SetPopupActive( true );
1234 mEventData->mDecoratorUpdated = true;
1236 else if( EventData::EDITING == mEventData->mState )
1238 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1239 if( mEventData->mCursorBlinkEnabled )
1241 mEventData->mDecorator->StartCursorBlink();
1243 // Grab handle is not shown until a tap is received whilst EDITING
1244 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1245 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1246 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1247 if( mEventData->mGrabHandlePopupEnabled )
1249 mEventData->mDecorator->SetPopupActive( false );
1251 mEventData->mDecoratorUpdated = true;
1254 else if( EventData::EDITING_WITH_POPUP == mEventData->mState )
1256 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1257 if( mEventData->mCursorBlinkEnabled )
1259 mEventData->mDecorator->StartCursorBlink();
1261 if( mEventData->mSelectionEnabled )
1263 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1264 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1268 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
1270 if( mEventData->mGrabHandlePopupEnabled )
1273 mEventData->mDecorator->SetPopupActive( true );
1276 mEventData->mDecoratorUpdated = true;
1278 else if( EventData::EDITING_WITH_GRAB_HANDLE == mEventData->mState )
1280 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1281 if( mEventData->mCursorBlinkEnabled )
1283 mEventData->mDecorator->StartCursorBlink();
1285 // Grab handle is not shown until a tap is received whilst EDITING
1286 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
1287 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1288 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1289 if( mEventData->mGrabHandlePopupEnabled )
1291 mEventData->mDecorator->SetPopupActive( false );
1293 mEventData->mDecoratorUpdated = true;
1296 else if ( EventData::SELECTION_HANDLE_PANNING == mEventData->mState )
1298 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
1299 mEventData->mDecorator->StopCursorBlink();
1300 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1301 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true );
1302 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true );
1303 if( mEventData->mGrabHandlePopupEnabled )
1305 mEventData->mDecorator->SetPopupActive( false );
1307 mEventData->mDecoratorUpdated = true;
1309 else if ( EventData::GRAB_HANDLE_PANNING == mEventData->mState )
1311 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1312 if( mEventData->mCursorBlinkEnabled )
1314 mEventData->mDecorator->StartCursorBlink();
1316 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
1317 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1318 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1319 if( mEventData->mGrabHandlePopupEnabled )
1321 mEventData->mDecorator->SetPopupActive( false );
1323 mEventData->mDecoratorUpdated = true;
1328 LineIndex Controller::Impl::GetClosestLine( float y ) const
1330 float totalHeight = 0.f;
1331 LineIndex lineIndex = 0u;
1333 const Vector<LineRun>& lines = mVisualModel->mLines;
1334 for( LineIndex endLine = lines.Count();
1335 lineIndex < endLine;
1338 const LineRun& lineRun = lines[lineIndex];
1339 totalHeight += lineRun.ascender + -lineRun.descender;
1340 if( y < totalHeight )
1346 if( lineIndex == 0 )
1354 void Controller::Impl::FindSelectionIndices( float visualX, float visualY, CharacterIndex& startIndex, CharacterIndex& endIndex )
1356 CharacterIndex hitCharacter = GetClosestCursorIndex( visualX, visualY );
1357 DALI_ASSERT_DEBUG( hitCharacter <= mLogicalModel->mText.Count() && "GetClosestCursorIndex returned out of bounds index" );
1359 if ( mLogicalModel->mText.Count() == 0 )
1361 return; // if model empty
1364 if( hitCharacter >= mLogicalModel->mText.Count() )
1366 // Closest hit character is the last character.
1367 if ( hitCharacter == mLogicalModel->mText.Count() )
1369 hitCharacter--; //Hit character index set to last character in logical model
1373 // hitCharacter is out of bounds
1378 startIndex = hitCharacter;
1379 endIndex = hitCharacter;
1381 if( !TextAbstraction::IsWhiteSpace( mLogicalModel->mText[hitCharacter] ) )
1383 // Find the start and end of the text
1384 for( startIndex = hitCharacter; startIndex > 0; --startIndex )
1386 Character charCode = mLogicalModel->mText[ startIndex-1 ];
1387 if( TextAbstraction::IsWhiteSpace( charCode ) )
1392 const CharacterIndex pastTheEnd = mLogicalModel->mText.Count();
1393 for( endIndex = hitCharacter + 1u; endIndex < pastTheEnd; ++endIndex )
1395 Character charCode = mLogicalModel->mText[ endIndex ];
1396 if( TextAbstraction::IsWhiteSpace( charCode ) )
1404 CharacterIndex Controller::Impl::GetClosestCursorIndex( float visualX,
1407 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "GetClosestCursorIndex %p closest visualX %f visualY %f\n", this, visualX, visualY );
1409 if( NULL == mEventData )
1411 // Nothing to do if there is no text input.
1415 CharacterIndex logicalIndex = 0u;
1417 const Length numberOfGlyphs = mVisualModel->mGlyphs.Count();
1418 const Length numberOfLines = mVisualModel->mLines.Count();
1419 if( 0 == numberOfGlyphs ||
1420 0 == numberOfLines )
1422 return logicalIndex;
1425 // Find which line is closest
1426 const LineIndex lineIndex = GetClosestLine( visualY );
1427 const LineRun& line = mVisualModel->mLines[lineIndex];
1429 // Get the positions of the glyphs.
1430 const Vector<Vector2>& positions = mVisualModel->mGlyphPositions;
1431 const Vector2* const positionsBuffer = positions.Begin();
1433 // Get the visual to logical conversion tables.
1434 const CharacterIndex* const visualToLogicalBuffer = ( 0u != mLogicalModel->mVisualToLogicalMap.Count() ) ? mLogicalModel->mVisualToLogicalMap.Begin() : NULL;
1435 const CharacterIndex* const visualToLogicalCursorBuffer = mLogicalModel->mVisualToLogicalCursorMap.Begin();
1437 // Get the character to glyph conversion table.
1438 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
1440 // Get the glyphs per character table.
1441 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
1442 const Length* const charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
1444 // If the vector is void, there is no right to left characters.
1445 const bool hasRightToLeftCharacters = NULL != visualToLogicalBuffer;
1447 const CharacterIndex startCharacter = line.characterRun.characterIndex;
1448 const CharacterIndex endCharacter = line.characterRun.characterIndex + line.characterRun.numberOfCharacters;
1449 DALI_ASSERT_DEBUG( endCharacter <= mLogicalModel->mText.Count() && "Invalid line info" );
1451 // Whether there is a hit on a glyph.
1452 bool matched = false;
1454 // Traverses glyphs in visual order. To do that use the visual to logical conversion table.
1455 CharacterIndex visualIndex = startCharacter;
1456 for( ; !matched && ( visualIndex < endCharacter ); ++visualIndex )
1458 // The character in logical order.
1459 const CharacterIndex characterLogicalOrderIndex = hasRightToLeftCharacters ? *( visualToLogicalBuffer + visualIndex ) : visualIndex;
1461 // Get the script of the character.
1462 const Script script = mLogicalModel->GetScript( characterLogicalOrderIndex );
1464 // The first glyph for that character in logical order.
1465 const GlyphIndex glyphLogicalOrderIndex = *( charactersToGlyphBuffer + characterLogicalOrderIndex );
1466 // The number of glyphs for that character
1467 const Length numberOfGlyphs = *( glyphsPerCharacterBuffer + characterLogicalOrderIndex );
1469 // Get the metrics for the group of glyphs.
1470 GlyphMetrics glyphMetrics;
1471 GetGlyphsMetrics( glyphLogicalOrderIndex,
1477 const Vector2& position = *( positionsBuffer + glyphLogicalOrderIndex );
1479 // Prevents to jump the whole Latin ligatures like fi, ff, or Arabic ï»»...
1480 const Length numberOfCharactersInLigature = HasLigatureMustBreak( script ) ? *( charactersPerGlyphBuffer + glyphLogicalOrderIndex ) : 1u;
1481 const float glyphAdvance = glyphMetrics.advance / static_cast<float>( numberOfCharactersInLigature );
1483 for( GlyphIndex index = 0u; !matched && ( index < numberOfCharactersInLigature ); ++index )
1485 // Find the mid-point of the area containing the glyph
1486 const float glyphCenter = -glyphMetrics.xBearing + position.x + ( static_cast<float>( index ) + 0.5f ) * glyphAdvance;
1488 if( visualX < glyphCenter )
1490 visualIndex += index;
1502 // Return the logical position of the cursor in characters.
1506 visualIndex = endCharacter;
1509 logicalIndex = hasRightToLeftCharacters ? *( visualToLogicalCursorBuffer + visualIndex ) : visualIndex;
1510 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "%p closest visualIndex %d logicalIndex %d\n", this, visualIndex, logicalIndex );
1512 DALI_ASSERT_DEBUG( ( logicalIndex <= mLogicalModel->mText.Count() && logicalIndex >= 0 ) && "GetClosestCursorIndex - Out of bounds index" );
1514 return logicalIndex;
1517 void Controller::Impl::GetCursorPosition( CharacterIndex logical,
1518 CursorInfo& cursorInfo )
1520 // TODO: Check for multiline with \n, etc...
1522 // Check if the logical position is the first or the last one of the text.
1523 const bool isFirstPosition = 0u == logical;
1524 const bool isLastPosition = mLogicalModel->mText.Count() == logical;
1526 if( isFirstPosition && isLastPosition )
1528 // There is zero characters. Get the default font's line height.
1529 cursorInfo.lineHeight = GetDefaultFontLineHeight();
1530 cursorInfo.primaryCursorHeight = cursorInfo.lineHeight;
1532 cursorInfo.primaryPosition.x = 0.f;
1533 cursorInfo.primaryPosition.y = 0.f;
1535 // Nothing else to do.
1539 // 'logical' is the logical 'cursor' index.
1540 // Get the next and current logical 'character' index.
1541 const CharacterIndex nextCharacterIndex = logical;
1542 const CharacterIndex characterIndex = isFirstPosition ? logical : logical - 1u;
1544 // Get the direction of the character and the next one.
1545 const CharacterDirection* const modelCharacterDirectionsBuffer = ( 0u != mLogicalModel->mCharacterDirections.Count() ) ? mLogicalModel->mCharacterDirections.Begin() : NULL;
1547 CharacterDirection isCurrentRightToLeft = false;
1548 CharacterDirection isNextRightToLeft = false;
1549 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
1551 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + characterIndex );
1552 isNextRightToLeft = *( modelCharacterDirectionsBuffer + nextCharacterIndex );
1555 // Get the line where the character is laid-out.
1556 const LineRun* const modelLines = mVisualModel->mLines.Begin();
1558 const LineIndex lineIndex = mVisualModel->GetLineOfCharacter( characterIndex );
1559 const LineRun& line = *( modelLines + lineIndex );
1561 // Get the paragraph's direction.
1562 const CharacterDirection isRightToLeftParagraph = line.direction;
1564 // Check whether there is an alternative position:
1566 cursorInfo.isSecondaryCursor = ( !isLastPosition && ( isCurrentRightToLeft != isNextRightToLeft ) ) ||
1567 ( isLastPosition && ( isRightToLeftParagraph != isCurrentRightToLeft ) );
1569 // Set the line height.
1570 cursorInfo.lineHeight = line.ascender + -line.descender;
1572 // Calculate the primary cursor.
1574 CharacterIndex index = characterIndex;
1575 if( cursorInfo.isSecondaryCursor )
1577 // If there is a secondary position, the primary cursor may be in a different place than the logical index.
1579 if( isLastPosition )
1581 // The position of the cursor after the last character needs special
1582 // care depending on its direction and the direction of the paragraph.
1584 // Need to find the first character after the last character with the paragraph's direction.
1585 // i.e l0 l1 l2 r0 r1 should find r0.
1587 // TODO: check for more than one line!
1588 index = isRightToLeftParagraph ? line.characterRun.characterIndex : line.characterRun.characterIndex + line.characterRun.numberOfCharacters - 1u;
1589 index = mLogicalModel->GetLogicalCharacterIndex( index );
1593 index = ( isRightToLeftParagraph == isCurrentRightToLeft ) ? characterIndex : nextCharacterIndex;
1597 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
1598 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
1599 const Length* const charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
1600 const CharacterIndex* const glyphsToCharactersBuffer = mVisualModel->mGlyphsToCharacters.Begin();
1601 const Vector2* const glyphPositionsBuffer = mVisualModel->mGlyphPositions.Begin();
1603 // Convert the cursor position into the glyph position.
1604 const GlyphIndex primaryGlyphIndex = *( charactersToGlyphBuffer + index );
1605 const Length primaryNumberOfGlyphs = *( glyphsPerCharacterBuffer + index );
1606 const Length primaryNumberOfCharacters = *( charactersPerGlyphBuffer + primaryGlyphIndex );
1608 // Get the metrics for the group of glyphs.
1609 GlyphMetrics glyphMetrics;
1610 GetGlyphsMetrics( primaryGlyphIndex,
1611 primaryNumberOfGlyphs,
1616 // Whether to add the glyph's advance to the cursor position.
1617 // 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,
1618 // if the logical cursor is one, the position is the position of the first glyph and the advance is added.
1619 // A 'truth table' was build and an online Karnaugh map tool was used to simplify the logic.
1640 // Where F -> isFirstPosition
1641 // L -> isLastPosition
1642 // C -> isCurrentRightToLeft
1643 // P -> isRightToLeftParagraph
1644 // A -> Whether to add the glyph's advance.
1646 const bool addGlyphAdvance = ( ( isLastPosition && !isRightToLeftParagraph ) ||
1647 ( isFirstPosition && isRightToLeftParagraph ) ||
1648 ( !isFirstPosition && !isLastPosition && !isCurrentRightToLeft ) );
1650 float glyphAdvance = addGlyphAdvance ? glyphMetrics.advance : 0.f;
1652 if( !isLastPosition &&
1653 ( primaryNumberOfCharacters > 1u ) )
1655 const CharacterIndex firstIndex = *( glyphsToCharactersBuffer + primaryGlyphIndex );
1657 bool isCurrentRightToLeft = false;
1658 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
1660 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + index );
1663 Length numberOfGlyphAdvance = ( isFirstPosition ? 0u : 1u ) + characterIndex - firstIndex;
1664 if( isCurrentRightToLeft )
1666 numberOfGlyphAdvance = primaryNumberOfCharacters - numberOfGlyphAdvance;
1669 glyphAdvance = static_cast<float>( numberOfGlyphAdvance ) * glyphMetrics.advance / static_cast<float>( primaryNumberOfCharacters );
1672 // Get the glyph position and x bearing.
1673 const Vector2& primaryPosition = *( glyphPositionsBuffer + primaryGlyphIndex );
1675 // Set the primary cursor's height.
1676 cursorInfo.primaryCursorHeight = cursorInfo.isSecondaryCursor ? 0.5f * glyphMetrics.fontHeight : glyphMetrics.fontHeight;
1678 // Set the primary cursor's position.
1679 cursorInfo.primaryPosition.x = -glyphMetrics.xBearing + primaryPosition.x + glyphAdvance;
1680 cursorInfo.primaryPosition.y = line.ascender - glyphMetrics.ascender;
1682 // Calculate the secondary cursor.
1684 if( cursorInfo.isSecondaryCursor )
1686 // Set the secondary cursor's height.
1687 cursorInfo.secondaryCursorHeight = 0.5f * glyphMetrics.fontHeight;
1689 CharacterIndex index = characterIndex;
1690 if( !isLastPosition )
1692 index = ( isRightToLeftParagraph == isCurrentRightToLeft ) ? nextCharacterIndex : characterIndex;
1695 const GlyphIndex secondaryGlyphIndex = *( charactersToGlyphBuffer + index );
1696 const Length secondaryNumberOfGlyphs = *( glyphsPerCharacterBuffer + index );
1698 const Vector2& secondaryPosition = *( glyphPositionsBuffer + secondaryGlyphIndex );
1700 GetGlyphsMetrics( secondaryGlyphIndex,
1701 secondaryNumberOfGlyphs,
1706 // Set the secondary cursor's position.
1707 cursorInfo.secondaryPosition.x = -glyphMetrics.xBearing + secondaryPosition.x + ( isCurrentRightToLeft ? 0.f : glyphMetrics.advance );
1708 cursorInfo.secondaryPosition.y = cursorInfo.lineHeight - cursorInfo.secondaryCursorHeight - line.descender - ( glyphMetrics.fontHeight - glyphMetrics.ascender );
1712 CharacterIndex Controller::Impl::CalculateNewCursorIndex( CharacterIndex index ) const
1714 if( NULL == mEventData )
1716 // Nothing to do if there is no text input.
1720 CharacterIndex cursorIndex = mEventData->mPrimaryCursorPosition;
1722 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
1723 const Length* const charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
1725 GlyphIndex glyphIndex = *( charactersToGlyphBuffer + index );
1726 Length numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
1728 if( numberOfCharacters > 1u )
1730 const Script script = mLogicalModel->GetScript( index );
1731 if( HasLigatureMustBreak( script ) )
1733 // Prevents to jump the whole Latin ligatures like fi, ff, or Arabic ï»», ...
1734 numberOfCharacters = 1u;
1739 while( 0u == numberOfCharacters )
1742 numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
1746 if( index < mEventData->mPrimaryCursorPosition )
1748 cursorIndex -= numberOfCharacters;
1752 cursorIndex += numberOfCharacters;
1758 void Controller::Impl::UpdateCursorPosition()
1760 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "-->Controller::UpdateCursorPosition %p\n", this );
1761 if( NULL == mEventData )
1763 // Nothing to do if there is no text input.
1764 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition no event data\n" );
1768 if( IsShowingPlaceholderText() || ( 0u == mLogicalModel->mText.Count() ) )
1770 // Do not want to use the place-holder text to set the cursor position.
1772 // Use the line's height of the font's family set to set the cursor's size.
1773 // If there is no font's family set, use the default font.
1774 // Use the current alignment to place the cursor at the beginning, center or end of the box.
1776 float lineHeight = 0.f;
1778 FontId defaultFontId = 0u;
1779 if( NULL == mFontDefaults )
1781 TextAbstraction::FontDescription fontDescription;
1782 defaultFontId = mFontClient.GetFontId( fontDescription );
1786 defaultFontId = mFontDefaults->GetFontId( mFontClient );
1789 Text::FontMetrics fontMetrics;
1790 mMetrics->GetFontMetrics( defaultFontId, fontMetrics );
1792 lineHeight = fontMetrics.ascender - fontMetrics.descender;
1795 Vector2 cursorPosition;
1797 switch( mLayoutEngine.GetHorizontalAlignment() )
1799 case LayoutEngine::HORIZONTAL_ALIGN_BEGIN:
1801 cursorPosition.x = 0.f;
1804 case LayoutEngine::HORIZONTAL_ALIGN_CENTER:
1806 cursorPosition.x = floor( 0.5f * mVisualModel->mControlSize.width );
1809 case LayoutEngine::HORIZONTAL_ALIGN_END:
1811 cursorPosition.x = mVisualModel->mControlSize.width - mEventData->mDecorator->GetCursorWidth();
1816 switch( mLayoutEngine.GetVerticalAlignment() )
1818 case LayoutEngine::VERTICAL_ALIGN_TOP:
1820 cursorPosition.y = 0.f;
1823 case LayoutEngine::VERTICAL_ALIGN_CENTER:
1825 cursorPosition.y = floorf( 0.5f * ( mVisualModel->mControlSize.height - lineHeight ) );
1828 case LayoutEngine::VERTICAL_ALIGN_BOTTOM:
1830 cursorPosition.y = mVisualModel->mControlSize.height - lineHeight;
1835 mEventData->mDecorator->SetPosition( PRIMARY_CURSOR,
1843 CursorInfo cursorInfo;
1844 GetCursorPosition( mEventData->mPrimaryCursorPosition,
1847 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
1848 const Vector2 cursorPosition = cursorInfo.primaryPosition + offset;
1850 // Sets the cursor position.
1851 mEventData->mDecorator->SetPosition( PRIMARY_CURSOR,
1854 cursorInfo.primaryCursorHeight,
1855 cursorInfo.lineHeight );
1856 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Primary cursor position: %f,%f\n", cursorPosition.x, cursorPosition.y );
1858 // Sets the grab handle position.
1859 mEventData->mDecorator->SetPosition( GRAB_HANDLE,
1862 cursorInfo.lineHeight );
1864 if( cursorInfo.isSecondaryCursor )
1866 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_BOTH );
1867 mEventData->mDecorator->SetPosition( SECONDARY_CURSOR,
1868 cursorInfo.secondaryPosition.x + offset.x,
1869 cursorInfo.secondaryPosition.y + offset.y,
1870 cursorInfo.secondaryCursorHeight,
1871 cursorInfo.lineHeight );
1872 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Secondary cursor position: %f,%f\n", cursorInfo.secondaryPosition.x + offset.x, cursorInfo.secondaryPosition.y + offset.y );
1876 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1879 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition\n" );
1882 void Controller::Impl::UpdateSelectionHandle( HandleType handleType )
1884 if( ( LEFT_SELECTION_HANDLE != handleType ) &&
1885 ( RIGHT_SELECTION_HANDLE != handleType ) )
1890 const bool leftSelectionHandle = LEFT_SELECTION_HANDLE == handleType;
1891 const CharacterIndex index = leftSelectionHandle ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition;
1893 CursorInfo cursorInfo;
1894 GetCursorPosition( index,
1897 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
1898 const Vector2 cursorPosition = cursorInfo.primaryPosition + offset;
1900 // Sets the grab handle position.
1901 mEventData->mDecorator->SetPosition( handleType,
1904 cursorInfo.lineHeight );
1906 // If selection handle at start of the text and other at end of the text then all text is selected.
1907 const CharacterIndex startOfSelection = std::min( mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition );
1908 const CharacterIndex endOfSelection = std::max ( mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition );
1909 mEventData->mAllTextSelected = ( startOfSelection == 0 ) && ( endOfSelection == mLogicalModel->mText.Count() );
1912 void Controller::Impl::ClampHorizontalScroll( const Vector2& actualSize )
1914 // Clamp between -space & 0 (and the text alignment).
1915 if( actualSize.width > mVisualModel->mControlSize.width )
1917 const float space = ( actualSize.width - mVisualModel->mControlSize.width ) + mAlignmentOffset.x;
1918 mEventData->mScrollPosition.x = ( mEventData->mScrollPosition.x < -space ) ? -space : mEventData->mScrollPosition.x;
1919 mEventData->mScrollPosition.x = ( mEventData->mScrollPosition.x > -mAlignmentOffset.x ) ? -mAlignmentOffset.x : mEventData->mScrollPosition.x;
1921 mEventData->mDecoratorUpdated = true;
1925 mEventData->mScrollPosition.x = 0.f;
1929 void Controller::Impl::ClampVerticalScroll( const Vector2& actualSize )
1931 // Clamp between -space & 0 (and the text alignment).
1932 if( actualSize.height > mVisualModel->mControlSize.height )
1934 const float space = ( actualSize.height - mVisualModel->mControlSize.height ) + mAlignmentOffset.y;
1935 mEventData->mScrollPosition.y = ( mEventData->mScrollPosition.y < -space ) ? -space : mEventData->mScrollPosition.y;
1936 mEventData->mScrollPosition.y = ( mEventData->mScrollPosition.y > -mAlignmentOffset.y ) ? -mAlignmentOffset.y : mEventData->mScrollPosition.y;
1938 mEventData->mDecoratorUpdated = true;
1942 mEventData->mScrollPosition.y = 0.f;
1946 void Controller::Impl::ScrollToMakePositionVisible( const Vector2& position )
1949 bool updateDecorator = false;
1950 if( position.x < 0.f )
1952 offset.x = -position.x;
1953 mEventData->mScrollPosition.x += offset.x;
1954 updateDecorator = true;
1956 else if( position.x > mVisualModel->mControlSize.width )
1958 offset.x = mVisualModel->mControlSize.width - position.x;
1959 mEventData->mScrollPosition.x += offset.x;
1960 updateDecorator = true;
1963 if( updateDecorator && mEventData->mDecorator )
1965 mEventData->mDecorator->UpdatePositions( offset );
1968 // TODO : calculate the vertical scroll.
1971 void Controller::Impl::ScrollTextToMatchCursor()
1973 // Get the current cursor position in decorator coords.
1974 const Vector2& currentCursorPosition = mEventData->mDecorator->GetPosition( PRIMARY_CURSOR );
1976 // Calculate the new cursor position.
1977 CursorInfo cursorInfo;
1978 GetCursorPosition( mEventData->mPrimaryCursorPosition,
1981 // Calculate the offset to match the cursor position before the character was deleted.
1982 mEventData->mScrollPosition.x = currentCursorPosition.x - cursorInfo.primaryPosition.x - mAlignmentOffset.x;
1984 ClampHorizontalScroll( mVisualModel->GetActualSize() );
1986 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
1987 const Vector2 cursorPosition = cursorInfo.primaryPosition + offset;
1989 // Sets the cursor position.
1990 mEventData->mDecorator->SetPosition( PRIMARY_CURSOR,
1993 cursorInfo.primaryCursorHeight,
1994 cursorInfo.lineHeight );
1996 // Sets the grab handle position.
1997 mEventData->mDecorator->SetPosition( GRAB_HANDLE,
2000 cursorInfo.lineHeight );
2002 if( cursorInfo.isSecondaryCursor )
2004 mEventData->mDecorator->SetPosition( SECONDARY_CURSOR,
2005 cursorInfo.secondaryPosition.x + offset.x,
2006 cursorInfo.secondaryPosition.y + offset.y,
2007 cursorInfo.secondaryCursorHeight,
2008 cursorInfo.lineHeight );
2009 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Secondary cursor position: %f,%f\n", cursorInfo.secondaryPosition.x + offset.x, cursorInfo.secondaryPosition.y + offset.y );
2012 // Set which cursors are active according the state.
2013 if( ( EventData::EDITING == mEventData->mState ) ||
2014 ( EventData::EDITING_WITH_POPUP == mEventData->mState ) ||
2015 ( EventData::EDITING_WITH_GRAB_HANDLE == mEventData->mState ) ||
2016 ( EventData::GRAB_HANDLE_PANNING == mEventData->mState ) )
2018 if( cursorInfo.isSecondaryCursor )
2020 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_BOTH );
2024 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
2029 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
2033 void Controller::Impl::RequestRelayout()
2035 mControlInterface.RequestTextRelayout();
2040 } // namespace Toolkit