X-Git-Url: http://review.tizen.org/git/?a=blobdiff_plain;ds=sidebyside;f=dali-toolkit%2Finternal%2Ftext%2Ftext-controller-impl.cpp;h=ec00903e4629998a6cb693e63ec79979b03cc116;hb=HEAD;hp=47877c487090e25e61bd10a3b0e29bcc35f56d85;hpb=144dda05c357f536307dc5802dbf5c26cee53c33;p=platform%2Fcore%2Fuifw%2Fdali-toolkit.git diff --git a/dali-toolkit/internal/text/text-controller-impl.cpp b/dali-toolkit/internal/text/text-controller-impl.cpp deleted file mode 100644 index 47877c4..0000000 --- a/dali-toolkit/internal/text/text-controller-impl.cpp +++ /dev/null @@ -1,1944 +0,0 @@ -/* - * 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. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ - -// CLASS HEADER -#include - -// EXTERNAL INCLUDES -#include -#include -#include -#include -#include - -// INTERNAL INCLUDES -#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 - -constexpr float MAX_FLOAT = std::numeric_limits::max(); - -const std::string EMPTY_STRING(""); - -} // namespace - -namespace Dali::Toolkit::Text -{ -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; - - 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(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; - } - } -} - -void ChangeTextControllerState(Controller::Impl& impl, EventData::State newState) -{ - EventData* eventData = impl.mEventData; - - if(nullptr == eventData) - { - // Nothing to do if there is no text input. - return; - } - - DecoratorPtr& decorator = eventData->mDecorator; - if(!decorator) - { - // Nothing to do if there is no decorator. - return; - } - - DALI_LOG_INFO(gLogFilter, Debug::General, "ChangeState state:%d newstate:%d\n", eventData->mState, newState); - - if(eventData->mState != newState) - { - eventData->mPreviousState = eventData->mState; - eventData->mState = newState; - - 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; - } - - 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); - - 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; - } - } - } -} - -void UpdateCursorPositionForAlignment(Controller::Impl& impl, bool needFullAlignment) -{ - EventData* eventData = impl.mEventData; - - // Set the flag to redo the alignment operation - impl.mOperationsPending = static_cast(impl.mOperationsPending | Controller::OperationsMask::ALIGN); - - if(eventData) - { - // Note: mUpdateAlignment is currently only needed for horizontal alignment - eventData->mUpdateAlignment = needFullAlignment; - - // Update the cursor if it's in editing mode - if(EventData::IsEditingState(eventData->mState)) - { - impl.ChangeState(EventData::EDITING); - eventData->mUpdateCursorPosition = true; - } - } -} - -} // 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). - 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) -{ -} - -bool Controller::Impl::ProcessInputEvents() -{ - return ControllerImplEventHandler::ProcessInputEvents(*this); -} - -void Controller::Impl::NotifyInputMethodContext() -{ - if(mEventData && mEventData->mInputMethodContext) - { - CharacterIndex cursorPosition = GetLogicalCursorPosition(); - - const Length numberOfWhiteSpaces = GetNumberOfWhiteSpaces(0u); - - // Update the cursor position by removing the initial white spaces. - if(cursorPosition < numberOfWhiteSpaces) - { - cursorPosition = 0u; - } - else - { - cursorPosition -= numberOfWhiteSpaces; - } - - mEventData->mInputMethodContext.SetCursorPosition(cursorPosition); - mEventData->mInputMethodContext.NotifyCursorPosition(); - } -} - -void Controller::Impl::NotifyInputMethodContextMultiLineStatus() -{ - if(mEventData && mEventData->mInputMethodContext) - { - Text::Layout::Engine::Type layout = mLayoutEngine.GetLayout(); - mEventData->mInputMethodContext.NotifyTextInputMultiLine(layout == Text::Layout::Engine::MULTI_LINE_BOX); - } -} - -CharacterIndex Controller::Impl::GetLogicalCursorPosition() const -{ - CharacterIndex cursorPosition = 0u; - - if(mEventData) - { - if((EventData::SELECTING == mEventData->mState) || - (EventData::SELECTION_HANDLE_PANNING == mEventData->mState)) - { - cursorPosition = std::min(mEventData->mRightSelectionPosition, mEventData->mLeftSelectionPosition); - } - else - { - cursorPosition = mEventData->mPrimaryCursorPosition; - } - } - - return cursorPosition; -} - -Length Controller::Impl::GetNumberOfWhiteSpaces(CharacterIndex index) const -{ - Length numberOfWhiteSpaces = 0u; - - // Get the buffer to the text. - Character* utf32CharacterBuffer = mModel->mLogicalModel->mText.Begin(); - - const Length totalNumberOfCharacters = mModel->mLogicalModel->mText.Count(); - for(; index < totalNumberOfCharacters; ++index, ++numberOfWhiteSpaces) - { - if(!TextAbstraction::IsWhiteSpace(*(utf32CharacterBuffer + index))) - { - break; - } - } - - return numberOfWhiteSpaces; -} - -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) - { - 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)) - { - Window window = DevelWindow::Get(actor); - return static_cast(window ? window.GetRootLayer().GetProperty(Dali::Actor::Property::LAYOUT_DIRECTION).Get() : LayoutDirection::LEFT_TO_RIGHT); - } - else - { - return static_cast(actor.GetProperty(Dali::Actor::Property::LAYOUT_DIRECTION).Get()); - } -} - -Toolkit::DevelText::TextDirection::Type Controller::Impl::GetTextDirection() -{ - if(mUpdateTextDirection) - { - // 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) -{ - mTextUpdateInfo.mParagraphCharacterIndex = 0u; - mTextUpdateInfo.mStartGlyphIndex = 0u; - mTextUpdateInfo.mStartLineIndex = 0u; - numberOfCharacters = 0u; - - const Length numberOfParagraphs = mModel->mLogicalModel->mParagraphInfo.Count(); - if(0u == numberOfParagraphs) - { - mTextUpdateInfo.mParagraphCharacterIndex = 0u; - numberOfCharacters = 0u; - - mTextUpdateInfo.mRequestedNumberOfCharacters = mTextUpdateInfo.mNumberOfCharactersToAdd - mTextUpdateInfo.mNumberOfCharactersToRemove; - - // Nothing else to do if there are no paragraphs. - return; - } - - // Find the paragraphs to be updated. - Vector paragraphsToBeUpdated; - if(mTextUpdateInfo.mCharacterIndex >= mTextUpdateInfo.mPreviousNumberOfCharacters) - { - // Text is being added at the end of the current text. - 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.mRequestedNumberOfCharacters = mTextUpdateInfo.mNumberOfCharactersToAdd - mTextUpdateInfo.mNumberOfCharactersToRemove; - - mTextUpdateInfo.mStartGlyphIndex = mModel->mVisualModel->mGlyphs.Count(); - mTextUpdateInfo.mStartLineIndex = mModel->mVisualModel->mLines.Count() - 1u; - - // Nothing else to do; - return; - } - - paragraphsToBeUpdated.PushBack(numberOfParagraphs - 1u); - } - else - { - Length numberOfCharactersToUpdate = 0u; - if(mTextUpdateInfo.mFullRelayoutNeeded) - { - numberOfCharactersToUpdate = mTextUpdateInfo.mPreviousNumberOfCharacters; - } - else - { - numberOfCharactersToUpdate = (mTextUpdateInfo.mNumberOfCharactersToRemove > 0u) ? mTextUpdateInfo.mNumberOfCharactersToRemove : 1u; - } - mModel->mLogicalModel->FindParagraphs(mTextUpdateInfo.mCharacterIndex, - numberOfCharactersToUpdate, - paragraphsToBeUpdated); - } - - if(0u != paragraphsToBeUpdated.Count()) - { - 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); - - 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); - - numberOfCharacters = lastParagraph.characterRun.characterIndex + lastParagraph.characterRun.numberOfCharacters - mTextUpdateInfo.mParagraphCharacterIndex; - } - else - { - numberOfCharacters = lastParagraph.characterRun.characterIndex + lastParagraph.characterRun.numberOfCharacters - mTextUpdateInfo.mParagraphCharacterIndex; - } - } - - mTextUpdateInfo.mRequestedNumberOfCharacters = numberOfCharacters + mTextUpdateInfo.mNumberOfCharactersToAdd - mTextUpdateInfo.mNumberOfCharactersToRemove; - mTextUpdateInfo.mStartGlyphIndex = *(mModel->mVisualModel->mCharactersToGlyph.Begin() + mTextUpdateInfo.mParagraphCharacterIndex); -} - -void Controller::Impl::ClearModelData(CharacterIndex startIndex, CharacterIndex endIndex, OperationsMask operations) -{ - 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); -} - -float Controller::Impl::GetDefaultFontLineHeight() -{ - FontId defaultFontId = 0u; - if(nullptr == mFontDefaults) - { - TextAbstraction::FontDescription fontDescription; - defaultFontId = mFontClient.GetFontId(fontDescription, TextAbstraction::FontClient::DEFAULT_POINT_SIZE * GetFontSizeScale()); - } - else - { - defaultFontId = mFontDefaults->GetFontId(mFontClient, mFontDefaults->mDefaultPointSize * GetFontSizeScale()); - } - - Text::FontMetrics fontMetrics; - mMetrics->GetFontMetrics(defaultFontId, fontMetrics); - - return (fontMetrics.ascender - fontMetrics.descender); -} - -bool Controller::Impl::SetDefaultLineSpacing(float lineSpacing) -{ - if(std::fabs(lineSpacing - mLayoutEngine.GetDefaultLineSpacing()) > Math::MACHINE_EPSILON_1000) - { - mLayoutEngine.SetDefaultLineSpacing(lineSpacing); - - RelayoutAllCharacters(); - return true; - } - return false; -} - -bool Controller::Impl::SetDefaultLineSize(float lineSize) -{ - if(std::fabs(lineSize - mLayoutEngine.GetDefaultLineSize()) > Math::MACHINE_EPSILON_1000) - { - mLayoutEngine.SetDefaultLineSize(lineSize); - - RelayoutAllCharacters(); - return true; - } - return false; -} - -bool Controller::Impl::SetRelativeLineSize(float relativeLineSize) -{ - if(std::fabs(relativeLineSize - GetRelativeLineSize()) > Math::MACHINE_EPSILON_1000) - { - mLayoutEngine.SetRelativeLineSize(relativeLineSize); - - RelayoutAllCharacters(); - return true; - } - return false; -} - -float Controller::Impl::GetRelativeLineSize() -{ - return mLayoutEngine.GetRelativeLineSize(); -} - -string Controller::Impl::GetSelectedText() -{ - string text; - if(EventData::SELECTING == mEventData->mState) - { - RetrieveSelection(text, false); - } - return text; -} - -string Controller::Impl::CopyText() -{ - string text; - RetrieveSelection(text, false); - SendSelectionToClipboard(false); // Text not modified - - mEventData->mUpdateCursorPosition = true; - - RequestRelayout(); // Cursor, Handles, Selection Highlight, Popup - - return text; -} - -string Controller::Impl::CutText() -{ - string text; - RetrieveSelection(text, false); - - if(!IsEditable()) - { - return EMPTY_STRING; - } - - SendSelectionToClipboard(true); // Synchronous call to modify text - mOperationsPending = ALL_OPERATIONS; - - if((0u != mModel->mLogicalModel->mText.Count()) || - !IsPlaceholderAvailable()) - { - QueueModifyEvent(ModifyEvent::TEXT_DELETED); - } - else - { - PlaceholderHandler::ShowPlaceholderText(*this); - } - - mEventData->mUpdateCursorPosition = true; - mEventData->mScrollAfterDelete = true; - - RequestRelayout(); - - if(nullptr != mEditableControlInterface) - { - mEditableControlInterface->TextChanged(true); - } - return text; -} - -void Controller::Impl::SetTextSelectionRange(const uint32_t* pStart, const uint32_t* pEnd) -{ - if(nullptr == mEventData) - { - // Nothing to do if there is no text. - return; - } - - if(mEventData->mSelectionEnabled && (pStart || pEnd)) - { - uint32_t length = static_cast(mModel->mLogicalModel->mText.Count()); - uint32_t oldStart = mEventData->mLeftSelectionPosition; - uint32_t oldEnd = mEventData->mRightSelectionPosition; - - if(pStart) - { - mEventData->mLeftSelectionPosition = std::min(*pStart, length); - } - if(pEnd) - { - mEventData->mRightSelectionPosition = std::min(*pEnd, length); - } - - if(mEventData->mLeftSelectionPosition == mEventData->mRightSelectionPosition) - { - ChangeState(EventData::EDITING); - mEventData->mPrimaryCursorPosition = mEventData->mLeftSelectionPosition = mEventData->mRightSelectionPosition; - mEventData->mUpdateCursorPosition = true; - } - else - { - 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) - { - return 0; - } - return mEventData->mPrimaryCursorPosition; -} - -bool Controller::Impl::SetPrimaryCursorPosition(CharacterIndex index, bool focused) -{ - if(nullptr == mEventData) - { - // Nothing to do if there is no text. - return false; - } - - 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 oldCursorPos = mEventData->mPrimaryCursorPosition; - mEventData->mPrimaryCursorPosition = std::min(index, length); - // 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; -} - -Uint32Pair Controller::Impl::GetTextSelectionRange() const -{ - Uint32Pair range; - - if(mEventData) - { - range.first = mEventData->mLeftSelectionPosition; - range.second = mEventData->mRightSelectionPosition; - } - - return range; -} - -bool Controller::Impl::IsEditable() const -{ - return mEventData && mEventData->mEditingEnabled; -} - -void Controller::Impl::SetEditable(bool editable) -{ - if(mEventData) - { - mEventData->mEditingEnabled = editable; - - if(mEventData->mDecorator) - { - bool decoratorEditable = editable && mIsUserInteractionEnabled; - mEventData->mDecorator->SetEditable(decoratorEditable); - } - } -} - -void Controller::Impl::UpdateAfterFontChange(const std::string& newDefaultFont) -{ - 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(); - return; - } - - 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; - - Vector& utf32Characters = mModel->mLogicalModel->mText; - const Length numberOfCharacters = utf32Characters.Count(); - - // Validate the start and end selection points - if((startOfSelectedText + lengthOfSelectedText) <= numberOfCharacters) - { - //Get text as a UTF8 string - Utf32ToUtf8(&utf32Characters[startOfSelectedText], lengthOfSelectedText, selectedText); - - if(deleteAfterRetrieval) // Only delete text if copied successfully - { - // Keep a copy of the current input style. - InputStyle currentInputStyle; - currentInputStyle.Copy(mEventData->mInputStyle); - - // Set as input style the style of the first deleted character. - mModel->mLogicalModel->RetrieveStyle(startOfSelectedText, mEventData->mInputStyle); - - // Compare if the input style has changed. - const bool hasInputStyleChanged = !currentInputStyle.Equal(mEventData->mInputStyle); - - if(hasInputStyleChanged) - { - const InputStyle::Mask styleChangedMask = currentInputStyle.GetInputStyleChangeMask(mEventData->mInputStyle); - // Queue the input style changed signal. - mEventData->mInputStyleChangedQueue.PushBack(styleChangedMask); - } - - mModel->mLogicalModel->UpdateTextStyleRuns(startOfSelectedText, -static_cast(lengthOfSelectedText)); - - // Mark the paragraphs to be updated. - if(Layout::Engine::SINGLE_LINE_BOX == mLayoutEngine.GetLayout()) - { - mTextUpdateInfo.mCharacterIndex = 0; - mTextUpdateInfo.mNumberOfCharactersToRemove = mTextUpdateInfo.mPreviousNumberOfCharacters; - mTextUpdateInfo.mNumberOfCharactersToAdd = mTextUpdateInfo.mPreviousNumberOfCharacters - lengthOfSelectedText; - mTextUpdateInfo.mClearAll = true; - } - else - { - 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); - - // Will show the cursor at the first character of the selection. - mEventData->mPrimaryCursorPosition = handlesCrossed ? mEventData->mRightSelectionPosition : mEventData->mLeftSelectionPosition; - } - else - { - // Will show the cursor at the last character of the selection. - mEventData->mPrimaryCursorPosition = handlesCrossed ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition; - } - - mEventData->mDecoratorUpdated = true; - } -} - -void Controller::Impl::SetSelection(int start, int end) -{ - uint32_t oldStart = mEventData->mLeftSelectionPosition; - uint32_t oldEnd = mEventData->mRightSelectionPosition; - - mEventData->mLeftSelectionPosition = start; - mEventData->mRightSelectionPosition = end; - mEventData->mUpdateCursorPosition = true; - - if(mSelectableControlInterface != nullptr) - { - mSelectableControlInterface->SelectionChanged(oldStart, oldEnd, start, end); - } -} - -std::pair Controller::Impl::GetSelectionIndexes() const -{ - return {mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition}; -} - -void Controller::Impl::ShowClipboard() -{ - if(mClipboard) - { - mClipboard.ShowClipboard(); - } -} - -void Controller::Impl::HideClipboard() -{ - if(mClipboard && mClipboardHideEnabled) - { - mClipboard.HideClipboard(); - } -} - -void Controller::Impl::SetClipboardHideEnable(bool enable) -{ - mClipboardHideEnabled = enable; -} - -bool Controller::Impl::CopyStringToClipboard(const std::string& source) -{ - //Send string to clipboard - return (mClipboard && mClipboard.SetItem(source)); -} - -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) -{ - ChangeTextControllerState(*this, newState); -} - -void Controller::Impl::GetCursorPosition(CharacterIndex logical, - CursorInfo& cursorInfo) -{ - if(!IsShowingRealText()) - { - // Do not want to use the place-holder text to set the cursor position. - - // Use the line's height of the font's family set to set the cursor's size. - // 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.primaryCursorHeight = cursorInfo.lineHeight; - - bool isRTL = false; - if(mModel->mMatchLayoutDirection != DevelText::MatchLayoutDirection::CONTENTS) - { - isRTL = mLayoutDirection == LayoutDirection::RIGHT_TO_LEFT; - } - - switch(mModel->mHorizontalAlignment) - { - case Text::HorizontalAlignment::BEGIN: - { - if(isRTL) - { - cursorInfo.primaryPosition.x = mModel->mVisualModel->mControlSize.width - mEventData->mDecorator->GetCursorWidth(); - } - else - { - cursorInfo.primaryPosition.x = 0.f; - } - break; - } - case Text::HorizontalAlignment::CENTER: - { - cursorInfo.primaryPosition.x = floorf(0.5f * mModel->mVisualModel->mControlSize.width); - break; - } - case Text::HorizontalAlignment::END: - { - if(isRTL) - { - cursorInfo.primaryPosition.x = 0.f; - } - else - { - cursorInfo.primaryPosition.x = mModel->mVisualModel->mControlSize.width - mEventData->mDecorator->GetCursorWidth(); - } - break; - } - } - - // Nothing else to do. - return; - } - - const bool isMultiLine = (Layout::Engine::MULTI_LINE_BOX == mLayoutEngine.GetLayout()); - GetCursorPositionParameters parameters; - parameters.visualModel = mModel->mVisualModel; - parameters.logicalModel = mModel->mLogicalModel; - parameters.metrics = mMetrics; - parameters.logical = logical; - parameters.isMultiline = isMultiLine; - - float defaultFontLineHeight = GetDefaultFontLineHeight(); - - Text::GetCursorPosition(parameters, - defaultFontLineHeight, - cursorInfo); - - // Adds Outline offset. - 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 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) - { - cursorInfo.primaryPosition.x = 0.f; - } - - 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 -{ - if(nullptr == mEventData) - { - // Nothing to do if there is no text input. - return 0u; - } - - CharacterIndex cursorIndex = mEventData->mPrimaryCursorPosition; - - const GlyphIndex* const charactersToGlyphBuffer = mModel->mVisualModel->mCharactersToGlyph.Begin(); - const Length* const charactersPerGlyphBuffer = mModel->mVisualModel->mCharactersPerGlyph.Begin(); - - GlyphIndex glyphIndex = *(charactersToGlyphBuffer + index); - Length numberOfCharacters = *(charactersPerGlyphBuffer + glyphIndex); - - if(numberOfCharacters > 1u) - { - const Script script = mModel->mLogicalModel->GetScript(index); - if(HasLigatureMustBreak(script)) - { - // Prevents to jump the whole Latin ligatures like fi, ff, or Arabic ï»», ... - numberOfCharacters = 1u; - } - } - else - { - while(0u == numberOfCharacters) - { - ++glyphIndex; - numberOfCharacters = *(charactersPerGlyphBuffer + glyphIndex); - } - } - - if(index < mEventData->mPrimaryCursorPosition) - { - cursorIndex -= numberOfCharacters; - } - else - { - cursorIndex += numberOfCharacters; - } - - // Will update the cursor hook position. - mEventData->mUpdateCursorHookPosition = true; - - return cursorIndex; -} - -void Controller::Impl::UpdateCursorPosition(const CursorInfo& cursorInfo) -{ - 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"); - return; - } - - const Vector2 cursorPosition = cursorInfo.primaryPosition + mModel->mScrollPosition; - - 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); - - if(mEventData->mUpdateGrabHandlePosition) - { - // Sets the grab handle position. - mEventData->mDecorator->SetPosition(GRAB_HANDLE, - cursorPosition.x, - cursorInfo.lineOffset + mModel->mScrollPosition.y, - cursorInfo.lineHeight); - } - - 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); - } - - // Set which cursors are active according the state. - if(EventData::IsEditingState(mEventData->mState) || (EventData::GRAB_HANDLE_PANNING == mEventData->mState)) - { - if(cursorInfo.isSecondaryCursor) - { - mEventData->mDecorator->SetActiveCursor(ACTIVE_CURSOR_BOTH); - } - else - { - mEventData->mDecorator->SetActiveCursor(ACTIVE_CURSOR_PRIMARY); - } - } - else - { - mEventData->mDecorator->SetActiveCursor(ACTIVE_CURSOR_NONE); - } - - DALI_LOG_INFO(gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition\n"); -} - -void Controller::Impl::UpdateSelectionHandle(HandleType handleType, - const CursorInfo& cursorInfo) -{ - SelectionHandleController::Update(*this, handleType, cursorInfo); -} - -void Controller::Impl::ClampHorizontalScroll(const Vector2& layoutSize) -{ - // Clamp between -space & -alignment offset. - - 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; - - mEventData->mDecoratorUpdated = true; - } - else - { - mModel->mScrollPosition.x = 0.f; - } -} - -void Controller::Impl::ClampVerticalScroll(const Vector2& layoutSize) -{ - 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) - { - 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; - } - else - { - mModel->mScrollPosition.y = 0.f; - } -} - -void Controller::Impl::ScrollToMakePositionVisible(const Vector2& position, float lineHeight) -{ - const float cursorWidth = mEventData->mDecorator ? static_cast(mEventData->mDecorator->GetCursorWidth()) : 0.f; - - // position is in actor's coords. - const float positionEndX = position.x + cursorWidth; - const float positionEndY = position.y + lineHeight; - - // Transform the position to decorator coords. - const float decoratorPositionBeginX = position.x + 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; - - if(decoratorPositionBeginX < 0.f) - { - mModel->mScrollPosition.x = -position.x; - } - else if(decoratorPositionEndX > mModel->mVisualModel->mControlSize.width) - { - mModel->mScrollPosition.x = mModel->mVisualModel->mControlSize.width - positionEndX; - } - - if(Layout::Engine::MULTI_LINE_BOX == mLayoutEngine.GetLayout()) - { - if(decoratorPositionBeginY < 0.f) - { - mModel->mScrollPosition.y = -position.y; - } - else if(decoratorPositionEndY > mModel->mVisualModel->mControlSize.height) - { - mModel->mScrollPosition.y = mModel->mVisualModel->mControlSize.height - positionEndY; - } - else if(mModel->mLogicalModel->mText.Count() == 0u) - { - Relayouter::CalculateVerticalOffset(*this, mModel->mVisualModel->mControlSize); - } - } -} - -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); - - // 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) - { - const float currentCursorGlyphOffset = mEventData->mDecorator->GetGlyphOffset(PRIMARY_CURSOR); - mModel->mScrollPosition.y = currentCursorPosition.y - cursorInfo.lineOffset - currentCursorGlyphOffset; - } - - ClampHorizontalScroll(mModel->mVisualModel->GetLayoutSize()); - ClampVerticalScroll(mModel->mVisualModel->GetLayoutSize()); - - // Makes the new cursor position visible if needed. - ScrollToMakePositionVisible(cursorInfo.primaryPosition, cursorInfo.lineHeight); -} - -void Controller::Impl::ScrollTextToMatchCursor() -{ - CursorInfo cursorInfo; - GetCursorPosition(mEventData->mPrimaryCursorPosition, cursorInfo); - ScrollTextToMatchCursor(cursorInfo); -} - -void Controller::Impl::RequestRelayout() -{ - if(nullptr != mControlInterface) - { - mControlInterface->RequestTextRelayout(); - } -} - -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 -{ - //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); - } -} - -int32_t Controller::Impl::GetAnchorIndex(size_t characterOffset) const -{ - Vector::Iterator it = mModel->mLogicalModel->mAnchors.Begin(); - - while(it != mModel->mLogicalModel->mAnchors.End() && (it->startIndex > characterOffset || it->endIndex <= characterOffset)) - { - 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; - - if(shouldClearPreUnderlineRuns) - { - mModel->mVisualModel->mUnderlineRuns.Clear(); - } - - for(Vector::ConstIterator it = underlinedCharacterRuns.Begin(), endIt = underlinedCharacterRuns.End(); it != endIt; ++it) - { - CharacterIndex characterIndex = it->characterRun.characterIndex; - Length numberOfCharacters = it->characterRun.numberOfCharacters; - - if(numberOfCharacters == 0) - { - continue; - } - - // 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; - - for(Length index = 1u; index < numberOfCharacters; index++) - { - underlineGlyphRun.glyphRun.numberOfGlyphs += glyphsPerCharacter[characterIndex + index]; - } - - mModel->mVisualModel->mUnderlineRuns.PushBack(underlineGlyphRun); - } -} - -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; - - mModel->mVisualModel->mStrikethroughRuns.Clear(); - - for(Vector::ConstIterator it = strikethroughCharacterRuns.Begin(), endIt = strikethroughCharacterRuns.End(); it != endIt; ++it) - { - CharacterIndex characterIndex = it->characterRun.characterIndex; - Length numberOfCharacters = it->characterRun.numberOfCharacters; - - if(numberOfCharacters == 0) - { - continue; - } - - StrikethroughGlyphRun strikethroughGlyphRun; - strikethroughGlyphRun.properties = it->properties; - strikethroughGlyphRun.glyphRun.glyphIndex = charactersToGlyph[characterIndex]; - strikethroughGlyphRun.glyphRun.numberOfGlyphs = glyphsPerCharacter[characterIndex]; - - for(Length index = 1u; index < numberOfCharacters; index++) - { - strikethroughGlyphRun.glyphRun.numberOfGlyphs += glyphsPerCharacter[characterIndex + index]; - } - - mModel->mVisualModel->mStrikethroughRuns.PushBack(strikethroughGlyphRun); - } -} - -void Controller::Impl::CopyCharacterSpacingFromLogicalToVisualModels() -{ - //CharacterSpacing character runs from markup-processor - const Vector& characterSpacingCharacterRuns = mModel->mLogicalModel->mCharacterSpacingCharacterRuns; - const Vector& charactersToGlyph = mModel->mVisualModel->mCharactersToGlyph; - const Vector& glyphsPerCharacter = mModel->mVisualModel->mGlyphsPerCharacter; - - mModel->mVisualModel->mCharacterSpacingRuns.Clear(); - - for(Vector::ConstIterator it = characterSpacingCharacterRuns.Begin(), endIt = characterSpacingCharacterRuns.End(); it != endIt; ++it) - { - const CharacterIndex& characterIndex = it->characterRun.characterIndex; - const Length& numberOfCharacters = it->characterRun.numberOfCharacters; - - if(numberOfCharacters == 0) - { - continue; - } - - CharacterSpacingGlyphRun characterSpacingGlyphRun; - characterSpacingGlyphRun.value = it->value; - characterSpacingGlyphRun.glyphRun.glyphIndex = charactersToGlyph[characterIndex]; - characterSpacingGlyphRun.glyphRun.numberOfGlyphs = glyphsPerCharacter[characterIndex]; - - for(Length index = 1u; index < numberOfCharacters; index++) - { - characterSpacingGlyphRun.glyphRun.numberOfGlyphs += glyphsPerCharacter[characterIndex + index]; - } - - mModel->mVisualModel->mCharacterSpacingRuns.PushBack(characterSpacingGlyphRun); - } -} - -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"); - } - - 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) - { - 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; - - 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; - UpdateCursorPositionForAlignment(*this, true); - RequestRelayout(); - } -} - -void Controller::Impl::SetVerticalAlignment(VerticalAlignment::Type alignment) -{ - if(alignment != mModel->mVerticalAlignment) - { - // Set the alignment. - mModel->mVerticalAlignment = alignment; - UpdateCursorPositionForAlignment(*this, false); - 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(); - } -} - -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::SetUserInteractionEnabled(bool enabled) -{ - mIsUserInteractionEnabled = enabled; - - if(mEventData && mEventData->mDecorator) - { - bool editable = mEventData->mEditingEnabled && enabled; - mEventData->mDecorator->SetEditable(editable); - } -} - -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); -} - -void Controller::Impl::ClearStyleData() -{ - mModel->mLogicalModel->mColorRuns.Clear(); - mModel->mLogicalModel->ClearFontDescriptionRuns(); - mModel->mLogicalModel->ClearStrikethroughRuns(); -} - -void Controller::Impl::ResetScrollPosition() -{ - if(mEventData) - { - // Reset the scroll position. - mModel->mScrollPosition = Vector2::ZERO; - mEventData->mScrollAfterUpdatePosition = true; - } -} - -} // namespace Dali::Toolkit::Text