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/markup-processor.h>
28 #include <dali-toolkit/internal/text/text-controller-impl.h>
29 #include <dali-toolkit/internal/text/text-controller-placeholder-handler.h>
30 #include <dali-toolkit/internal/text/text-editable-control-interface.h>
34 #if defined(DEBUG_ENABLED)
35 Debug::Filter* gLogFilter = Debug::Filter::New(Debug::NoLogging, true, "LOG_TEXT_CONTROLS");
46 void Controller::TextUpdater::SetText(Controller& controller, const std::string& text)
48 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Controller::SetText\n");
50 Controller::Impl& impl = *controller.mImpl;
52 // Reset keyboard as text changed
53 impl.ResetInputMethodContext();
55 // Remove the previously set text and style.
56 ResetText(controller);
59 impl.ClearStyleData();
61 CharacterIndex lastCursorIndex = 0u;
63 EventData*& eventData = impl.mEventData;
65 if(nullptr != eventData)
67 // If popup shown then hide it by switching to Editing state
68 if((EventData::SELECTING == eventData->mState) ||
69 (EventData::EDITING_WITH_POPUP == eventData->mState) ||
70 (EventData::EDITING_WITH_GRAB_HANDLE == eventData->mState) ||
71 (EventData::EDITING_WITH_PASTE_POPUP == eventData->mState))
73 if((impl.mSelectableControlInterface != nullptr) && (EventData::SELECTING == eventData->mState))
75 impl.mSelectableControlInterface->SelectionChanged(eventData->mLeftSelectionPosition, eventData->mRightSelectionPosition, eventData->mPrimaryCursorPosition, eventData->mPrimaryCursorPosition);
78 impl.ChangeState(EventData::EDITING);
84 ModelPtr& model = impl.mModel;
85 LogicalModelPtr& logicalModel = model->mLogicalModel;
86 model->mVisualModel->SetTextColor(impl.mTextColor);
88 MarkupProcessData markupProcessData(logicalModel->mColorRuns,
89 logicalModel->mFontDescriptionRuns,
90 logicalModel->mEmbeddedItems,
91 logicalModel->mAnchors,
92 logicalModel->mUnderlinedCharacterRuns,
93 logicalModel->mBackgroundColorRuns,
94 logicalModel->mStrikethroughCharacterRuns,
95 logicalModel->mBoundedParagraphRuns);
98 const uint8_t* utf8 = NULL;
99 if(impl.mMarkupProcessorEnabled)
101 ProcessMarkupString(text, markupProcessData);
102 textSize = markupProcessData.markupProcessedText.size();
104 // This is a bit horrible but std::string returns a (signed) char*
105 utf8 = reinterpret_cast<const uint8_t*>(markupProcessData.markupProcessedText.c_str());
109 textSize = text.size();
111 // This is a bit horrible but std::string returns a (signed) char*
112 utf8 = reinterpret_cast<const uint8_t*>(text.c_str());
115 // Convert text into UTF-32
116 Vector<Character>& utf32Characters = logicalModel->mText;
117 utf32Characters.Resize(textSize);
119 // Transform a text array encoded in utf8 into an array encoded in utf32.
120 // It returns the actual number of characters.
121 Length characterCount = Utf8ToUtf32(utf8, textSize, utf32Characters.Begin());
122 utf32Characters.Resize(characterCount);
124 DALI_ASSERT_DEBUG(textSize >= characterCount && "Invalid UTF32 conversion length");
125 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Controller::SetText %p UTF8 size %d, UTF32 size %d\n", &controller, textSize, logicalModel->mText.Count());
127 // The characters to be added.
128 impl.mTextUpdateInfo.mNumberOfCharactersToAdd = logicalModel->mText.Count();
130 // To reset the cursor position
131 lastCursorIndex = characterCount;
133 // Update the rest of the model during size negotiation
134 impl.QueueModifyEvent(ModifyEvent::TEXT_REPLACED);
136 // The natural size needs to be re-calculated.
137 impl.mRecalculateNaturalSize = true;
139 // The text direction needs to be updated.
140 impl.mUpdateTextDirection = true;
142 // Apply modifications to the model
143 impl.mOperationsPending = ALL_OPERATIONS;
147 PlaceholderHandler::ShowPlaceholderText(impl);
150 unsigned int oldCursorPos = (nullptr != eventData ? eventData->mPrimaryCursorPosition : 0);
152 // Resets the cursor position.
153 controller.ResetCursorPosition(lastCursorIndex);
155 // Scrolls the text to make the cursor visible.
156 impl.ResetScrollPosition();
158 impl.RequestRelayout();
160 if(nullptr != eventData)
162 // Cancel previously queued events
163 eventData->mEventQueue.clear();
166 // Do this last since it provides callbacks into application code.
167 if(NULL != impl.mEditableControlInterface)
169 impl.mEditableControlInterface->CursorPositionChanged(oldCursorPos, lastCursorIndex);
170 impl.mEditableControlInterface->TextChanged(true);
174 void Controller::TextUpdater::InsertText(Controller& controller, const std::string& text, Controller::InsertType type)
176 Controller::Impl& impl = *controller.mImpl;
177 EventData*& eventData = impl.mEventData;
179 DALI_ASSERT_DEBUG(nullptr != eventData && "Unexpected InsertText")
181 if(NULL == eventData)
186 bool removedPrevious = false;
187 bool removedSelected = false;
188 bool maxLengthReached = false;
189 unsigned int oldCursorPos = eventData->mPrimaryCursorPosition;
191 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);
193 ModelPtr& model = impl.mModel;
194 LogicalModelPtr& logicalModel = model->mLogicalModel;
196 // TODO: At the moment the underline runs are only for pre-edit.
197 model->mVisualModel->mUnderlineRuns.Clear();
199 // Remove the previous InputMethodContext pre-edit.
200 if(eventData->mPreEditFlag && (0u != eventData->mPreEditLength))
202 removedPrevious = RemoveText(controller,
203 -static_cast<int>(eventData->mPrimaryCursorPosition - eventData->mPreEditStartPosition),
204 eventData->mPreEditLength,
205 DONT_UPDATE_INPUT_STYLE);
207 eventData->mPrimaryCursorPosition = eventData->mPreEditStartPosition;
208 eventData->mPreEditLength = 0u;
212 // Remove the previous Selection.
213 removedSelected = RemoveSelectedText(controller);
216 Vector<Character> utf32Characters;
217 Length characterCount = 0u;
221 // Convert text into UTF-32
222 utf32Characters.Resize(text.size());
224 // This is a bit horrible but std::string returns a (signed) char*
225 const uint8_t* utf8 = reinterpret_cast<const uint8_t*>(text.c_str());
227 // Transform a text array encoded in utf8 into an array encoded in utf32.
228 // It returns the actual number of characters.
229 characterCount = Utf8ToUtf32(utf8, text.size(), utf32Characters.Begin());
230 utf32Characters.Resize(characterCount);
232 DALI_ASSERT_DEBUG(text.size() >= utf32Characters.Count() && "Invalid UTF32 conversion length");
233 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "UTF8 size %d, UTF32 size %d\n", text.size(), utf32Characters.Count());
236 if(0u != utf32Characters.Count()) // Check if Utf8ToUtf32 conversion succeeded
238 // The placeholder text is no longer needed
239 if(impl.IsShowingPlaceholderText())
241 ResetText(controller);
244 impl.ChangeState(EventData::EDITING);
246 // Handle the InputMethodContext (predicitive text) state changes
249 // InputMethodContext is no longer handling key-events
250 impl.ClearPreEditFlag();
254 if(!eventData->mPreEditFlag)
256 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Entered PreEdit state\n");
258 // Record the start of the pre-edit text
259 eventData->mPreEditStartPosition = eventData->mPrimaryCursorPosition;
262 eventData->mPreEditLength = utf32Characters.Count();
263 eventData->mPreEditFlag = true;
265 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "mPreEditStartPosition %d mPreEditLength %d\n", eventData->mPreEditStartPosition, eventData->mPreEditLength);
268 const Length numberOfCharactersInModel = logicalModel->mText.Count();
270 // Restrict new text to fit within Maximum characters setting.
271 Length temp_length = (impl.mMaximumNumberOfCharacters > numberOfCharactersInModel ? impl.mMaximumNumberOfCharacters - numberOfCharactersInModel : 0);
272 Length maxSizeOfNewText = std::min(temp_length, characterCount);
273 maxLengthReached = (characterCount > maxSizeOfNewText);
275 // The cursor position.
276 CharacterIndex& cursorIndex = eventData->mPrimaryCursorPosition;
278 // Update the text's style.
280 // Updates the text style runs by adding characters.
281 logicalModel->UpdateTextStyleRuns(cursorIndex, maxSizeOfNewText);
283 // Get the character index from the cursor index.
284 const CharacterIndex styleIndex = (cursorIndex > 0u) ? cursorIndex - 1u : 0u;
286 // Retrieve the text's style for the given index.
288 impl.RetrieveDefaultInputStyle(style);
289 logicalModel->RetrieveStyle(styleIndex, style);
291 InputStyle& inputStyle = eventData->mInputStyle;
293 // Whether to add a new text color run.
294 const bool addColorRun = (style.textColor != inputStyle.textColor) && !inputStyle.isDefaultColor;
296 // Whether to add a new font run.
297 const bool addFontNameRun = (style.familyName != inputStyle.familyName) && inputStyle.isFamilyDefined;
298 const bool addFontWeightRun = (style.weight != inputStyle.weight) && inputStyle.isWeightDefined;
299 const bool addFontWidthRun = (style.width != inputStyle.width) && inputStyle.isWidthDefined;
300 const bool addFontSlantRun = (style.slant != inputStyle.slant) && inputStyle.isSlantDefined;
301 const bool addFontSizeRun = (style.size != inputStyle.size) && inputStyle.isSizeDefined;
306 const VectorBase::SizeType numberOfRuns = logicalModel->mColorRuns.Count();
307 logicalModel->mColorRuns.Resize(numberOfRuns + 1u);
309 ColorRun& colorRun = *(logicalModel->mColorRuns.Begin() + numberOfRuns);
310 colorRun.color = inputStyle.textColor;
311 colorRun.characterRun.characterIndex = cursorIndex;
312 colorRun.characterRun.numberOfCharacters = maxSizeOfNewText;
321 const VectorBase::SizeType numberOfRuns = logicalModel->mFontDescriptionRuns.Count();
322 logicalModel->mFontDescriptionRuns.Resize(numberOfRuns + 1u);
324 FontDescriptionRun& fontDescriptionRun = *(logicalModel->mFontDescriptionRuns.Begin() + numberOfRuns);
328 fontDescriptionRun.familyLength = inputStyle.familyName.size();
329 fontDescriptionRun.familyName = new char[fontDescriptionRun.familyLength];
330 memcpy(fontDescriptionRun.familyName, inputStyle.familyName.c_str(), fontDescriptionRun.familyLength);
331 fontDescriptionRun.familyDefined = true;
333 // The memory allocated for the font family name is freed when the font description is removed from the logical model.
338 fontDescriptionRun.weight = inputStyle.weight;
339 fontDescriptionRun.weightDefined = true;
344 fontDescriptionRun.width = inputStyle.width;
345 fontDescriptionRun.widthDefined = true;
350 fontDescriptionRun.slant = inputStyle.slant;
351 fontDescriptionRun.slantDefined = true;
356 fontDescriptionRun.size = static_cast<PointSize26Dot6>(inputStyle.size * impl.GetFontSizeScale() * 64.f);
357 fontDescriptionRun.sizeDefined = true;
360 fontDescriptionRun.characterRun.characterIndex = cursorIndex;
361 fontDescriptionRun.characterRun.numberOfCharacters = maxSizeOfNewText;
364 // Insert at current cursor position.
365 Vector<Character>& modifyText = logicalModel->mText;
367 auto pos = modifyText.End();
368 if(cursorIndex < numberOfCharactersInModel)
370 pos = modifyText.Begin() + cursorIndex;
372 unsigned int realPos = pos - modifyText.Begin();
373 modifyText.Insert(pos, utf32Characters.Begin(), utf32Characters.Begin() + maxSizeOfNewText);
375 if(NULL != impl.mEditableControlInterface)
377 impl.mEditableControlInterface->TextInserted(realPos, maxSizeOfNewText, text);
380 TextUpdateInfo& textUpdateInfo = impl.mTextUpdateInfo;
382 // Mark the first paragraph to be updated.
383 if(Layout::Engine::SINGLE_LINE_BOX == impl.mLayoutEngine.GetLayout())
385 textUpdateInfo.mCharacterIndex = 0;
386 textUpdateInfo.mNumberOfCharactersToRemove = textUpdateInfo.mPreviousNumberOfCharacters;
387 textUpdateInfo.mNumberOfCharactersToAdd = numberOfCharactersInModel + maxSizeOfNewText;
388 textUpdateInfo.mClearAll = true;
392 textUpdateInfo.mCharacterIndex = std::min(cursorIndex, textUpdateInfo.mCharacterIndex);
393 textUpdateInfo.mNumberOfCharactersToAdd += maxSizeOfNewText;
396 if(impl.mMarkupProcessorEnabled)
398 InsertTextAnchor(controller, maxSizeOfNewText, cursorIndex);
401 // Update the cursor index.
402 cursorIndex += maxSizeOfNewText;
404 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Inserted %d characters, new size %d new cursor %d\n", maxSizeOfNewText, logicalModel->mText.Count(), eventData->mPrimaryCursorPosition);
407 if((0u == logicalModel->mText.Count()) &&
408 impl.IsPlaceholderAvailable())
410 // Show place-holder if empty after removing the pre-edit text
411 PlaceholderHandler::ShowPlaceholderText(impl);
412 eventData->mUpdateCursorPosition = true;
413 impl.ClearPreEditFlag();
415 else if(removedPrevious ||
417 (0 != utf32Characters.Count()))
419 // Queue an inserted event
420 impl.QueueModifyEvent(ModifyEvent::TEXT_INSERTED);
422 eventData->mUpdateCursorPosition = true;
425 eventData->mScrollAfterDelete = true;
429 eventData->mScrollAfterUpdatePosition = true;
433 if(nullptr != impl.mEditableControlInterface)
435 impl.mEditableControlInterface->CursorPositionChanged(oldCursorPos, eventData->mPrimaryCursorPosition);
440 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "MaxLengthReached (%d)\n", logicalModel->mText.Count());
442 impl.ResetInputMethodContext();
444 if(NULL != impl.mEditableControlInterface)
446 // Do this last since it provides callbacks into application code
447 impl.mEditableControlInterface->MaxLengthReached();
452 void Controller::TextUpdater::PasteText(Controller& controller, const std::string& stringToPaste)
454 InsertText(controller, stringToPaste, Text::Controller::COMMIT);
455 Controller::Impl& impl = *controller.mImpl;
456 impl.ChangeState(EventData::EDITING);
457 impl.RequestRelayout();
459 if(NULL != impl.mEditableControlInterface)
461 // Do this last since it provides callbacks into application code
462 impl.mEditableControlInterface->TextChanged(true);
466 bool Controller::TextUpdater::RemoveText(
467 Controller& controller,
469 int numberOfCharacters,
470 UpdateInputStyleType type)
472 bool removed = false;
474 Controller::Impl& impl = *controller.mImpl;
475 EventData*& eventData = impl.mEventData;
477 if(nullptr == eventData)
482 ModelPtr& model = impl.mModel;
483 LogicalModelPtr& logicalModel = model->mLogicalModel;
485 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);
487 if(!impl.IsShowingPlaceholderText())
489 // Delete at current cursor position
490 Vector<Character>& currentText = logicalModel->mText;
491 CharacterIndex& previousCursorIndex = eventData->mPrimaryCursorPosition;
493 CharacterIndex cursorIndex = 0;
495 // Validate the cursor position & number of characters
496 if((static_cast<int>(eventData->mPrimaryCursorPosition) + cursorOffset) >= 0)
498 cursorIndex = eventData->mPrimaryCursorPosition + cursorOffset;
501 if((cursorIndex + numberOfCharacters) > currentText.Count())
503 numberOfCharacters = currentText.Count() - cursorIndex;
506 TextUpdateInfo& textUpdateInfo = impl.mTextUpdateInfo;
508 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.
509 ((cursorIndex + numberOfCharacters) <= textUpdateInfo.mPreviousNumberOfCharacters))
511 // Mark the paragraphs to be updated.
512 if(Layout::Engine::SINGLE_LINE_BOX == impl.mLayoutEngine.GetLayout())
514 textUpdateInfo.mCharacterIndex = 0;
515 textUpdateInfo.mNumberOfCharactersToRemove = textUpdateInfo.mPreviousNumberOfCharacters;
516 textUpdateInfo.mNumberOfCharactersToAdd = textUpdateInfo.mPreviousNumberOfCharacters - numberOfCharacters;
517 textUpdateInfo.mClearAll = true;
521 textUpdateInfo.mCharacterIndex = std::min(cursorIndex, textUpdateInfo.mCharacterIndex);
522 textUpdateInfo.mNumberOfCharactersToRemove += numberOfCharacters;
525 // Update the input style and remove the text's style before removing the text.
527 if(UPDATE_INPUT_STYLE == type)
529 InputStyle& eventDataInputStyle = eventData->mInputStyle;
531 // Keep a copy of the current input style.
532 InputStyle currentInputStyle;
533 currentInputStyle.Copy(eventDataInputStyle);
535 // Set first the default input style.
536 impl.RetrieveDefaultInputStyle(eventDataInputStyle);
538 // Update the input style.
539 logicalModel->RetrieveStyle(cursorIndex, eventDataInputStyle);
541 // Compare if the input style has changed.
542 const bool hasInputStyleChanged = !currentInputStyle.Equal(eventDataInputStyle);
544 if(hasInputStyleChanged)
546 const InputStyle::Mask styleChangedMask = currentInputStyle.GetInputStyleChangeMask(eventDataInputStyle);
547 // Queue the input style changed signal.
548 eventData->mInputStyleChangedQueue.PushBack(styleChangedMask);
552 // If the number of current text and the number of characters to be deleted are same,
553 // it means all texts should be removed and all Preedit variables should be initialized.
554 if((currentText.Count() - numberOfCharacters == 0) && (cursorIndex == 0))
556 impl.ClearPreEditFlag();
557 textUpdateInfo.mNumberOfCharactersToAdd = 0;
560 // Updates the text style runs by removing characters. Runs with no characters are removed.
561 logicalModel->UpdateTextStyleRuns(cursorIndex, -numberOfCharacters);
563 // Remove the characters.
564 Vector<Character>::Iterator first = currentText.Begin() + cursorIndex;
565 Vector<Character>::Iterator last = first + numberOfCharacters;
567 if(NULL != impl.mEditableControlInterface)
570 Utf32ToUtf8(first, numberOfCharacters, utf8);
571 impl.mEditableControlInterface->TextDeleted(cursorIndex, numberOfCharacters, utf8);
574 currentText.Erase(first, last);
576 if(impl.mMarkupProcessorEnabled)
578 RemoveTextAnchor(controller, cursorOffset, numberOfCharacters, previousCursorIndex);
581 if(nullptr != impl.mEditableControlInterface)
583 impl.mEditableControlInterface->CursorPositionChanged(previousCursorIndex, cursorIndex);
586 // Cursor position retreat
587 previousCursorIndex = cursorIndex;
589 eventData->mScrollAfterDelete = true;
591 if(EventData::INACTIVE == eventData->mState)
593 impl.ChangeState(EventData::EDITING);
596 DALI_LOG_INFO(gLogFilter, Debug::General, "Controller::RemoveText %p removed %d\n", &controller, numberOfCharacters);
604 bool Controller::TextUpdater::RemoveSelectedText(Controller& controller)
606 bool textRemoved(false);
608 Controller::Impl& impl = *controller.mImpl;
610 if(EventData::SELECTING == impl.mEventData->mState)
612 std::string removedString;
613 uint32_t oldSelStart = impl.mEventData->mLeftSelectionPosition;
614 uint32_t oldSelEnd = impl.mEventData->mRightSelectionPosition;
616 impl.RetrieveSelection(removedString, true);
618 if(!removedString.empty())
621 impl.ChangeState(EventData::EDITING);
623 if(impl.mMarkupProcessorEnabled)
625 int cursorOffset = -1;
626 int numberOfCharacters = removedString.length();
627 CharacterIndex& cursorIndex = impl.mEventData->mPrimaryCursorPosition;
628 CharacterIndex previousCursorIndex = cursorIndex + numberOfCharacters;
630 RemoveTextAnchor(controller, cursorOffset, numberOfCharacters, previousCursorIndex);
633 if(impl.mSelectableControlInterface != nullptr)
635 impl.mSelectableControlInterface->SelectionChanged(oldSelStart, oldSelEnd, impl.mEventData->mPrimaryCursorPosition, impl.mEventData->mPrimaryCursorPosition);
643 void Controller::TextUpdater::ResetText(Controller& controller)
645 Controller::Impl& impl = *controller.mImpl;
646 LogicalModelPtr& logicalModel = impl.mModel->mLogicalModel;
649 logicalModel->mText.Clear();
651 // Reset the embedded images buffer.
652 logicalModel->ClearEmbeddedImages();
654 // Reset the anchors buffer.
655 logicalModel->ClearAnchors();
657 // We have cleared everything including the placeholder-text
658 impl.PlaceholderCleared();
660 impl.mTextUpdateInfo.mCharacterIndex = 0u;
661 impl.mTextUpdateInfo.mNumberOfCharactersToRemove = impl.mTextUpdateInfo.mPreviousNumberOfCharacters;
662 impl.mTextUpdateInfo.mNumberOfCharactersToAdd = 0u;
664 // Clear any previous text.
665 impl.mTextUpdateInfo.mClearAll = true;
667 // The natural size needs to be re-calculated.
668 impl.mRecalculateNaturalSize = true;
670 // The text direction needs to be updated.
671 impl.mUpdateTextDirection = true;
673 // Apply modifications to the model
674 impl.mOperationsPending = ALL_OPERATIONS;
677 void Controller::TextUpdater::InsertTextAnchor(Controller& controller, int numberOfCharacters, CharacterIndex previousCursorIndex)
679 Controller::Impl& impl = *controller.mImpl;
680 ModelPtr& model = impl.mModel;
681 LogicalModelPtr& logicalModel = model->mLogicalModel;
683 for(auto& anchor : logicalModel->mAnchors)
685 if(anchor.endIndex < previousCursorIndex) // [anchor] CUR
689 if(anchor.startIndex < previousCursorIndex) // [anCURr]
691 anchor.endIndex += numberOfCharacters;
695 anchor.startIndex += numberOfCharacters;
696 anchor.endIndex += numberOfCharacters;
698 DALI_LOG_INFO(gLogFilter, Debug::General, "Controller::InsertTextAnchor[%p] Anchor[%s] start[%d] end[%d]\n", &controller, anchor.href, anchor.startIndex, anchor.endIndex);
702 void Controller::TextUpdater::RemoveTextAnchor(Controller& controller, int cursorOffset, int numberOfCharacters, CharacterIndex previousCursorIndex)
704 Controller::Impl& impl = *controller.mImpl;
705 ModelPtr& model = impl.mModel;
706 LogicalModelPtr& logicalModel = model->mLogicalModel;
707 Vector<Anchor>::Iterator it = logicalModel->mAnchors.Begin();
709 while(it != logicalModel->mAnchors.End())
711 Anchor& anchor = *it;
713 if(anchor.endIndex <= previousCursorIndex && cursorOffset == 0) // [anchor] CUR >>
717 else if(anchor.endIndex <= previousCursorIndex && cursorOffset == -1) // [anchor] << CUR
719 int endIndex = anchor.endIndex;
720 int offset = previousCursorIndex - endIndex;
721 int index = endIndex - (numberOfCharacters - offset);
728 if((int)anchor.startIndex >= endIndex)
732 delete[] anchor.href;
734 it = logicalModel->mAnchors.Erase(it);
739 anchor.endIndex = endIndex;
742 else if(anchor.startIndex >= previousCursorIndex && cursorOffset == -1) // << CUR [anchor]
744 anchor.startIndex -= numberOfCharacters;
745 anchor.endIndex -= numberOfCharacters;
747 else if(anchor.startIndex >= previousCursorIndex && cursorOffset == 0) // CUR >> [anchor]
749 int startIndex = anchor.startIndex;
750 int endIndex = anchor.endIndex;
751 int index = previousCursorIndex + numberOfCharacters - 1;
753 if(startIndex > index)
755 anchor.startIndex -= numberOfCharacters;
756 anchor.endIndex -= numberOfCharacters;
758 else if(endIndex > index + 1)
760 anchor.endIndex -= numberOfCharacters;
766 delete[] anchor.href;
768 it = logicalModel->mAnchors.Erase(it);
772 else if(cursorOffset == -1) // [<< CUR]
774 int startIndex = anchor.startIndex;
775 int index = previousCursorIndex - numberOfCharacters;
777 if(startIndex >= index)
779 anchor.startIndex = index;
781 anchor.endIndex -= numberOfCharacters;
783 else if(cursorOffset == 0) // [CUR >>]
785 anchor.endIndex -= numberOfCharacters;
789 // When this condition is reached, someting is wrong.
790 DALI_LOG_ERROR("Controller::RemoveTextAnchor[%p] Invaild state cursorOffset[%d]\n", &controller, cursorOffset);
793 DALI_LOG_INFO(gLogFilter, Debug::General, "Controller::RemoveTextAnchor[%p] Anchor[%s] start[%d] end[%d]\n", &controller, anchor.href, anchor.startIndex, anchor.endIndex);
801 } // namespace Toolkit