2 * Copyright (c) 2021 Samsung Electronics Co., Ltd.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
19 #include <dali-toolkit/internal/text/text-controller-text-updater.h>
22 #include <dali/integration-api/debug.h>
26 #include <dali-toolkit/internal/text/character-set-conversion.h>
27 #include <dali-toolkit/internal/text/markup-processor.h>
28 #include <dali-toolkit/internal/text/text-controller-impl.h>
29 #include <dali-toolkit/internal/text/text-editable-control-interface.h>
33 #if defined(DEBUG_ENABLED)
34 Debug::Filter* gLogFilter = Debug::Filter::New(Debug::NoLogging, true, "LOG_TEXT_CONTROLS");
45 void Controller::TextUpdater::SetText(Controller& controller, const std::string& text)
47 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Controller::SetText\n");
49 Controller::Impl& impl = *controller.mImpl;
51 // Reset keyboard as text changed
52 impl.ResetInputMethodContext();
54 // Remove the previously set text and style.
55 ResetText(controller);
58 controller.ClearStyleData();
60 CharacterIndex lastCursorIndex = 0u;
62 EventData*& eventData = impl.mEventData;
64 if(nullptr != eventData)
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))
72 impl.ChangeState(EventData::EDITING);
78 ModelPtr& model = impl.mModel;
79 LogicalModelPtr& logicalModel = model->mLogicalModel;
80 model->mVisualModel->SetTextColor(impl.mTextColor);
82 MarkupProcessData markupProcessData(logicalModel->mColorRuns,
83 logicalModel->mFontDescriptionRuns,
84 logicalModel->mEmbeddedItems);
87 const uint8_t* utf8 = NULL;
88 if(impl.mMarkupProcessorEnabled)
90 ProcessMarkupString(text, markupProcessData);
91 textSize = markupProcessData.markupProcessedText.size();
93 // This is a bit horrible but std::string returns a (signed) char*
94 utf8 = reinterpret_cast<const uint8_t*>(markupProcessData.markupProcessedText.c_str());
98 textSize = text.size();
100 // This is a bit horrible but std::string returns a (signed) char*
101 utf8 = reinterpret_cast<const uint8_t*>(text.c_str());
104 // Convert text into UTF-32
105 Vector<Character>& utf32Characters = logicalModel->mText;
106 utf32Characters.Resize(textSize);
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);
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());
116 // The characters to be added.
117 impl.mTextUpdateInfo.mNumberOfCharactersToAdd = logicalModel->mText.Count();
119 // To reset the cursor position
120 lastCursorIndex = characterCount;
122 // Update the rest of the model during size negotiation
123 impl.QueueModifyEvent(ModifyEvent::TEXT_REPLACED);
125 // The natural size needs to be re-calculated.
126 impl.mRecalculateNaturalSize = true;
128 // The text direction needs to be updated.
129 impl.mUpdateTextDirection = true;
131 // Apply modifications to the model
132 impl.mOperationsPending = ALL_OPERATIONS;
136 controller.ShowPlaceholderText();
139 // Resets the cursor position.
140 controller.ResetCursorPosition(lastCursorIndex);
142 // Scrolls the text to make the cursor visible.
143 controller.ResetScrollPosition();
145 impl.RequestRelayout();
147 if(nullptr != eventData)
149 // Cancel previously queued events
150 eventData->mEventQueue.clear();
153 // Do this last since it provides callbacks into application code.
154 if(NULL != impl.mEditableControlInterface)
156 impl.mEditableControlInterface->TextChanged(true);
160 void Controller::TextUpdater::InsertText(Controller& controller, const std::string& text, Controller::InsertType type)
162 Controller::Impl& impl = *controller.mImpl;
163 EventData*& eventData = impl.mEventData;
165 DALI_ASSERT_DEBUG(nullptr != eventData && "Unexpected InsertText")
167 if(NULL == eventData)
172 bool removedPrevious = false;
173 bool removedSelected = false;
174 bool maxLengthReached = false;
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);
178 ModelPtr& model = impl.mModel;
179 LogicalModelPtr& logicalModel = model->mLogicalModel;
181 // TODO: At the moment the underline runs are only for pre-edit.
182 model->mVisualModel->mUnderlineRuns.Clear();
184 // Remove the previous InputMethodContext pre-edit.
185 if(eventData->mPreEditFlag && (0u != eventData->mPreEditLength))
187 removedPrevious = RemoveText(controller,
188 -static_cast<int>(eventData->mPrimaryCursorPosition - eventData->mPreEditStartPosition),
189 eventData->mPreEditLength,
190 DONT_UPDATE_INPUT_STYLE);
192 eventData->mPrimaryCursorPosition = eventData->mPreEditStartPosition;
193 eventData->mPreEditLength = 0u;
197 // Remove the previous Selection.
198 removedSelected = RemoveSelectedText(controller);
201 Vector<Character> utf32Characters;
202 Length characterCount = 0u;
206 // Convert text into UTF-32
207 utf32Characters.Resize(text.size());
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());
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);
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());
221 if(0u != utf32Characters.Count()) // Check if Utf8ToUtf32 conversion succeeded
223 // The placeholder text is no longer needed
224 if(impl.IsShowingPlaceholderText())
226 ResetText(controller);
229 impl.ChangeState(EventData::EDITING);
231 // Handle the InputMethodContext (predicitive text) state changes
234 // InputMethodContext is no longer handling key-events
235 impl.ClearPreEditFlag();
239 if(!eventData->mPreEditFlag)
241 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Entered PreEdit state\n");
243 // Record the start of the pre-edit text
244 eventData->mPreEditStartPosition = eventData->mPrimaryCursorPosition;
247 eventData->mPreEditLength = utf32Characters.Count();
248 eventData->mPreEditFlag = true;
250 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "mPreEditStartPosition %d mPreEditLength %d\n", eventData->mPreEditStartPosition, eventData->mPreEditLength);
253 const Length numberOfCharactersInModel = logicalModel->mText.Count();
255 // Restrict new text to fit within Maximum characters setting.
256 Length maxSizeOfNewText = std::min((impl.mMaximumNumberOfCharacters - numberOfCharactersInModel), characterCount);
257 maxLengthReached = (characterCount > maxSizeOfNewText);
259 // The cursor position.
260 CharacterIndex& cursorIndex = eventData->mPrimaryCursorPosition;
262 // Update the text's style.
264 // Updates the text style runs by adding characters.
265 logicalModel->UpdateTextStyleRuns(cursorIndex, maxSizeOfNewText);
267 // Get the character index from the cursor index.
268 const CharacterIndex styleIndex = (cursorIndex > 0u) ? cursorIndex - 1u : 0u;
270 // Retrieve the text's style for the given index.
272 impl.RetrieveDefaultInputStyle(style);
273 logicalModel->RetrieveStyle(styleIndex, style);
275 InputStyle& inputStyle = eventData->mInputStyle;
277 // Whether to add a new text color run.
278 const bool addColorRun = (style.textColor != inputStyle.textColor) && !inputStyle.isDefaultColor;
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;
290 const VectorBase::SizeType numberOfRuns = logicalModel->mColorRuns.Count();
291 logicalModel->mColorRuns.Resize(numberOfRuns + 1u);
293 ColorRun& colorRun = *(logicalModel->mColorRuns.Begin() + numberOfRuns);
294 colorRun.color = inputStyle.textColor;
295 colorRun.characterRun.characterIndex = cursorIndex;
296 colorRun.characterRun.numberOfCharacters = maxSizeOfNewText;
305 const VectorBase::SizeType numberOfRuns = logicalModel->mFontDescriptionRuns.Count();
306 logicalModel->mFontDescriptionRuns.Resize(numberOfRuns + 1u);
308 FontDescriptionRun& fontDescriptionRun = *(logicalModel->mFontDescriptionRuns.Begin() + numberOfRuns);
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;
317 // The memory allocated for the font family name is freed when the font description is removed from the logical model.
322 fontDescriptionRun.weight = inputStyle.weight;
323 fontDescriptionRun.weightDefined = true;
328 fontDescriptionRun.width = inputStyle.width;
329 fontDescriptionRun.widthDefined = true;
334 fontDescriptionRun.slant = inputStyle.slant;
335 fontDescriptionRun.slantDefined = true;
340 fontDescriptionRun.size = static_cast<PointSize26Dot6>(inputStyle.size * impl.mFontSizeScale * 64.f);
341 fontDescriptionRun.sizeDefined = true;
344 fontDescriptionRun.characterRun.characterIndex = cursorIndex;
345 fontDescriptionRun.characterRun.numberOfCharacters = maxSizeOfNewText;
348 // Insert at current cursor position.
349 Vector<Character>& modifyText = logicalModel->mText;
351 auto pos = modifyText.End();
352 if(cursorIndex < numberOfCharactersInModel)
354 pos = modifyText.Begin() + cursorIndex;
356 unsigned int realPos = pos - modifyText.Begin();
357 modifyText.Insert(pos, utf32Characters.Begin(), utf32Characters.Begin() + maxSizeOfNewText);
359 if(NULL != impl.mEditableControlInterface)
361 impl.mEditableControlInterface->TextInserted(realPos, maxSizeOfNewText, text);
364 TextUpdateInfo& textUpdateInfo = impl.mTextUpdateInfo;
366 // Mark the first paragraph to be updated.
367 if(Layout::Engine::SINGLE_LINE_BOX == impl.mLayoutEngine.GetLayout())
369 textUpdateInfo.mCharacterIndex = 0;
370 textUpdateInfo.mNumberOfCharactersToRemove = textUpdateInfo.mPreviousNumberOfCharacters;
371 textUpdateInfo.mNumberOfCharactersToAdd = numberOfCharactersInModel + maxSizeOfNewText;
372 textUpdateInfo.mClearAll = true;
376 textUpdateInfo.mCharacterIndex = std::min(cursorIndex, textUpdateInfo.mCharacterIndex);
377 textUpdateInfo.mNumberOfCharactersToAdd += maxSizeOfNewText;
380 // Update the cursor index.
381 cursorIndex += maxSizeOfNewText;
383 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Inserted %d characters, new size %d new cursor %d\n", maxSizeOfNewText, logicalModel->mText.Count(), eventData->mPrimaryCursorPosition);
386 if((0u == logicalModel->mText.Count()) &&
387 impl.IsPlaceholderAvailable())
389 // Show place-holder if empty after removing the pre-edit text
390 controller.ShowPlaceholderText();
391 eventData->mUpdateCursorPosition = true;
392 impl.ClearPreEditFlag();
394 else if(removedPrevious ||
396 (0 != utf32Characters.Count()))
398 // Queue an inserted event
399 impl.QueueModifyEvent(ModifyEvent::TEXT_INSERTED);
401 eventData->mUpdateCursorPosition = true;
404 eventData->mScrollAfterDelete = true;
408 eventData->mScrollAfterUpdatePosition = true;
414 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "MaxLengthReached (%d)\n", logicalModel->mText.Count());
416 impl.ResetInputMethodContext();
418 if(NULL != impl.mEditableControlInterface)
420 // Do this last since it provides callbacks into application code
421 impl.mEditableControlInterface->MaxLengthReached();
426 void Controller::TextUpdater::PasteText(Controller& controller, const std::string& stringToPaste)
428 InsertText(controller, stringToPaste, Text::Controller::COMMIT);
429 Controller::Impl& impl = *controller.mImpl;
430 impl.ChangeState(EventData::EDITING);
431 impl.RequestRelayout();
433 if(NULL != impl.mEditableControlInterface)
435 // Do this last since it provides callbacks into application code
436 impl.mEditableControlInterface->TextChanged(true);
440 bool Controller::TextUpdater::RemoveText(
441 Controller& controller,
443 int numberOfCharacters,
444 UpdateInputStyleType type)
446 bool removed = false;
448 Controller::Impl& impl = *controller.mImpl;
449 EventData*& eventData = impl.mEventData;
451 if(nullptr == eventData)
456 ModelPtr& model = impl.mModel;
457 LogicalModelPtr& logicalModel = model->mLogicalModel;
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);
461 if(!impl.IsShowingPlaceholderText())
463 // Delete at current cursor position
464 Vector<Character>& currentText = logicalModel->mText;
465 CharacterIndex& oldCursorIndex = eventData->mPrimaryCursorPosition;
467 CharacterIndex cursorIndex = 0;
469 // Validate the cursor position & number of characters
470 if((static_cast<int>(eventData->mPrimaryCursorPosition) + cursorOffset) >= 0)
472 cursorIndex = eventData->mPrimaryCursorPosition + cursorOffset;
475 if((cursorIndex + numberOfCharacters) > currentText.Count())
477 numberOfCharacters = currentText.Count() - cursorIndex;
480 TextUpdateInfo& textUpdateInfo = impl.mTextUpdateInfo;
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))
485 // Mark the paragraphs to be updated.
486 if(Layout::Engine::SINGLE_LINE_BOX == impl.mLayoutEngine.GetLayout())
488 textUpdateInfo.mCharacterIndex = 0;
489 textUpdateInfo.mNumberOfCharactersToRemove = textUpdateInfo.mPreviousNumberOfCharacters;
490 textUpdateInfo.mNumberOfCharactersToAdd = textUpdateInfo.mPreviousNumberOfCharacters - numberOfCharacters;
491 textUpdateInfo.mClearAll = true;
495 textUpdateInfo.mCharacterIndex = std::min(cursorIndex, textUpdateInfo.mCharacterIndex);
496 textUpdateInfo.mNumberOfCharactersToRemove += numberOfCharacters;
499 // Update the input style and remove the text's style before removing the text.
501 if(UPDATE_INPUT_STYLE == type)
503 InputStyle& eventDataInputStyle = eventData->mInputStyle;
505 // Keep a copy of the current input style.
506 InputStyle currentInputStyle;
507 currentInputStyle.Copy(eventDataInputStyle);
509 // Set first the default input style.
510 impl.RetrieveDefaultInputStyle(eventDataInputStyle);
512 // Update the input style.
513 logicalModel->RetrieveStyle(cursorIndex, eventDataInputStyle);
515 // Compare if the input style has changed.
516 const bool hasInputStyleChanged = !currentInputStyle.Equal(eventDataInputStyle);
518 if(hasInputStyleChanged)
520 const InputStyle::Mask styleChangedMask = currentInputStyle.GetInputStyleChangeMask(eventDataInputStyle);
521 // Queue the input style changed signal.
522 eventData->mInputStyleChangedQueue.PushBack(styleChangedMask);
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))
530 impl.ClearPreEditFlag();
531 textUpdateInfo.mNumberOfCharactersToAdd = 0;
534 // Updates the text style runs by removing characters. Runs with no characters are removed.
535 logicalModel->UpdateTextStyleRuns(cursorIndex, -numberOfCharacters);
537 // Remove the characters.
538 Vector<Character>::Iterator first = currentText.Begin() + cursorIndex;
539 Vector<Character>::Iterator last = first + numberOfCharacters;
541 if(NULL != impl.mEditableControlInterface)
544 Utf32ToUtf8(first, numberOfCharacters, utf8);
545 impl.mEditableControlInterface->TextDeleted(cursorIndex, numberOfCharacters, utf8);
548 currentText.Erase(first, last);
550 // Cursor position retreat
551 oldCursorIndex = cursorIndex;
553 eventData->mScrollAfterDelete = true;
555 if(EventData::INACTIVE == eventData->mState)
557 impl.ChangeState(EventData::EDITING);
560 DALI_LOG_INFO(gLogFilter, Debug::General, "Controller::RemoveText %p removed %d\n", &controller, numberOfCharacters);
568 bool Controller::TextUpdater::RemoveSelectedText(Controller& controller)
570 bool textRemoved(false);
572 Controller::Impl& impl = *controller.mImpl;
574 if(EventData::SELECTING == impl.mEventData->mState)
576 std::string removedString;
577 impl.RetrieveSelection(removedString, true);
579 if(!removedString.empty())
582 impl.ChangeState(EventData::EDITING);
589 void Controller::TextUpdater::ResetText(Controller& controller)
591 Controller::Impl& impl = *controller.mImpl;
592 LogicalModelPtr& logicalModel = impl.mModel->mLogicalModel;
595 logicalModel->mText.Clear();
597 // Reset the embedded images buffer.
598 logicalModel->ClearEmbeddedImages();
600 // We have cleared everything including the placeholder-text
601 impl.PlaceholderCleared();
603 impl.mTextUpdateInfo.mCharacterIndex = 0u;
604 impl.mTextUpdateInfo.mNumberOfCharactersToRemove = impl.mTextUpdateInfo.mPreviousNumberOfCharacters;
605 impl.mTextUpdateInfo.mNumberOfCharactersToAdd = 0u;
607 // Clear any previous text.
608 impl.mTextUpdateInfo.mClearAll = true;
610 // The natural size needs to be re-calculated.
611 impl.mRecalculateNaturalSize = true;
613 // The text direction needs to be updated.
614 impl.mUpdateTextDirection = true;
616 // Apply modifications to the model
617 impl.mOperationsPending = ALL_OPERATIONS;
622 } // namespace Toolkit