2 * Copyright (c) 2021 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/markup-processor.h>
28 #include <dali-toolkit/internal/text/text-controller-impl.h>
29 #include <dali-toolkit/internal/text/text-editable-control-interface.h>
33 #if defined(DEBUG_ENABLED)
34 Debug::Filter* gLogFilter = Debug::Filter::New(Debug::NoLogging, true, "LOG_TEXT_CONTROLS");
45 void Controller::TextUpdater::SetText(Controller& controller, const std::string& text)
47 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Controller::SetText\n");
49 Controller::Impl& impl = *controller.mImpl;
51 // Reset keyboard as text changed
52 impl.ResetInputMethodContext();
54 // Remove the previously set text and style.
55 ResetText(controller);
58 controller.ClearStyleData();
60 CharacterIndex lastCursorIndex = 0u;
62 EventData*& eventData = impl.mEventData;
64 if(nullptr != eventData)
66 // If popup shown then hide it by switching to Editing state
67 if((EventData::SELECTING == eventData->mState) ||
68 (EventData::EDITING_WITH_POPUP == eventData->mState) ||
69 (EventData::EDITING_WITH_GRAB_HANDLE == eventData->mState) ||
70 (EventData::EDITING_WITH_PASTE_POPUP == eventData->mState))
72 impl.ChangeState(EventData::EDITING);
78 ModelPtr& model = impl.mModel;
79 LogicalModelPtr& logicalModel = model->mLogicalModel;
80 model->mVisualModel->SetTextColor(impl.mTextColor);
82 MarkupProcessData markupProcessData(logicalModel->mColorRuns,
83 logicalModel->mFontDescriptionRuns,
84 logicalModel->mEmbeddedItems,
85 logicalModel->mAnchors,
86 logicalModel->mUnderlinedCharacterRuns,
87 logicalModel->mBackgroundColorRuns);
90 const uint8_t* utf8 = NULL;
91 if(impl.mMarkupProcessorEnabled)
93 ProcessMarkupString(text, markupProcessData);
94 textSize = markupProcessData.markupProcessedText.size();
96 // This is a bit horrible but std::string returns a (signed) char*
97 utf8 = reinterpret_cast<const uint8_t*>(markupProcessData.markupProcessedText.c_str());
101 textSize = text.size();
103 // This is a bit horrible but std::string returns a (signed) char*
104 utf8 = reinterpret_cast<const uint8_t*>(text.c_str());
107 // Convert text into UTF-32
108 Vector<Character>& utf32Characters = logicalModel->mText;
109 utf32Characters.Resize(textSize);
111 // Transform a text array encoded in utf8 into an array encoded in utf32.
112 // It returns the actual number of characters.
113 Length characterCount = Utf8ToUtf32(utf8, textSize, utf32Characters.Begin());
114 utf32Characters.Resize(characterCount);
116 DALI_ASSERT_DEBUG(textSize >= characterCount && "Invalid UTF32 conversion length");
117 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Controller::SetText %p UTF8 size %d, UTF32 size %d\n", &controller, textSize, logicalModel->mText.Count());
119 // The characters to be added.
120 impl.mTextUpdateInfo.mNumberOfCharactersToAdd = logicalModel->mText.Count();
122 // To reset the cursor position
123 lastCursorIndex = characterCount;
125 // Update the rest of the model during size negotiation
126 impl.QueueModifyEvent(ModifyEvent::TEXT_REPLACED);
128 // The natural size needs to be re-calculated.
129 impl.mRecalculateNaturalSize = true;
131 // The text direction needs to be updated.
132 impl.mUpdateTextDirection = true;
134 // Apply modifications to the model
135 impl.mOperationsPending = ALL_OPERATIONS;
139 controller.ShowPlaceholderText();
142 // Resets the cursor position.
143 controller.ResetCursorPosition(lastCursorIndex);
145 // Scrolls the text to make the cursor visible.
146 controller.ResetScrollPosition();
148 impl.RequestRelayout();
150 if(nullptr != eventData)
152 // Cancel previously queued events
153 eventData->mEventQueue.clear();
156 // Do this last since it provides callbacks into application code.
157 if(NULL != impl.mEditableControlInterface)
159 impl.mEditableControlInterface->TextChanged(true);
163 void Controller::TextUpdater::InsertText(Controller& controller, const std::string& text, Controller::InsertType type)
165 Controller::Impl& impl = *controller.mImpl;
166 EventData*& eventData = impl.mEventData;
168 DALI_ASSERT_DEBUG(nullptr != eventData && "Unexpected InsertText")
170 if(NULL == eventData)
175 bool removedPrevious = false;
176 bool removedSelected = false;
177 bool maxLengthReached = false;
179 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);
181 ModelPtr& model = impl.mModel;
182 LogicalModelPtr& logicalModel = model->mLogicalModel;
184 // TODO: At the moment the underline runs are only for pre-edit.
185 model->mVisualModel->mUnderlineRuns.Clear();
187 // Remove the previous InputMethodContext pre-edit.
188 if(eventData->mPreEditFlag && (0u != eventData->mPreEditLength))
190 removedPrevious = RemoveText(controller,
191 -static_cast<int>(eventData->mPrimaryCursorPosition - eventData->mPreEditStartPosition),
192 eventData->mPreEditLength,
193 DONT_UPDATE_INPUT_STYLE);
195 eventData->mPrimaryCursorPosition = eventData->mPreEditStartPosition;
196 eventData->mPreEditLength = 0u;
200 // Remove the previous Selection.
201 removedSelected = RemoveSelectedText(controller);
204 Vector<Character> utf32Characters;
205 Length characterCount = 0u;
209 // Convert text into UTF-32
210 utf32Characters.Resize(text.size());
212 // This is a bit horrible but std::string returns a (signed) char*
213 const uint8_t* utf8 = reinterpret_cast<const uint8_t*>(text.c_str());
215 // Transform a text array encoded in utf8 into an array encoded in utf32.
216 // It returns the actual number of characters.
217 characterCount = Utf8ToUtf32(utf8, text.size(), utf32Characters.Begin());
218 utf32Characters.Resize(characterCount);
220 DALI_ASSERT_DEBUG(text.size() >= utf32Characters.Count() && "Invalid UTF32 conversion length");
221 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "UTF8 size %d, UTF32 size %d\n", text.size(), utf32Characters.Count());
224 if(0u != utf32Characters.Count()) // Check if Utf8ToUtf32 conversion succeeded
226 // The placeholder text is no longer needed
227 if(impl.IsShowingPlaceholderText())
229 ResetText(controller);
232 impl.ChangeState(EventData::EDITING);
234 // Handle the InputMethodContext (predicitive text) state changes
237 // InputMethodContext is no longer handling key-events
238 impl.ClearPreEditFlag();
242 if(!eventData->mPreEditFlag)
244 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Entered PreEdit state\n");
246 // Record the start of the pre-edit text
247 eventData->mPreEditStartPosition = eventData->mPrimaryCursorPosition;
250 eventData->mPreEditLength = utf32Characters.Count();
251 eventData->mPreEditFlag = true;
253 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "mPreEditStartPosition %d mPreEditLength %d\n", eventData->mPreEditStartPosition, eventData->mPreEditLength);
256 const Length numberOfCharactersInModel = logicalModel->mText.Count();
258 // Restrict new text to fit within Maximum characters setting.
259 Length temp_length = (impl.mMaximumNumberOfCharacters > numberOfCharactersInModel ? impl.mMaximumNumberOfCharacters - numberOfCharactersInModel : 0);
260 Length maxSizeOfNewText = std::min(temp_length, characterCount);
261 maxLengthReached = (characterCount > maxSizeOfNewText);
263 // The cursor position.
264 CharacterIndex& cursorIndex = eventData->mPrimaryCursorPosition;
266 // Update the text's style.
268 // Updates the text style runs by adding characters.
269 logicalModel->UpdateTextStyleRuns(cursorIndex, maxSizeOfNewText);
271 // Get the character index from the cursor index.
272 const CharacterIndex styleIndex = (cursorIndex > 0u) ? cursorIndex - 1u : 0u;
274 // Retrieve the text's style for the given index.
276 impl.RetrieveDefaultInputStyle(style);
277 logicalModel->RetrieveStyle(styleIndex, style);
279 InputStyle& inputStyle = eventData->mInputStyle;
281 // Whether to add a new text color run.
282 const bool addColorRun = (style.textColor != inputStyle.textColor) && !inputStyle.isDefaultColor;
284 // Whether to add a new font run.
285 const bool addFontNameRun = (style.familyName != inputStyle.familyName) && inputStyle.isFamilyDefined;
286 const bool addFontWeightRun = (style.weight != inputStyle.weight) && inputStyle.isWeightDefined;
287 const bool addFontWidthRun = (style.width != inputStyle.width) && inputStyle.isWidthDefined;
288 const bool addFontSlantRun = (style.slant != inputStyle.slant) && inputStyle.isSlantDefined;
289 const bool addFontSizeRun = (style.size != inputStyle.size) && inputStyle.isSizeDefined;
294 const VectorBase::SizeType numberOfRuns = logicalModel->mColorRuns.Count();
295 logicalModel->mColorRuns.Resize(numberOfRuns + 1u);
297 ColorRun& colorRun = *(logicalModel->mColorRuns.Begin() + numberOfRuns);
298 colorRun.color = inputStyle.textColor;
299 colorRun.characterRun.characterIndex = cursorIndex;
300 colorRun.characterRun.numberOfCharacters = maxSizeOfNewText;
309 const VectorBase::SizeType numberOfRuns = logicalModel->mFontDescriptionRuns.Count();
310 logicalModel->mFontDescriptionRuns.Resize(numberOfRuns + 1u);
312 FontDescriptionRun& fontDescriptionRun = *(logicalModel->mFontDescriptionRuns.Begin() + numberOfRuns);
316 fontDescriptionRun.familyLength = inputStyle.familyName.size();
317 fontDescriptionRun.familyName = new char[fontDescriptionRun.familyLength];
318 memcpy(fontDescriptionRun.familyName, inputStyle.familyName.c_str(), fontDescriptionRun.familyLength);
319 fontDescriptionRun.familyDefined = true;
321 // The memory allocated for the font family name is freed when the font description is removed from the logical model.
326 fontDescriptionRun.weight = inputStyle.weight;
327 fontDescriptionRun.weightDefined = true;
332 fontDescriptionRun.width = inputStyle.width;
333 fontDescriptionRun.widthDefined = true;
338 fontDescriptionRun.slant = inputStyle.slant;
339 fontDescriptionRun.slantDefined = true;
344 fontDescriptionRun.size = static_cast<PointSize26Dot6>(inputStyle.size * impl.mFontSizeScale * 64.f);
345 fontDescriptionRun.sizeDefined = true;
348 fontDescriptionRun.characterRun.characterIndex = cursorIndex;
349 fontDescriptionRun.characterRun.numberOfCharacters = maxSizeOfNewText;
352 // Insert at current cursor position.
353 Vector<Character>& modifyText = logicalModel->mText;
355 auto pos = modifyText.End();
356 if(cursorIndex < numberOfCharactersInModel)
358 pos = modifyText.Begin() + cursorIndex;
360 unsigned int realPos = pos - modifyText.Begin();
361 modifyText.Insert(pos, utf32Characters.Begin(), utf32Characters.Begin() + maxSizeOfNewText);
363 if(NULL != impl.mEditableControlInterface)
365 impl.mEditableControlInterface->TextInserted(realPos, maxSizeOfNewText, text);
368 TextUpdateInfo& textUpdateInfo = impl.mTextUpdateInfo;
370 // Mark the first paragraph to be updated.
371 if(Layout::Engine::SINGLE_LINE_BOX == impl.mLayoutEngine.GetLayout())
373 textUpdateInfo.mCharacterIndex = 0;
374 textUpdateInfo.mNumberOfCharactersToRemove = textUpdateInfo.mPreviousNumberOfCharacters;
375 textUpdateInfo.mNumberOfCharactersToAdd = numberOfCharactersInModel + maxSizeOfNewText;
376 textUpdateInfo.mClearAll = true;
380 textUpdateInfo.mCharacterIndex = std::min(cursorIndex, textUpdateInfo.mCharacterIndex);
381 textUpdateInfo.mNumberOfCharactersToAdd += maxSizeOfNewText;
384 if(impl.mMarkupProcessorEnabled)
386 InsertTextAnchor(controller, maxSizeOfNewText, cursorIndex);
389 // Update the cursor index.
390 cursorIndex += maxSizeOfNewText;
392 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Inserted %d characters, new size %d new cursor %d\n", maxSizeOfNewText, logicalModel->mText.Count(), eventData->mPrimaryCursorPosition);
395 if((0u == logicalModel->mText.Count()) &&
396 impl.IsPlaceholderAvailable())
398 // Show place-holder if empty after removing the pre-edit text
399 controller.ShowPlaceholderText();
400 eventData->mUpdateCursorPosition = true;
401 impl.ClearPreEditFlag();
403 else if(removedPrevious ||
405 (0 != utf32Characters.Count()))
407 // Queue an inserted event
408 impl.QueueModifyEvent(ModifyEvent::TEXT_INSERTED);
410 eventData->mUpdateCursorPosition = true;
413 eventData->mScrollAfterDelete = true;
417 eventData->mScrollAfterUpdatePosition = true;
423 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "MaxLengthReached (%d)\n", logicalModel->mText.Count());
425 impl.ResetInputMethodContext();
427 if(NULL != impl.mEditableControlInterface)
429 // Do this last since it provides callbacks into application code
430 impl.mEditableControlInterface->MaxLengthReached();
435 void Controller::TextUpdater::PasteText(Controller& controller, const std::string& stringToPaste)
437 InsertText(controller, stringToPaste, Text::Controller::COMMIT);
438 Controller::Impl& impl = *controller.mImpl;
439 impl.ChangeState(EventData::EDITING);
440 impl.RequestRelayout();
442 if(NULL != impl.mEditableControlInterface)
444 // Do this last since it provides callbacks into application code
445 impl.mEditableControlInterface->TextChanged(true);
449 bool Controller::TextUpdater::RemoveText(
450 Controller& controller,
452 int numberOfCharacters,
453 UpdateInputStyleType type)
455 bool removed = false;
457 Controller::Impl& impl = *controller.mImpl;
458 EventData*& eventData = impl.mEventData;
460 if(nullptr == eventData)
465 ModelPtr& model = impl.mModel;
466 LogicalModelPtr& logicalModel = model->mLogicalModel;
468 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);
470 if(!impl.IsShowingPlaceholderText())
472 // Delete at current cursor position
473 Vector<Character>& currentText = logicalModel->mText;
474 CharacterIndex& previousCursorIndex = eventData->mPrimaryCursorPosition;
476 CharacterIndex cursorIndex = 0;
478 // Validate the cursor position & number of characters
479 if((static_cast<int>(eventData->mPrimaryCursorPosition) + cursorOffset) >= 0)
481 cursorIndex = eventData->mPrimaryCursorPosition + cursorOffset;
484 if((cursorIndex + numberOfCharacters) > currentText.Count())
486 numberOfCharacters = currentText.Count() - cursorIndex;
489 TextUpdateInfo& textUpdateInfo = impl.mTextUpdateInfo;
491 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.
492 ((cursorIndex + numberOfCharacters) <= textUpdateInfo.mPreviousNumberOfCharacters))
494 // Mark the paragraphs to be updated.
495 if(Layout::Engine::SINGLE_LINE_BOX == impl.mLayoutEngine.GetLayout())
497 textUpdateInfo.mCharacterIndex = 0;
498 textUpdateInfo.mNumberOfCharactersToRemove = textUpdateInfo.mPreviousNumberOfCharacters;
499 textUpdateInfo.mNumberOfCharactersToAdd = textUpdateInfo.mPreviousNumberOfCharacters - numberOfCharacters;
500 textUpdateInfo.mClearAll = true;
504 textUpdateInfo.mCharacterIndex = std::min(cursorIndex, textUpdateInfo.mCharacterIndex);
505 textUpdateInfo.mNumberOfCharactersToRemove += numberOfCharacters;
508 // Update the input style and remove the text's style before removing the text.
510 if(UPDATE_INPUT_STYLE == type)
512 InputStyle& eventDataInputStyle = eventData->mInputStyle;
514 // Keep a copy of the current input style.
515 InputStyle currentInputStyle;
516 currentInputStyle.Copy(eventDataInputStyle);
518 // Set first the default input style.
519 impl.RetrieveDefaultInputStyle(eventDataInputStyle);
521 // Update the input style.
522 logicalModel->RetrieveStyle(cursorIndex, eventDataInputStyle);
524 // Compare if the input style has changed.
525 const bool hasInputStyleChanged = !currentInputStyle.Equal(eventDataInputStyle);
527 if(hasInputStyleChanged)
529 const InputStyle::Mask styleChangedMask = currentInputStyle.GetInputStyleChangeMask(eventDataInputStyle);
530 // Queue the input style changed signal.
531 eventData->mInputStyleChangedQueue.PushBack(styleChangedMask);
535 // If the number of current text and the number of characters to be deleted are same,
536 // it means all texts should be removed and all Preedit variables should be initialized.
537 if((currentText.Count() - numberOfCharacters == 0) && (cursorIndex == 0))
539 impl.ClearPreEditFlag();
540 textUpdateInfo.mNumberOfCharactersToAdd = 0;
543 // Updates the text style runs by removing characters. Runs with no characters are removed.
544 logicalModel->UpdateTextStyleRuns(cursorIndex, -numberOfCharacters);
546 // Remove the characters.
547 Vector<Character>::Iterator first = currentText.Begin() + cursorIndex;
548 Vector<Character>::Iterator last = first + numberOfCharacters;
550 if(NULL != impl.mEditableControlInterface)
553 Utf32ToUtf8(first, numberOfCharacters, utf8);
554 impl.mEditableControlInterface->TextDeleted(cursorIndex, numberOfCharacters, utf8);
557 currentText.Erase(first, last);
559 if(impl.mMarkupProcessorEnabled)
561 RemoveTextAnchor(controller, cursorOffset, numberOfCharacters, previousCursorIndex);
564 // Cursor position retreat
565 previousCursorIndex = cursorIndex;
567 eventData->mScrollAfterDelete = true;
569 if(EventData::INACTIVE == eventData->mState)
571 impl.ChangeState(EventData::EDITING);
574 DALI_LOG_INFO(gLogFilter, Debug::General, "Controller::RemoveText %p removed %d\n", &controller, numberOfCharacters);
582 bool Controller::TextUpdater::RemoveSelectedText(Controller& controller)
584 bool textRemoved(false);
586 Controller::Impl& impl = *controller.mImpl;
588 if(EventData::SELECTING == impl.mEventData->mState)
590 std::string removedString;
591 impl.RetrieveSelection(removedString, true);
593 if(!removedString.empty())
596 impl.ChangeState(EventData::EDITING);
598 if(impl.mMarkupProcessorEnabled)
600 int cursorOffset = -1;
601 int numberOfCharacters = removedString.length();
602 CharacterIndex& cursorIndex = impl.mEventData->mPrimaryCursorPosition;
603 CharacterIndex previousCursorIndex = cursorIndex + numberOfCharacters;
605 RemoveTextAnchor(controller, cursorOffset, numberOfCharacters, previousCursorIndex);
613 void Controller::TextUpdater::ResetText(Controller& controller)
615 Controller::Impl& impl = *controller.mImpl;
616 LogicalModelPtr& logicalModel = impl.mModel->mLogicalModel;
619 logicalModel->mText.Clear();
621 // Reset the embedded images buffer.
622 logicalModel->ClearEmbeddedImages();
624 // Reset the anchors buffer.
625 logicalModel->ClearAnchors();
627 // We have cleared everything including the placeholder-text
628 impl.PlaceholderCleared();
630 impl.mTextUpdateInfo.mCharacterIndex = 0u;
631 impl.mTextUpdateInfo.mNumberOfCharactersToRemove = impl.mTextUpdateInfo.mPreviousNumberOfCharacters;
632 impl.mTextUpdateInfo.mNumberOfCharactersToAdd = 0u;
634 // Clear any previous text.
635 impl.mTextUpdateInfo.mClearAll = true;
637 // The natural size needs to be re-calculated.
638 impl.mRecalculateNaturalSize = true;
640 // The text direction needs to be updated.
641 impl.mUpdateTextDirection = true;
643 // Apply modifications to the model
644 impl.mOperationsPending = ALL_OPERATIONS;
647 void Controller::TextUpdater::InsertTextAnchor(Controller& controller, int numberOfCharacters, CharacterIndex previousCursorIndex)
649 Controller::Impl& impl = *controller.mImpl;
650 ModelPtr& model = impl.mModel;
651 LogicalModelPtr& logicalModel = model->mLogicalModel;
653 for(auto& anchor : logicalModel->mAnchors)
655 if(anchor.endIndex < previousCursorIndex) // [anchor] CUR
659 if(anchor.startIndex < previousCursorIndex) // [anCURr]
661 anchor.endIndex += numberOfCharacters;
665 anchor.startIndex += numberOfCharacters;
666 anchor.endIndex += numberOfCharacters;
668 DALI_LOG_INFO(gLogFilter, Debug::General, "Controller::InsertTextAnchor[%p] Anchor[%s] start[%d] end[%d]\n", &controller, anchor.href, anchor.startIndex, anchor.endIndex);
672 void Controller::TextUpdater::RemoveTextAnchor(Controller& controller, int cursorOffset, int numberOfCharacters, CharacterIndex previousCursorIndex)
674 Controller::Impl& impl = *controller.mImpl;
675 ModelPtr& model = impl.mModel;
676 LogicalModelPtr& logicalModel = model->mLogicalModel;
677 Vector<Anchor>::Iterator it = logicalModel->mAnchors.Begin();
679 while(it != logicalModel->mAnchors.End())
681 Anchor& anchor = *it;
683 if(anchor.endIndex <= previousCursorIndex && cursorOffset == 0) // [anchor] CUR >>
687 else if(anchor.endIndex <= previousCursorIndex && cursorOffset == -1) // [anchor] << CUR
689 int endIndex = anchor.endIndex;
690 int offset = previousCursorIndex - endIndex;
691 int index = endIndex - (numberOfCharacters - offset);
698 if((int)anchor.startIndex >= endIndex)
702 delete[] anchor.href;
704 it = logicalModel->mAnchors.Erase(it);
709 anchor.endIndex = endIndex;
712 else if(anchor.startIndex >= previousCursorIndex && cursorOffset == -1) // << CUR [anchor]
714 anchor.startIndex -= numberOfCharacters;
715 anchor.endIndex -= numberOfCharacters;
717 else if(anchor.startIndex >= previousCursorIndex && cursorOffset == 0) // CUR >> [anchor]
719 int startIndex = anchor.startIndex;
720 int endIndex = anchor.endIndex;
721 int index = previousCursorIndex + numberOfCharacters - 1;
723 if(startIndex > index)
725 anchor.startIndex -= numberOfCharacters;
726 anchor.endIndex -= numberOfCharacters;
728 else if(endIndex > index + 1)
730 anchor.endIndex -= numberOfCharacters;
736 delete[] anchor.href;
738 it = logicalModel->mAnchors.Erase(it);
742 else if(cursorOffset == -1) // [<< CUR]
744 int startIndex = anchor.startIndex;
745 int index = previousCursorIndex - numberOfCharacters;
747 if(startIndex >= index)
749 anchor.startIndex = index;
751 anchor.endIndex -= numberOfCharacters;
753 else if(cursorOffset == 0) // [CUR >>]
755 anchor.endIndex -= numberOfCharacters;
759 // When this condition is reached, someting is wrong.
760 DALI_LOG_ERROR("Controller::RemoveTextAnchor[%p] Invaild state cursorOffset[%d]\n", &controller, cursorOffset);
763 DALI_LOG_INFO(gLogFilter, Debug::General, "Controller::RemoveTextAnchor[%p] Anchor[%s] start[%d] end[%d]\n", &controller, anchor.href, anchor.startIndex, anchor.endIndex);
771 } // namespace Toolkit