+ mImpl->mTextUpdateInfo.mCharacterIndex = startOfSelectedText;
+ mImpl->mTextUpdateInfo.mNumberOfCharactersToRemove = lengthOfSelectedText;
+ mImpl->mTextUpdateInfo.mNumberOfCharactersToAdd = lengthOfSelectedText;
+
+ // As the font might change, recalculate the handle positions is needed.
+ mImpl->mEventData->mUpdateLeftSelectionPosition = true;
+ mImpl->mEventData->mUpdateRightSelectionPosition = true;
+ mImpl->mEventData->mUpdateHighlightBox = true;
+ mImpl->mEventData->mScrollAfterUpdatePosition = true;
+ }
+ }
+}
+
+float Controller::GetInputFontPointSize() const
+{
+ if( NULL != mImpl->mEventData )
+ {
+ return mImpl->mEventData->mInputStyle.size;
+ }
+
+ // Return the default font's point size if there is no EventData.
+ return GetDefaultFontSize( Text::Controller::POINT_SIZE );
+}
+
+void Controller::SetInputLineSpacing( float lineSpacing )
+{
+ if( NULL != mImpl->mEventData )
+ {
+ mImpl->mEventData->mInputStyle.lineSpacing = lineSpacing;
+ mImpl->mEventData->mInputStyle.isLineSpacingDefined = true;
+ }
+}
+
+float Controller::GetInputLineSpacing() const
+{
+ if( NULL != mImpl->mEventData )
+ {
+ return mImpl->mEventData->mInputStyle.lineSpacing;
+ }
+
+ return 0.f;
+}
+
+void Controller::SetInputShadowProperties( const std::string& shadowProperties )
+{
+ if( NULL != mImpl->mEventData )
+ {
+ mImpl->mEventData->mInputStyle.shadowProperties = shadowProperties;
+ }
+}
+
+const std::string& Controller::GetInputShadowProperties() const
+{
+ if( NULL != mImpl->mEventData )
+ {
+ return mImpl->mEventData->mInputStyle.shadowProperties;
+ }
+
+ return EMPTY_STRING;
+}
+
+void Controller::SetInputUnderlineProperties( const std::string& underlineProperties )
+{
+ if( NULL != mImpl->mEventData )
+ {
+ mImpl->mEventData->mInputStyle.underlineProperties = underlineProperties;
+ }
+}
+
+const std::string& Controller::GetInputUnderlineProperties() const
+{
+ if( NULL != mImpl->mEventData )
+ {
+ return mImpl->mEventData->mInputStyle.underlineProperties;
+ }
+
+ return EMPTY_STRING;
+}
+
+void Controller::SetInputEmbossProperties( const std::string& embossProperties )
+{
+ if( NULL != mImpl->mEventData )
+ {
+ mImpl->mEventData->mInputStyle.embossProperties = embossProperties;
+ }
+}
+
+const std::string& Controller::GetInputEmbossProperties() const
+{
+ if( NULL != mImpl->mEventData )
+ {
+ return mImpl->mEventData->mInputStyle.embossProperties;
+ }
+
+ return GetDefaultEmbossProperties();
+}
+
+void Controller::SetInputOutlineProperties( const std::string& outlineProperties )
+{
+ if( NULL != mImpl->mEventData )
+ {
+ mImpl->mEventData->mInputStyle.outlineProperties = outlineProperties;
+ }
+}
+
+const std::string& Controller::GetInputOutlineProperties() const
+{
+ if( NULL != mImpl->mEventData )
+ {
+ return mImpl->mEventData->mInputStyle.outlineProperties;
+ }
+
+ return GetDefaultOutlineProperties();
+}
+
+void Controller::SetInputModePassword( bool passwordInput )
+{
+ if( NULL != mImpl->mEventData )
+ {
+ mImpl->mEventData->mPasswordInput = passwordInput;
+ }
+}
+
+bool Controller::IsInputModePassword()
+{
+ if( NULL != mImpl->mEventData )
+ {
+ return mImpl->mEventData->mPasswordInput;
+ }
+ return false;
+}
+
+void Controller::SetNoTextDoubleTapAction( NoTextTap::Action action )
+{
+ if( NULL != mImpl->mEventData )
+ {
+ mImpl->mEventData->mDoubleTapAction = action;
+ }
+}
+
+Controller::NoTextTap::Action Controller::GetNoTextDoubleTapAction() const
+{
+ NoTextTap::Action action = NoTextTap::NO_ACTION;
+
+ if( NULL != mImpl->mEventData )
+ {
+ action = mImpl->mEventData->mDoubleTapAction;
+ }
+
+ return action;
+}
+
+void Controller::SetNoTextLongPressAction( NoTextTap::Action action )
+{
+ if( NULL != mImpl->mEventData )
+ {
+ mImpl->mEventData->mLongPressAction = action;
+ }
+}
+
+Controller::NoTextTap::Action Controller::GetNoTextLongPressAction() const
+{
+ NoTextTap::Action action = NoTextTap::NO_ACTION;
+
+ if( NULL != mImpl->mEventData )
+ {
+ action = mImpl->mEventData->mLongPressAction;
+ }
+
+ return action;
+}
+
+bool Controller::IsUnderlineSetByString()
+{
+ return mImpl->mUnderlineSetByString;
+}
+
+void Controller::UnderlineSetByString( bool setByString )
+{
+ mImpl->mUnderlineSetByString = setByString;
+}
+
+bool Controller::IsShadowSetByString()
+{
+ return mImpl->mShadowSetByString;
+}
+
+void Controller::ShadowSetByString( bool setByString )
+{
+ mImpl->mShadowSetByString = setByString;
+}
+
+bool Controller::IsOutlineSetByString()
+{
+ return mImpl->mOutlineSetByString;
+}
+
+void Controller::OutlineSetByString( bool setByString )
+{
+ mImpl->mOutlineSetByString = setByString;
+}
+
+bool Controller::IsFontStyleSetByString()
+{
+ return mImpl->mFontStyleSetByString;
+}
+
+void Controller::FontStyleSetByString( bool setByString )
+{
+ mImpl->mFontStyleSetByString = setByString;
+}
+
+// public : Queries & retrieves.
+
+Layout::Engine& Controller::GetLayoutEngine()
+{
+ return mImpl->mLayoutEngine;
+}
+
+View& Controller::GetView()
+{
+ return mImpl->mView;
+}
+
+Vector3 Controller::GetNaturalSize()
+{
+ DALI_LOG_INFO( gLogFilter, Debug::Verbose, "-->Controller::GetNaturalSize\n" );
+ Vector3 naturalSize;
+
+ // Make sure the model is up-to-date before layouting
+ ProcessModifyEvents();
+
+ if( mImpl->mRecalculateNaturalSize )
+ {
+ // Operations that can be done only once until the text changes.
+ const OperationsMask onlyOnceOperations = static_cast<OperationsMask>( CONVERT_TO_UTF32 |
+ GET_SCRIPTS |
+ VALIDATE_FONTS |
+ GET_LINE_BREAKS |
+ GET_WORD_BREAKS |
+ BIDI_INFO |
+ SHAPE_TEXT |
+ GET_GLYPH_METRICS );
+
+ // Set the update info to relayout the whole text.
+ mImpl->mTextUpdateInfo.mParagraphCharacterIndex = 0u;
+ mImpl->mTextUpdateInfo.mRequestedNumberOfCharacters = mImpl->mModel->mLogicalModel->mText.Count();
+
+ // Make sure the model is up-to-date before layouting
+ mImpl->UpdateModel( onlyOnceOperations );
+
+ // Layout the text for the new width.
+ mImpl->mOperationsPending = static_cast<OperationsMask>( mImpl->mOperationsPending | LAYOUT | REORDER );
+
+ // Store the actual control's size to restore later.
+ const Size actualControlSize = mImpl->mModel->mVisualModel->mControlSize;
+
+ DoRelayout( Size( MAX_FLOAT, MAX_FLOAT ),
+ static_cast<OperationsMask>( onlyOnceOperations |
+ LAYOUT | REORDER ),
+ naturalSize.GetVectorXY() );
+
+ // Do not do again the only once operations.
+ mImpl->mOperationsPending = static_cast<OperationsMask>( mImpl->mOperationsPending & ~onlyOnceOperations );
+
+ // Do the size related operations again.
+ const OperationsMask sizeOperations = static_cast<OperationsMask>( LAYOUT |
+ ALIGN |
+ REORDER );
+ mImpl->mOperationsPending = static_cast<OperationsMask>( mImpl->mOperationsPending | sizeOperations );
+
+ // Stores the natural size to avoid recalculate it again
+ // unless the text/style changes.
+ mImpl->mModel->mVisualModel->SetNaturalSize( naturalSize.GetVectorXY() );
+
+ mImpl->mRecalculateNaturalSize = false;
+
+ // Clear the update info. This info will be set the next time the text is updated.
+ mImpl->mTextUpdateInfo.Clear();
+
+ // Restore the actual control's size.
+ mImpl->mModel->mVisualModel->mControlSize = actualControlSize;
+
+ DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::GetNaturalSize calculated %f,%f,%f\n", naturalSize.x, naturalSize.y, naturalSize.z );
+ }
+ else
+ {
+ naturalSize = mImpl->mModel->mVisualModel->GetNaturalSize();
+
+ DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::GetNaturalSize cached %f,%f,%f\n", naturalSize.x, naturalSize.y, naturalSize.z );
+ }
+
+ naturalSize.x = ConvertToEven( naturalSize.x );
+ naturalSize.y = ConvertToEven( naturalSize.y );
+
+ return naturalSize;
+}
+
+float Controller::GetHeightForWidth( float width )
+{
+ DALI_LOG_INFO( gLogFilter, Debug::Verbose, "-->Controller::GetHeightForWidth %p width %f\n", this, width );
+ // Make sure the model is up-to-date before layouting
+ ProcessModifyEvents();
+
+ Size layoutSize;
+ if( fabsf( width - mImpl->mModel->mVisualModel->mControlSize.width ) > Math::MACHINE_EPSILON_1000 ||
+ mImpl->mTextUpdateInfo.mFullRelayoutNeeded ||
+ mImpl->mTextUpdateInfo.mClearAll )
+ {
+ // Operations that can be done only once until the text changes.
+ const OperationsMask onlyOnceOperations = static_cast<OperationsMask>( CONVERT_TO_UTF32 |
+ GET_SCRIPTS |
+ VALIDATE_FONTS |
+ GET_LINE_BREAKS |
+ GET_WORD_BREAKS |
+ BIDI_INFO |
+ SHAPE_TEXT |
+ GET_GLYPH_METRICS );
+
+ // Set the update info to relayout the whole text.
+ mImpl->mTextUpdateInfo.mParagraphCharacterIndex = 0u;
+ mImpl->mTextUpdateInfo.mRequestedNumberOfCharacters = mImpl->mModel->mLogicalModel->mText.Count();
+
+ // Make sure the model is up-to-date before layouting
+ mImpl->UpdateModel( onlyOnceOperations );
+
+
+ // Layout the text for the new width.
+ mImpl->mOperationsPending = static_cast<OperationsMask>( mImpl->mOperationsPending | LAYOUT );
+
+ // Store the actual control's width.
+ const float actualControlWidth = mImpl->mModel->mVisualModel->mControlSize.width;
+
+ DoRelayout( Size( width, MAX_FLOAT ),
+ static_cast<OperationsMask>( onlyOnceOperations |
+ LAYOUT ),
+ layoutSize );
+
+ // Do not do again the only once operations.
+ mImpl->mOperationsPending = static_cast<OperationsMask>( mImpl->mOperationsPending & ~onlyOnceOperations );
+
+ // Do the size related operations again.
+ const OperationsMask sizeOperations = static_cast<OperationsMask>( LAYOUT |
+ ALIGN |
+ REORDER );
+
+ mImpl->mOperationsPending = static_cast<OperationsMask>( mImpl->mOperationsPending | sizeOperations );
+
+ // Clear the update info. This info will be set the next time the text is updated.
+ mImpl->mTextUpdateInfo.Clear();
+
+ // Restore the actual control's width.
+ mImpl->mModel->mVisualModel->mControlSize.width = actualControlWidth;
+
+ DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::GetHeightForWidth calculated %f\n", layoutSize.height );
+ }
+ else
+ {
+ layoutSize = mImpl->mModel->mVisualModel->GetLayoutSize();
+ DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::GetHeightForWidth cached %f\n", layoutSize.height );
+ }
+
+ return layoutSize.height;
+}
+
+int Controller::GetLineCount( float width )
+{
+ GetHeightForWidth( width );
+ int numberofLines = mImpl->mModel->GetNumberOfLines();
+ return numberofLines;
+}
+
+const ModelInterface* const Controller::GetTextModel() const
+{
+ return mImpl->mModel.Get();
+}
+
+float Controller::GetScrollAmountByUserInput()
+{
+ float scrollAmount = 0.0f;
+
+ if (NULL != mImpl->mEventData && mImpl->mEventData->mCheckScrollAmount)
+ {
+ scrollAmount = mImpl->mModel->mScrollPosition.y - mImpl->mModel->mScrollPositionLast.y;
+ mImpl->mEventData->mCheckScrollAmount = false;
+ }
+ return scrollAmount;
+}
+
+bool Controller::GetTextScrollInfo( float& scrollPosition, float& controlHeight, float& layoutHeight )
+{
+ const Vector2& layout = mImpl->mModel->mVisualModel->GetLayoutSize();
+ bool isScrolled;
+
+ controlHeight = mImpl->mModel->mVisualModel->mControlSize.height;
+ layoutHeight = layout.height;
+ scrollPosition = mImpl->mModel->mScrollPosition.y;
+ isScrolled = !Equals( mImpl->mModel->mScrollPosition.y, mImpl->mModel->mScrollPositionLast.y, Math::MACHINE_EPSILON_1 );
+ return isScrolled;
+}
+
+void Controller::SetHiddenInputOption(const Property::Map& options )
+{
+ if( NULL == mImpl->mHiddenInput )
+ {
+ mImpl->mHiddenInput = new HiddenText( this );
+ }
+ mImpl->mHiddenInput->SetProperties(options);
+}
+
+void Controller::GetHiddenInputOption(Property::Map& options )
+{
+ if( NULL != mImpl->mHiddenInput )
+ {
+ mImpl->mHiddenInput->GetProperties(options);
+ }
+}
+
+void Controller::SetPlaceholderProperty( const Property::Map& map )
+{
+ const Property::Map::SizeType count = map.Count();
+
+ for( Property::Map::SizeType position = 0; position < count; ++position )
+ {
+ KeyValuePair keyValue = map.GetKeyValue( position );
+ Property::Key& key = keyValue.first;
+ Property::Value& value = keyValue.second;
+
+ if( key == Toolkit::Text::PlaceHolder::Property::TEXT || key == PLACEHOLDER_TEXT )
+ {
+ std::string text = "";
+ value.Get( text );
+ SetPlaceholderText( Controller::PLACEHOLDER_TYPE_INACTIVE, text );
+ }
+ else if( key == Toolkit::Text::PlaceHolder::Property::TEXT_FOCUSED || key == PLACEHOLDER_TEXT_FOCUSED )
+ {
+ std::string text = "";
+ value.Get( text );
+ SetPlaceholderText( Controller::PLACEHOLDER_TYPE_ACTIVE, text );
+ }
+ else if( key == Toolkit::Text::PlaceHolder::Property::COLOR || key == PLACEHOLDER_COLOR )
+ {
+ Vector4 textColor;
+ value.Get( textColor );
+ if( GetPlaceholderTextColor() != textColor )
+ {
+ SetPlaceholderTextColor( textColor );
+ }
+ }
+ else if( key == Toolkit::Text::PlaceHolder::Property::FONT_FAMILY || key == PLACEHOLDER_FONT_FAMILY )
+ {
+ std::string fontFamily = "";
+ value.Get( fontFamily );
+ SetPlaceholderFontFamily( fontFamily );
+ }
+ else if( key == Toolkit::Text::PlaceHolder::Property::FONT_STYLE || key == PLACEHOLDER_FONT_STYLE )
+ {
+ SetFontStyleProperty( this, value, Text::FontStyle::PLACEHOLDER );
+ }
+ else if( key == Toolkit::Text::PlaceHolder::Property::POINT_SIZE || key == PLACEHOLDER_POINT_SIZE )
+ {
+ float pointSize;
+ value.Get( pointSize );
+ if( !Equals( GetPlaceholderTextFontSize( Text::Controller::POINT_SIZE ), pointSize ) )
+ {
+ SetPlaceholderTextFontSize( pointSize, Text::Controller::POINT_SIZE );
+ }
+ }
+ else if( key == Toolkit::Text::PlaceHolder::Property::PIXEL_SIZE || key == PLACEHOLDER_PIXEL_SIZE )
+ {
+ float pixelSize;
+ value.Get( pixelSize );
+ if( !Equals( GetPlaceholderTextFontSize( Text::Controller::PIXEL_SIZE ), pixelSize ) )
+ {
+ SetPlaceholderTextFontSize( pixelSize, Text::Controller::PIXEL_SIZE );
+ }
+ }
+ else if( key == Toolkit::Text::PlaceHolder::Property::ELLIPSIS || key == PLACEHOLDER_ELLIPSIS )
+ {
+ bool ellipsis;
+ value.Get( ellipsis );
+ SetPlaceholderTextElideEnabled( ellipsis );
+ }
+ }
+}
+
+void Controller::GetPlaceholderProperty( Property::Map& map )
+{
+ if( NULL != mImpl->mEventData )
+ {
+ if( !mImpl->mEventData->mPlaceholderTextActive.empty() )
+ {
+ map[ Text::PlaceHolder::Property::TEXT_FOCUSED ] = mImpl->mEventData->mPlaceholderTextActive;
+ }
+ if( !mImpl->mEventData->mPlaceholderTextInactive.empty() )
+ {
+ map[ Text::PlaceHolder::Property::TEXT ] = mImpl->mEventData->mPlaceholderTextInactive;
+ }
+
+ map[ Text::PlaceHolder::Property::COLOR ] = mImpl->mEventData->mPlaceholderTextColor;
+ map[ Text::PlaceHolder::Property::FONT_FAMILY ] = GetPlaceholderFontFamily();
+
+ Property::Value fontStyleMapGet;
+ GetFontStyleProperty( this, fontStyleMapGet, Text::FontStyle::PLACEHOLDER );
+ map[ Text::PlaceHolder::Property::FONT_STYLE ] = fontStyleMapGet;
+
+ // Choose font size : POINT_SIZE or PIXEL_SIZE
+ if( !mImpl->mEventData->mIsPlaceholderPixelSize )
+ {
+ map[ Text::PlaceHolder::Property::POINT_SIZE ] = GetPlaceholderTextFontSize( Text::Controller::POINT_SIZE );
+ }
+ else
+ {
+ map[ Text::PlaceHolder::Property::PIXEL_SIZE ] = GetPlaceholderTextFontSize( Text::Controller::PIXEL_SIZE );
+ }
+
+ if( mImpl->mEventData->mPlaceholderEllipsisFlag )
+ {
+ map[ Text::PlaceHolder::Property::ELLIPSIS ] = IsPlaceholderTextElideEnabled();
+ }
+ }
+}
+
+Toolkit::DevelText::TextDirection::Type Controller::GetTextDirection()
+{
+ const LineRun* const firstline = mImpl->mModel->mVisualModel->mLines.Begin();
+ if ( firstline && firstline->direction )
+ {
+ return Toolkit::DevelText::TextDirection::RIGHT_TO_LEFT;
+ }
+
+ return Toolkit::DevelText::TextDirection::LEFT_TO_RIGHT;
+}
+
+// public : Relayout.
+
+Controller::UpdateTextType Controller::Relayout( const Size& size )
+{
+ DALI_LOG_INFO( gLogFilter, Debug::Verbose, "-->Controller::Relayout %p size %f,%f, autoScroll[%s]\n", this, size.width, size.height, mImpl->mIsAutoScrollEnabled ?"true":"false" );
+
+ UpdateTextType updateTextType = NONE_UPDATED;
+
+ if( ( size.width < Math::MACHINE_EPSILON_1000 ) || ( size.height < Math::MACHINE_EPSILON_1000 ) )
+ {
+ if( 0u != mImpl->mModel->mVisualModel->mGlyphPositions.Count() )
+ {
+ mImpl->mModel->mVisualModel->mGlyphPositions.Clear();
+ updateTextType = MODEL_UPDATED;
+ }
+
+ // Clear the update info. This info will be set the next time the text is updated.
+ mImpl->mTextUpdateInfo.Clear();
+
+ // Not worth to relayout if width or height is equal to zero.
+ DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::Relayout (skipped)\n" );
+
+ return updateTextType;
+ }
+
+ // Whether a new size has been set.
+ const bool newSize = ( size != mImpl->mModel->mVisualModel->mControlSize );
+
+ if( newSize )
+ {
+ DALI_LOG_INFO( gLogFilter, Debug::Verbose, "new size (previous size %f,%f)\n", mImpl->mModel->mVisualModel->mControlSize.width, mImpl->mModel->mVisualModel->mControlSize.height );
+
+ // Layout operations that need to be done if the size changes.
+ mImpl->mOperationsPending = static_cast<OperationsMask>( mImpl->mOperationsPending |
+ LAYOUT |
+ ALIGN |
+ UPDATE_LAYOUT_SIZE |
+ REORDER );
+ // Set the update info to relayout the whole text.
+ mImpl->mTextUpdateInfo.mFullRelayoutNeeded = true;
+ mImpl->mTextUpdateInfo.mCharacterIndex = 0u;
+
+ // Store the size used to layout the text.
+ mImpl->mModel->mVisualModel->mControlSize = size;
+ }
+
+ // Whether there are modify events.
+ if( 0u != mImpl->mModifyEvents.Count() )
+ {
+ // Style operations that need to be done if the text is modified.
+ mImpl->mOperationsPending = static_cast<OperationsMask>( mImpl->mOperationsPending |
+ COLOR );
+ }
+
+ // Set the update info to elide the text.
+ if( mImpl->mModel->mElideEnabled ||
+ ( ( NULL != mImpl->mEventData ) && mImpl->mEventData->mIsPlaceholderElideEnabled ) )
+ {
+ // Update Text layout for applying elided
+ mImpl->mOperationsPending = static_cast<OperationsMask>( mImpl->mOperationsPending |
+ ALIGN |
+ LAYOUT |
+ UPDATE_LAYOUT_SIZE |
+ REORDER );
+ mImpl->mTextUpdateInfo.mFullRelayoutNeeded = true;
+ mImpl->mTextUpdateInfo.mCharacterIndex = 0u;
+ }
+
+ // Make sure the model is up-to-date before layouting.
+ ProcessModifyEvents();
+ bool updated = mImpl->UpdateModel( mImpl->mOperationsPending );
+
+ // Layout the text.
+ Size layoutSize;
+ updated = DoRelayout( size,
+ mImpl->mOperationsPending,
+ layoutSize ) || updated;
+
+ if( updated )
+ {
+ updateTextType = MODEL_UPDATED;
+ }
+
+ // Do not re-do any operation until something changes.
+ mImpl->mOperationsPending = NO_OPERATION;
+ mImpl->mModel->mScrollPositionLast = mImpl->mModel->mScrollPosition;
+
+ // Whether the text control is editable
+ const bool isEditable = NULL != mImpl->mEventData;
+
+ // Keep the current offset as it will be used to update the decorator's positions (if the size changes).
+ Vector2 offset;
+ if( newSize && isEditable )
+ {
+ offset = mImpl->mModel->mScrollPosition;
+ }
+
+ if( !isEditable || !IsMultiLineEnabled() )
+ {
+ // After doing the text layout, the vertical offset to place the actor in the desired position can be calculated.
+ CalculateVerticalOffset( size );
+ }
+
+ if( isEditable )
+ {
+ if( newSize )
+ {
+ // If there is a new size, the scroll position needs to be clamped.
+ mImpl->ClampHorizontalScroll( layoutSize );
+
+ // Update the decorator's positions is needed if there is a new size.
+ mImpl->mEventData->mDecorator->UpdatePositions( mImpl->mModel->mScrollPosition - offset );
+ }
+
+ // Move the cursor, grab handle etc.
+ if( mImpl->ProcessInputEvents() )
+ {
+ updateTextType = static_cast<UpdateTextType>( updateTextType | DECORATOR_UPDATED );
+ }
+ }
+
+ // Clear the update info. This info will be set the next time the text is updated.
+ mImpl->mTextUpdateInfo.Clear();
+ DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::Relayout\n" );
+
+ return updateTextType;
+}
+
+void Controller::RequestRelayout()
+{
+ mImpl->RequestRelayout();
+}
+
+// public : Input style change signals.
+
+bool Controller::IsInputStyleChangedSignalsQueueEmpty()
+{
+ return ( NULL == mImpl->mEventData ) || ( 0u == mImpl->mEventData->mInputStyleChangedQueue.Count() );
+}
+
+void Controller::ProcessInputStyleChangedSignals()
+{
+ if( NULL == mImpl->mEventData )
+ {
+ // Nothing to do.
+ return;
+ }
+
+ for( Vector<InputStyle::Mask>::ConstIterator it = mImpl->mEventData->mInputStyleChangedQueue.Begin(),
+ endIt = mImpl->mEventData->mInputStyleChangedQueue.End();
+ it != endIt;
+ ++it )
+ {
+ const InputStyle::Mask mask = *it;
+
+ if( NULL != mImpl->mEditableControlInterface )
+ {
+ // Emit the input style changed signal.
+ mImpl->mEditableControlInterface->InputStyleChanged( mask );
+ }
+ }
+
+ mImpl->mEventData->mInputStyleChangedQueue.Clear();
+}
+
+// public : Text-input Event Queuing.
+
+void Controller::KeyboardFocusGainEvent()
+{
+ DALI_ASSERT_DEBUG( mImpl->mEventData && "Unexpected KeyboardFocusGainEvent" );
+
+ if( NULL != mImpl->mEventData )
+ {
+ if( ( EventData::INACTIVE == mImpl->mEventData->mState ) ||
+ ( EventData::INTERRUPTED == mImpl->mEventData->mState ) )
+ {
+ mImpl->ChangeState( EventData::EDITING );
+ mImpl->mEventData->mUpdateCursorPosition = true; //If editing started without tap event, cursor update must be triggered.
+ mImpl->mEventData->mUpdateInputStyle = true;
+ }
+ mImpl->NotifyImfMultiLineStatus();
+ if( mImpl->IsShowingPlaceholderText() )
+ {
+ // Show alternative placeholder-text when editing
+ ShowPlaceholderText();
+ }
+
+ mImpl->RequestRelayout();
+ }
+}
+
+void Controller::KeyboardFocusLostEvent()
+{
+ DALI_ASSERT_DEBUG( mImpl->mEventData && "Unexpected KeyboardFocusLostEvent" );
+
+ if( NULL != mImpl->mEventData )
+ {
+ if( EventData::INTERRUPTED != mImpl->mEventData->mState )
+ {
+ mImpl->ChangeState( EventData::INACTIVE );
+
+ if( !mImpl->IsShowingRealText() )
+ {
+ // Revert to regular placeholder-text when not editing
+ ShowPlaceholderText();
+ }
+ }
+ }
+ mImpl->RequestRelayout();
+}
+
+bool Controller::KeyEvent( const Dali::KeyEvent& keyEvent )
+{
+ DALI_ASSERT_DEBUG( mImpl->mEventData && "Unexpected KeyEvent" );
+
+ bool textChanged = false;
+ bool relayoutNeeded = false;
+
+ if( ( NULL != mImpl->mEventData ) &&
+ ( keyEvent.state == KeyEvent::Down ) )
+ {
+ int keyCode = keyEvent.keyCode;
+ const std::string& keyString = keyEvent.keyPressed;
+ const std::string keyName = keyEvent.keyPressedName;
+
+ const bool isNullKey = ( 0 == keyCode ) && ( keyString.empty() );
+
+ // Pre-process to separate modifying events from non-modifying input events.
+ if( isNullKey )
+ {
+ // In some platforms arrive key events with no key code.
+ // Do nothing.
+ return false;
+ }
+ else if( Dali::DALI_KEY_ESCAPE == keyCode || Dali::DALI_KEY_BACK == keyCode || Dali::DALI_KEY_SEARCH == keyCode )
+ {
+ // Do nothing
+ return false;
+ }
+ else if( ( Dali::DALI_KEY_CURSOR_LEFT == keyCode ) ||
+ ( Dali::DALI_KEY_CURSOR_RIGHT == keyCode ) ||
+ ( Dali::DALI_KEY_CURSOR_UP == keyCode ) ||
+ ( Dali::DALI_KEY_CURSOR_DOWN == keyCode ) )
+ {
+ // If don't have any text, do nothing.
+ if( !mImpl->mTextUpdateInfo.mPreviousNumberOfCharacters )
+ {
+ return false;
+ }
+
+ uint32_t cursorPosition = mImpl->mEventData->mPrimaryCursorPosition;
+ uint32_t numberOfCharacters = mImpl->mTextUpdateInfo.mPreviousNumberOfCharacters;
+ uint32_t cursorLine = mImpl->mModel->mVisualModel->GetLineOfCharacter( cursorPosition );
+ uint32_t numberOfLines = mImpl->mModel->GetNumberOfLines();
+
+ // Logic to determine whether this text control will lose focus or not.
+ if( ( Dali::DALI_KEY_CURSOR_LEFT == keyCode && 0 == cursorPosition ) ||
+ ( Dali::DALI_KEY_CURSOR_RIGHT == keyCode && numberOfCharacters == cursorPosition) ||
+ ( Dali::DALI_KEY_CURSOR_DOWN == keyCode && cursorLine == numberOfLines -1 ) ||
+ ( Dali::DALI_KEY_CURSOR_DOWN == keyCode && numberOfCharacters == cursorPosition && cursorLine -1 == numberOfLines -1 ) ||
+ ( Dali::DALI_KEY_CURSOR_UP == keyCode && cursorLine == 0 ) ||
+ ( Dali::DALI_KEY_CURSOR_UP == keyCode && numberOfCharacters == cursorPosition && cursorLine == 1 ) )
+ {
+ return false;
+ }
+
+ mImpl->mEventData->mCheckScrollAmount = true;
+ Event event( Event::CURSOR_KEY_EVENT );
+ event.p1.mInt = keyCode;
+ event.p2.mBool = keyEvent.IsShiftModifier();
+ mImpl->mEventData->mEventQueue.push_back( event );
+
+ // Will request for relayout.
+ relayoutNeeded = true;
+ }
+ else if ( Dali::DevelKey::DALI_KEY_CONTROL_LEFT == keyCode || Dali::DevelKey::DALI_KEY_CONTROL_RIGHT == keyCode )
+ {
+ // Left or Right Control key event is received before Ctrl-C/V/X key event is received
+ // If not handle it here, any selected text will be deleted
+
+ // Do nothing
+ return false;
+ }
+ else if ( keyEvent.IsCtrlModifier() )
+ {
+ bool consumed = false;
+ if (keyName == KEY_C_NAME)
+ {
+ // Ctrl-C to copy the selected text
+ TextPopupButtonTouched( Toolkit::TextSelectionPopup::COPY );
+ consumed = true;
+ }
+ else if (keyName == KEY_V_NAME)
+ {
+ // Ctrl-V to paste the copied text
+ TextPopupButtonTouched( Toolkit::TextSelectionPopup::PASTE );
+ consumed = true;
+ }
+ else if (keyName == KEY_X_NAME)
+ {
+ // Ctrl-X to cut the selected text
+ TextPopupButtonTouched( Toolkit::TextSelectionPopup::CUT );
+ consumed = true;
+ }
+ return consumed;
+ }
+ else if( ( Dali::DALI_KEY_BACKSPACE == keyCode ) ||
+ ( Dali::DevelKey::DALI_KEY_DELETE == keyCode ) )
+ {
+ textChanged = DeleteEvent( keyCode );
+
+ // Will request for relayout.
+ relayoutNeeded = true;
+ }
+ else if( IsKey( keyEvent, Dali::DALI_KEY_POWER ) ||
+ IsKey( keyEvent, Dali::DALI_KEY_MENU ) ||
+ IsKey( keyEvent, Dali::DALI_KEY_HOME ) )
+ {
+ // Power key/Menu/Home key behaviour does not allow edit mode to resume.
+ mImpl->ChangeState( EventData::INACTIVE );
+
+ // Will request for relayout.
+ relayoutNeeded = true;
+
+ // This branch avoids calling the InsertText() method of the 'else' branch which can delete selected text.
+ }
+ else if( Dali::DALI_KEY_SHIFT_LEFT == keyCode )
+ {
+ // DALI_KEY_SHIFT_LEFT is the key code for the Left Shift. It's sent (by the imf?) when the predictive text is enabled
+ // and a character is typed after the type of a upper case latin character.
+
+ // Do nothing.
+ return false;
+ }
+ else if( ( Dali::DALI_KEY_VOLUME_UP == keyCode ) || ( Dali::DALI_KEY_VOLUME_DOWN == keyCode ) )
+ {
+ // This branch avoids calling the InsertText() method of the 'else' branch which can delete selected text.
+ // Do nothing.
+ return false;
+ }
+ else
+ {
+ DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Controller::KeyEvent %p keyString %s\n", this, keyString.c_str() );
+
+ // IMF manager is no longer handling key-events
+ mImpl->ClearPreEditFlag();
+
+ InsertText( keyString, COMMIT );
+ textChanged = true;
+
+ // Will request for relayout.
+ relayoutNeeded = true;
+ }
+
+ if ( ( mImpl->mEventData->mState != EventData::INTERRUPTED ) &&
+ ( mImpl->mEventData->mState != EventData::INACTIVE ) &&
+ ( !isNullKey ) &&
+ ( Dali::DALI_KEY_SHIFT_LEFT != keyCode ) &&
+ ( Dali::DALI_KEY_VOLUME_UP != keyCode ) &&
+ ( Dali::DALI_KEY_VOLUME_DOWN != keyCode ) )
+ {
+ // Should not change the state if the key is the shift send by the imf manager.
+ // Otherwise, when the state is SELECTING the text controller can't send the right
+ // surrounding info to the imf.
+ mImpl->ChangeState( EventData::EDITING );
+
+ // Will request for relayout.
+ relayoutNeeded = true;
+ }
+
+ if( relayoutNeeded )
+ {
+ mImpl->RequestRelayout();
+ }
+ }
+
+ if( textChanged &&
+ ( NULL != mImpl->mEditableControlInterface ) )
+ {
+ // Do this last since it provides callbacks into application code
+ mImpl->mEditableControlInterface->TextChanged();
+ }
+
+ return true;
+}
+
+void Controller::TapEvent( unsigned int tapCount, float x, float y )
+{
+ DALI_ASSERT_DEBUG( mImpl->mEventData && "Unexpected TapEvent" );
+
+ if( NULL != mImpl->mEventData )
+ {
+ DALI_LOG_INFO( gLogFilter, Debug::Concise, "TapEvent state:%d \n", mImpl->mEventData->mState );
+ EventData::State state( mImpl->mEventData->mState );
+ bool relayoutNeeded( false ); // to avoid unnecessary relayouts when tapping an empty text-field
+
+ if( mImpl->IsClipboardVisible() )
+ {
+ if( EventData::INACTIVE == state || EventData::EDITING == state)
+ {
+ mImpl->ChangeState( EventData::EDITING_WITH_GRAB_HANDLE );
+ }
+ relayoutNeeded = true;
+ }
+ else if( 1u == tapCount )
+ {
+ if( EventData::EDITING_WITH_POPUP == state || EventData::EDITING_WITH_PASTE_POPUP == state )
+ {
+ mImpl->ChangeState( EventData::EDITING_WITH_GRAB_HANDLE ); // If Popup shown hide it here so can be shown again if required.
+ }
+
+ if( mImpl->IsShowingRealText() && ( EventData::INACTIVE != state ) )
+ {
+ mImpl->ChangeState( EventData::EDITING_WITH_GRAB_HANDLE );
+ relayoutNeeded = true;
+ }
+ else
+ {
+ if( mImpl->IsShowingPlaceholderText() && !mImpl->IsFocusedPlaceholderAvailable() )
+ {
+ // Hide placeholder text
+ ResetText();
+ }
+
+ if( EventData::INACTIVE == state )
+ {
+ mImpl->ChangeState( EventData::EDITING );
+ }
+ else if( !mImpl->IsClipboardEmpty() )
+ {
+ mImpl->ChangeState( EventData::EDITING_WITH_POPUP );
+ }
+ relayoutNeeded = true;
+ }
+ }
+ else if( 2u == tapCount )
+ {
+ if( mImpl->mEventData->mSelectionEnabled &&
+ mImpl->IsShowingRealText() )
+ {
+ relayoutNeeded = true;
+ mImpl->mEventData->mIsLeftHandleSelected = true;
+ mImpl->mEventData->mIsRightHandleSelected = true;
+ }
+ }
+
+ // Handles & cursors must be repositioned after Relayout() i.e. after the Model has been updated
+ if( relayoutNeeded )
+ {
+ Event event( Event::TAP_EVENT );
+ event.p1.mUint = tapCount;
+ event.p2.mFloat = x;
+ event.p3.mFloat = y;
+ mImpl->mEventData->mEventQueue.push_back( event );
+
+ mImpl->RequestRelayout();
+ }
+ }
+
+ // Reset keyboard as tap event has occurred.
+ mImpl->ResetImfManager();
+}
+
+void Controller::PanEvent( Gesture::State state, const Vector2& displacement )
+{
+ DALI_ASSERT_DEBUG( mImpl->mEventData && "Unexpected PanEvent" );
+
+ if( NULL != mImpl->mEventData )
+ {
+ Event event( Event::PAN_EVENT );
+ event.p1.mInt = state;
+ event.p2.mFloat = displacement.x;
+ event.p3.mFloat = displacement.y;
+ mImpl->mEventData->mEventQueue.push_back( event );
+
+ mImpl->RequestRelayout();
+ }
+}
+
+void Controller::LongPressEvent( Gesture::State state, float x, float y )
+{
+ DALI_ASSERT_DEBUG( mImpl->mEventData && "Unexpected LongPressEvent" );
+
+ if( ( state == Gesture::Started ) &&
+ ( NULL != mImpl->mEventData ) )
+ {
+ // The 1st long-press on inactive text-field is treated as tap
+ if( EventData::INACTIVE == mImpl->mEventData->mState )
+ {
+ mImpl->ChangeState( EventData::EDITING );
+
+ Event event( Event::TAP_EVENT );
+ event.p1.mUint = 1;
+ event.p2.mFloat = x;
+ event.p3.mFloat = y;
+ mImpl->mEventData->mEventQueue.push_back( event );
+
+ mImpl->RequestRelayout();
+ }
+ else if( !mImpl->IsShowingRealText() )
+ {
+ Event event( Event::LONG_PRESS_EVENT );
+ event.p1.mInt = state;
+ event.p2.mFloat = x;
+ event.p3.mFloat = y;
+ mImpl->mEventData->mEventQueue.push_back( event );
+ mImpl->RequestRelayout();
+ }
+ else if( !mImpl->IsClipboardVisible() )
+ {
+ // Reset the imf manager to commit the pre-edit before selecting the text.
+ mImpl->ResetImfManager();
+
+ Event event( Event::LONG_PRESS_EVENT );
+ event.p1.mInt = state;
+ event.p2.mFloat = x;
+ event.p3.mFloat = y;
+ mImpl->mEventData->mEventQueue.push_back( event );
+ mImpl->RequestRelayout();
+
+ mImpl->mEventData->mIsLeftHandleSelected = true;
+ mImpl->mEventData->mIsRightHandleSelected = true;
+ }
+ }
+}
+
+ImfManager::ImfCallbackData Controller::OnImfEvent( ImfManager& imfManager, const ImfManager::ImfEventData& imfEvent )
+{
+ // Whether the text needs to be relaid-out.
+ bool requestRelayout = false;
+
+ // Whether to retrieve the text and cursor position to be sent to the IMF manager.
+ bool retrieveText = false;
+ bool retrieveCursor = false;
+
+ switch( imfEvent.eventName )
+ {
+ case ImfManager::COMMIT:
+ {
+ InsertText( imfEvent.predictiveString, Text::Controller::COMMIT );
+ requestRelayout = true;
+ retrieveCursor = true;
+ break;
+ }
+ case ImfManager::PREEDIT:
+ {
+ InsertText( imfEvent.predictiveString, Text::Controller::PRE_EDIT );
+ requestRelayout = true;
+ retrieveCursor = true;
+ break;
+ }
+ case ImfManager::DELETESURROUNDING:
+ {
+ const bool textDeleted = RemoveText( imfEvent.cursorOffset,
+ imfEvent.numberOfChars,
+ DONT_UPDATE_INPUT_STYLE );
+
+ if( textDeleted )
+ {
+ if( ( 0u != mImpl->mModel->mLogicalModel->mText.Count() ) ||
+ !mImpl->IsPlaceholderAvailable() )
+ {
+ mImpl->QueueModifyEvent( ModifyEvent::TEXT_DELETED );
+ }
+ else
+ {
+ ShowPlaceholderText();
+ }
+ mImpl->mEventData->mUpdateCursorPosition = true;
+ mImpl->mEventData->mScrollAfterDelete = true;
+
+ requestRelayout = true;
+ }
+ break;
+ }
+ case ImfManager::GETSURROUNDING:
+ {
+ retrieveText = true;
+ retrieveCursor = true;
+ break;
+ }
+ case ImfManager::PRIVATECOMMAND:
+ {
+ // PRIVATECOMMAND event is just for getting the private command message
+ retrieveText = true;
+ retrieveCursor = true;
+ break;
+ }
+ case ImfManager::VOID:
+ {
+ // do nothing
+ break;
+ }
+ } // end switch
+
+ if( requestRelayout )
+ {
+ mImpl->mOperationsPending = ALL_OPERATIONS;
+ mImpl->RequestRelayout();
+ }
+
+ std::string text;
+ CharacterIndex cursorPosition = 0u;
+ Length numberOfWhiteSpaces = 0u;
+
+ if( retrieveCursor )
+ {
+ numberOfWhiteSpaces = mImpl->GetNumberOfWhiteSpaces( 0u );
+
+ cursorPosition = mImpl->GetLogicalCursorPosition();
+
+ if( cursorPosition < numberOfWhiteSpaces )
+ {
+ cursorPosition = 0u;
+ }
+ else
+ {
+ cursorPosition -= numberOfWhiteSpaces;
+ }
+ }
+
+ if( retrieveText )
+ {
+ if( !mImpl->IsShowingPlaceholderText() )
+ {
+ // Retrieves the normal text string.
+ mImpl->GetText( numberOfWhiteSpaces, text );
+ }
+ else
+ {
+ // When the current text is Placeholder Text, the surrounding text should be empty string.
+ // It means DALi should send empty string ("") to IME.
+ text = "";
+ }
+ }
+
+ ImfManager::ImfCallbackData callbackData( ( retrieveText || retrieveCursor ), cursorPosition, text, false );
+
+ if( requestRelayout &&
+ ( NULL != mImpl->mEditableControlInterface ) )
+ {
+ // Do this last since it provides callbacks into application code
+ mImpl->mEditableControlInterface->TextChanged();
+ }
+
+ return callbackData;
+}
+
+void Controller::PasteClipboardItemEvent()
+{
+ // Retrieve the clipboard contents first
+ ClipboardEventNotifier notifier( ClipboardEventNotifier::Get() );
+ std::string stringToPaste( notifier.GetContent() );
+
+ // Commit the current pre-edit text; the contents of the clipboard should be appended
+ mImpl->ResetImfManager();
+
+ // Temporary disable hiding clipboard
+ mImpl->SetClipboardHideEnable( false );
+
+ // Paste
+ PasteText( stringToPaste );
+
+ mImpl->SetClipboardHideEnable( true );
+}
+
+// protected : Inherit from Text::Decorator::ControllerInterface.
+
+void Controller::GetTargetSize( Vector2& targetSize )
+{
+ targetSize = mImpl->mModel->mVisualModel->mControlSize;
+}
+
+void Controller::AddDecoration( Actor& actor, bool needsClipping )
+{
+ if( NULL != mImpl->mEditableControlInterface )
+ {
+ mImpl->mEditableControlInterface->AddDecoration( actor, needsClipping );
+ }
+}
+
+void Controller::DecorationEvent( HandleType handleType, HandleState state, float x, float y )
+{
+ DALI_ASSERT_DEBUG( mImpl->mEventData && "Unexpected DecorationEvent" );
+
+ if( NULL != mImpl->mEventData )
+ {
+ switch( handleType )