X-Git-Url: http://review.tizen.org/git/?p=platform%2Fcore%2Fuifw%2Fdali-toolkit.git;a=blobdiff_plain;f=dali-toolkit%2Finternal%2Ftext%2Ftext-controller-impl.cpp;h=8140c84a0b7545f82d15c525cd0ea6ad11209a52;hp=6554a2c62fcd290045d8cdbc44c740e2a979974b;hb=22451f7abe0230b418d551d9eac0c685273e72f1;hpb=af1b5886e64600f578dc10edc257aad5f0574196 diff --git a/dali-toolkit/internal/text/text-controller-impl.cpp b/dali-toolkit/internal/text/text-controller-impl.cpp index 6554a2c..cf49df5 100644 --- a/dali-toolkit/internal/text/text-controller-impl.cpp +++ b/dali-toolkit/internal/text/text-controller-impl.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 Samsung Electronics Co., Ltd. + * Copyright (c) 2022 Samsung Electronics Co., Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,133 +19,397 @@ #include // EXTERNAL INCLUDES -#include +#include #include +#include +#include +#include // INTERNAL INCLUDES -#include -#include #include -#include #include -#include -#include -#include +#include #include +#include #include +#include +#include +#include +#include +#include #include #include +#include using namespace Dali; namespace { - #if defined(DEBUG_ENABLED) Debug::Filter* gLogFilter = Debug::Filter::New(Debug::NoLogging, true, "LOG_TEXT_CONTROLS"); #endif -#define MAKE_SHADER(A)#A +constexpr float MAX_FLOAT = std::numeric_limits::max(); + +const std::string EMPTY_STRING(""); -const char* VERTEX_SHADER_BACKGROUND = MAKE_SHADER( -attribute mediump vec2 aPosition; -attribute mediump vec4 aColor; -varying mediump vec4 vColor; -uniform highp mat4 uMvpMatrix; +} // namespace -void main() +namespace Dali::Toolkit::Text { - mediump vec4 position = vec4( aPosition, 0.0, 1.0 ); - gl_Position = uMvpMatrix * position; - vColor = aColor; -} -); +namespace +{ +void SetDefaultInputStyle(InputStyle& inputStyle, const FontDefaults* const fontDefaults, const Vector4& textColor) +{ + // Sets the default text's color. + inputStyle.textColor = textColor; + inputStyle.isDefaultColor = true; + + inputStyle.familyName.clear(); + inputStyle.weight = TextAbstraction::FontWeight::NORMAL; + inputStyle.width = TextAbstraction::FontWidth::NORMAL; + inputStyle.slant = TextAbstraction::FontSlant::NORMAL; + inputStyle.size = 0.f; -const char* FRAGMENT_SHADER_BACKGROUND = MAKE_SHADER( -varying mediump vec4 vColor; -uniform lowp vec4 uColor; + inputStyle.lineSpacing = 0.f; -void main() -{ - gl_FragColor = vColor * uColor; + inputStyle.underlineProperties.clear(); + inputStyle.shadowProperties.clear(); + inputStyle.embossProperties.clear(); + inputStyle.outlineProperties.clear(); + + inputStyle.isFamilyDefined = false; + inputStyle.isWeightDefined = false; + inputStyle.isWidthDefined = false; + inputStyle.isSlantDefined = false; + inputStyle.isSizeDefined = false; + + inputStyle.isLineSpacingDefined = false; + + inputStyle.isUnderlineDefined = false; + inputStyle.isShadowDefined = false; + inputStyle.isEmbossDefined = false; + inputStyle.isOutlineDefined = false; + + // Sets the default font's family name, weight, width, slant and size. + if(fontDefaults) + { + if(fontDefaults->familyDefined) + { + inputStyle.familyName = fontDefaults->mFontDescription.family; + inputStyle.isFamilyDefined = true; + } + + if(fontDefaults->weightDefined) + { + inputStyle.weight = fontDefaults->mFontDescription.weight; + inputStyle.isWeightDefined = true; + } + + if(fontDefaults->widthDefined) + { + inputStyle.width = fontDefaults->mFontDescription.width; + inputStyle.isWidthDefined = true; + } + + if(fontDefaults->slantDefined) + { + inputStyle.slant = fontDefaults->mFontDescription.slant; + inputStyle.isSlantDefined = true; + } + + if(fontDefaults->sizeDefined) + { + inputStyle.size = fontDefaults->mDefaultPointSize; + inputStyle.isSizeDefined = true; + } + } } -); -struct BackgroundVertex +void ChangeTextControllerState(Controller::Impl& impl, EventData::State newState) { - Vector2 mPosition; ///< Vertex posiiton - Vector4 mColor; ///< Vertex color -}; + EventData* eventData = impl.mEventData; -struct BackgroundMesh -{ - Vector< BackgroundVertex > mVertices; ///< container of vertices - Vector< unsigned short > mIndices; ///< container of indices -}; + if(nullptr == eventData) + { + // Nothing to do if there is no text input. + return; + } -const Dali::Vector4 LIGHT_BLUE( 0.75f, 0.96f, 1.f, 1.f ); -const Dali::Vector4 BACKGROUND_SUB4( 0.58f, 0.87f, 0.96f, 1.f ); -const Dali::Vector4 BACKGROUND_SUB5( 0.83f, 0.94f, 0.98f, 1.f ); -const Dali::Vector4 BACKGROUND_SUB6( 1.f, 0.5f, 0.5f, 1.f ); -const Dali::Vector4 BACKGROUND_SUB7( 1.f, 0.8f, 0.8f, 1.f ); + DecoratorPtr& decorator = eventData->mDecorator; + if(!decorator) + { + // Nothing to do if there is no decorator. + return; + } -} // namespace + DALI_LOG_INFO(gLogFilter, Debug::General, "ChangeState state:%d newstate:%d\n", eventData->mState, newState); -namespace Dali -{ + if(eventData->mState != newState) + { + eventData->mPreviousState = eventData->mState; + eventData->mState = newState; -namespace Toolkit -{ + switch(eventData->mState) + { + case EventData::INACTIVE: + { + decorator->SetActiveCursor(ACTIVE_CURSOR_NONE); + decorator->StopCursorBlink(); + decorator->SetHandleActive(GRAB_HANDLE, false); + decorator->SetHandleActive(LEFT_SELECTION_HANDLE, false); + decorator->SetHandleActive(RIGHT_SELECTION_HANDLE, false); + decorator->SetHighlightActive(false); + decorator->SetPopupActive(false); + eventData->mDecoratorUpdated = true; + break; + } -namespace Text -{ + case EventData::INTERRUPTED: + { + decorator->SetHandleActive(GRAB_HANDLE, false); + decorator->SetHandleActive(LEFT_SELECTION_HANDLE, false); + decorator->SetHandleActive(RIGHT_SELECTION_HANDLE, false); + decorator->SetHighlightActive(false); + decorator->SetPopupActive(false); + eventData->mDecoratorUpdated = true; + break; + } + + case EventData::SELECTING: + { + decorator->SetActiveCursor(ACTIVE_CURSOR_NONE); + decorator->StopCursorBlink(); + decorator->SetHandleActive(GRAB_HANDLE, false); + if(eventData->mGrabHandleEnabled) + { + decorator->SetHandleActive(LEFT_SELECTION_HANDLE, true); + decorator->SetHandleActive(RIGHT_SELECTION_HANDLE, true); + } + decorator->SetHighlightActive(true); + if(eventData->mGrabHandlePopupEnabled) + { + impl.SetPopupButtons(); + decorator->SetPopupActive(true); + } + eventData->mDecoratorUpdated = true; + break; + } + + case EventData::EDITING: + { + decorator->SetActiveCursor(ACTIVE_CURSOR_PRIMARY); + if(eventData->mCursorBlinkEnabled) + { + decorator->StartCursorBlink(); + } + // Grab handle is not shown until a tap is received whilst EDITING + decorator->SetHandleActive(GRAB_HANDLE, false); + decorator->SetHandleActive(LEFT_SELECTION_HANDLE, false); + decorator->SetHandleActive(RIGHT_SELECTION_HANDLE, false); + decorator->SetHighlightActive(false); + if(eventData->mGrabHandlePopupEnabled) + { + decorator->SetPopupActive(false); + } + eventData->mDecoratorUpdated = true; + break; + } + case EventData::EDITING_WITH_POPUP: + { + DALI_LOG_INFO(gLogFilter, Debug::Verbose, "EDITING_WITH_POPUP \n", newState); + + decorator->SetActiveCursor(ACTIVE_CURSOR_PRIMARY); + if(eventData->mCursorBlinkEnabled) + { + decorator->StartCursorBlink(); + } + if(eventData->mSelectionEnabled) + { + decorator->SetHandleActive(LEFT_SELECTION_HANDLE, false); + decorator->SetHandleActive(RIGHT_SELECTION_HANDLE, false); + decorator->SetHighlightActive(false); + } + else if(eventData->mGrabHandleEnabled) + { + decorator->SetHandleActive(GRAB_HANDLE, true); + } + if(eventData->mGrabHandlePopupEnabled) + { + impl.SetPopupButtons(); + decorator->SetPopupActive(true); + } + eventData->mDecoratorUpdated = true; + break; + } + case EventData::EDITING_WITH_GRAB_HANDLE: + { + DALI_LOG_INFO(gLogFilter, Debug::Verbose, "EDITING_WITH_GRAB_HANDLE \n", newState); + + decorator->SetActiveCursor(ACTIVE_CURSOR_PRIMARY); + if(eventData->mCursorBlinkEnabled) + { + decorator->StartCursorBlink(); + } + // Grab handle is not shown until a tap is received whilst EDITING + if(eventData->mGrabHandleEnabled) + { + decorator->SetHandleActive(GRAB_HANDLE, true); + } + decorator->SetHandleActive(LEFT_SELECTION_HANDLE, false); + decorator->SetHandleActive(RIGHT_SELECTION_HANDLE, false); + decorator->SetHighlightActive(false); + if(eventData->mGrabHandlePopupEnabled) + { + decorator->SetPopupActive(false); + } + eventData->mDecoratorUpdated = true; + break; + } + + case EventData::SELECTION_HANDLE_PANNING: + { + decorator->SetActiveCursor(ACTIVE_CURSOR_NONE); + decorator->StopCursorBlink(); + decorator->SetHandleActive(GRAB_HANDLE, false); + if(eventData->mGrabHandleEnabled) + { + decorator->SetHandleActive(LEFT_SELECTION_HANDLE, true); + decorator->SetHandleActive(RIGHT_SELECTION_HANDLE, true); + } + decorator->SetHighlightActive(true); + if(eventData->mGrabHandlePopupEnabled) + { + decorator->SetPopupActive(false); + } + eventData->mDecoratorUpdated = true; + break; + } + + case EventData::GRAB_HANDLE_PANNING: + { + DALI_LOG_INFO(gLogFilter, Debug::Verbose, "GRAB_HANDLE_PANNING \n", newState); -EventData::EventData( DecoratorPtr decorator, InputMethodContext& inputMethodContext ) -: mDecorator( decorator ), - mInputMethodContext( inputMethodContext ), - mPlaceholderFont( NULL ), + decorator->SetActiveCursor(ACTIVE_CURSOR_PRIMARY); + if(eventData->mCursorBlinkEnabled) + { + decorator->StartCursorBlink(); + } + if(eventData->mGrabHandleEnabled) + { + decorator->SetHandleActive(GRAB_HANDLE, true); + } + decorator->SetHandleActive(LEFT_SELECTION_HANDLE, false); + decorator->SetHandleActive(RIGHT_SELECTION_HANDLE, false); + decorator->SetHighlightActive(false); + if(eventData->mGrabHandlePopupEnabled) + { + decorator->SetPopupActive(false); + } + eventData->mDecoratorUpdated = true; + break; + } + + case EventData::EDITING_WITH_PASTE_POPUP: + { + DALI_LOG_INFO(gLogFilter, Debug::Verbose, "EDITING_WITH_PASTE_POPUP \n", newState); + + decorator->SetActiveCursor(ACTIVE_CURSOR_PRIMARY); + if(eventData->mCursorBlinkEnabled) + { + decorator->StartCursorBlink(); + } + + if(eventData->mGrabHandleEnabled) + { + decorator->SetHandleActive(GRAB_HANDLE, true); + } + decorator->SetHandleActive(LEFT_SELECTION_HANDLE, false); + decorator->SetHandleActive(RIGHT_SELECTION_HANDLE, false); + decorator->SetHighlightActive(false); + + if(eventData->mGrabHandlePopupEnabled) + { + impl.SetPopupButtons(); + decorator->SetPopupActive(true); + } + eventData->mDecoratorUpdated = true; + break; + } + + case EventData::TEXT_PANNING: + { + decorator->SetActiveCursor(ACTIVE_CURSOR_NONE); + decorator->StopCursorBlink(); + decorator->SetHandleActive(GRAB_HANDLE, false); + if(eventData->mDecorator->IsHandleActive(LEFT_SELECTION_HANDLE) || + decorator->IsHandleActive(RIGHT_SELECTION_HANDLE)) + { + decorator->SetHandleActive(LEFT_SELECTION_HANDLE, false); + decorator->SetHandleActive(RIGHT_SELECTION_HANDLE, false); + decorator->SetHighlightActive(true); + } + + if(eventData->mGrabHandlePopupEnabled) + { + decorator->SetPopupActive(false); + } + + eventData->mDecoratorUpdated = true; + break; + } + } + } +} + +} // unnamed Namespace + +EventData::EventData(DecoratorPtr decorator, InputMethodContext& inputMethodContext) +: mDecorator(decorator), + mInputMethodContext(inputMethodContext), + mPlaceholderFont(nullptr), mPlaceholderTextActive(), mPlaceholderTextInactive(), - mPlaceholderTextColor( 0.8f, 0.8f, 0.8f, 0.8f ), // This color has been published in the Public API (placeholder-properties.h). + mPlaceholderTextColor(0.8f, 0.8f, 0.8f, 0.8f), // This color has been published in the Public API (placeholder-properties.h). mEventQueue(), mInputStyleChangedQueue(), - mPreviousState( INACTIVE ), - mState( INACTIVE ), - mPrimaryCursorPosition( 0u ), - mLeftSelectionPosition( 0u ), - mRightSelectionPosition( 0u ), - mPreEditStartPosition( 0u ), - mPreEditLength( 0u ), - mCursorHookPositionX( 0.f ), - mDoubleTapAction( Controller::NoTextTap::NO_ACTION ), - mLongPressAction( Controller::NoTextTap::SHOW_SELECTION_POPUP ), - mIsShowingPlaceholderText( false ), - mPreEditFlag( false ), - mDecoratorUpdated( false ), - mCursorBlinkEnabled( true ), - mGrabHandleEnabled( true ), - mGrabHandlePopupEnabled( true ), - mSelectionEnabled( true ), - mUpdateCursorHookPosition( false ), - mUpdateCursorPosition( false ), - mUpdateGrabHandlePosition( false ), - mUpdateLeftSelectionPosition( false ), - mUpdateRightSelectionPosition( false ), - mIsLeftHandleSelected( false ), - mIsRightHandleSelected( false ), - mUpdateHighlightBox( false ), - mScrollAfterUpdatePosition( false ), - mScrollAfterDelete( false ), - mAllTextSelected( false ), - mUpdateInputStyle( false ), - mPasswordInput( false ), - mCheckScrollAmount( false ), - mIsPlaceholderPixelSize( false ), - mIsPlaceholderElideEnabled( false ), - mPlaceholderEllipsisFlag( false ), - mShiftSelectionFlag( true ), - mUpdateAlignment( false ), - mEditingEnabled( true ) + mPreviousState(INACTIVE), + mState(INACTIVE), + mPrimaryCursorPosition(0u), + mLeftSelectionPosition(0u), + mRightSelectionPosition(0u), + mPreEditStartPosition(0u), + mPreEditLength(0u), + mCursorHookPositionX(0.f), + mDoubleTapAction(Controller::NoTextTap::NO_ACTION), + mLongPressAction(Controller::NoTextTap::SHOW_SELECTION_POPUP), + mIsShowingPlaceholderText(false), + mPreEditFlag(false), + mDecoratorUpdated(false), + mCursorBlinkEnabled(true), + mGrabHandleEnabled(true), + mGrabHandlePopupEnabled(true), + mSelectionEnabled(true), + mUpdateCursorHookPosition(false), + mUpdateCursorPosition(false), + mUpdateGrabHandlePosition(false), + mUpdateLeftSelectionPosition(false), + mUpdateRightSelectionPosition(false), + mIsLeftHandleSelected(false), + mIsRightHandleSelected(false), + mUpdateHighlightBox(false), + mScrollAfterUpdatePosition(false), + mScrollAfterDelete(false), + mAllTextSelected(false), + mUpdateInputStyle(false), + mPasswordInput(false), + mCheckScrollAmount(false), + mIsPlaceholderPixelSize(false), + mIsPlaceholderElideEnabled(false), + mPlaceholderEllipsisFlag(false), + mShiftSelectionFlag(true), + mUpdateAlignment(false), + mEditingEnabled(true) { } @@ -156,14 +420,14 @@ bool Controller::Impl::ProcessInputEvents() void Controller::Impl::NotifyInputMethodContext() { - if( mEventData && mEventData->mInputMethodContext ) + if(mEventData && mEventData->mInputMethodContext) { CharacterIndex cursorPosition = GetLogicalCursorPosition(); - const Length numberOfWhiteSpaces = GetNumberOfWhiteSpaces( 0u ); + const Length numberOfWhiteSpaces = GetNumberOfWhiteSpaces(0u); // Update the cursor position by removing the initial white spaces. - if( cursorPosition < numberOfWhiteSpaces ) + if(cursorPosition < numberOfWhiteSpaces) { cursorPosition = 0u; } @@ -172,17 +436,17 @@ void Controller::Impl::NotifyInputMethodContext() cursorPosition -= numberOfWhiteSpaces; } - mEventData->mInputMethodContext.SetCursorPosition( cursorPosition ); + mEventData->mInputMethodContext.SetCursorPosition(cursorPosition); mEventData->mInputMethodContext.NotifyCursorPosition(); } } void Controller::Impl::NotifyInputMethodContextMultiLineStatus() { - if ( mEventData && mEventData->mInputMethodContext ) + if(mEventData && mEventData->mInputMethodContext) { Text::Layout::Engine::Type layout = mLayoutEngine.GetLayout(); - mEventData->mInputMethodContext.NotifyTextInputMultiLine( layout == Text::Layout::Engine::MULTI_LINE_BOX ); + mEventData->mInputMethodContext.NotifyTextInputMultiLine(layout == Text::Layout::Engine::MULTI_LINE_BOX); } } @@ -190,12 +454,12 @@ CharacterIndex Controller::Impl::GetLogicalCursorPosition() const { CharacterIndex cursorPosition = 0u; - if( mEventData ) + if(mEventData) { - if( ( EventData::SELECTING == mEventData->mState ) || - ( EventData::SELECTION_HANDLE_PANNING == mEventData->mState ) ) + if((EventData::SELECTING == mEventData->mState) || + (EventData::SELECTION_HANDLE_PANNING == mEventData->mState)) { - cursorPosition = std::min( mEventData->mRightSelectionPosition, mEventData->mLeftSelectionPosition ); + cursorPosition = std::min(mEventData->mRightSelectionPosition, mEventData->mLeftSelectionPosition); } else { @@ -206,7 +470,7 @@ CharacterIndex Controller::Impl::GetLogicalCursorPosition() const return cursorPosition; } -Length Controller::Impl::GetNumberOfWhiteSpaces( CharacterIndex index ) const +Length Controller::Impl::GetNumberOfWhiteSpaces(CharacterIndex index) const { Length numberOfWhiteSpaces = 0u; @@ -214,9 +478,9 @@ Length Controller::Impl::GetNumberOfWhiteSpaces( CharacterIndex index ) const Character* utf32CharacterBuffer = mModel->mLogicalModel->mText.Begin(); const Length totalNumberOfCharacters = mModel->mLogicalModel->mText.Count(); - for( ; index < totalNumberOfCharacters; ++index, ++numberOfWhiteSpaces ) + for(; index < totalNumberOfCharacters; ++index, ++numberOfWhiteSpaces) { - if( !TextAbstraction::IsWhiteSpace( *( utf32CharacterBuffer + index ) ) ) + if(!TextAbstraction::IsWhiteSpace(*(utf32CharacterBuffer + index))) { break; } @@ -225,30 +489,98 @@ Length Controller::Impl::GetNumberOfWhiteSpaces( CharacterIndex index ) const return numberOfWhiteSpaces; } -void Controller::Impl::GetText( CharacterIndex index, std::string& text ) const +void Controller::Impl::GetText(std::string& text) const +{ + if(!IsShowingPlaceholderText()) + { + // Retrieves the text string. + GetText(0u, text); + } + else + { + DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Controller::GetText %p empty (but showing placeholder)\n", this); + } +} + +void Controller::Impl::GetText(CharacterIndex index, std::string& text) const { // Get the total number of characters. Length numberOfCharacters = mModel->mLogicalModel->mText.Count(); // Retrieve the text. - if( 0u != numberOfCharacters ) + if(0u != numberOfCharacters) + { + Utf32ToUtf8(mModel->mLogicalModel->mText.Begin() + index, numberOfCharacters - index, text); + } +} + +Dali::LayoutDirection::Type Controller::Impl::GetLayoutDirection(Dali::Actor& actor) const +{ + if(mModel->mMatchLayoutDirection == DevelText::MatchLayoutDirection::LOCALE || + (mModel->mMatchLayoutDirection == DevelText::MatchLayoutDirection::INHERIT && !mIsLayoutDirectionChanged)) + { + return static_cast(DevelWindow::Get(actor).GetRootLayer().GetProperty(Dali::Actor::Property::LAYOUT_DIRECTION).Get()); + } + else + { + return static_cast(actor.GetProperty(Dali::Actor::Property::LAYOUT_DIRECTION).Get()); + } +} + +Toolkit::DevelText::TextDirection::Type Controller::Impl::GetTextDirection() +{ + if(mUpdateTextDirection) { - Utf32ToUtf8( mModel->mLogicalModel->mText.Begin() + index, numberOfCharacters - index, text ); + // Operations that can be done only once until the text changes. + const OperationsMask onlyOnceOperations = static_cast(CONVERT_TO_UTF32 | + GET_SCRIPTS | + VALIDATE_FONTS | + GET_LINE_BREAKS | + BIDI_INFO | + SHAPE_TEXT | + GET_GLYPH_METRICS); + + // Set the update info to relayout the whole text. + mTextUpdateInfo.mParagraphCharacterIndex = 0u; + mTextUpdateInfo.mRequestedNumberOfCharacters = mModel->mLogicalModel->mText.Count(); + + // Make sure the model is up-to-date before layouting + UpdateModel(onlyOnceOperations); + + Vector3 naturalSize; + Relayouter::DoRelayout(*this, + Size(MAX_FLOAT, MAX_FLOAT), + static_cast(onlyOnceOperations | + LAYOUT | REORDER | UPDATE_DIRECTION), + naturalSize.GetVectorXY()); + + // Do not do again the only once operations. + mOperationsPending = static_cast(mOperationsPending & ~onlyOnceOperations); + + // Clear the update info. This info will be set the next time the text is updated. + mTextUpdateInfo.Clear(); + + // FullRelayoutNeeded should be true because DoRelayout is MAX_FLOAT, MAX_FLOAT. + mTextUpdateInfo.mFullRelayoutNeeded = true; + + mUpdateTextDirection = false; } + + return mIsTextDirectionRTL ? Toolkit::DevelText::TextDirection::RIGHT_TO_LEFT : Toolkit::DevelText::TextDirection::LEFT_TO_RIGHT; } -void Controller::Impl::CalculateTextUpdateIndices( Length& numberOfCharacters ) +void Controller::Impl::CalculateTextUpdateIndices(Length& numberOfCharacters) { mTextUpdateInfo.mParagraphCharacterIndex = 0u; - mTextUpdateInfo.mStartGlyphIndex = 0u; - mTextUpdateInfo.mStartLineIndex = 0u; - numberOfCharacters = 0u; + mTextUpdateInfo.mStartGlyphIndex = 0u; + mTextUpdateInfo.mStartLineIndex = 0u; + numberOfCharacters = 0u; const Length numberOfParagraphs = mModel->mLogicalModel->mParagraphInfo.Count(); - if( 0u == numberOfParagraphs ) + if(0u == numberOfParagraphs) { mTextUpdateInfo.mParagraphCharacterIndex = 0u; - numberOfCharacters = 0u; + numberOfCharacters = 0u; mTextUpdateInfo.mRequestedNumberOfCharacters = mTextUpdateInfo.mNumberOfCharactersToAdd - mTextUpdateInfo.mNumberOfCharactersToRemove; @@ -258,57 +590,57 @@ void Controller::Impl::CalculateTextUpdateIndices( Length& numberOfCharacters ) // Find the paragraphs to be updated. Vector paragraphsToBeUpdated; - if( mTextUpdateInfo.mCharacterIndex >= mTextUpdateInfo.mPreviousNumberOfCharacters ) + if(mTextUpdateInfo.mCharacterIndex >= mTextUpdateInfo.mPreviousNumberOfCharacters) { // Text is being added at the end of the current text. - if( mTextUpdateInfo.mIsLastCharacterNewParagraph ) + if(mTextUpdateInfo.mIsLastCharacterNewParagraph) { // Text is being added in a new paragraph after the last character of the text. - mTextUpdateInfo.mParagraphCharacterIndex = mTextUpdateInfo.mPreviousNumberOfCharacters; - numberOfCharacters = 0u; + mTextUpdateInfo.mParagraphCharacterIndex = mTextUpdateInfo.mPreviousNumberOfCharacters; + numberOfCharacters = 0u; mTextUpdateInfo.mRequestedNumberOfCharacters = mTextUpdateInfo.mNumberOfCharactersToAdd - mTextUpdateInfo.mNumberOfCharactersToRemove; mTextUpdateInfo.mStartGlyphIndex = mModel->mVisualModel->mGlyphs.Count(); - mTextUpdateInfo.mStartLineIndex = mModel->mVisualModel->mLines.Count() - 1u; + mTextUpdateInfo.mStartLineIndex = mModel->mVisualModel->mLines.Count() - 1u; // Nothing else to do; return; } - paragraphsToBeUpdated.PushBack( numberOfParagraphs - 1u ); + paragraphsToBeUpdated.PushBack(numberOfParagraphs - 1u); } else { Length numberOfCharactersToUpdate = 0u; - if( mTextUpdateInfo.mFullRelayoutNeeded ) + if(mTextUpdateInfo.mFullRelayoutNeeded) { numberOfCharactersToUpdate = mTextUpdateInfo.mPreviousNumberOfCharacters; } else { - numberOfCharactersToUpdate = ( mTextUpdateInfo.mNumberOfCharactersToRemove > 0u ) ? mTextUpdateInfo.mNumberOfCharactersToRemove : 1u; + numberOfCharactersToUpdate = (mTextUpdateInfo.mNumberOfCharactersToRemove > 0u) ? mTextUpdateInfo.mNumberOfCharactersToRemove : 1u; } - mModel->mLogicalModel->FindParagraphs( mTextUpdateInfo.mCharacterIndex, - numberOfCharactersToUpdate, - paragraphsToBeUpdated ); + mModel->mLogicalModel->FindParagraphs(mTextUpdateInfo.mCharacterIndex, + numberOfCharactersToUpdate, + paragraphsToBeUpdated); } - if( 0u != paragraphsToBeUpdated.Count() ) + if(0u != paragraphsToBeUpdated.Count()) { - const ParagraphRunIndex firstParagraphIndex = *( paragraphsToBeUpdated.Begin() ); - const ParagraphRun& firstParagraph = *( mModel->mLogicalModel->mParagraphInfo.Begin() + firstParagraphIndex ); - mTextUpdateInfo.mParagraphCharacterIndex = firstParagraph.characterRun.characterIndex; + const ParagraphRunIndex firstParagraphIndex = *(paragraphsToBeUpdated.Begin()); + const ParagraphRun& firstParagraph = *(mModel->mLogicalModel->mParagraphInfo.Begin() + firstParagraphIndex); + mTextUpdateInfo.mParagraphCharacterIndex = firstParagraph.characterRun.characterIndex; - ParagraphRunIndex lastParagraphIndex = *( paragraphsToBeUpdated.End() - 1u ); - const ParagraphRun& lastParagraph = *( mModel->mLogicalModel->mParagraphInfo.Begin() + lastParagraphIndex ); + ParagraphRunIndex lastParagraphIndex = *(paragraphsToBeUpdated.End() - 1u); + const ParagraphRun& lastParagraph = *(mModel->mLogicalModel->mParagraphInfo.Begin() + lastParagraphIndex); - if( ( mTextUpdateInfo.mNumberOfCharactersToRemove > 0u ) && // Some character are removed. - ( lastParagraphIndex < numberOfParagraphs - 1u ) && // There is a next paragraph. - ( ( lastParagraph.characterRun.characterIndex + lastParagraph.characterRun.numberOfCharacters ) == // The last removed character is the new paragraph character. - ( mTextUpdateInfo.mCharacterIndex + mTextUpdateInfo.mNumberOfCharactersToRemove ) ) ) + if((mTextUpdateInfo.mNumberOfCharactersToRemove > 0u) && // Some character are removed. + (lastParagraphIndex < numberOfParagraphs - 1u) && // There is a next paragraph. + ((lastParagraph.characterRun.characterIndex + lastParagraph.characterRun.numberOfCharacters) == // The last removed character is the new paragraph character. + (mTextUpdateInfo.mCharacterIndex + mTextUpdateInfo.mNumberOfCharactersToRemove))) { // The new paragraph character of the last updated paragraph has been removed so is going to be merged with the next one. - const ParagraphRun& lastParagraph = *( mModel->mLogicalModel->mParagraphInfo.Begin() + lastParagraphIndex + 1u ); + const ParagraphRun& lastParagraph = *(mModel->mLogicalModel->mParagraphInfo.Begin() + lastParagraphIndex + 1u); numberOfCharacters = lastParagraph.characterRun.characterIndex + lastParagraph.characterRun.numberOfCharacters - mTextUpdateInfo.mParagraphCharacterIndex; } @@ -319,863 +651,235 @@ void Controller::Impl::CalculateTextUpdateIndices( Length& numberOfCharacters ) } mTextUpdateInfo.mRequestedNumberOfCharacters = numberOfCharacters + mTextUpdateInfo.mNumberOfCharactersToAdd - mTextUpdateInfo.mNumberOfCharactersToRemove; - mTextUpdateInfo.mStartGlyphIndex = *( mModel->mVisualModel->mCharactersToGlyph.Begin() + mTextUpdateInfo.mParagraphCharacterIndex ); + mTextUpdateInfo.mStartGlyphIndex = *(mModel->mVisualModel->mCharactersToGlyph.Begin() + mTextUpdateInfo.mParagraphCharacterIndex); } -void Controller::Impl::ClearFullModelData( OperationsMask operations ) +void Controller::Impl::ClearModelData(CharacterIndex startIndex, CharacterIndex endIndex, OperationsMask operations) { - if( NO_OPERATION != ( GET_LINE_BREAKS & operations ) ) - { - mModel->mLogicalModel->mLineBreakInfo.Clear(); - mModel->mLogicalModel->mParagraphInfo.Clear(); - } + ControllerImplDataClearer::ClearModelData(*this, startIndex, endIndex, operations); +} + +bool Controller::Impl::UpdateModel(OperationsMask operationsRequired) +{ + return ControllerImplModelUpdater::Update(*this, operationsRequired); +} + +void Controller::Impl::RetrieveDefaultInputStyle(InputStyle& inputStyle) +{ + SetDefaultInputStyle(inputStyle, mFontDefaults, mTextColor); +} - if( NO_OPERATION != ( GET_SCRIPTS & operations ) ) +float Controller::Impl::GetDefaultFontLineHeight() +{ + FontId defaultFontId = 0u; + if(nullptr == mFontDefaults) { - mModel->mLogicalModel->mScriptRuns.Clear(); + TextAbstraction::FontDescription fontDescription; + defaultFontId = mFontClient.GetFontId(fontDescription, TextAbstraction::FontClient::DEFAULT_POINT_SIZE * GetFontSizeScale()); } - - if( NO_OPERATION != ( VALIDATE_FONTS & operations ) ) + else { - mModel->mLogicalModel->mFontRuns.Clear(); + defaultFontId = mFontDefaults->GetFontId(mFontClient, mFontDefaults->mDefaultPointSize * GetFontSizeScale()); } - if( 0u != mModel->mLogicalModel->mBidirectionalParagraphInfo.Count() ) - { - if( NO_OPERATION != ( BIDI_INFO & operations ) ) - { - mModel->mLogicalModel->mBidirectionalParagraphInfo.Clear(); - mModel->mLogicalModel->mCharacterDirections.Clear(); - } + Text::FontMetrics fontMetrics; + mMetrics->GetFontMetrics(defaultFontId, fontMetrics); - if( NO_OPERATION != ( REORDER & operations ) ) - { - // Free the allocated memory used to store the conversion table in the bidirectional line info run. - for( Vector::Iterator it = mModel->mLogicalModel->mBidirectionalLineInfo.Begin(), - endIt = mModel->mLogicalModel->mBidirectionalLineInfo.End(); - it != endIt; - ++it ) - { - BidirectionalLineInfoRun& bidiLineInfo = *it; + return (fontMetrics.ascender - fontMetrics.descender); +} - free( bidiLineInfo.visualToLogicalMap ); - bidiLineInfo.visualToLogicalMap = NULL; - } - mModel->mLogicalModel->mBidirectionalLineInfo.Clear(); - } +bool Controller::Impl::SetDefaultLineSpacing(float lineSpacing) +{ + if(std::fabs(lineSpacing - mLayoutEngine.GetDefaultLineSpacing()) > Math::MACHINE_EPSILON_1000) + { + mLayoutEngine.SetDefaultLineSpacing(lineSpacing); + + RelayoutAllCharacters(); + return true; } + return false; +} - if( NO_OPERATION != ( SHAPE_TEXT & operations ) ) +bool Controller::Impl::SetDefaultLineSize(float lineSize) +{ + if(std::fabs(lineSize - mLayoutEngine.GetDefaultLineSize()) > Math::MACHINE_EPSILON_1000) { - mModel->mVisualModel->mGlyphs.Clear(); - mModel->mVisualModel->mGlyphsToCharacters.Clear(); - mModel->mVisualModel->mCharactersToGlyph.Clear(); - mModel->mVisualModel->mCharactersPerGlyph.Clear(); - mModel->mVisualModel->mGlyphsPerCharacter.Clear(); - mModel->mVisualModel->mGlyphPositions.Clear(); + mLayoutEngine.SetDefaultLineSize(lineSize); + + RelayoutAllCharacters(); + return true; } + return false; +} - if( NO_OPERATION != ( LAYOUT & operations ) ) +bool Controller::Impl::SetRelativeLineSize(float relativeLineSize) +{ + if(std::fabs(relativeLineSize - GetRelativeLineSize()) > Math::MACHINE_EPSILON_1000) { - mModel->mVisualModel->mLines.Clear(); + mLayoutEngine.SetRelativeLineSize(relativeLineSize); + + RelayoutAllCharacters(); + return true; } + return false; +} + +float Controller::Impl::GetRelativeLineSize() +{ + return mLayoutEngine.GetRelativeLineSize(); +} - if( NO_OPERATION != ( COLOR & operations ) ) +string Controller::Impl::GetSelectedText() +{ + string text; + if(EventData::SELECTING == mEventData->mState) { - mModel->mVisualModel->mColorIndices.Clear(); - mModel->mVisualModel->mBackgroundColorIndices.Clear(); + RetrieveSelection(text, false); } + return text; } -void Controller::Impl::ClearCharacterModelData( CharacterIndex startIndex, CharacterIndex endIndex, OperationsMask operations ) +string Controller::Impl::CopyText() { - const CharacterIndex endIndexPlusOne = endIndex + 1u; + string text; + RetrieveSelection(text, false); + SendSelectionToClipboard(false); // Text not modified - if( NO_OPERATION != ( GET_LINE_BREAKS & operations ) ) - { - // Clear the line break info. - LineBreakInfo* lineBreakInfoBuffer = mModel->mLogicalModel->mLineBreakInfo.Begin(); + mEventData->mUpdateCursorPosition = true; - mModel->mLogicalModel->mLineBreakInfo.Erase( lineBreakInfoBuffer + startIndex, - lineBreakInfoBuffer + endIndexPlusOne ); + RequestRelayout(); // Cursor, Handles, Selection Highlight, Popup - // Clear the paragraphs. - ClearCharacterRuns( startIndex, - endIndex, - mModel->mLogicalModel->mParagraphInfo ); - } + return text; +} + +string Controller::Impl::CutText() +{ + string text; + RetrieveSelection(text, false); - if( NO_OPERATION != ( GET_SCRIPTS & operations ) ) + if(!IsEditable()) { - // Clear the scripts. - ClearCharacterRuns( startIndex, - endIndex, - mModel->mLogicalModel->mScriptRuns ); + return EMPTY_STRING; } - if( NO_OPERATION != ( VALIDATE_FONTS & operations ) ) + SendSelectionToClipboard(true); // Synchronous call to modify text + mOperationsPending = ALL_OPERATIONS; + + if((0u != mModel->mLogicalModel->mText.Count()) || + !IsPlaceholderAvailable()) { - // Clear the fonts. - ClearCharacterRuns( startIndex, - endIndex, - mModel->mLogicalModel->mFontRuns ); + QueueModifyEvent(ModifyEvent::TEXT_DELETED); } - - if( 0u != mModel->mLogicalModel->mBidirectionalParagraphInfo.Count() ) + else { - if( NO_OPERATION != ( BIDI_INFO & operations ) ) - { - // Clear the bidirectional paragraph info. - ClearCharacterRuns( startIndex, - endIndex, - mModel->mLogicalModel->mBidirectionalParagraphInfo ); - - // Clear the character's directions. - CharacterDirection* characterDirectionsBuffer = mModel->mLogicalModel->mCharacterDirections.Begin(); - - mModel->mLogicalModel->mCharacterDirections.Erase( characterDirectionsBuffer + startIndex, - characterDirectionsBuffer + endIndexPlusOne ); - } + PlaceholderHandler::ShowPlaceholderText(*this); + } - if( NO_OPERATION != ( REORDER & operations ) ) - { - uint32_t startRemoveIndex = mModel->mLogicalModel->mBidirectionalLineInfo.Count(); - uint32_t endRemoveIndex = startRemoveIndex; - ClearCharacterRuns( startIndex, - endIndex, - mModel->mLogicalModel->mBidirectionalLineInfo, - startRemoveIndex, - endRemoveIndex ); - - BidirectionalLineInfoRun* bidirectionalLineInfoBuffer = mModel->mLogicalModel->mBidirectionalLineInfo.Begin(); - - // Free the allocated memory used to store the conversion table in the bidirectional line info run. - for( Vector::Iterator it = bidirectionalLineInfoBuffer + startRemoveIndex, - endIt = bidirectionalLineInfoBuffer + endRemoveIndex; - it != endIt; - ++it ) - { - BidirectionalLineInfoRun& bidiLineInfo = *it; + mEventData->mUpdateCursorPosition = true; + mEventData->mScrollAfterDelete = true; - free( bidiLineInfo.visualToLogicalMap ); - bidiLineInfo.visualToLogicalMap = NULL; - } + RequestRelayout(); - mModel->mLogicalModel->mBidirectionalLineInfo.Erase( bidirectionalLineInfoBuffer + startRemoveIndex, - bidirectionalLineInfoBuffer + endRemoveIndex ); - } + if(nullptr != mEditableControlInterface) + { + mEditableControlInterface->TextChanged(true); } + return text; } -void Controller::Impl::ClearGlyphModelData( CharacterIndex startIndex, CharacterIndex endIndex, OperationsMask operations ) +void Controller::Impl::SetTextSelectionRange(const uint32_t* pStart, const uint32_t* pEnd) { - const CharacterIndex endIndexPlusOne = endIndex + 1u; - const Length numberOfCharactersRemoved = endIndexPlusOne - startIndex; - - // Convert the character index to glyph index before deleting the character to glyph and the glyphs per character buffers. - GlyphIndex* charactersToGlyphBuffer = mModel->mVisualModel->mCharactersToGlyph.Begin(); - Length* glyphsPerCharacterBuffer = mModel->mVisualModel->mGlyphsPerCharacter.Begin(); - - const GlyphIndex endGlyphIndexPlusOne = *( charactersToGlyphBuffer + endIndex ) + *( glyphsPerCharacterBuffer + endIndex ); - const Length numberOfGlyphsRemoved = endGlyphIndexPlusOne - mTextUpdateInfo.mStartGlyphIndex; - - if( NO_OPERATION != ( SHAPE_TEXT & operations ) ) - { - // Update the character to glyph indices. - for( Vector::Iterator it = charactersToGlyphBuffer + endIndexPlusOne, - endIt = charactersToGlyphBuffer + mModel->mVisualModel->mCharactersToGlyph.Count(); - it != endIt; - ++it ) - { - CharacterIndex& index = *it; - index -= numberOfGlyphsRemoved; - } - - // Clear the character to glyph conversion table. - mModel->mVisualModel->mCharactersToGlyph.Erase( charactersToGlyphBuffer + startIndex, - charactersToGlyphBuffer + endIndexPlusOne ); - - // Clear the glyphs per character table. - mModel->mVisualModel->mGlyphsPerCharacter.Erase( glyphsPerCharacterBuffer + startIndex, - glyphsPerCharacterBuffer + endIndexPlusOne ); - - // Clear the glyphs buffer. - GlyphInfo* glyphsBuffer = mModel->mVisualModel->mGlyphs.Begin(); - mModel->mVisualModel->mGlyphs.Erase( glyphsBuffer + mTextUpdateInfo.mStartGlyphIndex, - glyphsBuffer + endGlyphIndexPlusOne ); - - CharacterIndex* glyphsToCharactersBuffer = mModel->mVisualModel->mGlyphsToCharacters.Begin(); - - // Update the glyph to character indices. - for( Vector::Iterator it = glyphsToCharactersBuffer + endGlyphIndexPlusOne, - endIt = glyphsToCharactersBuffer + mModel->mVisualModel->mGlyphsToCharacters.Count(); - it != endIt; - ++it ) - { - CharacterIndex& index = *it; - index -= numberOfCharactersRemoved; - } - - // Clear the glyphs to characters buffer. - mModel->mVisualModel->mGlyphsToCharacters.Erase( glyphsToCharactersBuffer + mTextUpdateInfo.mStartGlyphIndex, - glyphsToCharactersBuffer + endGlyphIndexPlusOne ); - - // Clear the characters per glyph buffer. - Length* charactersPerGlyphBuffer = mModel->mVisualModel->mCharactersPerGlyph.Begin(); - mModel->mVisualModel->mCharactersPerGlyph.Erase( charactersPerGlyphBuffer + mTextUpdateInfo.mStartGlyphIndex, - charactersPerGlyphBuffer + endGlyphIndexPlusOne ); - - // Clear the positions buffer. - Vector2* positionsBuffer = mModel->mVisualModel->mGlyphPositions.Begin(); - mModel->mVisualModel->mGlyphPositions.Erase( positionsBuffer + mTextUpdateInfo.mStartGlyphIndex, - positionsBuffer + endGlyphIndexPlusOne ); - } - - if( NO_OPERATION != ( LAYOUT & operations ) ) - { - // Clear the lines. - uint32_t startRemoveIndex = mModel->mVisualModel->mLines.Count(); - uint32_t endRemoveIndex = startRemoveIndex; - ClearCharacterRuns( startIndex, - endIndex, - mModel->mVisualModel->mLines, - startRemoveIndex, - endRemoveIndex ); - - // Will update the glyph runs. - startRemoveIndex = mModel->mVisualModel->mLines.Count(); - endRemoveIndex = startRemoveIndex; - ClearGlyphRuns( mTextUpdateInfo.mStartGlyphIndex, - endGlyphIndexPlusOne - 1u, - mModel->mVisualModel->mLines, - startRemoveIndex, - endRemoveIndex ); - - // Set the line index from where to insert the new laid-out lines. - mTextUpdateInfo.mStartLineIndex = startRemoveIndex; - - LineRun* linesBuffer = mModel->mVisualModel->mLines.Begin(); - mModel->mVisualModel->mLines.Erase( linesBuffer + startRemoveIndex, - linesBuffer + endRemoveIndex ); - } - - if( NO_OPERATION != ( COLOR & operations ) ) - { - if( 0u != mModel->mVisualModel->mColorIndices.Count() ) - { - ColorIndex* colorIndexBuffer = mModel->mVisualModel->mColorIndices.Begin(); - mModel->mVisualModel->mColorIndices.Erase( colorIndexBuffer + mTextUpdateInfo.mStartGlyphIndex, - colorIndexBuffer + endGlyphIndexPlusOne ); - } - - if( 0u != mModel->mVisualModel->mBackgroundColorIndices.Count() ) - { - ColorIndex* backgroundColorIndexBuffer = mModel->mVisualModel->mBackgroundColorIndices.Begin(); - mModel->mVisualModel->mBackgroundColorIndices.Erase( backgroundColorIndexBuffer + mTextUpdateInfo.mStartGlyphIndex, - backgroundColorIndexBuffer + endGlyphIndexPlusOne ); - } - } -} - -void Controller::Impl::ClearModelData( CharacterIndex startIndex, CharacterIndex endIndex, OperationsMask operations ) -{ - if( mTextUpdateInfo.mClearAll || - ( ( 0u == startIndex ) && - ( mTextUpdateInfo.mPreviousNumberOfCharacters == endIndex + 1u ) ) ) - { - ClearFullModelData( operations ); - } - else - { - // Clear the model data related with characters. - ClearCharacterModelData( startIndex, endIndex, operations ); - - // Clear the model data related with glyphs. - ClearGlyphModelData( startIndex, endIndex, operations ); - } - - // The estimated number of lines. Used to avoid reallocations when layouting. - mTextUpdateInfo.mEstimatedNumberOfLines = std::max( mModel->mVisualModel->mLines.Count(), mModel->mLogicalModel->mParagraphInfo.Count() ); - - mModel->mVisualModel->ClearCaches(); -} - -bool Controller::Impl::UpdateModel( OperationsMask operationsRequired ) -{ - DALI_LOG_INFO( gLogFilter, Debug::General, "Controller::UpdateModel\n" ); - - // Calculate the operations to be done. - const OperationsMask operations = static_cast( mOperationsPending & operationsRequired ); - - if( NO_OPERATION == operations ) - { - // Nothing to do if no operations are pending and required. - return false; - } - - Vector& srcCharacters = mModel->mLogicalModel->mText; - Vector displayCharacters; - bool useHiddenText = false; - if ( mHiddenInput && mEventData != NULL && !mEventData->mIsShowingPlaceholderText) - { - mHiddenInput->Substitute( srcCharacters,displayCharacters ); - useHiddenText = true; - } - - Vector& utf32Characters = useHiddenText ? displayCharacters : srcCharacters; - const Length numberOfCharacters = utf32Characters.Count(); - - // Index to the first character of the first paragraph to be updated. - CharacterIndex startIndex = 0u; - // Number of characters of the paragraphs to be removed. - Length paragraphCharacters = 0u; - - CalculateTextUpdateIndices( paragraphCharacters ); - - // Check whether the indices for updating the text is valid - if ( numberOfCharacters > 0u && - ( mTextUpdateInfo.mParagraphCharacterIndex > numberOfCharacters || - mTextUpdateInfo.mRequestedNumberOfCharacters > numberOfCharacters ) ) - { - std::string currentText; - Utf32ToUtf8( mModel->mLogicalModel->mText.Begin(), numberOfCharacters, currentText ); - - DALI_LOG_ERROR( "Controller::Impl::UpdateModel: mTextUpdateInfo has invalid indices\n" ); - DALI_LOG_ERROR( "Number of characters: %d, current text is: %s\n", numberOfCharacters, currentText.c_str() ); - - // Dump mTextUpdateInfo - DALI_LOG_ERROR( "Dump mTextUpdateInfo:\n" ); - DALI_LOG_ERROR( " mTextUpdateInfo.mCharacterIndex = %u\n", mTextUpdateInfo.mCharacterIndex ); - DALI_LOG_ERROR( " mTextUpdateInfo.mNumberOfCharactersToRemove = %u\n", mTextUpdateInfo.mNumberOfCharactersToRemove ); - DALI_LOG_ERROR( " mTextUpdateInfo.mNumberOfCharactersToAdd = %u\n", mTextUpdateInfo.mNumberOfCharactersToAdd ); - DALI_LOG_ERROR( " mTextUpdateInfo.mPreviousNumberOfCharacters = %u\n", mTextUpdateInfo.mPreviousNumberOfCharacters ); - DALI_LOG_ERROR( " mTextUpdateInfo.mParagraphCharacterIndex = %u\n", mTextUpdateInfo.mParagraphCharacterIndex ); - DALI_LOG_ERROR( " mTextUpdateInfo.mRequestedNumberOfCharacters = %u\n", mTextUpdateInfo.mRequestedNumberOfCharacters ); - DALI_LOG_ERROR( " mTextUpdateInfo.mStartGlyphIndex = %u\n", mTextUpdateInfo.mStartGlyphIndex ); - DALI_LOG_ERROR( " mTextUpdateInfo.mStartLineIndex = %u\n", mTextUpdateInfo.mStartLineIndex ); - DALI_LOG_ERROR( " mTextUpdateInfo.mEstimatedNumberOfLines = %u\n", mTextUpdateInfo.mEstimatedNumberOfLines ); - DALI_LOG_ERROR( " mTextUpdateInfo.mClearAll = %d\n", mTextUpdateInfo.mClearAll ); - DALI_LOG_ERROR( " mTextUpdateInfo.mFullRelayoutNeeded = %d\n", mTextUpdateInfo.mFullRelayoutNeeded ); - DALI_LOG_ERROR( " mTextUpdateInfo.mIsLastCharacterNewParagraph = %d\n", mTextUpdateInfo.mIsLastCharacterNewParagraph ); - - return false; - } - - startIndex = mTextUpdateInfo.mParagraphCharacterIndex; - - if( mTextUpdateInfo.mClearAll || - ( 0u != paragraphCharacters ) ) - { - ClearModelData( startIndex, startIndex + ( ( paragraphCharacters > 0u ) ? paragraphCharacters - 1u : 0u ), operations ); - } - - mTextUpdateInfo.mClearAll = false; - - // Whether the model is updated. - bool updated = false; - - Vector& lineBreakInfo = mModel->mLogicalModel->mLineBreakInfo; - const Length requestedNumberOfCharacters = mTextUpdateInfo.mRequestedNumberOfCharacters; - - if( NO_OPERATION != ( GET_LINE_BREAKS & operations ) ) - { - // Retrieves the line break info. The line break info is used to split the text in 'paragraphs' to - // calculate the bidirectional info for each 'paragraph'. - // It's also used to layout the text (where it should be a new line) or to shape the text (text in different lines - // is not shaped together). - lineBreakInfo.Resize( numberOfCharacters, TextAbstraction::LINE_NO_BREAK ); - - SetLineBreakInfo( utf32Characters, - startIndex, - requestedNumberOfCharacters, - lineBreakInfo ); - - // Create the paragraph info. - mModel->mLogicalModel->CreateParagraphInfo( startIndex, - requestedNumberOfCharacters ); - updated = true; - } - - const bool getScripts = NO_OPERATION != ( GET_SCRIPTS & operations ); - const bool validateFonts = NO_OPERATION != ( VALIDATE_FONTS & operations ); - - Vector& scripts = mModel->mLogicalModel->mScriptRuns; - Vector& validFonts = mModel->mLogicalModel->mFontRuns; - - if( getScripts || validateFonts ) - { - // Validates the fonts assigned by the application or assigns default ones. - // It makes sure all the characters are going to be rendered by the correct font. - MultilanguageSupport multilanguageSupport = MultilanguageSupport::Get(); - - if( getScripts ) - { - // Retrieves the scripts used in the text. - multilanguageSupport.SetScripts( utf32Characters, - startIndex, - requestedNumberOfCharacters, - scripts ); - } - - if( validateFonts ) - { - // Validate the fonts set through the mark-up string. - Vector& fontDescriptionRuns = mModel->mLogicalModel->mFontDescriptionRuns; - - // Get the default font's description. - TextAbstraction::FontDescription defaultFontDescription; - TextAbstraction::PointSize26Dot6 defaultPointSize = TextAbstraction::FontClient::DEFAULT_POINT_SIZE * mFontSizeScale; - - if( IsShowingPlaceholderText() && mEventData && ( NULL != mEventData->mPlaceholderFont ) ) - { - // If the placeholder font is set specifically, only placeholder font is changed. - defaultFontDescription = mEventData->mPlaceholderFont->mFontDescription; - if( mEventData->mPlaceholderFont->sizeDefined ) - { - defaultPointSize = mEventData->mPlaceholderFont->mDefaultPointSize * mFontSizeScale * 64u; - } - } - else if( NULL != mFontDefaults ) - { - // Set the normal font and the placeholder font. - defaultFontDescription = mFontDefaults->mFontDescription; - - if( mTextFitEnabled ) - { - defaultPointSize = mFontDefaults->mFitPointSize * 64u; - } - else - { - defaultPointSize = mFontDefaults->mDefaultPointSize * mFontSizeScale * 64u; - } - } - - // Validates the fonts. If there is a character with no assigned font it sets a default one. - // After this call, fonts are validated. - multilanguageSupport.ValidateFonts( utf32Characters, - scripts, - fontDescriptionRuns, - defaultFontDescription, - defaultPointSize, - startIndex, - requestedNumberOfCharacters, - validFonts ); - } - updated = true; - } - - Vector mirroredUtf32Characters; - bool textMirrored = false; - const Length numberOfParagraphs = mModel->mLogicalModel->mParagraphInfo.Count(); - if( NO_OPERATION != ( BIDI_INFO & operations ) ) - { - Vector& bidirectionalInfo = mModel->mLogicalModel->mBidirectionalParagraphInfo; - bidirectionalInfo.Reserve( numberOfParagraphs ); - - // Calculates the bidirectional info for the whole paragraph if it contains right to left scripts. - SetBidirectionalInfo( utf32Characters, - scripts, - lineBreakInfo, - startIndex, - requestedNumberOfCharacters, - bidirectionalInfo, - mModel->mMatchSystemLanguageDirection, - mLayoutDirection ); - - if( 0u != bidirectionalInfo.Count() ) - { - // Only set the character directions if there is right to left characters. - Vector& directions = mModel->mLogicalModel->mCharacterDirections; - GetCharactersDirection( bidirectionalInfo, - numberOfCharacters, - startIndex, - requestedNumberOfCharacters, - directions ); - - // This paragraph has right to left text. Some characters may need to be mirrored. - // TODO: consider if the mirrored string can be stored as well. - - textMirrored = GetMirroredText( utf32Characters, - directions, - bidirectionalInfo, - startIndex, - requestedNumberOfCharacters, - mirroredUtf32Characters ); - } - else - { - // There is no right to left characters. Clear the directions vector. - mModel->mLogicalModel->mCharacterDirections.Clear(); - } - updated = true; - } - - Vector& glyphs = mModel->mVisualModel->mGlyphs; - Vector& glyphsToCharactersMap = mModel->mVisualModel->mGlyphsToCharacters; - Vector& charactersPerGlyph = mModel->mVisualModel->mCharactersPerGlyph; - Vector newParagraphGlyphs; - newParagraphGlyphs.Reserve( numberOfParagraphs ); - - const Length currentNumberOfGlyphs = glyphs.Count(); - if( NO_OPERATION != ( SHAPE_TEXT & operations ) ) - { - const Vector& textToShape = textMirrored ? mirroredUtf32Characters : utf32Characters; - // Shapes the text. - ShapeText( textToShape, - lineBreakInfo, - scripts, - validFonts, - startIndex, - mTextUpdateInfo.mStartGlyphIndex, - requestedNumberOfCharacters, - glyphs, - glyphsToCharactersMap, - charactersPerGlyph, - newParagraphGlyphs ); - - // Create the 'number of glyphs' per character and the glyph to character conversion tables. - mModel->mVisualModel->CreateGlyphsPerCharacterTable( startIndex, mTextUpdateInfo.mStartGlyphIndex, requestedNumberOfCharacters ); - mModel->mVisualModel->CreateCharacterToGlyphTable( startIndex, mTextUpdateInfo.mStartGlyphIndex, requestedNumberOfCharacters ); - updated = true; - } - - const Length numberOfGlyphs = glyphs.Count() - currentNumberOfGlyphs; - - if( NO_OPERATION != ( GET_GLYPH_METRICS & operations ) ) - { - GlyphInfo* glyphsBuffer = glyphs.Begin(); - mMetrics->GetGlyphMetrics( glyphsBuffer + mTextUpdateInfo.mStartGlyphIndex, numberOfGlyphs ); - - // Update the width and advance of all new paragraph characters. - for( Vector::ConstIterator it = newParagraphGlyphs.Begin(), endIt = newParagraphGlyphs.End(); it != endIt; ++it ) - { - const GlyphIndex index = *it; - GlyphInfo& glyph = *( glyphsBuffer + index ); - - glyph.xBearing = 0.f; - glyph.width = 0.f; - glyph.advance = 0.f; - } - updated = true; - } - - if( ( NULL != mEventData ) && - mEventData->mPreEditFlag && - ( 0u != mModel->mVisualModel->mCharactersToGlyph.Count() ) ) - { - Dali::InputMethodContext::PreEditAttributeDataContainer attrs; - mEventData->mInputMethodContext.GetPreeditStyle( attrs ); - Dali::InputMethodContext::PreeditStyle type = Dali::InputMethodContext::PreeditStyle::NONE; - - // Check the type of preedit and run it. - for( Dali::InputMethodContext::PreEditAttributeDataContainer::Iterator it = attrs.Begin(), endIt = attrs.End(); it != endIt; it++ ) - { - Dali::InputMethodContext::PreeditAttributeData attrData = *it; - DALI_LOG_INFO( gLogFilter, Debug::General, "Controller::UpdateModel PreeditStyle type : %d start %d end %d \n", attrData.preeditType, attrData.startIndex, attrData.endIndex ); - type = attrData.preeditType; - - // Check the number of commit characters for the start position. - unsigned int numberOfCommit = mEventData->mPrimaryCursorPosition - mEventData->mPreEditLength; - Length numberOfIndices = attrData.endIndex - attrData.startIndex; - - switch( type ) - { - case Dali::InputMethodContext::PreeditStyle::UNDERLINE: - { - // Add the underline for the pre-edit text. - GlyphRun underlineRun; - underlineRun.glyphIndex = attrData.startIndex + numberOfCommit; - underlineRun.numberOfGlyphs = numberOfIndices; - mModel->mVisualModel->mUnderlineRuns.PushBack( underlineRun ); - break; - } - case Dali::InputMethodContext::PreeditStyle::REVERSE: - { - Vector4 textColor = mModel->mVisualModel->GetTextColor(); - ColorRun backgroundColorRun; - backgroundColorRun.characterRun.characterIndex = attrData.startIndex + numberOfCommit; - backgroundColorRun.characterRun.numberOfCharacters = numberOfIndices; - backgroundColorRun.color = textColor; - mModel->mLogicalModel->mBackgroundColorRuns.PushBack( backgroundColorRun ); - - Vector4 backgroundColor = mModel->mVisualModel->GetBackgroundColor(); - Vector colorRuns; - colorRuns.Resize( 1u ); - ColorRun& colorRun = *( colorRuns.Begin() ); - colorRun.color = backgroundColor; - colorRun.characterRun.characterIndex = attrData.startIndex + numberOfCommit; - colorRun.characterRun.numberOfCharacters = numberOfIndices; - - mModel->mLogicalModel->mColorRuns.PushBack( colorRun ); - break; - } - case Dali::InputMethodContext::PreeditStyle::HIGHLIGHT: - { - ColorRun backgroundColorRun; - backgroundColorRun.characterRun.characterIndex = attrData.startIndex + numberOfCommit; - backgroundColorRun.characterRun.numberOfCharacters = numberOfIndices; - backgroundColorRun.color = LIGHT_BLUE; - mModel->mLogicalModel->mBackgroundColorRuns.PushBack( backgroundColorRun ); - break; - } - case Dali::InputMethodContext::PreeditStyle::CUSTOM_PLATFORM_STYLE_1: - { - // CUSTOM_PLATFORM_STYLE_1 should be drawn with background and underline together. - ColorRun backgroundColorRun; - backgroundColorRun.characterRun.characterIndex = attrData.startIndex + numberOfCommit; - backgroundColorRun.characterRun.numberOfCharacters = numberOfIndices; - backgroundColorRun.color = BACKGROUND_SUB4; - mModel->mLogicalModel->mBackgroundColorRuns.PushBack( backgroundColorRun ); - - GlyphRun underlineRun; - underlineRun.glyphIndex = attrData.startIndex + numberOfCommit; - underlineRun.numberOfGlyphs = numberOfIndices; - mModel->mVisualModel->mUnderlineRuns.PushBack( underlineRun ); - break; - } - case Dali::InputMethodContext::PreeditStyle::CUSTOM_PLATFORM_STYLE_2: - { - // CUSTOM_PLATFORM_STYLE_2 should be drawn with background and underline together. - ColorRun backgroundColorRun; - backgroundColorRun.characterRun.characterIndex = attrData.startIndex + numberOfCommit; - backgroundColorRun.characterRun.numberOfCharacters = numberOfIndices; - backgroundColorRun.color = BACKGROUND_SUB5; - mModel->mLogicalModel->mBackgroundColorRuns.PushBack( backgroundColorRun ); - - GlyphRun underlineRun; - underlineRun.glyphIndex = attrData.startIndex + numberOfCommit; - underlineRun.numberOfGlyphs = numberOfIndices; - mModel->mVisualModel->mUnderlineRuns.PushBack( underlineRun ); - break; - } - case Dali::InputMethodContext::PreeditStyle::CUSTOM_PLATFORM_STYLE_3: - { - // CUSTOM_PLATFORM_STYLE_3 should be drawn with background and underline together. - ColorRun backgroundColorRun; - backgroundColorRun.characterRun.characterIndex = attrData.startIndex + numberOfCommit; - backgroundColorRun.characterRun.numberOfCharacters = numberOfIndices; - backgroundColorRun.color = BACKGROUND_SUB6; - mModel->mLogicalModel->mBackgroundColorRuns.PushBack( backgroundColorRun ); - - GlyphRun underlineRun; - underlineRun.glyphIndex = attrData.startIndex + numberOfCommit; - underlineRun.numberOfGlyphs = numberOfIndices; - mModel->mVisualModel->mUnderlineRuns.PushBack( underlineRun ); - break; - } - case Dali::InputMethodContext::PreeditStyle::CUSTOM_PLATFORM_STYLE_4: - { - // CUSTOM_PLATFORM_STYLE_4 should be drawn with background and underline together. - ColorRun backgroundColorRun; - backgroundColorRun.characterRun.characterIndex = attrData.startIndex + numberOfCommit; - backgroundColorRun.characterRun.numberOfCharacters = numberOfIndices; - backgroundColorRun.color = BACKGROUND_SUB7; - mModel->mLogicalModel->mBackgroundColorRuns.PushBack( backgroundColorRun ); - - GlyphRun underlineRun; - underlineRun.glyphIndex = attrData.startIndex + numberOfCommit; - underlineRun.numberOfGlyphs = numberOfIndices; - mModel->mVisualModel->mUnderlineRuns.PushBack( underlineRun ); - break; - } - case Dali::InputMethodContext::PreeditStyle::NONE: - default: - { - break; - } - } - } - attrs.Clear(); - updated = true; - } - - if( NO_OPERATION != ( COLOR & operations ) ) - { - // Set the color runs in glyphs. - SetColorSegmentationInfo( mModel->mLogicalModel->mColorRuns, - mModel->mVisualModel->mCharactersToGlyph, - mModel->mVisualModel->mGlyphsPerCharacter, - startIndex, - mTextUpdateInfo.mStartGlyphIndex, - requestedNumberOfCharacters, - mModel->mVisualModel->mColors, - mModel->mVisualModel->mColorIndices ); - - // Set the background color runs in glyphs. - SetColorSegmentationInfo( mModel->mLogicalModel->mBackgroundColorRuns, - mModel->mVisualModel->mCharactersToGlyph, - mModel->mVisualModel->mGlyphsPerCharacter, - startIndex, - mTextUpdateInfo.mStartGlyphIndex, - requestedNumberOfCharacters, - mModel->mVisualModel->mBackgroundColors, - mModel->mVisualModel->mBackgroundColorIndices ); - - updated = true; - } - - - // The estimated number of lines. Used to avoid reallocations when layouting. - mTextUpdateInfo.mEstimatedNumberOfLines = std::max( mModel->mVisualModel->mLines.Count(), mModel->mLogicalModel->mParagraphInfo.Count() ); - - // Set the previous number of characters for the next time the text is updated. - mTextUpdateInfo.mPreviousNumberOfCharacters = numberOfCharacters; - - return updated; -} - -void Controller::Impl::RetrieveDefaultInputStyle( InputStyle& inputStyle ) -{ - // Sets the default text's color. - inputStyle.textColor = mTextColor; - inputStyle.isDefaultColor = true; - - inputStyle.familyName.clear(); - inputStyle.weight = TextAbstraction::FontWeight::NORMAL; - inputStyle.width = TextAbstraction::FontWidth::NORMAL; - inputStyle.slant = TextAbstraction::FontSlant::NORMAL; - inputStyle.size = 0.f; - - inputStyle.lineSpacing = 0.f; - - inputStyle.underlineProperties.clear(); - inputStyle.shadowProperties.clear(); - inputStyle.embossProperties.clear(); - inputStyle.outlineProperties.clear(); - - inputStyle.isFamilyDefined = false; - inputStyle.isWeightDefined = false; - inputStyle.isWidthDefined = false; - inputStyle.isSlantDefined = false; - inputStyle.isSizeDefined = false; - - inputStyle.isLineSpacingDefined = false; - - inputStyle.isUnderlineDefined = false; - inputStyle.isShadowDefined = false; - inputStyle.isEmbossDefined = false; - inputStyle.isOutlineDefined = false; - - // Sets the default font's family name, weight, width, slant and size. - if( mFontDefaults ) - { - if( mFontDefaults->familyDefined ) - { - inputStyle.familyName = mFontDefaults->mFontDescription.family; - inputStyle.isFamilyDefined = true; - } - - if( mFontDefaults->weightDefined ) - { - inputStyle.weight = mFontDefaults->mFontDescription.weight; - inputStyle.isWeightDefined = true; - } - - if( mFontDefaults->widthDefined ) - { - inputStyle.width = mFontDefaults->mFontDescription.width; - inputStyle.isWidthDefined = true; - } - - if( mFontDefaults->slantDefined ) - { - inputStyle.slant = mFontDefaults->mFontDescription.slant; - inputStyle.isSlantDefined = true; - } - - if( mFontDefaults->sizeDefined ) - { - inputStyle.size = mFontDefaults->mDefaultPointSize; - inputStyle.isSizeDefined = true; - } - } -} - -float Controller::Impl::GetDefaultFontLineHeight() -{ - FontId defaultFontId = 0u; - if( NULL == mFontDefaults ) - { - TextAbstraction::FontDescription fontDescription; - defaultFontId = mFontClient.GetFontId( fontDescription, TextAbstraction::FontClient::DEFAULT_POINT_SIZE * mFontSizeScale ); - } - else - { - defaultFontId = mFontDefaults->GetFontId( mFontClient, mFontDefaults->mDefaultPointSize * mFontSizeScale ); - } - - Text::FontMetrics fontMetrics; - mMetrics->GetFontMetrics( defaultFontId, fontMetrics ); - - return( fontMetrics.ascender - fontMetrics.descender ); -} - -void Controller::Impl::SetTextSelectionRange(const uint32_t *pStart, const uint32_t *pEnd) -{ - if( nullptr == mEventData ) + if(nullptr == mEventData) { // Nothing to do if there is no text. return; } - if( mEventData->mSelectionEnabled && (pStart || pEnd)) + if(mEventData->mSelectionEnabled && (pStart || pEnd)) { - uint32_t length = static_cast(mModel->mLogicalModel->mText.Count()); + uint32_t length = static_cast(mModel->mLogicalModel->mText.Count()); + uint32_t oldStart = mEventData->mLeftSelectionPosition; + uint32_t oldEnd = mEventData->mRightSelectionPosition; - if (pStart) + if(pStart) { mEventData->mLeftSelectionPosition = std::min(*pStart, length); } - if (pEnd) + if(pEnd) { mEventData->mRightSelectionPosition = std::min(*pEnd, length); } - if (mEventData->mLeftSelectionPosition == mEventData->mRightSelectionPosition) + if(mEventData->mLeftSelectionPosition == mEventData->mRightSelectionPosition) { - ChangeState( EventData::EDITING ); + ChangeState(EventData::EDITING); mEventData->mPrimaryCursorPosition = mEventData->mLeftSelectionPosition = mEventData->mRightSelectionPosition; - mEventData->mUpdateCursorPosition = true; + mEventData->mUpdateCursorPosition = true; } else { - ChangeState( EventData::SELECTING ); - mEventData->mUpdateHighlightBox = true; - mEventData->mUpdateLeftSelectionPosition = true; + ChangeState(EventData::SELECTING); + mEventData->mUpdateHighlightBox = true; + mEventData->mUpdateLeftSelectionPosition = true; mEventData->mUpdateRightSelectionPosition = true; } + + if(mSelectableControlInterface != nullptr) + { + mSelectableControlInterface->SelectionChanged(oldStart, oldEnd, mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition); + } } } CharacterIndex Controller::Impl::GetPrimaryCursorPosition() const { - if( nullptr == mEventData ) + if(nullptr == mEventData) { return 0; } return mEventData->mPrimaryCursorPosition; } -bool Controller::Impl::SetPrimaryCursorPosition( CharacterIndex index ) +bool Controller::Impl::SetPrimaryCursorPosition(CharacterIndex index, bool focused) { - if( nullptr == mEventData ) + if(nullptr == mEventData) { // Nothing to do if there is no text. return false; } - if( mEventData->mPrimaryCursorPosition == index ) + if(mEventData->mPrimaryCursorPosition == index && mEventData->mState != EventData::SELECTING) { // Nothing for same cursor position. return false; } - uint32_t length = static_cast(mModel->mLogicalModel->mText.Count()); + uint32_t length = static_cast(mModel->mLogicalModel->mText.Count()); + uint32_t oldCursorPos = mEventData->mPrimaryCursorPosition; mEventData->mPrimaryCursorPosition = std::min(index, length); - ChangeState( EventData::EDITING ); - mEventData->mLeftSelectionPosition = mEventData->mRightSelectionPosition = mEventData->mPrimaryCursorPosition; - mEventData->mUpdateCursorPosition = true; - ScrollTextToMatchCursor(); + // If there is no focus, only the value is updated. + if(focused) + { + bool wasInSelectingState = mEventData->mState == EventData::SELECTING; + uint32_t oldStart = mEventData->mLeftSelectionPosition; + uint32_t oldEnd = mEventData->mRightSelectionPosition; + ChangeState(EventData::EDITING); + mEventData->mLeftSelectionPosition = mEventData->mRightSelectionPosition = mEventData->mPrimaryCursorPosition; + mEventData->mUpdateCursorPosition = true; + + if(mSelectableControlInterface != nullptr && wasInSelectingState) + { + mSelectableControlInterface->SelectionChanged(oldStart, oldEnd, mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition); + } + + ScrollTextToMatchCursor(); + } + + if(nullptr != mEditableControlInterface) + { + mEditableControlInterface->CursorPositionChanged(oldCursorPos, mEventData->mPrimaryCursorPosition); + } + return true; } @@ -1183,9 +887,9 @@ Uint32Pair Controller::Impl::GetTextSelectionRange() const { Uint32Pair range; - if( mEventData ) + if(mEventData) { - range.first = mEventData->mLeftSelectionPosition; + range.first = mEventData->mLeftSelectionPosition; range.second = mEventData->mRightSelectionPosition; } @@ -1197,17 +901,37 @@ bool Controller::Impl::IsEditable() const return mEventData && mEventData->mEditingEnabled; } -void Controller::Impl::SetEditable( bool editable ) +void Controller::Impl::SetEditable(bool editable) { - if( mEventData) + if(mEventData) { mEventData->mEditingEnabled = editable; + + if(mEventData->mDecorator) + { + mEventData->mDecorator->SetEditable(editable); + } } } -void Controller::Impl::RetrieveSelection( std::string& selectedText, bool deleteAfterRetrieval ) +void Controller::Impl::UpdateAfterFontChange(const std::string& newDefaultFont) { - if( mEventData->mLeftSelectionPosition == mEventData->mRightSelectionPosition ) + DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Controller::UpdateAfterFontChange\n"); + + if(!mFontDefaults->familyDefined) // If user defined font then should not update when system font changes + { + DALI_LOG_INFO(gLogFilter, Debug::Concise, "Controller::UpdateAfterFontChange newDefaultFont(%s)\n", newDefaultFont.c_str()); + mFontDefaults->mFontDescription.family = newDefaultFont; + + ClearFontData(); + + RequestRelayout(); + } +} + +void Controller::Impl::RetrieveSelection(std::string& selectedText, bool deleteAfterRetrieval) +{ + if(mEventData->mLeftSelectionPosition == mEventData->mRightSelectionPosition) { // Nothing to select if handles are in the same place. selectedText.clear(); @@ -1217,57 +941,57 @@ void Controller::Impl::RetrieveSelection( std::string& selectedText, bool delete const bool handlesCrossed = mEventData->mLeftSelectionPosition > mEventData->mRightSelectionPosition; //Get start and end position of selection - const CharacterIndex startOfSelectedText = handlesCrossed ? mEventData->mRightSelectionPosition : mEventData->mLeftSelectionPosition; - const Length lengthOfSelectedText = ( handlesCrossed ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition ) - startOfSelectedText; + const CharacterIndex startOfSelectedText = handlesCrossed ? mEventData->mRightSelectionPosition : mEventData->mLeftSelectionPosition; + const Length lengthOfSelectedText = (handlesCrossed ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition) - startOfSelectedText; - Vector& utf32Characters = mModel->mLogicalModel->mText; - const Length numberOfCharacters = utf32Characters.Count(); + Vector& utf32Characters = mModel->mLogicalModel->mText; + const Length numberOfCharacters = utf32Characters.Count(); // Validate the start and end selection points - if( ( startOfSelectedText + lengthOfSelectedText ) <= numberOfCharacters ) + if((startOfSelectedText + lengthOfSelectedText) <= numberOfCharacters) { //Get text as a UTF8 string - Utf32ToUtf8( &utf32Characters[startOfSelectedText], lengthOfSelectedText, selectedText ); + Utf32ToUtf8(&utf32Characters[startOfSelectedText], lengthOfSelectedText, selectedText); - if( deleteAfterRetrieval ) // Only delete text if copied successfully + if(deleteAfterRetrieval) // Only delete text if copied successfully { // Keep a copy of the current input style. InputStyle currentInputStyle; - currentInputStyle.Copy( mEventData->mInputStyle ); + currentInputStyle.Copy(mEventData->mInputStyle); // Set as input style the style of the first deleted character. - mModel->mLogicalModel->RetrieveStyle( startOfSelectedText, mEventData->mInputStyle ); + mModel->mLogicalModel->RetrieveStyle(startOfSelectedText, mEventData->mInputStyle); // Compare if the input style has changed. - const bool hasInputStyleChanged = !currentInputStyle.Equal( mEventData->mInputStyle ); + const bool hasInputStyleChanged = !currentInputStyle.Equal(mEventData->mInputStyle); - if( hasInputStyleChanged ) + if(hasInputStyleChanged) { - const InputStyle::Mask styleChangedMask = currentInputStyle.GetInputStyleChangeMask( mEventData->mInputStyle ); + const InputStyle::Mask styleChangedMask = currentInputStyle.GetInputStyleChangeMask(mEventData->mInputStyle); // Queue the input style changed signal. - mEventData->mInputStyleChangedQueue.PushBack( styleChangedMask ); + mEventData->mInputStyleChangedQueue.PushBack(styleChangedMask); } - mModel->mLogicalModel->UpdateTextStyleRuns( startOfSelectedText, -static_cast( lengthOfSelectedText ) ); + mModel->mLogicalModel->UpdateTextStyleRuns(startOfSelectedText, -static_cast(lengthOfSelectedText)); // Mark the paragraphs to be updated. - if( Layout::Engine::SINGLE_LINE_BOX == mLayoutEngine.GetLayout() ) + if(Layout::Engine::SINGLE_LINE_BOX == mLayoutEngine.GetLayout()) { - mTextUpdateInfo.mCharacterIndex = 0; + mTextUpdateInfo.mCharacterIndex = 0; mTextUpdateInfo.mNumberOfCharactersToRemove = mTextUpdateInfo.mPreviousNumberOfCharacters; - mTextUpdateInfo.mNumberOfCharactersToAdd = mTextUpdateInfo.mPreviousNumberOfCharacters - lengthOfSelectedText; - mTextUpdateInfo.mClearAll = true; + mTextUpdateInfo.mNumberOfCharactersToAdd = mTextUpdateInfo.mPreviousNumberOfCharacters - lengthOfSelectedText; + mTextUpdateInfo.mClearAll = true; } else { - mTextUpdateInfo.mCharacterIndex = startOfSelectedText; + mTextUpdateInfo.mCharacterIndex = startOfSelectedText; mTextUpdateInfo.mNumberOfCharactersToRemove = lengthOfSelectedText; } // Delete text between handles Vector::Iterator first = utf32Characters.Begin() + startOfSelectedText; Vector::Iterator last = first + lengthOfSelectedText; - utf32Characters.Erase( first, last ); + utf32Characters.Erase(first, last); // Will show the cursor at the first character of the selection. mEventData->mPrimaryCursorPosition = handlesCrossed ? mEventData->mRightSelectionPosition : mEventData->mLeftSelectionPosition; @@ -1282,21 +1006,29 @@ void Controller::Impl::RetrieveSelection( std::string& selectedText, bool delete } } -void Controller::Impl::SetSelection( int start, int end ) +void Controller::Impl::SetSelection(int start, int end) { - mEventData->mLeftSelectionPosition = start; + uint32_t oldStart = mEventData->mLeftSelectionPosition; + uint32_t oldEnd = mEventData->mRightSelectionPosition; + + mEventData->mLeftSelectionPosition = start; mEventData->mRightSelectionPosition = end; - mEventData->mUpdateCursorPosition = true; + mEventData->mUpdateCursorPosition = true; + + if(mSelectableControlInterface != nullptr) + { + mSelectableControlInterface->SelectionChanged(oldStart, oldEnd, start, end); + } } -std::pair< int, int > Controller::Impl::GetSelectionIndexes() const +std::pair Controller::Impl::GetSelectionIndexes() const { - return { mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition }; + return {mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition}; } void Controller::Impl::ShowClipboard() { - if( mClipboard ) + if(mClipboard) { mClipboard.ShowClipboard(); } @@ -1304,7 +1036,7 @@ void Controller::Impl::ShowClipboard() void Controller::Impl::HideClipboard() { - if( mClipboard && mClipboardHideEnabled ) + if(mClipboard && mClipboardHideEnabled) { mClipboard.HideClipboard(); } @@ -1315,327 +1047,112 @@ void Controller::Impl::SetClipboardHideEnable(bool enable) mClipboardHideEnabled = enable; } -bool Controller::Impl::CopyStringToClipboard( const std::string& source ) +bool Controller::Impl::CopyStringToClipboard(const std::string& source) { //Send string to clipboard - return ( mClipboard && mClipboard.SetItem( source ) ); + return (mClipboard && mClipboard.SetItem(source)); } -void Controller::Impl::SendSelectionToClipboard( bool deleteAfterSending ) +void Controller::Impl::SendSelectionToClipboard(bool deleteAfterSending) { std::string selectedText; - RetrieveSelection( selectedText, deleteAfterSending ); - CopyStringToClipboard( selectedText ); - ChangeState( EventData::EDITING ); -} - -void Controller::Impl::RequestGetTextFromClipboard() -{ - if ( mClipboard ) - { - mClipboard.RequestItem(); - } -} - -void Controller::Impl::RepositionSelectionHandles() -{ - SelectionHandleController::Reposition(*this); -} -void Controller::Impl::RepositionSelectionHandles( float visualX, float visualY, Controller::NoTextTap::Action action ) -{ - SelectionHandleController::Reposition(*this, visualX, visualY, action); -} - -void Controller::Impl::SetPopupButtons() -{ - /** - * Sets the Popup buttons to be shown depending on State. - * - * If SELECTING : CUT & COPY + ( PASTE & CLIPBOARD if content available to paste ) - * - * If EDITING_WITH_POPUP : SELECT & SELECT_ALL - */ - - bool isEditable = IsEditable(); - TextSelectionPopup::Buttons buttonsToShow = TextSelectionPopup::NONE; - - if( EventData::SELECTING == mEventData->mState ) - { - buttonsToShow = TextSelectionPopup::Buttons( TextSelectionPopup::COPY ); - if(isEditable) - { - buttonsToShow = TextSelectionPopup::Buttons( buttonsToShow | TextSelectionPopup::CUT ); - } - - if( !IsClipboardEmpty() ) - { - if(isEditable) - { - buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) ); - } - buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::CLIPBOARD ) ); - } - - if( !mEventData->mAllTextSelected ) - { - buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::SELECT_ALL ) ); - } - } - else if( EventData::EDITING_WITH_POPUP == mEventData->mState ) - { - if( mModel->mLogicalModel->mText.Count() && !IsShowingPlaceholderText() ) - { - buttonsToShow = TextSelectionPopup::Buttons( TextSelectionPopup::SELECT | TextSelectionPopup::SELECT_ALL ); - } - - if( !IsClipboardEmpty() ) - { - if(isEditable) - { - buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) ); - } - buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::CLIPBOARD ) ); - } - } - else if( EventData::EDITING_WITH_PASTE_POPUP == mEventData->mState ) - { - if ( !IsClipboardEmpty() ) - { - if(isEditable) - { - buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) ); - } - buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::CLIPBOARD ) ); - } - } - - mEventData->mDecorator->SetEnabledPopupButtons( buttonsToShow ); -} - -void Controller::Impl::ChangeState( EventData::State newState ) -{ - if( NULL == mEventData ) - { - // Nothing to do if there is no text input. - return; - } - - DALI_LOG_INFO( gLogFilter, Debug::General, "ChangeState state:%d newstate:%d\n", mEventData->mState, newState ); - - if( mEventData->mState != newState ) - { - mEventData->mPreviousState = mEventData->mState; - mEventData->mState = newState; - - switch( mEventData->mState ) - { - case EventData::INACTIVE: - { - mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE ); - mEventData->mDecorator->StopCursorBlink(); - mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false ); - mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false ); - mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false ); - mEventData->mDecorator->SetHighlightActive( false ); - mEventData->mDecorator->SetPopupActive( false ); - mEventData->mDecoratorUpdated = true; - break; - } - case EventData::INTERRUPTED: - { - mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false ); - mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false ); - mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false ); - mEventData->mDecorator->SetHighlightActive( false ); - mEventData->mDecorator->SetPopupActive( false ); - mEventData->mDecoratorUpdated = true; - break; - } - case EventData::SELECTING: - { - mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE ); - mEventData->mDecorator->StopCursorBlink(); - mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false ); - if ( mEventData->mGrabHandleEnabled ) - { - mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true ); - mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true ); - } - mEventData->mDecorator->SetHighlightActive( true ); - if( mEventData->mGrabHandlePopupEnabled ) - { - SetPopupButtons(); - mEventData->mDecorator->SetPopupActive( true ); - } - mEventData->mDecoratorUpdated = true; - break; - } - case EventData::EDITING: - { - mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY ); - if( mEventData->mCursorBlinkEnabled ) - { - mEventData->mDecorator->StartCursorBlink(); - } - // Grab handle is not shown until a tap is received whilst EDITING - mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false ); - mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false ); - mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false ); - mEventData->mDecorator->SetHighlightActive( false ); - if( mEventData->mGrabHandlePopupEnabled ) - { - mEventData->mDecorator->SetPopupActive( false ); - } - mEventData->mDecoratorUpdated = true; - break; - } - case EventData::EDITING_WITH_POPUP: - { - DALI_LOG_INFO( gLogFilter, Debug::Verbose, "EDITING_WITH_POPUP \n", newState ); - - mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY ); - if( mEventData->mCursorBlinkEnabled ) - { - mEventData->mDecorator->StartCursorBlink(); - } - if( mEventData->mSelectionEnabled ) - { - mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false ); - mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false ); - mEventData->mDecorator->SetHighlightActive( false ); - } - else if ( mEventData->mGrabHandleEnabled ) - { - mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true ); - } - if( mEventData->mGrabHandlePopupEnabled ) - { - SetPopupButtons(); - mEventData->mDecorator->SetPopupActive( true ); - } - mEventData->mDecoratorUpdated = true; - break; - } - case EventData::EDITING_WITH_GRAB_HANDLE: - { - DALI_LOG_INFO( gLogFilter, Debug::Verbose, "EDITING_WITH_GRAB_HANDLE \n", newState ); + RetrieveSelection(selectedText, deleteAfterSending); + CopyStringToClipboard(selectedText); + ChangeState(EventData::EDITING); +} - mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY ); - if( mEventData->mCursorBlinkEnabled ) - { - mEventData->mDecorator->StartCursorBlink(); - } - // Grab handle is not shown until a tap is received whilst EDITING - if ( mEventData->mGrabHandleEnabled ) - { - mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true ); - } - mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false ); - mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false ); - mEventData->mDecorator->SetHighlightActive( false ); - if( mEventData->mGrabHandlePopupEnabled ) - { - mEventData->mDecorator->SetPopupActive( false ); - } - mEventData->mDecoratorUpdated = true; - break; - } - case EventData::SELECTION_HANDLE_PANNING: - { - mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE ); - mEventData->mDecorator->StopCursorBlink(); - mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false ); - if ( mEventData->mGrabHandleEnabled ) - { - mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true ); - mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true ); - } - mEventData->mDecorator->SetHighlightActive( true ); - if( mEventData->mGrabHandlePopupEnabled ) - { - mEventData->mDecorator->SetPopupActive( false ); - } - mEventData->mDecoratorUpdated = true; - break; - } - case EventData::GRAB_HANDLE_PANNING: - { - DALI_LOG_INFO( gLogFilter, Debug::Verbose, "GRAB_HANDLE_PANNING \n", newState ); +void Controller::Impl::RequestGetTextFromClipboard() +{ + if(mClipboard) + { + mClipboard.RequestItem(); + } +} - mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY ); - if( mEventData->mCursorBlinkEnabled ) - { - mEventData->mDecorator->StartCursorBlink(); - } - if ( mEventData->mGrabHandleEnabled ) - { - mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true ); - } - mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false ); - mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false ); - mEventData->mDecorator->SetHighlightActive( false ); - if( mEventData->mGrabHandlePopupEnabled ) - { - mEventData->mDecorator->SetPopupActive( false ); - } - mEventData->mDecoratorUpdated = true; - break; - } - case EventData::EDITING_WITH_PASTE_POPUP: - { - DALI_LOG_INFO( gLogFilter, Debug::Verbose, "EDITING_WITH_PASTE_POPUP \n", newState ); +void Controller::Impl::RepositionSelectionHandles() +{ + SelectionHandleController::Reposition(*this); +} +void Controller::Impl::RepositionSelectionHandles(float visualX, float visualY, Controller::NoTextTap::Action action) +{ + SelectionHandleController::Reposition(*this, visualX, visualY, action); +} - mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY ); - if( mEventData->mCursorBlinkEnabled ) - { - mEventData->mDecorator->StartCursorBlink(); - } +void Controller::Impl::SetPopupButtons() +{ + /** + * Sets the Popup buttons to be shown depending on State. + * + * If SELECTING : CUT & COPY + ( PASTE & CLIPBOARD if content available to paste ) + * + * If EDITING_WITH_POPUP : SELECT & SELECT_ALL + */ - if ( mEventData->mGrabHandleEnabled ) - { - mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true ); - } - mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false ); - mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false ); - mEventData->mDecorator->SetHighlightActive( false ); + bool isEditable = IsEditable(); + TextSelectionPopup::Buttons buttonsToShow = TextSelectionPopup::NONE; - if( mEventData->mGrabHandlePopupEnabled ) - { - SetPopupButtons(); - mEventData->mDecorator->SetPopupActive( true ); - } - mEventData->mDecoratorUpdated = true; - break; - } - case EventData::TEXT_PANNING: + if(EventData::SELECTING == mEventData->mState) + { + buttonsToShow = TextSelectionPopup::Buttons(TextSelectionPopup::COPY); + if(isEditable) + { + buttonsToShow = TextSelectionPopup::Buttons(buttonsToShow | TextSelectionPopup::CUT); + } + + if(!IsClipboardEmpty()) + { + if(isEditable) { - mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE ); - mEventData->mDecorator->StopCursorBlink(); - mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false ); - if( mEventData->mDecorator->IsHandleActive( LEFT_SELECTION_HANDLE ) || - mEventData->mDecorator->IsHandleActive( RIGHT_SELECTION_HANDLE ) ) - { - mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false ); - mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false ); - mEventData->mDecorator->SetHighlightActive( true ); - } + buttonsToShow = TextSelectionPopup::Buttons((buttonsToShow | TextSelectionPopup::PASTE)); + } + buttonsToShow = TextSelectionPopup::Buttons((buttonsToShow | TextSelectionPopup::CLIPBOARD)); + } - if( mEventData->mGrabHandlePopupEnabled ) - { - mEventData->mDecorator->SetPopupActive( false ); - } + if(!mEventData->mAllTextSelected) + { + buttonsToShow = TextSelectionPopup::Buttons((buttonsToShow | TextSelectionPopup::SELECT_ALL)); + } + } + else if(EventData::EDITING_WITH_POPUP == mEventData->mState) + { + if(mModel->mLogicalModel->mText.Count() && !IsShowingPlaceholderText()) + { + buttonsToShow = TextSelectionPopup::Buttons(TextSelectionPopup::SELECT | TextSelectionPopup::SELECT_ALL); + } - mEventData->mDecoratorUpdated = true; - break; + if(!IsClipboardEmpty()) + { + if(isEditable) + { + buttonsToShow = TextSelectionPopup::Buttons((buttonsToShow | TextSelectionPopup::PASTE)); + } + buttonsToShow = TextSelectionPopup::Buttons((buttonsToShow | TextSelectionPopup::CLIPBOARD)); + } + } + else if(EventData::EDITING_WITH_PASTE_POPUP == mEventData->mState) + { + if(!IsClipboardEmpty()) + { + if(isEditable) + { + buttonsToShow = TextSelectionPopup::Buttons((buttonsToShow | TextSelectionPopup::PASTE)); } + buttonsToShow = TextSelectionPopup::Buttons((buttonsToShow | TextSelectionPopup::CLIPBOARD)); } } + + mEventData->mDecorator->SetEnabledPopupButtons(buttonsToShow); +} + +void Controller::Impl::ChangeState(EventData::State newState) +{ + ChangeTextControllerState(*this, newState); } -void Controller::Impl::GetCursorPosition( CharacterIndex logical, - CursorInfo& cursorInfo ) +void Controller::Impl::GetCursorPosition(CharacterIndex logical, + CursorInfo& cursorInfo) { - if( !IsShowingRealText() ) + if(!IsShowingRealText()) { // Do not want to use the place-holder text to set the cursor position. @@ -1643,21 +1160,21 @@ void Controller::Impl::GetCursorPosition( CharacterIndex logical, // If there is no font's family set, use the default font. // Use the current alignment to place the cursor at the beginning, center or end of the box. - cursorInfo.lineOffset = 0.f; - cursorInfo.lineHeight = GetDefaultFontLineHeight(); + cursorInfo.lineOffset = 0.f; + cursorInfo.lineHeight = GetDefaultFontLineHeight(); cursorInfo.primaryCursorHeight = cursorInfo.lineHeight; bool isRTL = false; - if( mModel->mMatchSystemLanguageDirection ) + if(mModel->mMatchLayoutDirection != DevelText::MatchLayoutDirection::CONTENTS) { isRTL = mLayoutDirection == LayoutDirection::RIGHT_TO_LEFT; } - switch( mModel->mHorizontalAlignment ) + switch(mModel->mHorizontalAlignment) { - case Text::HorizontalAlignment::BEGIN : + case Text::HorizontalAlignment::BEGIN: { - if( isRTL ) + if(isRTL) { cursorInfo.primaryPosition.x = mModel->mVisualModel->mControlSize.width - mEventData->mDecorator->GetCursorWidth(); } @@ -1669,12 +1186,12 @@ void Controller::Impl::GetCursorPosition( CharacterIndex logical, } case Text::HorizontalAlignment::CENTER: { - cursorInfo.primaryPosition.x = floorf( 0.5f * mModel->mVisualModel->mControlSize.width ); + cursorInfo.primaryPosition.x = floorf(0.5f * mModel->mVisualModel->mControlSize.width); break; } case Text::HorizontalAlignment::END: { - if( isRTL ) + if(isRTL) { cursorInfo.primaryPosition.x = 0.f; } @@ -1690,47 +1207,50 @@ void Controller::Impl::GetCursorPosition( CharacterIndex logical, return; } - const bool isMultiLine = ( Layout::Engine::MULTI_LINE_BOX == mLayoutEngine.GetLayout() ); + const bool isMultiLine = (Layout::Engine::MULTI_LINE_BOX == mLayoutEngine.GetLayout()); GetCursorPositionParameters parameters; - parameters.visualModel = mModel->mVisualModel; + parameters.visualModel = mModel->mVisualModel; parameters.logicalModel = mModel->mLogicalModel; - parameters.metrics = mMetrics; - parameters.logical = logical; - parameters.isMultiline = isMultiLine; + parameters.metrics = mMetrics; + parameters.logical = logical; + parameters.isMultiline = isMultiLine; - Text::GetCursorPosition( parameters, - cursorInfo ); + float defaultFontLineHeight = GetDefaultFontLineHeight(); + + Text::GetCursorPosition(parameters, + defaultFontLineHeight, + cursorInfo); // Adds Outline offset. - const float outlineWidth = static_cast( mModel->GetOutlineWidth() ); + const float outlineWidth = static_cast(mModel->GetOutlineWidth()); cursorInfo.primaryPosition.x += outlineWidth; cursorInfo.primaryPosition.y += outlineWidth; cursorInfo.secondaryPosition.x += outlineWidth; cursorInfo.secondaryPosition.y += outlineWidth; - if( isMultiLine ) + if(isMultiLine) { // If the text is editable and multi-line, the cursor position after a white space shouldn't exceed the boundaries of the text control. // Note the white spaces laid-out at the end of the line might exceed the boundaries of the control. // The reason is a wrapped line must not start with a white space so they are laid-out at the end of the line. - if( 0.f > cursorInfo.primaryPosition.x ) + if(0.f > cursorInfo.primaryPosition.x) { cursorInfo.primaryPosition.x = 0.f; } - const float edgeWidth = mModel->mVisualModel->mControlSize.width - static_cast( mEventData->mDecorator->GetCursorWidth() ); - if( cursorInfo.primaryPosition.x > edgeWidth ) + const float edgeWidth = mModel->mVisualModel->mControlSize.width - static_cast(mEventData->mDecorator->GetCursorWidth()); + if(cursorInfo.primaryPosition.x > edgeWidth) { cursorInfo.primaryPosition.x = edgeWidth; } } } -CharacterIndex Controller::Impl::CalculateNewCursorIndex( CharacterIndex index ) const +CharacterIndex Controller::Impl::CalculateNewCursorIndex(CharacterIndex index) const { - if( NULL == mEventData ) + if(nullptr == mEventData) { // Nothing to do if there is no text input. return 0u; @@ -1738,16 +1258,16 @@ CharacterIndex Controller::Impl::CalculateNewCursorIndex( CharacterIndex index ) CharacterIndex cursorIndex = mEventData->mPrimaryCursorPosition; - const GlyphIndex* const charactersToGlyphBuffer = mModel->mVisualModel->mCharactersToGlyph.Begin(); - const Length* const charactersPerGlyphBuffer = mModel->mVisualModel->mCharactersPerGlyph.Begin(); + const GlyphIndex* const charactersToGlyphBuffer = mModel->mVisualModel->mCharactersToGlyph.Begin(); + const Length* const charactersPerGlyphBuffer = mModel->mVisualModel->mCharactersPerGlyph.Begin(); - GlyphIndex glyphIndex = *( charactersToGlyphBuffer + index ); - Length numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex ); + GlyphIndex glyphIndex = *(charactersToGlyphBuffer + index); + Length numberOfCharacters = *(charactersPerGlyphBuffer + glyphIndex); - if( numberOfCharacters > 1u ) + if(numberOfCharacters > 1u) { - const Script script = mModel->mLogicalModel->GetScript( index ); - if( HasLigatureMustBreak( script ) ) + const Script script = mModel->mLogicalModel->GetScript(index); + if(HasLigatureMustBreak(script)) { // Prevents to jump the whole Latin ligatures like fi, ff, or Arabic ï»», ... numberOfCharacters = 1u; @@ -1755,14 +1275,14 @@ CharacterIndex Controller::Impl::CalculateNewCursorIndex( CharacterIndex index ) } else { - while( 0u == numberOfCharacters ) + while(0u == numberOfCharacters) { ++glyphIndex; - numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex ); + numberOfCharacters = *(charactersPerGlyphBuffer + glyphIndex); } } - if( index < mEventData->mPrimaryCursorPosition ) + if(index < mEventData->mPrimaryCursorPosition) { cursorIndex -= numberOfCharacters; } @@ -1777,82 +1297,82 @@ CharacterIndex Controller::Impl::CalculateNewCursorIndex( CharacterIndex index ) return cursorIndex; } -void Controller::Impl::UpdateCursorPosition( const CursorInfo& cursorInfo ) +void Controller::Impl::UpdateCursorPosition(const CursorInfo& cursorInfo) { - DALI_LOG_INFO( gLogFilter, Debug::Verbose, "-->Controller::UpdateCursorPosition %p\n", this ); - if( NULL == mEventData ) + DALI_LOG_INFO(gLogFilter, Debug::Verbose, "-->Controller::UpdateCursorPosition %p\n", this); + if(nullptr == mEventData) { // Nothing to do if there is no text input. - DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition no event data\n" ); + DALI_LOG_INFO(gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition no event data\n"); return; } const Vector2 cursorPosition = cursorInfo.primaryPosition + mModel->mScrollPosition; - mEventData->mDecorator->SetGlyphOffset( PRIMARY_CURSOR, cursorInfo.glyphOffset ); + mEventData->mDecorator->SetGlyphOffset(PRIMARY_CURSOR, cursorInfo.glyphOffset); // Sets the cursor position. - mEventData->mDecorator->SetPosition( PRIMARY_CURSOR, - cursorPosition.x, - cursorPosition.y, - cursorInfo.primaryCursorHeight, - cursorInfo.lineHeight ); - DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Primary cursor position: %f,%f\n", cursorPosition.x, cursorPosition.y ); + mEventData->mDecorator->SetPosition(PRIMARY_CURSOR, + cursorPosition.x, + cursorPosition.y, + cursorInfo.primaryCursorHeight, + cursorInfo.lineHeight); + DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Primary cursor position: %f,%f\n", cursorPosition.x, cursorPosition.y); - if( mEventData->mUpdateGrabHandlePosition ) + if(mEventData->mUpdateGrabHandlePosition) { // Sets the grab handle position. - mEventData->mDecorator->SetPosition( GRAB_HANDLE, - cursorPosition.x, - cursorInfo.lineOffset + mModel->mScrollPosition.y, - cursorInfo.lineHeight ); + mEventData->mDecorator->SetPosition(GRAB_HANDLE, + cursorPosition.x, + cursorInfo.lineOffset + mModel->mScrollPosition.y, + cursorInfo.lineHeight); } - if( cursorInfo.isSecondaryCursor ) + if(cursorInfo.isSecondaryCursor) { - mEventData->mDecorator->SetPosition( SECONDARY_CURSOR, - cursorInfo.secondaryPosition.x + mModel->mScrollPosition.x, - cursorInfo.secondaryPosition.y + mModel->mScrollPosition.y, - cursorInfo.secondaryCursorHeight, - cursorInfo.lineHeight ); - DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Secondary cursor position: %f,%f\n", cursorInfo.secondaryPosition.x + mModel->mScrollPosition.x, cursorInfo.secondaryPosition.y + mModel->mScrollPosition.y ); + mEventData->mDecorator->SetPosition(SECONDARY_CURSOR, + cursorInfo.secondaryPosition.x + mModel->mScrollPosition.x, + cursorInfo.secondaryPosition.y + mModel->mScrollPosition.y, + cursorInfo.secondaryCursorHeight, + cursorInfo.lineHeight); + DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Secondary cursor position: %f,%f\n", cursorInfo.secondaryPosition.x + mModel->mScrollPosition.x, cursorInfo.secondaryPosition.y + mModel->mScrollPosition.y); } // Set which cursors are active according the state. - if( EventData::IsEditingState( mEventData->mState ) || ( EventData::GRAB_HANDLE_PANNING == mEventData->mState ) ) + if(EventData::IsEditingState(mEventData->mState) || (EventData::GRAB_HANDLE_PANNING == mEventData->mState)) { - if( cursorInfo.isSecondaryCursor ) + if(cursorInfo.isSecondaryCursor) { - mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_BOTH ); + mEventData->mDecorator->SetActiveCursor(ACTIVE_CURSOR_BOTH); } else { - mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY ); + mEventData->mDecorator->SetActiveCursor(ACTIVE_CURSOR_PRIMARY); } } else { - mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE ); + mEventData->mDecorator->SetActiveCursor(ACTIVE_CURSOR_NONE); } - DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition\n" ); + DALI_LOG_INFO(gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition\n"); } -void Controller::Impl::UpdateSelectionHandle( HandleType handleType, - const CursorInfo& cursorInfo ) +void Controller::Impl::UpdateSelectionHandle(HandleType handleType, + const CursorInfo& cursorInfo) { SelectionHandleController::Update(*this, handleType, cursorInfo); } -void Controller::Impl::ClampHorizontalScroll( const Vector2& layoutSize ) +void Controller::Impl::ClampHorizontalScroll(const Vector2& layoutSize) { // Clamp between -space & -alignment offset. - if( layoutSize.width > mModel->mVisualModel->mControlSize.width ) + if(layoutSize.width > mModel->mVisualModel->mControlSize.width) { - const float space = ( layoutSize.width - mModel->mVisualModel->mControlSize.width ) + mModel->mAlignmentOffset; - mModel->mScrollPosition.x = ( mModel->mScrollPosition.x < -space ) ? -space : mModel->mScrollPosition.x; - mModel->mScrollPosition.x = ( mModel->mScrollPosition.x > -mModel->mAlignmentOffset ) ? -mModel->mAlignmentOffset : mModel->mScrollPosition.x; + const float space = (layoutSize.width - mModel->mVisualModel->mControlSize.width) + mModel->mAlignmentOffset; + mModel->mScrollPosition.x = (mModel->mScrollPosition.x < -space) ? -space : mModel->mScrollPosition.x; + mModel->mScrollPosition.x = (mModel->mScrollPosition.x > -mModel->mAlignmentOffset) ? -mModel->mAlignmentOffset : mModel->mScrollPosition.x; mEventData->mDecoratorUpdated = true; } @@ -1862,20 +1382,20 @@ void Controller::Impl::ClampHorizontalScroll( const Vector2& layoutSize ) } } -void Controller::Impl::ClampVerticalScroll( const Vector2& layoutSize ) +void Controller::Impl::ClampVerticalScroll(const Vector2& layoutSize) { - if( Layout::Engine::SINGLE_LINE_BOX == mLayoutEngine.GetLayout() ) + if(Layout::Engine::SINGLE_LINE_BOX == mLayoutEngine.GetLayout()) { // Nothing to do if the text is single line. return; } // Clamp between -space & 0. - if( layoutSize.height > mModel->mVisualModel->mControlSize.height ) + if(layoutSize.height > mModel->mVisualModel->mControlSize.height) { - const float space = ( layoutSize.height - mModel->mVisualModel->mControlSize.height ); - mModel->mScrollPosition.y = ( mModel->mScrollPosition.y < -space ) ? -space : mModel->mScrollPosition.y; - mModel->mScrollPosition.y = ( mModel->mScrollPosition.y > 0.f ) ? 0.f : mModel->mScrollPosition.y; + const float space = (layoutSize.height - mModel->mVisualModel->mControlSize.height); + mModel->mScrollPosition.y = (mModel->mScrollPosition.y < -space) ? -space : mModel->mScrollPosition.y; + mModel->mScrollPosition.y = (mModel->mScrollPosition.y > 0.f) ? 0.f : mModel->mScrollPosition.y; mEventData->mDecoratorUpdated = true; } @@ -1885,9 +1405,9 @@ void Controller::Impl::ClampVerticalScroll( const Vector2& layoutSize ) } } -void Controller::Impl::ScrollToMakePositionVisible( const Vector2& position, float lineHeight ) +void Controller::Impl::ScrollToMakePositionVisible(const Vector2& position, float lineHeight) { - const float cursorWidth = mEventData->mDecorator ? static_cast( mEventData->mDecorator->GetCursorWidth() ) : 0.f; + const float cursorWidth = mEventData->mDecorator ? static_cast(mEventData->mDecorator->GetCursorWidth()) : 0.f; // position is in actor's coords. const float positionEndX = position.x + cursorWidth; @@ -1895,241 +1415,475 @@ void Controller::Impl::ScrollToMakePositionVisible( const Vector2& position, flo // Transform the position to decorator coords. const float decoratorPositionBeginX = position.x + mModel->mScrollPosition.x; - const float decoratorPositionEndX = positionEndX + mModel->mScrollPosition.x; + const float decoratorPositionEndX = positionEndX + mModel->mScrollPosition.x; const float decoratorPositionBeginY = position.y + mModel->mScrollPosition.y; - const float decoratorPositionEndY = positionEndY + mModel->mScrollPosition.y; + const float decoratorPositionEndY = positionEndY + mModel->mScrollPosition.y; - if( decoratorPositionBeginX < 0.f ) + if(decoratorPositionBeginX < 0.f) { mModel->mScrollPosition.x = -position.x; } - else if( decoratorPositionEndX > mModel->mVisualModel->mControlSize.width ) + else if(decoratorPositionEndX > mModel->mVisualModel->mControlSize.width) { mModel->mScrollPosition.x = mModel->mVisualModel->mControlSize.width - positionEndX; } - if( Layout::Engine::MULTI_LINE_BOX == mLayoutEngine.GetLayout() ) + if(Layout::Engine::MULTI_LINE_BOX == mLayoutEngine.GetLayout()) { - if( decoratorPositionBeginY < 0.f ) + if(decoratorPositionBeginY < 0.f) { mModel->mScrollPosition.y = -position.y; } - else if( decoratorPositionEndY > mModel->mVisualModel->mControlSize.height ) + else if(decoratorPositionEndY > mModel->mVisualModel->mControlSize.height) { mModel->mScrollPosition.y = mModel->mVisualModel->mControlSize.height - positionEndY; } } } -void Controller::Impl::ScrollTextToMatchCursor( const CursorInfo& cursorInfo ) +void Controller::Impl::ScrollTextToMatchCursor(const CursorInfo& cursorInfo) { // Get the current cursor position in decorator coords. - const Vector2& currentCursorPosition = mEventData->mDecorator->GetPosition( PRIMARY_CURSOR ); - - const LineIndex lineIndex = mModel->mVisualModel->GetLineOfCharacter( mEventData->mPrimaryCursorPosition ); - + const Vector2& currentCursorPosition = mEventData->mDecorator->GetPosition(PRIMARY_CURSOR); + const LineIndex lineIndex = mModel->mVisualModel->GetLineOfCharacter(mEventData->mPrimaryCursorPosition); // Calculate the offset to match the cursor position before the character was deleted. mModel->mScrollPosition.x = currentCursorPosition.x - cursorInfo.primaryPosition.x; //If text control has more than two lines and current line index is not last, calculate scrollpositionY - if( mModel->mVisualModel->mLines.Count() > 1u && lineIndex != mModel->mVisualModel->mLines.Count() -1u ) + if(mModel->mVisualModel->mLines.Count() > 1u && lineIndex != mModel->mVisualModel->mLines.Count() - 1u) { - const float currentCursorGlyphOffset = mEventData->mDecorator->GetGlyphOffset( PRIMARY_CURSOR ); - mModel->mScrollPosition.y = currentCursorPosition.y - cursorInfo.lineOffset - currentCursorGlyphOffset; + const float currentCursorGlyphOffset = mEventData->mDecorator->GetGlyphOffset(PRIMARY_CURSOR); + mModel->mScrollPosition.y = currentCursorPosition.y - cursorInfo.lineOffset - currentCursorGlyphOffset; } - - ClampHorizontalScroll( mModel->mVisualModel->GetLayoutSize() ); - ClampVerticalScroll( mModel->mVisualModel->GetLayoutSize() ); + ClampHorizontalScroll(mModel->mVisualModel->GetLayoutSize()); + ClampVerticalScroll(mModel->mVisualModel->GetLayoutSize()); // Makes the new cursor position visible if needed. - ScrollToMakePositionVisible( cursorInfo.primaryPosition, cursorInfo.lineHeight ); + ScrollToMakePositionVisible(cursorInfo.primaryPosition, cursorInfo.lineHeight); } void Controller::Impl::ScrollTextToMatchCursor() { CursorInfo cursorInfo; - GetCursorPosition( mEventData->mPrimaryCursorPosition, cursorInfo ); + GetCursorPosition(mEventData->mPrimaryCursorPosition, cursorInfo); ScrollTextToMatchCursor(cursorInfo); } void Controller::Impl::RequestRelayout() { - if( NULL != mControlInterface ) + if(nullptr != mControlInterface) { mControlInterface->RequestTextRelayout(); } } -Actor Controller::Impl::CreateBackgroundActor() +void Controller::Impl::RelayoutAllCharacters() +{ + // relayout all characters + mTextUpdateInfo.mCharacterIndex = 0; + mTextUpdateInfo.mNumberOfCharactersToRemove = mTextUpdateInfo.mPreviousNumberOfCharacters; + mTextUpdateInfo.mNumberOfCharactersToAdd = mModel->mLogicalModel->mText.Count(); + mOperationsPending = static_cast(mOperationsPending | LAYOUT); + + mTextUpdateInfo.mFullRelayoutNeeded = true; + + // Need to recalculate natural size + mRecalculateNaturalSize = true; + + //remove selection + if((mEventData != nullptr) && (mEventData->mState == EventData::SELECTING)) + { + ChangeState(EventData::EDITING); + } + + RequestRelayout(); +} + +bool Controller::Impl::IsInputStyleChangedSignalsQueueEmpty() +{ + return (NULL == mEventData) || (0u == mEventData->mInputStyleChangedQueue.Count()); +} + +void Controller::Impl::ProcessInputStyleChangedSignals() +{ + if(mEventData) + { + if(mEditableControlInterface) + { + // Emit the input style changed signal for each mask + std::for_each(mEventData->mInputStyleChangedQueue.begin(), + mEventData->mInputStyleChangedQueue.end(), + [&](const auto mask) { mEditableControlInterface->InputStyleChanged(mask); }); + } + + mEventData->mInputStyleChangedQueue.Clear(); + } +} + +void Controller::Impl::ScrollBy(Vector2 scroll) +{ + if(mEventData && (fabs(scroll.x) > Math::MACHINE_EPSILON_0 || fabs(scroll.y) > Math::MACHINE_EPSILON_0)) + { + const Vector2& layoutSize = mModel->mVisualModel->GetLayoutSize(); + const Vector2 currentScroll = mModel->mScrollPosition; + + scroll.x = -scroll.x; + scroll.y = -scroll.y; + + if(fabs(scroll.x) > Math::MACHINE_EPSILON_0) + { + mModel->mScrollPosition.x += scroll.x; + ClampHorizontalScroll(layoutSize); + } + + if(fabs(scroll.y) > Math::MACHINE_EPSILON_0) + { + mModel->mScrollPosition.y += scroll.y; + ClampVerticalScroll(layoutSize); + } + + if(mModel->mScrollPosition != currentScroll) + { + mEventData->mDecorator->UpdatePositions(mModel->mScrollPosition - currentScroll); + RequestRelayout(); + } + } +} + +float Controller::Impl::GetHorizontalScrollPosition() +{ + // Scroll values are negative internally so we convert them to positive numbers + return mEventData ? -mModel->mScrollPosition.x : 0.0f; +} + +float Controller::Impl::GetVerticalScrollPosition() +{ + // Scroll values are negative internally so we convert them to positive numbers + return mEventData ? -mModel->mScrollPosition.y : 0.0f; +} + +Vector3 Controller::Impl::GetAnchorPosition(Anchor anchor) const +{ + //TODO + return Vector3(10.f, 10.f, 10.f); +} + +Vector2 Controller::Impl::GetAnchorSize(Anchor anchor) const { - // NOTE: Currently we only support background color for one line left-to-right text, - // so the following calculation is based on one line left-to-right text only! + //TODO + return Vector2(10.f, 10.f); +} + +Toolkit::TextAnchor Controller::Impl::CreateAnchorActor(Anchor anchor) +{ + auto actor = Toolkit::TextAnchor::New(); + actor.SetProperty(Actor::Property::PARENT_ORIGIN, ParentOrigin::TOP_LEFT); + actor.SetProperty(Actor::Property::ANCHOR_POINT, AnchorPoint::TOP_LEFT); + const Vector3 anchorPosition = GetAnchorPosition(anchor); + actor.SetProperty(Actor::Property::POSITION, anchorPosition); + const Vector2 anchorSize = GetAnchorSize(anchor); + actor.SetProperty(Actor::Property::SIZE, anchorSize); + std::string anchorText(mModel->mLogicalModel->mText.Begin() + anchor.startIndex, mModel->mLogicalModel->mText.Begin() + anchor.endIndex); + actor.SetProperty(Actor::Property::NAME, anchorText); + actor.SetProperty(Toolkit::TextAnchor::Property::URI, std::string(anchor.href)); + actor.SetProperty(Toolkit::TextAnchor::Property::START_CHARACTER_INDEX, static_cast(anchor.startIndex)); + actor.SetProperty(Toolkit::TextAnchor::Property::END_CHARACTER_INDEX, static_cast(anchor.endIndex)); + return actor; +} + +void Controller::Impl::GetAnchorActors(std::vector& anchorActors) +{ + /* TODO: Now actors are created/destroyed in every "RenderText" function call. Even when we add just 1 character, + we need to create and destroy potentially many actors. Some optimization can be considered here. + Maybe a "dirty" flag in mLogicalModel? */ + anchorActors.clear(); + for(auto& anchor : mModel->mLogicalModel->mAnchors) + { + auto actor = CreateAnchorActor(anchor); + anchorActors.push_back(actor); + } +} - Actor actor; +int32_t Controller::Impl::GetAnchorIndex(size_t characterOffset) const +{ + Vector::Iterator it = mModel->mLogicalModel->mAnchors.Begin(); - Length numberOfGlyphs = mView.GetNumberOfGlyphs(); - if( numberOfGlyphs > 0u ) + while(it != mModel->mLogicalModel->mAnchors.End() && (it->startIndex > characterOffset || it->endIndex <= characterOffset)) { - Vector glyphs; - glyphs.Resize( numberOfGlyphs ); + it++; + } + + return it == mModel->mLogicalModel->mAnchors.End() ? -1 : it - mModel->mLogicalModel->mAnchors.Begin(); +} + +void Controller::Impl::CopyUnderlinedFromLogicalToVisualModels(bool shouldClearPreUnderlineRuns) +{ + //Underlined character runs for markup-processor + const Vector& underlinedCharacterRuns = mModel->mLogicalModel->mUnderlinedCharacterRuns; + const Vector& charactersToGlyph = mModel->mVisualModel->mCharactersToGlyph; + const Vector& glyphsPerCharacter = mModel->mVisualModel->mGlyphsPerCharacter; - Vector positions; - positions.Resize( numberOfGlyphs ); + if(shouldClearPreUnderlineRuns) + { + mModel->mVisualModel->mUnderlineRuns.Clear(); + } - // Get the line where the glyphs are laid-out. - const LineRun* lineRun = mModel->mVisualModel->mLines.Begin(); - float alignmentOffset = lineRun->alignmentOffset; - numberOfGlyphs = mView.GetGlyphs( glyphs.Begin(), - positions.Begin(), - alignmentOffset, - 0u, - numberOfGlyphs ); + for(Vector::ConstIterator it = underlinedCharacterRuns.Begin(), endIt = underlinedCharacterRuns.End(); it != endIt; ++it) + { + CharacterIndex characterIndex = it->characterRun.characterIndex; + Length numberOfCharacters = it->characterRun.numberOfCharacters; - glyphs.Resize( numberOfGlyphs ); - positions.Resize( numberOfGlyphs ); + if(numberOfCharacters == 0) + { + continue; + } - const GlyphInfo* const glyphsBuffer = glyphs.Begin(); - const Vector2* const positionsBuffer = positions.Begin(); + // Create one run for all glyphs of all run's characters that has same properties + // This enhance performance and reduce the needed memory to store glyphs-runs + UnderlinedGlyphRun underlineGlyphRun; + underlineGlyphRun.glyphRun.glyphIndex = charactersToGlyph[characterIndex]; + underlineGlyphRun.glyphRun.numberOfGlyphs = glyphsPerCharacter[characterIndex]; + //Copy properties (attributes) + underlineGlyphRun.properties = it->properties; - BackgroundMesh mesh; - mesh.mVertices.Reserve( 4u * glyphs.Size() ); - mesh.mIndices.Reserve( 6u * glyphs.Size() ); + for(Length index = 1u; index < numberOfCharacters; index++) + { + underlineGlyphRun.glyphRun.numberOfGlyphs += glyphsPerCharacter[characterIndex + index]; + } - const Vector2 textSize = mView.GetLayoutSize(); + mModel->mVisualModel->mUnderlineRuns.PushBack(underlineGlyphRun); + } +} - const float offsetX = textSize.width * 0.5f; - const float offsetY = textSize.height * 0.5f; +void Controller::Impl::CopyStrikethroughFromLogicalToVisualModels() +{ + //Strikethrough character runs from markup-processor + const Vector& strikethroughCharacterRuns = mModel->mLogicalModel->mStrikethroughCharacterRuns; + const Vector& charactersToGlyph = mModel->mVisualModel->mCharactersToGlyph; + const Vector& glyphsPerCharacter = mModel->mVisualModel->mGlyphsPerCharacter; - const Vector4* const backgroundColorsBuffer = mView.GetBackgroundColors(); - const ColorIndex* const backgroundColorIndicesBuffer = mView.GetBackgroundColorIndices(); - const Vector4& defaultBackgroundColor = mModel->mVisualModel->IsBackgroundEnabled() ? mModel->mVisualModel->GetBackgroundColor() : Color::TRANSPARENT; + mModel->mVisualModel->mStrikethroughRuns.Clear(); - Vector4 quad; - uint32_t numberOfQuads = 0u; + for(Vector::ConstIterator it = strikethroughCharacterRuns.Begin(), endIt = strikethroughCharacterRuns.End(); it != endIt; ++it) + { + CharacterIndex characterIndex = it->characterRun.characterIndex; + Length numberOfCharacters = it->characterRun.numberOfCharacters; - for( uint32_t i = 0, glyphSize = glyphs.Size(); i < glyphSize; ++i ) + if(numberOfCharacters == 0) { - const GlyphInfo& glyph = *( glyphsBuffer + i ); + continue; + } - // Get the background color of the character. - // The color index zero is reserved for the default background color (i.e. Color::TRANSPARENT) - const ColorIndex backgroundColorIndex = ( nullptr == backgroundColorsBuffer ) ? 0u : *( backgroundColorIndicesBuffer + i ); - const Vector4& backgroundColor = ( 0u == backgroundColorIndex ) ? defaultBackgroundColor : *( backgroundColorsBuffer + backgroundColorIndex - 1u ); + StrikethroughGlyphRun strikethroughGlyphRun; + strikethroughGlyphRun.color = it->color; + strikethroughGlyphRun.isColorSet = it->isColorSet; + strikethroughGlyphRun.glyphRun.glyphIndex = charactersToGlyph[characterIndex]; + strikethroughGlyphRun.glyphRun.numberOfGlyphs = glyphsPerCharacter[characterIndex]; - // Only create quads for glyphs with a background color - if ( backgroundColor != Color::TRANSPARENT ) - { - const Vector2 position = *( positionsBuffer + i ); + for(Length index = 1u; index < numberOfCharacters; index++) + { + strikethroughGlyphRun.glyphRun.numberOfGlyphs += glyphsPerCharacter[characterIndex + index]; + } - if ( i == 0u && glyphSize == 1u ) // Only one glyph in the whole text - { - quad.x = position.x; - quad.y = 0.0f; - quad.z = quad.x + std::max( glyph.advance, glyph.xBearing + glyph.width ); - quad.w = textSize.height; - } - else if ( i == 0u ) // The first glyph in the whole text - { - quad.x = position.x; - quad.y = 0.0f; - quad.z = quad.x - glyph.xBearing + glyph.advance; - quad.w = textSize.height; - } - else if ( i == glyphSize - 1u ) // The last glyph in the whole text - { - quad.x = position.x - glyph.xBearing; - quad.y = 0.0f; - quad.z = quad.x + std::max( glyph.advance, glyph.xBearing + glyph.width ); - quad.w = textSize.height; - } - else // The glyph in the middle of the text - { - quad.x = position.x - glyph.xBearing; - quad.y = 0.0f; - quad.z = quad.x + glyph.advance; - quad.w = textSize.height; - } + mModel->mVisualModel->mStrikethroughRuns.PushBack(strikethroughGlyphRun); + } +} - BackgroundVertex vertex; - - // Top left - vertex.mPosition.x = quad.x - offsetX; - vertex.mPosition.y = quad.y - offsetY; - vertex.mColor = backgroundColor; - mesh.mVertices.PushBack( vertex ); - - // Top right - vertex.mPosition.x = quad.z - offsetX; - vertex.mPosition.y = quad.y - offsetY; - vertex.mColor = backgroundColor; - mesh.mVertices.PushBack( vertex ); - - // Bottom left - vertex.mPosition.x = quad.x - offsetX; - vertex.mPosition.y = quad.w - offsetY; - vertex.mColor = backgroundColor; - mesh.mVertices.PushBack( vertex ); - - // Bottom right - vertex.mPosition.x = quad.z - offsetX; - vertex.mPosition.y = quad.w - offsetY; - vertex.mColor = backgroundColor; - mesh.mVertices.PushBack( vertex ); - - // Six indices in counter clockwise winding - mesh.mIndices.PushBack( 1u + 4 * numberOfQuads ); - mesh.mIndices.PushBack( 0u + 4 * numberOfQuads ); - mesh.mIndices.PushBack( 2u + 4 * numberOfQuads ); - mesh.mIndices.PushBack( 2u + 4 * numberOfQuads ); - mesh.mIndices.PushBack( 3u + 4 * numberOfQuads ); - mesh.mIndices.PushBack( 1u + 4 * numberOfQuads ); - - numberOfQuads++; - } +void Controller::Impl::SetAutoScrollEnabled(bool enable) +{ + if(mLayoutEngine.GetLayout() == Layout::Engine::SINGLE_LINE_BOX) + { + mOperationsPending = static_cast(mOperationsPending | + LAYOUT | + ALIGN | + UPDATE_LAYOUT_SIZE | + REORDER); + + if(enable) + { + DALI_LOG_INFO(gLogFilter, Debug::General, "Controller::SetAutoScrollEnabled for SINGLE_LINE_BOX\n"); + mOperationsPending = static_cast(mOperationsPending | UPDATE_DIRECTION); + } + else + { + DALI_LOG_INFO(gLogFilter, Debug::General, "Controller::SetAutoScrollEnabled Disabling autoscroll\n"); } - // Only create the background actor if there are glyphs with background color - if ( mesh.mVertices.Count() > 0u ) + mIsAutoScrollEnabled = enable; + RequestRelayout(); + } + else + { + DALI_LOG_WARNING("Attempted AutoScrolling on a non SINGLE_LINE_BOX, request ignored\n"); + mIsAutoScrollEnabled = false; + } +} + +void Controller::Impl::SetEnableCursorBlink(bool enable) +{ + DALI_ASSERT_DEBUG(NULL != mEventData && "TextInput disabled"); + + if(mEventData) + { + mEventData->mCursorBlinkEnabled = enable; + + if(!enable && mEventData->mDecorator) { - Property::Map quadVertexFormat; - quadVertexFormat[ "aPosition" ] = Property::VECTOR2; - quadVertexFormat[ "aColor" ] = Property::VECTOR4; + mEventData->mDecorator->StopCursorBlink(); + } + } +} + +void Controller::Impl::SetMultiLineEnabled(bool enable) +{ + const Layout::Engine::Type layout = enable ? Layout::Engine::MULTI_LINE_BOX : Layout::Engine::SINGLE_LINE_BOX; - VertexBuffer quadVertices = VertexBuffer::New( quadVertexFormat ); - quadVertices.SetData( &mesh.mVertices[ 0 ], mesh.mVertices.Size() ); + if(layout != mLayoutEngine.GetLayout()) + { + // Set the layout type. + mLayoutEngine.SetLayout(layout); + + // Set the flags to redo the layout operations + const OperationsMask layoutOperations = static_cast(LAYOUT | + UPDATE_LAYOUT_SIZE | + ALIGN | + REORDER); + + mTextUpdateInfo.mFullRelayoutNeeded = true; + mOperationsPending = static_cast(mOperationsPending | layoutOperations); + + // Need to recalculate natural size + mRecalculateNaturalSize = true; + + RequestRelayout(); + } +} + +void Controller::Impl::SetHorizontalAlignment(Text::HorizontalAlignment::Type alignment) +{ + if(alignment != mModel->mHorizontalAlignment) + { + // Set the alignment. + mModel->mHorizontalAlignment = alignment; - Geometry quadGeometry = Geometry::New(); - quadGeometry.AddVertexBuffer( quadVertices ); - quadGeometry.SetIndexBuffer( &mesh.mIndices[ 0 ], mesh.mIndices.Size() ); + // Set the flag to redo the alignment operation. + mOperationsPending = static_cast(mOperationsPending | ALIGN); + + if(mEventData) + { + mEventData->mUpdateAlignment = true; - if( !mShaderBackground ) + // Update the cursor if it's in editing mode + if(EventData::IsEditingState(mEventData->mState)) { - mShaderBackground = Shader::New( VERTEX_SHADER_BACKGROUND, FRAGMENT_SHADER_BACKGROUND ); + ChangeState(EventData::EDITING); + mEventData->mUpdateCursorPosition = true; } + } + + RequestRelayout(); + } +} - Dali::Renderer renderer = Dali::Renderer::New( quadGeometry, mShaderBackground ); - renderer.SetProperty( Dali::Renderer::Property::BLEND_MODE, BlendMode::ON ); - renderer.SetProperty( Dali::Renderer::Property::DEPTH_INDEX, DepthIndex::CONTENT ); - - actor = Actor::New(); - actor.SetProperty( Dali::Actor::Property::NAME, "TextBackgroundColorActor" ); - actor.SetProperty( Actor::Property::PARENT_ORIGIN, ParentOrigin::TOP_LEFT ); - actor.SetProperty( Actor::Property::ANCHOR_POINT, AnchorPoint::TOP_LEFT ); - actor.SetProperty( Actor::Property::SIZE, textSize ); - actor.SetProperty( Actor::Property::COLOR_MODE, USE_OWN_MULTIPLY_PARENT_COLOR ); - actor.AddRenderer( renderer ); +void Controller::Impl::SetVerticalAlignment(VerticalAlignment::Type alignment) +{ + if(alignment != mModel->mVerticalAlignment) + { + // Set the alignment. + mModel->mVerticalAlignment = alignment; + mOperationsPending = static_cast(mOperationsPending | ALIGN); + RequestRelayout(); + } +} + +void Controller::Impl::SetLineWrapMode(Text::LineWrap::Mode lineWrapMode) +{ + if(lineWrapMode != mModel->mLineWrapMode) + { + // Update Text layout for applying wrap mode + mOperationsPending = static_cast(mOperationsPending | + ALIGN | + LAYOUT | + UPDATE_LAYOUT_SIZE | + REORDER); + + if((mModel->mLineWrapMode == (Text::LineWrap::Mode)DevelText::LineWrap::HYPHENATION) || (lineWrapMode == (Text::LineWrap::Mode)DevelText::LineWrap::HYPHENATION) || + (mModel->mLineWrapMode == (Text::LineWrap::Mode)DevelText::LineWrap::MIXED) || (lineWrapMode == (Text::LineWrap::Mode)DevelText::LineWrap::MIXED)) // hyphen is treated as line break + { + mOperationsPending = static_cast(mOperationsPending | GET_LINE_BREAKS); } + + // Set the text wrap mode. + mModel->mLineWrapMode = lineWrapMode; + + mTextUpdateInfo.mCharacterIndex = 0u; + mTextUpdateInfo.mNumberOfCharactersToRemove = mTextUpdateInfo.mPreviousNumberOfCharacters; + mTextUpdateInfo.mNumberOfCharactersToAdd = mModel->mLogicalModel->mText.Count(); + + // Request relayout + RequestRelayout(); } +} - return actor; +void Controller::Impl::SetDefaultColor(const Vector4& color) +{ + mTextColor = color; + + if(!IsShowingPlaceholderText()) + { + mModel->mVisualModel->SetTextColor(color); + mModel->mLogicalModel->mColorRuns.Clear(); + mOperationsPending = static_cast(mOperationsPending | COLOR); + RequestRelayout(); + } +} + +void Controller::Impl::ClearFontData() +{ + if(mFontDefaults) + { + mFontDefaults->mFontId = 0u; // Remove old font ID + } + + // Set flags to update the model. + mTextUpdateInfo.mCharacterIndex = 0u; + mTextUpdateInfo.mNumberOfCharactersToRemove = mTextUpdateInfo.mPreviousNumberOfCharacters; + mTextUpdateInfo.mNumberOfCharactersToAdd = mModel->mLogicalModel->mText.Count(); + + mTextUpdateInfo.mClearAll = true; + mTextUpdateInfo.mFullRelayoutNeeded = true; + mRecalculateNaturalSize = true; + + mOperationsPending = static_cast(mOperationsPending | + VALIDATE_FONTS | + SHAPE_TEXT | + BIDI_INFO | + GET_GLYPH_METRICS | + LAYOUT | + UPDATE_LAYOUT_SIZE | + REORDER | + ALIGN); } -} // namespace Text +void Controller::Impl::ClearStyleData() +{ + mModel->mLogicalModel->mColorRuns.Clear(); + mModel->mLogicalModel->ClearFontDescriptionRuns(); + mModel->mLogicalModel->ClearStrikethroughRuns(); +} -} // namespace Toolkit +void Controller::Impl::ResetScrollPosition() +{ + if(mEventData) + { + // Reset the scroll position. + mModel->mScrollPosition = Vector2::ZERO; + mEventData->mScrollAfterUpdatePosition = true; + } +} -} // namespace Dali +} // namespace Dali::Toolkit::Text