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