2 * Copyright (c) 2020 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>
23 #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>
34 #if defined(DEBUG_ENABLED)
35 Debug::Filter* gLogFilter = Debug::Filter::New(Debug::NoLogging, true, "LOG_TEXT_CONTROLS");
49 void Controller::TextUpdater::SetText(Controller& controller, const std::string& text)
51 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Controller::SetText\n" );
53 Controller::Impl& impl = *controller.mImpl;
55 // Reset keyboard as text changed
56 impl.ResetInputMethodContext();
58 // Remove the previously set text and style.
59 ResetText(controller);
62 controller.ClearStyleData();
64 CharacterIndex lastCursorIndex = 0u;
66 EventData*& eventData = impl.mEventData;
68 if( nullptr != eventData )
70 // If popup shown then hide it by switching to Editing state
71 if( ( EventData::SELECTING == eventData->mState ) ||
72 ( EventData::EDITING_WITH_POPUP == eventData->mState ) ||
73 ( EventData::EDITING_WITH_GRAB_HANDLE == eventData->mState ) ||
74 ( EventData::EDITING_WITH_PASTE_POPUP == eventData->mState ) )
76 impl.ChangeState( EventData::EDITING );
82 ModelPtr& model = impl.mModel;
83 LogicalModelPtr& logicalModel = model->mLogicalModel;
84 model->mVisualModel->SetTextColor( impl.mTextColor );
86 MarkupProcessData markupProcessData( logicalModel->mColorRuns,
87 logicalModel->mFontDescriptionRuns,
88 logicalModel->mEmbeddedItems );
91 const uint8_t* utf8 = NULL;
92 if( impl.mMarkupProcessorEnabled )
94 ProcessMarkupString( text, markupProcessData );
95 textSize = markupProcessData.markupProcessedText.size();
97 // This is a bit horrible but std::string returns a (signed) char*
98 utf8 = reinterpret_cast<const uint8_t*>( markupProcessData.markupProcessedText.c_str() );
102 textSize = text.size();
104 // This is a bit horrible but std::string returns a (signed) char*
105 utf8 = reinterpret_cast<const uint8_t*>( text.c_str() );
108 // Convert text into UTF-32
109 Vector<Character>& utf32Characters = logicalModel->mText;
110 utf32Characters.Resize( textSize );
112 // Transform a text array encoded in utf8 into an array encoded in utf32.
113 // It returns the actual number of characters.
114 Length characterCount = Utf8ToUtf32( utf8, textSize, utf32Characters.Begin() );
115 utf32Characters.Resize( characterCount );
117 DALI_ASSERT_DEBUG( textSize >= characterCount && "Invalid UTF32 conversion length" );
118 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Controller::SetText %p UTF8 size %d, UTF32 size %d\n", &controller, textSize, logicalModel->mText.Count() );
120 // The characters to be added.
121 impl.mTextUpdateInfo.mNumberOfCharactersToAdd = logicalModel->mText.Count();
123 // To reset the cursor position
124 lastCursorIndex = characterCount;
126 // Update the rest of the model during size negotiation
127 impl.QueueModifyEvent( ModifyEvent::TEXT_REPLACED );
129 // The natural size needs to be re-calculated.
130 impl.mRecalculateNaturalSize = true;
132 // The text direction needs to be updated.
133 impl.mUpdateTextDirection = true;
135 // Apply modifications to the model
136 impl.mOperationsPending = ALL_OPERATIONS;
140 controller.ShowPlaceholderText();
143 // Resets the cursor position.
144 controller.ResetCursorPosition( lastCursorIndex );
146 // Scrolls the text to make the cursor visible.
147 controller.ResetScrollPosition();
149 impl.RequestRelayout();
151 if( nullptr != eventData )
153 // Cancel previously queued events
154 eventData->mEventQueue.clear();
157 // Do this last since it provides callbacks into application code.
158 if( NULL != impl.mEditableControlInterface )
160 impl.mEditableControlInterface->TextChanged();
164 void Controller::TextUpdater::InsertText(Controller& controller, const std::string& text, Controller::InsertType type)
166 Controller::Impl& impl = *controller.mImpl;
167 EventData*& eventData = impl.mEventData;
169 DALI_ASSERT_DEBUG( nullptr != eventData && "Unexpected InsertText" )
171 if( NULL == eventData )
176 bool removedPrevious = false;
177 bool removedSelected = false;
178 bool maxLengthReached = false;
180 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Controller::InsertText %p %s (%s) mPrimaryCursorPosition %d mPreEditFlag %d mPreEditStartPosition %d mPreEditLength %d\n",
181 &controller, text.c_str(), (COMMIT == type ? "COMMIT" : "PRE_EDIT"),
182 eventData->mPrimaryCursorPosition, eventData->mPreEditFlag, eventData->mPreEditStartPosition, eventData->mPreEditLength );
184 ModelPtr& model = impl.mModel;
185 LogicalModelPtr& logicalModel = model->mLogicalModel;
187 // TODO: At the moment the underline runs are only for pre-edit.
188 model->mVisualModel->mUnderlineRuns.Clear();
190 // Remove the previous InputMethodContext pre-edit.
191 if( eventData->mPreEditFlag && ( 0u != eventData->mPreEditLength ) )
193 removedPrevious = RemoveText( controller,
194 -static_cast<int>( eventData->mPrimaryCursorPosition - eventData->mPreEditStartPosition ),
195 eventData->mPreEditLength,
196 DONT_UPDATE_INPUT_STYLE );
198 eventData->mPrimaryCursorPosition = eventData->mPreEditStartPosition;
199 eventData->mPreEditLength = 0u;
203 // Remove the previous Selection.
204 removedSelected = RemoveSelectedText(controller);
208 Vector<Character> utf32Characters;
209 Length characterCount = 0u;
213 // Convert text into UTF-32
214 utf32Characters.Resize( text.size() );
216 // This is a bit horrible but std::string returns a (signed) char*
217 const uint8_t* utf8 = reinterpret_cast<const uint8_t*>( text.c_str() );
219 // Transform a text array encoded in utf8 into an array encoded in utf32.
220 // It returns the actual number of characters.
221 characterCount = Utf8ToUtf32( utf8, text.size(), utf32Characters.Begin() );
222 utf32Characters.Resize( characterCount );
224 DALI_ASSERT_DEBUG( text.size() >= utf32Characters.Count() && "Invalid UTF32 conversion length" );
225 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "UTF8 size %d, UTF32 size %d\n", text.size(), utf32Characters.Count() );
228 if( 0u != utf32Characters.Count() ) // Check if Utf8ToUtf32 conversion succeeded
230 // The placeholder text is no longer needed
231 if( impl.IsShowingPlaceholderText() )
233 ResetText(controller);
236 impl.ChangeState( EventData::EDITING );
238 // Handle the InputMethodContext (predicitive text) state changes
241 // InputMethodContext is no longer handling key-events
242 impl.ClearPreEditFlag();
246 if( !eventData->mPreEditFlag )
248 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Entered PreEdit state\n" );
250 // Record the start of the pre-edit text
251 eventData->mPreEditStartPosition = eventData->mPrimaryCursorPosition;
254 eventData->mPreEditLength = utf32Characters.Count();
255 eventData->mPreEditFlag = true;
257 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "mPreEditStartPosition %d mPreEditLength %d\n", eventData->mPreEditStartPosition, eventData->mPreEditLength );
260 const Length numberOfCharactersInModel = logicalModel->mText.Count();
262 // Restrict new text to fit within Maximum characters setting.
263 Length maxSizeOfNewText = std::min( ( impl.mMaximumNumberOfCharacters - numberOfCharactersInModel ), characterCount );
264 maxLengthReached = ( characterCount > maxSizeOfNewText );
266 // The cursor position.
267 CharacterIndex& cursorIndex = eventData->mPrimaryCursorPosition;
269 // Update the text's style.
271 // Updates the text style runs by adding characters.
272 logicalModel->UpdateTextStyleRuns( cursorIndex, maxSizeOfNewText );
274 // Get the character index from the cursor index.
275 const CharacterIndex styleIndex = ( cursorIndex > 0u ) ? cursorIndex - 1u : 0u;
277 // Retrieve the text's style for the given index.
279 impl.RetrieveDefaultInputStyle( style );
280 logicalModel->RetrieveStyle( styleIndex, style );
282 InputStyle& inputStyle = eventData->mInputStyle;
284 // Whether to add a new text color run.
285 const bool addColorRun = ( style.textColor != inputStyle.textColor ) && !inputStyle.isDefaultColor;
287 // Whether to add a new font run.
288 const bool addFontNameRun = ( style.familyName != inputStyle.familyName ) && inputStyle.isFamilyDefined;
289 const bool addFontWeightRun = ( style.weight != inputStyle.weight ) && inputStyle.isWeightDefined;
290 const bool addFontWidthRun = ( style.width != inputStyle.width ) && inputStyle.isWidthDefined;
291 const bool addFontSlantRun = ( style.slant != inputStyle.slant ) && inputStyle.isSlantDefined;
292 const bool addFontSizeRun = ( style.size != inputStyle.size ) && inputStyle.isSizeDefined ;
297 const VectorBase::SizeType numberOfRuns = logicalModel->mColorRuns.Count();
298 logicalModel->mColorRuns.Resize( numberOfRuns + 1u );
300 ColorRun& colorRun = *( logicalModel->mColorRuns.Begin() + numberOfRuns );
301 colorRun.color = inputStyle.textColor;
302 colorRun.characterRun.characterIndex = cursorIndex;
303 colorRun.characterRun.numberOfCharacters = maxSizeOfNewText;
306 if( addFontNameRun ||
312 const VectorBase::SizeType numberOfRuns = logicalModel->mFontDescriptionRuns.Count();
313 logicalModel->mFontDescriptionRuns.Resize( numberOfRuns + 1u );
315 FontDescriptionRun& fontDescriptionRun = *( logicalModel->mFontDescriptionRuns.Begin() + numberOfRuns );
319 fontDescriptionRun.familyLength = inputStyle.familyName.size();
320 fontDescriptionRun.familyName = new char[fontDescriptionRun.familyLength];
321 memcpy( fontDescriptionRun.familyName, inputStyle.familyName.c_str(), fontDescriptionRun.familyLength );
322 fontDescriptionRun.familyDefined = true;
324 // The memory allocated for the font family name is freed when the font description is removed from the logical model.
327 if( addFontWeightRun )
329 fontDescriptionRun.weight = inputStyle.weight;
330 fontDescriptionRun.weightDefined = true;
333 if( addFontWidthRun )
335 fontDescriptionRun.width = inputStyle.width;
336 fontDescriptionRun.widthDefined = true;
339 if( addFontSlantRun )
341 fontDescriptionRun.slant = inputStyle.slant;
342 fontDescriptionRun.slantDefined = true;
347 fontDescriptionRun.size = static_cast<PointSize26Dot6>( inputStyle.size * impl.mFontSizeScale * 64.f );
348 fontDescriptionRun.sizeDefined = true;
351 fontDescriptionRun.characterRun.characterIndex = cursorIndex;
352 fontDescriptionRun.characterRun.numberOfCharacters = maxSizeOfNewText;
355 // Insert at current cursor position.
356 Vector<Character>& modifyText = logicalModel->mText;
358 auto pos = modifyText.End();
359 if( cursorIndex < numberOfCharactersInModel )
361 pos = modifyText.Begin() + cursorIndex;
363 unsigned int realPos = pos - modifyText.Begin();
364 modifyText.Insert( pos, utf32Characters.Begin(), utf32Characters.Begin() + maxSizeOfNewText );
366 if( NULL != impl.mEditableControlInterface )
368 impl.mEditableControlInterface->TextInserted( realPos, maxSizeOfNewText, text );
371 TextUpdateInfo& textUpdateInfo = impl.mTextUpdateInfo;
373 // Mark the first paragraph to be updated.
374 if( Layout::Engine::SINGLE_LINE_BOX == impl.mLayoutEngine.GetLayout() )
376 textUpdateInfo.mCharacterIndex = 0;
377 textUpdateInfo.mNumberOfCharactersToRemove = textUpdateInfo.mPreviousNumberOfCharacters;
378 textUpdateInfo.mNumberOfCharactersToAdd = numberOfCharactersInModel + maxSizeOfNewText;
379 textUpdateInfo.mClearAll = true;
383 textUpdateInfo.mCharacterIndex = std::min( cursorIndex, textUpdateInfo.mCharacterIndex );
384 textUpdateInfo.mNumberOfCharactersToAdd += maxSizeOfNewText;
387 // Update the cursor index.
388 cursorIndex += maxSizeOfNewText;
390 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Inserted %d characters, new size %d new cursor %d\n", maxSizeOfNewText, logicalModel->mText.Count(), eventData->mPrimaryCursorPosition );
393 if( ( 0u == logicalModel->mText.Count() ) &&
394 impl.IsPlaceholderAvailable() )
396 // Show place-holder if empty after removing the pre-edit text
397 controller.ShowPlaceholderText();
398 eventData->mUpdateCursorPosition = true;
399 impl.ClearPreEditFlag();
401 else if( removedPrevious ||
403 ( 0 != utf32Characters.Count() ) )
405 // Queue an inserted event
406 impl.QueueModifyEvent( ModifyEvent::TEXT_INSERTED );
408 eventData->mUpdateCursorPosition = true;
409 if( removedSelected )
411 eventData->mScrollAfterDelete = true;
415 eventData->mScrollAfterUpdatePosition = true;
419 if( maxLengthReached )
421 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "MaxLengthReached (%d)\n", logicalModel->mText.Count() );
423 impl.ResetInputMethodContext();
425 if( NULL != impl.mEditableControlInterface )
427 // Do this last since it provides callbacks into application code
428 impl.mEditableControlInterface->MaxLengthReached();
433 void Controller::TextUpdater::PasteText(Controller& controller, const std::string& stringToPaste)
435 InsertText( controller, stringToPaste, Text::Controller::COMMIT );
436 Controller::Impl& impl = *controller.mImpl;
437 impl.ChangeState( EventData::EDITING );
438 impl.RequestRelayout();
440 if( NULL != impl.mEditableControlInterface )
442 // Do this last since it provides callbacks into application code
443 impl.mEditableControlInterface->TextChanged();
447 bool Controller::TextUpdater::RemoveText(
448 Controller& controller,
450 int numberOfCharacters,
451 UpdateInputStyleType type )
453 bool removed = false;
455 Controller::Impl& impl = *controller.mImpl;
456 EventData*& eventData = impl.mEventData;
458 if( nullptr == eventData )
463 ModelPtr& model = impl.mModel;
464 LogicalModelPtr& logicalModel = model->mLogicalModel;
466 DALI_LOG_INFO( gLogFilter, Debug::General, "Controller::RemoveText %p mText.Count() %d cursor %d cursorOffset %d numberOfCharacters %d\n",
467 &controller, logicalModel->mText.Count(), eventData->mPrimaryCursorPosition, cursorOffset, numberOfCharacters );
469 if( !impl.IsShowingPlaceholderText() )
471 // Delete at current cursor position
472 Vector<Character>& currentText = logicalModel->mText;
473 CharacterIndex& oldCursorIndex = eventData->mPrimaryCursorPosition;
475 CharacterIndex cursorIndex = 0;
477 // Validate the cursor position & number of characters
478 if( ( static_cast< int >( eventData->mPrimaryCursorPosition ) + cursorOffset ) >= 0 )
480 cursorIndex = eventData->mPrimaryCursorPosition + cursorOffset;
483 if( ( cursorIndex + numberOfCharacters ) > currentText.Count() )
485 numberOfCharacters = currentText.Count() - cursorIndex;
488 TextUpdateInfo& textUpdateInfo = impl.mTextUpdateInfo;
490 if( eventData->mPreEditFlag || // If the preedit flag is enabled, it means two (or more) of them came together i.e. when two keys have been pressed at the same time.
491 ( ( cursorIndex + numberOfCharacters ) <= textUpdateInfo.mPreviousNumberOfCharacters ) )
493 // Mark the paragraphs to be updated.
494 if( Layout::Engine::SINGLE_LINE_BOX == impl.mLayoutEngine.GetLayout() )
496 textUpdateInfo.mCharacterIndex = 0;
497 textUpdateInfo.mNumberOfCharactersToRemove = textUpdateInfo.mPreviousNumberOfCharacters;
498 textUpdateInfo.mNumberOfCharactersToAdd = textUpdateInfo.mPreviousNumberOfCharacters - numberOfCharacters;
499 textUpdateInfo.mClearAll = true;
503 textUpdateInfo.mCharacterIndex = std::min( cursorIndex, textUpdateInfo.mCharacterIndex );
504 textUpdateInfo.mNumberOfCharactersToRemove += numberOfCharacters;
507 // Update the input style and remove the text's style before removing the text.
509 if( UPDATE_INPUT_STYLE == type )
511 InputStyle& eventDataInputStyle = eventData->mInputStyle;
513 // Keep a copy of the current input style.
514 InputStyle currentInputStyle;
515 currentInputStyle.Copy( eventDataInputStyle );
517 // Set first the default input style.
518 impl.RetrieveDefaultInputStyle( eventDataInputStyle );
520 // Update the input style.
521 logicalModel->RetrieveStyle( cursorIndex, eventDataInputStyle );
523 // Compare if the input style has changed.
524 const bool hasInputStyleChanged = !currentInputStyle.Equal( eventDataInputStyle );
526 if( hasInputStyleChanged )
528 const InputStyle::Mask styleChangedMask = currentInputStyle.GetInputStyleChangeMask( eventDataInputStyle );
529 // Queue the input style changed signal.
530 eventData->mInputStyleChangedQueue.PushBack( styleChangedMask );
534 // If the number of current text and the number of characters to be deleted are same,
535 // it means all texts should be removed and all Preedit variables should be initialized.
536 if( ( currentText.Count() - numberOfCharacters == 0 ) && ( cursorIndex == 0 ) )
538 impl.ClearPreEditFlag();
539 textUpdateInfo.mNumberOfCharactersToAdd = 0;
542 // Updates the text style runs by removing characters. Runs with no characters are removed.
543 logicalModel->UpdateTextStyleRuns( cursorIndex, -numberOfCharacters );
545 // Remove the characters.
546 Vector<Character>::Iterator first = currentText.Begin() + cursorIndex;
547 Vector<Character>::Iterator last = first + numberOfCharacters;
549 if( NULL != impl.mEditableControlInterface )
552 Utf32ToUtf8( first, numberOfCharacters, utf8 );
553 impl.mEditableControlInterface->TextDeleted( cursorIndex, numberOfCharacters, utf8 );
556 currentText.Erase( first, last );
558 // Cursor position retreat
559 oldCursorIndex = cursorIndex;
561 eventData->mScrollAfterDelete = true;
563 if( EventData::INACTIVE == eventData->mState )
565 impl.ChangeState( EventData::EDITING );
568 DALI_LOG_INFO( gLogFilter, Debug::General, "Controller::RemoveText %p removed %d\n", &controller, numberOfCharacters );
576 bool Controller::TextUpdater::RemoveSelectedText(Controller& controller)
578 bool textRemoved( false );
580 Controller::Impl& impl = *controller.mImpl;
582 if( EventData::SELECTING == impl.mEventData->mState )
584 std::string removedString;
585 impl.RetrieveSelection( removedString, true );
587 if( !removedString.empty() )
590 impl.ChangeState( EventData::EDITING );
597 void Controller::TextUpdater::ResetText(Controller& controller)
599 Controller::Impl& impl = *controller.mImpl;
600 LogicalModelPtr& logicalModel = impl.mModel->mLogicalModel;
603 logicalModel->mText.Clear();
605 // Reset the embedded images buffer.
606 logicalModel->ClearEmbeddedImages();
608 // We have cleared everything including the placeholder-text
609 impl.PlaceholderCleared();
611 impl.mTextUpdateInfo.mCharacterIndex = 0u;
612 impl.mTextUpdateInfo.mNumberOfCharactersToRemove = impl.mTextUpdateInfo.mPreviousNumberOfCharacters;
613 impl.mTextUpdateInfo.mNumberOfCharactersToAdd = 0u;
615 // Clear any previous text.
616 impl.mTextUpdateInfo.mClearAll = true;
618 // The natural size needs to be re-calculated.
619 impl.mRecalculateNaturalSize = true;
621 // The text direction needs to be updated.
622 impl.mUpdateTextDirection = true;
624 // Apply modifications to the model
625 impl.mOperationsPending = ALL_OPERATIONS;
630 } // namespace Toolkit