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/text-controller-text-updater.h>
22 #include <dali/integration-api/debug.h>
26 #include <dali-toolkit/internal/text/character-set-conversion.h>
27 #include <dali-toolkit/internal/text/characters-helper-functions.h>
28 #include <dali-toolkit/internal/text/emoji-helper.h>
29 #include <dali-toolkit/internal/text/markup-processor.h>
30 #include <dali-toolkit/internal/text/text-controller-impl.h>
31 #include <dali-toolkit/internal/text/text-controller-placeholder-handler.h>
32 #include <dali-toolkit/internal/text/text-editable-control-interface.h>
36 #if defined(DEBUG_ENABLED)
37 Debug::Filter* gLogFilter = Debug::Filter::New(Debug::NoLogging, true, "LOG_TEXT_CONTROLS");
48 void Controller::TextUpdater::SetText(Controller& controller, const std::string& text)
50 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Controller::SetText\n");
52 Controller::Impl& impl = *controller.mImpl;
54 // Reset keyboard as text changed
55 impl.ResetInputMethodContext();
57 // Remove the previously set text and style.
58 ResetText(controller);
61 impl.ClearStyleData();
63 CharacterIndex lastCursorIndex = 0u;
65 EventData*& eventData = impl.mEventData;
67 if(nullptr != eventData)
69 // If popup shown then hide it by switching to Editing state
70 if((EventData::SELECTING == eventData->mState) ||
71 (EventData::EDITING_WITH_POPUP == eventData->mState) ||
72 (EventData::EDITING_WITH_GRAB_HANDLE == eventData->mState) ||
73 (EventData::EDITING_WITH_PASTE_POPUP == eventData->mState))
75 if((impl.mSelectableControlInterface != nullptr) && (EventData::SELECTING == eventData->mState))
77 impl.mSelectableControlInterface->SelectionChanged(eventData->mLeftSelectionPosition, eventData->mRightSelectionPosition, eventData->mPrimaryCursorPosition, eventData->mPrimaryCursorPosition);
80 impl.ChangeState(EventData::EDITING);
86 ModelPtr& model = impl.mModel;
87 LogicalModelPtr& logicalModel = model->mLogicalModel;
88 model->mVisualModel->SetTextColor(impl.mTextColor);
90 MarkupProcessData markupProcessData(logicalModel->mColorRuns,
91 logicalModel->mFontDescriptionRuns,
92 logicalModel->mEmbeddedItems,
93 logicalModel->mAnchors,
94 logicalModel->mUnderlinedCharacterRuns,
95 logicalModel->mBackgroundColorRuns,
96 logicalModel->mStrikethroughCharacterRuns,
97 logicalModel->mBoundedParagraphRuns,
98 logicalModel->mCharacterSpacingCharacterRuns);
100 Length textSize = 0u;
101 const uint8_t* utf8 = NULL;
102 if(impl.mMarkupProcessorEnabled)
104 ProcessMarkupString(text, markupProcessData);
105 textSize = markupProcessData.markupProcessedText.size();
107 // This is a bit horrible but std::string returns a (signed) char*
108 utf8 = reinterpret_cast<const uint8_t*>(markupProcessData.markupProcessedText.c_str());
112 textSize = text.size();
114 // This is a bit horrible but std::string returns a (signed) char*
115 utf8 = reinterpret_cast<const uint8_t*>(text.c_str());
118 // Convert text into UTF-32
119 Vector<Character>& utf32Characters = logicalModel->mText;
120 utf32Characters.Resize(textSize);
122 // Transform a text array encoded in utf8 into an array encoded in utf32.
123 // It returns the actual number of characters.
124 Length characterCount = Utf8ToUtf32(utf8, textSize, utf32Characters.Begin());
125 utf32Characters.Resize(characterCount);
127 DALI_ASSERT_DEBUG(textSize >= characterCount && "Invalid UTF32 conversion length");
128 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Controller::SetText %p UTF8 size %d, UTF32 size %d\n", &controller, textSize, logicalModel->mText.Count());
130 // The characters to be added.
131 impl.mTextUpdateInfo.mNumberOfCharactersToAdd = logicalModel->mText.Count();
133 // To reset the cursor position
134 lastCursorIndex = characterCount;
136 // Update the rest of the model during size negotiation
137 impl.QueueModifyEvent(ModifyEvent::TEXT_REPLACED);
139 // The natural size needs to be re-calculated.
140 impl.mRecalculateNaturalSize = true;
142 // The text direction needs to be updated.
143 impl.mUpdateTextDirection = true;
145 // Apply modifications to the model
146 impl.mOperationsPending = ALL_OPERATIONS;
150 PlaceholderHandler::ShowPlaceholderText(impl);
153 unsigned int oldCursorPos = (nullptr != eventData ? eventData->mPrimaryCursorPosition : 0);
155 // Resets the cursor position.
156 controller.ResetCursorPosition(lastCursorIndex);
158 // Scrolls the text to make the cursor visible.
159 impl.ResetScrollPosition();
161 impl.RequestRelayout();
163 if(nullptr != eventData)
165 // Cancel previously queued events
166 eventData->mEventQueue.clear();
169 // Do this last since it provides callbacks into application code.
170 if(NULL != impl.mEditableControlInterface)
172 impl.mEditableControlInterface->CursorPositionChanged(oldCursorPos, lastCursorIndex);
173 impl.mEditableControlInterface->TextChanged(true);
177 void Controller::TextUpdater::InsertText(Controller& controller, const std::string& text, Controller::InsertType type)
179 Controller::Impl& impl = *controller.mImpl;
180 EventData*& eventData = impl.mEventData;
182 DALI_ASSERT_DEBUG(nullptr != eventData && "Unexpected InsertText")
184 if(NULL == eventData)
189 bool removedPrevious = false;
190 bool removedSelected = false;
191 bool maxLengthReached = false;
192 unsigned int oldCursorPos = eventData->mPrimaryCursorPosition;
194 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);
196 ModelPtr& model = impl.mModel;
197 LogicalModelPtr& logicalModel = model->mLogicalModel;
199 // TODO: At the moment the underline runs are only for pre-edit.
200 model->mVisualModel->mUnderlineRuns.Clear();
202 // Remove the previous InputMethodContext pre-edit.
203 if(eventData->mPreEditFlag && (0u != eventData->mPreEditLength))
205 removedPrevious = RemoveText(controller,
206 -static_cast<int>(eventData->mPrimaryCursorPosition - eventData->mPreEditStartPosition),
207 eventData->mPreEditLength,
208 DONT_UPDATE_INPUT_STYLE);
210 eventData->mPrimaryCursorPosition = eventData->mPreEditStartPosition;
211 eventData->mPreEditLength = 0u;
215 // Remove the previous Selection.
216 removedSelected = RemoveSelectedText(controller);
219 Vector<Character> utf32Characters;
220 Length characterCount = 0u;
224 // Convert text into UTF-32
225 utf32Characters.Resize(text.size());
227 // This is a bit horrible but std::string returns a (signed) char*
228 const uint8_t* utf8 = reinterpret_cast<const uint8_t*>(text.c_str());
230 // Transform a text array encoded in utf8 into an array encoded in utf32.
231 // It returns the actual number of characters.
232 characterCount = Utf8ToUtf32(utf8, text.size(), utf32Characters.Begin());
233 utf32Characters.Resize(characterCount);
235 DALI_ASSERT_DEBUG(text.size() >= utf32Characters.Count() && "Invalid UTF32 conversion length");
236 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "UTF8 size %d, UTF32 size %d\n", text.size(), utf32Characters.Count());
239 if(0u != utf32Characters.Count()) // Check if Utf8ToUtf32 conversion succeeded
241 // The placeholder text is no longer needed
242 if(impl.IsShowingPlaceholderText())
244 ResetText(controller);
247 impl.ChangeState(EventData::EDITING);
249 // Handle the InputMethodContext (predicitive text) state changes
252 // InputMethodContext is no longer handling key-events
253 impl.ClearPreEditFlag();
257 if(!eventData->mPreEditFlag)
259 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Entered PreEdit state\n");
261 // Record the start of the pre-edit text
262 eventData->mPreEditStartPosition = eventData->mPrimaryCursorPosition;
265 eventData->mPreEditLength = utf32Characters.Count();
266 eventData->mPreEditFlag = true;
268 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "mPreEditStartPosition %d mPreEditLength %d\n", eventData->mPreEditStartPosition, eventData->mPreEditLength);
271 const Length numberOfCharactersInModel = logicalModel->mText.Count();
273 // Restrict new text to fit within Maximum characters setting.
274 Length temp_length = (impl.mMaximumNumberOfCharacters > numberOfCharactersInModel ? impl.mMaximumNumberOfCharacters - numberOfCharactersInModel : 0);
275 Length maxSizeOfNewText = std::min(temp_length, characterCount);
276 maxLengthReached = (characterCount > maxSizeOfNewText);
278 // The cursor position.
279 CharacterIndex& cursorIndex = eventData->mPrimaryCursorPosition;
281 // Update the text's style.
283 // Updates the text style runs by adding characters.
284 logicalModel->UpdateTextStyleRuns(cursorIndex, maxSizeOfNewText);
286 // Get the character index from the cursor index.
287 const CharacterIndex styleIndex = (cursorIndex > 0u) ? cursorIndex - 1u : 0u;
289 // Retrieve the text's style for the given index.
291 impl.RetrieveDefaultInputStyle(style);
292 logicalModel->RetrieveStyle(styleIndex, style);
294 InputStyle& inputStyle = eventData->mInputStyle;
296 // Whether to add a new text color run.
297 const bool addColorRun = (style.textColor != inputStyle.textColor) && !inputStyle.isDefaultColor;
299 // Whether to add a new font run.
300 const bool addFontNameRun = (style.familyName != inputStyle.familyName) && inputStyle.isFamilyDefined;
301 const bool addFontWeightRun = (style.weight != inputStyle.weight) && inputStyle.isWeightDefined;
302 const bool addFontWidthRun = (style.width != inputStyle.width) && inputStyle.isWidthDefined;
303 const bool addFontSlantRun = (style.slant != inputStyle.slant) && inputStyle.isSlantDefined;
304 const bool addFontSizeRun = (style.size != inputStyle.size) && inputStyle.isSizeDefined;
309 const VectorBase::SizeType numberOfRuns = logicalModel->mColorRuns.Count();
310 logicalModel->mColorRuns.Resize(numberOfRuns + 1u);
312 ColorRun& colorRun = *(logicalModel->mColorRuns.Begin() + numberOfRuns);
313 colorRun.color = inputStyle.textColor;
314 colorRun.characterRun.characterIndex = cursorIndex;
315 colorRun.characterRun.numberOfCharacters = maxSizeOfNewText;
324 const VectorBase::SizeType numberOfRuns = logicalModel->mFontDescriptionRuns.Count();
325 logicalModel->mFontDescriptionRuns.Resize(numberOfRuns + 1u);
327 FontDescriptionRun& fontDescriptionRun = *(logicalModel->mFontDescriptionRuns.Begin() + numberOfRuns);
331 fontDescriptionRun.familyLength = inputStyle.familyName.size();
332 fontDescriptionRun.familyName = new char[fontDescriptionRun.familyLength];
333 memcpy(fontDescriptionRun.familyName, inputStyle.familyName.c_str(), fontDescriptionRun.familyLength);
334 fontDescriptionRun.familyDefined = true;
336 // The memory allocated for the font family name is freed when the font description is removed from the logical model.
341 fontDescriptionRun.weight = inputStyle.weight;
342 fontDescriptionRun.weightDefined = true;
347 fontDescriptionRun.width = inputStyle.width;
348 fontDescriptionRun.widthDefined = true;
353 fontDescriptionRun.slant = inputStyle.slant;
354 fontDescriptionRun.slantDefined = true;
359 fontDescriptionRun.size = static_cast<PointSize26Dot6>(inputStyle.size * impl.GetFontSizeScale() * 64.f);
360 fontDescriptionRun.sizeDefined = true;
363 fontDescriptionRun.characterRun.characterIndex = cursorIndex;
364 fontDescriptionRun.characterRun.numberOfCharacters = maxSizeOfNewText;
367 // Insert at current cursor position.
368 Vector<Character>& modifyText = logicalModel->mText;
370 auto pos = modifyText.End();
371 if(cursorIndex < numberOfCharactersInModel)
373 pos = modifyText.Begin() + cursorIndex;
375 unsigned int realPos = pos - modifyText.Begin();
376 modifyText.Insert(pos, utf32Characters.Begin(), utf32Characters.Begin() + maxSizeOfNewText);
378 if(NULL != impl.mEditableControlInterface)
380 impl.mEditableControlInterface->TextInserted(realPos, maxSizeOfNewText, text);
383 TextUpdateInfo& textUpdateInfo = impl.mTextUpdateInfo;
385 // Mark the first paragraph to be updated.
386 if(Layout::Engine::SINGLE_LINE_BOX == impl.mLayoutEngine.GetLayout())
388 textUpdateInfo.mCharacterIndex = 0;
389 textUpdateInfo.mNumberOfCharactersToRemove = textUpdateInfo.mPreviousNumberOfCharacters;
390 textUpdateInfo.mNumberOfCharactersToAdd = numberOfCharactersInModel + maxSizeOfNewText;
391 textUpdateInfo.mClearAll = true;
395 textUpdateInfo.mCharacterIndex = std::min(cursorIndex, textUpdateInfo.mCharacterIndex);
396 textUpdateInfo.mNumberOfCharactersToAdd += maxSizeOfNewText;
399 if(impl.mMarkupProcessorEnabled)
401 InsertTextAnchor(controller, maxSizeOfNewText, cursorIndex);
404 // Update the cursor index.
405 cursorIndex += maxSizeOfNewText;
407 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Inserted %d characters, new size %d new cursor %d\n", maxSizeOfNewText, logicalModel->mText.Count(), eventData->mPrimaryCursorPosition);
410 if((0u == logicalModel->mText.Count()) &&
411 impl.IsPlaceholderAvailable())
413 // Show place-holder if empty after removing the pre-edit text
414 PlaceholderHandler::ShowPlaceholderText(impl);
415 eventData->mUpdateCursorPosition = true;
416 impl.ClearPreEditFlag();
418 else if(removedPrevious ||
420 (0 != utf32Characters.Count()))
422 // Queue an inserted event
423 impl.QueueModifyEvent(ModifyEvent::TEXT_INSERTED);
425 eventData->mUpdateCursorPosition = true;
428 eventData->mScrollAfterDelete = true;
432 eventData->mScrollAfterUpdatePosition = true;
436 if(nullptr != impl.mEditableControlInterface)
438 impl.mEditableControlInterface->CursorPositionChanged(oldCursorPos, eventData->mPrimaryCursorPosition);
443 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "MaxLengthReached (%d)\n", logicalModel->mText.Count());
445 impl.ResetInputMethodContext();
447 if(NULL != impl.mEditableControlInterface)
449 // Do this last since it provides callbacks into application code
450 impl.mEditableControlInterface->MaxLengthReached();
455 void Controller::TextUpdater::PasteText(Controller& controller, const std::string& stringToPaste)
457 InsertText(controller, stringToPaste, Text::Controller::COMMIT);
458 Controller::Impl& impl = *controller.mImpl;
459 impl.ChangeState(EventData::EDITING);
460 impl.RequestRelayout();
462 if(NULL != impl.mEditableControlInterface)
464 // Do this last since it provides callbacks into application code
465 impl.mEditableControlInterface->TextChanged(true);
469 bool Controller::TextUpdater::RemoveText(
470 Controller& controller,
472 int numberOfCharacters,
473 UpdateInputStyleType type)
475 bool removed = false;
477 Controller::Impl& impl = *controller.mImpl;
478 EventData*& eventData = impl.mEventData;
480 if(nullptr == eventData)
485 ModelPtr& model = impl.mModel;
486 LogicalModelPtr& logicalModel = model->mLogicalModel;
487 VisualModelPtr& visualModel = model->mVisualModel;
489 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);
491 if(!impl.IsShowingPlaceholderText())
493 // Delete at current cursor position
494 Vector<Character>& currentText = logicalModel->mText;
495 CharacterIndex& previousCursorIndex = eventData->mPrimaryCursorPosition;
497 CharacterIndex cursorIndex = 0;
499 // Validate the cursor position & number of characters
500 if((static_cast<int>(eventData->mPrimaryCursorPosition) + cursorOffset) >= 0)
502 cursorIndex = eventData->mPrimaryCursorPosition + cursorOffset;
505 //Handle Emoji clustering for cursor handling
506 // Deletion case: this is handling the deletion cases when the cursor is before or after Emoji
507 // - Before: when use delete key and cursor is before Emoji (cursorOffset = -1)
508 // - After: when use backspace key and cursor is after Emoji (cursorOffset = 0)
510 const Script script = logicalModel->GetScript(cursorIndex);
511 if((numberOfCharacters == 1u) &&
512 (IsOneOfEmojiScripts(script)))
514 //TODO: Use this clustering for Emoji cases only. This needs more testing to generalize to all scripts.
515 CharacterRun emojiClusteredCharacters = RetrieveClusteredCharactersOfCharacterIndex(visualModel, logicalModel, cursorIndex);
516 Length actualNumberOfCharacters = emojiClusteredCharacters.numberOfCharacters;
518 //Set cursorIndex at the first characterIndex of clustred Emoji
519 cursorIndex = emojiClusteredCharacters.characterIndex;
521 numberOfCharacters = actualNumberOfCharacters;
524 if((cursorIndex + numberOfCharacters) > currentText.Count())
526 numberOfCharacters = currentText.Count() - cursorIndex;
529 TextUpdateInfo& textUpdateInfo = impl.mTextUpdateInfo;
531 if(eventData->mPreEditFlag || // 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.
532 ((cursorIndex + numberOfCharacters) <= textUpdateInfo.mPreviousNumberOfCharacters))
534 // Mark the paragraphs to be updated.
535 if(Layout::Engine::SINGLE_LINE_BOX == impl.mLayoutEngine.GetLayout())
537 textUpdateInfo.mCharacterIndex = 0;
538 textUpdateInfo.mNumberOfCharactersToRemove = textUpdateInfo.mPreviousNumberOfCharacters;
539 textUpdateInfo.mNumberOfCharactersToAdd = textUpdateInfo.mPreviousNumberOfCharacters - numberOfCharacters;
540 textUpdateInfo.mClearAll = true;
544 textUpdateInfo.mCharacterIndex = std::min(cursorIndex, textUpdateInfo.mCharacterIndex);
545 textUpdateInfo.mNumberOfCharactersToRemove += numberOfCharacters;
548 // Update the input style and remove the text's style before removing the text.
550 if(UPDATE_INPUT_STYLE == type)
552 InputStyle& eventDataInputStyle = eventData->mInputStyle;
554 // Keep a copy of the current input style.
555 InputStyle currentInputStyle;
556 currentInputStyle.Copy(eventDataInputStyle);
558 // Set first the default input style.
559 impl.RetrieveDefaultInputStyle(eventDataInputStyle);
561 // Update the input style.
562 logicalModel->RetrieveStyle(cursorIndex, eventDataInputStyle);
564 // Compare if the input style has changed.
565 const bool hasInputStyleChanged = !currentInputStyle.Equal(eventDataInputStyle);
567 if(hasInputStyleChanged)
569 const InputStyle::Mask styleChangedMask = currentInputStyle.GetInputStyleChangeMask(eventDataInputStyle);
570 // Queue the input style changed signal.
571 eventData->mInputStyleChangedQueue.PushBack(styleChangedMask);
575 // If the number of current text and the number of characters to be deleted are same,
576 // it means all texts should be removed and all Preedit variables should be initialized.
577 if((currentText.Count() - numberOfCharacters == 0) && (cursorIndex == 0))
579 impl.ClearPreEditFlag();
580 textUpdateInfo.mNumberOfCharactersToAdd = 0;
583 // Updates the text style runs by removing characters. Runs with no characters are removed.
584 logicalModel->UpdateTextStyleRuns(cursorIndex, -numberOfCharacters);
586 // Remove the characters.
587 Vector<Character>::Iterator first = currentText.Begin() + cursorIndex;
588 Vector<Character>::Iterator last = first + numberOfCharacters;
590 if(NULL != impl.mEditableControlInterface)
593 Utf32ToUtf8(first, numberOfCharacters, utf8);
594 impl.mEditableControlInterface->TextDeleted(cursorIndex, numberOfCharacters, utf8);
597 currentText.Erase(first, last);
599 if(impl.mMarkupProcessorEnabled)
601 RemoveTextAnchor(controller, cursorOffset, numberOfCharacters, previousCursorIndex);
604 if(nullptr != impl.mEditableControlInterface)
606 impl.mEditableControlInterface->CursorPositionChanged(previousCursorIndex, cursorIndex);
609 // Cursor position retreat
610 previousCursorIndex = cursorIndex;
612 eventData->mScrollAfterDelete = true;
614 if(EventData::INACTIVE == eventData->mState)
616 impl.ChangeState(EventData::EDITING);
619 DALI_LOG_INFO(gLogFilter, Debug::General, "Controller::RemoveText %p removed %d\n", &controller, numberOfCharacters);
627 bool Controller::TextUpdater::RemoveSelectedText(Controller& controller)
629 bool textRemoved(false);
631 Controller::Impl& impl = *controller.mImpl;
633 if(EventData::SELECTING == impl.mEventData->mState)
635 std::string removedString;
636 uint32_t oldSelStart = impl.mEventData->mLeftSelectionPosition;
637 uint32_t oldSelEnd = impl.mEventData->mRightSelectionPosition;
639 impl.RetrieveSelection(removedString, true);
641 if(!removedString.empty())
644 impl.ChangeState(EventData::EDITING);
646 if(impl.mMarkupProcessorEnabled)
648 int cursorOffset = -1;
649 int numberOfCharacters = removedString.length();
650 CharacterIndex& cursorIndex = impl.mEventData->mPrimaryCursorPosition;
651 CharacterIndex previousCursorIndex = cursorIndex + numberOfCharacters;
653 RemoveTextAnchor(controller, cursorOffset, numberOfCharacters, previousCursorIndex);
656 if(impl.mSelectableControlInterface != nullptr)
658 impl.mSelectableControlInterface->SelectionChanged(oldSelStart, oldSelEnd, impl.mEventData->mPrimaryCursorPosition, impl.mEventData->mPrimaryCursorPosition);
666 void Controller::TextUpdater::ResetText(Controller& controller)
668 Controller::Impl& impl = *controller.mImpl;
669 LogicalModelPtr& logicalModel = impl.mModel->mLogicalModel;
672 logicalModel->mText.Clear();
674 // Reset the embedded images buffer.
675 logicalModel->ClearEmbeddedImages();
677 // Reset the anchors buffer.
678 logicalModel->ClearAnchors();
680 // We have cleared everything including the placeholder-text
681 impl.PlaceholderCleared();
683 impl.mTextUpdateInfo.mCharacterIndex = 0u;
684 impl.mTextUpdateInfo.mNumberOfCharactersToRemove = impl.mTextUpdateInfo.mPreviousNumberOfCharacters;
685 impl.mTextUpdateInfo.mNumberOfCharactersToAdd = 0u;
687 // Clear any previous text.
688 impl.mTextUpdateInfo.mClearAll = true;
690 // The natural size needs to be re-calculated.
691 impl.mRecalculateNaturalSize = true;
693 // The text direction needs to be updated.
694 impl.mUpdateTextDirection = true;
696 // Apply modifications to the model
697 impl.mOperationsPending = ALL_OPERATIONS;
700 void Controller::TextUpdater::InsertTextAnchor(Controller& controller, int numberOfCharacters, CharacterIndex previousCursorIndex)
702 Controller::Impl& impl = *controller.mImpl;
703 ModelPtr& model = impl.mModel;
704 LogicalModelPtr& logicalModel = model->mLogicalModel;
706 for(auto& anchor : logicalModel->mAnchors)
708 if(anchor.endIndex < previousCursorIndex) // [anchor] CUR
712 if(anchor.startIndex < previousCursorIndex) // [anCURr]
714 anchor.endIndex += numberOfCharacters;
718 anchor.startIndex += numberOfCharacters;
719 anchor.endIndex += numberOfCharacters;
721 DALI_LOG_INFO(gLogFilter, Debug::General, "Controller::InsertTextAnchor[%p] Anchor[%s] start[%d] end[%d]\n", &controller, anchor.href, anchor.startIndex, anchor.endIndex);
725 void Controller::TextUpdater::RemoveTextAnchor(Controller& controller, int cursorOffset, int numberOfCharacters, CharacterIndex previousCursorIndex)
727 Controller::Impl& impl = *controller.mImpl;
728 ModelPtr& model = impl.mModel;
729 LogicalModelPtr& logicalModel = model->mLogicalModel;
730 Vector<Anchor>::Iterator it = logicalModel->mAnchors.Begin();
732 while(it != logicalModel->mAnchors.End())
734 Anchor& anchor = *it;
736 if(anchor.endIndex <= previousCursorIndex && cursorOffset == 0) // [anchor] CUR >>
740 else if(anchor.endIndex <= previousCursorIndex && cursorOffset == -1) // [anchor] << CUR
742 int endIndex = anchor.endIndex;
743 int offset = previousCursorIndex - endIndex;
744 int index = endIndex - (numberOfCharacters - offset);
751 if((int)anchor.startIndex >= endIndex)
755 delete[] anchor.href;
757 it = logicalModel->mAnchors.Erase(it);
762 anchor.endIndex = endIndex;
765 else if(anchor.startIndex >= previousCursorIndex && cursorOffset == -1) // << CUR [anchor]
767 anchor.startIndex -= numberOfCharacters;
768 anchor.endIndex -= numberOfCharacters;
770 else if(anchor.startIndex >= previousCursorIndex && cursorOffset == 0) // CUR >> [anchor]
772 int startIndex = anchor.startIndex;
773 int endIndex = anchor.endIndex;
774 int index = previousCursorIndex + numberOfCharacters - 1;
776 if(startIndex > index)
778 anchor.startIndex -= numberOfCharacters;
779 anchor.endIndex -= numberOfCharacters;
781 else if(endIndex > index + 1)
783 anchor.endIndex -= numberOfCharacters;
789 delete[] anchor.href;
791 it = logicalModel->mAnchors.Erase(it);
795 else if(cursorOffset == -1) // [<< CUR]
797 int startIndex = anchor.startIndex;
798 int index = previousCursorIndex - numberOfCharacters;
800 if(startIndex >= index)
802 anchor.startIndex = index;
804 anchor.endIndex -= numberOfCharacters;
806 else if(cursorOffset == 0) // [CUR >>]
808 anchor.endIndex -= numberOfCharacters;
812 // When this condition is reached, someting is wrong.
813 DALI_LOG_ERROR("Controller::RemoveTextAnchor[%p] Invaild state cursorOffset[%d]\n", &controller, cursorOffset);
816 DALI_LOG_INFO(gLogFilter, Debug::General, "Controller::RemoveTextAnchor[%p] Anchor[%s] start[%d] end[%d]\n", &controller, anchor.href, anchor.startIndex, anchor.endIndex);
824 } // namespace Toolkit