[dali_2.1.0] Merge branch 'devel/master'
[platform/core/uifw/dali-toolkit.git] / dali-toolkit / internal / text / text-controller-impl.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-impl.h>
20
21 // EXTERNAL INCLUDES
22 #include <cmath>
23 #include <dali/integration-api/debug.h>
24 #include <dali/public-api/actors/layer.h>
25 #include <dali/devel-api/adaptor-framework/window-devel.h>
26
27 // INTERNAL INCLUDES
28 #include <dali-toolkit/internal/text/character-set-conversion.h>
29 #include <dali-toolkit/internal/text/cursor-helper-functions.h>
30 #include <dali-toolkit/internal/text/text-control-interface.h>
31 #include <dali-toolkit/internal/text/text-controller-impl-data-clearer.h>
32 #include <dali-toolkit/internal/text/text-controller-impl-event-handler.h>
33 #include <dali-toolkit/internal/text/text-controller-impl-model-updater.h>
34 #include <dali-toolkit/internal/text/text-controller-placeholder-handler.h>
35 #include <dali-toolkit/internal/text/text-controller-relayouter.h>
36 #include <dali-toolkit/internal/text/text-editable-control-interface.h>
37 #include <dali-toolkit/internal/text/text-enumerations-impl.h>
38 #include <dali-toolkit/internal/text/text-run-container.h>
39 #include <dali-toolkit/internal/text/text-selection-handle-controller.h>
40
41 using namespace Dali;
42
43 namespace
44 {
45 #if defined(DEBUG_ENABLED)
46 Debug::Filter* gLogFilter = Debug::Filter::New(Debug::NoLogging, true, "LOG_TEXT_CONTROLS");
47 #endif
48
49 constexpr float MAX_FLOAT = std::numeric_limits<float>::max();
50
51 const std::string EMPTY_STRING("");
52
53 } // namespace
54
55 namespace Dali::Toolkit::Text
56 {
57
58 namespace
59 {
60
61 void SetDefaultInputStyle(InputStyle& inputStyle, const FontDefaults* const fontDefaults, const Vector4& textColor)
62 {
63   // Sets the default text's color.
64   inputStyle.textColor      = textColor;
65   inputStyle.isDefaultColor = true;
66
67   inputStyle.familyName.clear();
68   inputStyle.weight = TextAbstraction::FontWeight::NORMAL;
69   inputStyle.width  = TextAbstraction::FontWidth::NORMAL;
70   inputStyle.slant  = TextAbstraction::FontSlant::NORMAL;
71   inputStyle.size   = 0.f;
72
73   inputStyle.lineSpacing = 0.f;
74
75   inputStyle.underlineProperties.clear();
76   inputStyle.shadowProperties.clear();
77   inputStyle.embossProperties.clear();
78   inputStyle.outlineProperties.clear();
79
80   inputStyle.isFamilyDefined = false;
81   inputStyle.isWeightDefined = false;
82   inputStyle.isWidthDefined  = false;
83   inputStyle.isSlantDefined  = false;
84   inputStyle.isSizeDefined   = false;
85
86   inputStyle.isLineSpacingDefined = false;
87
88   inputStyle.isUnderlineDefined = false;
89   inputStyle.isShadowDefined    = false;
90   inputStyle.isEmbossDefined    = false;
91   inputStyle.isOutlineDefined   = false;
92
93   // Sets the default font's family name, weight, width, slant and size.
94   if(fontDefaults)
95   {
96     if(fontDefaults->familyDefined)
97     {
98       inputStyle.familyName      = fontDefaults->mFontDescription.family;
99       inputStyle.isFamilyDefined = true;
100     }
101
102     if(fontDefaults->weightDefined)
103     {
104       inputStyle.weight          = fontDefaults->mFontDescription.weight;
105       inputStyle.isWeightDefined = true;
106     }
107
108     if(fontDefaults->widthDefined)
109     {
110       inputStyle.width          = fontDefaults->mFontDescription.width;
111       inputStyle.isWidthDefined = true;
112     }
113
114     if(fontDefaults->slantDefined)
115     {
116       inputStyle.slant          = fontDefaults->mFontDescription.slant;
117       inputStyle.isSlantDefined = true;
118     }
119
120     if(fontDefaults->sizeDefined)
121     {
122       inputStyle.size          = fontDefaults->mDefaultPointSize;
123       inputStyle.isSizeDefined = true;
124     }
125   }
126 }
127
128 void ChangeTextControllerState(Controller::Impl& impl, EventData::State newState)
129 {
130   EventData* eventData = impl.mEventData;
131
132   if(nullptr == eventData)
133   {
134     // Nothing to do if there is no text input.
135     return;
136   }
137
138   DecoratorPtr& decorator = eventData->mDecorator;
139   if(!decorator)
140   {
141     // Nothing to do if there is no decorator.
142     return;
143   }
144
145   DALI_LOG_INFO(gLogFilter, Debug::General, "ChangeState state:%d  newstate:%d\n", eventData->mState, newState);
146
147   if(eventData->mState != newState)
148   {
149     eventData->mPreviousState = eventData->mState;
150     eventData->mState         = newState;
151
152     switch(eventData->mState)
153     {
154       case EventData::INACTIVE:
155       {
156         decorator->SetActiveCursor(ACTIVE_CURSOR_NONE);
157         decorator->StopCursorBlink();
158         decorator->SetHandleActive(GRAB_HANDLE, false);
159         decorator->SetHandleActive(LEFT_SELECTION_HANDLE, false);
160         decorator->SetHandleActive(RIGHT_SELECTION_HANDLE, false);
161         decorator->SetHighlightActive(false);
162         decorator->SetPopupActive(false);
163         eventData->mDecoratorUpdated = true;
164         break;
165       }
166
167       case EventData::INTERRUPTED:
168       {
169         decorator->SetHandleActive(GRAB_HANDLE, false);
170         decorator->SetHandleActive(LEFT_SELECTION_HANDLE, false);
171         decorator->SetHandleActive(RIGHT_SELECTION_HANDLE, false);
172         decorator->SetHighlightActive(false);
173         decorator->SetPopupActive(false);
174         eventData->mDecoratorUpdated = true;
175         break;
176       }
177
178       case EventData::SELECTING:
179       {
180         decorator->SetActiveCursor(ACTIVE_CURSOR_NONE);
181         decorator->StopCursorBlink();
182         decorator->SetHandleActive(GRAB_HANDLE, false);
183         if(eventData->mGrabHandleEnabled)
184         {
185           decorator->SetHandleActive(LEFT_SELECTION_HANDLE, true);
186           decorator->SetHandleActive(RIGHT_SELECTION_HANDLE, true);
187         }
188         decorator->SetHighlightActive(true);
189         if(eventData->mGrabHandlePopupEnabled)
190         {
191           impl.SetPopupButtons();
192           decorator->SetPopupActive(true);
193         }
194         eventData->mDecoratorUpdated = true;
195         break;
196       }
197
198       case EventData::EDITING:
199       {
200         decorator->SetActiveCursor(ACTIVE_CURSOR_PRIMARY);
201         if(eventData->mCursorBlinkEnabled)
202         {
203           decorator->StartCursorBlink();
204         }
205         // Grab handle is not shown until a tap is received whilst EDITING
206         decorator->SetHandleActive(GRAB_HANDLE, false);
207         decorator->SetHandleActive(LEFT_SELECTION_HANDLE, false);
208         decorator->SetHandleActive(RIGHT_SELECTION_HANDLE, false);
209         decorator->SetHighlightActive(false);
210         if(eventData->mGrabHandlePopupEnabled)
211         {
212           decorator->SetPopupActive(false);
213         }
214         eventData->mDecoratorUpdated = true;
215         break;
216       }
217       case EventData::EDITING_WITH_POPUP:
218       {
219         DALI_LOG_INFO(gLogFilter, Debug::Verbose, "EDITING_WITH_POPUP \n", newState);
220
221         decorator->SetActiveCursor(ACTIVE_CURSOR_PRIMARY);
222         if(eventData->mCursorBlinkEnabled)
223         {
224           decorator->StartCursorBlink();
225         }
226         if(eventData->mSelectionEnabled)
227         {
228           decorator->SetHandleActive(LEFT_SELECTION_HANDLE, false);
229           decorator->SetHandleActive(RIGHT_SELECTION_HANDLE, false);
230           decorator->SetHighlightActive(false);
231         }
232         else if(eventData->mGrabHandleEnabled)
233         {
234           decorator->SetHandleActive(GRAB_HANDLE, true);
235         }
236         if(eventData->mGrabHandlePopupEnabled)
237         {
238           impl.SetPopupButtons();
239           decorator->SetPopupActive(true);
240         }
241         eventData->mDecoratorUpdated = true;
242         break;
243       }
244       case EventData::EDITING_WITH_GRAB_HANDLE:
245       {
246         DALI_LOG_INFO(gLogFilter, Debug::Verbose, "EDITING_WITH_GRAB_HANDLE \n", newState);
247
248         decorator->SetActiveCursor(ACTIVE_CURSOR_PRIMARY);
249         if(eventData->mCursorBlinkEnabled)
250         {
251           decorator->StartCursorBlink();
252         }
253         // Grab handle is not shown until a tap is received whilst EDITING
254         if(eventData->mGrabHandleEnabled)
255         {
256           decorator->SetHandleActive(GRAB_HANDLE, true);
257         }
258         decorator->SetHandleActive(LEFT_SELECTION_HANDLE, false);
259         decorator->SetHandleActive(RIGHT_SELECTION_HANDLE, false);
260         decorator->SetHighlightActive(false);
261         if(eventData->mGrabHandlePopupEnabled)
262         {
263           decorator->SetPopupActive(false);
264         }
265         eventData->mDecoratorUpdated = true;
266         break;
267       }
268
269       case EventData::SELECTION_HANDLE_PANNING:
270       {
271         decorator->SetActiveCursor(ACTIVE_CURSOR_NONE);
272         decorator->StopCursorBlink();
273         decorator->SetHandleActive(GRAB_HANDLE, false);
274         if(eventData->mGrabHandleEnabled)
275         {
276           decorator->SetHandleActive(LEFT_SELECTION_HANDLE, true);
277           decorator->SetHandleActive(RIGHT_SELECTION_HANDLE, true);
278         }
279         decorator->SetHighlightActive(true);
280         if(eventData->mGrabHandlePopupEnabled)
281         {
282           decorator->SetPopupActive(false);
283         }
284         eventData->mDecoratorUpdated = true;
285         break;
286       }
287
288       case EventData::GRAB_HANDLE_PANNING:
289       {
290         DALI_LOG_INFO(gLogFilter, Debug::Verbose, "GRAB_HANDLE_PANNING \n", newState);
291
292         decorator->SetActiveCursor(ACTIVE_CURSOR_PRIMARY);
293         if(eventData->mCursorBlinkEnabled)
294         {
295           decorator->StartCursorBlink();
296         }
297         if(eventData->mGrabHandleEnabled)
298         {
299           decorator->SetHandleActive(GRAB_HANDLE, true);
300         }
301         decorator->SetHandleActive(LEFT_SELECTION_HANDLE, false);
302         decorator->SetHandleActive(RIGHT_SELECTION_HANDLE, false);
303         decorator->SetHighlightActive(false);
304         if(eventData->mGrabHandlePopupEnabled)
305         {
306           decorator->SetPopupActive(false);
307         }
308         eventData->mDecoratorUpdated = true;
309         break;
310       }
311
312       case EventData::EDITING_WITH_PASTE_POPUP:
313       {
314         DALI_LOG_INFO(gLogFilter, Debug::Verbose, "EDITING_WITH_PASTE_POPUP \n", newState);
315
316         decorator->SetActiveCursor(ACTIVE_CURSOR_PRIMARY);
317         if(eventData->mCursorBlinkEnabled)
318         {
319           decorator->StartCursorBlink();
320         }
321
322         if(eventData->mGrabHandleEnabled)
323         {
324           decorator->SetHandleActive(GRAB_HANDLE, true);
325         }
326         decorator->SetHandleActive(LEFT_SELECTION_HANDLE, false);
327         decorator->SetHandleActive(RIGHT_SELECTION_HANDLE, false);
328         decorator->SetHighlightActive(false);
329
330         if(eventData->mGrabHandlePopupEnabled)
331         {
332           impl.SetPopupButtons();
333           decorator->SetPopupActive(true);
334         }
335         eventData->mDecoratorUpdated = true;
336         break;
337       }
338
339       case EventData::TEXT_PANNING:
340       {
341         decorator->SetActiveCursor(ACTIVE_CURSOR_NONE);
342         decorator->StopCursorBlink();
343         decorator->SetHandleActive(GRAB_HANDLE, false);
344         if(eventData->mDecorator->IsHandleActive(LEFT_SELECTION_HANDLE) ||
345             decorator->IsHandleActive(RIGHT_SELECTION_HANDLE))
346         {
347           decorator->SetHandleActive(LEFT_SELECTION_HANDLE, false);
348           decorator->SetHandleActive(RIGHT_SELECTION_HANDLE, false);
349           decorator->SetHighlightActive(true);
350         }
351
352         if(eventData->mGrabHandlePopupEnabled)
353         {
354           decorator->SetPopupActive(false);
355         }
356
357         eventData->mDecoratorUpdated = true;
358         break;
359       }
360     }
361   }
362 }
363
364 } // unnamed Namespace
365
366 EventData::EventData(DecoratorPtr decorator, InputMethodContext& inputMethodContext)
367 : mDecorator(decorator),
368   mInputMethodContext(inputMethodContext),
369   mPlaceholderFont(nullptr),
370   mPlaceholderTextActive(),
371   mPlaceholderTextInactive(),
372   mPlaceholderTextColor(0.8f, 0.8f, 0.8f, 0.8f), // This color has been published in the Public API (placeholder-properties.h).
373   mEventQueue(),
374   mInputStyleChangedQueue(),
375   mPreviousState(INACTIVE),
376   mState(INACTIVE),
377   mPrimaryCursorPosition(0u),
378   mLeftSelectionPosition(0u),
379   mRightSelectionPosition(0u),
380   mPreEditStartPosition(0u),
381   mPreEditLength(0u),
382   mCursorHookPositionX(0.f),
383   mDoubleTapAction(Controller::NoTextTap::NO_ACTION),
384   mLongPressAction(Controller::NoTextTap::SHOW_SELECTION_POPUP),
385   mIsShowingPlaceholderText(false),
386   mPreEditFlag(false),
387   mDecoratorUpdated(false),
388   mCursorBlinkEnabled(true),
389   mGrabHandleEnabled(true),
390   mGrabHandlePopupEnabled(true),
391   mSelectionEnabled(true),
392   mUpdateCursorHookPosition(false),
393   mUpdateCursorPosition(false),
394   mUpdateGrabHandlePosition(false),
395   mUpdateLeftSelectionPosition(false),
396   mUpdateRightSelectionPosition(false),
397   mIsLeftHandleSelected(false),
398   mIsRightHandleSelected(false),
399   mUpdateHighlightBox(false),
400   mScrollAfterUpdatePosition(false),
401   mScrollAfterDelete(false),
402   mAllTextSelected(false),
403   mUpdateInputStyle(false),
404   mPasswordInput(false),
405   mCheckScrollAmount(false),
406   mIsPlaceholderPixelSize(false),
407   mIsPlaceholderElideEnabled(false),
408   mPlaceholderEllipsisFlag(false),
409   mShiftSelectionFlag(true),
410   mUpdateAlignment(false),
411   mEditingEnabled(true)
412 {
413 }
414
415 bool Controller::Impl::ProcessInputEvents()
416 {
417   return ControllerImplEventHandler::ProcessInputEvents(*this);
418 }
419
420 void Controller::Impl::NotifyInputMethodContext()
421 {
422   if(mEventData && mEventData->mInputMethodContext)
423   {
424     CharacterIndex cursorPosition = GetLogicalCursorPosition();
425
426     const Length numberOfWhiteSpaces = GetNumberOfWhiteSpaces(0u);
427
428     // Update the cursor position by removing the initial white spaces.
429     if(cursorPosition < numberOfWhiteSpaces)
430     {
431       cursorPosition = 0u;
432     }
433     else
434     {
435       cursorPosition -= numberOfWhiteSpaces;
436     }
437
438     mEventData->mInputMethodContext.SetCursorPosition(cursorPosition);
439     mEventData->mInputMethodContext.NotifyCursorPosition();
440   }
441 }
442
443 void Controller::Impl::NotifyInputMethodContextMultiLineStatus()
444 {
445   if(mEventData && mEventData->mInputMethodContext)
446   {
447     Text::Layout::Engine::Type layout = mLayoutEngine.GetLayout();
448     mEventData->mInputMethodContext.NotifyTextInputMultiLine(layout == Text::Layout::Engine::MULTI_LINE_BOX);
449   }
450 }
451
452 CharacterIndex Controller::Impl::GetLogicalCursorPosition() const
453 {
454   CharacterIndex cursorPosition = 0u;
455
456   if(mEventData)
457   {
458     if((EventData::SELECTING == mEventData->mState) ||
459        (EventData::SELECTION_HANDLE_PANNING == mEventData->mState))
460     {
461       cursorPosition = std::min(mEventData->mRightSelectionPosition, mEventData->mLeftSelectionPosition);
462     }
463     else
464     {
465       cursorPosition = mEventData->mPrimaryCursorPosition;
466     }
467   }
468
469   return cursorPosition;
470 }
471
472 Length Controller::Impl::GetNumberOfWhiteSpaces(CharacterIndex index) const
473 {
474   Length numberOfWhiteSpaces = 0u;
475
476   // Get the buffer to the text.
477   Character* utf32CharacterBuffer = mModel->mLogicalModel->mText.Begin();
478
479   const Length totalNumberOfCharacters = mModel->mLogicalModel->mText.Count();
480   for(; index < totalNumberOfCharacters; ++index, ++numberOfWhiteSpaces)
481   {
482     if(!TextAbstraction::IsWhiteSpace(*(utf32CharacterBuffer + index)))
483     {
484       break;
485     }
486   }
487
488   return numberOfWhiteSpaces;
489 }
490
491 void Controller::Impl::GetText(std::string& text) const
492 {
493   if(!IsShowingPlaceholderText())
494   {
495     // Retrieves the text string.
496     GetText(0u, text);
497   }
498   else
499   {
500     DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Controller::GetText %p empty (but showing placeholder)\n", this);
501   }
502 }
503
504 void Controller::Impl::GetText(CharacterIndex index, std::string& text) const
505 {
506   // Get the total number of characters.
507   Length numberOfCharacters = mModel->mLogicalModel->mText.Count();
508
509   // Retrieve the text.
510   if(0u != numberOfCharacters)
511   {
512     Utf32ToUtf8(mModel->mLogicalModel->mText.Begin() + index, numberOfCharacters - index, text);
513   }
514 }
515
516 Dali::LayoutDirection::Type Controller::Impl::GetLayoutDirection(Dali::Actor& actor) const
517 {
518   if(mModel->mMatchLayoutDirection == DevelText::MatchLayoutDirection::LOCALE ||
519      (mModel->mMatchLayoutDirection == DevelText::MatchLayoutDirection::INHERIT && !mIsLayoutDirectionChanged))
520   {
521     return static_cast<Dali::LayoutDirection::Type>(DevelWindow::Get(actor).GetRootLayer().GetProperty(Dali::Actor::Property::LAYOUT_DIRECTION).Get<int>());
522   }
523   else
524   {
525     return static_cast<Dali::LayoutDirection::Type>(actor.GetProperty(Dali::Actor::Property::LAYOUT_DIRECTION).Get<int>());
526   }
527 }
528
529 Toolkit::DevelText::TextDirection::Type Controller::Impl::GetTextDirection()
530 {
531   if(mUpdateTextDirection)
532   {
533     // Operations that can be done only once until the text changes.
534     const OperationsMask onlyOnceOperations = static_cast<OperationsMask>(CONVERT_TO_UTF32 |
535                                                                           GET_SCRIPTS |
536                                                                           VALIDATE_FONTS |
537                                                                           GET_LINE_BREAKS |
538                                                                           BIDI_INFO |
539                                                                           SHAPE_TEXT |
540                                                                           GET_GLYPH_METRICS);
541
542     // Set the update info to relayout the whole text.
543     mTextUpdateInfo.mParagraphCharacterIndex     = 0u;
544     mTextUpdateInfo.mRequestedNumberOfCharacters = mModel->mLogicalModel->mText.Count();
545
546     // Make sure the model is up-to-date before layouting
547     UpdateModel(onlyOnceOperations);
548
549     Vector3 naturalSize;
550     Relayouter::DoRelayout(*this,
551                            Size(MAX_FLOAT, MAX_FLOAT),
552                            static_cast<OperationsMask>(onlyOnceOperations |
553                                                        LAYOUT | REORDER | UPDATE_DIRECTION),
554                            naturalSize.GetVectorXY());
555
556     // Do not do again the only once operations.
557     mOperationsPending = static_cast<OperationsMask>(mOperationsPending & ~onlyOnceOperations);
558
559     // Clear the update info. This info will be set the next time the text is updated.
560     mTextUpdateInfo.Clear();
561
562     // FullRelayoutNeeded should be true because DoRelayout is MAX_FLOAT, MAX_FLOAT.
563     mTextUpdateInfo.mFullRelayoutNeeded = true;
564
565     mUpdateTextDirection = false;
566   }
567
568   return mIsTextDirectionRTL ? Toolkit::DevelText::TextDirection::RIGHT_TO_LEFT : Toolkit::DevelText::TextDirection::LEFT_TO_RIGHT;
569 }
570
571 void Controller::Impl::CalculateTextUpdateIndices(Length& numberOfCharacters)
572 {
573   mTextUpdateInfo.mParagraphCharacterIndex = 0u;
574   mTextUpdateInfo.mStartGlyphIndex         = 0u;
575   mTextUpdateInfo.mStartLineIndex          = 0u;
576   numberOfCharacters                       = 0u;
577
578   const Length numberOfParagraphs = mModel->mLogicalModel->mParagraphInfo.Count();
579   if(0u == numberOfParagraphs)
580   {
581     mTextUpdateInfo.mParagraphCharacterIndex = 0u;
582     numberOfCharacters                       = 0u;
583
584     mTextUpdateInfo.mRequestedNumberOfCharacters = mTextUpdateInfo.mNumberOfCharactersToAdd - mTextUpdateInfo.mNumberOfCharactersToRemove;
585
586     // Nothing else to do if there are no paragraphs.
587     return;
588   }
589
590   // Find the paragraphs to be updated.
591   Vector<ParagraphRunIndex> paragraphsToBeUpdated;
592   if(mTextUpdateInfo.mCharacterIndex >= mTextUpdateInfo.mPreviousNumberOfCharacters)
593   {
594     // Text is being added at the end of the current text.
595     if(mTextUpdateInfo.mIsLastCharacterNewParagraph)
596     {
597       // Text is being added in a new paragraph after the last character of the text.
598       mTextUpdateInfo.mParagraphCharacterIndex     = mTextUpdateInfo.mPreviousNumberOfCharacters;
599       numberOfCharacters                           = 0u;
600       mTextUpdateInfo.mRequestedNumberOfCharacters = mTextUpdateInfo.mNumberOfCharactersToAdd - mTextUpdateInfo.mNumberOfCharactersToRemove;
601
602       mTextUpdateInfo.mStartGlyphIndex = mModel->mVisualModel->mGlyphs.Count();
603       mTextUpdateInfo.mStartLineIndex  = mModel->mVisualModel->mLines.Count() - 1u;
604
605       // Nothing else to do;
606       return;
607     }
608
609     paragraphsToBeUpdated.PushBack(numberOfParagraphs - 1u);
610   }
611   else
612   {
613     Length numberOfCharactersToUpdate = 0u;
614     if(mTextUpdateInfo.mFullRelayoutNeeded)
615     {
616       numberOfCharactersToUpdate = mTextUpdateInfo.mPreviousNumberOfCharacters;
617     }
618     else
619     {
620       numberOfCharactersToUpdate = (mTextUpdateInfo.mNumberOfCharactersToRemove > 0u) ? mTextUpdateInfo.mNumberOfCharactersToRemove : 1u;
621     }
622     mModel->mLogicalModel->FindParagraphs(mTextUpdateInfo.mCharacterIndex,
623                                           numberOfCharactersToUpdate,
624                                           paragraphsToBeUpdated);
625   }
626
627   if(0u != paragraphsToBeUpdated.Count())
628   {
629     const ParagraphRunIndex firstParagraphIndex = *(paragraphsToBeUpdated.Begin());
630     const ParagraphRun&     firstParagraph      = *(mModel->mLogicalModel->mParagraphInfo.Begin() + firstParagraphIndex);
631     mTextUpdateInfo.mParagraphCharacterIndex    = firstParagraph.characterRun.characterIndex;
632
633     ParagraphRunIndex   lastParagraphIndex = *(paragraphsToBeUpdated.End() - 1u);
634     const ParagraphRun& lastParagraph      = *(mModel->mLogicalModel->mParagraphInfo.Begin() + lastParagraphIndex);
635
636     if((mTextUpdateInfo.mNumberOfCharactersToRemove > 0u) &&                                           // Some character are removed.
637        (lastParagraphIndex < numberOfParagraphs - 1u) &&                                               // There is a next paragraph.
638        ((lastParagraph.characterRun.characterIndex + lastParagraph.characterRun.numberOfCharacters) == // The last removed character is the new paragraph character.
639         (mTextUpdateInfo.mCharacterIndex + mTextUpdateInfo.mNumberOfCharactersToRemove)))
640     {
641       // The new paragraph character of the last updated paragraph has been removed so is going to be merged with the next one.
642       const ParagraphRun& lastParagraph = *(mModel->mLogicalModel->mParagraphInfo.Begin() + lastParagraphIndex + 1u);
643
644       numberOfCharacters = lastParagraph.characterRun.characterIndex + lastParagraph.characterRun.numberOfCharacters - mTextUpdateInfo.mParagraphCharacterIndex;
645     }
646     else
647     {
648       numberOfCharacters = lastParagraph.characterRun.characterIndex + lastParagraph.characterRun.numberOfCharacters - mTextUpdateInfo.mParagraphCharacterIndex;
649     }
650   }
651
652   mTextUpdateInfo.mRequestedNumberOfCharacters = numberOfCharacters + mTextUpdateInfo.mNumberOfCharactersToAdd - mTextUpdateInfo.mNumberOfCharactersToRemove;
653   mTextUpdateInfo.mStartGlyphIndex             = *(mModel->mVisualModel->mCharactersToGlyph.Begin() + mTextUpdateInfo.mParagraphCharacterIndex);
654 }
655
656 void Controller::Impl::ClearModelData(CharacterIndex startIndex, CharacterIndex endIndex, OperationsMask operations)
657 {
658   ControllerImplDataClearer::ClearModelData(*this, startIndex, endIndex, operations);
659 }
660
661 bool Controller::Impl::UpdateModel(OperationsMask operationsRequired)
662 {
663   return ControllerImplModelUpdater::Update(*this, operationsRequired);
664 }
665
666 void Controller::Impl::RetrieveDefaultInputStyle(InputStyle& inputStyle)
667 {
668   SetDefaultInputStyle(inputStyle, mFontDefaults, mTextColor);
669 }
670
671 float Controller::Impl::GetDefaultFontLineHeight()
672 {
673   FontId defaultFontId = 0u;
674   if(nullptr == mFontDefaults)
675   {
676     TextAbstraction::FontDescription fontDescription;
677     defaultFontId = mFontClient.GetFontId(fontDescription, TextAbstraction::FontClient::DEFAULT_POINT_SIZE * mFontSizeScale);
678   }
679   else
680   {
681     defaultFontId = mFontDefaults->GetFontId(mFontClient, mFontDefaults->mDefaultPointSize * mFontSizeScale);
682   }
683
684   Text::FontMetrics fontMetrics;
685   mMetrics->GetFontMetrics(defaultFontId, fontMetrics);
686
687   return (fontMetrics.ascender - fontMetrics.descender);
688 }
689
690 bool Controller::Impl::SetDefaultLineSpacing(float lineSpacing)
691 {
692   if(std::fabs(lineSpacing - mLayoutEngine.GetDefaultLineSpacing()) > Math::MACHINE_EPSILON_1000)
693   {
694     mLayoutEngine.SetDefaultLineSpacing(lineSpacing);
695     mRecalculateNaturalSize = true;
696
697     RelayoutForNewLineSize();
698     return true;
699   }
700   return false;
701 }
702
703 bool Controller::Impl::SetDefaultLineSize(float lineSize)
704 {
705   if(std::fabs(lineSize - mLayoutEngine.GetDefaultLineSize()) > Math::MACHINE_EPSILON_1000)
706   {
707     mLayoutEngine.SetDefaultLineSize(lineSize);
708     mRecalculateNaturalSize = true;
709
710     RelayoutForNewLineSize();
711     return true;
712   }
713   return false;
714 }
715
716 string Controller::Impl::GetSelectedText()
717 {
718   string text;
719   if(EventData::SELECTING == mEventData->mState)
720   {
721     RetrieveSelection(text, false);
722   }
723   return text;
724 }
725
726 string Controller::Impl::CopyText()
727 {
728   string text;
729   RetrieveSelection(text, false);
730   SendSelectionToClipboard(false); // Text not modified
731
732   mEventData->mUpdateCursorPosition = true;
733
734   RequestRelayout(); // Cursor, Handles, Selection Highlight, Popup
735
736   return text;
737 }
738
739 string Controller::Impl::CutText()
740 {
741   string text;
742   RetrieveSelection(text, false);
743
744   if(!IsEditable())
745   {
746     return EMPTY_STRING;
747   }
748
749   SendSelectionToClipboard(true); // Synchronous call to modify text
750   mOperationsPending = ALL_OPERATIONS;
751
752   if((0u != mModel->mLogicalModel->mText.Count()) ||
753      !IsPlaceholderAvailable())
754   {
755     QueueModifyEvent(ModifyEvent::TEXT_DELETED);
756   }
757   else
758   {
759     PlaceholderHandler::ShowPlaceholderText(*this);
760   }
761
762   mEventData->mUpdateCursorPosition = true;
763   mEventData->mScrollAfterDelete    = true;
764
765   RequestRelayout();
766
767   if(nullptr != mEditableControlInterface)
768   {
769     mEditableControlInterface->TextChanged(true);
770   }
771   return text;
772 }
773
774 void Controller::Impl::SetTextSelectionRange(const uint32_t* pStart, const uint32_t* pEnd)
775 {
776   if(nullptr == mEventData)
777   {
778     // Nothing to do if there is no text.
779     return;
780   }
781
782   if(mEventData->mSelectionEnabled && (pStart || pEnd))
783   {
784     uint32_t length   = static_cast<uint32_t>(mModel->mLogicalModel->mText.Count());
785     uint32_t oldStart = mEventData->mLeftSelectionPosition;
786     uint32_t oldEnd   = mEventData->mRightSelectionPosition;
787
788     if(pStart)
789     {
790       mEventData->mLeftSelectionPosition = std::min(*pStart, length);
791     }
792     if(pEnd)
793     {
794       mEventData->mRightSelectionPosition = std::min(*pEnd, length);
795     }
796
797     if(mEventData->mLeftSelectionPosition == mEventData->mRightSelectionPosition)
798     {
799       ChangeState(EventData::EDITING);
800       mEventData->mPrimaryCursorPosition = mEventData->mLeftSelectionPosition = mEventData->mRightSelectionPosition;
801       mEventData->mUpdateCursorPosition                                       = true;
802     }
803     else
804     {
805       ChangeState(EventData::SELECTING);
806       mEventData->mUpdateHighlightBox           = true;
807       mEventData->mUpdateLeftSelectionPosition  = true;
808       mEventData->mUpdateRightSelectionPosition = true;
809     }
810
811     if(mSelectableControlInterface != nullptr)
812     {
813       mSelectableControlInterface->SelectionChanged(oldStart, oldEnd, mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition);
814     }
815   }
816 }
817
818 CharacterIndex Controller::Impl::GetPrimaryCursorPosition() const
819 {
820   if(nullptr == mEventData)
821   {
822     return 0;
823   }
824   return mEventData->mPrimaryCursorPosition;
825 }
826
827 bool Controller::Impl::SetPrimaryCursorPosition(CharacterIndex index, bool focused)
828 {
829   if(nullptr == mEventData)
830   {
831     // Nothing to do if there is no text.
832     return false;
833   }
834
835   if(mEventData->mPrimaryCursorPosition == index && mEventData->mState != EventData::SELECTING)
836   {
837     // Nothing for same cursor position.
838     return false;
839   }
840
841   uint32_t length                    = static_cast<uint32_t>(mModel->mLogicalModel->mText.Count());
842   uint32_t oldCursorPos              = mEventData->mPrimaryCursorPosition;
843   mEventData->mPrimaryCursorPosition = std::min(index, length);
844   // If there is no focus, only the value is updated.
845   if(focused)
846   {
847     bool     wasInSelectingState = mEventData->mState == EventData::SELECTING;
848     uint32_t oldStart            = mEventData->mLeftSelectionPosition;
849     uint32_t oldEnd              = mEventData->mRightSelectionPosition;
850     ChangeState(EventData::EDITING);
851     mEventData->mLeftSelectionPosition = mEventData->mRightSelectionPosition = mEventData->mPrimaryCursorPosition;
852     mEventData->mUpdateCursorPosition                                        = true;
853
854     if(mSelectableControlInterface != nullptr && wasInSelectingState)
855     {
856       mSelectableControlInterface->SelectionChanged(oldStart, oldEnd, mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition);
857     }
858
859     ScrollTextToMatchCursor();
860   }
861
862   if(nullptr != mEditableControlInterface)
863   {
864     mEditableControlInterface->CursorPositionChanged(oldCursorPos, mEventData->mPrimaryCursorPosition);
865   }
866
867   return true;
868 }
869
870 Uint32Pair Controller::Impl::GetTextSelectionRange() const
871 {
872   Uint32Pair range;
873
874   if(mEventData)
875   {
876     range.first  = mEventData->mLeftSelectionPosition;
877     range.second = mEventData->mRightSelectionPosition;
878   }
879
880   return range;
881 }
882
883 bool Controller::Impl::IsEditable() const
884 {
885   return mEventData && mEventData->mEditingEnabled;
886 }
887
888 void Controller::Impl::SetEditable(bool editable)
889 {
890   if(mEventData)
891   {
892     mEventData->mEditingEnabled = editable;
893
894     if(mEventData->mDecorator)
895     {
896       mEventData->mDecorator->SetEditable(editable);
897     }
898   }
899 }
900
901 void Controller::Impl::UpdateAfterFontChange(const std::string& newDefaultFont)
902 {
903   DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Controller::UpdateAfterFontChange\n");
904
905   if(!mFontDefaults->familyDefined) // If user defined font then should not update when system font changes
906   {
907     DALI_LOG_INFO(gLogFilter, Debug::Concise, "Controller::UpdateAfterFontChange newDefaultFont(%s)\n", newDefaultFont.c_str());
908     mFontDefaults->mFontDescription.family = newDefaultFont;
909
910     ClearFontData();
911
912     RequestRelayout();
913   }
914 }
915
916 void Controller::Impl::RetrieveSelection(std::string& selectedText, bool deleteAfterRetrieval)
917 {
918   if(mEventData->mLeftSelectionPosition == mEventData->mRightSelectionPosition)
919   {
920     // Nothing to select if handles are in the same place.
921     selectedText.clear();
922     return;
923   }
924
925   const bool handlesCrossed = mEventData->mLeftSelectionPosition > mEventData->mRightSelectionPosition;
926
927   //Get start and end position of selection
928   const CharacterIndex startOfSelectedText  = handlesCrossed ? mEventData->mRightSelectionPosition : mEventData->mLeftSelectionPosition;
929   const Length         lengthOfSelectedText = (handlesCrossed ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition) - startOfSelectedText;
930
931   Vector<Character>& utf32Characters    = mModel->mLogicalModel->mText;
932   const Length       numberOfCharacters = utf32Characters.Count();
933
934   // Validate the start and end selection points
935   if((startOfSelectedText + lengthOfSelectedText) <= numberOfCharacters)
936   {
937     //Get text as a UTF8 string
938     Utf32ToUtf8(&utf32Characters[startOfSelectedText], lengthOfSelectedText, selectedText);
939
940     if(deleteAfterRetrieval) // Only delete text if copied successfully
941     {
942       // Keep a copy of the current input style.
943       InputStyle currentInputStyle;
944       currentInputStyle.Copy(mEventData->mInputStyle);
945
946       // Set as input style the style of the first deleted character.
947       mModel->mLogicalModel->RetrieveStyle(startOfSelectedText, mEventData->mInputStyle);
948
949       // Compare if the input style has changed.
950       const bool hasInputStyleChanged = !currentInputStyle.Equal(mEventData->mInputStyle);
951
952       if(hasInputStyleChanged)
953       {
954         const InputStyle::Mask styleChangedMask = currentInputStyle.GetInputStyleChangeMask(mEventData->mInputStyle);
955         // Queue the input style changed signal.
956         mEventData->mInputStyleChangedQueue.PushBack(styleChangedMask);
957       }
958
959       mModel->mLogicalModel->UpdateTextStyleRuns(startOfSelectedText, -static_cast<int>(lengthOfSelectedText));
960
961       // Mark the paragraphs to be updated.
962       if(Layout::Engine::SINGLE_LINE_BOX == mLayoutEngine.GetLayout())
963       {
964         mTextUpdateInfo.mCharacterIndex             = 0;
965         mTextUpdateInfo.mNumberOfCharactersToRemove = mTextUpdateInfo.mPreviousNumberOfCharacters;
966         mTextUpdateInfo.mNumberOfCharactersToAdd    = mTextUpdateInfo.mPreviousNumberOfCharacters - lengthOfSelectedText;
967         mTextUpdateInfo.mClearAll                   = true;
968       }
969       else
970       {
971         mTextUpdateInfo.mCharacterIndex             = startOfSelectedText;
972         mTextUpdateInfo.mNumberOfCharactersToRemove = lengthOfSelectedText;
973       }
974
975       // Delete text between handles
976       Vector<Character>::Iterator first = utf32Characters.Begin() + startOfSelectedText;
977       Vector<Character>::Iterator last  = first + lengthOfSelectedText;
978       utf32Characters.Erase(first, last);
979
980       // Will show the cursor at the first character of the selection.
981       mEventData->mPrimaryCursorPosition = handlesCrossed ? mEventData->mRightSelectionPosition : mEventData->mLeftSelectionPosition;
982     }
983     else
984     {
985       // Will show the cursor at the last character of the selection.
986       mEventData->mPrimaryCursorPosition = handlesCrossed ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition;
987     }
988
989     mEventData->mDecoratorUpdated = true;
990   }
991 }
992
993 void Controller::Impl::SetSelection(int start, int end)
994 {
995   uint32_t oldStart = mEventData->mLeftSelectionPosition;
996   uint32_t oldEnd   = mEventData->mRightSelectionPosition;
997
998   mEventData->mLeftSelectionPosition  = start;
999   mEventData->mRightSelectionPosition = end;
1000   mEventData->mUpdateCursorPosition   = true;
1001
1002   if(mSelectableControlInterface != nullptr)
1003   {
1004     mSelectableControlInterface->SelectionChanged(oldStart, oldEnd, start, end);
1005   }
1006 }
1007
1008 std::pair<int, int> Controller::Impl::GetSelectionIndexes() const
1009 {
1010   return {mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition};
1011 }
1012
1013 void Controller::Impl::ShowClipboard()
1014 {
1015   if(mClipboard)
1016   {
1017     mClipboard.ShowClipboard();
1018   }
1019 }
1020
1021 void Controller::Impl::HideClipboard()
1022 {
1023   if(mClipboard && mClipboardHideEnabled)
1024   {
1025     mClipboard.HideClipboard();
1026   }
1027 }
1028
1029 void Controller::Impl::SetClipboardHideEnable(bool enable)
1030 {
1031   mClipboardHideEnabled = enable;
1032 }
1033
1034 bool Controller::Impl::CopyStringToClipboard(const std::string& source)
1035 {
1036   //Send string to clipboard
1037   return (mClipboard && mClipboard.SetItem(source));
1038 }
1039
1040 void Controller::Impl::SendSelectionToClipboard(bool deleteAfterSending)
1041 {
1042   std::string selectedText;
1043   RetrieveSelection(selectedText, deleteAfterSending);
1044   CopyStringToClipboard(selectedText);
1045   ChangeState(EventData::EDITING);
1046 }
1047
1048 void Controller::Impl::RequestGetTextFromClipboard()
1049 {
1050   if(mClipboard)
1051   {
1052     mClipboard.RequestItem();
1053   }
1054 }
1055
1056 void Controller::Impl::RepositionSelectionHandles()
1057 {
1058   SelectionHandleController::Reposition(*this);
1059 }
1060 void Controller::Impl::RepositionSelectionHandles(float visualX, float visualY, Controller::NoTextTap::Action action)
1061 {
1062   SelectionHandleController::Reposition(*this, visualX, visualY, action);
1063 }
1064
1065 void Controller::Impl::SetPopupButtons()
1066 {
1067   /**
1068    *  Sets the Popup buttons to be shown depending on State.
1069    *
1070    *  If SELECTING :  CUT & COPY + ( PASTE & CLIPBOARD if content available to paste )
1071    *
1072    *  If EDITING_WITH_POPUP : SELECT & SELECT_ALL
1073    */
1074
1075   bool                        isEditable    = IsEditable();
1076   TextSelectionPopup::Buttons buttonsToShow = TextSelectionPopup::NONE;
1077
1078   if(EventData::SELECTING == mEventData->mState)
1079   {
1080     buttonsToShow = TextSelectionPopup::Buttons(TextSelectionPopup::COPY);
1081     if(isEditable)
1082     {
1083       buttonsToShow = TextSelectionPopup::Buttons(buttonsToShow | TextSelectionPopup::CUT);
1084     }
1085
1086     if(!IsClipboardEmpty())
1087     {
1088       if(isEditable)
1089       {
1090         buttonsToShow = TextSelectionPopup::Buttons((buttonsToShow | TextSelectionPopup::PASTE));
1091       }
1092       buttonsToShow = TextSelectionPopup::Buttons((buttonsToShow | TextSelectionPopup::CLIPBOARD));
1093     }
1094
1095     if(!mEventData->mAllTextSelected)
1096     {
1097       buttonsToShow = TextSelectionPopup::Buttons((buttonsToShow | TextSelectionPopup::SELECT_ALL));
1098     }
1099   }
1100   else if(EventData::EDITING_WITH_POPUP == mEventData->mState)
1101   {
1102     if(mModel->mLogicalModel->mText.Count() && !IsShowingPlaceholderText())
1103     {
1104       buttonsToShow = TextSelectionPopup::Buttons(TextSelectionPopup::SELECT | TextSelectionPopup::SELECT_ALL);
1105     }
1106
1107     if(!IsClipboardEmpty())
1108     {
1109       if(isEditable)
1110       {
1111         buttonsToShow = TextSelectionPopup::Buttons((buttonsToShow | TextSelectionPopup::PASTE));
1112       }
1113       buttonsToShow = TextSelectionPopup::Buttons((buttonsToShow | TextSelectionPopup::CLIPBOARD));
1114     }
1115   }
1116   else if(EventData::EDITING_WITH_PASTE_POPUP == mEventData->mState)
1117   {
1118     if(!IsClipboardEmpty())
1119     {
1120       if(isEditable)
1121       {
1122         buttonsToShow = TextSelectionPopup::Buttons((buttonsToShow | TextSelectionPopup::PASTE));
1123       }
1124       buttonsToShow = TextSelectionPopup::Buttons((buttonsToShow | TextSelectionPopup::CLIPBOARD));
1125     }
1126   }
1127
1128   mEventData->mDecorator->SetEnabledPopupButtons(buttonsToShow);
1129 }
1130
1131 void Controller::Impl::ChangeState(EventData::State newState)
1132 {
1133   ChangeTextControllerState(*this, newState);
1134 }
1135
1136 void Controller::Impl::GetCursorPosition(CharacterIndex logical,
1137                                          CursorInfo&    cursorInfo)
1138 {
1139   if(!IsShowingRealText())
1140   {
1141     // Do not want to use the place-holder text to set the cursor position.
1142
1143     // Use the line's height of the font's family set to set the cursor's size.
1144     // If there is no font's family set, use the default font.
1145     // Use the current alignment to place the cursor at the beginning, center or end of the box.
1146
1147     cursorInfo.lineOffset          = 0.f;
1148     cursorInfo.lineHeight          = GetDefaultFontLineHeight();
1149     cursorInfo.primaryCursorHeight = cursorInfo.lineHeight;
1150
1151     bool isRTL = false;
1152     if(mModel->mMatchLayoutDirection != DevelText::MatchLayoutDirection::CONTENTS)
1153     {
1154       isRTL = mLayoutDirection == LayoutDirection::RIGHT_TO_LEFT;
1155     }
1156
1157     switch(mModel->mHorizontalAlignment)
1158     {
1159       case Text::HorizontalAlignment::BEGIN:
1160       {
1161         if(isRTL)
1162         {
1163           cursorInfo.primaryPosition.x = mModel->mVisualModel->mControlSize.width - mEventData->mDecorator->GetCursorWidth();
1164         }
1165         else
1166         {
1167           cursorInfo.primaryPosition.x = 0.f;
1168         }
1169         break;
1170       }
1171       case Text::HorizontalAlignment::CENTER:
1172       {
1173         cursorInfo.primaryPosition.x = floorf(0.5f * mModel->mVisualModel->mControlSize.width);
1174         break;
1175       }
1176       case Text::HorizontalAlignment::END:
1177       {
1178         if(isRTL)
1179         {
1180           cursorInfo.primaryPosition.x = 0.f;
1181         }
1182         else
1183         {
1184           cursorInfo.primaryPosition.x = mModel->mVisualModel->mControlSize.width - mEventData->mDecorator->GetCursorWidth();
1185         }
1186         break;
1187       }
1188     }
1189
1190     // Nothing else to do.
1191     return;
1192   }
1193
1194   const bool                  isMultiLine = (Layout::Engine::MULTI_LINE_BOX == mLayoutEngine.GetLayout());
1195   GetCursorPositionParameters parameters;
1196   parameters.visualModel  = mModel->mVisualModel;
1197   parameters.logicalModel = mModel->mLogicalModel;
1198   parameters.metrics      = mMetrics;
1199   parameters.logical      = logical;
1200   parameters.isMultiline  = isMultiLine;
1201
1202   Text::GetCursorPosition(parameters,
1203                           cursorInfo);
1204
1205   // Adds Outline offset.
1206   const float outlineWidth = static_cast<float>(mModel->GetOutlineWidth());
1207   cursorInfo.primaryPosition.x += outlineWidth;
1208   cursorInfo.primaryPosition.y += outlineWidth;
1209   cursorInfo.secondaryPosition.x += outlineWidth;
1210   cursorInfo.secondaryPosition.y += outlineWidth;
1211
1212   if(isMultiLine)
1213   {
1214     // If the text is editable and multi-line, the cursor position after a white space shouldn't exceed the boundaries of the text control.
1215
1216     // Note the white spaces laid-out at the end of the line might exceed the boundaries of the control.
1217     // The reason is a wrapped line must not start with a white space so they are laid-out at the end of the line.
1218
1219     if(0.f > cursorInfo.primaryPosition.x)
1220     {
1221       cursorInfo.primaryPosition.x = 0.f;
1222     }
1223
1224     const float edgeWidth = mModel->mVisualModel->mControlSize.width - static_cast<float>(mEventData->mDecorator->GetCursorWidth());
1225     if(cursorInfo.primaryPosition.x > edgeWidth)
1226     {
1227       cursorInfo.primaryPosition.x = edgeWidth;
1228     }
1229   }
1230 }
1231
1232 CharacterIndex Controller::Impl::CalculateNewCursorIndex(CharacterIndex index) const
1233 {
1234   if(nullptr == mEventData)
1235   {
1236     // Nothing to do if there is no text input.
1237     return 0u;
1238   }
1239
1240   CharacterIndex cursorIndex = mEventData->mPrimaryCursorPosition;
1241
1242   const GlyphIndex* const charactersToGlyphBuffer  = mModel->mVisualModel->mCharactersToGlyph.Begin();
1243   const Length* const     charactersPerGlyphBuffer = mModel->mVisualModel->mCharactersPerGlyph.Begin();
1244
1245   GlyphIndex glyphIndex         = *(charactersToGlyphBuffer + index);
1246   Length     numberOfCharacters = *(charactersPerGlyphBuffer + glyphIndex);
1247
1248   if(numberOfCharacters > 1u)
1249   {
1250     const Script script = mModel->mLogicalModel->GetScript(index);
1251     if(HasLigatureMustBreak(script))
1252     {
1253       // Prevents to jump the whole Latin ligatures like fi, ff, or Arabic ï»», ...
1254       numberOfCharacters = 1u;
1255     }
1256   }
1257   else
1258   {
1259     while(0u == numberOfCharacters)
1260     {
1261       ++glyphIndex;
1262       numberOfCharacters = *(charactersPerGlyphBuffer + glyphIndex);
1263     }
1264   }
1265
1266   if(index < mEventData->mPrimaryCursorPosition)
1267   {
1268     cursorIndex -= numberOfCharacters;
1269   }
1270   else
1271   {
1272     cursorIndex += numberOfCharacters;
1273   }
1274
1275   // Will update the cursor hook position.
1276   mEventData->mUpdateCursorHookPosition = true;
1277
1278   return cursorIndex;
1279 }
1280
1281 void Controller::Impl::UpdateCursorPosition(const CursorInfo& cursorInfo)
1282 {
1283   DALI_LOG_INFO(gLogFilter, Debug::Verbose, "-->Controller::UpdateCursorPosition %p\n", this);
1284   if(nullptr == mEventData)
1285   {
1286     // Nothing to do if there is no text input.
1287     DALI_LOG_INFO(gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition no event data\n");
1288     return;
1289   }
1290
1291   const Vector2 cursorPosition = cursorInfo.primaryPosition + mModel->mScrollPosition;
1292
1293   mEventData->mDecorator->SetGlyphOffset(PRIMARY_CURSOR, cursorInfo.glyphOffset);
1294
1295   // Sets the cursor position.
1296   mEventData->mDecorator->SetPosition(PRIMARY_CURSOR,
1297                                       cursorPosition.x,
1298                                       cursorPosition.y,
1299                                       cursorInfo.primaryCursorHeight,
1300                                       cursorInfo.lineHeight);
1301   DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Primary cursor position: %f,%f\n", cursorPosition.x, cursorPosition.y);
1302
1303   if(mEventData->mUpdateGrabHandlePosition)
1304   {
1305     // Sets the grab handle position.
1306     mEventData->mDecorator->SetPosition(GRAB_HANDLE,
1307                                         cursorPosition.x,
1308                                         cursorInfo.lineOffset + mModel->mScrollPosition.y,
1309                                         cursorInfo.lineHeight);
1310   }
1311
1312   if(cursorInfo.isSecondaryCursor)
1313   {
1314     mEventData->mDecorator->SetPosition(SECONDARY_CURSOR,
1315                                         cursorInfo.secondaryPosition.x + mModel->mScrollPosition.x,
1316                                         cursorInfo.secondaryPosition.y + mModel->mScrollPosition.y,
1317                                         cursorInfo.secondaryCursorHeight,
1318                                         cursorInfo.lineHeight);
1319     DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Secondary cursor position: %f,%f\n", cursorInfo.secondaryPosition.x + mModel->mScrollPosition.x, cursorInfo.secondaryPosition.y + mModel->mScrollPosition.y);
1320   }
1321
1322   // Set which cursors are active according the state.
1323   if(EventData::IsEditingState(mEventData->mState) || (EventData::GRAB_HANDLE_PANNING == mEventData->mState))
1324   {
1325     if(cursorInfo.isSecondaryCursor)
1326     {
1327       mEventData->mDecorator->SetActiveCursor(ACTIVE_CURSOR_BOTH);
1328     }
1329     else
1330     {
1331       mEventData->mDecorator->SetActiveCursor(ACTIVE_CURSOR_PRIMARY);
1332     }
1333   }
1334   else
1335   {
1336     mEventData->mDecorator->SetActiveCursor(ACTIVE_CURSOR_NONE);
1337   }
1338
1339   DALI_LOG_INFO(gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition\n");
1340 }
1341
1342 void Controller::Impl::UpdateSelectionHandle(HandleType        handleType,
1343                                              const CursorInfo& cursorInfo)
1344 {
1345   SelectionHandleController::Update(*this, handleType, cursorInfo);
1346 }
1347
1348 void Controller::Impl::ClampHorizontalScroll(const Vector2& layoutSize)
1349 {
1350   // Clamp between -space & -alignment offset.
1351
1352   if(layoutSize.width > mModel->mVisualModel->mControlSize.width)
1353   {
1354     const float space         = (layoutSize.width - mModel->mVisualModel->mControlSize.width) + mModel->mAlignmentOffset;
1355     mModel->mScrollPosition.x = (mModel->mScrollPosition.x < -space) ? -space : mModel->mScrollPosition.x;
1356     mModel->mScrollPosition.x = (mModel->mScrollPosition.x > -mModel->mAlignmentOffset) ? -mModel->mAlignmentOffset : mModel->mScrollPosition.x;
1357
1358     mEventData->mDecoratorUpdated = true;
1359   }
1360   else
1361   {
1362     mModel->mScrollPosition.x = 0.f;
1363   }
1364 }
1365
1366 void Controller::Impl::ClampVerticalScroll(const Vector2& layoutSize)
1367 {
1368   if(Layout::Engine::SINGLE_LINE_BOX == mLayoutEngine.GetLayout())
1369   {
1370     // Nothing to do if the text is single line.
1371     return;
1372   }
1373
1374   // Clamp between -space & 0.
1375   if(layoutSize.height > mModel->mVisualModel->mControlSize.height)
1376   {
1377     const float space         = (layoutSize.height - mModel->mVisualModel->mControlSize.height);
1378     mModel->mScrollPosition.y = (mModel->mScrollPosition.y < -space) ? -space : mModel->mScrollPosition.y;
1379     mModel->mScrollPosition.y = (mModel->mScrollPosition.y > 0.f) ? 0.f : mModel->mScrollPosition.y;
1380
1381     mEventData->mDecoratorUpdated = true;
1382   }
1383   else
1384   {
1385     mModel->mScrollPosition.y = 0.f;
1386   }
1387 }
1388
1389 void Controller::Impl::ScrollToMakePositionVisible(const Vector2& position, float lineHeight)
1390 {
1391   const float cursorWidth = mEventData->mDecorator ? static_cast<float>(mEventData->mDecorator->GetCursorWidth()) : 0.f;
1392
1393   // position is in actor's coords.
1394   const float positionEndX = position.x + cursorWidth;
1395   const float positionEndY = position.y + lineHeight;
1396
1397   // Transform the position to decorator coords.
1398   const float decoratorPositionBeginX = position.x + mModel->mScrollPosition.x;
1399   const float decoratorPositionEndX   = positionEndX + mModel->mScrollPosition.x;
1400
1401   const float decoratorPositionBeginY = position.y + mModel->mScrollPosition.y;
1402   const float decoratorPositionEndY   = positionEndY + mModel->mScrollPosition.y;
1403
1404   if(decoratorPositionBeginX < 0.f)
1405   {
1406     mModel->mScrollPosition.x = -position.x;
1407   }
1408   else if(decoratorPositionEndX > mModel->mVisualModel->mControlSize.width)
1409   {
1410     mModel->mScrollPosition.x = mModel->mVisualModel->mControlSize.width - positionEndX;
1411   }
1412
1413   if(Layout::Engine::MULTI_LINE_BOX == mLayoutEngine.GetLayout())
1414   {
1415     if(decoratorPositionBeginY < 0.f)
1416     {
1417       mModel->mScrollPosition.y = -position.y;
1418     }
1419     else if(decoratorPositionEndY > mModel->mVisualModel->mControlSize.height)
1420     {
1421       mModel->mScrollPosition.y = mModel->mVisualModel->mControlSize.height - positionEndY;
1422     }
1423   }
1424 }
1425
1426 void Controller::Impl::ScrollTextToMatchCursor(const CursorInfo& cursorInfo)
1427 {
1428   // Get the current cursor position in decorator coords.
1429   const Vector2& currentCursorPosition = mEventData->mDecorator->GetPosition(PRIMARY_CURSOR);
1430
1431   const LineIndex lineIndex = mModel->mVisualModel->GetLineOfCharacter(mEventData->mPrimaryCursorPosition);
1432
1433   // Calculate the offset to match the cursor position before the character was deleted.
1434   mModel->mScrollPosition.x = currentCursorPosition.x - cursorInfo.primaryPosition.x;
1435
1436   //If text control has more than two lines and current line index is not last, calculate scrollpositionY
1437   if(mModel->mVisualModel->mLines.Count() > 1u && lineIndex != mModel->mVisualModel->mLines.Count() - 1u)
1438   {
1439     const float currentCursorGlyphOffset = mEventData->mDecorator->GetGlyphOffset(PRIMARY_CURSOR);
1440     mModel->mScrollPosition.y            = currentCursorPosition.y - cursorInfo.lineOffset - currentCursorGlyphOffset;
1441   }
1442
1443   ClampHorizontalScroll(mModel->mVisualModel->GetLayoutSize());
1444   ClampVerticalScroll(mModel->mVisualModel->GetLayoutSize());
1445
1446   // Makes the new cursor position visible if needed.
1447   ScrollToMakePositionVisible(cursorInfo.primaryPosition, cursorInfo.lineHeight);
1448 }
1449
1450 void Controller::Impl::ScrollTextToMatchCursor()
1451 {
1452   CursorInfo cursorInfo;
1453   GetCursorPosition(mEventData->mPrimaryCursorPosition, cursorInfo);
1454   ScrollTextToMatchCursor(cursorInfo);
1455 }
1456
1457 void Controller::Impl::RequestRelayout()
1458 {
1459   if(nullptr != mControlInterface)
1460   {
1461     mControlInterface->RequestTextRelayout();
1462   }
1463 }
1464
1465 void Controller::Impl::RelayoutForNewLineSize()
1466 {
1467   // relayout all characters
1468   mTextUpdateInfo.mCharacterIndex             = 0;
1469   mTextUpdateInfo.mNumberOfCharactersToRemove = mTextUpdateInfo.mPreviousNumberOfCharacters;
1470   mTextUpdateInfo.mNumberOfCharactersToAdd    = mModel->mLogicalModel->mText.Count();
1471   mOperationsPending                          = static_cast<OperationsMask>(mOperationsPending | LAYOUT);
1472
1473   //remove selection
1474   if(mEventData && mEventData->mState == EventData::SELECTING)
1475   {
1476     ChangeState(EventData::EDITING);
1477   }
1478
1479   RequestRelayout();
1480 }
1481
1482 bool Controller::Impl::IsInputStyleChangedSignalsQueueEmpty()
1483 {
1484   return (NULL == mEventData) || (0u == mEventData->mInputStyleChangedQueue.Count());
1485 }
1486
1487 void Controller::Impl::ProcessInputStyleChangedSignals()
1488 {
1489   if(mEventData)
1490   {
1491     if(mEditableControlInterface)
1492     {
1493       // Emit the input style changed signal for each mask
1494       std::for_each(mEventData->mInputStyleChangedQueue.begin(),
1495                     mEventData->mInputStyleChangedQueue.end(),
1496                     [&](const auto mask) { mEditableControlInterface->InputStyleChanged(mask); } );
1497     }
1498
1499     mEventData->mInputStyleChangedQueue.Clear();
1500   }
1501 }
1502
1503 void Controller::Impl::ScrollBy(Vector2 scroll)
1504 {
1505   if(mEventData && (fabs(scroll.x) > Math::MACHINE_EPSILON_0 || fabs(scroll.y) > Math::MACHINE_EPSILON_0))
1506   {
1507     const Vector2& layoutSize    = mModel->mVisualModel->GetLayoutSize();
1508     const Vector2  currentScroll = mModel->mScrollPosition;
1509
1510     scroll.x = -scroll.x;
1511     scroll.y = -scroll.y;
1512
1513     if(fabs(scroll.x) > Math::MACHINE_EPSILON_0)
1514     {
1515       mModel->mScrollPosition.x += scroll.x;
1516       ClampHorizontalScroll(layoutSize);
1517     }
1518
1519     if(fabs(scroll.y) > Math::MACHINE_EPSILON_0)
1520     {
1521       mModel->mScrollPosition.y += scroll.y;
1522       ClampVerticalScroll(layoutSize);
1523     }
1524
1525     if(mModel->mScrollPosition != currentScroll)
1526     {
1527       mEventData->mDecorator->UpdatePositions(mModel->mScrollPosition - currentScroll);
1528       RequestRelayout();
1529     }
1530   }
1531 }
1532
1533 float Controller::Impl::GetHorizontalScrollPosition()
1534 {
1535   // Scroll values are negative internally so we convert them to positive numbers
1536   return mEventData ? -mModel->mScrollPosition.x : 0.0f;
1537 }
1538
1539 float Controller::Impl::GetVerticalScrollPosition()
1540 {
1541   // Scroll values are negative internally so we convert them to positive numbers
1542   return mEventData ? -mModel->mScrollPosition.y : 0.0f;
1543 }
1544
1545 Vector3 Controller::Impl::GetAnchorPosition(Anchor anchor) const
1546 {
1547   //TODO
1548   return Vector3(10.f, 10.f, 10.f);
1549 }
1550
1551 Vector2 Controller::Impl::GetAnchorSize(Anchor anchor) const
1552 {
1553   //TODO
1554   return Vector2(10.f, 10.f);
1555 }
1556
1557 Toolkit::TextAnchor Controller::Impl::CreateAnchorActor(Anchor anchor)
1558 {
1559   auto actor = Toolkit::TextAnchor::New();
1560   actor.SetProperty(Actor::Property::PARENT_ORIGIN, ParentOrigin::TOP_LEFT);
1561   actor.SetProperty(Actor::Property::ANCHOR_POINT, AnchorPoint::TOP_LEFT);
1562   const Vector3 anchorPosition = GetAnchorPosition(anchor);
1563   actor.SetProperty(Actor::Property::POSITION, anchorPosition);
1564   const Vector2 anchorSize = GetAnchorSize(anchor);
1565   actor.SetProperty(Actor::Property::SIZE, anchorSize);
1566   std::string anchorText(mModel->mLogicalModel->mText.Begin() + anchor.startIndex, mModel->mLogicalModel->mText.Begin() + anchor.endIndex);
1567   actor.SetProperty(Actor::Property::NAME, anchorText);
1568   actor.SetProperty(Toolkit::TextAnchor::Property::URI, std::string(anchor.href));
1569   actor.SetProperty(Toolkit::TextAnchor::Property::START_CHARACTER_INDEX, static_cast<int>(anchor.startIndex));
1570   actor.SetProperty(Toolkit::TextAnchor::Property::END_CHARACTER_INDEX, static_cast<int>(anchor.endIndex));
1571   return actor;
1572 }
1573
1574 void Controller::Impl::GetAnchorActors(std::vector<Toolkit::TextAnchor>& anchorActors)
1575 {
1576   /* TODO: Now actors are created/destroyed in every "RenderText" function call. Even when we add just 1 character,
1577            we need to create and destroy potentially many actors. Some optimization can be considered here.
1578            Maybe a "dirty" flag in mLogicalModel? */
1579   anchorActors.clear();
1580   for(auto& anchor : mModel->mLogicalModel->mAnchors)
1581   {
1582     auto actor = CreateAnchorActor(anchor);
1583     anchorActors.push_back(actor);
1584   }
1585 }
1586
1587 int32_t Controller::Impl::GetAnchorIndex(size_t characterOffset) const
1588 {
1589   Vector<Anchor>::Iterator it = mModel->mLogicalModel->mAnchors.Begin();
1590
1591   while(it != mModel->mLogicalModel->mAnchors.End() && (it->startIndex > characterOffset || it->endIndex <= characterOffset))
1592   {
1593     it++;
1594   }
1595
1596   return it == mModel->mLogicalModel->mAnchors.End() ? -1 : it - mModel->mLogicalModel->mAnchors.Begin();
1597 }
1598
1599 void Controller::Impl::CopyUnderlinedFromLogicalToVisualModels(bool shouldClearPreUnderlineRuns)
1600 {
1601   //Underlined character runs for markup-processor
1602   const Vector<UnderlinedCharacterRun>& underlinedCharacterRuns = mModel->mLogicalModel->mUnderlinedCharacterRuns;
1603   const Vector<GlyphIndex>&             charactersToGlyph       = mModel->mVisualModel->mCharactersToGlyph;
1604   const Vector<Length>&                 glyphsPerCharacter      = mModel->mVisualModel->mGlyphsPerCharacter;
1605
1606   if(shouldClearPreUnderlineRuns)
1607   {
1608     mModel->mVisualModel->mUnderlineRuns.Clear();
1609   }
1610
1611   for(Vector<UnderlinedCharacterRun>::ConstIterator it = underlinedCharacterRuns.Begin(), endIt = underlinedCharacterRuns.End(); it != endIt; ++it)
1612   {
1613     CharacterIndex characterIndex     = it->characterRun.characterIndex;
1614     Length         numberOfCharacters = it->characterRun.numberOfCharacters;
1615     for(Length index = 0u; index < numberOfCharacters; index++)
1616     {
1617       GlyphRun underlineGlyphRun;
1618       underlineGlyphRun.glyphIndex     = charactersToGlyph[characterIndex + index];
1619       underlineGlyphRun.numberOfGlyphs = glyphsPerCharacter[characterIndex + index];
1620       mModel->mVisualModel->mUnderlineRuns.PushBack(underlineGlyphRun);
1621     }
1622   }
1623 }
1624
1625 void Controller::Impl::SetAutoScrollEnabled(bool enable)
1626 {
1627   if(mLayoutEngine.GetLayout() == Layout::Engine::SINGLE_LINE_BOX)
1628   {
1629     mOperationsPending = static_cast<OperationsMask>(mOperationsPending |
1630                                                      LAYOUT |
1631                                                      ALIGN |
1632                                                      UPDATE_LAYOUT_SIZE |
1633                                                      REORDER);
1634
1635     if(enable)
1636     {
1637       DALI_LOG_INFO(gLogFilter, Debug::General, "Controller::SetAutoScrollEnabled for SINGLE_LINE_BOX\n");
1638       mOperationsPending = static_cast<OperationsMask>(mOperationsPending | UPDATE_DIRECTION);
1639     }
1640     else
1641     {
1642       DALI_LOG_INFO(gLogFilter, Debug::General, "Controller::SetAutoScrollEnabled Disabling autoscroll\n");
1643     }
1644
1645     mIsAutoScrollEnabled = enable;
1646     RequestRelayout();
1647   }
1648   else
1649   {
1650     DALI_LOG_WARNING("Attempted AutoScrolling on a non SINGLE_LINE_BOX, request ignored\n");
1651     mIsAutoScrollEnabled = false;
1652   }
1653 }
1654
1655 void Controller::Impl::SetEnableCursorBlink(bool enable)
1656 {
1657   DALI_ASSERT_DEBUG(NULL != mEventData && "TextInput disabled");
1658
1659   if(mEventData)
1660   {
1661     mEventData->mCursorBlinkEnabled = enable;
1662
1663     if(!enable && mEventData->mDecorator)
1664     {
1665       mEventData->mDecorator->StopCursorBlink();
1666     }
1667   }
1668 }
1669
1670 void Controller::Impl::SetMultiLineEnabled(bool enable)
1671 {
1672   const Layout::Engine::Type layout = enable ? Layout::Engine::MULTI_LINE_BOX : Layout::Engine::SINGLE_LINE_BOX;
1673
1674   if(layout != mLayoutEngine.GetLayout())
1675   {
1676     // Set the layout type.
1677     mLayoutEngine.SetLayout(layout);
1678
1679     // Set the flags to redo the layout operations
1680     const OperationsMask layoutOperations = static_cast<OperationsMask>(LAYOUT |
1681                                                                         UPDATE_LAYOUT_SIZE |
1682                                                                         ALIGN |
1683                                                                         REORDER);
1684
1685     mTextUpdateInfo.mFullRelayoutNeeded = true;
1686     mOperationsPending                  = static_cast<OperationsMask>(mOperationsPending | layoutOperations);
1687
1688     // Need to recalculate natural size
1689     mRecalculateNaturalSize = true;
1690
1691     RequestRelayout();
1692   }
1693 }
1694
1695 void Controller::Impl::SetHorizontalAlignment(Text::HorizontalAlignment::Type alignment)
1696 {
1697   if(alignment != mModel->mHorizontalAlignment)
1698   {
1699     // Set the alignment.
1700     mModel->mHorizontalAlignment = alignment;
1701
1702     // Set the flag to redo the alignment operation.
1703     mOperationsPending = static_cast<OperationsMask>(mOperationsPending | ALIGN);
1704
1705     if(mEventData)
1706     {
1707       mEventData->mUpdateAlignment = true;
1708
1709       // Update the cursor if it's in editing mode
1710       if(EventData::IsEditingState(mEventData->mState))
1711       {
1712         ChangeState(EventData::EDITING);
1713         mEventData->mUpdateCursorPosition = true;
1714       }
1715     }
1716
1717     RequestRelayout();
1718   }
1719 }
1720
1721 void Controller::Impl::SetVerticalAlignment(VerticalAlignment::Type alignment)
1722 {
1723   if(alignment != mModel->mVerticalAlignment)
1724   {
1725     // Set the alignment.
1726     mModel->mVerticalAlignment = alignment;
1727     mOperationsPending = static_cast<OperationsMask>(mOperationsPending | ALIGN);
1728     RequestRelayout();
1729   }
1730 }
1731
1732 void Controller::Impl::SetLineWrapMode(Text::LineWrap::Mode lineWrapMode)
1733 {
1734   if(lineWrapMode != mModel->mLineWrapMode)
1735   {
1736     // Update Text layout for applying wrap mode
1737     mOperationsPending = static_cast<OperationsMask>(mOperationsPending |
1738                                                      ALIGN |
1739                                                      LAYOUT |
1740                                                      UPDATE_LAYOUT_SIZE |
1741                                                      REORDER);
1742
1743     if((mModel->mLineWrapMode == (Text::LineWrap::Mode)DevelText::LineWrap::HYPHENATION) || (lineWrapMode == (Text::LineWrap::Mode)DevelText::LineWrap::HYPHENATION) ||
1744        (mModel->mLineWrapMode == (Text::LineWrap::Mode)DevelText::LineWrap::MIXED) || (lineWrapMode == (Text::LineWrap::Mode)DevelText::LineWrap::MIXED)) // hyphen is treated as line break
1745     {
1746       mOperationsPending = static_cast<OperationsMask>(mOperationsPending | GET_LINE_BREAKS);
1747     }
1748
1749     // Set the text wrap mode.
1750     mModel->mLineWrapMode = lineWrapMode;
1751
1752     mTextUpdateInfo.mCharacterIndex             = 0u;
1753     mTextUpdateInfo.mNumberOfCharactersToRemove = mTextUpdateInfo.mPreviousNumberOfCharacters;
1754     mTextUpdateInfo.mNumberOfCharactersToAdd    = mModel->mLogicalModel->mText.Count();
1755
1756     // Request relayout
1757     RequestRelayout();
1758   }
1759 }
1760
1761 void Controller::Impl::SetDefaultColor(const Vector4& color)
1762 {
1763   mTextColor = color;
1764
1765   if(!IsShowingPlaceholderText())
1766   {
1767     mModel->mVisualModel->SetTextColor(color);
1768     mModel->mLogicalModel->mColorRuns.Clear();
1769     mOperationsPending = static_cast<OperationsMask>(mOperationsPending | COLOR);
1770     RequestRelayout();
1771   }
1772 }
1773
1774 void Controller::Impl::ClearFontData()
1775 {
1776   if(mFontDefaults)
1777   {
1778     mFontDefaults->mFontId = 0u; // Remove old font ID
1779   }
1780
1781   // Set flags to update the model.
1782   mTextUpdateInfo.mCharacterIndex             = 0u;
1783   mTextUpdateInfo.mNumberOfCharactersToRemove = mTextUpdateInfo.mPreviousNumberOfCharacters;
1784   mTextUpdateInfo.mNumberOfCharactersToAdd    = mModel->mLogicalModel->mText.Count();
1785
1786   mTextUpdateInfo.mClearAll           = true;
1787   mTextUpdateInfo.mFullRelayoutNeeded = true;
1788   mRecalculateNaturalSize             = true;
1789
1790   mOperationsPending = static_cast<OperationsMask>(mOperationsPending |
1791                                                    VALIDATE_FONTS |
1792                                                    SHAPE_TEXT |
1793                                                    BIDI_INFO |
1794                                                    GET_GLYPH_METRICS |
1795                                                    LAYOUT |
1796                                                    UPDATE_LAYOUT_SIZE |
1797                                                    REORDER |
1798                                                    ALIGN);
1799 }
1800
1801 void Controller::Impl::ClearStyleData()
1802 {
1803   mModel->mLogicalModel->mColorRuns.Clear();
1804   mModel->mLogicalModel->ClearFontDescriptionRuns();
1805 }
1806
1807
1808 void Controller::Impl::ResetScrollPosition()
1809 {
1810   if(mEventData)
1811   {
1812     // Reset the scroll position.
1813     mModel->mScrollPosition                = Vector2::ZERO;
1814     mEventData->mScrollAfterUpdatePosition = true;
1815   }
1816 }
1817
1818 } // namespace Dali::Toolkit::Text