Merge "Support Underline to Markup using underlined-character-run" into devel/master
[platform/core/uifw/dali-toolkit.git] / dali-toolkit / internal / text / text-controller-text-updater.cpp
1 /*
2  * Copyright (c) 2021 Samsung Electronics Co., Ltd.
3  *
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
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  *
16  */
17
18 // CLASS HEADER
19 #include <dali-toolkit/internal/text/text-controller-text-updater.h>
20
21 // EXTERNAL INCLUDES
22 #include <dali/integration-api/debug.h>
23 #include <memory.h>
24
25 // INTERNAL INCLUDES
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>
30
31 namespace
32 {
33 #if defined(DEBUG_ENABLED)
34 Debug::Filter* gLogFilter = Debug::Filter::New(Debug::NoLogging, true, "LOG_TEXT_CONTROLS");
35 #endif
36
37 } // namespace
38
39 namespace Dali
40 {
41 namespace Toolkit
42 {
43 namespace Text
44 {
45 void Controller::TextUpdater::SetText(Controller& controller, const std::string& text)
46 {
47   DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Controller::SetText\n");
48
49   Controller::Impl& impl = *controller.mImpl;
50
51   // Reset keyboard as text changed
52   impl.ResetInputMethodContext();
53
54   // Remove the previously set text and style.
55   ResetText(controller);
56
57   // Remove the style.
58   controller.ClearStyleData();
59
60   CharacterIndex lastCursorIndex = 0u;
61
62   EventData*& eventData = impl.mEventData;
63
64   if(nullptr != eventData)
65   {
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))
71     {
72       impl.ChangeState(EventData::EDITING);
73     }
74   }
75
76   if(!text.empty())
77   {
78     ModelPtr&        model        = impl.mModel;
79     LogicalModelPtr& logicalModel = model->mLogicalModel;
80     model->mVisualModel->SetTextColor(impl.mTextColor);
81
82     MarkupProcessData markupProcessData(logicalModel->mColorRuns,
83                                         logicalModel->mFontDescriptionRuns,
84                                         logicalModel->mEmbeddedItems,
85                                         logicalModel->mAnchors,
86                                         logicalModel->mUnderlinedCharacterRuns);
87
88     Length         textSize = 0u;
89     const uint8_t* utf8     = NULL;
90     if(impl.mMarkupProcessorEnabled)
91     {
92       ProcessMarkupString(text, markupProcessData);
93       textSize = markupProcessData.markupProcessedText.size();
94
95       // This is a bit horrible but std::string returns a (signed) char*
96       utf8 = reinterpret_cast<const uint8_t*>(markupProcessData.markupProcessedText.c_str());
97     }
98     else
99     {
100       textSize = text.size();
101
102       // This is a bit horrible but std::string returns a (signed) char*
103       utf8 = reinterpret_cast<const uint8_t*>(text.c_str());
104     }
105
106     //  Convert text into UTF-32
107     Vector<Character>& utf32Characters = logicalModel->mText;
108     utf32Characters.Resize(textSize);
109
110     // Transform a text array encoded in utf8 into an array encoded in utf32.
111     // It returns the actual number of characters.
112     Length characterCount = Utf8ToUtf32(utf8, textSize, utf32Characters.Begin());
113     utf32Characters.Resize(characterCount);
114
115     DALI_ASSERT_DEBUG(textSize >= characterCount && "Invalid UTF32 conversion length");
116     DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Controller::SetText %p UTF8 size %d, UTF32 size %d\n", &controller, textSize, logicalModel->mText.Count());
117
118     // The characters to be added.
119     impl.mTextUpdateInfo.mNumberOfCharactersToAdd = logicalModel->mText.Count();
120
121     // To reset the cursor position
122     lastCursorIndex = characterCount;
123
124     // Update the rest of the model during size negotiation
125     impl.QueueModifyEvent(ModifyEvent::TEXT_REPLACED);
126
127     // The natural size needs to be re-calculated.
128     impl.mRecalculateNaturalSize = true;
129
130     // The text direction needs to be updated.
131     impl.mUpdateTextDirection = true;
132
133     // Apply modifications to the model
134     impl.mOperationsPending = ALL_OPERATIONS;
135   }
136   else
137   {
138     controller.ShowPlaceholderText();
139   }
140
141   // Resets the cursor position.
142   controller.ResetCursorPosition(lastCursorIndex);
143
144   // Scrolls the text to make the cursor visible.
145   controller.ResetScrollPosition();
146
147   impl.RequestRelayout();
148
149   if(nullptr != eventData)
150   {
151     // Cancel previously queued events
152     eventData->mEventQueue.clear();
153   }
154
155   // Do this last since it provides callbacks into application code.
156   if(NULL != impl.mEditableControlInterface)
157   {
158     impl.mEditableControlInterface->TextChanged(true);
159   }
160 }
161
162 void Controller::TextUpdater::InsertText(Controller& controller, const std::string& text, Controller::InsertType type)
163 {
164   Controller::Impl& impl      = *controller.mImpl;
165   EventData*&       eventData = impl.mEventData;
166
167   DALI_ASSERT_DEBUG(nullptr != eventData && "Unexpected InsertText")
168
169   if(NULL == eventData)
170   {
171     return;
172   }
173
174   bool removedPrevious  = false;
175   bool removedSelected  = false;
176   bool maxLengthReached = false;
177
178   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);
179
180   ModelPtr&        model        = impl.mModel;
181   LogicalModelPtr& logicalModel = model->mLogicalModel;
182
183   // TODO: At the moment the underline runs are only for pre-edit.
184   model->mVisualModel->mUnderlineRuns.Clear();
185
186   // Remove the previous InputMethodContext pre-edit.
187   if(eventData->mPreEditFlag && (0u != eventData->mPreEditLength))
188   {
189     removedPrevious = RemoveText(controller,
190                                  -static_cast<int>(eventData->mPrimaryCursorPosition - eventData->mPreEditStartPosition),
191                                  eventData->mPreEditLength,
192                                  DONT_UPDATE_INPUT_STYLE);
193
194     eventData->mPrimaryCursorPosition = eventData->mPreEditStartPosition;
195     eventData->mPreEditLength         = 0u;
196   }
197   else
198   {
199     // Remove the previous Selection.
200     removedSelected = RemoveSelectedText(controller);
201   }
202
203   Vector<Character> utf32Characters;
204   Length            characterCount = 0u;
205
206   if(!text.empty())
207   {
208     //  Convert text into UTF-32
209     utf32Characters.Resize(text.size());
210
211     // This is a bit horrible but std::string returns a (signed) char*
212     const uint8_t* utf8 = reinterpret_cast<const uint8_t*>(text.c_str());
213
214     // Transform a text array encoded in utf8 into an array encoded in utf32.
215     // It returns the actual number of characters.
216     characterCount = Utf8ToUtf32(utf8, text.size(), utf32Characters.Begin());
217     utf32Characters.Resize(characterCount);
218
219     DALI_ASSERT_DEBUG(text.size() >= utf32Characters.Count() && "Invalid UTF32 conversion length");
220     DALI_LOG_INFO(gLogFilter, Debug::Verbose, "UTF8 size %d, UTF32 size %d\n", text.size(), utf32Characters.Count());
221   }
222
223   if(0u != utf32Characters.Count()) // Check if Utf8ToUtf32 conversion succeeded
224   {
225     // The placeholder text is no longer needed
226     if(impl.IsShowingPlaceholderText())
227     {
228       ResetText(controller);
229     }
230
231     impl.ChangeState(EventData::EDITING);
232
233     // Handle the InputMethodContext (predicitive text) state changes
234     if(COMMIT == type)
235     {
236       // InputMethodContext is no longer handling key-events
237       impl.ClearPreEditFlag();
238     }
239     else // PRE_EDIT
240     {
241       if(!eventData->mPreEditFlag)
242       {
243         DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Entered PreEdit state\n");
244
245         // Record the start of the pre-edit text
246         eventData->mPreEditStartPosition = eventData->mPrimaryCursorPosition;
247       }
248
249       eventData->mPreEditLength = utf32Characters.Count();
250       eventData->mPreEditFlag   = true;
251
252       DALI_LOG_INFO(gLogFilter, Debug::Verbose, "mPreEditStartPosition %d mPreEditLength %d\n", eventData->mPreEditStartPosition, eventData->mPreEditLength);
253     }
254
255     const Length numberOfCharactersInModel = logicalModel->mText.Count();
256
257     // Restrict new text to fit within Maximum characters setting.
258     Length temp_length      = (impl.mMaximumNumberOfCharacters > numberOfCharactersInModel ? impl.mMaximumNumberOfCharacters - numberOfCharactersInModel : 0);
259     Length maxSizeOfNewText = std::min(temp_length, characterCount);
260     maxLengthReached        = (characterCount > maxSizeOfNewText);
261
262     // The cursor position.
263     CharacterIndex& cursorIndex = eventData->mPrimaryCursorPosition;
264
265     // Update the text's style.
266
267     // Updates the text style runs by adding characters.
268     logicalModel->UpdateTextStyleRuns(cursorIndex, maxSizeOfNewText);
269
270     // Get the character index from the cursor index.
271     const CharacterIndex styleIndex = (cursorIndex > 0u) ? cursorIndex - 1u : 0u;
272
273     // Retrieve the text's style for the given index.
274     InputStyle style;
275     impl.RetrieveDefaultInputStyle(style);
276     logicalModel->RetrieveStyle(styleIndex, style);
277
278     InputStyle& inputStyle = eventData->mInputStyle;
279
280     // Whether to add a new text color run.
281     const bool addColorRun = (style.textColor != inputStyle.textColor) && !inputStyle.isDefaultColor;
282
283     // Whether to add a new font run.
284     const bool addFontNameRun   = (style.familyName != inputStyle.familyName) && inputStyle.isFamilyDefined;
285     const bool addFontWeightRun = (style.weight != inputStyle.weight) && inputStyle.isWeightDefined;
286     const bool addFontWidthRun  = (style.width != inputStyle.width) && inputStyle.isWidthDefined;
287     const bool addFontSlantRun  = (style.slant != inputStyle.slant) && inputStyle.isSlantDefined;
288     const bool addFontSizeRun   = (style.size != inputStyle.size) && inputStyle.isSizeDefined;
289
290     // Add style runs.
291     if(addColorRun)
292     {
293       const VectorBase::SizeType numberOfRuns = logicalModel->mColorRuns.Count();
294       logicalModel->mColorRuns.Resize(numberOfRuns + 1u);
295
296       ColorRun& colorRun                       = *(logicalModel->mColorRuns.Begin() + numberOfRuns);
297       colorRun.color                           = inputStyle.textColor;
298       colorRun.characterRun.characterIndex     = cursorIndex;
299       colorRun.characterRun.numberOfCharacters = maxSizeOfNewText;
300     }
301
302     if(addFontNameRun ||
303        addFontWeightRun ||
304        addFontWidthRun ||
305        addFontSlantRun ||
306        addFontSizeRun)
307     {
308       const VectorBase::SizeType numberOfRuns = logicalModel->mFontDescriptionRuns.Count();
309       logicalModel->mFontDescriptionRuns.Resize(numberOfRuns + 1u);
310
311       FontDescriptionRun& fontDescriptionRun = *(logicalModel->mFontDescriptionRuns.Begin() + numberOfRuns);
312
313       if(addFontNameRun)
314       {
315         fontDescriptionRun.familyLength = inputStyle.familyName.size();
316         fontDescriptionRun.familyName   = new char[fontDescriptionRun.familyLength];
317         memcpy(fontDescriptionRun.familyName, inputStyle.familyName.c_str(), fontDescriptionRun.familyLength);
318         fontDescriptionRun.familyDefined = true;
319
320         // The memory allocated for the font family name is freed when the font description is removed from the logical model.
321       }
322
323       if(addFontWeightRun)
324       {
325         fontDescriptionRun.weight        = inputStyle.weight;
326         fontDescriptionRun.weightDefined = true;
327       }
328
329       if(addFontWidthRun)
330       {
331         fontDescriptionRun.width        = inputStyle.width;
332         fontDescriptionRun.widthDefined = true;
333       }
334
335       if(addFontSlantRun)
336       {
337         fontDescriptionRun.slant        = inputStyle.slant;
338         fontDescriptionRun.slantDefined = true;
339       }
340
341       if(addFontSizeRun)
342       {
343         fontDescriptionRun.size        = static_cast<PointSize26Dot6>(inputStyle.size * impl.mFontSizeScale * 64.f);
344         fontDescriptionRun.sizeDefined = true;
345       }
346
347       fontDescriptionRun.characterRun.characterIndex     = cursorIndex;
348       fontDescriptionRun.characterRun.numberOfCharacters = maxSizeOfNewText;
349     }
350
351     // Insert at current cursor position.
352     Vector<Character>& modifyText = logicalModel->mText;
353
354     auto pos = modifyText.End();
355     if(cursorIndex < numberOfCharactersInModel)
356     {
357       pos = modifyText.Begin() + cursorIndex;
358     }
359     unsigned int realPos = pos - modifyText.Begin();
360     modifyText.Insert(pos, utf32Characters.Begin(), utf32Characters.Begin() + maxSizeOfNewText);
361
362     if(NULL != impl.mEditableControlInterface)
363     {
364       impl.mEditableControlInterface->TextInserted(realPos, maxSizeOfNewText, text);
365     }
366
367     TextUpdateInfo& textUpdateInfo = impl.mTextUpdateInfo;
368
369     // Mark the first paragraph to be updated.
370     if(Layout::Engine::SINGLE_LINE_BOX == impl.mLayoutEngine.GetLayout())
371     {
372       textUpdateInfo.mCharacterIndex             = 0;
373       textUpdateInfo.mNumberOfCharactersToRemove = textUpdateInfo.mPreviousNumberOfCharacters;
374       textUpdateInfo.mNumberOfCharactersToAdd    = numberOfCharactersInModel + maxSizeOfNewText;
375       textUpdateInfo.mClearAll                   = true;
376     }
377     else
378     {
379       textUpdateInfo.mCharacterIndex = std::min(cursorIndex, textUpdateInfo.mCharacterIndex);
380       textUpdateInfo.mNumberOfCharactersToAdd += maxSizeOfNewText;
381     }
382
383     if(impl.mMarkupProcessorEnabled)
384     {
385       InsertTextAnchor(controller, maxSizeOfNewText, cursorIndex);
386     }
387
388     // Update the cursor index.
389     cursorIndex += maxSizeOfNewText;
390
391     DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Inserted %d characters, new size %d new cursor %d\n", maxSizeOfNewText, logicalModel->mText.Count(), eventData->mPrimaryCursorPosition);
392   }
393
394   if((0u == logicalModel->mText.Count()) &&
395      impl.IsPlaceholderAvailable())
396   {
397     // Show place-holder if empty after removing the pre-edit text
398     controller.ShowPlaceholderText();
399     eventData->mUpdateCursorPosition = true;
400     impl.ClearPreEditFlag();
401   }
402   else if(removedPrevious ||
403           removedSelected ||
404           (0 != utf32Characters.Count()))
405   {
406     // Queue an inserted event
407     impl.QueueModifyEvent(ModifyEvent::TEXT_INSERTED);
408
409     eventData->mUpdateCursorPosition = true;
410     if(removedSelected)
411     {
412       eventData->mScrollAfterDelete = true;
413     }
414     else
415     {
416       eventData->mScrollAfterUpdatePosition = true;
417     }
418   }
419
420   if(maxLengthReached)
421   {
422     DALI_LOG_INFO(gLogFilter, Debug::Verbose, "MaxLengthReached (%d)\n", logicalModel->mText.Count());
423
424     impl.ResetInputMethodContext();
425
426     if(NULL != impl.mEditableControlInterface)
427     {
428       // Do this last since it provides callbacks into application code
429       impl.mEditableControlInterface->MaxLengthReached();
430     }
431   }
432 }
433
434 void Controller::TextUpdater::PasteText(Controller& controller, const std::string& stringToPaste)
435 {
436   InsertText(controller, stringToPaste, Text::Controller::COMMIT);
437   Controller::Impl& impl = *controller.mImpl;
438   impl.ChangeState(EventData::EDITING);
439   impl.RequestRelayout();
440
441   if(NULL != impl.mEditableControlInterface)
442   {
443     // Do this last since it provides callbacks into application code
444     impl.mEditableControlInterface->TextChanged(true);
445   }
446 }
447
448 bool Controller::TextUpdater::RemoveText(
449   Controller&          controller,
450   int                  cursorOffset,
451   int                  numberOfCharacters,
452   UpdateInputStyleType type)
453 {
454   bool removed = false;
455
456   Controller::Impl& impl      = *controller.mImpl;
457   EventData*&       eventData = impl.mEventData;
458
459   if(nullptr == eventData)
460   {
461     return removed;
462   }
463
464   ModelPtr&        model        = impl.mModel;
465   LogicalModelPtr& logicalModel = model->mLogicalModel;
466
467   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);
468
469   if(!impl.IsShowingPlaceholderText())
470   {
471     // Delete at current cursor position
472     Vector<Character>& currentText         = logicalModel->mText;
473     CharacterIndex&    previousCursorIndex = eventData->mPrimaryCursorPosition;
474
475     CharacterIndex cursorIndex = 0;
476
477     // Validate the cursor position & number of characters
478     if((static_cast<int>(eventData->mPrimaryCursorPosition) + cursorOffset) >= 0)
479     {
480       cursorIndex = eventData->mPrimaryCursorPosition + cursorOffset;
481     }
482
483     if((cursorIndex + numberOfCharacters) > currentText.Count())
484     {
485       numberOfCharacters = currentText.Count() - cursorIndex;
486     }
487
488     TextUpdateInfo& textUpdateInfo = impl.mTextUpdateInfo;
489
490     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.
491        ((cursorIndex + numberOfCharacters) <= textUpdateInfo.mPreviousNumberOfCharacters))
492     {
493       // Mark the paragraphs to be updated.
494       if(Layout::Engine::SINGLE_LINE_BOX == impl.mLayoutEngine.GetLayout())
495       {
496         textUpdateInfo.mCharacterIndex             = 0;
497         textUpdateInfo.mNumberOfCharactersToRemove = textUpdateInfo.mPreviousNumberOfCharacters;
498         textUpdateInfo.mNumberOfCharactersToAdd    = textUpdateInfo.mPreviousNumberOfCharacters - numberOfCharacters;
499         textUpdateInfo.mClearAll                   = true;
500       }
501       else
502       {
503         textUpdateInfo.mCharacterIndex = std::min(cursorIndex, textUpdateInfo.mCharacterIndex);
504         textUpdateInfo.mNumberOfCharactersToRemove += numberOfCharacters;
505       }
506
507       // Update the input style and remove the text's style before removing the text.
508
509       if(UPDATE_INPUT_STYLE == type)
510       {
511         InputStyle& eventDataInputStyle = eventData->mInputStyle;
512
513         // Keep a copy of the current input style.
514         InputStyle currentInputStyle;
515         currentInputStyle.Copy(eventDataInputStyle);
516
517         // Set first the default input style.
518         impl.RetrieveDefaultInputStyle(eventDataInputStyle);
519
520         // Update the input style.
521         logicalModel->RetrieveStyle(cursorIndex, eventDataInputStyle);
522
523         // Compare if the input style has changed.
524         const bool hasInputStyleChanged = !currentInputStyle.Equal(eventDataInputStyle);
525
526         if(hasInputStyleChanged)
527         {
528           const InputStyle::Mask styleChangedMask = currentInputStyle.GetInputStyleChangeMask(eventDataInputStyle);
529           // Queue the input style changed signal.
530           eventData->mInputStyleChangedQueue.PushBack(styleChangedMask);
531         }
532       }
533
534       // If the number of current text and the number of characters to be deleted are same,
535       // it means all texts should be removed and all Preedit variables should be initialized.
536       if((currentText.Count() - numberOfCharacters == 0) && (cursorIndex == 0))
537       {
538         impl.ClearPreEditFlag();
539         textUpdateInfo.mNumberOfCharactersToAdd = 0;
540       }
541
542       // Updates the text style runs by removing characters. Runs with no characters are removed.
543       logicalModel->UpdateTextStyleRuns(cursorIndex, -numberOfCharacters);
544
545       // Remove the characters.
546       Vector<Character>::Iterator first = currentText.Begin() + cursorIndex;
547       Vector<Character>::Iterator last  = first + numberOfCharacters;
548
549       if(NULL != impl.mEditableControlInterface)
550       {
551         std::string utf8;
552         Utf32ToUtf8(first, numberOfCharacters, utf8);
553         impl.mEditableControlInterface->TextDeleted(cursorIndex, numberOfCharacters, utf8);
554       }
555
556       currentText.Erase(first, last);
557
558       if(impl.mMarkupProcessorEnabled)
559       {
560         RemoveTextAnchor(controller, cursorOffset, numberOfCharacters, previousCursorIndex);
561       }
562
563       // Cursor position retreat
564       previousCursorIndex = cursorIndex;
565
566       eventData->mScrollAfterDelete = true;
567
568       if(EventData::INACTIVE == eventData->mState)
569       {
570         impl.ChangeState(EventData::EDITING);
571       }
572
573       DALI_LOG_INFO(gLogFilter, Debug::General, "Controller::RemoveText %p removed %d\n", &controller, numberOfCharacters);
574       removed = true;
575     }
576   }
577
578   return removed;
579 }
580
581 bool Controller::TextUpdater::RemoveSelectedText(Controller& controller)
582 {
583   bool textRemoved(false);
584
585   Controller::Impl& impl = *controller.mImpl;
586
587   if(EventData::SELECTING == impl.mEventData->mState)
588   {
589     std::string removedString;
590     impl.RetrieveSelection(removedString, true);
591
592     if(!removedString.empty())
593     {
594       textRemoved = true;
595       impl.ChangeState(EventData::EDITING);
596
597       if(impl.mMarkupProcessorEnabled)
598       {
599         int             cursorOffset        = -1;
600         int             numberOfCharacters  = removedString.length();
601         CharacterIndex& cursorIndex         = impl.mEventData->mPrimaryCursorPosition;
602         CharacterIndex  previousCursorIndex = cursorIndex + numberOfCharacters;
603
604         RemoveTextAnchor(controller, cursorOffset, numberOfCharacters, previousCursorIndex);
605       }
606     }
607   }
608
609   return textRemoved;
610 }
611
612 void Controller::TextUpdater::ResetText(Controller& controller)
613 {
614   Controller::Impl& impl         = *controller.mImpl;
615   LogicalModelPtr&  logicalModel = impl.mModel->mLogicalModel;
616
617   // Reset buffers.
618   logicalModel->mText.Clear();
619
620   // Reset the embedded images buffer.
621   logicalModel->ClearEmbeddedImages();
622
623   // Reset the anchors buffer.
624   logicalModel->ClearAnchors();
625
626   // We have cleared everything including the placeholder-text
627   impl.PlaceholderCleared();
628
629   impl.mTextUpdateInfo.mCharacterIndex             = 0u;
630   impl.mTextUpdateInfo.mNumberOfCharactersToRemove = impl.mTextUpdateInfo.mPreviousNumberOfCharacters;
631   impl.mTextUpdateInfo.mNumberOfCharactersToAdd    = 0u;
632
633   // Clear any previous text.
634   impl.mTextUpdateInfo.mClearAll = true;
635
636   // The natural size needs to be re-calculated.
637   impl.mRecalculateNaturalSize = true;
638
639   // The text direction needs to be updated.
640   impl.mUpdateTextDirection = true;
641
642   // Apply modifications to the model
643   impl.mOperationsPending = ALL_OPERATIONS;
644 }
645
646 void Controller::TextUpdater::InsertTextAnchor(Controller& controller, int numberOfCharacters, CharacterIndex previousCursorIndex)
647 {
648   Controller::Impl& impl         = *controller.mImpl;
649   ModelPtr&         model        = impl.mModel;
650   LogicalModelPtr&  logicalModel = model->mLogicalModel;
651
652   for(auto& anchor : logicalModel->mAnchors)
653   {
654     if(anchor.endIndex < previousCursorIndex) //      [anchor]  CUR
655     {
656       continue;
657     }
658     if(anchor.startIndex < previousCursorIndex) //      [anCURr]
659     {
660       anchor.endIndex += numberOfCharacters;
661     }
662     else // CUR  [anchor]
663     {
664       anchor.startIndex += numberOfCharacters;
665       anchor.endIndex += numberOfCharacters;
666     }
667     DALI_LOG_INFO(gLogFilter, Debug::General, "Controller::InsertTextAnchor[%p] Anchor[%s] start[%d] end[%d]\n", &controller, anchor.href, anchor.startIndex, anchor.endIndex);
668   }
669 }
670
671 void Controller::TextUpdater::RemoveTextAnchor(Controller& controller, int cursorOffset, int numberOfCharacters, CharacterIndex previousCursorIndex)
672 {
673   Controller::Impl&        impl         = *controller.mImpl;
674   ModelPtr&                model        = impl.mModel;
675   LogicalModelPtr&         logicalModel = model->mLogicalModel;
676   Vector<Anchor>::Iterator it           = logicalModel->mAnchors.Begin();
677
678   while(it != logicalModel->mAnchors.End())
679   {
680     Anchor& anchor = *it;
681
682     if(anchor.endIndex <= previousCursorIndex && cursorOffset == 0) // [anchor]    CUR >>
683     {
684       // Nothing happens.
685     }
686     else if(anchor.endIndex <= previousCursorIndex && cursorOffset == -1) // [anchor] << CUR
687     {
688       int endIndex = anchor.endIndex;
689       int offset   = previousCursorIndex - endIndex;
690       int index    = endIndex - (numberOfCharacters - offset);
691
692       if(index < endIndex)
693       {
694         endIndex = index;
695       }
696
697       if((int)anchor.startIndex >= endIndex)
698       {
699         if(anchor.href)
700         {
701           delete[] anchor.href;
702         }
703         it = logicalModel->mAnchors.Erase(it);
704         continue;
705       }
706       else
707       {
708         anchor.endIndex = endIndex;
709       }
710     }
711     else if(anchor.startIndex >= previousCursorIndex && cursorOffset == -1) // << CUR    [anchor]
712     {
713       anchor.startIndex -= numberOfCharacters;
714       anchor.endIndex -= numberOfCharacters;
715     }
716     else if(anchor.startIndex >= previousCursorIndex && cursorOffset == 0) //    CUR >> [anchor]
717     {
718       int startIndex = anchor.startIndex;
719       int endIndex   = anchor.endIndex;
720       int index      = previousCursorIndex + numberOfCharacters - 1;
721
722       if(startIndex > index)
723       {
724         anchor.startIndex -= numberOfCharacters;
725         anchor.endIndex -= numberOfCharacters;
726       }
727       else if(endIndex > index + 1)
728       {
729         anchor.endIndex -= numberOfCharacters;
730       }
731       else
732       {
733         if(anchor.href)
734         {
735           delete[] anchor.href;
736         }
737         it = logicalModel->mAnchors.Erase(it);
738         continue;
739       }
740     }
741     else if(cursorOffset == -1) // [<< CUR]
742     {
743       int startIndex = anchor.startIndex;
744       int index      = previousCursorIndex - numberOfCharacters;
745
746       if(startIndex >= index)
747       {
748         anchor.startIndex = index;
749       }
750       anchor.endIndex -= numberOfCharacters;
751     }
752     else if(cursorOffset == 0) // [CUR >>]
753     {
754       anchor.endIndex -= numberOfCharacters;
755     }
756     else
757     {
758       // When this condition is reached, someting is wrong.
759       DALI_LOG_ERROR("Controller::RemoveTextAnchor[%p] Invaild state cursorOffset[%d]\n", &controller, cursorOffset);
760     }
761
762     DALI_LOG_INFO(gLogFilter, Debug::General, "Controller::RemoveTextAnchor[%p] Anchor[%s] start[%d] end[%d]\n", &controller, anchor.href, anchor.startIndex, anchor.endIndex);
763
764     it++;
765   }
766 }
767
768 } // namespace Text
769
770 } // namespace Toolkit
771
772 } // namespace Dali