Merge "Add APIs for intercepting http request in web view." 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
86     Length         textSize = 0u;
87     const uint8_t* utf8     = NULL;
88     if(impl.mMarkupProcessorEnabled)
89     {
90       ProcessMarkupString(text, markupProcessData);
91       textSize = markupProcessData.markupProcessedText.size();
92
93       // This is a bit horrible but std::string returns a (signed) char*
94       utf8 = reinterpret_cast<const uint8_t*>(markupProcessData.markupProcessedText.c_str());
95     }
96     else
97     {
98       textSize = text.size();
99
100       // This is a bit horrible but std::string returns a (signed) char*
101       utf8 = reinterpret_cast<const uint8_t*>(text.c_str());
102     }
103
104     //  Convert text into UTF-32
105     Vector<Character>& utf32Characters = logicalModel->mText;
106     utf32Characters.Resize(textSize);
107
108     // Transform a text array encoded in utf8 into an array encoded in utf32.
109     // It returns the actual number of characters.
110     Length characterCount = Utf8ToUtf32(utf8, textSize, utf32Characters.Begin());
111     utf32Characters.Resize(characterCount);
112
113     DALI_ASSERT_DEBUG(textSize >= characterCount && "Invalid UTF32 conversion length");
114     DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Controller::SetText %p UTF8 size %d, UTF32 size %d\n", &controller, textSize, logicalModel->mText.Count());
115
116     // The characters to be added.
117     impl.mTextUpdateInfo.mNumberOfCharactersToAdd = logicalModel->mText.Count();
118
119     // To reset the cursor position
120     lastCursorIndex = characterCount;
121
122     // Update the rest of the model during size negotiation
123     impl.QueueModifyEvent(ModifyEvent::TEXT_REPLACED);
124
125     // The natural size needs to be re-calculated.
126     impl.mRecalculateNaturalSize = true;
127
128     // The text direction needs to be updated.
129     impl.mUpdateTextDirection = true;
130
131     // Apply modifications to the model
132     impl.mOperationsPending = ALL_OPERATIONS;
133   }
134   else
135   {
136     controller.ShowPlaceholderText();
137   }
138
139   // Resets the cursor position.
140   controller.ResetCursorPosition(lastCursorIndex);
141
142   // Scrolls the text to make the cursor visible.
143   controller.ResetScrollPosition();
144
145   impl.RequestRelayout();
146
147   if(nullptr != eventData)
148   {
149     // Cancel previously queued events
150     eventData->mEventQueue.clear();
151   }
152
153   // Do this last since it provides callbacks into application code.
154   if(NULL != impl.mEditableControlInterface)
155   {
156     impl.mEditableControlInterface->TextChanged(true);
157   }
158 }
159
160 void Controller::TextUpdater::InsertText(Controller& controller, const std::string& text, Controller::InsertType type)
161 {
162   Controller::Impl& impl      = *controller.mImpl;
163   EventData*&       eventData = impl.mEventData;
164
165   DALI_ASSERT_DEBUG(nullptr != eventData && "Unexpected InsertText")
166
167   if(NULL == eventData)
168   {
169     return;
170   }
171
172   bool removedPrevious  = false;
173   bool removedSelected  = false;
174   bool maxLengthReached = false;
175
176   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);
177
178   ModelPtr&        model        = impl.mModel;
179   LogicalModelPtr& logicalModel = model->mLogicalModel;
180
181   // TODO: At the moment the underline runs are only for pre-edit.
182   model->mVisualModel->mUnderlineRuns.Clear();
183
184   // Remove the previous InputMethodContext pre-edit.
185   if(eventData->mPreEditFlag && (0u != eventData->mPreEditLength))
186   {
187     removedPrevious = RemoveText(controller,
188                                  -static_cast<int>(eventData->mPrimaryCursorPosition - eventData->mPreEditStartPosition),
189                                  eventData->mPreEditLength,
190                                  DONT_UPDATE_INPUT_STYLE);
191
192     eventData->mPrimaryCursorPosition = eventData->mPreEditStartPosition;
193     eventData->mPreEditLength         = 0u;
194   }
195   else
196   {
197     // Remove the previous Selection.
198     removedSelected = RemoveSelectedText(controller);
199   }
200
201   Vector<Character> utf32Characters;
202   Length            characterCount = 0u;
203
204   if(!text.empty())
205   {
206     //  Convert text into UTF-32
207     utf32Characters.Resize(text.size());
208
209     // This is a bit horrible but std::string returns a (signed) char*
210     const uint8_t* utf8 = reinterpret_cast<const uint8_t*>(text.c_str());
211
212     // Transform a text array encoded in utf8 into an array encoded in utf32.
213     // It returns the actual number of characters.
214     characterCount = Utf8ToUtf32(utf8, text.size(), utf32Characters.Begin());
215     utf32Characters.Resize(characterCount);
216
217     DALI_ASSERT_DEBUG(text.size() >= utf32Characters.Count() && "Invalid UTF32 conversion length");
218     DALI_LOG_INFO(gLogFilter, Debug::Verbose, "UTF8 size %d, UTF32 size %d\n", text.size(), utf32Characters.Count());
219   }
220
221   if(0u != utf32Characters.Count()) // Check if Utf8ToUtf32 conversion succeeded
222   {
223     // The placeholder text is no longer needed
224     if(impl.IsShowingPlaceholderText())
225     {
226       ResetText(controller);
227     }
228
229     impl.ChangeState(EventData::EDITING);
230
231     // Handle the InputMethodContext (predicitive text) state changes
232     if(COMMIT == type)
233     {
234       // InputMethodContext is no longer handling key-events
235       impl.ClearPreEditFlag();
236     }
237     else // PRE_EDIT
238     {
239       if(!eventData->mPreEditFlag)
240       {
241         DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Entered PreEdit state\n");
242
243         // Record the start of the pre-edit text
244         eventData->mPreEditStartPosition = eventData->mPrimaryCursorPosition;
245       }
246
247       eventData->mPreEditLength = utf32Characters.Count();
248       eventData->mPreEditFlag   = true;
249
250       DALI_LOG_INFO(gLogFilter, Debug::Verbose, "mPreEditStartPosition %d mPreEditLength %d\n", eventData->mPreEditStartPosition, eventData->mPreEditLength);
251     }
252
253     const Length numberOfCharactersInModel = logicalModel->mText.Count();
254
255     // Restrict new text to fit within Maximum characters setting.
256     Length maxSizeOfNewText = std::min((impl.mMaximumNumberOfCharacters - numberOfCharactersInModel), characterCount);
257     maxLengthReached        = (characterCount > maxSizeOfNewText);
258
259     // The cursor position.
260     CharacterIndex& cursorIndex = eventData->mPrimaryCursorPosition;
261
262     // Update the text's style.
263
264     // Updates the text style runs by adding characters.
265     logicalModel->UpdateTextStyleRuns(cursorIndex, maxSizeOfNewText);
266
267     // Get the character index from the cursor index.
268     const CharacterIndex styleIndex = (cursorIndex > 0u) ? cursorIndex - 1u : 0u;
269
270     // Retrieve the text's style for the given index.
271     InputStyle style;
272     impl.RetrieveDefaultInputStyle(style);
273     logicalModel->RetrieveStyle(styleIndex, style);
274
275     InputStyle& inputStyle = eventData->mInputStyle;
276
277     // Whether to add a new text color run.
278     const bool addColorRun = (style.textColor != inputStyle.textColor) && !inputStyle.isDefaultColor;
279
280     // Whether to add a new font run.
281     const bool addFontNameRun   = (style.familyName != inputStyle.familyName) && inputStyle.isFamilyDefined;
282     const bool addFontWeightRun = (style.weight != inputStyle.weight) && inputStyle.isWeightDefined;
283     const bool addFontWidthRun  = (style.width != inputStyle.width) && inputStyle.isWidthDefined;
284     const bool addFontSlantRun  = (style.slant != inputStyle.slant) && inputStyle.isSlantDefined;
285     const bool addFontSizeRun   = (style.size != inputStyle.size) && inputStyle.isSizeDefined;
286
287     // Add style runs.
288     if(addColorRun)
289     {
290       const VectorBase::SizeType numberOfRuns = logicalModel->mColorRuns.Count();
291       logicalModel->mColorRuns.Resize(numberOfRuns + 1u);
292
293       ColorRun& colorRun                       = *(logicalModel->mColorRuns.Begin() + numberOfRuns);
294       colorRun.color                           = inputStyle.textColor;
295       colorRun.characterRun.characterIndex     = cursorIndex;
296       colorRun.characterRun.numberOfCharacters = maxSizeOfNewText;
297     }
298
299     if(addFontNameRun ||
300        addFontWeightRun ||
301        addFontWidthRun ||
302        addFontSlantRun ||
303        addFontSizeRun)
304     {
305       const VectorBase::SizeType numberOfRuns = logicalModel->mFontDescriptionRuns.Count();
306       logicalModel->mFontDescriptionRuns.Resize(numberOfRuns + 1u);
307
308       FontDescriptionRun& fontDescriptionRun = *(logicalModel->mFontDescriptionRuns.Begin() + numberOfRuns);
309
310       if(addFontNameRun)
311       {
312         fontDescriptionRun.familyLength = inputStyle.familyName.size();
313         fontDescriptionRun.familyName   = new char[fontDescriptionRun.familyLength];
314         memcpy(fontDescriptionRun.familyName, inputStyle.familyName.c_str(), fontDescriptionRun.familyLength);
315         fontDescriptionRun.familyDefined = true;
316
317         // The memory allocated for the font family name is freed when the font description is removed from the logical model.
318       }
319
320       if(addFontWeightRun)
321       {
322         fontDescriptionRun.weight        = inputStyle.weight;
323         fontDescriptionRun.weightDefined = true;
324       }
325
326       if(addFontWidthRun)
327       {
328         fontDescriptionRun.width        = inputStyle.width;
329         fontDescriptionRun.widthDefined = true;
330       }
331
332       if(addFontSlantRun)
333       {
334         fontDescriptionRun.slant        = inputStyle.slant;
335         fontDescriptionRun.slantDefined = true;
336       }
337
338       if(addFontSizeRun)
339       {
340         fontDescriptionRun.size        = static_cast<PointSize26Dot6>(inputStyle.size * impl.mFontSizeScale * 64.f);
341         fontDescriptionRun.sizeDefined = true;
342       }
343
344       fontDescriptionRun.characterRun.characterIndex     = cursorIndex;
345       fontDescriptionRun.characterRun.numberOfCharacters = maxSizeOfNewText;
346     }
347
348     // Insert at current cursor position.
349     Vector<Character>& modifyText = logicalModel->mText;
350
351     auto pos = modifyText.End();
352     if(cursorIndex < numberOfCharactersInModel)
353     {
354       pos = modifyText.Begin() + cursorIndex;
355     }
356     unsigned int realPos = pos - modifyText.Begin();
357     modifyText.Insert(pos, utf32Characters.Begin(), utf32Characters.Begin() + maxSizeOfNewText);
358
359     if(NULL != impl.mEditableControlInterface)
360     {
361       impl.mEditableControlInterface->TextInserted(realPos, maxSizeOfNewText, text);
362     }
363
364     TextUpdateInfo& textUpdateInfo = impl.mTextUpdateInfo;
365
366     // Mark the first paragraph to be updated.
367     if(Layout::Engine::SINGLE_LINE_BOX == impl.mLayoutEngine.GetLayout())
368     {
369       textUpdateInfo.mCharacterIndex             = 0;
370       textUpdateInfo.mNumberOfCharactersToRemove = textUpdateInfo.mPreviousNumberOfCharacters;
371       textUpdateInfo.mNumberOfCharactersToAdd    = numberOfCharactersInModel + maxSizeOfNewText;
372       textUpdateInfo.mClearAll                   = true;
373     }
374     else
375     {
376       textUpdateInfo.mCharacterIndex = std::min(cursorIndex, textUpdateInfo.mCharacterIndex);
377       textUpdateInfo.mNumberOfCharactersToAdd += maxSizeOfNewText;
378     }
379
380     // Update the cursor index.
381     cursorIndex += maxSizeOfNewText;
382
383     DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Inserted %d characters, new size %d new cursor %d\n", maxSizeOfNewText, logicalModel->mText.Count(), eventData->mPrimaryCursorPosition);
384   }
385
386   if((0u == logicalModel->mText.Count()) &&
387      impl.IsPlaceholderAvailable())
388   {
389     // Show place-holder if empty after removing the pre-edit text
390     controller.ShowPlaceholderText();
391     eventData->mUpdateCursorPosition = true;
392     impl.ClearPreEditFlag();
393   }
394   else if(removedPrevious ||
395           removedSelected ||
396           (0 != utf32Characters.Count()))
397   {
398     // Queue an inserted event
399     impl.QueueModifyEvent(ModifyEvent::TEXT_INSERTED);
400
401     eventData->mUpdateCursorPosition = true;
402     if(removedSelected)
403     {
404       eventData->mScrollAfterDelete = true;
405     }
406     else
407     {
408       eventData->mScrollAfterUpdatePosition = true;
409     }
410   }
411
412   if(maxLengthReached)
413   {
414     DALI_LOG_INFO(gLogFilter, Debug::Verbose, "MaxLengthReached (%d)\n", logicalModel->mText.Count());
415
416     impl.ResetInputMethodContext();
417
418     if(NULL != impl.mEditableControlInterface)
419     {
420       // Do this last since it provides callbacks into application code
421       impl.mEditableControlInterface->MaxLengthReached();
422     }
423   }
424 }
425
426 void Controller::TextUpdater::PasteText(Controller& controller, const std::string& stringToPaste)
427 {
428   InsertText(controller, stringToPaste, Text::Controller::COMMIT);
429   Controller::Impl& impl = *controller.mImpl;
430   impl.ChangeState(EventData::EDITING);
431   impl.RequestRelayout();
432
433   if(NULL != impl.mEditableControlInterface)
434   {
435     // Do this last since it provides callbacks into application code
436     impl.mEditableControlInterface->TextChanged(true);
437   }
438 }
439
440 bool Controller::TextUpdater::RemoveText(
441   Controller&          controller,
442   int                  cursorOffset,
443   int                  numberOfCharacters,
444   UpdateInputStyleType type)
445 {
446   bool removed = false;
447
448   Controller::Impl& impl      = *controller.mImpl;
449   EventData*&       eventData = impl.mEventData;
450
451   if(nullptr == eventData)
452   {
453     return removed;
454   }
455
456   ModelPtr&        model        = impl.mModel;
457   LogicalModelPtr& logicalModel = model->mLogicalModel;
458
459   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);
460
461   if(!impl.IsShowingPlaceholderText())
462   {
463     // Delete at current cursor position
464     Vector<Character>& currentText    = logicalModel->mText;
465     CharacterIndex&    oldCursorIndex = eventData->mPrimaryCursorPosition;
466
467     CharacterIndex cursorIndex = 0;
468
469     // Validate the cursor position & number of characters
470     if((static_cast<int>(eventData->mPrimaryCursorPosition) + cursorOffset) >= 0)
471     {
472       cursorIndex = eventData->mPrimaryCursorPosition + cursorOffset;
473     }
474
475     if((cursorIndex + numberOfCharacters) > currentText.Count())
476     {
477       numberOfCharacters = currentText.Count() - cursorIndex;
478     }
479
480     TextUpdateInfo& textUpdateInfo = impl.mTextUpdateInfo;
481
482     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.
483        ((cursorIndex + numberOfCharacters) <= textUpdateInfo.mPreviousNumberOfCharacters))
484     {
485       // Mark the paragraphs to be updated.
486       if(Layout::Engine::SINGLE_LINE_BOX == impl.mLayoutEngine.GetLayout())
487       {
488         textUpdateInfo.mCharacterIndex             = 0;
489         textUpdateInfo.mNumberOfCharactersToRemove = textUpdateInfo.mPreviousNumberOfCharacters;
490         textUpdateInfo.mNumberOfCharactersToAdd    = textUpdateInfo.mPreviousNumberOfCharacters - numberOfCharacters;
491         textUpdateInfo.mClearAll                   = true;
492       }
493       else
494       {
495         textUpdateInfo.mCharacterIndex = std::min(cursorIndex, textUpdateInfo.mCharacterIndex);
496         textUpdateInfo.mNumberOfCharactersToRemove += numberOfCharacters;
497       }
498
499       // Update the input style and remove the text's style before removing the text.
500
501       if(UPDATE_INPUT_STYLE == type)
502       {
503         InputStyle& eventDataInputStyle = eventData->mInputStyle;
504
505         // Keep a copy of the current input style.
506         InputStyle currentInputStyle;
507         currentInputStyle.Copy(eventDataInputStyle);
508
509         // Set first the default input style.
510         impl.RetrieveDefaultInputStyle(eventDataInputStyle);
511
512         // Update the input style.
513         logicalModel->RetrieveStyle(cursorIndex, eventDataInputStyle);
514
515         // Compare if the input style has changed.
516         const bool hasInputStyleChanged = !currentInputStyle.Equal(eventDataInputStyle);
517
518         if(hasInputStyleChanged)
519         {
520           const InputStyle::Mask styleChangedMask = currentInputStyle.GetInputStyleChangeMask(eventDataInputStyle);
521           // Queue the input style changed signal.
522           eventData->mInputStyleChangedQueue.PushBack(styleChangedMask);
523         }
524       }
525
526       // If the number of current text and the number of characters to be deleted are same,
527       // it means all texts should be removed and all Preedit variables should be initialized.
528       if((currentText.Count() - numberOfCharacters == 0) && (cursorIndex == 0))
529       {
530         impl.ClearPreEditFlag();
531         textUpdateInfo.mNumberOfCharactersToAdd = 0;
532       }
533
534       // Updates the text style runs by removing characters. Runs with no characters are removed.
535       logicalModel->UpdateTextStyleRuns(cursorIndex, -numberOfCharacters);
536
537       // Remove the characters.
538       Vector<Character>::Iterator first = currentText.Begin() + cursorIndex;
539       Vector<Character>::Iterator last  = first + numberOfCharacters;
540
541       if(NULL != impl.mEditableControlInterface)
542       {
543         std::string utf8;
544         Utf32ToUtf8(first, numberOfCharacters, utf8);
545         impl.mEditableControlInterface->TextDeleted(cursorIndex, numberOfCharacters, utf8);
546       }
547
548       currentText.Erase(first, last);
549
550       // Cursor position retreat
551       oldCursorIndex = cursorIndex;
552
553       eventData->mScrollAfterDelete = true;
554
555       if(EventData::INACTIVE == eventData->mState)
556       {
557         impl.ChangeState(EventData::EDITING);
558       }
559
560       DALI_LOG_INFO(gLogFilter, Debug::General, "Controller::RemoveText %p removed %d\n", &controller, numberOfCharacters);
561       removed = true;
562     }
563   }
564
565   return removed;
566 }
567
568 bool Controller::TextUpdater::RemoveSelectedText(Controller& controller)
569 {
570   bool textRemoved(false);
571
572   Controller::Impl& impl = *controller.mImpl;
573
574   if(EventData::SELECTING == impl.mEventData->mState)
575   {
576     std::string removedString;
577     impl.RetrieveSelection(removedString, true);
578
579     if(!removedString.empty())
580     {
581       textRemoved = true;
582       impl.ChangeState(EventData::EDITING);
583     }
584   }
585
586   return textRemoved;
587 }
588
589 void Controller::TextUpdater::ResetText(Controller& controller)
590 {
591   Controller::Impl& impl         = *controller.mImpl;
592   LogicalModelPtr&  logicalModel = impl.mModel->mLogicalModel;
593
594   // Reset buffers.
595   logicalModel->mText.Clear();
596
597   // Reset the embedded images buffer.
598   logicalModel->ClearEmbeddedImages();
599
600   // We have cleared everything including the placeholder-text
601   impl.PlaceholderCleared();
602
603   impl.mTextUpdateInfo.mCharacterIndex             = 0u;
604   impl.mTextUpdateInfo.mNumberOfCharactersToRemove = impl.mTextUpdateInfo.mPreviousNumberOfCharacters;
605   impl.mTextUpdateInfo.mNumberOfCharactersToAdd    = 0u;
606
607   // Clear any previous text.
608   impl.mTextUpdateInfo.mClearAll = true;
609
610   // The natural size needs to be re-calculated.
611   impl.mRecalculateNaturalSize = true;
612
613   // The text direction needs to be updated.
614   impl.mUpdateTextDirection = true;
615
616   // Apply modifications to the model
617   impl.mOperationsPending = ALL_OPERATIONS;
618 }
619
620 } // namespace Text
621
622 } // namespace Toolkit
623
624 } // namespace Dali