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/color-segmentation.h>
29 #include <dali-toolkit/internal/text/multi-language-support.h>
30 #include <dali-toolkit/internal/text/segmentation.h>
31 #include <dali-toolkit/internal/text/shaper.h>
36 #if defined(DEBUG_ENABLED)
37 Debug::Filter* gLogFilter = Debug::Filter::New(Debug::NoLogging, true, "LOG_TEXT_CONTROLS");
41 * @brief Some characters can be shaped in more than one glyph.
42 * This struct is used to retrieve metrics from these group of glyphs.
56 float fontHeight; ///< The font's height of that glyphs.
57 float advance; ///< The sum of all the advances of all the glyphs.
58 float ascender; ///< The font's ascender.
59 float xBearing; ///< The x bearing of the first glyph.
74 * @brief Get some glyph's metrics of a group of glyphs formed as a result of shaping one character.
76 * @param[in] glyphIndex The index to the first glyph.
77 * @param[in] numberOfGlyphs The number of glyphs.
78 * @param[out] glyphMetrics Some glyph metrics (font height, advance, ascender and x bearing).
79 * @param[in] visualModel The visual model.
80 * @param[in] metrics Used to access metrics from FontClient.
82 void GetGlyphsMetrics( GlyphIndex glyphIndex,
83 Length numberOfGlyphs,
84 GlyphMetrics& glyphMetrics,
85 VisualModelPtr& visualModel,
88 const GlyphInfo* glyphsBuffer = visualModel->mGlyphs.Begin();
90 const GlyphInfo& firstGlyph = *( glyphsBuffer + glyphIndex );
92 Text::FontMetrics fontMetrics;
93 metrics->GetFontMetrics( firstGlyph.fontId, fontMetrics );
95 glyphMetrics.fontHeight = fontMetrics.height;
96 glyphMetrics.advance = firstGlyph.advance;
97 glyphMetrics.ascender = fontMetrics.ascender;
98 glyphMetrics.xBearing = firstGlyph.xBearing;
100 for( unsigned int i = 1u; i < numberOfGlyphs; ++i )
102 const GlyphInfo& glyphInfo = *( glyphsBuffer + glyphIndex + i );
104 glyphMetrics.advance += glyphInfo.advance;
108 EventData::EventData( DecoratorPtr decorator )
109 : mDecorator( decorator ),
111 mPlaceholderTextActive(),
112 mPlaceholderTextInactive(),
113 mPlaceholderTextColor( 0.8f, 0.8f, 0.8f, 0.8f ),
117 mPrimaryCursorPosition( 0u ),
118 mLeftSelectionPosition( 0u ),
119 mRightSelectionPosition( 0u ),
120 mPreEditStartPosition( 0u ),
121 mPreEditLength( 0u ),
122 mIsShowingPlaceholderText( false ),
123 mPreEditFlag( false ),
124 mDecoratorUpdated( false ),
125 mCursorBlinkEnabled( true ),
126 mGrabHandleEnabled( true ),
127 mGrabHandlePopupEnabled( true ),
128 mSelectionEnabled( true ),
129 mHorizontalScrollingEnabled( true ),
130 mVerticalScrollingEnabled( false ),
131 mUpdateCursorPosition( false ),
132 mUpdateLeftSelectionPosition( false ),
133 mUpdateRightSelectionPosition( false ),
134 mScrollAfterUpdatePosition( false ),
135 mScrollAfterDelete( false ),
136 mAllTextSelected( false ),
137 mUpdateInputStyle( false )
139 mImfManager = ImfManager::Get();
142 EventData::~EventData()
145 bool Controller::Impl::ProcessInputEvents()
147 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "-->Controller::ProcessInputEvents\n" );
148 if( NULL == mEventData )
150 // Nothing to do if there is no text input.
151 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::ProcessInputEvents no event data\n" );
155 if( mEventData->mDecorator )
157 for( std::vector<Event>::iterator iter = mEventData->mEventQueue.begin();
158 iter != mEventData->mEventQueue.end();
163 case Event::CURSOR_KEY_EVENT:
165 OnCursorKeyEvent( *iter );
168 case Event::TAP_EVENT:
173 case Event::LONG_PRESS_EVENT:
175 OnLongPressEvent( *iter );
178 case Event::PAN_EVENT:
183 case Event::GRAB_HANDLE_EVENT:
184 case Event::LEFT_SELECTION_HANDLE_EVENT:
185 case Event::RIGHT_SELECTION_HANDLE_EVENT: // Fall through
187 OnHandleEvent( *iter );
192 OnSelectEvent( *iter );
195 case Event::SELECT_ALL:
204 // The cursor must also be repositioned after inserts into the model
205 if( mEventData->mUpdateCursorPosition )
207 // Updates the cursor position and scrolls the text to make it visible.
208 CursorInfo cursorInfo;
209 GetCursorPosition( mEventData->mPrimaryCursorPosition,
212 if( mEventData->mScrollAfterUpdatePosition )
214 ScrollToMakePositionVisible( cursorInfo.primaryPosition );
215 mEventData->mScrollAfterUpdatePosition = false;
217 else if( mEventData->mScrollAfterDelete )
219 ScrollTextToMatchCursor( cursorInfo );
220 mEventData->mScrollAfterDelete = false;
223 UpdateCursorPosition( cursorInfo );
225 mEventData->mDecoratorUpdated = true;
226 mEventData->mUpdateCursorPosition = false;
230 bool leftScroll = false;
231 bool rightScroll = false;
233 CursorInfo leftHandleInfo;
234 CursorInfo rightHandleInfo;
236 if( mEventData->mUpdateLeftSelectionPosition )
238 GetCursorPosition( mEventData->mLeftSelectionPosition,
241 if( mEventData->mScrollAfterUpdatePosition )
243 ScrollToMakePositionVisible( leftHandleInfo.primaryPosition );
248 if( mEventData->mUpdateRightSelectionPosition )
250 GetCursorPosition( mEventData->mRightSelectionPosition,
253 if( mEventData->mScrollAfterUpdatePosition )
255 ScrollToMakePositionVisible( rightHandleInfo.primaryPosition );
260 if( mEventData->mUpdateLeftSelectionPosition )
262 UpdateSelectionHandle( LEFT_SELECTION_HANDLE,
266 mEventData->mDecoratorUpdated = true;
269 if( mEventData->mUpdateRightSelectionPosition )
271 UpdateSelectionHandle( RIGHT_SELECTION_HANDLE,
275 mEventData->mDecoratorUpdated = true;
278 if( mEventData->mUpdateLeftSelectionPosition || mEventData->mUpdateRightSelectionPosition )
280 RepositionSelectionHandles();
282 mEventData->mUpdateLeftSelectionPosition = false;
283 mEventData->mUpdateRightSelectionPosition = false;
286 if( leftScroll || rightScroll )
288 mEventData->mScrollAfterUpdatePosition = false;
292 if( mEventData->mUpdateInputStyle )
294 // Set the default style first.
295 RetrieveDefaultInputStyle( mEventData->mInputStyle );
297 // Get the character index from the cursor index.
298 const CharacterIndex styleIndex = ( mEventData->mPrimaryCursorPosition > 0u ) ? mEventData->mPrimaryCursorPosition - 1u : 0u;
300 // Retrieve the style from the style runs stored in the logical model.
301 mLogicalModel->RetrieveStyle( styleIndex, mEventData->mInputStyle );
303 mEventData->mUpdateInputStyle = false;
306 mEventData->mEventQueue.clear();
308 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::ProcessInputEvents\n" );
310 const bool decoratorUpdated = mEventData->mDecoratorUpdated;
311 mEventData->mDecoratorUpdated = false;
313 return decoratorUpdated;
316 void Controller::Impl::UpdateModel( OperationsMask operationsRequired )
318 DALI_LOG_INFO( gLogFilter, Debug::General, "Controller::UpdateModel\n" );
320 // Calculate the operations to be done.
321 const OperationsMask operations = static_cast<OperationsMask>( mOperationsPending & operationsRequired );
323 Vector<Character>& utf32Characters = mLogicalModel->mText;
325 const Length numberOfCharacters = utf32Characters.Count();
327 Vector<LineBreakInfo>& lineBreakInfo = mLogicalModel->mLineBreakInfo;
328 CharacterIndex startIndex = 0u;
329 Length requestedNumberOfCharacters = numberOfCharacters;
330 if( GET_LINE_BREAKS & operations )
332 // Retrieves the line break info. The line break info is used to split the text in 'paragraphs' to
333 // calculate the bidirectional info for each 'paragraph'.
334 // It's also used to layout the text (where it should be a new line) or to shape the text (text in different lines
335 // is not shaped together).
336 lineBreakInfo.Resize( numberOfCharacters, TextAbstraction::LINE_NO_BREAK );
338 SetLineBreakInfo( utf32Characters,
342 Vector<WordBreakInfo>& wordBreakInfo = mLogicalModel->mWordBreakInfo;
343 if( GET_WORD_BREAKS & operations )
345 // Retrieves the word break info. The word break info is used to layout the text (where to wrap the text in lines).
346 wordBreakInfo.Resize( numberOfCharacters, TextAbstraction::WORD_NO_BREAK );
348 SetWordBreakInfo( utf32Characters,
350 requestedNumberOfCharacters,
354 const bool getScripts = GET_SCRIPTS & operations;
355 const bool validateFonts = VALIDATE_FONTS & operations;
357 Vector<ScriptRun>& scripts = mLogicalModel->mScriptRuns;
358 Vector<FontRun>& validFonts = mLogicalModel->mFontRuns;
360 if( getScripts || validateFonts )
362 // Validates the fonts assigned by the application or assigns default ones.
363 // It makes sure all the characters are going to be rendered by the correct font.
364 MultilanguageSupport multilanguageSupport = MultilanguageSupport::Get();
368 // Retrieves the scripts used in the text.
369 multilanguageSupport.SetScripts( utf32Characters,
371 requestedNumberOfCharacters,
377 // Validate the fonts set through the mark-up string.
378 Vector<FontDescriptionRun>& fontDescriptionRuns = mLogicalModel->mFontDescriptionRuns;
380 // Get the default font id.
381 const FontId defaultFontId = ( NULL == mFontDefaults ) ? 0u : mFontDefaults->GetFontId( mFontClient );
383 // Validates the fonts. If there is a character with no assigned font it sets a default one.
384 // After this call, fonts are validated.
385 multilanguageSupport.ValidateFonts( utf32Characters,
390 requestedNumberOfCharacters,
395 Vector<Character> mirroredUtf32Characters;
396 bool textMirrored = false;
397 Length numberOfParagraphs = 0u;
398 if( BIDI_INFO & operations )
400 // Count the number of LINE_NO_BREAK to reserve some space for the vector of paragraph's
401 // bidirectional info.
403 const TextAbstraction::LineBreakInfo* lineBreakInfoBuffer = lineBreakInfo.Begin();
404 for( Length index = 0u; index < numberOfCharacters; ++index )
406 if( TextAbstraction::LINE_NO_BREAK == *( lineBreakInfoBuffer + index ) )
408 ++numberOfParagraphs;
412 Vector<BidirectionalParagraphInfoRun>& bidirectionalInfo = mLogicalModel->mBidirectionalParagraphInfo;
413 bidirectionalInfo.Reserve( numberOfParagraphs );
415 // Calculates the bidirectional info for the whole paragraph if it contains right to left scripts.
416 SetBidirectionalInfo( utf32Characters,
421 if( 0u != bidirectionalInfo.Count() )
423 // Only set the character directions if there is right to left characters.
424 Vector<CharacterDirection>& directions = mLogicalModel->mCharacterDirections;
425 directions.Resize( numberOfCharacters );
427 GetCharactersDirection( bidirectionalInfo,
430 // This paragraph has right to left text. Some characters may need to be mirrored.
431 // TODO: consider if the mirrored string can be stored as well.
433 textMirrored = GetMirroredText( utf32Characters,
436 mirroredUtf32Characters );
440 // There is no right to left characters. Clear the directions vector.
441 mLogicalModel->mCharacterDirections.Clear();
445 Vector<GlyphInfo>& glyphs = mVisualModel->mGlyphs;
446 Vector<CharacterIndex>& glyphsToCharactersMap = mVisualModel->mGlyphsToCharacters;
447 Vector<Length>& charactersPerGlyph = mVisualModel->mCharactersPerGlyph;
448 Vector<GlyphIndex> newParagraphGlyphs;
449 newParagraphGlyphs.Reserve( numberOfParagraphs );
451 if( SHAPE_TEXT & operations )
453 const Vector<Character>& textToShape = textMirrored ? mirroredUtf32Characters : utf32Characters;
455 ShapeText( textToShape,
460 glyphsToCharactersMap,
462 newParagraphGlyphs );
464 // Create the 'number of glyphs' per character and the glyph to character conversion tables.
465 mVisualModel->CreateGlyphsPerCharacterTable( numberOfCharacters );
466 mVisualModel->CreateCharacterToGlyphTable( numberOfCharacters );
469 const Length numberOfGlyphs = glyphs.Count();
471 if( GET_GLYPH_METRICS & operations )
473 GlyphInfo* glyphsBuffer = glyphs.Begin();
474 mMetrics->GetGlyphMetrics( glyphsBuffer, numberOfGlyphs );
476 // Update the width and advance of all new paragraph characters.
477 for( Vector<GlyphIndex>::ConstIterator it = newParagraphGlyphs.Begin(), endIt = newParagraphGlyphs.End(); it != endIt; ++it )
479 const GlyphIndex index = *it;
480 GlyphInfo& glyph = *( glyphsBuffer + index );
482 glyph.xBearing = 0.f;
488 if( ( NULL != mEventData ) &&
489 mEventData->mPreEditFlag &&
490 ( 0u != mVisualModel->mCharactersToGlyph.Count() ) )
492 // Add the underline for the pre-edit text.
493 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
494 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
496 const GlyphIndex glyphStart = *( charactersToGlyphBuffer + mEventData->mPreEditStartPosition );
497 const CharacterIndex lastPreEditCharacter = mEventData->mPreEditStartPosition + ( ( mEventData->mPreEditLength > 0u ) ? mEventData->mPreEditLength - 1u : 0u );
498 const Length numberOfGlyphsLastCharacter = *( glyphsPerCharacterBuffer + lastPreEditCharacter );
499 const GlyphIndex glyphEnd = *( charactersToGlyphBuffer + lastPreEditCharacter ) + ( numberOfGlyphsLastCharacter > 1u ? numberOfGlyphsLastCharacter - 1u : 0u );
501 GlyphRun underlineRun;
502 underlineRun.glyphIndex = glyphStart;
503 underlineRun.numberOfGlyphs = 1u + glyphEnd - glyphStart;
505 // TODO: At the moment the underline runs are only for pre-edit.
506 mVisualModel->mUnderlineRuns.PushBack( underlineRun );
510 bool Controller::Impl::UpdateModelStyle( OperationsMask operationsRequired )
512 bool updated = false;
514 if( COLOR & operationsRequired )
516 // Set the color runs in glyphs.
517 SetColorSegmentationInfo( mLogicalModel->mColorRuns,
518 mVisualModel->mCharactersToGlyph,
519 mVisualModel->mGlyphsPerCharacter,
520 mVisualModel->mColorRuns );
528 void Controller::Impl::RetrieveDefaultInputStyle( InputStyle& inputStyle )
530 // Sets the default text's color.
531 inputStyle.textColor = mTextColor;
533 // Sets the default font's family name, weight, width, slant and size.
536 inputStyle.familyName = mFontDefaults->mFontDescription.family;
537 inputStyle.weight = mFontDefaults->mFontDescription.weight;
538 inputStyle.width = mFontDefaults->mFontDescription.width;
539 inputStyle.slant = mFontDefaults->mFontDescription.slant;
540 inputStyle.size = mFontDefaults->mDefaultPointSize;
542 inputStyle.familyDefined = mFontDefaults->familyDefined;
543 inputStyle.weightDefined = mFontDefaults->weightDefined;
544 inputStyle.widthDefined = mFontDefaults->widthDefined;
545 inputStyle.slantDefined = mFontDefaults->slantDefined;
546 inputStyle.sizeDefined = mFontDefaults->sizeDefined;
550 inputStyle.familyName.clear();
551 inputStyle.weight = TextAbstraction::FontWeight::NORMAL;
552 inputStyle.width = TextAbstraction::FontWidth::NORMAL;
553 inputStyle.slant = TextAbstraction::FontSlant::NORMAL;
554 inputStyle.size = 0.f;
556 inputStyle.familyDefined = false;
557 inputStyle.weightDefined = false;
558 inputStyle.widthDefined = false;
559 inputStyle.slantDefined = false;
560 inputStyle.sizeDefined = false;
564 float Controller::Impl::GetDefaultFontLineHeight()
566 FontId defaultFontId = 0u;
567 if( NULL == mFontDefaults )
569 TextAbstraction::FontDescription fontDescription;
570 defaultFontId = mFontClient.GetFontId( fontDescription );
574 defaultFontId = mFontDefaults->GetFontId( mFontClient );
577 Text::FontMetrics fontMetrics;
578 mMetrics->GetFontMetrics( defaultFontId, fontMetrics );
580 return( fontMetrics.ascender - fontMetrics.descender );
583 void Controller::Impl::OnCursorKeyEvent( const Event& event )
585 if( NULL == mEventData )
587 // Nothing to do if there is no text input.
591 int keyCode = event.p1.mInt;
593 if( Dali::DALI_KEY_CURSOR_LEFT == keyCode )
595 if( mEventData->mPrimaryCursorPosition > 0u )
597 mEventData->mPrimaryCursorPosition = CalculateNewCursorIndex( mEventData->mPrimaryCursorPosition - 1u );
600 else if( Dali::DALI_KEY_CURSOR_RIGHT == keyCode )
602 if( mLogicalModel->mText.Count() > mEventData->mPrimaryCursorPosition )
604 mEventData->mPrimaryCursorPosition = CalculateNewCursorIndex( mEventData->mPrimaryCursorPosition );
607 else if( Dali::DALI_KEY_CURSOR_UP == keyCode )
611 else if( Dali::DALI_KEY_CURSOR_DOWN == keyCode )
616 mEventData->mUpdateCursorPosition = true;
617 mEventData->mUpdateInputStyle = true;
618 mEventData->mScrollAfterUpdatePosition = true;
621 void Controller::Impl::OnTapEvent( const Event& event )
623 if( NULL != mEventData )
625 const unsigned int tapCount = event.p1.mUint;
629 if( IsShowingRealText() )
631 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
632 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
634 mEventData->mPrimaryCursorPosition = GetClosestCursorIndex( xPosition,
637 // When the cursor position is changing, delay cursor blinking
638 mEventData->mDecorator->DelayCursorBlink();
642 mEventData->mPrimaryCursorPosition = 0u;
645 mEventData->mUpdateCursorPosition = true;
646 mEventData->mScrollAfterUpdatePosition = true;
647 mEventData->mUpdateInputStyle = true;
649 // Notify the cursor position to the imf manager.
650 if( mEventData->mImfManager )
652 mEventData->mImfManager.SetCursorPosition( mEventData->mPrimaryCursorPosition );
653 mEventData->mImfManager.NotifyCursorPosition();
659 void Controller::Impl::OnPanEvent( const Event& event )
661 if( NULL == mEventData )
663 // Nothing to do if there is no text input.
667 int state = event.p1.mInt;
669 if( Gesture::Started == state ||
670 Gesture::Continuing == state )
672 const Vector2& actualSize = mVisualModel->GetActualSize();
673 const Vector2 currentScroll = mEventData->mScrollPosition;
675 if( mEventData->mHorizontalScrollingEnabled )
677 const float displacementX = event.p2.mFloat;
678 mEventData->mScrollPosition.x += displacementX;
680 ClampHorizontalScroll( actualSize );
683 if( mEventData->mVerticalScrollingEnabled )
685 const float displacementY = event.p3.mFloat;
686 mEventData->mScrollPosition.y += displacementY;
688 ClampVerticalScroll( actualSize );
691 if( mEventData->mDecorator )
693 mEventData->mDecorator->UpdatePositions( mEventData->mScrollPosition - currentScroll );
698 void Controller::Impl::OnLongPressEvent( const Event& event )
700 DALI_LOG_INFO( gLogFilter, Debug::General, "Controller::OnLongPressEvent\n" );
702 if( EventData::EDITING == mEventData->mState )
704 ChangeState ( EventData::EDITING_WITH_POPUP );
705 mEventData->mDecoratorUpdated = true;
709 void Controller::Impl::OnHandleEvent( const Event& event )
711 if( NULL == mEventData )
713 // Nothing to do if there is no text input.
717 const unsigned int state = event.p1.mUint;
718 const bool handleStopScrolling = ( HANDLE_STOP_SCROLLING == state );
720 if( HANDLE_PRESSED == state )
722 // The event.p2 and event.p3 are in decorator coords. Need to transforms to text coords.
723 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
724 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
726 const CharacterIndex handleNewPosition = GetClosestCursorIndex( xPosition, yPosition );
728 if( Event::GRAB_HANDLE_EVENT == event.type )
730 ChangeState ( EventData::GRAB_HANDLE_PANNING );
732 if( handleNewPosition != mEventData->mPrimaryCursorPosition )
734 mEventData->mPrimaryCursorPosition = handleNewPosition;
735 mEventData->mUpdateCursorPosition = true;
738 else if( Event::LEFT_SELECTION_HANDLE_EVENT == event.type )
740 ChangeState ( EventData::SELECTION_HANDLE_PANNING );
742 if( ( handleNewPosition != mEventData->mLeftSelectionPosition ) &&
743 ( handleNewPosition != mEventData->mRightSelectionPosition ) )
745 mEventData->mLeftSelectionPosition = handleNewPosition;
747 mEventData->mUpdateLeftSelectionPosition = true;
750 else if( Event::RIGHT_SELECTION_HANDLE_EVENT == event.type )
752 ChangeState ( EventData::SELECTION_HANDLE_PANNING );
754 if( ( handleNewPosition != mEventData->mRightSelectionPosition ) &&
755 ( handleNewPosition != mEventData->mLeftSelectionPosition ) )
757 mEventData->mRightSelectionPosition = handleNewPosition;
759 mEventData->mUpdateRightSelectionPosition = true;
762 } // end ( HANDLE_PRESSED == state )
763 else if( ( HANDLE_RELEASED == state ) ||
764 handleStopScrolling )
766 CharacterIndex handlePosition = 0u;
767 if( handleStopScrolling )
769 // The event.p2 and event.p3 are in decorator coords. Need to transforms to text coords.
770 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
771 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
773 handlePosition = GetClosestCursorIndex( xPosition, yPosition );
776 if( Event::GRAB_HANDLE_EVENT == event.type )
778 mEventData->mUpdateCursorPosition = true;
779 mEventData->mUpdateInputStyle = true;
781 if( !IsClipboardEmpty() )
783 ChangeState( EventData::EDITING_WITH_PASTE_POPUP ); // Moving grabhandle will show Paste Popup
786 if( handleStopScrolling )
788 mEventData->mScrollAfterUpdatePosition = mEventData->mPrimaryCursorPosition != handlePosition;
789 mEventData->mPrimaryCursorPosition = handlePosition;
792 else if( Event::LEFT_SELECTION_HANDLE_EVENT == event.type )
794 ChangeState( EventData::SELECTING );
796 if( handleStopScrolling )
798 mEventData->mUpdateLeftSelectionPosition = ( mEventData->mRightSelectionPosition != handlePosition );
799 mEventData->mScrollAfterUpdatePosition = mEventData->mUpdateLeftSelectionPosition;
801 if( mEventData->mUpdateLeftSelectionPosition )
803 mEventData->mLeftSelectionPosition = handlePosition;
807 else if( Event::RIGHT_SELECTION_HANDLE_EVENT == event.type )
809 ChangeState( EventData::SELECTING );
811 if( handleStopScrolling )
813 mEventData->mUpdateRightSelectionPosition = ( mEventData->mLeftSelectionPosition != handlePosition );
814 mEventData->mScrollAfterUpdatePosition = mEventData->mUpdateRightSelectionPosition;
815 if( mEventData->mUpdateRightSelectionPosition )
817 mEventData->mRightSelectionPosition = handlePosition;
822 mEventData->mDecoratorUpdated = true;
823 } // end ( ( HANDLE_RELEASED == state ) || ( HANDLE_STOP_SCROLLING == state ) )
824 else if( HANDLE_SCROLLING == state )
826 const float xSpeed = event.p2.mFloat;
827 const Vector2& actualSize = mVisualModel->GetActualSize();
828 const Vector2 currentScrollPosition = mEventData->mScrollPosition;
830 mEventData->mScrollPosition.x += xSpeed;
832 ClampHorizontalScroll( actualSize );
834 bool endOfScroll = false;
835 if( Vector2::ZERO == ( currentScrollPosition - mEventData->mScrollPosition ) )
837 // Notify the decorator there is no more text to scroll.
838 // The decorator won't send more scroll events.
839 mEventData->mDecorator->NotifyEndOfScroll();
840 // Still need to set the position of the handle.
844 // Set the position of the handle.
845 const bool scrollRightDirection = xSpeed > 0.f;
846 const bool leftSelectionHandleEvent = Event::LEFT_SELECTION_HANDLE_EVENT == event.type;
847 const bool rightSelectionHandleEvent = Event::RIGHT_SELECTION_HANDLE_EVENT == event.type;
849 if( Event::GRAB_HANDLE_EVENT == event.type )
851 ChangeState( EventData::GRAB_HANDLE_PANNING );
853 Vector2 position = mEventData->mDecorator->GetPosition( GRAB_HANDLE );
855 // Position the grag handle close to either the left or right edge.
856 position.x = scrollRightDirection ? 0.f : mVisualModel->mControlSize.width;
858 // Get the new handle position.
859 // The grab handle's position is in decorator coords. Need to transforms to text coords.
860 const CharacterIndex handlePosition = GetClosestCursorIndex( position.x - mEventData->mScrollPosition.x - mAlignmentOffset.x,
861 position.y - mEventData->mScrollPosition.y - mAlignmentOffset.y );
863 mEventData->mUpdateCursorPosition = mEventData->mPrimaryCursorPosition != handlePosition;
864 mEventData->mScrollAfterUpdatePosition = mEventData->mUpdateCursorPosition;
865 mEventData->mPrimaryCursorPosition = handlePosition;
866 mEventData->mUpdateInputStyle = mEventData->mUpdateCursorPosition;
868 else if( leftSelectionHandleEvent || rightSelectionHandleEvent )
870 // TODO: This is recalculating the selection box every time the text is scrolled with the selection handles.
871 // Think if something can be done to save power.
873 ChangeState( EventData::SELECTION_HANDLE_PANNING );
875 Vector2 position = mEventData->mDecorator->GetPosition( leftSelectionHandleEvent ? Text::LEFT_SELECTION_HANDLE : Text::RIGHT_SELECTION_HANDLE );
877 // Position the selection handle close to either the left or right edge.
878 position.x = scrollRightDirection ? 0.f : mVisualModel->mControlSize.width;
880 // Get the new handle position.
881 // The selection handle's position is in decorator coords. Need to transforms to text coords.
882 const CharacterIndex handlePosition = GetClosestCursorIndex( position.x - mEventData->mScrollPosition.x - mAlignmentOffset.x,
883 position.y - mEventData->mScrollPosition.y - mAlignmentOffset.y );
885 if( leftSelectionHandleEvent )
887 const bool differentHandles = ( mEventData->mLeftSelectionPosition != handlePosition ) && ( mEventData->mRightSelectionPosition != handlePosition );
888 mEventData->mUpdateLeftSelectionPosition = endOfScroll || differentHandles;
889 if( differentHandles )
891 mEventData->mLeftSelectionPosition = handlePosition;
896 const bool differentHandles = ( mEventData->mRightSelectionPosition != handlePosition ) && ( mEventData->mLeftSelectionPosition != handlePosition );
897 mEventData->mUpdateRightSelectionPosition = endOfScroll || differentHandles;
898 if( differentHandles )
900 mEventData->mRightSelectionPosition = handlePosition;
904 if( mEventData->mUpdateLeftSelectionPosition || mEventData->mUpdateRightSelectionPosition )
906 RepositionSelectionHandles();
908 mEventData->mScrollAfterUpdatePosition = true;
911 mEventData->mDecoratorUpdated = true;
912 } // end ( HANDLE_SCROLLING == state )
915 void Controller::Impl::OnSelectEvent( const Event& event )
917 if( NULL == mEventData )
919 // Nothing to do if there is no text.
923 if( mEventData->mSelectionEnabled )
925 // The event.p2 and event.p3 are in decorator coords. Need to transforms to text coords.
926 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
927 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
929 // Calculates the logical position from the x,y coords.
930 RepositionSelectionHandles( xPosition,
933 mEventData->mUpdateLeftSelectionPosition = true;
934 mEventData->mUpdateRightSelectionPosition = true;
936 mEventData->mScrollAfterUpdatePosition = ( mEventData->mLeftSelectionPosition != mEventData->mRightSelectionPosition );
940 void Controller::Impl::OnSelectAllEvent()
942 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "OnSelectAllEvent mEventData->mSelectionEnabled%s \n", mEventData->mSelectionEnabled?"true":"false");
944 if( NULL == mEventData )
946 // Nothing to do if there is no text.
950 if( mEventData->mSelectionEnabled )
952 mEventData->mLeftSelectionPosition = 0u;
953 mEventData->mRightSelectionPosition = mLogicalModel->mText.Count();
955 mEventData->mScrollAfterUpdatePosition = true;
956 mEventData->mUpdateLeftSelectionPosition = true;
957 mEventData->mUpdateRightSelectionPosition = true;
961 void Controller::Impl::RetrieveSelection( std::string& selectedText, bool deleteAfterRetrieval )
963 if( mEventData->mLeftSelectionPosition == mEventData->mRightSelectionPosition )
965 // Nothing to select if handles are in the same place.
966 selectedText.clear();
970 const bool handlesCrossed = mEventData->mLeftSelectionPosition > mEventData->mRightSelectionPosition;
972 //Get start and end position of selection
973 const CharacterIndex startOfSelectedText = handlesCrossed ? mEventData->mRightSelectionPosition : mEventData->mLeftSelectionPosition;
974 const Length lengthOfSelectedText = ( handlesCrossed ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition ) - startOfSelectedText;
976 // Validate the start and end selection points
977 if( ( startOfSelectedText + lengthOfSelectedText ) <= mLogicalModel->mText.Count() )
979 //Get text as a UTF8 string
980 Vector<Character>& utf32Characters = mLogicalModel->mText;
982 Utf32ToUtf8( &utf32Characters[startOfSelectedText], lengthOfSelectedText, selectedText );
984 if( deleteAfterRetrieval ) // Only delete text if copied successfully
986 // Set as input style the style of the first deleted character.
987 mLogicalModel->RetrieveStyle( startOfSelectedText, mEventData->mInputStyle );
989 mLogicalModel->UpdateTextStyleRuns( startOfSelectedText, -static_cast<int>( lengthOfSelectedText ) );
991 // Delete text between handles
992 Vector<Character>& currentText = mLogicalModel->mText;
994 Vector<Character>::Iterator first = currentText.Begin() + startOfSelectedText;
995 Vector<Character>::Iterator last = first + lengthOfSelectedText;
996 currentText.Erase( first, last );
998 // Scroll after delete.
999 mEventData->mPrimaryCursorPosition = handlesCrossed ? mEventData->mRightSelectionPosition : mEventData->mLeftSelectionPosition;
1000 mEventData->mScrollAfterDelete = true;
1002 // Udpade the cursor position and the decorator.
1003 // Scroll after the position is updated if is not scrolling after delete.
1004 mEventData->mUpdateCursorPosition = true;
1005 mEventData->mScrollAfterUpdatePosition = !mEventData->mScrollAfterDelete;
1006 mEventData->mDecoratorUpdated = true;
1010 void Controller::Impl::ShowClipboard()
1014 mClipboard.ShowClipboard();
1018 void Controller::Impl::HideClipboard()
1022 mClipboard.HideClipboard();
1026 bool Controller::Impl::CopyStringToClipboard( std::string& source )
1028 //Send string to clipboard
1029 return ( mClipboard && mClipboard.SetItem( source ) );
1032 void Controller::Impl::SendSelectionToClipboard( bool deleteAfterSending )
1034 std::string selectedText;
1035 RetrieveSelection( selectedText, deleteAfterSending );
1036 CopyStringToClipboard( selectedText );
1037 ChangeState( EventData::EDITING );
1040 void Controller::Impl::GetTextFromClipboard( unsigned int itemIndex, std::string& retrievedString )
1044 retrievedString = mClipboard.GetItem( itemIndex );
1048 void Controller::Impl::RepositionSelectionHandles()
1050 CharacterIndex selectionStart = mEventData->mLeftSelectionPosition;
1051 CharacterIndex selectionEnd = mEventData->mRightSelectionPosition;
1053 if( selectionStart == selectionEnd )
1055 // Nothing to select if handles are in the same place.
1059 mEventData->mDecorator->ClearHighlights();
1061 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
1062 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
1063 const GlyphInfo* const glyphsBuffer = mVisualModel->mGlyphs.Begin();
1064 const Vector2* const positionsBuffer = mVisualModel->mGlyphPositions.Begin();
1065 const Length* const charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
1066 const CharacterIndex* const glyphToCharacterBuffer = mVisualModel->mGlyphsToCharacters.Begin();
1067 const CharacterDirection* const modelCharacterDirectionsBuffer = ( 0u != mLogicalModel->mCharacterDirections.Count() ) ? mLogicalModel->mCharacterDirections.Begin() : NULL;
1069 // TODO: Better algorithm to create the highlight box.
1070 // TODO: Multi-line.
1072 // Get the height of the line.
1073 const Vector<LineRun>& lines = mVisualModel->mLines;
1074 const LineRun& firstLine = *lines.Begin();
1075 const float height = firstLine.ascender + -firstLine.descender;
1077 const bool isLastCharacter = selectionEnd >= mLogicalModel->mText.Count();
1078 const bool startDirection = ( ( NULL == modelCharacterDirectionsBuffer ) ? false : *( modelCharacterDirectionsBuffer + selectionStart ) );
1079 const bool endDirection = ( ( NULL == modelCharacterDirectionsBuffer ) ? false : *( modelCharacterDirectionsBuffer + ( selectionEnd - ( isLastCharacter ? 1u : 0u ) ) ) );
1081 // Swap the indices if the start is greater than the end.
1082 const bool indicesSwapped = selectionStart > selectionEnd;
1084 // Tell the decorator to flip the selection handles if needed.
1085 mEventData->mDecorator->SetSelectionHandleFlipState( indicesSwapped, startDirection, endDirection );
1087 if( indicesSwapped )
1089 std::swap( selectionStart, selectionEnd );
1092 // Get the indices to the first and last selected glyphs.
1093 const CharacterIndex selectionEndMinusOne = selectionEnd - 1u;
1094 const GlyphIndex glyphStart = *( charactersToGlyphBuffer + selectionStart );
1095 const Length numberOfGlyphs = *( glyphsPerCharacterBuffer + selectionEndMinusOne );
1096 const GlyphIndex glyphEnd = *( charactersToGlyphBuffer + selectionEndMinusOne ) + ( ( numberOfGlyphs > 0 ) ? numberOfGlyphs - 1u : 0u );
1098 // Check if the first glyph is a ligature that must be broken like Latin ff, fi, or Arabic ﻻ, etc which needs special code.
1099 const Length numberOfCharactersStart = *( charactersPerGlyphBuffer + glyphStart );
1100 bool splitStartGlyph = ( numberOfCharactersStart > 1u ) && HasLigatureMustBreak( mLogicalModel->GetScript( selectionStart ) );
1102 // Check if the last glyph is a ligature that must be broken like Latin ff, fi, or Arabic ﻻ, etc which needs special code.
1103 const Length numberOfCharactersEnd = *( charactersPerGlyphBuffer + glyphEnd );
1104 bool splitEndGlyph = ( glyphStart != glyphEnd ) && ( numberOfCharactersEnd > 1u ) && HasLigatureMustBreak( mLogicalModel->GetScript( selectionEndMinusOne ) );
1106 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
1108 // Traverse the glyphs.
1109 for( GlyphIndex index = glyphStart; index <= glyphEnd; ++index )
1111 const GlyphInfo& glyph = *( glyphsBuffer + index );
1112 const Vector2& position = *( positionsBuffer + index );
1114 if( splitStartGlyph )
1116 // 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.
1118 const float glyphAdvance = glyph.advance / static_cast<float>( numberOfCharactersStart );
1119 const CharacterIndex interGlyphIndex = selectionStart - *( glyphToCharacterBuffer + glyphStart );
1120 // Get the direction of the character.
1121 CharacterDirection isCurrentRightToLeft = false;
1122 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
1124 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + selectionStart );
1127 // The end point could be in the middle of the ligature.
1128 // Calculate the number of characters selected.
1129 const Length numberOfCharacters = ( glyphStart == glyphEnd ) ? ( selectionEnd - selectionStart ) : ( numberOfCharactersStart - interGlyphIndex );
1131 const float xPosition = position.x - glyph.xBearing + offset.x + glyphAdvance * static_cast<float>( isCurrentRightToLeft ? ( numberOfCharactersStart - interGlyphIndex - numberOfCharacters ) : interGlyphIndex );
1133 mEventData->mDecorator->AddHighlight( xPosition,
1135 xPosition + static_cast<float>( numberOfCharacters ) * glyphAdvance,
1136 offset.y + height );
1138 splitStartGlyph = false;
1142 if( splitEndGlyph && ( index == glyphEnd ) )
1144 // 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.
1146 const float glyphAdvance = glyph.advance / static_cast<float>( numberOfCharactersEnd );
1147 const CharacterIndex interGlyphIndex = selectionEnd - *( glyphToCharacterBuffer + glyphEnd );
1148 // Get the direction of the character.
1149 CharacterDirection isCurrentRightToLeft = false;
1150 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
1152 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + selectionEnd );
1155 const Length numberOfCharacters = numberOfCharactersEnd - interGlyphIndex;
1157 const float xPosition = position.x - glyph.xBearing + offset.x + ( isCurrentRightToLeft ? ( glyphAdvance * static_cast<float>( numberOfCharacters ) ) : 0.f );
1158 mEventData->mDecorator->AddHighlight( xPosition,
1160 xPosition + static_cast<float>( interGlyphIndex ) * glyphAdvance,
1161 offset.y + height );
1163 splitEndGlyph = false;
1167 const float xPosition = position.x - glyph.xBearing + offset.x;
1168 mEventData->mDecorator->AddHighlight( xPosition,
1170 xPosition + glyph.advance,
1171 offset.y + height );
1174 CursorInfo primaryCursorInfo;
1175 GetCursorPosition( mEventData->mLeftSelectionPosition,
1176 primaryCursorInfo );
1178 CursorInfo secondaryCursorInfo;
1179 GetCursorPosition( mEventData->mRightSelectionPosition,
1180 secondaryCursorInfo );
1182 const Vector2 primaryPosition = primaryCursorInfo.primaryPosition + offset;
1183 const Vector2 secondaryPosition = secondaryCursorInfo.primaryPosition + offset;
1185 mEventData->mDecorator->SetPosition( LEFT_SELECTION_HANDLE,
1187 primaryCursorInfo.lineOffset + offset.y,
1188 primaryCursorInfo.lineHeight );
1190 mEventData->mDecorator->SetPosition( RIGHT_SELECTION_HANDLE,
1191 secondaryPosition.x,
1192 secondaryCursorInfo.lineOffset + offset.y,
1193 secondaryCursorInfo.lineHeight );
1195 // Cursor to be positioned at end of selection so if selection interrupted and edit mode restarted the cursor will be at end of selection
1196 mEventData->mPrimaryCursorPosition = ( indicesSwapped ) ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition;
1198 // Set the flag to update the decorator.
1199 mEventData->mDecoratorUpdated = true;
1202 void Controller::Impl::RepositionSelectionHandles( float visualX, float visualY )
1204 if( NULL == mEventData )
1206 // Nothing to do if there is no text input.
1210 if( IsShowingPlaceholderText() )
1212 // Nothing to do if there is the place-holder text.
1216 const Length numberOfGlyphs = mVisualModel->mGlyphs.Count();
1217 const Length numberOfLines = mVisualModel->mLines.Count();
1218 if( ( 0 == numberOfGlyphs ) ||
1219 ( 0 == numberOfLines ) )
1221 // Nothing to do if there is no text.
1225 // Find which word was selected
1226 CharacterIndex selectionStart( 0 );
1227 CharacterIndex selectionEnd( 0 );
1228 FindSelectionIndices( visualX, visualY, selectionStart, selectionEnd );
1229 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "%p selectionStart %d selectionEnd %d\n", this, selectionStart, selectionEnd );
1231 if( selectionStart == selectionEnd )
1233 ChangeState( EventData::EDITING );
1234 // Nothing to select. i.e. a white space, out of bounds
1238 mEventData->mLeftSelectionPosition = selectionStart;
1239 mEventData->mRightSelectionPosition = selectionEnd;
1242 void Controller::Impl::SetPopupButtons()
1245 * Sets the Popup buttons to be shown depending on State.
1247 * If SELECTING : CUT & COPY + ( PASTE & CLIPBOARD if content available to paste )
1249 * If EDITING_WITH_POPUP : SELECT & SELECT_ALL
1252 TextSelectionPopup::Buttons buttonsToShow = TextSelectionPopup::NONE;
1254 if( EventData::SELECTING == mEventData->mState )
1256 buttonsToShow = TextSelectionPopup::Buttons( TextSelectionPopup::CUT | TextSelectionPopup::COPY );
1258 if( !IsClipboardEmpty() )
1260 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
1261 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::CLIPBOARD ) );
1264 if( !mEventData->mAllTextSelected )
1266 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::SELECT_ALL ) );
1269 else if( EventData::EDITING_WITH_POPUP == mEventData->mState )
1271 if( mLogicalModel->mText.Count() && !IsShowingPlaceholderText() )
1273 buttonsToShow = TextSelectionPopup::Buttons( TextSelectionPopup::SELECT | TextSelectionPopup::SELECT_ALL );
1276 if( !IsClipboardEmpty() )
1278 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
1279 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::CLIPBOARD ) );
1282 else if( EventData::EDITING_WITH_PASTE_POPUP == mEventData->mState )
1284 if ( !IsClipboardEmpty() )
1286 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
1287 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::CLIPBOARD ) );
1291 mEventData->mDecorator->SetEnabledPopupButtons( buttonsToShow );
1294 void Controller::Impl::ChangeState( EventData::State newState )
1296 if( NULL == mEventData )
1298 // Nothing to do if there is no text input.
1302 DALI_LOG_INFO( gLogFilter, Debug::General, "ChangeState state:%d newstate:%d\n", mEventData->mState, newState );
1304 if( mEventData->mState != newState )
1306 mEventData->mState = newState;
1308 if( EventData::INACTIVE == mEventData->mState )
1310 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
1311 mEventData->mDecorator->StopCursorBlink();
1312 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1313 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1314 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1315 mEventData->mDecorator->SetPopupActive( false );
1316 mEventData->mDecoratorUpdated = true;
1319 else if( EventData::INTERRUPTED == mEventData->mState)
1321 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1322 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1323 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1324 mEventData->mDecorator->SetPopupActive( false );
1325 mEventData->mDecoratorUpdated = true;
1328 else if( EventData::SELECTING == mEventData->mState )
1330 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
1331 mEventData->mDecorator->StopCursorBlink();
1332 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1333 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true );
1334 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true );
1335 if( mEventData->mGrabHandlePopupEnabled )
1338 mEventData->mDecorator->SetPopupActive( true );
1340 mEventData->mDecoratorUpdated = true;
1342 else if( EventData::EDITING == mEventData->mState )
1344 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1345 if( mEventData->mCursorBlinkEnabled )
1347 mEventData->mDecorator->StartCursorBlink();
1349 // Grab handle is not shown until a tap is received whilst EDITING
1350 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1351 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1352 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1353 if( mEventData->mGrabHandlePopupEnabled )
1355 mEventData->mDecorator->SetPopupActive( false );
1357 mEventData->mDecoratorUpdated = true;
1360 else if( EventData::EDITING_WITH_POPUP == mEventData->mState )
1362 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "EDITING_WITH_POPUP \n", newState );
1364 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1365 if( mEventData->mCursorBlinkEnabled )
1367 mEventData->mDecorator->StartCursorBlink();
1369 if( mEventData->mSelectionEnabled )
1371 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1372 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1376 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
1378 if( mEventData->mGrabHandlePopupEnabled )
1381 mEventData->mDecorator->SetPopupActive( true );
1384 mEventData->mDecoratorUpdated = true;
1386 else if( EventData::EDITING_WITH_GRAB_HANDLE == mEventData->mState )
1388 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "EDITING_WITH_GRAB_HANDLE \n", newState );
1390 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1391 if( mEventData->mCursorBlinkEnabled )
1393 mEventData->mDecorator->StartCursorBlink();
1395 // Grab handle is not shown until a tap is received whilst EDITING
1396 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
1397 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1398 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1399 if( mEventData->mGrabHandlePopupEnabled )
1401 mEventData->mDecorator->SetPopupActive( false );
1403 mEventData->mDecoratorUpdated = true;
1406 else if( EventData::SELECTION_HANDLE_PANNING == mEventData->mState )
1408 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
1409 mEventData->mDecorator->StopCursorBlink();
1410 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1411 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true );
1412 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true );
1413 if( mEventData->mGrabHandlePopupEnabled )
1415 mEventData->mDecorator->SetPopupActive( false );
1417 mEventData->mDecoratorUpdated = true;
1419 else if( EventData::GRAB_HANDLE_PANNING == mEventData->mState )
1421 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "GRAB_HANDLE_PANNING \n", newState );
1423 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1424 if( mEventData->mCursorBlinkEnabled )
1426 mEventData->mDecorator->StartCursorBlink();
1428 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
1429 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1430 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1431 if( mEventData->mGrabHandlePopupEnabled )
1433 mEventData->mDecorator->SetPopupActive( false );
1435 mEventData->mDecoratorUpdated = true;
1437 else if( EventData::EDITING_WITH_PASTE_POPUP == mEventData->mState )
1439 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "EDITING_WITH_PASTE_POPUP \n", newState );
1441 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1442 if( mEventData->mCursorBlinkEnabled )
1444 mEventData->mDecorator->StartCursorBlink();
1447 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
1448 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1449 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1451 if( mEventData->mGrabHandlePopupEnabled )
1454 mEventData->mDecorator->SetPopupActive( true );
1457 mEventData->mDecoratorUpdated = true;
1462 LineIndex Controller::Impl::GetClosestLine( float y ) const
1464 float totalHeight = 0.f;
1465 LineIndex lineIndex = 0u;
1467 const Vector<LineRun>& lines = mVisualModel->mLines;
1468 for( LineIndex endLine = lines.Count();
1469 lineIndex < endLine;
1472 const LineRun& lineRun = lines[lineIndex];
1473 totalHeight += lineRun.ascender + -lineRun.descender;
1474 if( y < totalHeight )
1480 if( lineIndex == 0 )
1488 void Controller::Impl::FindSelectionIndices( float visualX, float visualY, CharacterIndex& startIndex, CharacterIndex& endIndex )
1490 CharacterIndex hitCharacter = GetClosestCursorIndex( visualX, visualY );
1491 DALI_ASSERT_DEBUG( hitCharacter <= mLogicalModel->mText.Count() && "GetClosestCursorIndex returned out of bounds index" );
1493 if( mLogicalModel->mText.Count() == 0 )
1495 return; // if model empty
1498 if( hitCharacter >= mLogicalModel->mText.Count() )
1500 // Closest hit character is the last character.
1501 if( hitCharacter == mLogicalModel->mText.Count() )
1503 hitCharacter--; //Hit character index set to last character in logical model
1507 // hitCharacter is out of bounds
1512 startIndex = hitCharacter;
1513 endIndex = hitCharacter;
1514 bool isHitCharacterWhitespace = TextAbstraction::IsWhiteSpace( mLogicalModel->mText[hitCharacter] );
1516 // Find the start and end of the text
1517 for( startIndex = hitCharacter; startIndex > 0; --startIndex )
1519 if( isHitCharacterWhitespace != TextAbstraction::IsWhiteSpace( mLogicalModel->mText[ startIndex-1 ] ) )
1524 const CharacterIndex pastTheEnd = mLogicalModel->mText.Count();
1525 for( endIndex = hitCharacter + 1u; endIndex < pastTheEnd; ++endIndex )
1527 if( isHitCharacterWhitespace != TextAbstraction::IsWhiteSpace( mLogicalModel->mText[ endIndex ] ) )
1534 CharacterIndex Controller::Impl::GetClosestCursorIndex( float visualX,
1537 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "GetClosestCursorIndex %p closest visualX %f visualY %f\n", this, visualX, visualY );
1539 if( NULL == mEventData )
1541 // Nothing to do if there is no text input.
1545 CharacterIndex logicalIndex = 0u;
1547 const Length numberOfGlyphs = mVisualModel->mGlyphs.Count();
1548 const Length numberOfLines = mVisualModel->mLines.Count();
1549 if( ( 0 == numberOfGlyphs ) ||
1550 ( 0 == numberOfLines ) )
1552 return logicalIndex;
1555 // Find which line is closest
1556 const LineIndex lineIndex = GetClosestLine( visualY );
1557 const LineRun& line = mVisualModel->mLines[lineIndex];
1559 // Get the positions of the glyphs.
1560 const Vector<Vector2>& positions = mVisualModel->mGlyphPositions;
1561 const Vector2* const positionsBuffer = positions.Begin();
1563 // Get the visual to logical conversion tables.
1564 const CharacterIndex* const visualToLogicalBuffer = ( 0u != mLogicalModel->mVisualToLogicalMap.Count() ) ? mLogicalModel->mVisualToLogicalMap.Begin() : NULL;
1565 const CharacterIndex* const visualToLogicalCursorBuffer = mLogicalModel->mVisualToLogicalCursorMap.Begin();
1567 // Get the character to glyph conversion table.
1568 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
1570 // Get the glyphs per character table.
1571 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
1573 // If the vector is void, there is no right to left characters.
1574 const bool hasRightToLeftCharacters = NULL != visualToLogicalBuffer;
1576 const CharacterIndex startCharacter = line.characterRun.characterIndex;
1577 const CharacterIndex endCharacter = line.characterRun.characterIndex + line.characterRun.numberOfCharacters;
1578 DALI_ASSERT_DEBUG( endCharacter <= mLogicalModel->mText.Count() && "Invalid line info" );
1580 // Whether there is a hit on a glyph.
1581 bool matched = false;
1583 // Traverses glyphs in visual order. To do that use the visual to logical conversion table.
1584 CharacterIndex visualIndex = startCharacter;
1585 Length numberOfCharacters = 0u;
1586 for( ; !matched && ( visualIndex < endCharacter ); ++visualIndex )
1588 // The character in logical order.
1589 const CharacterIndex characterLogicalOrderIndex = hasRightToLeftCharacters ? *( visualToLogicalBuffer + visualIndex ) : visualIndex;
1591 // Get the script of the character.
1592 const Script script = mLogicalModel->GetScript( characterLogicalOrderIndex );
1594 // The number of glyphs for that character
1595 const Length numberOfGlyphs = *( glyphsPerCharacterBuffer + characterLogicalOrderIndex );
1596 ++numberOfCharacters;
1599 if( 0u != numberOfGlyphs )
1601 // Get the first character/glyph of the group of glyphs.
1602 const CharacterIndex firstVisualCharacterIndex = 1u + visualIndex - numberOfCharacters;
1603 const CharacterIndex firstLogicalCharacterIndex = hasRightToLeftCharacters ? *( visualToLogicalBuffer + firstVisualCharacterIndex ) : firstVisualCharacterIndex;
1604 const GlyphIndex firstLogicalGlyphIndex = *( charactersToGlyphBuffer + firstLogicalCharacterIndex );
1606 // Get the metrics for the group of glyphs.
1607 GlyphMetrics glyphMetrics;
1608 GetGlyphsMetrics( firstLogicalGlyphIndex,
1614 // Get the position of the first glyph.
1615 const Vector2& position = *( positionsBuffer + firstLogicalGlyphIndex );
1617 // Whether the glyph can be split, like Latin ligatures fi, ff or Arabic ﻻ.
1618 const bool isInterglyphIndex = ( numberOfCharacters > numberOfGlyphs ) && HasLigatureMustBreak( script );
1619 const Length numberOfBlocks = isInterglyphIndex ? numberOfCharacters : 1u;
1620 const float glyphAdvance = glyphMetrics.advance / static_cast<float>( numberOfBlocks );
1622 GlyphIndex index = 0u;
1623 for( ; !matched && ( index < numberOfBlocks ); ++index )
1625 // Find the mid-point of the area containing the glyph
1626 const float glyphCenter = -glyphMetrics.xBearing + position.x + ( static_cast<float>( index ) + 0.5f ) * glyphAdvance;
1628 if( visualX < glyphCenter )
1637 visualIndex = firstVisualCharacterIndex + index;
1641 numberOfCharacters = 0u;
1647 // Return the logical position of the cursor in characters.
1651 visualIndex = endCharacter;
1654 logicalIndex = hasRightToLeftCharacters ? *( visualToLogicalCursorBuffer + visualIndex ) : visualIndex;
1655 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "%p closest visualIndex %d logicalIndex %d\n", this, visualIndex, logicalIndex );
1657 DALI_ASSERT_DEBUG( ( logicalIndex <= mLogicalModel->mText.Count() && logicalIndex >= 0 ) && "GetClosestCursorIndex - Out of bounds index" );
1659 return logicalIndex;
1662 void Controller::Impl::GetCursorPosition( CharacterIndex logical,
1663 CursorInfo& cursorInfo )
1665 // TODO: Check for multiline with \n, etc...
1667 const Length numberOfCharacters = mLogicalModel->mText.Count();
1668 if( !IsShowingRealText() )
1670 // Do not want to use the place-holder text to set the cursor position.
1672 // Use the line's height of the font's family set to set the cursor's size.
1673 // If there is no font's family set, use the default font.
1674 // Use the current alignment to place the cursor at the beginning, center or end of the box.
1676 cursorInfo.lineOffset = 0.f;
1677 cursorInfo.lineHeight = GetDefaultFontLineHeight();
1678 cursorInfo.primaryCursorHeight = cursorInfo.lineHeight;
1680 switch( mLayoutEngine.GetHorizontalAlignment() )
1682 case LayoutEngine::HORIZONTAL_ALIGN_BEGIN:
1684 cursorInfo.primaryPosition.x = 0.f;
1687 case LayoutEngine::HORIZONTAL_ALIGN_CENTER:
1689 cursorInfo.primaryPosition.x = floorf( 0.5f * mVisualModel->mControlSize.width );
1692 case LayoutEngine::HORIZONTAL_ALIGN_END:
1694 cursorInfo.primaryPosition.x = mVisualModel->mControlSize.width - mEventData->mDecorator->GetCursorWidth();
1699 switch( mLayoutEngine.GetVerticalAlignment() )
1701 case LayoutEngine::VERTICAL_ALIGN_TOP:
1703 cursorInfo.primaryPosition.y = 0.f;
1706 case LayoutEngine::VERTICAL_ALIGN_CENTER:
1708 cursorInfo.primaryPosition.y = floorf( 0.5f * ( mVisualModel->mControlSize.height - cursorInfo.lineHeight ) );
1711 case LayoutEngine::VERTICAL_ALIGN_BOTTOM:
1713 cursorInfo.primaryPosition.y = mVisualModel->mControlSize.height - cursorInfo.lineHeight;
1718 // Nothing else to do.
1722 // Check if the logical position is the first or the last one of the text.
1723 const bool isFirstPosition = 0u == logical;
1724 const bool isLastPosition = numberOfCharacters == logical;
1726 // 'logical' is the logical 'cursor' index.
1727 // Get the next and current logical 'character' index.
1728 const CharacterIndex nextCharacterIndex = logical;
1729 const CharacterIndex characterIndex = isFirstPosition ? logical : logical - 1u;
1731 // Get the direction of the character and the next one.
1732 const CharacterDirection* const modelCharacterDirectionsBuffer = ( 0u != mLogicalModel->mCharacterDirections.Count() ) ? mLogicalModel->mCharacterDirections.Begin() : NULL;
1734 CharacterDirection isCurrentRightToLeft = false;
1735 CharacterDirection isNextRightToLeft = false;
1736 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
1738 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + characterIndex );
1739 isNextRightToLeft = *( modelCharacterDirectionsBuffer + nextCharacterIndex );
1742 // Get the line where the character is laid-out.
1743 const LineRun* const modelLines = mVisualModel->mLines.Begin();
1745 const LineIndex lineIndex = mVisualModel->GetLineOfCharacter( characterIndex );
1746 const LineRun& line = *( modelLines + lineIndex );
1748 // Get the paragraph's direction.
1749 const CharacterDirection isRightToLeftParagraph = line.direction;
1751 // Check whether there is an alternative position:
1753 cursorInfo.isSecondaryCursor = ( !isLastPosition && ( isCurrentRightToLeft != isNextRightToLeft ) ) ||
1754 ( isLastPosition && ( isRightToLeftParagraph != isCurrentRightToLeft ) );
1756 // Set the line offset and height.
1757 cursorInfo.lineOffset = 0.f;
1758 cursorInfo.lineHeight = line.ascender + -line.descender;
1760 // Calculate the primary cursor.
1762 CharacterIndex index = characterIndex;
1763 if( cursorInfo.isSecondaryCursor )
1765 // If there is a secondary position, the primary cursor may be in a different place than the logical index.
1767 if( isLastPosition )
1769 // The position of the cursor after the last character needs special
1770 // care depending on its direction and the direction of the paragraph.
1772 // Need to find the first character after the last character with the paragraph's direction.
1773 // i.e l0 l1 l2 r0 r1 should find r0.
1775 // TODO: check for more than one line!
1776 index = isRightToLeftParagraph ? line.characterRun.characterIndex : line.characterRun.characterIndex + line.characterRun.numberOfCharacters - 1u;
1777 index = mLogicalModel->GetLogicalCharacterIndex( index );
1781 index = ( isRightToLeftParagraph == isCurrentRightToLeft ) ? characterIndex : nextCharacterIndex;
1785 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
1786 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
1787 const Length* const charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
1788 const CharacterIndex* const glyphsToCharactersBuffer = mVisualModel->mGlyphsToCharacters.Begin();
1789 const Vector2* const glyphPositionsBuffer = mVisualModel->mGlyphPositions.Begin();
1791 // Convert the cursor position into the glyph position.
1792 const GlyphIndex primaryGlyphIndex = *( charactersToGlyphBuffer + index );
1793 const Length primaryNumberOfGlyphs = *( glyphsPerCharacterBuffer + index );
1794 const Length primaryNumberOfCharacters = *( charactersPerGlyphBuffer + primaryGlyphIndex );
1796 // Get the metrics for the group of glyphs.
1797 GlyphMetrics glyphMetrics;
1798 GetGlyphsMetrics( primaryGlyphIndex,
1799 primaryNumberOfGlyphs,
1804 // Whether to add the glyph's advance to the cursor position.
1805 // 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,
1806 // if the logical cursor is one, the position is the position of the first glyph and the advance is added.
1807 // A 'truth table' was build and an online Karnaugh map tool was used to simplify the logic.
1828 // Where F -> isFirstPosition
1829 // L -> isLastPosition
1830 // C -> isCurrentRightToLeft
1831 // P -> isRightToLeftParagraph
1832 // A -> Whether to add the glyph's advance.
1834 const bool addGlyphAdvance = ( ( isLastPosition && !isRightToLeftParagraph ) ||
1835 ( isFirstPosition && isRightToLeftParagraph ) ||
1836 ( !isFirstPosition && !isLastPosition && !isCurrentRightToLeft ) );
1838 float glyphAdvance = addGlyphAdvance ? glyphMetrics.advance : 0.f;
1840 if( !isLastPosition &&
1841 ( primaryNumberOfCharacters > 1u ) )
1843 const CharacterIndex firstIndex = *( glyphsToCharactersBuffer + primaryGlyphIndex );
1845 bool isCurrentRightToLeft = false;
1846 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
1848 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + index );
1851 Length numberOfGlyphAdvance = ( isFirstPosition ? 0u : 1u ) + characterIndex - firstIndex;
1852 if( isCurrentRightToLeft )
1854 numberOfGlyphAdvance = primaryNumberOfCharacters - numberOfGlyphAdvance;
1857 glyphAdvance = static_cast<float>( numberOfGlyphAdvance ) * glyphMetrics.advance / static_cast<float>( primaryNumberOfCharacters );
1860 // Get the glyph position and x bearing.
1861 const Vector2& primaryPosition = *( glyphPositionsBuffer + primaryGlyphIndex );
1863 // Set the primary cursor's height.
1864 cursorInfo.primaryCursorHeight = cursorInfo.isSecondaryCursor ? 0.5f * glyphMetrics.fontHeight : glyphMetrics.fontHeight;
1866 // Set the primary cursor's position.
1867 cursorInfo.primaryPosition.x = -glyphMetrics.xBearing + primaryPosition.x + glyphAdvance;
1868 cursorInfo.primaryPosition.y = line.ascender - glyphMetrics.ascender;
1870 // Calculate the secondary cursor.
1872 if( cursorInfo.isSecondaryCursor )
1874 // Set the secondary cursor's height.
1875 cursorInfo.secondaryCursorHeight = 0.5f * glyphMetrics.fontHeight;
1877 CharacterIndex index = characterIndex;
1878 if( !isLastPosition )
1880 index = ( isRightToLeftParagraph == isCurrentRightToLeft ) ? nextCharacterIndex : characterIndex;
1883 const GlyphIndex secondaryGlyphIndex = *( charactersToGlyphBuffer + index );
1884 const Length secondaryNumberOfGlyphs = *( glyphsPerCharacterBuffer + index );
1886 const Vector2& secondaryPosition = *( glyphPositionsBuffer + secondaryGlyphIndex );
1888 GetGlyphsMetrics( secondaryGlyphIndex,
1889 secondaryNumberOfGlyphs,
1894 // Set the secondary cursor's position.
1895 cursorInfo.secondaryPosition.x = -glyphMetrics.xBearing + secondaryPosition.x + ( isCurrentRightToLeft ? 0.f : glyphMetrics.advance );
1896 cursorInfo.secondaryPosition.y = cursorInfo.lineHeight - cursorInfo.secondaryCursorHeight - line.descender - ( glyphMetrics.fontHeight - glyphMetrics.ascender );
1900 CharacterIndex Controller::Impl::CalculateNewCursorIndex( CharacterIndex index ) const
1902 if( NULL == mEventData )
1904 // Nothing to do if there is no text input.
1908 CharacterIndex cursorIndex = mEventData->mPrimaryCursorPosition;
1910 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
1911 const Length* const charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
1913 GlyphIndex glyphIndex = *( charactersToGlyphBuffer + index );
1914 Length numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
1916 if( numberOfCharacters > 1u )
1918 const Script script = mLogicalModel->GetScript( index );
1919 if( HasLigatureMustBreak( script ) )
1921 // Prevents to jump the whole Latin ligatures like fi, ff, or Arabic ﻻ, ...
1922 numberOfCharacters = 1u;
1927 while( 0u == numberOfCharacters )
1930 numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
1934 if( index < mEventData->mPrimaryCursorPosition )
1936 cursorIndex -= numberOfCharacters;
1940 cursorIndex += numberOfCharacters;
1946 void Controller::Impl::UpdateCursorPosition( const CursorInfo& cursorInfo )
1948 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "-->Controller::UpdateCursorPosition %p\n", this );
1949 if( NULL == mEventData )
1951 // Nothing to do if there is no text input.
1952 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition no event data\n" );
1956 const Vector2 offset = mEventData->mScrollPosition + ( IsShowingRealText() ? mAlignmentOffset : Vector2::ZERO );
1957 const Vector2 cursorPosition = cursorInfo.primaryPosition + offset;
1959 // Sets the cursor position.
1960 mEventData->mDecorator->SetPosition( PRIMARY_CURSOR,
1963 cursorInfo.primaryCursorHeight,
1964 cursorInfo.lineHeight );
1965 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Primary cursor position: %f,%f\n", cursorPosition.x, cursorPosition.y );
1967 // Sets the grab handle position.
1968 mEventData->mDecorator->SetPosition( GRAB_HANDLE,
1970 cursorInfo.lineOffset + offset.y,
1971 cursorInfo.lineHeight );
1973 if( cursorInfo.isSecondaryCursor )
1975 mEventData->mDecorator->SetPosition( SECONDARY_CURSOR,
1976 cursorInfo.secondaryPosition.x + offset.x,
1977 cursorInfo.secondaryPosition.y + offset.y,
1978 cursorInfo.secondaryCursorHeight,
1979 cursorInfo.lineHeight );
1980 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Secondary cursor position: %f,%f\n", cursorInfo.secondaryPosition.x + offset.x, cursorInfo.secondaryPosition.y + offset.y );
1983 // Set which cursors are active according the state.
1984 if( EventData::IsEditingState( mEventData->mState ) || ( EventData::GRAB_HANDLE_PANNING == mEventData->mState ) )
1986 if( cursorInfo.isSecondaryCursor )
1988 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_BOTH );
1992 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1997 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
2000 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition\n" );
2003 void Controller::Impl::UpdateSelectionHandle( HandleType handleType,
2004 const CursorInfo& cursorInfo )
2006 if( ( LEFT_SELECTION_HANDLE != handleType ) &&
2007 ( RIGHT_SELECTION_HANDLE != handleType ) )
2012 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
2013 const Vector2 cursorPosition = cursorInfo.primaryPosition + offset;
2015 // Sets the handle's position.
2016 mEventData->mDecorator->SetPosition( handleType,
2018 cursorInfo.lineOffset + offset.y,
2019 cursorInfo.lineHeight );
2021 // If selection handle at start of the text and other at end of the text then all text is selected.
2022 const CharacterIndex startOfSelection = std::min( mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition );
2023 const CharacterIndex endOfSelection = std::max ( mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition );
2024 mEventData->mAllTextSelected = ( startOfSelection == 0 ) && ( endOfSelection == mLogicalModel->mText.Count() );
2027 void Controller::Impl::ClampHorizontalScroll( const Vector2& actualSize )
2029 // Clamp between -space & 0 (and the text alignment).
2031 if( actualSize.width > mVisualModel->mControlSize.width )
2033 const float space = ( actualSize.width - mVisualModel->mControlSize.width ) + mAlignmentOffset.x;
2034 mEventData->mScrollPosition.x = ( mEventData->mScrollPosition.x < -space ) ? -space : mEventData->mScrollPosition.x;
2035 mEventData->mScrollPosition.x = ( mEventData->mScrollPosition.x > -mAlignmentOffset.x ) ? -mAlignmentOffset.x : mEventData->mScrollPosition.x;
2037 mEventData->mDecoratorUpdated = true;
2041 mEventData->mScrollPosition.x = 0.f;
2045 void Controller::Impl::ClampVerticalScroll( const Vector2& actualSize )
2047 // Clamp between -space & 0 (and the text alignment).
2048 if( actualSize.height > mVisualModel->mControlSize.height )
2050 const float space = ( actualSize.height - mVisualModel->mControlSize.height ) + mAlignmentOffset.y;
2051 mEventData->mScrollPosition.y = ( mEventData->mScrollPosition.y < -space ) ? -space : mEventData->mScrollPosition.y;
2052 mEventData->mScrollPosition.y = ( mEventData->mScrollPosition.y > -mAlignmentOffset.y ) ? -mAlignmentOffset.y : mEventData->mScrollPosition.y;
2054 mEventData->mDecoratorUpdated = true;
2058 mEventData->mScrollPosition.y = 0.f;
2062 void Controller::Impl::ScrollToMakePositionVisible( const Vector2& position )
2064 // position is in actor's coords.
2065 const float positionEnd = position.x + ( mEventData->mDecorator ? mEventData->mDecorator->GetCursorWidth() : 0.f );
2067 // Transform the position to decorator coords.
2068 const float offset = mEventData->mScrollPosition.x + mAlignmentOffset.x;
2069 const float decoratorPositionBegin = position.x + offset;
2070 const float decoratorPositionEnd = positionEnd + offset;
2072 if( decoratorPositionBegin < 0.f )
2074 mEventData->mScrollPosition.x = -position.x - mAlignmentOffset.x;
2076 else if( decoratorPositionEnd > mVisualModel->mControlSize.width )
2078 mEventData->mScrollPosition.x = mVisualModel->mControlSize.width - positionEnd - mAlignmentOffset.x;
2082 void Controller::Impl::ScrollTextToMatchCursor( const CursorInfo& cursorInfo )
2084 // Get the current cursor position in decorator coords.
2085 const Vector2& currentCursorPosition = mEventData->mDecorator->GetPosition( PRIMARY_CURSOR );
2087 // Calculate the offset to match the cursor position before the character was deleted.
2088 mEventData->mScrollPosition.x = currentCursorPosition.x - cursorInfo.primaryPosition.x - mAlignmentOffset.x;
2090 ClampHorizontalScroll( mVisualModel->GetActualSize() );
2093 void Controller::Impl::RequestRelayout()
2095 mControlInterface.RequestTextRelayout();
2100 } // namespace Toolkit