2 * Copyright (c) 2022 Samsung Electronics Co., Ltd.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
19 #include <dali-toolkit/internal/text/controller/text-controller-text-updater.h>
22 #include <dali/integration-api/debug.h>
23 #include <dali/public-api/math/math-utils.h>
27 #include <dali-toolkit/internal/text/character-set-conversion.h>
28 #include <dali-toolkit/internal/text/characters-helper-functions.h>
29 #include <dali-toolkit/internal/text/controller/text-controller-impl.h>
30 #include <dali-toolkit/internal/text/controller/text-controller-placeholder-handler.h>
31 #include <dali-toolkit/internal/text/emoji-helper.h>
32 #include <dali-toolkit/internal/text/markup-processor/markup-processor.h>
33 #include <dali-toolkit/internal/text/text-editable-control-interface.h>
37 #if defined(DEBUG_ENABLED)
38 Debug::Filter* gLogFilter = Debug::Filter::New(Debug::NoLogging, true, "LOG_TEXT_CONTROLS");
49 void Controller::TextUpdater::SetText(Controller& controller, const std::string& text)
51 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Controller::SetText\n");
53 Controller::Impl& impl = *controller.mImpl;
55 // Reset keyboard as text changed
56 impl.ResetInputMethodContext();
58 // Remove the previously set text and style.
59 ResetText(controller);
62 impl.ClearStyleData();
64 CharacterIndex lastCursorIndex = 0u;
66 EventData*& eventData = impl.mEventData;
68 if(nullptr != eventData)
70 // If popup shown then hide it by switching to Editing state
71 if((EventData::SELECTING == eventData->mState) ||
72 (EventData::EDITING_WITH_POPUP == eventData->mState) ||
73 (EventData::EDITING_WITH_GRAB_HANDLE == eventData->mState) ||
74 (EventData::EDITING_WITH_PASTE_POPUP == eventData->mState))
76 if((impl.mSelectableControlInterface != nullptr) && (EventData::SELECTING == eventData->mState))
78 impl.mSelectableControlInterface->SelectionChanged(eventData->mLeftSelectionPosition, eventData->mRightSelectionPosition, eventData->mPrimaryCursorPosition, eventData->mPrimaryCursorPosition);
81 impl.ChangeState(EventData::EDITING);
87 ModelPtr& model = impl.mModel;
88 LogicalModelPtr& logicalModel = model->mLogicalModel;
89 model->mVisualModel->SetTextColor(impl.mTextColor);
91 MarkupProcessData markupProcessData(logicalModel->mColorRuns,
92 logicalModel->mFontDescriptionRuns,
93 logicalModel->mEmbeddedItems,
94 logicalModel->mAnchors,
95 logicalModel->mUnderlinedCharacterRuns,
96 logicalModel->mBackgroundColorRuns,
97 logicalModel->mStrikethroughCharacterRuns,
98 logicalModel->mBoundedParagraphRuns,
99 logicalModel->mCharacterSpacingCharacterRuns);
101 Length textSize = 0u;
102 const uint8_t* utf8 = NULL;
103 if(impl.mMarkupProcessorEnabled)
105 MarkupPropertyData markupPropertyData(impl.mAnchorColor, impl.mAnchorClickedColor);
107 ProcessMarkupString(text, markupPropertyData, markupProcessData);
108 textSize = markupProcessData.markupProcessedText.size();
110 // This is a bit horrible but std::string returns a (signed) char*
111 utf8 = reinterpret_cast<const uint8_t*>(markupProcessData.markupProcessedText.c_str());
115 textSize = text.size();
117 // This is a bit horrible but std::string returns a (signed) char*
118 utf8 = reinterpret_cast<const uint8_t*>(text.c_str());
121 // Convert text into UTF-32
122 Vector<Character>& utf32Characters = logicalModel->mText;
123 utf32Characters.Resize(textSize);
125 // Transform a text array encoded in utf8 into an array encoded in utf32.
126 // It returns the actual number of characters.
127 Length characterCount = Utf8ToUtf32(utf8, textSize, utf32Characters.Begin());
128 utf32Characters.Resize(characterCount);
130 DALI_ASSERT_DEBUG(textSize >= characterCount && "Invalid UTF32 conversion length");
131 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Controller::SetText %p UTF8 size %d, UTF32 size %d\n", &controller, textSize, logicalModel->mText.Count());
133 // The characters to be added.
134 impl.mTextUpdateInfo.mNumberOfCharactersToAdd = logicalModel->mText.Count();
136 // To reset the cursor position
137 lastCursorIndex = characterCount;
139 // Update the rest of the model during size negotiation
140 impl.QueueModifyEvent(ModifyEvent::TEXT_REPLACED);
142 // The natural size needs to be re-calculated.
143 impl.mRecalculateNaturalSize = true;
145 // The text direction needs to be updated.
146 impl.mUpdateTextDirection = true;
148 // Apply modifications to the model
149 impl.mOperationsPending = ALL_OPERATIONS;
153 PlaceholderHandler::ShowPlaceholderText(impl);
156 unsigned int oldCursorPos = (nullptr != eventData ? eventData->mPrimaryCursorPosition : 0);
158 // Resets the cursor position.
159 controller.ResetCursorPosition(lastCursorIndex);
161 // Scrolls the text to make the cursor visible.
162 impl.ResetScrollPosition();
164 impl.RequestRelayout();
166 if(nullptr != eventData)
168 // Cancel previously queued events
169 eventData->mEventQueue.clear();
172 // Do this last since it provides callbacks into application code.
173 if(NULL != impl.mEditableControlInterface)
175 impl.mEditableControlInterface->CursorPositionChanged(oldCursorPos, lastCursorIndex);
176 impl.mEditableControlInterface->TextChanged(true);
180 void Controller::TextUpdater::InsertText(Controller& controller, const std::string& text, Controller::InsertType type)
182 Controller::Impl& impl = *controller.mImpl;
183 EventData*& eventData = impl.mEventData;
185 DALI_ASSERT_DEBUG(nullptr != eventData && "Unexpected InsertText")
187 if(NULL == eventData)
192 bool removedPrevious = false;
193 bool removedSelected = false;
194 bool maxLengthReached = false;
195 unsigned int oldCursorPos = eventData->mPrimaryCursorPosition;
197 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Controller::InsertText %p %s (%s) mPrimaryCursorPosition %d mPreEditFlag %d mPreEditStartPosition %d mPreEditLength %d\n", &controller, text.c_str(), (COMMIT == type ? "COMMIT" : "PRE_EDIT"), eventData->mPrimaryCursorPosition, eventData->mPreEditFlag, eventData->mPreEditStartPosition, eventData->mPreEditLength);
199 ModelPtr& model = impl.mModel;
200 LogicalModelPtr& logicalModel = model->mLogicalModel;
202 // TODO: At the moment the underline runs are only for pre-edit.
203 model->mVisualModel->mUnderlineRuns.Clear();
205 // Remove the previous InputMethodContext pre-edit.
206 if(eventData->mPreEditFlag && (0u != eventData->mPreEditLength))
208 removedPrevious = RemoveText(controller,
209 -static_cast<int>(eventData->mPrimaryCursorPosition - eventData->mPreEditStartPosition),
210 eventData->mPreEditLength,
211 DONT_UPDATE_INPUT_STYLE,
214 eventData->mPrimaryCursorPosition = eventData->mPreEditStartPosition;
215 eventData->mPreEditLength = 0u;
219 // Remove the previous Selection.
220 removedSelected = RemoveSelectedText(controller);
223 Vector<Character> utf32Characters;
224 Length characterCount = 0u;
228 std::string redefinedText = text;
230 if(controller.mImpl->mInputFilter != NULL)
232 bool accepted = false;
233 bool rejected = false;
235 accepted = impl.mInputFilter->Filter(Toolkit::InputFilter::Property::ACCEPTED, redefinedText);
236 rejected = impl.mInputFilter->Filter(Toolkit::InputFilter::Property::REJECTED, redefinedText);
240 // Signal emits when the string to be inserted is filtered by the accepted filter.
241 controller.mImpl->mEditableControlInterface->InputFiltered(Toolkit::InputFilter::Property::ACCEPTED);
245 // Signal emits when the string to be inserted is filtered by the rejected filter.
246 controller.mImpl->mEditableControlInterface->InputFiltered(Toolkit::InputFilter::Property::REJECTED);
250 // Convert text into UTF-32
251 utf32Characters.Resize(redefinedText.size());
253 // This is a bit horrible but std::string returns a (signed) char*
254 const uint8_t* utf8 = reinterpret_cast<const uint8_t*>(redefinedText.c_str());
256 // Transform a text array encoded in utf8 into an array encoded in utf32.
257 // It returns the actual number of characters.
258 characterCount = Utf8ToUtf32(utf8, redefinedText.size(), utf32Characters.Begin());
259 utf32Characters.Resize(characterCount);
261 DALI_ASSERT_DEBUG(redefinedText.size() >= utf32Characters.Count() && "Invalid UTF32 conversion length");
262 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "UTF8 size %d, UTF32 size %d\n", redefinedText.size(), utf32Characters.Count());
265 if(0u != utf32Characters.Count()) // Check if Utf8ToUtf32 conversion succeeded
267 // The placeholder text is no longer needed
268 if(impl.IsShowingPlaceholderText())
270 ResetText(controller);
273 impl.ChangeState(EventData::EDITING);
275 // Handle the InputMethodContext (predicitive text) state changes
278 // InputMethodContext is no longer handling key-events
279 impl.ClearPreEditFlag();
283 if(!eventData->mPreEditFlag)
285 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Entered PreEdit state\n");
287 // Record the start of the pre-edit text
288 eventData->mPreEditStartPosition = eventData->mPrimaryCursorPosition;
291 eventData->mPreEditLength = utf32Characters.Count();
292 eventData->mPreEditFlag = true;
294 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "mPreEditStartPosition %d mPreEditLength %d\n", eventData->mPreEditStartPosition, eventData->mPreEditLength);
297 const Length numberOfCharactersInModel = logicalModel->mText.Count();
299 // Restrict new text to fit within Maximum characters setting.
300 Length temp_length = (impl.mMaximumNumberOfCharacters > numberOfCharactersInModel ? impl.mMaximumNumberOfCharacters - numberOfCharactersInModel : 0);
301 Length maxSizeOfNewText = std::min(temp_length, characterCount);
302 maxLengthReached = (characterCount > maxSizeOfNewText);
304 // The cursor position.
305 CharacterIndex& cursorIndex = eventData->mPrimaryCursorPosition;
307 // Update the text's style.
309 // Updates the text style runs by adding characters.
310 logicalModel->UpdateTextStyleRuns(cursorIndex, maxSizeOfNewText);
312 // Get the character index from the cursor index.
313 const CharacterIndex styleIndex = (cursorIndex > 0u) ? cursorIndex - 1u : 0u;
315 // Retrieve the text's style for the given index.
317 impl.RetrieveDefaultInputStyle(style);
318 logicalModel->RetrieveStyle(styleIndex, style);
320 InputStyle& inputStyle = eventData->mInputStyle;
322 // Whether to add a new text color run.
323 const bool addColorRun = (style.textColor != inputStyle.textColor) && !inputStyle.isDefaultColor;
325 // Whether to add a new font run.
326 const bool addFontNameRun = (style.familyName != inputStyle.familyName) && inputStyle.isFamilyDefined;
327 const bool addFontWeightRun = (style.weight != inputStyle.weight) && inputStyle.isWeightDefined;
328 const bool addFontWidthRun = (style.width != inputStyle.width) && inputStyle.isWidthDefined;
329 const bool addFontSlantRun = (style.slant != inputStyle.slant) && inputStyle.isSlantDefined;
330 const bool addFontSizeRun = (!Dali::Equals(style.size, inputStyle.size)) && inputStyle.isSizeDefined;
335 const VectorBase::SizeType numberOfRuns = logicalModel->mColorRuns.Count();
336 logicalModel->mColorRuns.Resize(numberOfRuns + 1u);
338 ColorRun& colorRun = *(logicalModel->mColorRuns.Begin() + numberOfRuns);
339 colorRun.color = inputStyle.textColor;
340 colorRun.characterRun.characterIndex = cursorIndex;
341 colorRun.characterRun.numberOfCharacters = maxSizeOfNewText;
350 const VectorBase::SizeType numberOfRuns = logicalModel->mFontDescriptionRuns.Count();
351 logicalModel->mFontDescriptionRuns.Resize(numberOfRuns + 1u);
353 FontDescriptionRun& fontDescriptionRun = *(logicalModel->mFontDescriptionRuns.Begin() + numberOfRuns);
357 fontDescriptionRun.familyLength = inputStyle.familyName.size();
358 fontDescriptionRun.familyName = new char[fontDescriptionRun.familyLength];
359 memcpy(fontDescriptionRun.familyName, inputStyle.familyName.c_str(), fontDescriptionRun.familyLength);
360 fontDescriptionRun.familyDefined = true;
362 // The memory allocated for the font family name is freed when the font description is removed from the logical model.
367 fontDescriptionRun.weight = inputStyle.weight;
368 fontDescriptionRun.weightDefined = true;
373 fontDescriptionRun.width = inputStyle.width;
374 fontDescriptionRun.widthDefined = true;
379 fontDescriptionRun.slant = inputStyle.slant;
380 fontDescriptionRun.slantDefined = true;
385 fontDescriptionRun.size = static_cast<PointSize26Dot6>(inputStyle.size * impl.GetFontSizeScale() * 64.f);
386 fontDescriptionRun.sizeDefined = true;
389 fontDescriptionRun.characterRun.characterIndex = cursorIndex;
390 fontDescriptionRun.characterRun.numberOfCharacters = maxSizeOfNewText;
393 // Insert at current cursor position.
394 Vector<Character>& modifyText = logicalModel->mText;
396 auto pos = modifyText.End();
397 if(cursorIndex < numberOfCharactersInModel)
399 pos = modifyText.Begin() + cursorIndex;
401 unsigned int realPos = static_cast<unsigned int>(pos - modifyText.Begin());
402 modifyText.Insert(pos, utf32Characters.Begin(), utf32Characters.Begin() + maxSizeOfNewText);
404 if(NULL != impl.mEditableControlInterface)
406 impl.mEditableControlInterface->TextInserted(realPos, maxSizeOfNewText, text);
409 TextUpdateInfo& textUpdateInfo = impl.mTextUpdateInfo;
411 // Mark the first paragraph to be updated.
412 if(Layout::Engine::SINGLE_LINE_BOX == impl.mLayoutEngine.GetLayout())
414 textUpdateInfo.mCharacterIndex = 0;
415 textUpdateInfo.mNumberOfCharactersToRemove = textUpdateInfo.mPreviousNumberOfCharacters;
416 textUpdateInfo.mNumberOfCharactersToAdd = numberOfCharactersInModel + maxSizeOfNewText;
417 textUpdateInfo.mClearAll = true;
421 textUpdateInfo.mCharacterIndex = std::min(cursorIndex, textUpdateInfo.mCharacterIndex);
422 textUpdateInfo.mNumberOfCharactersToAdd += maxSizeOfNewText;
425 if(impl.mMarkupProcessorEnabled)
427 InsertTextAnchor(controller, maxSizeOfNewText, cursorIndex);
430 // Update the cursor index.
431 cursorIndex += maxSizeOfNewText;
433 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Inserted %d characters, new size %d new cursor %d\n", maxSizeOfNewText, logicalModel->mText.Count(), eventData->mPrimaryCursorPosition);
436 if((0u == logicalModel->mText.Count()) &&
437 impl.IsPlaceholderAvailable())
439 // Show place-holder if empty after removing the pre-edit text
440 PlaceholderHandler::ShowPlaceholderText(impl);
441 eventData->mUpdateCursorPosition = true;
442 impl.ClearPreEditFlag();
444 else if(removedPrevious ||
446 (0 != utf32Characters.Count()))
448 // Queue an inserted event
449 impl.QueueModifyEvent(ModifyEvent::TEXT_INSERTED);
451 eventData->mUpdateCursorPosition = true;
454 eventData->mScrollAfterDelete = true;
458 eventData->mScrollAfterUpdatePosition = true;
462 if(nullptr != impl.mEditableControlInterface)
464 impl.mEditableControlInterface->CursorPositionChanged(oldCursorPos, eventData->mPrimaryCursorPosition);
469 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "MaxLengthReached (%d)\n", logicalModel->mText.Count());
471 impl.ResetInputMethodContext();
473 if(NULL != impl.mEditableControlInterface)
475 // Do this last since it provides callbacks into application code
476 impl.mEditableControlInterface->MaxLengthReached();
481 void Controller::TextUpdater::PasteText(Controller& controller, const std::string& stringToPaste)
483 InsertText(controller, stringToPaste, Text::Controller::COMMIT);
484 Controller::Impl& impl = *controller.mImpl;
485 impl.ChangeState(EventData::EDITING);
486 impl.RequestRelayout();
488 if(NULL != impl.mEditableControlInterface)
490 // Do this last since it provides callbacks into application code
491 impl.mEditableControlInterface->TextChanged(true);
495 bool Controller::TextUpdater::RemoveText(
496 Controller& controller,
498 int numberOfCharacters,
499 UpdateInputStyleType type,
500 bool isDeletingPreEdit)
502 bool removed = false;
503 bool removeAll = false;
505 Controller::Impl& impl = *controller.mImpl;
506 EventData*& eventData = impl.mEventData;
508 if(nullptr == eventData)
513 ModelPtr& model = impl.mModel;
514 LogicalModelPtr& logicalModel = model->mLogicalModel;
515 VisualModelPtr& visualModel = model->mVisualModel;
517 DALI_LOG_INFO(gLogFilter, Debug::General, "Controller::RemoveText %p mText.Count() %d cursor %d cursorOffset %d numberOfCharacters %d\n", &controller, logicalModel->mText.Count(), eventData->mPrimaryCursorPosition, cursorOffset, numberOfCharacters);
519 if(!impl.IsShowingPlaceholderText())
521 // Delete at current cursor position
522 Vector<Character>& currentText = logicalModel->mText;
523 CharacterIndex& previousCursorIndex = eventData->mPrimaryCursorPosition;
525 CharacterIndex cursorIndex = 0;
527 // Validate the cursor position & number of characters
528 if((static_cast<int>(eventData->mPrimaryCursorPosition) + cursorOffset) >= 0)
530 cursorIndex = eventData->mPrimaryCursorPosition + cursorOffset;
533 //Handle Emoji clustering for cursor handling
534 // Deletion case: this is handling the deletion cases when the cursor is before or after Emoji
535 // - Before: when use delete key and cursor is before Emoji (cursorOffset = -1)
536 // - After: when use backspace key and cursor is after Emoji (cursorOffset = 0)
538 const Script script = logicalModel->GetScript(cursorIndex);
539 if((numberOfCharacters == 1u) &&
540 (IsOneOfEmojiScripts(script)))
542 //TODO: Use this clustering for Emoji cases only. This needs more testing to generalize to all scripts.
543 CharacterRun emojiClusteredCharacters = RetrieveClusteredCharactersOfCharacterIndex(visualModel, logicalModel, cursorIndex);
544 Length actualNumberOfCharacters = emojiClusteredCharacters.numberOfCharacters;
546 //Set cursorIndex at the first characterIndex of clustred Emoji
547 cursorIndex = emojiClusteredCharacters.characterIndex;
549 numberOfCharacters = actualNumberOfCharacters;
552 if((cursorIndex + numberOfCharacters) > currentText.Count())
554 numberOfCharacters = currentText.Count() - cursorIndex;
557 if((cursorIndex == 0) && (currentText.Count() - numberOfCharacters == 0))
562 TextUpdateInfo& textUpdateInfo = impl.mTextUpdateInfo;
564 if(eventData->mPreEditFlag || removeAll || // If the preedit flag is enabled, it means two (or more) of them came together i.e. when two keys have been pressed at the same time.
565 ((cursorIndex + numberOfCharacters) <= textUpdateInfo.mPreviousNumberOfCharacters))
567 // Mark the paragraphs to be updated.
568 if(Layout::Engine::SINGLE_LINE_BOX == impl.mLayoutEngine.GetLayout())
570 textUpdateInfo.mCharacterIndex = 0;
571 textUpdateInfo.mNumberOfCharactersToRemove = textUpdateInfo.mPreviousNumberOfCharacters;
572 textUpdateInfo.mNumberOfCharactersToAdd = textUpdateInfo.mPreviousNumberOfCharacters - numberOfCharacters;
573 textUpdateInfo.mClearAll = true;
577 textUpdateInfo.mCharacterIndex = std::min(cursorIndex, textUpdateInfo.mCharacterIndex);
578 textUpdateInfo.mNumberOfCharactersToRemove += numberOfCharacters;
581 // Update the input style and remove the text's style before removing the text.
583 if(UPDATE_INPUT_STYLE == type)
585 InputStyle& eventDataInputStyle = eventData->mInputStyle;
587 // Keep a copy of the current input style.
588 InputStyle currentInputStyle;
589 currentInputStyle.Copy(eventDataInputStyle);
591 // Set first the default input style.
592 impl.RetrieveDefaultInputStyle(eventDataInputStyle);
594 // Update the input style.
595 logicalModel->RetrieveStyle(cursorIndex, eventDataInputStyle);
597 // Compare if the input style has changed.
598 const bool hasInputStyleChanged = !currentInputStyle.Equal(eventDataInputStyle);
600 if(hasInputStyleChanged)
602 const InputStyle::Mask styleChangedMask = currentInputStyle.GetInputStyleChangeMask(eventDataInputStyle);
603 // Queue the input style changed signal.
604 eventData->mInputStyleChangedQueue.PushBack(styleChangedMask);
608 // If the number of current text and the number of characters to be deleted are same,
609 // it means all texts should be removed and all Preedit variables should be initialized.
612 impl.ClearPreEditFlag();
613 if(!isDeletingPreEdit)
615 textUpdateInfo.mNumberOfCharactersToAdd = 0;
619 // Updates the text style runs by removing characters. Runs with no characters are removed.
620 logicalModel->UpdateTextStyleRuns(cursorIndex, -numberOfCharacters);
622 // Remove the characters.
623 Vector<Character>::Iterator first = currentText.Begin() + cursorIndex;
624 Vector<Character>::Iterator last = first + numberOfCharacters;
626 if(NULL != impl.mEditableControlInterface)
629 Utf32ToUtf8(first, numberOfCharacters, utf8);
630 impl.mEditableControlInterface->TextDeleted(cursorIndex, numberOfCharacters, utf8);
633 currentText.Erase(first, last);
635 if(impl.mMarkupProcessorEnabled)
637 RemoveTextAnchor(controller, cursorOffset, numberOfCharacters, previousCursorIndex);
640 if(nullptr != impl.mEditableControlInterface)
642 impl.mEditableControlInterface->CursorPositionChanged(previousCursorIndex, cursorIndex);
645 // Cursor position retreat
646 previousCursorIndex = cursorIndex;
648 eventData->mScrollAfterDelete = true;
650 if(EventData::INACTIVE == eventData->mState)
652 impl.ChangeState(EventData::EDITING);
655 DALI_LOG_INFO(gLogFilter, Debug::General, "Controller::RemoveText %p removed %d\n", &controller, numberOfCharacters);
664 bool Controller::TextUpdater::RemoveSelectedText(Controller& controller)
666 bool textRemoved(false);
668 Controller::Impl& impl = *controller.mImpl;
670 if(EventData::SELECTING == impl.mEventData->mState)
672 std::string removedString;
673 uint32_t oldSelStart = impl.mEventData->mLeftSelectionPosition;
674 uint32_t oldSelEnd = impl.mEventData->mRightSelectionPosition;
676 impl.RetrieveSelection(removedString, true);
678 if(!removedString.empty())
681 impl.ChangeState(EventData::EDITING);
683 if(impl.mMarkupProcessorEnabled)
685 int cursorOffset = -1;
686 int numberOfCharacters = removedString.length();
687 CharacterIndex& cursorIndex = impl.mEventData->mPrimaryCursorPosition;
688 CharacterIndex previousCursorIndex = cursorIndex + numberOfCharacters;
690 RemoveTextAnchor(controller, cursorOffset, numberOfCharacters, previousCursorIndex);
693 if(impl.mSelectableControlInterface != nullptr)
695 impl.mSelectableControlInterface->SelectionChanged(oldSelStart, oldSelEnd, impl.mEventData->mPrimaryCursorPosition, impl.mEventData->mPrimaryCursorPosition);
703 void Controller::TextUpdater::ResetText(Controller& controller)
705 Controller::Impl& impl = *controller.mImpl;
706 LogicalModelPtr& logicalModel = impl.mModel->mLogicalModel;
708 // Reset spanned-text
709 logicalModel->mSpannedTextPlaced = false;
712 logicalModel->mText.Clear();
714 // Reset the embedded images buffer.
715 logicalModel->ClearEmbeddedImages();
717 // Reset the anchors buffer.
718 logicalModel->ClearAnchors();
720 // We have cleared everything including the placeholder-text
721 impl.PlaceholderCleared();
723 impl.mTextUpdateInfo.mCharacterIndex = 0u;
724 impl.mTextUpdateInfo.mNumberOfCharactersToRemove = impl.mTextUpdateInfo.mPreviousNumberOfCharacters;
725 impl.mTextUpdateInfo.mNumberOfCharactersToAdd = 0u;
727 // Clear any previous text.
728 impl.mTextUpdateInfo.mClearAll = true;
730 // The natural size needs to be re-calculated.
731 impl.mRecalculateNaturalSize = true;
733 // The text direction needs to be updated.
734 impl.mUpdateTextDirection = true;
736 // Apply modifications to the model
737 impl.mOperationsPending = ALL_OPERATIONS;
740 void Controller::TextUpdater::InsertTextAnchor(Controller& controller, int numberOfCharacters, CharacterIndex previousCursorIndex)
742 Controller::Impl& impl = *controller.mImpl;
743 ModelPtr& model = impl.mModel;
744 LogicalModelPtr& logicalModel = model->mLogicalModel;
746 for(auto& anchor : logicalModel->mAnchors)
748 if(anchor.endIndex < previousCursorIndex) // [anchor] CUR
752 if(anchor.startIndex < previousCursorIndex) // [anCURr]
754 anchor.endIndex += numberOfCharacters;
758 anchor.startIndex += numberOfCharacters;
759 anchor.endIndex += numberOfCharacters;
761 DALI_LOG_INFO(gLogFilter, Debug::General, "Controller::InsertTextAnchor[%p] Anchor[%s] start[%d] end[%d]\n", &controller, anchor.href, anchor.startIndex, anchor.endIndex);
765 void Controller::TextUpdater::RemoveTextAnchor(Controller& controller, int cursorOffset, int numberOfCharacters, CharacterIndex previousCursorIndex)
767 Controller::Impl& impl = *controller.mImpl;
768 ModelPtr& model = impl.mModel;
769 LogicalModelPtr& logicalModel = model->mLogicalModel;
770 Vector<Anchor>::Iterator it = logicalModel->mAnchors.Begin();
772 while(it != logicalModel->mAnchors.End())
774 Anchor& anchor = *it;
776 if(anchor.endIndex <= previousCursorIndex && cursorOffset == 0) // [anchor] CUR >>
780 else if(anchor.endIndex <= previousCursorIndex && cursorOffset == -1) // [anchor] << CUR
782 int endIndex = anchor.endIndex;
783 int offset = previousCursorIndex - endIndex;
784 int index = endIndex - (numberOfCharacters - offset);
791 if((int)anchor.startIndex >= endIndex)
795 delete[] anchor.href;
797 it = logicalModel->mAnchors.Erase(it);
802 anchor.endIndex = endIndex;
805 else if(anchor.startIndex >= previousCursorIndex && cursorOffset == -1) // << CUR [anchor]
807 anchor.startIndex -= numberOfCharacters;
808 anchor.endIndex -= numberOfCharacters;
810 else if(anchor.startIndex >= previousCursorIndex && cursorOffset == 0) // CUR >> [anchor]
812 int startIndex = anchor.startIndex;
813 int endIndex = anchor.endIndex;
814 int index = previousCursorIndex + numberOfCharacters - 1;
816 if(startIndex > index)
818 anchor.startIndex -= numberOfCharacters;
819 anchor.endIndex -= numberOfCharacters;
821 else if(endIndex > index + 1)
823 anchor.endIndex -= numberOfCharacters;
829 delete[] anchor.href;
831 it = logicalModel->mAnchors.Erase(it);
835 else if(cursorOffset == -1) // [<< CUR]
837 int startIndex = anchor.startIndex;
838 int index = previousCursorIndex - numberOfCharacters;
840 if(startIndex >= index)
842 anchor.startIndex = index;
844 anchor.endIndex -= numberOfCharacters;
846 else if(cursorOffset == 0) // [CUR >>]
848 anchor.endIndex -= numberOfCharacters;
852 // When this condition is reached, someting is wrong.
853 DALI_LOG_ERROR("Controller::RemoveTextAnchor[%p] Invaild state cursorOffset[%d]\n", &controller, cursorOffset);
856 DALI_LOG_INFO(gLogFilter, Debug::General, "Controller::RemoveTextAnchor[%p] Anchor[%s] start[%d] end[%d]\n", &controller, anchor.href, anchor.startIndex, anchor.endIndex);
864 } // namespace Toolkit