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 mEventData->mPrimaryCursorPosition = handlesCrossed ? mEventData->mRightSelectionPosition : mEventData->mLeftSelectionPosition;
904 mEventData->mScrollAfterDelete = true;
905 mEventData->mDecoratorUpdated = true;
909 void Controller::Impl::ShowClipboard()
913 mClipboard.ShowClipboard();
917 void Controller::Impl::HideClipboard()
921 mClipboard.HideClipboard();
925 bool Controller::Impl::CopyStringToClipboard( std::string& source )
927 //Send string to clipboard
928 return ( mClipboard && mClipboard.SetItem( source ) );
931 void Controller::Impl::SendSelectionToClipboard( bool deleteAfterSending )
933 std::string selectedText;
934 RetrieveSelection( selectedText, deleteAfterSending );
935 CopyStringToClipboard( selectedText );
936 ChangeState( EventData::EDITING );
939 void Controller::Impl::GetTextFromClipboard( unsigned int itemIndex, std::string& retreivedString )
943 retreivedString = mClipboard.GetItem( itemIndex );
947 void Controller::Impl::RepositionSelectionHandles( CharacterIndex selectionStart, CharacterIndex selectionEnd )
949 if( selectionStart == selectionEnd )
951 // Nothing to select if handles are in the same place.
955 mEventData->mDecorator->ClearHighlights();
957 mEventData->mLeftSelectionPosition = selectionStart;
958 mEventData->mRightSelectionPosition = selectionEnd;
960 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
961 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
962 const GlyphInfo* const glyphsBuffer = mVisualModel->mGlyphs.Begin();
963 const Vector2* const positionsBuffer = mVisualModel->mGlyphPositions.Begin();
964 const Length* const charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
965 const CharacterIndex* const glyphToCharacterBuffer = mVisualModel->mGlyphsToCharacters.Begin();
966 const CharacterDirection* const modelCharacterDirectionsBuffer = ( 0u != mLogicalModel->mCharacterDirections.Count() ) ? mLogicalModel->mCharacterDirections.Begin() : NULL;
968 // TODO: Better algorithm to create the highlight box.
971 // Get the height of the line.
972 const Vector<LineRun>& lines = mVisualModel->mLines;
973 const LineRun& firstLine = *lines.Begin();
974 const float height = firstLine.ascender + -firstLine.descender;
976 const bool isLastCharacter = selectionEnd >= mLogicalModel->mText.Count();
977 const bool startDirection = ( ( NULL == modelCharacterDirectionsBuffer ) ? false : *( modelCharacterDirectionsBuffer + selectionStart ) );
978 const bool endDirection = ( ( NULL == modelCharacterDirectionsBuffer ) ? false : *( modelCharacterDirectionsBuffer + ( selectionEnd - ( isLastCharacter ? 1u : 0u ) ) ) );
980 // Swap the indices if the start is greater than the end.
981 const bool indicesSwapped = selectionStart > selectionEnd;
983 // Tell the decorator to flip the selection handles if needed.
984 mEventData->mDecorator->SetSelectionHandleFlipState( indicesSwapped, startDirection, endDirection );
988 std::swap( selectionStart, selectionEnd );
991 // Get the indices to the first and last selected glyphs.
992 const CharacterIndex selectionEndMinusOne = selectionEnd - 1u;
993 const GlyphIndex glyphStart = *( charactersToGlyphBuffer + selectionStart );
994 const Length numberOfGlyphs = *( glyphsPerCharacterBuffer + selectionEndMinusOne );
995 const GlyphIndex glyphEnd = *( charactersToGlyphBuffer + selectionEndMinusOne ) + ( ( numberOfGlyphs > 0 ) ? numberOfGlyphs - 1u : 0u );
997 // Check if the first glyph is a ligature that must be broken like Latin ff, fi, or Arabic ï»», etc which needs special code.
998 const Length numberOfCharactersStart = *( charactersPerGlyphBuffer + glyphStart );
999 bool splitStartGlyph = ( numberOfCharactersStart > 1u ) && HasLigatureMustBreak( mLogicalModel->GetScript( selectionStart ) );
1001 // Check if the last glyph is a ligature that must be broken like Latin ff, fi, or Arabic ï»», etc which needs special code.
1002 const Length numberOfCharactersEnd = *( charactersPerGlyphBuffer + glyphEnd );
1003 bool splitEndGlyph = ( glyphStart != glyphEnd ) && ( numberOfCharactersEnd > 1u ) && HasLigatureMustBreak( mLogicalModel->GetScript( selectionEndMinusOne ) );
1005 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
1007 // Traverse the glyphs.
1008 for( GlyphIndex index = glyphStart; index <= glyphEnd; ++index )
1010 const GlyphInfo& glyph = *( glyphsBuffer + index );
1011 const Vector2& position = *( positionsBuffer + index );
1013 if( splitStartGlyph )
1015 // 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.
1017 const float glyphAdvance = glyph.advance / static_cast<float>( numberOfCharactersStart );
1018 const CharacterIndex interGlyphIndex = selectionStart - *( glyphToCharacterBuffer + glyphStart );
1019 // Get the direction of the character.
1020 CharacterDirection isCurrentRightToLeft = false;
1021 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
1023 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + selectionStart );
1026 // The end point could be in the middle of the ligature.
1027 // Calculate the number of characters selected.
1028 const Length numberOfCharacters = ( glyphStart == glyphEnd ) ? ( selectionEnd - selectionStart ) : ( numberOfCharactersStart - interGlyphIndex );
1030 const float xPosition = position.x - glyph.xBearing + offset.x + glyphAdvance * static_cast<float>( isCurrentRightToLeft ? ( numberOfCharactersStart - interGlyphIndex - numberOfCharacters ) : interGlyphIndex );
1032 mEventData->mDecorator->AddHighlight( xPosition,
1034 xPosition + static_cast<float>( numberOfCharacters ) * glyphAdvance,
1035 offset.y + height );
1037 splitStartGlyph = false;
1041 if( splitEndGlyph && ( index == glyphEnd ) )
1043 // 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.
1045 const float glyphAdvance = glyph.advance / static_cast<float>( numberOfCharactersEnd );
1046 const CharacterIndex interGlyphIndex = selectionEnd - *( glyphToCharacterBuffer + glyphEnd );
1047 // Get the direction of the character.
1048 CharacterDirection isCurrentRightToLeft = false;
1049 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
1051 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + selectionEnd );
1054 const Length numberOfCharacters = numberOfCharactersEnd - interGlyphIndex;
1056 const float xPosition = position.x - glyph.xBearing + offset.x + ( isCurrentRightToLeft ? ( glyphAdvance * static_cast<float>( numberOfCharacters ) ) : 0.f );
1057 mEventData->mDecorator->AddHighlight( xPosition,
1059 xPosition + static_cast<float>( interGlyphIndex ) * glyphAdvance,
1060 offset.y + height );
1062 splitEndGlyph = false;
1066 const float xPosition = position.x - glyph.xBearing + offset.x;
1067 mEventData->mDecorator->AddHighlight( xPosition,
1069 xPosition + glyph.advance,
1070 offset.y + height );
1073 CursorInfo primaryCursorInfo;
1074 GetCursorPosition( mEventData->mLeftSelectionPosition,
1075 primaryCursorInfo );
1077 CursorInfo secondaryCursorInfo;
1078 GetCursorPosition( mEventData->mRightSelectionPosition,
1079 secondaryCursorInfo );
1081 const Vector2 primaryPosition = primaryCursorInfo.primaryPosition + offset;
1082 const Vector2 secondaryPosition = secondaryCursorInfo.primaryPosition + offset;
1084 mEventData->mDecorator->SetPosition( LEFT_SELECTION_HANDLE, primaryPosition.x, primaryPosition.y, primaryCursorInfo.lineHeight );
1086 mEventData->mDecorator->SetPosition( RIGHT_SELECTION_HANDLE, secondaryPosition.x, secondaryPosition.y, secondaryCursorInfo.lineHeight );
1088 // Cursor to be positioned at end of selection so if selection interrupted and edit mode restarted the cursor will be at end of selection
1089 mEventData->mPrimaryCursorPosition = ( indicesSwapped ) ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition;
1091 // Set the flag to update the decorator.
1092 mEventData->mDecoratorUpdated = true;
1095 void Controller::Impl::RepositionSelectionHandles( float visualX, float visualY )
1097 if( NULL == mEventData )
1099 // Nothing to do if there is no text input.
1103 if( IsShowingPlaceholderText() )
1105 // Nothing to do if there is the place-holder text.
1109 const Length numberOfGlyphs = mVisualModel->mGlyphs.Count();
1110 const Length numberOfLines = mVisualModel->mLines.Count();
1111 if( 0 == numberOfGlyphs ||
1112 0 == numberOfLines )
1114 // Nothing to do if there is no text.
1118 // Find which word was selected
1119 CharacterIndex selectionStart( 0 );
1120 CharacterIndex selectionEnd( 0 );
1121 FindSelectionIndices( visualX, visualY, selectionStart, selectionEnd );
1122 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "%p selectionStart %d selectionEnd %d\n", this, selectionStart, selectionEnd );
1124 if( selectionStart == selectionEnd )
1126 ChangeState( EventData::EDITING );
1127 // Nothing to select. i.e. a white space, out of bounds
1131 RepositionSelectionHandles( selectionStart, selectionEnd );
1134 void Controller::Impl::SetPopupButtons()
1137 * Sets the Popup buttons to be shown depending on State.
1139 * If SELECTING : CUT & COPY + ( PASTE & CLIPBOARD if content available to paste )
1141 * If EDITING_WITH_POPUP : SELECT & SELECT_ALL
1144 TextSelectionPopup::Buttons buttonsToShow = TextSelectionPopup::NONE;
1146 if ( ( EventData::SELECTING == mEventData->mState ) || ( EventData::SELECTION_CHANGED == mEventData->mState ) )
1148 buttonsToShow = TextSelectionPopup::Buttons( TextSelectionPopup::CUT | TextSelectionPopup::COPY );
1150 if ( !IsClipboardEmpty() )
1152 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
1153 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::CLIPBOARD ) );
1156 if ( !mEventData->mAllTextSelected )
1158 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::SELECT_ALL ) );
1161 else if ( EventData::EDITING_WITH_POPUP == mEventData->mState )
1163 if ( mLogicalModel->mText.Count() && !IsShowingPlaceholderText())
1165 buttonsToShow = TextSelectionPopup::Buttons( TextSelectionPopup::SELECT | TextSelectionPopup::SELECT_ALL );
1168 if ( !IsClipboardEmpty() )
1170 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
1171 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::CLIPBOARD ) );
1175 mEventData->mDecorator->SetEnabledPopupButtons( buttonsToShow );
1178 void Controller::Impl::ChangeState( EventData::State newState )
1180 if( NULL == mEventData )
1182 // Nothing to do if there is no text input.
1186 if( mEventData->mState != newState )
1188 mEventData->mState = newState;
1190 if( EventData::INACTIVE == mEventData->mState )
1192 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
1193 mEventData->mDecorator->StopCursorBlink();
1194 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1195 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1196 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1197 mEventData->mDecorator->SetPopupActive( false );
1198 mEventData->mDecoratorUpdated = true;
1201 else if ( EventData::INTERRUPTED == mEventData->mState)
1203 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1204 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1205 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1206 mEventData->mDecorator->SetPopupActive( false );
1207 mEventData->mDecoratorUpdated = true;
1210 else if ( EventData::SELECTING == mEventData->mState )
1212 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
1213 mEventData->mDecorator->StopCursorBlink();
1214 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1215 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true );
1216 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true );
1217 if( mEventData->mGrabHandlePopupEnabled )
1220 mEventData->mDecorator->SetPopupActive( true );
1222 mEventData->mDecoratorUpdated = true;
1224 else if ( EventData::SELECTION_CHANGED == mEventData->mState )
1226 if( mEventData->mGrabHandlePopupEnabled )
1229 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
1230 mEventData->mDecorator->SetPopupActive( true );
1232 mEventData->mDecoratorUpdated = true;
1234 else if( EventData::EDITING == mEventData->mState )
1236 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1237 if( mEventData->mCursorBlinkEnabled )
1239 mEventData->mDecorator->StartCursorBlink();
1241 // Grab handle is not shown until a tap is received whilst EDITING
1242 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1243 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1244 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1245 if( mEventData->mGrabHandlePopupEnabled )
1247 mEventData->mDecorator->SetPopupActive( false );
1249 mEventData->mDecoratorUpdated = true;
1252 else if( EventData::EDITING_WITH_POPUP == mEventData->mState )
1254 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1255 if( mEventData->mCursorBlinkEnabled )
1257 mEventData->mDecorator->StartCursorBlink();
1259 if( mEventData->mSelectionEnabled )
1261 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1262 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1266 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
1268 if( mEventData->mGrabHandlePopupEnabled )
1271 mEventData->mDecorator->SetPopupActive( true );
1274 mEventData->mDecoratorUpdated = true;
1276 else if( EventData::EDITING_WITH_GRAB_HANDLE == mEventData->mState )
1278 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1279 if( mEventData->mCursorBlinkEnabled )
1281 mEventData->mDecorator->StartCursorBlink();
1283 // Grab handle is not shown until a tap is received whilst EDITING
1284 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
1285 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1286 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1287 if( mEventData->mGrabHandlePopupEnabled )
1289 mEventData->mDecorator->SetPopupActive( false );
1291 mEventData->mDecoratorUpdated = true;
1294 else if ( EventData::SELECTION_HANDLE_PANNING == mEventData->mState )
1296 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
1297 mEventData->mDecorator->StopCursorBlink();
1298 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1299 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true );
1300 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true );
1301 if( mEventData->mGrabHandlePopupEnabled )
1303 mEventData->mDecorator->SetPopupActive( false );
1305 mEventData->mDecoratorUpdated = true;
1307 else if ( EventData::GRAB_HANDLE_PANNING == mEventData->mState )
1309 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1310 if( mEventData->mCursorBlinkEnabled )
1312 mEventData->mDecorator->StartCursorBlink();
1314 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
1315 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1316 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1317 if( mEventData->mGrabHandlePopupEnabled )
1319 mEventData->mDecorator->SetPopupActive( false );
1321 mEventData->mDecoratorUpdated = true;
1326 LineIndex Controller::Impl::GetClosestLine( float y ) const
1328 float totalHeight = 0.f;
1329 LineIndex lineIndex = 0u;
1331 const Vector<LineRun>& lines = mVisualModel->mLines;
1332 for( LineIndex endLine = lines.Count();
1333 lineIndex < endLine;
1336 const LineRun& lineRun = lines[lineIndex];
1337 totalHeight += lineRun.ascender + -lineRun.descender;
1338 if( y < totalHeight )
1344 if( lineIndex == 0 )
1352 void Controller::Impl::FindSelectionIndices( float visualX, float visualY, CharacterIndex& startIndex, CharacterIndex& endIndex )
1354 CharacterIndex hitCharacter = GetClosestCursorIndex( visualX, visualY );
1355 DALI_ASSERT_DEBUG( hitCharacter <= mLogicalModel->mText.Count() && "GetClosestCursorIndex returned out of bounds index" );
1357 if ( mLogicalModel->mText.Count() == 0 )
1359 return; // if model empty
1362 if( hitCharacter >= mLogicalModel->mText.Count() )
1364 // Closest hit character is the last character.
1365 if ( hitCharacter == mLogicalModel->mText.Count() )
1367 hitCharacter--; //Hit character index set to last character in logical model
1371 // hitCharacter is out of bounds
1376 startIndex = hitCharacter;
1377 endIndex = hitCharacter;
1379 if( !TextAbstraction::IsWhiteSpace( mLogicalModel->mText[hitCharacter] ) )
1381 // Find the start and end of the text
1382 for( startIndex = hitCharacter; startIndex > 0; --startIndex )
1384 Character charCode = mLogicalModel->mText[ startIndex-1 ];
1385 if( TextAbstraction::IsWhiteSpace( charCode ) )
1390 const CharacterIndex pastTheEnd = mLogicalModel->mText.Count();
1391 for( endIndex = hitCharacter + 1u; endIndex < pastTheEnd; ++endIndex )
1393 Character charCode = mLogicalModel->mText[ endIndex ];
1394 if( TextAbstraction::IsWhiteSpace( charCode ) )
1402 CharacterIndex Controller::Impl::GetClosestCursorIndex( float visualX,
1405 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "GetClosestCursorIndex %p closest visualX %f visualY %f\n", this, visualX, visualY );
1407 if( NULL == mEventData )
1409 // Nothing to do if there is no text input.
1413 CharacterIndex logicalIndex = 0u;
1415 const Length numberOfGlyphs = mVisualModel->mGlyphs.Count();
1416 const Length numberOfLines = mVisualModel->mLines.Count();
1417 if( 0 == numberOfGlyphs ||
1418 0 == numberOfLines )
1420 return logicalIndex;
1423 // Find which line is closest
1424 const LineIndex lineIndex = GetClosestLine( visualY );
1425 const LineRun& line = mVisualModel->mLines[lineIndex];
1427 // Get the positions of the glyphs.
1428 const Vector<Vector2>& positions = mVisualModel->mGlyphPositions;
1429 const Vector2* const positionsBuffer = positions.Begin();
1431 // Get the visual to logical conversion tables.
1432 const CharacterIndex* const visualToLogicalBuffer = ( 0u != mLogicalModel->mVisualToLogicalMap.Count() ) ? mLogicalModel->mVisualToLogicalMap.Begin() : NULL;
1433 const CharacterIndex* const visualToLogicalCursorBuffer = mLogicalModel->mVisualToLogicalCursorMap.Begin();
1435 // Get the character to glyph conversion table.
1436 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
1438 // Get the glyphs per character table.
1439 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
1440 const Length* const charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
1442 // If the vector is void, there is no right to left characters.
1443 const bool hasRightToLeftCharacters = NULL != visualToLogicalBuffer;
1445 const CharacterIndex startCharacter = line.characterRun.characterIndex;
1446 const CharacterIndex endCharacter = line.characterRun.characterIndex + line.characterRun.numberOfCharacters;
1447 DALI_ASSERT_DEBUG( endCharacter <= mLogicalModel->mText.Count() && "Invalid line info" );
1449 // Whether there is a hit on a glyph.
1450 bool matched = false;
1452 // Traverses glyphs in visual order. To do that use the visual to logical conversion table.
1453 CharacterIndex visualIndex = startCharacter;
1454 for( ; !matched && ( visualIndex < endCharacter ); ++visualIndex )
1456 // The character in logical order.
1457 const CharacterIndex characterLogicalOrderIndex = hasRightToLeftCharacters ? *( visualToLogicalBuffer + visualIndex ) : visualIndex;
1459 // Get the script of the character.
1460 const Script script = mLogicalModel->GetScript( characterLogicalOrderIndex );
1462 // The first glyph for that character in logical order.
1463 const GlyphIndex glyphLogicalOrderIndex = *( charactersToGlyphBuffer + characterLogicalOrderIndex );
1464 // The number of glyphs for that character
1465 const Length numberOfGlyphs = *( glyphsPerCharacterBuffer + characterLogicalOrderIndex );
1467 // Get the metrics for the group of glyphs.
1468 GlyphMetrics glyphMetrics;
1469 GetGlyphsMetrics( glyphLogicalOrderIndex,
1475 const Vector2& position = *( positionsBuffer + glyphLogicalOrderIndex );
1477 // Prevents to jump the whole Latin ligatures like fi, ff, or Arabic ï»»...
1478 const Length numberOfCharactersInLigature = HasLigatureMustBreak( script ) ? *( charactersPerGlyphBuffer + glyphLogicalOrderIndex ) : 1u;
1479 const float glyphAdvance = glyphMetrics.advance / static_cast<float>( numberOfCharactersInLigature );
1481 for( GlyphIndex index = 0u; !matched && ( index < numberOfCharactersInLigature ); ++index )
1483 // Find the mid-point of the area containing the glyph
1484 const float glyphCenter = -glyphMetrics.xBearing + position.x + ( static_cast<float>( index ) + 0.5f ) * glyphAdvance;
1486 if( visualX < glyphCenter )
1488 visualIndex += index;
1500 // Return the logical position of the cursor in characters.
1504 visualIndex = endCharacter;
1507 logicalIndex = hasRightToLeftCharacters ? *( visualToLogicalCursorBuffer + visualIndex ) : visualIndex;
1508 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "%p closest visualIndex %d logicalIndex %d\n", this, visualIndex, logicalIndex );
1510 DALI_ASSERT_DEBUG( ( logicalIndex <= mLogicalModel->mText.Count() && logicalIndex >= 0 ) && "GetClosestCursorIndex - Out of bounds index" );
1512 return logicalIndex;
1515 void Controller::Impl::GetCursorPosition( CharacterIndex logical,
1516 CursorInfo& cursorInfo )
1518 // TODO: Check for multiline with \n, etc...
1520 // Check if the logical position is the first or the last one of the text.
1521 const bool isFirstPosition = 0u == logical;
1522 const bool isLastPosition = mLogicalModel->mText.Count() == logical;
1524 if( isFirstPosition && isLastPosition )
1526 // There is zero characters. Get the default font's line height.
1527 cursorInfo.lineHeight = GetDefaultFontLineHeight();
1528 cursorInfo.primaryCursorHeight = cursorInfo.lineHeight;
1530 cursorInfo.primaryPosition.x = mEventData->mDecorator->GetCursorWidth();
1531 cursorInfo.primaryPosition.y = 0.f;
1533 // Nothing else to do.
1537 // 'logical' is the logical 'cursor' index.
1538 // Get the next and current logical 'character' index.
1539 const CharacterIndex nextCharacterIndex = logical;
1540 const CharacterIndex characterIndex = isFirstPosition ? logical : logical - 1u;
1542 // Get the direction of the character and the next one.
1543 const CharacterDirection* const modelCharacterDirectionsBuffer = ( 0u != mLogicalModel->mCharacterDirections.Count() ) ? mLogicalModel->mCharacterDirections.Begin() : NULL;
1545 CharacterDirection isCurrentRightToLeft = false;
1546 CharacterDirection isNextRightToLeft = false;
1547 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
1549 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + characterIndex );
1550 isNextRightToLeft = *( modelCharacterDirectionsBuffer + nextCharacterIndex );
1553 // Get the line where the character is laid-out.
1554 const LineRun* const modelLines = mVisualModel->mLines.Begin();
1556 const LineIndex lineIndex = mVisualModel->GetLineOfCharacter( characterIndex );
1557 const LineRun& line = *( modelLines + lineIndex );
1559 // Get the paragraph's direction.
1560 const CharacterDirection isRightToLeftParagraph = line.direction;
1562 // Check whether there is an alternative position:
1564 cursorInfo.isSecondaryCursor = ( !isLastPosition && ( isCurrentRightToLeft != isNextRightToLeft ) ) ||
1565 ( isLastPosition && ( isRightToLeftParagraph != isCurrentRightToLeft ) );
1567 // Set the line height.
1568 cursorInfo.lineHeight = line.ascender + -line.descender;
1570 // Calculate the primary cursor.
1572 CharacterIndex index = characterIndex;
1573 if( cursorInfo.isSecondaryCursor )
1575 // If there is a secondary position, the primary cursor may be in a different place than the logical index.
1577 if( isLastPosition )
1579 // The position of the cursor after the last character needs special
1580 // care depending on its direction and the direction of the paragraph.
1582 // Need to find the first character after the last character with the paragraph's direction.
1583 // i.e l0 l1 l2 r0 r1 should find r0.
1585 // TODO: check for more than one line!
1586 index = isRightToLeftParagraph ? line.characterRun.characterIndex : line.characterRun.characterIndex + line.characterRun.numberOfCharacters - 1u;
1587 index = mLogicalModel->GetLogicalCharacterIndex( index );
1591 index = ( isRightToLeftParagraph == isCurrentRightToLeft ) ? characterIndex : nextCharacterIndex;
1595 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
1596 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
1597 const Length* const charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
1598 const CharacterIndex* const glyphsToCharactersBuffer = mVisualModel->mGlyphsToCharacters.Begin();
1599 const Vector2* const glyphPositionsBuffer = mVisualModel->mGlyphPositions.Begin();
1601 // Convert the cursor position into the glyph position.
1602 const GlyphIndex primaryGlyphIndex = *( charactersToGlyphBuffer + index );
1603 const Length primaryNumberOfGlyphs = *( glyphsPerCharacterBuffer + index );
1604 const Length primaryNumberOfCharacters = *( charactersPerGlyphBuffer + primaryGlyphIndex );
1606 // Get the metrics for the group of glyphs.
1607 GlyphMetrics glyphMetrics;
1608 GetGlyphsMetrics( primaryGlyphIndex,
1609 primaryNumberOfGlyphs,
1614 // Whether to add the glyph's advance to the cursor position.
1615 // 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,
1616 // if the logical cursor is one, the position is the position of the first glyph and the advance is added.
1617 // A 'truth table' was build and an online Karnaugh map tool was used to simplify the logic.
1638 // Where F -> isFirstPosition
1639 // L -> isLastPosition
1640 // C -> isCurrentRightToLeft
1641 // P -> isRightToLeftParagraph
1642 // A -> Whether to add the glyph's advance.
1644 const bool addGlyphAdvance = ( ( isLastPosition && !isRightToLeftParagraph ) ||
1645 ( isFirstPosition && isRightToLeftParagraph ) ||
1646 ( !isFirstPosition && !isLastPosition && !isCurrentRightToLeft ) );
1648 float glyphAdvance = addGlyphAdvance ? glyphMetrics.advance : 0.f;
1650 if( !isLastPosition &&
1651 ( primaryNumberOfCharacters > 1u ) )
1653 const CharacterIndex firstIndex = *( glyphsToCharactersBuffer + primaryGlyphIndex );
1655 bool isCurrentRightToLeft = false;
1656 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
1658 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + index );
1661 Length numberOfGlyphAdvance = ( isFirstPosition ? 0u : 1u ) + characterIndex - firstIndex;
1662 if( isCurrentRightToLeft )
1664 numberOfGlyphAdvance = primaryNumberOfCharacters - numberOfGlyphAdvance;
1667 glyphAdvance = static_cast<float>( numberOfGlyphAdvance ) * glyphMetrics.advance / static_cast<float>( primaryNumberOfCharacters );
1670 // Get the glyph position and x bearing.
1671 const Vector2& primaryPosition = *( glyphPositionsBuffer + primaryGlyphIndex );
1673 // Set the primary cursor's height.
1674 cursorInfo.primaryCursorHeight = cursorInfo.isSecondaryCursor ? 0.5f * glyphMetrics.fontHeight : glyphMetrics.fontHeight;
1676 // Set the primary cursor's position.
1677 cursorInfo.primaryPosition.x = -glyphMetrics.xBearing + primaryPosition.x + glyphAdvance;
1678 cursorInfo.primaryPosition.y = line.ascender - glyphMetrics.ascender;
1680 // Calculate the secondary cursor.
1682 if( cursorInfo.isSecondaryCursor )
1684 // Set the secondary cursor's height.
1685 cursorInfo.secondaryCursorHeight = 0.5f * glyphMetrics.fontHeight;
1687 CharacterIndex index = characterIndex;
1688 if( !isLastPosition )
1690 index = ( isRightToLeftParagraph == isCurrentRightToLeft ) ? nextCharacterIndex : characterIndex;
1693 const GlyphIndex secondaryGlyphIndex = *( charactersToGlyphBuffer + index );
1694 const Length secondaryNumberOfGlyphs = *( glyphsPerCharacterBuffer + index );
1696 const Vector2& secondaryPosition = *( glyphPositionsBuffer + secondaryGlyphIndex );
1698 GetGlyphsMetrics( secondaryGlyphIndex,
1699 secondaryNumberOfGlyphs,
1704 // Set the secondary cursor's position.
1705 cursorInfo.secondaryPosition.x = -glyphMetrics.xBearing + secondaryPosition.x + ( isCurrentRightToLeft ? 0.f : glyphMetrics.advance );
1706 cursorInfo.secondaryPosition.y = cursorInfo.lineHeight - cursorInfo.secondaryCursorHeight - line.descender - ( glyphMetrics.fontHeight - glyphMetrics.ascender );
1710 CharacterIndex Controller::Impl::CalculateNewCursorIndex( CharacterIndex index ) const
1712 if( NULL == mEventData )
1714 // Nothing to do if there is no text input.
1718 CharacterIndex cursorIndex = mEventData->mPrimaryCursorPosition;
1720 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
1721 const Length* const charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
1723 GlyphIndex glyphIndex = *( charactersToGlyphBuffer + index );
1724 Length numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
1726 if( numberOfCharacters > 1u )
1728 const Script script = mLogicalModel->GetScript( index );
1729 if( HasLigatureMustBreak( script ) )
1731 // Prevents to jump the whole Latin ligatures like fi, ff, or Arabic ï»», ...
1732 numberOfCharacters = 1u;
1737 while( 0u == numberOfCharacters )
1740 numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
1744 if( index < mEventData->mPrimaryCursorPosition )
1746 cursorIndex -= numberOfCharacters;
1750 cursorIndex += numberOfCharacters;
1756 void Controller::Impl::UpdateCursorPosition()
1758 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "-->Controller::UpdateCursorPosition %p\n", this );
1759 if( NULL == mEventData )
1761 // Nothing to do if there is no text input.
1762 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition no event data\n" );
1766 if( IsShowingPlaceholderText() || ( 0u == mLogicalModel->mText.Count() ) )
1768 // Do not want to use the place-holder text to set the cursor position.
1770 // Use the line's height of the font's family set to set the cursor's size.
1771 // If there is no font's family set, use the default font.
1772 // Use the current alignment to place the cursor at the beginning, center or end of the box.
1774 float lineHeight = 0.f;
1776 FontId defaultFontId = 0u;
1777 if( NULL == mFontDefaults )
1779 TextAbstraction::FontDescription fontDescription;
1780 defaultFontId = mFontClient.GetFontId( fontDescription );
1784 defaultFontId = mFontDefaults->GetFontId( mFontClient );
1787 Text::FontMetrics fontMetrics;
1788 mMetrics->GetFontMetrics( defaultFontId, fontMetrics );
1790 lineHeight = fontMetrics.ascender - fontMetrics.descender;
1793 Vector2 cursorPosition;
1795 switch( mLayoutEngine.GetHorizontalAlignment() )
1797 case LayoutEngine::HORIZONTAL_ALIGN_BEGIN:
1799 cursorPosition.x = mEventData->mDecorator->GetCursorWidth();
1802 case LayoutEngine::HORIZONTAL_ALIGN_CENTER:
1804 cursorPosition.x = floor( 0.5f * mVisualModel->mControlSize.width );
1807 case LayoutEngine::HORIZONTAL_ALIGN_END:
1809 cursorPosition.x = mVisualModel->mControlSize.width;
1814 switch( mLayoutEngine.GetVerticalAlignment() )
1816 case LayoutEngine::VERTICAL_ALIGN_TOP:
1818 cursorPosition.y = 0.f;
1821 case LayoutEngine::VERTICAL_ALIGN_CENTER:
1823 cursorPosition.y = floorf( 0.5f * ( mVisualModel->mControlSize.height - lineHeight ) );
1826 case LayoutEngine::VERTICAL_ALIGN_BOTTOM:
1828 cursorPosition.y = mVisualModel->mControlSize.height - lineHeight;
1833 mEventData->mDecorator->SetPosition( PRIMARY_CURSOR,
1841 CursorInfo cursorInfo;
1842 GetCursorPosition( mEventData->mPrimaryCursorPosition,
1845 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
1846 const Vector2 cursorPosition = cursorInfo.primaryPosition + offset;
1848 // Sets the cursor position.
1849 mEventData->mDecorator->SetPosition( PRIMARY_CURSOR,
1852 cursorInfo.primaryCursorHeight,
1853 cursorInfo.lineHeight );
1854 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Primary cursor position: %f,%f\n", cursorPosition.x, cursorPosition.y );
1856 // Sets the grab handle position.
1857 mEventData->mDecorator->SetPosition( GRAB_HANDLE,
1860 cursorInfo.lineHeight );
1862 if( cursorInfo.isSecondaryCursor )
1864 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_BOTH );
1865 mEventData->mDecorator->SetPosition( SECONDARY_CURSOR,
1866 cursorInfo.secondaryPosition.x + offset.x,
1867 cursorInfo.secondaryPosition.y + offset.y,
1868 cursorInfo.secondaryCursorHeight,
1869 cursorInfo.lineHeight );
1870 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Secondary cursor position: %f,%f\n", cursorInfo.secondaryPosition.x + offset.x, cursorInfo.secondaryPosition.y + offset.y );
1874 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1877 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition\n" );
1880 void Controller::Impl::UpdateSelectionHandle( HandleType handleType )
1882 if( ( LEFT_SELECTION_HANDLE != handleType ) &&
1883 ( RIGHT_SELECTION_HANDLE != handleType ) )
1888 const bool leftSelectionHandle = LEFT_SELECTION_HANDLE == handleType;
1889 const CharacterIndex index = leftSelectionHandle ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition;
1891 CursorInfo cursorInfo;
1892 GetCursorPosition( index,
1895 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
1896 const Vector2 cursorPosition = cursorInfo.primaryPosition + offset;
1898 // Sets the grab handle position.
1899 mEventData->mDecorator->SetPosition( handleType,
1902 cursorInfo.lineHeight );
1904 // If selection handle at start of the text and other at end of the text then all text is selected.
1905 const CharacterIndex startOfSelection = std::min( mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition );
1906 const CharacterIndex endOfSelection = std::max ( mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition );
1907 mEventData->mAllTextSelected = ( startOfSelection == 0 ) && ( endOfSelection == mLogicalModel->mText.Count() );
1910 void Controller::Impl::ClampHorizontalScroll( const Vector2& actualSize )
1912 // Clamp between -space & 0 (and the text alignment).
1913 if( actualSize.width > mVisualModel->mControlSize.width )
1915 const float space = ( actualSize.width - mVisualModel->mControlSize.width ) + mAlignmentOffset.x;
1916 mEventData->mScrollPosition.x = ( mEventData->mScrollPosition.x < -space ) ? -space : mEventData->mScrollPosition.x;
1917 mEventData->mScrollPosition.x = ( mEventData->mScrollPosition.x > -mAlignmentOffset.x ) ? -mAlignmentOffset.x : mEventData->mScrollPosition.x;
1919 mEventData->mDecoratorUpdated = true;
1923 mEventData->mScrollPosition.x = 0.f;
1927 void Controller::Impl::ClampVerticalScroll( const Vector2& actualSize )
1929 // Clamp between -space & 0 (and the text alignment).
1930 if( actualSize.height > mVisualModel->mControlSize.height )
1932 const float space = ( actualSize.height - mVisualModel->mControlSize.height ) + mAlignmentOffset.y;
1933 mEventData->mScrollPosition.y = ( mEventData->mScrollPosition.y < -space ) ? -space : mEventData->mScrollPosition.y;
1934 mEventData->mScrollPosition.y = ( mEventData->mScrollPosition.y > -mAlignmentOffset.y ) ? -mAlignmentOffset.y : mEventData->mScrollPosition.y;
1936 mEventData->mDecoratorUpdated = true;
1940 mEventData->mScrollPosition.y = 0.f;
1944 void Controller::Impl::ScrollToMakePositionVisible( const Vector2& position )
1947 bool updateDecorator = false;
1948 if( position.x < 0.f )
1950 offset.x = -position.x;
1951 mEventData->mScrollPosition.x += offset.x;
1952 updateDecorator = true;
1954 else if( position.x > mVisualModel->mControlSize.width )
1956 offset.x = mVisualModel->mControlSize.width - position.x;
1957 mEventData->mScrollPosition.x += offset.x;
1958 updateDecorator = true;
1961 if( updateDecorator && mEventData->mDecorator )
1963 mEventData->mDecorator->UpdatePositions( offset );
1966 // TODO : calculate the vertical scroll.
1969 void Controller::Impl::ScrollTextToMatchCursor()
1971 // Get the current cursor position in decorator coords.
1972 const Vector2& currentCursorPosition = mEventData->mDecorator->GetPosition( PRIMARY_CURSOR );
1974 // Calculate the new cursor position.
1975 CursorInfo cursorInfo;
1976 GetCursorPosition( mEventData->mPrimaryCursorPosition,
1979 // Calculate the offset to match the cursor position before the character was deleted.
1980 mEventData->mScrollPosition.x = currentCursorPosition.x - cursorInfo.primaryPosition.x - mAlignmentOffset.x;
1982 ClampHorizontalScroll( mVisualModel->GetActualSize() );
1984 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
1985 const Vector2 cursorPosition = cursorInfo.primaryPosition + offset;
1987 // Sets the cursor position.
1988 mEventData->mDecorator->SetPosition( PRIMARY_CURSOR,
1991 cursorInfo.primaryCursorHeight,
1992 cursorInfo.lineHeight );
1994 // Sets the grab handle position.
1995 mEventData->mDecorator->SetPosition( GRAB_HANDLE,
1998 cursorInfo.lineHeight );
2000 if( cursorInfo.isSecondaryCursor )
2002 mEventData->mDecorator->SetPosition( SECONDARY_CURSOR,
2003 cursorInfo.secondaryPosition.x + offset.x,
2004 cursorInfo.secondaryPosition.y + offset.y,
2005 cursorInfo.secondaryCursorHeight,
2006 cursorInfo.lineHeight );
2007 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Secondary cursor position: %f,%f\n", cursorInfo.secondaryPosition.x + offset.x, cursorInfo.secondaryPosition.y + offset.y );
2010 // Set which cursors are active according the state.
2011 if( ( EventData::EDITING == mEventData->mState ) ||
2012 ( EventData::EDITING_WITH_POPUP == mEventData->mState ) ||
2013 ( EventData::EDITING_WITH_GRAB_HANDLE == mEventData->mState ) ||
2014 ( EventData::GRAB_HANDLE_PANNING == mEventData->mState ) )
2016 if( cursorInfo.isSecondaryCursor )
2018 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_BOTH );
2022 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
2027 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
2031 void Controller::Impl::RequestRelayout()
2033 mControlInterface.RequestTextRelayout();
2038 } // namespace Toolkit