/*
- * Copyright (c) 2020 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2022 Samsung Electronics Co., Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
#include <dali-toolkit/internal/text/text-controller-text-updater.h>
// EXTERNAL INCLUDES
-#include <memory.h>
#include <dali/integration-api/debug.h>
+#include <memory.h>
// INTERNAL INCLUDES
#include <dali-toolkit/internal/text/character-set-conversion.h>
+#include <dali-toolkit/internal/text/characters-helper-functions.h>
+#include <dali-toolkit/internal/text/emoji-helper.h>
#include <dali-toolkit/internal/text/markup-processor.h>
#include <dali-toolkit/internal/text/text-controller-impl.h>
+#include <dali-toolkit/internal/text/text-controller-placeholder-handler.h>
#include <dali-toolkit/internal/text/text-editable-control-interface.h>
namespace
{
-
#if defined(DEBUG_ENABLED)
Debug::Filter* gLogFilter = Debug::Filter::New(Debug::NoLogging, true, "LOG_TEXT_CONTROLS");
#endif
namespace Dali
{
-
namespace Toolkit
{
-
namespace Text
{
-
void Controller::TextUpdater::SetText(Controller& controller, const std::string& text)
{
- DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Controller::SetText\n" );
+ DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Controller::SetText\n");
Controller::Impl& impl = *controller.mImpl;
ResetText(controller);
// Remove the style.
- controller.ClearStyleData();
+ impl.ClearStyleData();
CharacterIndex lastCursorIndex = 0u;
EventData*& eventData = impl.mEventData;
- if( nullptr != eventData )
+ if(nullptr != eventData)
{
// If popup shown then hide it by switching to Editing state
- if( ( EventData::SELECTING == eventData->mState ) ||
- ( EventData::EDITING_WITH_POPUP == eventData->mState ) ||
- ( EventData::EDITING_WITH_GRAB_HANDLE == eventData->mState ) ||
- ( EventData::EDITING_WITH_PASTE_POPUP == eventData->mState ) )
+ if((EventData::SELECTING == eventData->mState) ||
+ (EventData::EDITING_WITH_POPUP == eventData->mState) ||
+ (EventData::EDITING_WITH_GRAB_HANDLE == eventData->mState) ||
+ (EventData::EDITING_WITH_PASTE_POPUP == eventData->mState))
{
- impl.ChangeState( EventData::EDITING );
+ if((impl.mSelectableControlInterface != nullptr) && (EventData::SELECTING == eventData->mState))
+ {
+ impl.mSelectableControlInterface->SelectionChanged(eventData->mLeftSelectionPosition, eventData->mRightSelectionPosition, eventData->mPrimaryCursorPosition, eventData->mPrimaryCursorPosition);
+ }
+
+ impl.ChangeState(EventData::EDITING);
}
}
- if( !text.empty() )
+ if(!text.empty())
{
- ModelPtr& model = impl.mModel;
+ ModelPtr& model = impl.mModel;
LogicalModelPtr& logicalModel = model->mLogicalModel;
- model->mVisualModel->SetTextColor( impl.mTextColor );
-
- MarkupProcessData markupProcessData( logicalModel->mColorRuns,
- logicalModel->mFontDescriptionRuns,
- logicalModel->mEmbeddedItems );
-
- Length textSize = 0u;
- const uint8_t* utf8 = NULL;
- if( impl.mMarkupProcessorEnabled )
+ model->mVisualModel->SetTextColor(impl.mTextColor);
+
+ MarkupProcessData markupProcessData(logicalModel->mColorRuns,
+ logicalModel->mFontDescriptionRuns,
+ logicalModel->mEmbeddedItems,
+ logicalModel->mAnchors,
+ logicalModel->mUnderlinedCharacterRuns,
+ logicalModel->mBackgroundColorRuns,
+ logicalModel->mStrikethroughCharacterRuns,
+ logicalModel->mBoundedParagraphRuns,
+ logicalModel->mCharacterSpacingCharacterRuns);
+
+ Length textSize = 0u;
+ const uint8_t* utf8 = NULL;
+ if(impl.mMarkupProcessorEnabled)
{
- ProcessMarkupString( text, markupProcessData );
+ ProcessMarkupString(text, markupProcessData);
textSize = markupProcessData.markupProcessedText.size();
// This is a bit horrible but std::string returns a (signed) char*
- utf8 = reinterpret_cast<const uint8_t*>( markupProcessData.markupProcessedText.c_str() );
+ utf8 = reinterpret_cast<const uint8_t*>(markupProcessData.markupProcessedText.c_str());
}
else
{
textSize = text.size();
// This is a bit horrible but std::string returns a (signed) char*
- utf8 = reinterpret_cast<const uint8_t*>( text.c_str() );
+ utf8 = reinterpret_cast<const uint8_t*>(text.c_str());
}
// Convert text into UTF-32
Vector<Character>& utf32Characters = logicalModel->mText;
- utf32Characters.Resize( textSize );
+ utf32Characters.Resize(textSize);
// Transform a text array encoded in utf8 into an array encoded in utf32.
// It returns the actual number of characters.
- Length characterCount = Utf8ToUtf32( utf8, textSize, utf32Characters.Begin() );
- utf32Characters.Resize( characterCount );
+ Length characterCount = Utf8ToUtf32(utf8, textSize, utf32Characters.Begin());
+ utf32Characters.Resize(characterCount);
- DALI_ASSERT_DEBUG( textSize >= characterCount && "Invalid UTF32 conversion length" );
- DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Controller::SetText %p UTF8 size %d, UTF32 size %d\n", &controller, textSize, logicalModel->mText.Count() );
+ DALI_ASSERT_DEBUG(textSize >= characterCount && "Invalid UTF32 conversion length");
+ DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Controller::SetText %p UTF8 size %d, UTF32 size %d\n", &controller, textSize, logicalModel->mText.Count());
// The characters to be added.
impl.mTextUpdateInfo.mNumberOfCharactersToAdd = logicalModel->mText.Count();
lastCursorIndex = characterCount;
// Update the rest of the model during size negotiation
- impl.QueueModifyEvent( ModifyEvent::TEXT_REPLACED );
+ impl.QueueModifyEvent(ModifyEvent::TEXT_REPLACED);
// The natural size needs to be re-calculated.
impl.mRecalculateNaturalSize = true;
}
else
{
- controller.ShowPlaceholderText();
+ PlaceholderHandler::ShowPlaceholderText(impl);
}
+ unsigned int oldCursorPos = (nullptr != eventData ? eventData->mPrimaryCursorPosition : 0);
+
// Resets the cursor position.
- controller.ResetCursorPosition( lastCursorIndex );
+ controller.ResetCursorPosition(lastCursorIndex);
// Scrolls the text to make the cursor visible.
- controller.ResetScrollPosition();
+ impl.ResetScrollPosition();
impl.RequestRelayout();
- if( nullptr != eventData )
+ if(nullptr != eventData)
{
// Cancel previously queued events
eventData->mEventQueue.clear();
}
// Do this last since it provides callbacks into application code.
- if( NULL != impl.mEditableControlInterface )
+ if(NULL != impl.mEditableControlInterface)
{
- impl.mEditableControlInterface->TextChanged();
+ impl.mEditableControlInterface->CursorPositionChanged(oldCursorPos, lastCursorIndex);
+ impl.mEditableControlInterface->TextChanged(true);
}
}
void Controller::TextUpdater::InsertText(Controller& controller, const std::string& text, Controller::InsertType type)
{
- Controller::Impl& impl = *controller.mImpl;
- EventData*& eventData = impl.mEventData;
+ Controller::Impl& impl = *controller.mImpl;
+ EventData*& eventData = impl.mEventData;
- DALI_ASSERT_DEBUG( nullptr != eventData && "Unexpected InsertText" )
+ DALI_ASSERT_DEBUG(nullptr != eventData && "Unexpected InsertText")
- if( NULL == eventData )
+ if(NULL == eventData)
{
return;
}
- bool removedPrevious = false;
- bool removedSelected = false;
- bool maxLengthReached = false;
+ bool removedPrevious = false;
+ bool removedSelected = false;
+ bool maxLengthReached = false;
+ unsigned int oldCursorPos = eventData->mPrimaryCursorPosition;
- 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 );
+ 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);
- ModelPtr& model = impl.mModel;
+ ModelPtr& model = impl.mModel;
LogicalModelPtr& logicalModel = model->mLogicalModel;
// TODO: At the moment the underline runs are only for pre-edit.
model->mVisualModel->mUnderlineRuns.Clear();
// Remove the previous InputMethodContext pre-edit.
- if( eventData->mPreEditFlag && ( 0u != eventData->mPreEditLength ) )
+ if(eventData->mPreEditFlag && (0u != eventData->mPreEditLength))
{
- removedPrevious = RemoveText( controller,
- -static_cast<int>( eventData->mPrimaryCursorPosition - eventData->mPreEditStartPosition ),
- eventData->mPreEditLength,
- DONT_UPDATE_INPUT_STYLE );
+ removedPrevious = RemoveText(controller,
+ -static_cast<int>(eventData->mPrimaryCursorPosition - eventData->mPreEditStartPosition),
+ eventData->mPreEditLength,
+ DONT_UPDATE_INPUT_STYLE);
eventData->mPrimaryCursorPosition = eventData->mPreEditStartPosition;
- eventData->mPreEditLength = 0u;
+ eventData->mPreEditLength = 0u;
}
else
{
// Remove the previous Selection.
removedSelected = RemoveSelectedText(controller);
-
}
Vector<Character> utf32Characters;
- Length characterCount = 0u;
+ Length characterCount = 0u;
- if( !text.empty() )
+ if(!text.empty())
{
// Convert text into UTF-32
- utf32Characters.Resize( text.size() );
+ utf32Characters.Resize(text.size());
// This is a bit horrible but std::string returns a (signed) char*
- const uint8_t* utf8 = reinterpret_cast<const uint8_t*>( text.c_str() );
+ const uint8_t* utf8 = reinterpret_cast<const uint8_t*>(text.c_str());
// Transform a text array encoded in utf8 into an array encoded in utf32.
// It returns the actual number of characters.
- characterCount = Utf8ToUtf32( utf8, text.size(), utf32Characters.Begin() );
- utf32Characters.Resize( characterCount );
+ characterCount = Utf8ToUtf32(utf8, text.size(), utf32Characters.Begin());
+ utf32Characters.Resize(characterCount);
- DALI_ASSERT_DEBUG( text.size() >= utf32Characters.Count() && "Invalid UTF32 conversion length" );
- DALI_LOG_INFO( gLogFilter, Debug::Verbose, "UTF8 size %d, UTF32 size %d\n", text.size(), utf32Characters.Count() );
+ DALI_ASSERT_DEBUG(text.size() >= utf32Characters.Count() && "Invalid UTF32 conversion length");
+ DALI_LOG_INFO(gLogFilter, Debug::Verbose, "UTF8 size %d, UTF32 size %d\n", text.size(), utf32Characters.Count());
}
- if( 0u != utf32Characters.Count() ) // Check if Utf8ToUtf32 conversion succeeded
+ if(0u != utf32Characters.Count()) // Check if Utf8ToUtf32 conversion succeeded
{
// The placeholder text is no longer needed
- if( impl.IsShowingPlaceholderText() )
+ if(impl.IsShowingPlaceholderText())
{
ResetText(controller);
}
- impl.ChangeState( EventData::EDITING );
+ impl.ChangeState(EventData::EDITING);
// Handle the InputMethodContext (predicitive text) state changes
- if( COMMIT == type )
+ if(COMMIT == type)
{
// InputMethodContext is no longer handling key-events
impl.ClearPreEditFlag();
}
else // PRE_EDIT
{
- if( !eventData->mPreEditFlag )
+ if(!eventData->mPreEditFlag)
{
- DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Entered PreEdit state\n" );
+ DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Entered PreEdit state\n");
// Record the start of the pre-edit text
eventData->mPreEditStartPosition = eventData->mPrimaryCursorPosition;
}
eventData->mPreEditLength = utf32Characters.Count();
- eventData->mPreEditFlag = true;
+ eventData->mPreEditFlag = true;
- DALI_LOG_INFO( gLogFilter, Debug::Verbose, "mPreEditStartPosition %d mPreEditLength %d\n", eventData->mPreEditStartPosition, eventData->mPreEditLength );
+ DALI_LOG_INFO(gLogFilter, Debug::Verbose, "mPreEditStartPosition %d mPreEditLength %d\n", eventData->mPreEditStartPosition, eventData->mPreEditLength);
}
const Length numberOfCharactersInModel = logicalModel->mText.Count();
// Restrict new text to fit within Maximum characters setting.
- Length maxSizeOfNewText = std::min( ( impl.mMaximumNumberOfCharacters - numberOfCharactersInModel ), characterCount );
- maxLengthReached = ( characterCount > maxSizeOfNewText );
+ Length temp_length = (impl.mMaximumNumberOfCharacters > numberOfCharactersInModel ? impl.mMaximumNumberOfCharacters - numberOfCharactersInModel : 0);
+ Length maxSizeOfNewText = std::min(temp_length, characterCount);
+ maxLengthReached = (characterCount > maxSizeOfNewText);
// The cursor position.
CharacterIndex& cursorIndex = eventData->mPrimaryCursorPosition;
// Update the text's style.
// Updates the text style runs by adding characters.
- logicalModel->UpdateTextStyleRuns( cursorIndex, maxSizeOfNewText );
+ logicalModel->UpdateTextStyleRuns(cursorIndex, maxSizeOfNewText);
// Get the character index from the cursor index.
- const CharacterIndex styleIndex = ( cursorIndex > 0u ) ? cursorIndex - 1u : 0u;
+ const CharacterIndex styleIndex = (cursorIndex > 0u) ? cursorIndex - 1u : 0u;
// Retrieve the text's style for the given index.
InputStyle style;
- impl.RetrieveDefaultInputStyle( style );
- logicalModel->RetrieveStyle( styleIndex, style );
+ impl.RetrieveDefaultInputStyle(style);
+ logicalModel->RetrieveStyle(styleIndex, style);
InputStyle& inputStyle = eventData->mInputStyle;
// Whether to add a new text color run.
- const bool addColorRun = ( style.textColor != inputStyle.textColor ) && !inputStyle.isDefaultColor;
+ const bool addColorRun = (style.textColor != inputStyle.textColor) && !inputStyle.isDefaultColor;
// Whether to add a new font run.
- const bool addFontNameRun = ( style.familyName != inputStyle.familyName ) && inputStyle.isFamilyDefined;
- const bool addFontWeightRun = ( style.weight != inputStyle.weight ) && inputStyle.isWeightDefined;
- const bool addFontWidthRun = ( style.width != inputStyle.width ) && inputStyle.isWidthDefined;
- const bool addFontSlantRun = ( style.slant != inputStyle.slant ) && inputStyle.isSlantDefined;
- const bool addFontSizeRun = ( style.size != inputStyle.size ) && inputStyle.isSizeDefined ;
+ const bool addFontNameRun = (style.familyName != inputStyle.familyName) && inputStyle.isFamilyDefined;
+ const bool addFontWeightRun = (style.weight != inputStyle.weight) && inputStyle.isWeightDefined;
+ const bool addFontWidthRun = (style.width != inputStyle.width) && inputStyle.isWidthDefined;
+ const bool addFontSlantRun = (style.slant != inputStyle.slant) && inputStyle.isSlantDefined;
+ const bool addFontSizeRun = (style.size != inputStyle.size) && inputStyle.isSizeDefined;
// Add style runs.
- if( addColorRun )
+ if(addColorRun)
{
const VectorBase::SizeType numberOfRuns = logicalModel->mColorRuns.Count();
- logicalModel->mColorRuns.Resize( numberOfRuns + 1u );
+ logicalModel->mColorRuns.Resize(numberOfRuns + 1u);
- ColorRun& colorRun = *( logicalModel->mColorRuns.Begin() + numberOfRuns );
- colorRun.color = inputStyle.textColor;
- colorRun.characterRun.characterIndex = cursorIndex;
+ ColorRun& colorRun = *(logicalModel->mColorRuns.Begin() + numberOfRuns);
+ colorRun.color = inputStyle.textColor;
+ colorRun.characterRun.characterIndex = cursorIndex;
colorRun.characterRun.numberOfCharacters = maxSizeOfNewText;
}
- if( addFontNameRun ||
- addFontWeightRun ||
- addFontWidthRun ||
- addFontSlantRun ||
- addFontSizeRun )
+ if(addFontNameRun ||
+ addFontWeightRun ||
+ addFontWidthRun ||
+ addFontSlantRun ||
+ addFontSizeRun)
{
const VectorBase::SizeType numberOfRuns = logicalModel->mFontDescriptionRuns.Count();
- logicalModel->mFontDescriptionRuns.Resize( numberOfRuns + 1u );
+ logicalModel->mFontDescriptionRuns.Resize(numberOfRuns + 1u);
- FontDescriptionRun& fontDescriptionRun = *( logicalModel->mFontDescriptionRuns.Begin() + numberOfRuns );
+ FontDescriptionRun& fontDescriptionRun = *(logicalModel->mFontDescriptionRuns.Begin() + numberOfRuns);
- if( addFontNameRun )
+ if(addFontNameRun)
{
fontDescriptionRun.familyLength = inputStyle.familyName.size();
- fontDescriptionRun.familyName = new char[fontDescriptionRun.familyLength];
- memcpy( fontDescriptionRun.familyName, inputStyle.familyName.c_str(), fontDescriptionRun.familyLength );
+ fontDescriptionRun.familyName = new char[fontDescriptionRun.familyLength];
+ memcpy(fontDescriptionRun.familyName, inputStyle.familyName.c_str(), fontDescriptionRun.familyLength);
fontDescriptionRun.familyDefined = true;
// The memory allocated for the font family name is freed when the font description is removed from the logical model.
}
- if( addFontWeightRun )
+ if(addFontWeightRun)
{
- fontDescriptionRun.weight = inputStyle.weight;
+ fontDescriptionRun.weight = inputStyle.weight;
fontDescriptionRun.weightDefined = true;
}
- if( addFontWidthRun )
+ if(addFontWidthRun)
{
- fontDescriptionRun.width = inputStyle.width;
+ fontDescriptionRun.width = inputStyle.width;
fontDescriptionRun.widthDefined = true;
}
- if( addFontSlantRun )
+ if(addFontSlantRun)
{
- fontDescriptionRun.slant = inputStyle.slant;
+ fontDescriptionRun.slant = inputStyle.slant;
fontDescriptionRun.slantDefined = true;
}
- if( addFontSizeRun )
+ if(addFontSizeRun)
{
- fontDescriptionRun.size = static_cast<PointSize26Dot6>( inputStyle.size * impl.mFontSizeScale * 64.f );
+ fontDescriptionRun.size = static_cast<PointSize26Dot6>(inputStyle.size * impl.GetFontSizeScale() * 64.f);
fontDescriptionRun.sizeDefined = true;
}
- fontDescriptionRun.characterRun.characterIndex = cursorIndex;
+ fontDescriptionRun.characterRun.characterIndex = cursorIndex;
fontDescriptionRun.characterRun.numberOfCharacters = maxSizeOfNewText;
}
Vector<Character>& modifyText = logicalModel->mText;
auto pos = modifyText.End();
- if( cursorIndex < numberOfCharactersInModel )
+ if(cursorIndex < numberOfCharactersInModel)
{
pos = modifyText.Begin() + cursorIndex;
}
unsigned int realPos = pos - modifyText.Begin();
- modifyText.Insert( pos, utf32Characters.Begin(), utf32Characters.Begin() + maxSizeOfNewText );
+ modifyText.Insert(pos, utf32Characters.Begin(), utf32Characters.Begin() + maxSizeOfNewText);
- if( NULL != impl.mEditableControlInterface )
+ if(NULL != impl.mEditableControlInterface)
{
- impl.mEditableControlInterface->TextInserted( realPos, maxSizeOfNewText, text );
+ impl.mEditableControlInterface->TextInserted(realPos, maxSizeOfNewText, text);
}
TextUpdateInfo& textUpdateInfo = impl.mTextUpdateInfo;
// Mark the first paragraph to be updated.
- if( Layout::Engine::SINGLE_LINE_BOX == impl.mLayoutEngine.GetLayout() )
+ if(Layout::Engine::SINGLE_LINE_BOX == impl.mLayoutEngine.GetLayout())
{
- textUpdateInfo.mCharacterIndex = 0;
+ textUpdateInfo.mCharacterIndex = 0;
textUpdateInfo.mNumberOfCharactersToRemove = textUpdateInfo.mPreviousNumberOfCharacters;
- textUpdateInfo.mNumberOfCharactersToAdd = numberOfCharactersInModel + maxSizeOfNewText;
- textUpdateInfo.mClearAll = true;
+ textUpdateInfo.mNumberOfCharactersToAdd = numberOfCharactersInModel + maxSizeOfNewText;
+ textUpdateInfo.mClearAll = true;
}
else
{
- textUpdateInfo.mCharacterIndex = std::min( cursorIndex, textUpdateInfo.mCharacterIndex );
+ textUpdateInfo.mCharacterIndex = std::min(cursorIndex, textUpdateInfo.mCharacterIndex);
textUpdateInfo.mNumberOfCharactersToAdd += maxSizeOfNewText;
}
+ if(impl.mMarkupProcessorEnabled)
+ {
+ InsertTextAnchor(controller, maxSizeOfNewText, cursorIndex);
+ }
+
// Update the cursor index.
cursorIndex += maxSizeOfNewText;
- DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Inserted %d characters, new size %d new cursor %d\n", maxSizeOfNewText, logicalModel->mText.Count(), eventData->mPrimaryCursorPosition );
+ DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Inserted %d characters, new size %d new cursor %d\n", maxSizeOfNewText, logicalModel->mText.Count(), eventData->mPrimaryCursorPosition);
}
- if( ( 0u == logicalModel->mText.Count() ) &&
- impl.IsPlaceholderAvailable() )
+ if((0u == logicalModel->mText.Count()) &&
+ impl.IsPlaceholderAvailable())
{
// Show place-holder if empty after removing the pre-edit text
- controller.ShowPlaceholderText();
+ PlaceholderHandler::ShowPlaceholderText(impl);
eventData->mUpdateCursorPosition = true;
impl.ClearPreEditFlag();
}
- else if( removedPrevious ||
- removedSelected ||
- ( 0 != utf32Characters.Count() ) )
+ else if(removedPrevious ||
+ removedSelected ||
+ (0 != utf32Characters.Count()))
{
// Queue an inserted event
- impl.QueueModifyEvent( ModifyEvent::TEXT_INSERTED );
+ impl.QueueModifyEvent(ModifyEvent::TEXT_INSERTED);
eventData->mUpdateCursorPosition = true;
- if( removedSelected )
+ if(removedSelected)
{
eventData->mScrollAfterDelete = true;
}
}
}
- if( maxLengthReached )
+ if(nullptr != impl.mEditableControlInterface)
+ {
+ impl.mEditableControlInterface->CursorPositionChanged(oldCursorPos, eventData->mPrimaryCursorPosition);
+ }
+
+ if(maxLengthReached)
{
- DALI_LOG_INFO( gLogFilter, Debug::Verbose, "MaxLengthReached (%d)\n", logicalModel->mText.Count() );
+ DALI_LOG_INFO(gLogFilter, Debug::Verbose, "MaxLengthReached (%d)\n", logicalModel->mText.Count());
impl.ResetInputMethodContext();
- if( NULL != impl.mEditableControlInterface )
+ if(NULL != impl.mEditableControlInterface)
{
// Do this last since it provides callbacks into application code
impl.mEditableControlInterface->MaxLengthReached();
void Controller::TextUpdater::PasteText(Controller& controller, const std::string& stringToPaste)
{
- InsertText( controller, stringToPaste, Text::Controller::COMMIT );
+ InsertText(controller, stringToPaste, Text::Controller::COMMIT);
Controller::Impl& impl = *controller.mImpl;
- impl.ChangeState( EventData::EDITING );
+ impl.ChangeState(EventData::EDITING);
impl.RequestRelayout();
- if( NULL != impl.mEditableControlInterface )
+ if(NULL != impl.mEditableControlInterface)
{
// Do this last since it provides callbacks into application code
- impl.mEditableControlInterface->TextChanged();
+ impl.mEditableControlInterface->TextChanged(true);
}
}
bool Controller::TextUpdater::RemoveText(
- Controller& controller,
- int cursorOffset,
- int numberOfCharacters,
- UpdateInputStyleType type )
+ Controller& controller,
+ int cursorOffset,
+ int numberOfCharacters,
+ UpdateInputStyleType type)
{
bool removed = false;
- Controller::Impl& impl = *controller.mImpl;
- EventData*& eventData = impl.mEventData;
+ Controller::Impl& impl = *controller.mImpl;
+ EventData*& eventData = impl.mEventData;
- if( nullptr == eventData )
+ if(nullptr == eventData)
{
return removed;
}
- ModelPtr& model = impl.mModel;
+ ModelPtr& model = impl.mModel;
LogicalModelPtr& logicalModel = model->mLogicalModel;
+ VisualModelPtr& visualModel = model->mVisualModel;
- 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 );
+ 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);
- if( !impl.IsShowingPlaceholderText() )
+ if(!impl.IsShowingPlaceholderText())
{
// Delete at current cursor position
- Vector<Character>& currentText = logicalModel->mText;
- CharacterIndex& oldCursorIndex = eventData->mPrimaryCursorPosition;
+ Vector<Character>& currentText = logicalModel->mText;
+ CharacterIndex& previousCursorIndex = eventData->mPrimaryCursorPosition;
CharacterIndex cursorIndex = 0;
// Validate the cursor position & number of characters
- if( ( static_cast< int >( eventData->mPrimaryCursorPosition ) + cursorOffset ) >= 0 )
+ if((static_cast<int>(eventData->mPrimaryCursorPosition) + cursorOffset) >= 0)
{
cursorIndex = eventData->mPrimaryCursorPosition + cursorOffset;
}
- if( ( cursorIndex + numberOfCharacters ) > currentText.Count() )
+ //Handle Emoji clustering for cursor handling
+ // Deletion case: this is handling the deletion cases when the cursor is before or after Emoji
+ // - Before: when use delete key and cursor is before Emoji (cursorOffset = -1)
+ // - After: when use backspace key and cursor is after Emoji (cursorOffset = 0)
+
+ const Script script = logicalModel->GetScript(cursorIndex);
+ if((numberOfCharacters == 1u) &&
+ (IsOneOfEmojiScripts(script)))
+ {
+ //TODO: Use this clustering for Emoji cases only. This needs more testing to generalize to all scripts.
+ CharacterRun emojiClusteredCharacters = RetrieveClusteredCharactersOfCharacterIndex(visualModel, logicalModel, cursorIndex);
+ Length actualNumberOfCharacters = emojiClusteredCharacters.numberOfCharacters;
+
+ //Set cursorIndex at the first characterIndex of clustred Emoji
+ cursorIndex = emojiClusteredCharacters.characterIndex;
+
+ numberOfCharacters = actualNumberOfCharacters;
+ }
+
+ if((cursorIndex + numberOfCharacters) > currentText.Count())
{
numberOfCharacters = currentText.Count() - cursorIndex;
}
TextUpdateInfo& textUpdateInfo = impl.mTextUpdateInfo;
- 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.
- ( ( cursorIndex + numberOfCharacters ) <= textUpdateInfo.mPreviousNumberOfCharacters ) )
+ 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.
+ ((cursorIndex + numberOfCharacters) <= textUpdateInfo.mPreviousNumberOfCharacters))
{
// Mark the paragraphs to be updated.
- if( Layout::Engine::SINGLE_LINE_BOX == impl.mLayoutEngine.GetLayout() )
+ if(Layout::Engine::SINGLE_LINE_BOX == impl.mLayoutEngine.GetLayout())
{
- textUpdateInfo.mCharacterIndex = 0;
+ textUpdateInfo.mCharacterIndex = 0;
textUpdateInfo.mNumberOfCharactersToRemove = textUpdateInfo.mPreviousNumberOfCharacters;
- textUpdateInfo.mNumberOfCharactersToAdd = textUpdateInfo.mPreviousNumberOfCharacters - numberOfCharacters;
- textUpdateInfo.mClearAll = true;
+ textUpdateInfo.mNumberOfCharactersToAdd = textUpdateInfo.mPreviousNumberOfCharacters - numberOfCharacters;
+ textUpdateInfo.mClearAll = true;
}
else
{
- textUpdateInfo.mCharacterIndex = std::min( cursorIndex, textUpdateInfo.mCharacterIndex );
+ textUpdateInfo.mCharacterIndex = std::min(cursorIndex, textUpdateInfo.mCharacterIndex);
textUpdateInfo.mNumberOfCharactersToRemove += numberOfCharacters;
}
// Update the input style and remove the text's style before removing the text.
- if( UPDATE_INPUT_STYLE == type )
+ if(UPDATE_INPUT_STYLE == type)
{
InputStyle& eventDataInputStyle = eventData->mInputStyle;
// Keep a copy of the current input style.
InputStyle currentInputStyle;
- currentInputStyle.Copy( eventDataInputStyle );
+ currentInputStyle.Copy(eventDataInputStyle);
// Set first the default input style.
- impl.RetrieveDefaultInputStyle( eventDataInputStyle );
+ impl.RetrieveDefaultInputStyle(eventDataInputStyle);
// Update the input style.
- logicalModel->RetrieveStyle( cursorIndex, eventDataInputStyle );
+ logicalModel->RetrieveStyle(cursorIndex, eventDataInputStyle);
// Compare if the input style has changed.
- const bool hasInputStyleChanged = !currentInputStyle.Equal( eventDataInputStyle );
+ const bool hasInputStyleChanged = !currentInputStyle.Equal(eventDataInputStyle);
- if( hasInputStyleChanged )
+ if(hasInputStyleChanged)
{
- const InputStyle::Mask styleChangedMask = currentInputStyle.GetInputStyleChangeMask( eventDataInputStyle );
+ const InputStyle::Mask styleChangedMask = currentInputStyle.GetInputStyleChangeMask(eventDataInputStyle);
// Queue the input style changed signal.
- eventData->mInputStyleChangedQueue.PushBack( styleChangedMask );
+ eventData->mInputStyleChangedQueue.PushBack(styleChangedMask);
}
}
// If the number of current text and the number of characters to be deleted are same,
// it means all texts should be removed and all Preedit variables should be initialized.
- if( ( currentText.Count() - numberOfCharacters == 0 ) && ( cursorIndex == 0 ) )
+ if((currentText.Count() - numberOfCharacters == 0) && (cursorIndex == 0))
{
impl.ClearPreEditFlag();
textUpdateInfo.mNumberOfCharactersToAdd = 0;
}
// Updates the text style runs by removing characters. Runs with no characters are removed.
- logicalModel->UpdateTextStyleRuns( cursorIndex, -numberOfCharacters );
+ logicalModel->UpdateTextStyleRuns(cursorIndex, -numberOfCharacters);
// Remove the characters.
Vector<Character>::Iterator first = currentText.Begin() + cursorIndex;
Vector<Character>::Iterator last = first + numberOfCharacters;
- if( NULL != impl.mEditableControlInterface )
+ if(NULL != impl.mEditableControlInterface)
{
std::string utf8;
- Utf32ToUtf8( first, numberOfCharacters, utf8 );
- impl.mEditableControlInterface->TextDeleted( cursorIndex, numberOfCharacters, utf8 );
+ Utf32ToUtf8(first, numberOfCharacters, utf8);
+ impl.mEditableControlInterface->TextDeleted(cursorIndex, numberOfCharacters, utf8);
}
- currentText.Erase( first, last );
+ currentText.Erase(first, last);
+
+ if(impl.mMarkupProcessorEnabled)
+ {
+ RemoveTextAnchor(controller, cursorOffset, numberOfCharacters, previousCursorIndex);
+ }
+
+ if(nullptr != impl.mEditableControlInterface)
+ {
+ impl.mEditableControlInterface->CursorPositionChanged(previousCursorIndex, cursorIndex);
+ }
// Cursor position retreat
- oldCursorIndex = cursorIndex;
+ previousCursorIndex = cursorIndex;
eventData->mScrollAfterDelete = true;
- if( EventData::INACTIVE == eventData->mState )
+ if(EventData::INACTIVE == eventData->mState)
{
- impl.ChangeState( EventData::EDITING );
+ impl.ChangeState(EventData::EDITING);
}
- DALI_LOG_INFO( gLogFilter, Debug::General, "Controller::RemoveText %p removed %d\n", &controller, numberOfCharacters );
+ DALI_LOG_INFO(gLogFilter, Debug::General, "Controller::RemoveText %p removed %d\n", &controller, numberOfCharacters);
removed = true;
}
}
bool Controller::TextUpdater::RemoveSelectedText(Controller& controller)
{
- bool textRemoved( false );
+ bool textRemoved(false);
Controller::Impl& impl = *controller.mImpl;
- if( EventData::SELECTING == impl.mEventData->mState )
+ if(EventData::SELECTING == impl.mEventData->mState)
{
std::string removedString;
- impl.RetrieveSelection( removedString, true );
+ uint32_t oldSelStart = impl.mEventData->mLeftSelectionPosition;
+ uint32_t oldSelEnd = impl.mEventData->mRightSelectionPosition;
+
+ impl.RetrieveSelection(removedString, true);
- if( !removedString.empty() )
+ if(!removedString.empty())
{
textRemoved = true;
- impl.ChangeState( EventData::EDITING );
+ impl.ChangeState(EventData::EDITING);
+
+ if(impl.mMarkupProcessorEnabled)
+ {
+ int cursorOffset = -1;
+ int numberOfCharacters = removedString.length();
+ CharacterIndex& cursorIndex = impl.mEventData->mPrimaryCursorPosition;
+ CharacterIndex previousCursorIndex = cursorIndex + numberOfCharacters;
+
+ RemoveTextAnchor(controller, cursorOffset, numberOfCharacters, previousCursorIndex);
+ }
+
+ if(impl.mSelectableControlInterface != nullptr)
+ {
+ impl.mSelectableControlInterface->SelectionChanged(oldSelStart, oldSelEnd, impl.mEventData->mPrimaryCursorPosition, impl.mEventData->mPrimaryCursorPosition);
+ }
}
}
void Controller::TextUpdater::ResetText(Controller& controller)
{
- Controller::Impl& impl = *controller.mImpl;
- LogicalModelPtr& logicalModel = impl.mModel->mLogicalModel;
+ Controller::Impl& impl = *controller.mImpl;
+ LogicalModelPtr& logicalModel = impl.mModel->mLogicalModel;
// Reset buffers.
logicalModel->mText.Clear();
// Reset the embedded images buffer.
logicalModel->ClearEmbeddedImages();
+ // Reset the anchors buffer.
+ logicalModel->ClearAnchors();
+
// We have cleared everything including the placeholder-text
impl.PlaceholderCleared();
- impl.mTextUpdateInfo.mCharacterIndex = 0u;
+ impl.mTextUpdateInfo.mCharacterIndex = 0u;
impl.mTextUpdateInfo.mNumberOfCharactersToRemove = impl.mTextUpdateInfo.mPreviousNumberOfCharacters;
- impl.mTextUpdateInfo.mNumberOfCharactersToAdd = 0u;
+ impl.mTextUpdateInfo.mNumberOfCharactersToAdd = 0u;
// Clear any previous text.
impl.mTextUpdateInfo.mClearAll = true;
impl.mOperationsPending = ALL_OPERATIONS;
}
+void Controller::TextUpdater::InsertTextAnchor(Controller& controller, int numberOfCharacters, CharacterIndex previousCursorIndex)
+{
+ Controller::Impl& impl = *controller.mImpl;
+ ModelPtr& model = impl.mModel;
+ LogicalModelPtr& logicalModel = model->mLogicalModel;
+
+ for(auto& anchor : logicalModel->mAnchors)
+ {
+ if(anchor.endIndex < previousCursorIndex) // [anchor] CUR
+ {
+ continue;
+ }
+ if(anchor.startIndex < previousCursorIndex) // [anCURr]
+ {
+ anchor.endIndex += numberOfCharacters;
+ }
+ else // CUR [anchor]
+ {
+ anchor.startIndex += numberOfCharacters;
+ anchor.endIndex += numberOfCharacters;
+ }
+ DALI_LOG_INFO(gLogFilter, Debug::General, "Controller::InsertTextAnchor[%p] Anchor[%s] start[%d] end[%d]\n", &controller, anchor.href, anchor.startIndex, anchor.endIndex);
+ }
+}
+
+void Controller::TextUpdater::RemoveTextAnchor(Controller& controller, int cursorOffset, int numberOfCharacters, CharacterIndex previousCursorIndex)
+{
+ Controller::Impl& impl = *controller.mImpl;
+ ModelPtr& model = impl.mModel;
+ LogicalModelPtr& logicalModel = model->mLogicalModel;
+ Vector<Anchor>::Iterator it = logicalModel->mAnchors.Begin();
+
+ while(it != logicalModel->mAnchors.End())
+ {
+ Anchor& anchor = *it;
+
+ if(anchor.endIndex <= previousCursorIndex && cursorOffset == 0) // [anchor] CUR >>
+ {
+ // Nothing happens.
+ }
+ else if(anchor.endIndex <= previousCursorIndex && cursorOffset == -1) // [anchor] << CUR
+ {
+ int endIndex = anchor.endIndex;
+ int offset = previousCursorIndex - endIndex;
+ int index = endIndex - (numberOfCharacters - offset);
+
+ if(index < endIndex)
+ {
+ endIndex = index;
+ }
+
+ if((int)anchor.startIndex >= endIndex)
+ {
+ if(anchor.href)
+ {
+ delete[] anchor.href;
+ }
+ it = logicalModel->mAnchors.Erase(it);
+ continue;
+ }
+ else
+ {
+ anchor.endIndex = endIndex;
+ }
+ }
+ else if(anchor.startIndex >= previousCursorIndex && cursorOffset == -1) // << CUR [anchor]
+ {
+ anchor.startIndex -= numberOfCharacters;
+ anchor.endIndex -= numberOfCharacters;
+ }
+ else if(anchor.startIndex >= previousCursorIndex && cursorOffset == 0) // CUR >> [anchor]
+ {
+ int startIndex = anchor.startIndex;
+ int endIndex = anchor.endIndex;
+ int index = previousCursorIndex + numberOfCharacters - 1;
+
+ if(startIndex > index)
+ {
+ anchor.startIndex -= numberOfCharacters;
+ anchor.endIndex -= numberOfCharacters;
+ }
+ else if(endIndex > index + 1)
+ {
+ anchor.endIndex -= numberOfCharacters;
+ }
+ else
+ {
+ if(anchor.href)
+ {
+ delete[] anchor.href;
+ }
+ it = logicalModel->mAnchors.Erase(it);
+ continue;
+ }
+ }
+ else if(cursorOffset == -1) // [<< CUR]
+ {
+ int startIndex = anchor.startIndex;
+ int index = previousCursorIndex - numberOfCharacters;
+
+ if(startIndex >= index)
+ {
+ anchor.startIndex = index;
+ }
+ anchor.endIndex -= numberOfCharacters;
+ }
+ else if(cursorOffset == 0) // [CUR >>]
+ {
+ anchor.endIndex -= numberOfCharacters;
+ }
+ else
+ {
+ // When this condition is reached, someting is wrong.
+ DALI_LOG_ERROR("Controller::RemoveTextAnchor[%p] Invaild state cursorOffset[%d]\n", &controller, cursorOffset);
+ }
+
+ DALI_LOG_INFO(gLogFilter, Debug::General, "Controller::RemoveTextAnchor[%p] Anchor[%s] start[%d] end[%d]\n", &controller, anchor.href, anchor.startIndex, anchor.endIndex);
+
+ it++;
+ }
+}
+
} // namespace Text
} // namespace Toolkit