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.h>
23 #include <dali/public-api/adaptor-framework/key.h>
24 #include <dali/integration-api/debug.h>
25 #include <dali/devel-api/adaptor-framework/clipboard-event-notifier.h>
28 #include <dali-toolkit/internal/text/bidirectional-support.h>
29 #include <dali-toolkit/internal/text/character-set-conversion.h>
30 #include <dali-toolkit/internal/text/layouts/layout-parameters.h>
31 #include <dali-toolkit/internal/text/markup-processor.h>
32 #include <dali-toolkit/internal/text/text-controller-impl.h>
37 #if defined(DEBUG_ENABLED)
38 Debug::Filter* gLogFilter = Debug::Filter::New(Debug::NoLogging, true, "LOG_TEXT_CONTROLS");
41 const float MAX_FLOAT = std::numeric_limits<float>::max();
42 const unsigned int POINTS_PER_INCH = 72;
44 const std::string EMPTY_STRING("");
45 const unsigned int ZERO = 0u;
47 float ConvertToEven( float value )
49 int intValue(static_cast<int>( value ));
50 return static_cast<float>(intValue % 2 == 0) ? intValue : (intValue + 1);
64 ControllerPtr Controller::New( ControlInterface& controlInterface )
66 return ControllerPtr( new Controller( controlInterface ) );
69 void Controller::EnableTextInput( DecoratorPtr decorator )
71 if( NULL == mImpl->mEventData )
73 mImpl->mEventData = new EventData( decorator );
77 void Controller::SetMarkupProcessorEnabled( bool enable )
79 mImpl->mMarkupProcessorEnabled = enable;
82 bool Controller::IsMarkupProcessorEnabled() const
84 return mImpl->mMarkupProcessorEnabled;
87 void Controller::SetText( const std::string& text )
89 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Controller::SetText\n" );
91 // Reset keyboard as text changed
92 mImpl->ResetImfManager();
94 // Remove the previously set text and style.
100 CharacterIndex lastCursorIndex = 0u;
102 if( NULL != mImpl->mEventData )
104 // If popup shown then hide it by switching to Editing state
105 if( ( EventData::SELECTING == mImpl->mEventData->mState ) ||
106 ( EventData::EDITING_WITH_POPUP == mImpl->mEventData->mState ) ||
107 ( EventData::EDITING_WITH_GRAB_HANDLE == mImpl->mEventData->mState ) ||
108 ( EventData::EDITING_WITH_PASTE_POPUP == mImpl->mEventData->mState ) )
110 mImpl->ChangeState( EventData::EDITING );
116 MarkupProcessData markupProcessData( mImpl->mLogicalModel->mColorRuns );
118 Length textSize = 0u;
119 const uint8_t* utf8 = NULL;
120 if( mImpl->mMarkupProcessorEnabled )
122 ProcessMarkupString( text, markupProcessData );
123 textSize = markupProcessData.markupProcessedText.size();
125 // This is a bit horrible but std::string returns a (signed) char*
126 utf8 = reinterpret_cast<const uint8_t*>( markupProcessData.markupProcessedText.c_str() );
130 textSize = text.size();
132 // This is a bit horrible but std::string returns a (signed) char*
133 utf8 = reinterpret_cast<const uint8_t*>( text.c_str() );
136 // Convert text into UTF-32
137 Vector<Character>& utf32Characters = mImpl->mLogicalModel->mText;
138 utf32Characters.Resize( textSize );
140 // Transform a text array encoded in utf8 into an array encoded in utf32.
141 // It returns the actual number of characters.
142 Length characterCount = Utf8ToUtf32( utf8, textSize, utf32Characters.Begin() );
143 utf32Characters.Resize( characterCount );
145 DALI_ASSERT_DEBUG( textSize >= characterCount && "Invalid UTF32 conversion length" );
146 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Controller::SetText %p UTF8 size %d, UTF32 size %d\n", this, textSize, mImpl->mLogicalModel->mText.Count() );
148 // To reset the cursor position
149 lastCursorIndex = characterCount;
151 // Update the rest of the model during size negotiation
152 mImpl->QueueModifyEvent( ModifyEvent::TEXT_REPLACED );
154 // The natural size needs to be re-calculated.
155 mImpl->mRecalculateNaturalSize = true;
157 // Apply modifications to the model
158 mImpl->mOperationsPending = ALL_OPERATIONS;
162 ShowPlaceholderText();
165 // Resets the cursor position.
166 ResetCursorPosition( lastCursorIndex );
168 // Scrolls the text to make the cursor visible.
169 ResetScrollPosition();
171 mImpl->RequestRelayout();
173 if( NULL != mImpl->mEventData )
175 // Cancel previously queued events
176 mImpl->mEventData->mEventQueue.clear();
179 // Notify IMF as text changed
182 // Do this last since it provides callbacks into application code
183 mImpl->mControlInterface.TextChanged();
186 void Controller::GetText( std::string& text ) const
188 if( !mImpl->IsShowingPlaceholderText() )
190 Vector<Character>& utf32Characters = mImpl->mLogicalModel->mText;
192 if( 0u != utf32Characters.Count() )
194 Utf32ToUtf8( &utf32Characters[0], utf32Characters.Count(), text );
199 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Controller::GetText %p empty (but showing placeholder)\n", this );
203 unsigned int Controller::GetLogicalCursorPosition() const
205 if( NULL != mImpl->mEventData )
207 return mImpl->mEventData->mPrimaryCursorPosition;
213 void Controller::SetPlaceholderText( PlaceholderType type, const std::string& text )
215 if( NULL != mImpl->mEventData )
217 if( PLACEHOLDER_TYPE_INACTIVE == type )
219 mImpl->mEventData->mPlaceholderTextInactive = text;
223 mImpl->mEventData->mPlaceholderTextActive = text;
226 // Update placeholder if there is no text
227 if( mImpl->IsShowingPlaceholderText() ||
228 ( 0u == mImpl->mLogicalModel->mText.Count() ) )
230 ShowPlaceholderText();
235 void Controller::GetPlaceholderText( PlaceholderType type, std::string& text ) const
237 if( NULL != mImpl->mEventData )
239 if( PLACEHOLDER_TYPE_INACTIVE == type )
241 text = mImpl->mEventData->mPlaceholderTextInactive;
245 text = mImpl->mEventData->mPlaceholderTextActive;
250 void Controller::SetMaximumNumberOfCharacters( int maxCharacters )
252 if( maxCharacters >= 0 )
254 mImpl->mMaximumNumberOfCharacters = maxCharacters;
258 int Controller::GetMaximumNumberOfCharacters()
260 return mImpl->mMaximumNumberOfCharacters;
263 void Controller::SetDefaultFontFamily( const std::string& defaultFontFamily )
265 if( NULL == mImpl->mFontDefaults )
267 mImpl->mFontDefaults = new FontDefaults();
270 mImpl->mFontDefaults->mFontDescription.family = defaultFontFamily;
271 DALI_LOG_INFO( gLogFilter, Debug::General, "Controller::SetDefaultFontFamily %s\n", defaultFontFamily.c_str());
272 mImpl->mUserDefinedFontFamily = true;
274 // Clear the font-specific data
277 mImpl->mOperationsPending = ALL_OPERATIONS;
278 mImpl->mRecalculateNaturalSize = true;
280 mImpl->RequestRelayout();
283 const std::string& Controller::GetDefaultFontFamily() const
285 if( NULL != mImpl->mFontDefaults )
287 return mImpl->mFontDefaults->mFontDescription.family;
293 void Controller::SetDefaultFontStyle( const std::string& style )
295 if( NULL == mImpl->mFontDefaults )
297 mImpl->mFontDefaults = new FontDefaults();
300 mImpl->mFontDefaults->mFontStyle = style;
303 const std::string& Controller::GetDefaultFontStyle() const
305 if( NULL != mImpl->mFontDefaults )
307 return mImpl->mFontDefaults->mFontStyle;
313 void Controller::SetDefaultFontWidth( FontWidth width )
315 if( NULL == mImpl->mFontDefaults )
317 mImpl->mFontDefaults = new FontDefaults();
320 mImpl->mFontDefaults->mFontDescription.width = width;
322 // Clear the font-specific data
325 mImpl->mOperationsPending = ALL_OPERATIONS;
326 mImpl->mRecalculateNaturalSize = true;
328 mImpl->RequestRelayout();
331 FontWidth Controller::GetDefaultFontWidth() const
333 if( NULL != mImpl->mFontDefaults )
335 return mImpl->mFontDefaults->mFontDescription.width;
338 return TextAbstraction::FontWidth::NORMAL;
341 void Controller::SetDefaultFontWeight( FontWeight weight )
343 if( NULL == mImpl->mFontDefaults )
345 mImpl->mFontDefaults = new FontDefaults();
348 mImpl->mFontDefaults->mFontDescription.weight = weight;
350 // Clear the font-specific data
353 mImpl->mOperationsPending = ALL_OPERATIONS;
354 mImpl->mRecalculateNaturalSize = true;
356 mImpl->RequestRelayout();
359 FontWeight Controller::GetDefaultFontWeight() const
361 if( NULL != mImpl->mFontDefaults )
363 return mImpl->mFontDefaults->mFontDescription.weight;
366 return TextAbstraction::FontWeight::NORMAL;
369 void Controller::SetDefaultFontSlant( FontSlant slant )
371 if( NULL == mImpl->mFontDefaults )
373 mImpl->mFontDefaults = new FontDefaults();
376 mImpl->mFontDefaults->mFontDescription.slant = slant;
378 // Clear the font-specific data
381 mImpl->mOperationsPending = ALL_OPERATIONS;
382 mImpl->mRecalculateNaturalSize = true;
384 mImpl->RequestRelayout();
387 FontSlant Controller::GetDefaultFontSlant() const
389 if( NULL != mImpl->mFontDefaults )
391 return mImpl->mFontDefaults->mFontDescription.slant;
394 return TextAbstraction::FontSlant::NORMAL;
397 void Controller::SetDefaultPointSize( float pointSize )
399 if( NULL == mImpl->mFontDefaults )
401 mImpl->mFontDefaults = new FontDefaults();
404 mImpl->mFontDefaults->mDefaultPointSize = pointSize;
406 unsigned int horizontalDpi( 0u );
407 unsigned int verticalDpi( 0u );
408 mImpl->mFontClient.GetDpi( horizontalDpi, verticalDpi );
410 // Adjust the metrics if the fixed-size font should be down-scaled
411 int maxEmojiSize( pointSize/POINTS_PER_INCH * verticalDpi );
412 DALI_LOG_INFO( gLogFilter, Debug::General, "Controller::SetDefaultPointSize %p setting MaxEmojiSize %d\n", this, maxEmojiSize );
413 mImpl->mMetrics->SetMaxEmojiSize( maxEmojiSize );
415 // Clear the font-specific data
418 mImpl->mOperationsPending = ALL_OPERATIONS;
419 mImpl->mRecalculateNaturalSize = true;
421 mImpl->RequestRelayout();
424 float Controller::GetDefaultPointSize() const
426 if( NULL != mImpl->mFontDefaults )
428 return mImpl->mFontDefaults->mDefaultPointSize;
434 void Controller::UpdateAfterFontChange( std::string& newDefaultFont )
436 DALI_LOG_INFO( gLogFilter, Debug::Concise, "Controller::UpdateAfterFontChange");
438 if( !mImpl->mUserDefinedFontFamily ) // If user defined font then should not update when system font changes
440 DALI_LOG_INFO( gLogFilter, Debug::Concise, "Controller::UpdateAfterFontChange newDefaultFont(%s)\n", newDefaultFont.c_str() );
442 mImpl->mFontDefaults->mFontDescription.family = newDefaultFont;
443 mImpl->UpdateModel( ALL_OPERATIONS );
444 mImpl->QueueModifyEvent( ModifyEvent::TEXT_REPLACED );
445 mImpl->mRecalculateNaturalSize = true;
446 mImpl->RequestRelayout();
450 void Controller::SetTextColor( const Vector4& textColor )
452 mImpl->mTextColor = textColor;
454 if( !mImpl->IsShowingPlaceholderText() )
456 mImpl->mVisualModel->SetTextColor( textColor );
458 mImpl->RequestRelayout();
462 const Vector4& Controller::GetTextColor() const
464 return mImpl->mTextColor;
467 bool Controller::RemoveText( int cursorOffset, int numberOfChars )
469 bool removed = false;
471 if( NULL == mImpl->mEventData )
476 DALI_LOG_INFO( gLogFilter, Debug::General, "Controller::RemoveText %p mText.Count() %d cursor %d cursorOffset %d numberOfChars %d\n",
477 this, mImpl->mLogicalModel->mText.Count(), mImpl->mEventData->mPrimaryCursorPosition, cursorOffset, numberOfChars );
479 if( !mImpl->IsShowingPlaceholderText() )
481 // Delete at current cursor position
482 Vector<Character>& currentText = mImpl->mLogicalModel->mText;
483 CharacterIndex& oldCursorIndex = mImpl->mEventData->mPrimaryCursorPosition;
485 CharacterIndex cursorIndex = oldCursorIndex;
487 // Validate the cursor position & number of characters
488 if( static_cast< CharacterIndex >( std::abs( cursorOffset ) ) <= cursorIndex )
490 cursorIndex = oldCursorIndex + cursorOffset;
493 if( ( cursorIndex + numberOfChars ) > currentText.Count() )
495 numberOfChars = currentText.Count() - cursorIndex;
498 if( ( cursorIndex + numberOfChars ) <= currentText.Count() )
500 // Update the input style and remove the text's style before removing the text.
502 // Set first the default input style.
503 mImpl->RetrieveDefaultInputStyle( mImpl->mEventData->mInputStyle );
505 // Update the input style.
506 mImpl->mLogicalModel->RetrieveStyle( cursorIndex, mImpl->mEventData->mInputStyle );
508 // Remove the text's style before removing the text.
509 mImpl->mLogicalModel->UpdateTextStyleRuns( cursorIndex, -numberOfChars );
511 // Remove the characters.
512 Vector<Character>::Iterator first = currentText.Begin() + cursorIndex;
513 Vector<Character>::Iterator last = first + numberOfChars;
515 currentText.Erase( first, last );
517 // Cursor position retreat
518 oldCursorIndex = cursorIndex;
520 DALI_LOG_INFO( gLogFilter, Debug::General, "Controller::RemoveText %p removed %d\n", this, numberOfChars );
528 void Controller::SetPlaceholderTextColor( const Vector4& textColor )
530 if( NULL != mImpl->mEventData )
532 mImpl->mEventData->mPlaceholderTextColor = textColor;
535 if( mImpl->IsShowingPlaceholderText() )
537 mImpl->mVisualModel->SetTextColor( textColor );
538 mImpl->RequestRelayout();
542 const Vector4& Controller::GetPlaceholderTextColor() const
544 if( NULL != mImpl->mEventData )
546 return mImpl->mEventData->mPlaceholderTextColor;
552 void Controller::SetShadowOffset( const Vector2& shadowOffset )
554 mImpl->mVisualModel->SetShadowOffset( shadowOffset );
556 mImpl->RequestRelayout();
559 const Vector2& Controller::GetShadowOffset() const
561 return mImpl->mVisualModel->GetShadowOffset();
564 void Controller::SetShadowColor( const Vector4& shadowColor )
566 mImpl->mVisualModel->SetShadowColor( shadowColor );
568 mImpl->RequestRelayout();
571 const Vector4& Controller::GetShadowColor() const
573 return mImpl->mVisualModel->GetShadowColor();
576 void Controller::SetUnderlineColor( const Vector4& color )
578 mImpl->mVisualModel->SetUnderlineColor( color );
580 mImpl->RequestRelayout();
583 const Vector4& Controller::GetUnderlineColor() const
585 return mImpl->mVisualModel->GetUnderlineColor();
588 void Controller::SetUnderlineEnabled( bool enabled )
590 mImpl->mVisualModel->SetUnderlineEnabled( enabled );
592 mImpl->RequestRelayout();
595 bool Controller::IsUnderlineEnabled() const
597 return mImpl->mVisualModel->IsUnderlineEnabled();
600 void Controller::SetUnderlineHeight( float height )
602 mImpl->mVisualModel->SetUnderlineHeight( height );
604 mImpl->RequestRelayout();
607 float Controller::GetUnderlineHeight() const
609 return mImpl->mVisualModel->GetUnderlineHeight();
612 void Controller::SetInputColor( const Vector4& color )
614 if( NULL != mImpl->mEventData )
616 mImpl->mEventData->mInputStyle.textColor = color;
618 if( EventData::SELECTING == mImpl->mEventData->mState )
620 const bool handlesCrossed = mImpl->mEventData->mLeftSelectionPosition > mImpl->mEventData->mRightSelectionPosition;
622 // Get start and end position of selection
623 const CharacterIndex startOfSelectedText = handlesCrossed ? mImpl->mEventData->mRightSelectionPosition : mImpl->mEventData->mLeftSelectionPosition;
624 const Length lengthOfSelectedText = ( handlesCrossed ? mImpl->mEventData->mLeftSelectionPosition : mImpl->mEventData->mRightSelectionPosition ) - startOfSelectedText;
626 // Add the color run.
627 const VectorBase::SizeType numberOfRuns = mImpl->mLogicalModel->mColorRuns.Count();
628 mImpl->mLogicalModel->mColorRuns.Resize( numberOfRuns + 1u );
630 ColorRun& colorRun = *( mImpl->mLogicalModel->mColorRuns.Begin() + numberOfRuns );
631 colorRun.color = color;
632 colorRun.characterRun.characterIndex = startOfSelectedText;
633 colorRun.characterRun.numberOfCharacters = lengthOfSelectedText;
635 // Request to relayout.
636 mImpl->mOperationsPending = static_cast<OperationsMask>( mImpl->mOperationsPending | COLOR );
637 mImpl->RequestRelayout();
642 const Vector4& Controller::GetInputColor() const
644 if( NULL != mImpl->mEventData )
646 return mImpl->mEventData->mInputStyle.textColor;
649 // Return the default text's color if there is no EventData.
650 return mImpl->mTextColor;
654 void Controller::SetEnableCursorBlink( bool enable )
656 DALI_ASSERT_DEBUG( NULL != mImpl->mEventData && "TextInput disabled" );
658 if( NULL != mImpl->mEventData )
660 mImpl->mEventData->mCursorBlinkEnabled = enable;
663 mImpl->mEventData->mDecorator )
665 mImpl->mEventData->mDecorator->StopCursorBlink();
670 bool Controller::GetEnableCursorBlink() const
672 if( NULL != mImpl->mEventData )
674 return mImpl->mEventData->mCursorBlinkEnabled;
680 const Vector2& Controller::GetScrollPosition() const
682 if( NULL != mImpl->mEventData )
684 return mImpl->mEventData->mScrollPosition;
687 return Vector2::ZERO;
690 const Vector2& Controller::GetAlignmentOffset() const
692 return mImpl->mAlignmentOffset;
695 Vector3 Controller::GetNaturalSize()
697 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "-->Controller::GetNaturalSize\n" );
700 // Make sure the model is up-to-date before layouting
701 ProcessModifyEvents();
703 if( mImpl->mRecalculateNaturalSize )
705 // Operations that can be done only once until the text changes.
706 const OperationsMask onlyOnceOperations = static_cast<OperationsMask>( CONVERT_TO_UTF32 |
714 // Make sure the model is up-to-date before layouting
715 mImpl->UpdateModel( onlyOnceOperations );
717 // Operations that need to be done if the size changes.
718 const OperationsMask sizeOperations = static_cast<OperationsMask>( LAYOUT |
722 DoRelayout( Size( MAX_FLOAT, MAX_FLOAT ),
723 static_cast<OperationsMask>( onlyOnceOperations |
725 naturalSize.GetVectorXY() );
727 // Do not do again the only once operations.
728 mImpl->mOperationsPending = static_cast<OperationsMask>( mImpl->mOperationsPending & ~onlyOnceOperations );
730 // Do the size related operations again.
731 mImpl->mOperationsPending = static_cast<OperationsMask>( mImpl->mOperationsPending | sizeOperations );
733 // Stores the natural size to avoid recalculate it again
734 // unless the text/style changes.
735 mImpl->mVisualModel->SetNaturalSize( naturalSize.GetVectorXY() );
737 mImpl->mRecalculateNaturalSize = false;
739 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::GetNaturalSize calculated %f,%f,%f\n", naturalSize.x, naturalSize.y, naturalSize.z );
743 naturalSize = mImpl->mVisualModel->GetNaturalSize();
745 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::GetNaturalSize cached %f,%f,%f\n", naturalSize.x, naturalSize.y, naturalSize.z );
748 naturalSize.x = ConvertToEven( naturalSize.x );
749 naturalSize.y = ConvertToEven( naturalSize.y );
754 float Controller::GetHeightForWidth( float width )
756 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "-->Controller::GetHeightForWidth %p width %f\n", this, width );
757 // Make sure the model is up-to-date before layouting
758 ProcessModifyEvents();
761 if( fabsf( width - mImpl->mVisualModel->mControlSize.width ) > Math::MACHINE_EPSILON_1000 )
763 // Operations that can be done only once until the text changes.
764 const OperationsMask onlyOnceOperations = static_cast<OperationsMask>( CONVERT_TO_UTF32 |
772 // Make sure the model is up-to-date before layouting
773 mImpl->UpdateModel( onlyOnceOperations );
775 // Operations that need to be done if the size changes.
776 const OperationsMask sizeOperations = static_cast<OperationsMask>( LAYOUT |
780 DoRelayout( Size( width, MAX_FLOAT ),
781 static_cast<OperationsMask>( onlyOnceOperations |
785 // Do not do again the only once operations.
786 mImpl->mOperationsPending = static_cast<OperationsMask>( mImpl->mOperationsPending & ~onlyOnceOperations );
788 // Do the size related operations again.
789 mImpl->mOperationsPending = static_cast<OperationsMask>( mImpl->mOperationsPending | sizeOperations );
790 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::GetHeightForWidth calculated %f\n", layoutSize.height );
794 layoutSize = mImpl->mVisualModel->GetActualSize();
795 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::GetHeightForWidth cached %f\n", layoutSize.height );
798 return layoutSize.height;
801 bool Controller::Relayout( const Size& size )
803 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "-->Controller::Relayout %p size %f,%f\n", this, size.width, size.height );
805 if( ( size.width < Math::MACHINE_EPSILON_1000 ) || ( size.height < Math::MACHINE_EPSILON_1000 ) )
807 bool glyphsRemoved( false );
808 if( 0u != mImpl->mVisualModel->mGlyphPositions.Count() )
810 mImpl->mVisualModel->mGlyphPositions.Clear();
811 glyphsRemoved = true;
813 // Not worth to relayout if width or height is equal to zero.
814 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::Relayout (skipped)\n" );
815 return glyphsRemoved;
818 const bool newSize = ( size != mImpl->mVisualModel->mControlSize );
822 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "new size (previous size %f,%f)\n", mImpl->mVisualModel->mControlSize.width, mImpl->mVisualModel->mControlSize.height );
824 // Layout operations that need to be done if the size changes.
825 mImpl->mOperationsPending = static_cast<OperationsMask>( mImpl->mOperationsPending |
831 mImpl->mVisualModel->mControlSize = size;
834 // Whether there are modify events.
835 const bool isModifyEventsEmpty = 0u == mImpl->mModifyEvents.Count();
837 // Make sure the model is up-to-date before layouting.
838 ProcessModifyEvents();
839 mImpl->UpdateModel( mImpl->mOperationsPending );
841 // Style operations that need to be done if the text is modified.
842 if( !isModifyEventsEmpty )
844 mImpl->mOperationsPending = static_cast<OperationsMask>( mImpl->mOperationsPending |
848 // Apply the style runs if text is modified.
849 bool updated = mImpl->UpdateModelStyle( mImpl->mOperationsPending );
853 updated = DoRelayout( mImpl->mVisualModel->mControlSize,
854 mImpl->mOperationsPending,
855 layoutSize ) || updated;
857 // Do not re-do any operation until something changes.
858 mImpl->mOperationsPending = NO_OPERATION;
860 // Whether the text control is editable
861 const bool isEditable = NULL != mImpl->mEventData;
863 // Keep the current offset and alignment as it will be used to update the decorator's positions (if the size changes).
865 if( newSize && isEditable )
867 offset = mImpl->mAlignmentOffset + mImpl->mEventData->mScrollPosition;
870 // After doing the text layout, the alignment offset to place the actor in the desired position can be calculated.
871 CalculateTextAlignment( size );
877 // If there is a new size, the scroll position needs to be clamped.
878 mImpl->ClampHorizontalScroll( layoutSize );
880 // Update the decorator's positions is needed if there is a new size.
881 mImpl->mEventData->mDecorator->UpdatePositions( mImpl->mAlignmentOffset + mImpl->mEventData->mScrollPosition - offset );
884 // Move the cursor, grab handle etc.
885 updated = mImpl->ProcessInputEvents() || updated;
888 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::Relayout\n" );
892 void Controller::ProcessModifyEvents()
894 Vector<ModifyEvent>& events = mImpl->mModifyEvents;
896 if( 0u == events.Count() )
902 for( Vector<ModifyEvent>::ConstIterator it = events.Begin(),
903 endIt = events.End();
907 const ModifyEvent& event = *it;
909 if( ModifyEvent::TEXT_REPLACED == event.type )
911 // A (single) replace event should come first, otherwise we wasted time processing NOOP events
912 DALI_ASSERT_DEBUG( it == events.Begin() && "Unexpected TEXT_REPLACED event" );
916 else if( ModifyEvent::TEXT_INSERTED == event.type )
920 else if( ModifyEvent::TEXT_DELETED == event.type )
922 // Placeholder-text cannot be deleted
923 if( !mImpl->IsShowingPlaceholderText() )
930 if( NULL != mImpl->mEventData )
932 // When the text is being modified, delay cursor blinking
933 mImpl->mEventData->mDecorator->DelayCursorBlink();
936 // Discard temporary text
940 void Controller::ResetText()
943 mImpl->mLogicalModel->mText.Clear();
946 // We have cleared everything including the placeholder-text
947 mImpl->PlaceholderCleared();
949 // The natural size needs to be re-calculated.
950 mImpl->mRecalculateNaturalSize = true;
952 // Apply modifications to the model
953 mImpl->mOperationsPending = ALL_OPERATIONS;
956 void Controller::ResetCursorPosition( CharacterIndex cursorIndex )
958 // Reset the cursor position
959 if( NULL != mImpl->mEventData )
961 mImpl->mEventData->mPrimaryCursorPosition = cursorIndex;
963 // Update the cursor if it's in editing mode.
964 if( EventData::IsEditingState( mImpl->mEventData->mState ) )
966 mImpl->mEventData->mUpdateCursorPosition = true;
971 void Controller::ResetScrollPosition()
973 if( NULL != mImpl->mEventData )
975 // Reset the scroll position.
976 mImpl->mEventData->mScrollPosition = Vector2::ZERO;
977 mImpl->mEventData->mScrollAfterUpdatePosition = true;
981 void Controller::TextReplacedEvent()
986 // The natural size needs to be re-calculated.
987 mImpl->mRecalculateNaturalSize = true;
989 // Apply modifications to the model
990 mImpl->mOperationsPending = ALL_OPERATIONS;
991 mImpl->UpdateModel( ALL_OPERATIONS );
992 mImpl->mOperationsPending = static_cast<OperationsMask>( LAYOUT |
998 void Controller::TextInsertedEvent()
1000 DALI_ASSERT_DEBUG( NULL != mImpl->mEventData && "Unexpected TextInsertedEvent" );
1002 if( NULL == mImpl->mEventData )
1007 // TODO - Optimize this
1010 // The natural size needs to be re-calculated.
1011 mImpl->mRecalculateNaturalSize = true;
1013 // Apply modifications to the model; TODO - Optimize this
1014 mImpl->mOperationsPending = ALL_OPERATIONS;
1015 mImpl->UpdateModel( ALL_OPERATIONS );
1016 mImpl->mOperationsPending = static_cast<OperationsMask>( LAYOUT |
1018 UPDATE_ACTUAL_SIZE |
1021 // Queue a cursor reposition event; this must wait until after DoRelayout()
1022 if( EventData::IsEditingState( mImpl->mEventData->mState ) )
1024 mImpl->mEventData->mUpdateCursorPosition = true;
1025 mImpl->mEventData->mScrollAfterUpdatePosition = true;
1029 void Controller::TextDeletedEvent()
1031 DALI_ASSERT_DEBUG( NULL != mImpl->mEventData && "Unexpected TextDeletedEvent" );
1033 if( NULL == mImpl->mEventData )
1038 // TODO - Optimize this
1041 // The natural size needs to be re-calculated.
1042 mImpl->mRecalculateNaturalSize = true;
1044 // Apply modifications to the model; TODO - Optimize this
1045 mImpl->mOperationsPending = ALL_OPERATIONS;
1046 mImpl->UpdateModel( ALL_OPERATIONS );
1047 mImpl->mOperationsPending = static_cast<OperationsMask>( LAYOUT |
1049 UPDATE_ACTUAL_SIZE |
1052 // Queue a cursor reposition event; this must wait until after DoRelayout()
1053 mImpl->mEventData->mUpdateCursorPosition = true;
1054 if( 0u != mImpl->mLogicalModel->mText.Count() )
1056 mImpl->mEventData->mScrollAfterDelete = true;
1060 bool Controller::DoRelayout( const Size& size,
1061 OperationsMask operationsRequired,
1064 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "-->Controller::DoRelayout %p size %f,%f\n", this, size.width, size.height );
1065 bool viewUpdated( false );
1067 // Calculate the operations to be done.
1068 const OperationsMask operations = static_cast<OperationsMask>( mImpl->mOperationsPending & operationsRequired );
1070 if( LAYOUT & operations )
1072 // Some vectors with data needed to layout and reorder may be void
1073 // after the first time the text has been laid out.
1074 // Fill the vectors again.
1076 const Length numberOfGlyphs = mImpl->mVisualModel->mGlyphs.Count();
1078 if( 0u == numberOfGlyphs )
1080 // Nothing else to do if there is no glyphs.
1081 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::DoRelayout no glyphs, view updated true\n" );
1085 const Vector<LineBreakInfo>& lineBreakInfo = mImpl->mLogicalModel->mLineBreakInfo;
1086 const Vector<WordBreakInfo>& wordBreakInfo = mImpl->mLogicalModel->mWordBreakInfo;
1087 const Vector<CharacterDirection>& characterDirection = mImpl->mLogicalModel->mCharacterDirections;
1088 const Vector<GlyphInfo>& glyphs = mImpl->mVisualModel->mGlyphs;
1089 const Vector<CharacterIndex>& glyphsToCharactersMap = mImpl->mVisualModel->mGlyphsToCharacters;
1090 const Vector<Length>& charactersPerGlyph = mImpl->mVisualModel->mCharactersPerGlyph;
1091 const Character* const textBuffer = mImpl->mLogicalModel->mText.Begin();
1093 // Set the layout parameters.
1094 LayoutParameters layoutParameters( size,
1096 lineBreakInfo.Begin(),
1097 wordBreakInfo.Begin(),
1098 ( 0u != characterDirection.Count() ) ? characterDirection.Begin() : NULL,
1101 glyphsToCharactersMap.Begin(),
1102 charactersPerGlyph.Begin() );
1104 // The laid-out lines.
1105 // It's not possible to know in how many lines the text is going to be laid-out,
1106 // but it can be resized at least with the number of 'paragraphs' to avoid
1107 // some re-allocations.
1108 Vector<LineRun>& lines = mImpl->mVisualModel->mLines;
1110 // Delete any previous laid out lines before setting the new ones.
1113 // The capacity of the bidirectional paragraph info is the number of paragraphs.
1114 lines.Reserve( mImpl->mLogicalModel->mBidirectionalParagraphInfo.Capacity() );
1116 // Resize the vector of positions to have the same size than the vector of glyphs.
1117 Vector<Vector2>& glyphPositions = mImpl->mVisualModel->mGlyphPositions;
1118 glyphPositions.Resize( numberOfGlyphs );
1120 // Whether the last character is a new paragraph character.
1121 layoutParameters.isLastNewParagraph = TextAbstraction::IsNewParagraph( *( textBuffer + ( mImpl->mLogicalModel->mText.Count() - 1u ) ) );
1123 // Update the visual model.
1124 viewUpdated = mImpl->mLayoutEngine.LayoutText( layoutParameters,
1131 // Reorder the lines
1132 if( REORDER & operations )
1134 Vector<BidirectionalParagraphInfoRun>& bidirectionalInfo = mImpl->mLogicalModel->mBidirectionalParagraphInfo;
1136 // Check first if there are paragraphs with bidirectional info.
1137 if( 0u != bidirectionalInfo.Count() )
1140 const Length numberOfLines = mImpl->mVisualModel->mLines.Count();
1142 // Reorder the lines.
1143 Vector<BidirectionalLineInfoRun> lineBidirectionalInfoRuns;
1144 lineBidirectionalInfoRuns.Reserve( numberOfLines ); // Reserve because is not known yet how many lines have right to left characters.
1145 ReorderLines( bidirectionalInfo,
1147 lineBidirectionalInfoRuns );
1149 // Set the bidirectional info into the model.
1150 const Length numberOfBidirectionalInfoRuns = lineBidirectionalInfoRuns.Count();
1151 mImpl->mLogicalModel->SetVisualToLogicalMap( lineBidirectionalInfoRuns.Begin(),
1152 numberOfBidirectionalInfoRuns );
1154 // Set the bidirectional info per line into the layout parameters.
1155 layoutParameters.lineBidirectionalInfoRunsBuffer = lineBidirectionalInfoRuns.Begin();
1156 layoutParameters.numberOfBidirectionalInfoRuns = numberOfBidirectionalInfoRuns;
1158 // Get the character to glyph conversion table and set into the layout.
1159 layoutParameters.charactersToGlyphsBuffer = mImpl->mVisualModel->mCharactersToGlyph.Begin();
1161 // Get the glyphs per character table and set into the layout.
1162 layoutParameters.glyphsPerCharacterBuffer = mImpl->mVisualModel->mGlyphsPerCharacter.Begin();
1164 // Re-layout the text. Reorder those lines with right to left characters.
1165 mImpl->mLayoutEngine.ReLayoutRightToLeftLines( layoutParameters,
1168 // Free the allocated memory used to store the conversion table in the bidirectional line info run.
1169 for( Vector<BidirectionalLineInfoRun>::Iterator it = lineBidirectionalInfoRuns.Begin(),
1170 endIt = lineBidirectionalInfoRuns.End();
1174 BidirectionalLineInfoRun& bidiLineInfo = *it;
1176 free( bidiLineInfo.visualToLogicalMap );
1181 // Sets the actual size.
1182 if( UPDATE_ACTUAL_SIZE & operations )
1184 mImpl->mVisualModel->SetActualSize( layoutSize );
1190 layoutSize = mImpl->mVisualModel->GetActualSize();
1193 if( ALIGN & operations )
1195 // The laid-out lines.
1196 Vector<LineRun>& lines = mImpl->mVisualModel->mLines;
1198 mImpl->mLayoutEngine.Align( layoutSize,
1204 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::DoRelayout, view updated %s\n", ( viewUpdated ? "true" : "false" ) );
1208 void Controller::SetMultiLineEnabled( bool enable )
1210 const LayoutEngine::Layout layout = enable ? LayoutEngine::MULTI_LINE_BOX : LayoutEngine::SINGLE_LINE_BOX;
1212 if( layout != mImpl->mLayoutEngine.GetLayout() )
1214 // Set the layout type.
1215 mImpl->mLayoutEngine.SetLayout( layout );
1217 // Set the flags to redo the layout operations
1218 const OperationsMask layoutOperations = static_cast<OperationsMask>( LAYOUT |
1219 UPDATE_ACTUAL_SIZE |
1223 mImpl->mOperationsPending = static_cast<OperationsMask>( mImpl->mOperationsPending | layoutOperations );
1225 mImpl->RequestRelayout();
1229 bool Controller::IsMultiLineEnabled() const
1231 return LayoutEngine::MULTI_LINE_BOX == mImpl->mLayoutEngine.GetLayout();
1234 void Controller::SetHorizontalAlignment( LayoutEngine::HorizontalAlignment alignment )
1236 if( alignment != mImpl->mLayoutEngine.GetHorizontalAlignment() )
1238 // Set the alignment.
1239 mImpl->mLayoutEngine.SetHorizontalAlignment( alignment );
1241 // Set the flag to redo the alignment operation.
1242 mImpl->mOperationsPending = static_cast<OperationsMask>( mImpl->mOperationsPending | ALIGN );
1244 mImpl->RequestRelayout();
1248 LayoutEngine::HorizontalAlignment Controller::GetHorizontalAlignment() const
1250 return mImpl->mLayoutEngine.GetHorizontalAlignment();
1253 void Controller::SetVerticalAlignment( LayoutEngine::VerticalAlignment alignment )
1255 if( alignment != mImpl->mLayoutEngine.GetVerticalAlignment() )
1257 // Set the alignment.
1258 mImpl->mLayoutEngine.SetVerticalAlignment( alignment );
1260 mImpl->mOperationsPending = static_cast<OperationsMask>( mImpl->mOperationsPending | ALIGN );
1262 mImpl->RequestRelayout();
1266 LayoutEngine::VerticalAlignment Controller::GetVerticalAlignment() const
1268 return mImpl->mLayoutEngine.GetVerticalAlignment();
1271 void Controller::CalculateTextAlignment( const Size& size )
1273 // Get the direction of the first character.
1274 const CharacterDirection firstParagraphDirection = mImpl->mLogicalModel->GetCharacterDirection( 0u );
1276 Size actualSize = mImpl->mVisualModel->GetActualSize();
1277 if( fabsf( actualSize.height ) < Math::MACHINE_EPSILON_1000 )
1279 // Get the line height of the default font.
1280 actualSize.height = mImpl->GetDefaultFontLineHeight();
1283 // If the first paragraph is right to left swap ALIGN_BEGIN and ALIGN_END;
1284 LayoutEngine::HorizontalAlignment horizontalAlignment = mImpl->mLayoutEngine.GetHorizontalAlignment();
1285 if( firstParagraphDirection &&
1286 ( LayoutEngine::HORIZONTAL_ALIGN_CENTER != horizontalAlignment ) )
1288 if( LayoutEngine::HORIZONTAL_ALIGN_BEGIN == horizontalAlignment )
1290 horizontalAlignment = LayoutEngine::HORIZONTAL_ALIGN_END;
1294 horizontalAlignment = LayoutEngine::HORIZONTAL_ALIGN_BEGIN;
1298 switch( horizontalAlignment )
1300 case LayoutEngine::HORIZONTAL_ALIGN_BEGIN:
1302 mImpl->mAlignmentOffset.x = 0.f;
1305 case LayoutEngine::HORIZONTAL_ALIGN_CENTER:
1307 mImpl->mAlignmentOffset.x = floorf( 0.5f * ( size.width - actualSize.width ) ); // try to avoid pixel alignment.
1310 case LayoutEngine::HORIZONTAL_ALIGN_END:
1312 mImpl->mAlignmentOffset.x = size.width - actualSize.width;
1317 const LayoutEngine::VerticalAlignment verticalAlignment = mImpl->mLayoutEngine.GetVerticalAlignment();
1318 switch( verticalAlignment )
1320 case LayoutEngine::VERTICAL_ALIGN_TOP:
1322 mImpl->mAlignmentOffset.y = 0.f;
1325 case LayoutEngine::VERTICAL_ALIGN_CENTER:
1327 mImpl->mAlignmentOffset.y = floorf( 0.5f * ( size.height - actualSize.height ) ); // try to avoid pixel alignment.
1330 case LayoutEngine::VERTICAL_ALIGN_BOTTOM:
1332 mImpl->mAlignmentOffset.y = size.height - actualSize.height;
1338 LayoutEngine& Controller::GetLayoutEngine()
1340 return mImpl->mLayoutEngine;
1343 View& Controller::GetView()
1345 return mImpl->mView;
1348 void Controller::KeyboardFocusGainEvent()
1350 DALI_ASSERT_DEBUG( mImpl->mEventData && "Unexpected KeyboardFocusGainEvent" );
1352 if( NULL != mImpl->mEventData )
1354 if( ( EventData::INACTIVE == mImpl->mEventData->mState ) ||
1355 ( EventData::INTERRUPTED == mImpl->mEventData->mState ) )
1357 mImpl->ChangeState( EventData::EDITING );
1358 mImpl->mEventData->mUpdateCursorPosition = true; //If editing started without tap event, cursor update must be triggered.
1361 if( mImpl->IsShowingPlaceholderText() )
1363 // Show alternative placeholder-text when editing
1364 ShowPlaceholderText();
1367 mImpl->RequestRelayout();
1371 void Controller::KeyboardFocusLostEvent()
1373 DALI_ASSERT_DEBUG( mImpl->mEventData && "Unexpected KeyboardFocusLostEvent" );
1375 if( NULL != mImpl->mEventData )
1377 if( EventData::INTERRUPTED != mImpl->mEventData->mState )
1379 mImpl->ChangeState( EventData::INACTIVE );
1381 if( !mImpl->IsShowingRealText() )
1383 // Revert to regular placeholder-text when not editing
1384 ShowPlaceholderText();
1388 mImpl->RequestRelayout();
1391 bool Controller::KeyEvent( const Dali::KeyEvent& keyEvent )
1393 DALI_ASSERT_DEBUG( mImpl->mEventData && "Unexpected KeyEvent" );
1395 bool textChanged( false );
1397 if( ( NULL != mImpl->mEventData ) &&
1398 ( keyEvent.state == KeyEvent::Down ) )
1400 int keyCode = keyEvent.keyCode;
1401 const std::string& keyString = keyEvent.keyPressed;
1403 // Pre-process to separate modifying events from non-modifying input events.
1404 if( Dali::DALI_KEY_ESCAPE == keyCode )
1406 // Escape key is a special case which causes focus loss
1407 KeyboardFocusLostEvent();
1409 else if( ( Dali::DALI_KEY_CURSOR_LEFT == keyCode ) ||
1410 ( Dali::DALI_KEY_CURSOR_RIGHT == keyCode ) ||
1411 ( Dali::DALI_KEY_CURSOR_UP == keyCode ) ||
1412 ( Dali::DALI_KEY_CURSOR_DOWN == keyCode ) )
1414 Event event( Event::CURSOR_KEY_EVENT );
1415 event.p1.mInt = keyCode;
1416 mImpl->mEventData->mEventQueue.push_back( event );
1418 else if( Dali::DALI_KEY_BACKSPACE == keyCode )
1420 textChanged = BackspaceKeyEvent();
1422 else if( IsKey( keyEvent, Dali::DALI_KEY_POWER ) )
1424 mImpl->ChangeState( EventData::INTERRUPTED ); // State is not INACTIVE as expect to return to edit mode.
1425 // Avoids calling the InsertText() method which can delete selected text
1427 else if( IsKey( keyEvent, Dali::DALI_KEY_MENU ) ||
1428 IsKey( keyEvent, Dali::DALI_KEY_HOME ) )
1430 mImpl->ChangeState( EventData::INACTIVE );
1431 // Menu/Home key behaviour does not allow edit mode to resume like Power key
1432 // Avoids calling the InsertText() method which can delete selected text
1434 else if( Dali::DALI_KEY_SHIFT_LEFT == keyCode )
1436 // DALI_KEY_SHIFT_LEFT is the key code for the Left Shift. It's sent (by the imf?) when the predictive text is enabled
1437 // and a character is typed after the type of a upper case latin character.
1443 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Controller::KeyEvent %p keyString %s\n", this, keyString.c_str() );
1445 // IMF manager is no longer handling key-events
1446 mImpl->ClearPreEditFlag();
1448 InsertText( keyString, COMMIT );
1452 if( ( mImpl->mEventData->mState != EventData::INTERRUPTED ) &&
1453 ( mImpl->mEventData->mState != EventData::INACTIVE ) )
1455 mImpl->ChangeState( EventData::EDITING );
1458 mImpl->RequestRelayout();
1463 // Do this last since it provides callbacks into application code
1464 mImpl->mControlInterface.TextChanged();
1470 void Controller::InsertText( const std::string& text, Controller::InsertType type )
1472 bool removedPrevious( false );
1473 bool maxLengthReached( false );
1475 DALI_ASSERT_DEBUG( NULL != mImpl->mEventData && "Unexpected InsertText" )
1477 if( NULL == mImpl->mEventData )
1482 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Controller::InsertText %p %s (%s) mPrimaryCursorPosition %d mPreEditFlag %d mPreEditStartPosition %d mPreEditLength %d\n",
1483 this, text.c_str(), (COMMIT == type ? "COMMIT" : "PRE_EDIT"),
1484 mImpl->mEventData->mPrimaryCursorPosition, mImpl->mEventData->mPreEditFlag, mImpl->mEventData->mPreEditStartPosition, mImpl->mEventData->mPreEditLength );
1486 // TODO: At the moment the underline runs are only for pre-edit.
1487 mImpl->mVisualModel->mUnderlineRuns.Clear();
1489 Vector<Character> utf32Characters;
1490 Length characterCount( 0u );
1492 // Remove the previous IMF pre-edit (predicitive text)
1493 if( mImpl->mEventData->mPreEditFlag &&
1494 ( 0 != mImpl->mEventData->mPreEditLength ) )
1496 CharacterIndex offset = mImpl->mEventData->mPrimaryCursorPosition - mImpl->mEventData->mPreEditStartPosition;
1498 removedPrevious = RemoveText( -static_cast<int>(offset), mImpl->mEventData->mPreEditLength );
1500 mImpl->mEventData->mPrimaryCursorPosition = mImpl->mEventData->mPreEditStartPosition;
1501 mImpl->mEventData->mPreEditLength = 0;
1505 // Remove the previous Selection
1506 removedPrevious = RemoveSelectedText();
1511 // Convert text into UTF-32
1512 utf32Characters.Resize( text.size() );
1514 // This is a bit horrible but std::string returns a (signed) char*
1515 const uint8_t* utf8 = reinterpret_cast<const uint8_t*>( text.c_str() );
1517 // Transform a text array encoded in utf8 into an array encoded in utf32.
1518 // It returns the actual number of characters.
1519 characterCount = Utf8ToUtf32( utf8, text.size(), utf32Characters.Begin() );
1520 utf32Characters.Resize( characterCount );
1522 DALI_ASSERT_DEBUG( text.size() >= utf32Characters.Count() && "Invalid UTF32 conversion length" );
1523 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "UTF8 size %d, UTF32 size %d\n", text.size(), utf32Characters.Count() );
1526 if( 0u != utf32Characters.Count() ) // Check if Utf8ToUtf32 conversion succeeded
1528 // The placeholder text is no longer needed
1529 if( mImpl->IsShowingPlaceholderText() )
1534 mImpl->ChangeState( EventData::EDITING );
1536 // Handle the IMF (predicitive text) state changes
1537 if( COMMIT == type )
1539 // IMF manager is no longer handling key-events
1540 mImpl->ClearPreEditFlag();
1544 if( !mImpl->mEventData->mPreEditFlag )
1546 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Entered PreEdit state" );
1548 // Record the start of the pre-edit text
1549 mImpl->mEventData->mPreEditStartPosition = mImpl->mEventData->mPrimaryCursorPosition;
1552 mImpl->mEventData->mPreEditLength = utf32Characters.Count();
1553 mImpl->mEventData->mPreEditFlag = true;
1555 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "mPreEditStartPosition %d mPreEditLength %d\n", mImpl->mEventData->mPreEditStartPosition, mImpl->mEventData->mPreEditLength );
1558 const Length numberOfCharactersInModel = mImpl->mLogicalModel->mText.Count();
1560 // Restrict new text to fit within Maximum characters setting
1561 Length maxSizeOfNewText = std::min ( ( mImpl->mMaximumNumberOfCharacters - numberOfCharactersInModel ), characterCount );
1562 maxLengthReached = ( characterCount > maxSizeOfNewText );
1564 // The cursor position.
1565 CharacterIndex& cursorIndex = mImpl->mEventData->mPrimaryCursorPosition;
1567 // Updates the text style runs.
1568 mImpl->mLogicalModel->UpdateTextStyleRuns( cursorIndex, maxSizeOfNewText );
1570 // Get the character index from the cursor index.
1571 const CharacterIndex styleIndex = ( cursorIndex > 0u ) ? cursorIndex - 1u : 0u;
1573 // Retrieve the text's style for the given index.
1575 mImpl->mLogicalModel->RetrieveStyle( styleIndex, style );
1577 // Whether to add a new text color run.
1578 const bool addColorRun = style.textColor != mImpl->mEventData->mInputStyle.textColor;
1583 const VectorBase::SizeType numberOfRuns = mImpl->mLogicalModel->mColorRuns.Count();
1584 mImpl->mLogicalModel->mColorRuns.Resize( numberOfRuns + 1u );
1586 ColorRun& colorRun = *( mImpl->mLogicalModel->mColorRuns.Begin() + numberOfRuns );
1587 colorRun.color = mImpl->mEventData->mInputStyle.textColor;
1588 colorRun.characterRun.characterIndex = cursorIndex;
1589 colorRun.characterRun.numberOfCharacters = maxSizeOfNewText;
1592 // Insert at current cursor position.
1593 Vector<Character>& modifyText = mImpl->mLogicalModel->mText;
1595 if( cursorIndex < numberOfCharactersInModel )
1597 modifyText.Insert( modifyText.Begin() + cursorIndex, utf32Characters.Begin(), utf32Characters.Begin() + maxSizeOfNewText );
1601 modifyText.Insert( modifyText.End(), utf32Characters.Begin(), utf32Characters.Begin() + maxSizeOfNewText );
1604 cursorIndex += maxSizeOfNewText;
1606 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Inserted %d characters, new size %d new cursor %d\n", maxSizeOfNewText, mImpl->mLogicalModel->mText.Count(), mImpl->mEventData->mPrimaryCursorPosition );
1609 if( ( 0u == mImpl->mLogicalModel->mText.Count() ) &&
1610 mImpl->IsPlaceholderAvailable() )
1612 // Show place-holder if empty after removing the pre-edit text
1613 ShowPlaceholderText();
1614 mImpl->mEventData->mUpdateCursorPosition = true;
1615 mImpl->ClearPreEditFlag();
1617 else if( removedPrevious ||
1618 ( 0 != utf32Characters.Count() ) )
1620 // Queue an inserted event
1621 mImpl->QueueModifyEvent( ModifyEvent::TEXT_INSERTED );
1624 if( maxLengthReached )
1626 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "MaxLengthReached (%d)\n", mImpl->mLogicalModel->mText.Count() );
1628 mImpl->ResetImfManager();
1630 // Do this last since it provides callbacks into application code
1631 mImpl->mControlInterface.MaxLengthReached();
1635 bool Controller::RemoveSelectedText()
1637 bool textRemoved( false );
1639 if( EventData::SELECTING == mImpl->mEventData->mState )
1641 std::string removedString;
1642 mImpl->RetrieveSelection( removedString, true );
1644 if( !removedString.empty() )
1647 mImpl->ChangeState( EventData::EDITING );
1654 void Controller::TapEvent( unsigned int tapCount, float x, float y )
1656 DALI_ASSERT_DEBUG( mImpl->mEventData && "Unexpected TapEvent" );
1658 if( NULL != mImpl->mEventData )
1660 DALI_LOG_INFO( gLogFilter, Debug::Concise, "TapEvent state:%d \n", mImpl->mEventData->mState );
1662 if( 1u == tapCount )
1664 // This is to avoid unnecessary relayouts when tapping an empty text-field
1665 bool relayoutNeeded( false );
1667 if( ( EventData::EDITING_WITH_PASTE_POPUP == mImpl->mEventData->mState ) ||
1668 ( EventData::EDITING_WITH_PASTE_POPUP == mImpl->mEventData->mState ) )
1670 mImpl->ChangeState( EventData::EDITING_WITH_GRAB_HANDLE); // If Popup shown hide it here so can be shown again if required.
1673 if( mImpl->IsShowingRealText() && ( EventData::INACTIVE != mImpl->mEventData->mState ) )
1675 // Already in an active state so show a popup
1676 if( !mImpl->IsClipboardEmpty() )
1678 // Shows Paste popup but could show full popup with Selection options. ( EDITING_WITH_POPUP )
1679 mImpl->ChangeState( EventData::EDITING_WITH_PASTE_POPUP );
1683 mImpl->ChangeState( EventData::EDITING_WITH_GRAB_HANDLE );
1685 relayoutNeeded = true;
1689 if( mImpl->IsShowingPlaceholderText() && !mImpl->IsFocusedPlaceholderAvailable() )
1691 // Hide placeholder text
1695 if( EventData::INACTIVE == mImpl->mEventData->mState )
1697 mImpl->ChangeState( EventData::EDITING );
1699 else if( !mImpl->IsClipboardEmpty() )
1701 mImpl->ChangeState( EventData::EDITING_WITH_POPUP );
1703 relayoutNeeded = true;
1706 // Handles & cursors must be repositioned after Relayout() i.e. after the Model has been updated
1707 if( relayoutNeeded )
1709 Event event( Event::TAP_EVENT );
1710 event.p1.mUint = tapCount;
1711 event.p2.mFloat = x;
1712 event.p3.mFloat = y;
1713 mImpl->mEventData->mEventQueue.push_back( event );
1715 mImpl->RequestRelayout();
1718 else if( 2u == tapCount )
1720 if( mImpl->mEventData->mSelectionEnabled &&
1721 mImpl->IsShowingRealText() )
1723 SelectEvent( x, y, false );
1728 // Reset keyboard as tap event has occurred.
1729 mImpl->ResetImfManager();
1732 void Controller::PanEvent( Gesture::State state, const Vector2& displacement )
1733 // Show cursor and grabhandle on first tap, this matches the behaviour of tapping when already editing
1735 DALI_ASSERT_DEBUG( mImpl->mEventData && "Unexpected PanEvent" );
1737 if( NULL != mImpl->mEventData )
1739 Event event( Event::PAN_EVENT );
1740 event.p1.mInt = state;
1741 event.p2.mFloat = displacement.x;
1742 event.p3.mFloat = displacement.y;
1743 mImpl->mEventData->mEventQueue.push_back( event );
1745 mImpl->RequestRelayout();
1749 void Controller::LongPressEvent( Gesture::State state, float x, float y )
1751 DALI_ASSERT_DEBUG( mImpl->mEventData && "Unexpected LongPressEvent" );
1753 if( ( state == Gesture::Started ) &&
1754 ( NULL != mImpl->mEventData ) )
1756 if( !mImpl->IsShowingRealText() )
1758 Event event( Event::LONG_PRESS_EVENT );
1759 event.p1.mInt = state;
1760 mImpl->mEventData->mEventQueue.push_back( event );
1761 mImpl->RequestRelayout();
1765 // The 1st long-press on inactive text-field is treated as tap
1766 if( EventData::INACTIVE == mImpl->mEventData->mState )
1768 mImpl->ChangeState( EventData::EDITING );
1770 Event event( Event::TAP_EVENT );
1772 event.p2.mFloat = x;
1773 event.p3.mFloat = y;
1774 mImpl->mEventData->mEventQueue.push_back( event );
1776 mImpl->RequestRelayout();
1780 // Reset the imf manger to commit the pre-edit before selecting the text.
1781 mImpl->ResetImfManager();
1783 SelectEvent( x, y, false );
1789 void Controller::SelectEvent( float x, float y, bool selectAll )
1791 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Controller::SelectEvent\n" );
1793 if( NULL != mImpl->mEventData )
1795 mImpl->ChangeState( EventData::SELECTING );
1799 Event event( Event::SELECT_ALL );
1800 mImpl->mEventData->mEventQueue.push_back( event );
1804 Event event( Event::SELECT );
1805 event.p2.mFloat = x;
1806 event.p3.mFloat = y;
1807 mImpl->mEventData->mEventQueue.push_back( event );
1810 mImpl->RequestRelayout();
1814 void Controller::GetTargetSize( Vector2& targetSize )
1816 targetSize = mImpl->mVisualModel->mControlSize;
1819 void Controller::AddDecoration( Actor& actor, bool needsClipping )
1821 mImpl->mControlInterface.AddDecoration( actor, needsClipping );
1824 void Controller::DecorationEvent( HandleType handleType, HandleState state, float x, float y )
1826 DALI_ASSERT_DEBUG( mImpl->mEventData && "Unexpected DecorationEvent" );
1828 if( NULL != mImpl->mEventData )
1830 switch( handleType )
1834 Event event( Event::GRAB_HANDLE_EVENT );
1835 event.p1.mUint = state;
1836 event.p2.mFloat = x;
1837 event.p3.mFloat = y;
1839 mImpl->mEventData->mEventQueue.push_back( event );
1842 case LEFT_SELECTION_HANDLE:
1844 Event event( Event::LEFT_SELECTION_HANDLE_EVENT );
1845 event.p1.mUint = state;
1846 event.p2.mFloat = x;
1847 event.p3.mFloat = y;
1849 mImpl->mEventData->mEventQueue.push_back( event );
1852 case RIGHT_SELECTION_HANDLE:
1854 Event event( Event::RIGHT_SELECTION_HANDLE_EVENT );
1855 event.p1.mUint = state;
1856 event.p2.mFloat = x;
1857 event.p3.mFloat = y;
1859 mImpl->mEventData->mEventQueue.push_back( event );
1862 case LEFT_SELECTION_HANDLE_MARKER:
1863 case RIGHT_SELECTION_HANDLE_MARKER:
1865 // Markers do not move the handles.
1868 case HANDLE_TYPE_COUNT:
1870 DALI_ASSERT_DEBUG( !"Controller::HandleEvent. Unexpected handle type" );
1874 mImpl->RequestRelayout();
1878 void Controller::PasteText( const std::string& stringToPaste )
1880 InsertText( stringToPaste, Text::Controller::COMMIT );
1881 mImpl->ChangeState( EventData::EDITING );
1882 mImpl->RequestRelayout();
1884 // Do this last since it provides callbacks into application code
1885 mImpl->mControlInterface.TextChanged();
1888 void Controller::PasteClipboardItemEvent()
1890 // Retrieve the clipboard contents first
1891 ClipboardEventNotifier notifier( ClipboardEventNotifier::Get() );
1892 std::string stringToPaste( notifier.GetContent() );
1894 // Commit the current pre-edit text; the contents of the clipboard should be appended
1895 mImpl->ResetImfManager();
1898 PasteText( stringToPaste );
1901 void Controller::TextPopupButtonTouched( Dali::Toolkit::TextSelectionPopup::Buttons button )
1903 if( NULL == mImpl->mEventData )
1910 case Toolkit::TextSelectionPopup::CUT:
1912 mImpl->SendSelectionToClipboard( true ); // Synchronous call to modify text
1913 mImpl->mOperationsPending = ALL_OPERATIONS;
1915 // This is to reset the virtual keyboard to Upper-case
1916 if( 0u == mImpl->mLogicalModel->mText.Count() )
1921 if( ( 0u != mImpl->mLogicalModel->mText.Count() ) ||
1922 !mImpl->IsPlaceholderAvailable() )
1924 mImpl->QueueModifyEvent( ModifyEvent::TEXT_DELETED );
1928 ShowPlaceholderText();
1929 mImpl->mEventData->mUpdateCursorPosition = true;
1931 mImpl->RequestRelayout();
1932 mImpl->mControlInterface.TextChanged();
1935 case Toolkit::TextSelectionPopup::COPY:
1937 mImpl->SendSelectionToClipboard( false ); // Text not modified
1938 mImpl->RequestRelayout(); // Handles, Selection Highlight, Popup
1941 case Toolkit::TextSelectionPopup::PASTE:
1943 std::string stringToPaste("");
1944 mImpl->GetTextFromClipboard( 0, stringToPaste ); // Paste latest item from system clipboard
1945 PasteText( stringToPaste );
1948 case Toolkit::TextSelectionPopup::SELECT:
1950 const Vector2& currentCursorPosition = mImpl->mEventData->mDecorator->GetPosition( PRIMARY_CURSOR );
1952 if( mImpl->mEventData->mSelectionEnabled )
1954 // Creates a SELECT event.
1955 SelectEvent( currentCursorPosition.x, currentCursorPosition.y, false );
1959 case Toolkit::TextSelectionPopup::SELECT_ALL:
1961 // Creates a SELECT_ALL event
1962 SelectEvent( 0.f, 0.f, true );
1965 case Toolkit::TextSelectionPopup::CLIPBOARD:
1967 mImpl->ShowClipboard();
1970 case Toolkit::TextSelectionPopup::NONE:
1978 ImfManager::ImfCallbackData Controller::OnImfEvent( ImfManager& imfManager, const ImfManager::ImfEventData& imfEvent )
1980 bool update = false;
1981 bool requestRelayout = false;
1984 unsigned int cursorPosition = 0u;
1986 switch( imfEvent.eventName )
1988 case ImfManager::COMMIT:
1990 InsertText( imfEvent.predictiveString, Text::Controller::COMMIT );
1992 requestRelayout = true;
1995 case ImfManager::PREEDIT:
1997 InsertText( imfEvent.predictiveString, Text::Controller::PRE_EDIT );
1999 requestRelayout = true;
2002 case ImfManager::DELETESURROUNDING:
2004 update = RemoveText( imfEvent.cursorOffset, imfEvent.numberOfChars );
2008 if( ( 0u != mImpl->mLogicalModel->mText.Count() ) ||
2009 !mImpl->IsPlaceholderAvailable() )
2011 mImpl->QueueModifyEvent( ModifyEvent::TEXT_DELETED );
2015 ShowPlaceholderText();
2016 mImpl->mEventData->mUpdateCursorPosition = true;
2019 requestRelayout = true;
2022 case ImfManager::GETSURROUNDING:
2025 cursorPosition = GetLogicalCursorPosition();
2027 imfManager.SetSurroundingText( text );
2028 imfManager.SetCursorPosition( cursorPosition );
2031 case ImfManager::VOID:
2038 if( ImfManager::GETSURROUNDING != imfEvent.eventName )
2041 cursorPosition = GetLogicalCursorPosition();
2044 if( requestRelayout )
2046 mImpl->mOperationsPending = ALL_OPERATIONS;
2047 mImpl->RequestRelayout();
2049 // Do this last since it provides callbacks into application code
2050 mImpl->mControlInterface.TextChanged();
2053 ImfManager::ImfCallbackData callbackData( update, cursorPosition, text, false );
2055 return callbackData;
2058 Controller::~Controller()
2063 bool Controller::BackspaceKeyEvent()
2065 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Controller::KeyEvent %p DALI_KEY_BACKSPACE\n", this );
2067 bool removed = false;
2069 if( NULL == mImpl->mEventData )
2074 // IMF manager is no longer handling key-events
2075 mImpl->ClearPreEditFlag();
2077 if( EventData::SELECTING == mImpl->mEventData->mState )
2079 removed = RemoveSelectedText();
2081 else if( mImpl->mEventData->mPrimaryCursorPosition > 0 )
2083 // Remove the character before the current cursor position
2084 removed = RemoveText( -1, 1 );
2089 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Controller::KeyEvent %p DALI_KEY_BACKSPACE RemovedText\n", this );
2090 // Notifiy the IMF manager after text changed
2091 // Automatic Upper-case and restarting prediction on an existing word require this.
2094 if( ( 0u != mImpl->mLogicalModel->mText.Count() ) ||
2095 !mImpl->IsPlaceholderAvailable() )
2097 mImpl->QueueModifyEvent( ModifyEvent::TEXT_DELETED );
2101 ShowPlaceholderText();
2102 mImpl->mEventData->mUpdateCursorPosition = true;
2109 void Controller::NotifyImfManager()
2111 if( NULL != mImpl->mEventData )
2113 if( mImpl->mEventData->mImfManager )
2115 // Notifying IMF of a cursor change triggers a surrounding text request so updating it now.
2118 mImpl->mEventData->mImfManager.SetSurroundingText( text );
2120 mImpl->mEventData->mImfManager.SetCursorPosition( GetLogicalCursorPosition() );
2121 mImpl->mEventData->mImfManager.NotifyCursorPosition();
2126 void Controller::ShowPlaceholderText()
2128 if( mImpl->IsPlaceholderAvailable() )
2130 DALI_ASSERT_DEBUG( mImpl->mEventData && "No placeholder text available" );
2132 if( NULL == mImpl->mEventData )
2137 mImpl->mEventData->mIsShowingPlaceholderText = true;
2139 // Disable handles when showing place-holder text
2140 mImpl->mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
2141 mImpl->mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
2142 mImpl->mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
2144 const char* text( NULL );
2147 // TODO - Switch placeholder text styles when changing state
2148 if( ( EventData::INACTIVE != mImpl->mEventData->mState ) &&
2149 ( 0u != mImpl->mEventData->mPlaceholderTextActive.c_str() ) )
2151 text = mImpl->mEventData->mPlaceholderTextActive.c_str();
2152 size = mImpl->mEventData->mPlaceholderTextActive.size();
2156 text = mImpl->mEventData->mPlaceholderTextInactive.c_str();
2157 size = mImpl->mEventData->mPlaceholderTextInactive.size();
2160 // Reset model for showing placeholder.
2161 mImpl->mLogicalModel->mText.Clear();
2163 mImpl->mVisualModel->SetTextColor( mImpl->mEventData->mPlaceholderTextColor );
2165 // Convert text into UTF-32
2166 Vector<Character>& utf32Characters = mImpl->mLogicalModel->mText;
2167 utf32Characters.Resize( size );
2169 // This is a bit horrible but std::string returns a (signed) char*
2170 const uint8_t* utf8 = reinterpret_cast<const uint8_t*>( text );
2172 // Transform a text array encoded in utf8 into an array encoded in utf32.
2173 // It returns the actual number of characters.
2174 Length characterCount = Utf8ToUtf32( utf8, size, utf32Characters.Begin() );
2175 utf32Characters.Resize( characterCount );
2177 // Reset the cursor position
2178 mImpl->mEventData->mPrimaryCursorPosition = 0;
2180 // The natural size needs to be re-calculated.
2181 mImpl->mRecalculateNaturalSize = true;
2183 // Apply modifications to the model
2184 mImpl->mOperationsPending = ALL_OPERATIONS;
2186 // Update the rest of the model during size negotiation
2187 mImpl->QueueModifyEvent( ModifyEvent::TEXT_REPLACED );
2191 void Controller::ClearModelData()
2193 // n.b. This does not Clear the mText from mLogicalModel
2194 mImpl->mLogicalModel->mScriptRuns.Clear();
2195 mImpl->mLogicalModel->mFontRuns.Clear();
2196 mImpl->mLogicalModel->mLineBreakInfo.Clear();
2197 mImpl->mLogicalModel->mWordBreakInfo.Clear();
2198 mImpl->mLogicalModel->mBidirectionalParagraphInfo.Clear();
2199 mImpl->mLogicalModel->mCharacterDirections.Clear();
2200 mImpl->mLogicalModel->mBidirectionalLineInfo.Clear();
2201 mImpl->mLogicalModel->mLogicalToVisualMap.Clear();
2202 mImpl->mLogicalModel->mVisualToLogicalMap.Clear();
2203 mImpl->mVisualModel->mGlyphs.Clear();
2204 mImpl->mVisualModel->mGlyphsToCharacters.Clear();
2205 mImpl->mVisualModel->mCharactersToGlyph.Clear();
2206 mImpl->mVisualModel->mCharactersPerGlyph.Clear();
2207 mImpl->mVisualModel->mGlyphsPerCharacter.Clear();
2208 mImpl->mVisualModel->mGlyphPositions.Clear();
2209 mImpl->mVisualModel->mLines.Clear();
2210 mImpl->mVisualModel->mColorRuns.Clear();
2211 mImpl->mVisualModel->ClearCaches();
2214 void Controller::ClearFontData()
2216 mImpl->mFontDefaults->mFontId = 0u; // Remove old font ID
2217 mImpl->mLogicalModel->mFontRuns.Clear();
2218 mImpl->mVisualModel->mGlyphs.Clear();
2219 mImpl->mVisualModel->mGlyphsToCharacters.Clear();
2220 mImpl->mVisualModel->mCharactersToGlyph.Clear();
2221 mImpl->mVisualModel->mCharactersPerGlyph.Clear();
2222 mImpl->mVisualModel->mGlyphsPerCharacter.Clear();
2223 mImpl->mVisualModel->mGlyphPositions.Clear();
2224 mImpl->mVisualModel->mLines.Clear();
2225 mImpl->mVisualModel->ClearCaches();
2228 void Controller::ClearStyleData()
2230 mImpl->mLogicalModel->mColorRuns.Clear();
2233 Controller::Controller( ControlInterface& controlInterface )
2236 mImpl = new Controller::Impl( controlInterface );
2241 } // namespace Toolkit