X-Git-Url: http://review.tizen.org/git/?p=platform%2Fcore%2Fuifw%2Fdali-toolkit.git;a=blobdiff_plain;f=dali-toolkit%2Finternal%2Ftext%2Ftext-controller-impl.cpp;h=e77f00d067b77a2d607f945ade851017e690f008;hp=7dd88e47550b6a013b19b46e5c510b542adacfd8;hb=7d540590dfce9f3c3773ca25486b3089b002c553;hpb=c7782244daa44358fe734237d3406833ca492196 diff --git a/dali-toolkit/internal/text/text-controller-impl.cpp b/dali-toolkit/internal/text/text-controller-impl.cpp index 7dd88e4..e77f00d 100644 --- a/dali-toolkit/internal/text/text-controller-impl.cpp +++ b/dali-toolkit/internal/text/text-controller-impl.cpp @@ -25,41 +25,20 @@ // INTERNAL INCLUDES #include #include +#include +#include #include #include #include +#include namespace { #if defined(DEBUG_ENABLED) - Debug::Filter* gLogFilter = Debug::Filter::New(Debug::Concise, true, "LOG_TEXT_CONTROLS"); + Debug::Filter* gLogFilter = Debug::Filter::New(Debug::NoLogging, true, "LOG_TEXT_CONTROLS"); #endif -/** - * @brief Some characters can be shaped in more than one glyph. - * This struct is used to retrieve metrics from these group of glyphs. - */ -struct GlyphMetrics -{ - GlyphMetrics() - : fontHeight( 0.f ), - advance( 0.f ), - ascender( 0.f ), - xBearing( 0.f ) - {} - - ~GlyphMetrics() - {} - - float fontHeight; ///< The font's height of that glyphs. - float advance; ///< The sum of all the advances of all the glyphs. - float ascender; ///< The font's ascender. - float xBearing; ///< The x bearing of the first glyph. -}; - -const std::string EMPTY_STRING(""); - } // namespace namespace Dali @@ -71,43 +50,9 @@ namespace Toolkit namespace Text { -/** - * @brief Get some glyph's metrics of a group of glyphs formed as a result of shaping one character. - * - * @param[in] glyphIndex The index to the first glyph. - * @param[in] numberOfGlyphs The number of glyphs. - * @param[out] glyphMetrics Some glyph metrics (font height, advance, ascender and x bearing). - * @param[in] visualModel The visual model. - * @param[in] fontClient The font client. - */ -void GetGlyphsMetrics( GlyphIndex glyphIndex, - Length numberOfGlyphs, - GlyphMetrics& glyphMetrics, - VisualModelPtr visualModel, - TextAbstraction::FontClient& fontClient ) -{ - const GlyphInfo* glyphsBuffer = visualModel->mGlyphs.Begin(); - - const GlyphInfo& firstGlyph = *( glyphsBuffer + glyphIndex ); - - Text::FontMetrics fontMetrics; - fontClient.GetFontMetrics( firstGlyph.fontId, fontMetrics ); - - glyphMetrics.fontHeight = fontMetrics.height; - glyphMetrics.advance = firstGlyph.advance; - glyphMetrics.ascender = fontMetrics.ascender; - glyphMetrics.xBearing = firstGlyph.xBearing; - - for( unsigned int i = 1u; i < numberOfGlyphs; ++i ) - { - const GlyphInfo& glyphInfo = *( glyphsBuffer + glyphIndex + i ); - - glyphMetrics.advance += glyphInfo.advance; - } -} - EventData::EventData( DecoratorPtr decorator ) : mDecorator( decorator ), + mImfManager(), mPlaceholderTextActive(), mPlaceholderTextInactive(), mPlaceholderTextColor( 0.8f, 0.8f, 0.8f, 0.8f ), @@ -133,8 +78,11 @@ EventData::EventData( DecoratorPtr decorator ) mUpdateRightSelectionPosition( false ), mScrollAfterUpdatePosition( false ), mScrollAfterDelete( false ), - mAllTextSelected( false ) -{} + mAllTextSelected( false ), + mUpdateInputStyle( false ) +{ + mImfManager = ImfManager::Get(); +} EventData::~EventData() {} @@ -202,62 +150,81 @@ bool Controller::Impl::ProcessInputEvents() if( mEventData->mUpdateCursorPosition ) { // Updates the cursor position and scrolls the text to make it visible. - - UpdateCursorPosition(); + CursorInfo cursorInfo; + GetCursorPosition( mEventData->mPrimaryCursorPosition, + cursorInfo ); if( mEventData->mScrollAfterUpdatePosition ) { - const Vector2& primaryCursorPosition = mEventData->mDecorator->GetPosition( PRIMARY_CURSOR ); - - ScrollToMakePositionVisible( primaryCursorPosition ); + ScrollToMakePositionVisible( cursorInfo.primaryPosition ); mEventData->mScrollAfterUpdatePosition = false; } + else if( mEventData->mScrollAfterDelete ) + { + ScrollTextToMatchCursor( cursorInfo ); + mEventData->mScrollAfterDelete = false; + } + + UpdateCursorPosition( cursorInfo ); mEventData->mDecoratorUpdated = true; mEventData->mUpdateCursorPosition = false; } - else if( mEventData->mScrollAfterDelete ) - { - ScrollTextToMatchCursor(); - mEventData->mDecoratorUpdated = true; - mEventData->mScrollAfterDelete = false; - } else { bool leftScroll = false; bool rightScroll = false; + CursorInfo leftHandleInfo; + CursorInfo rightHandleInfo; + if( mEventData->mUpdateLeftSelectionPosition ) { - UpdateSelectionHandle( LEFT_SELECTION_HANDLE ); + GetCursorPosition( mEventData->mLeftSelectionPosition, + leftHandleInfo ); if( mEventData->mScrollAfterUpdatePosition ) { - const Vector2& leftHandlePosition = mEventData->mDecorator->GetPosition( LEFT_SELECTION_HANDLE ); - - ScrollToMakePositionVisible( leftHandlePosition ); + ScrollToMakePositionVisible( leftHandleInfo.primaryPosition ); leftScroll = true; } - - SetPopupButtons(); - mEventData->mDecoratorUpdated = true; - mEventData->mUpdateLeftSelectionPosition = false; } if( mEventData->mUpdateRightSelectionPosition ) { - UpdateSelectionHandle( RIGHT_SELECTION_HANDLE ); + GetCursorPosition( mEventData->mRightSelectionPosition, + rightHandleInfo ); if( mEventData->mScrollAfterUpdatePosition ) { - const Vector2& rightHandlePosition = mEventData->mDecorator->GetPosition( RIGHT_SELECTION_HANDLE ); - - ScrollToMakePositionVisible( rightHandlePosition ); + ScrollToMakePositionVisible( rightHandleInfo.primaryPosition ); rightScroll = true; } + } + + if( mEventData->mUpdateLeftSelectionPosition ) + { + UpdateSelectionHandle( LEFT_SELECTION_HANDLE, + leftHandleInfo ); + + SetPopupButtons(); + mEventData->mDecoratorUpdated = true; + } + + if( mEventData->mUpdateRightSelectionPosition ) + { + UpdateSelectionHandle( RIGHT_SELECTION_HANDLE, + rightHandleInfo ); SetPopupButtons(); mEventData->mDecoratorUpdated = true; + } + + if( mEventData->mUpdateLeftSelectionPosition || mEventData->mUpdateRightSelectionPosition ) + { + RepositionSelectionHandles(); + + mEventData->mUpdateLeftSelectionPosition = false; mEventData->mUpdateRightSelectionPosition = false; } @@ -267,6 +234,20 @@ bool Controller::Impl::ProcessInputEvents() } } + if( mEventData->mUpdateInputStyle ) + { + // Set the default style first. + RetrieveDefaultInputStyle( mEventData->mInputStyle ); + + // Get the character index from the cursor index. + const CharacterIndex styleIndex = ( mEventData->mPrimaryCursorPosition > 0u ) ? mEventData->mPrimaryCursorPosition - 1u : 0u; + + // Retrieve the style from the style runs stored in the logical model. + mLogicalModel->RetrieveStyle( styleIndex, mEventData->mInputStyle ); + + mEventData->mUpdateInputStyle = false; + } + mEventData->mEventQueue.clear(); DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::ProcessInputEvents\n" ); @@ -277,17 +258,415 @@ bool Controller::Impl::ProcessInputEvents() return decoratorUpdated; } -void Controller::Impl::UpdateModel( OperationsMask operationsRequired ) +void Controller::Impl::CalculateTextUpdateIndices( Length& numberOfCharacters ) +{ + mTextUpdateInfo.mParagraphCharacterIndex = 0u; + mTextUpdateInfo.mStartGlyphIndex = 0u; + mTextUpdateInfo.mStartLineIndex = 0u; + numberOfCharacters = 0u; + + const Length numberOfParagraphs = mLogicalModel->mParagraphInfo.Count(); + if( 0u == numberOfParagraphs ) + { + mTextUpdateInfo.mParagraphCharacterIndex = 0u; + numberOfCharacters = 0u; + + mTextUpdateInfo.mRequestedNumberOfCharacters = mTextUpdateInfo.mNumberOfCharactersToAdd - mTextUpdateInfo.mNumberOfCharactersToRemove; + + // Nothing else to do if there are no paragraphs. + return; + } + + // Find the paragraphs to be updated. + Vector paragraphsToBeUpdated; + if( mTextUpdateInfo.mCharacterIndex >= mTextUpdateInfo.mPreviousNumberOfCharacters ) + { + // Text is being added at the end of the current text. + if( mTextUpdateInfo.mIsLastCharacterNewParagraph ) + { + // Text is being added in a new paragraph after the last character of the text. + mTextUpdateInfo.mParagraphCharacterIndex = mTextUpdateInfo.mPreviousNumberOfCharacters; + numberOfCharacters = 0u; + mTextUpdateInfo.mRequestedNumberOfCharacters = mTextUpdateInfo.mNumberOfCharactersToAdd - mTextUpdateInfo.mNumberOfCharactersToRemove; + + mTextUpdateInfo.mStartGlyphIndex = mVisualModel->mGlyphs.Count(); + mTextUpdateInfo.mStartLineIndex = mVisualModel->mLines.Count() - 1u; + + // Nothing else to do; + return; + } + + paragraphsToBeUpdated.PushBack( numberOfParagraphs - 1u ); + } + else + { + Length numberOfCharactersToUpdate = 0u; + if( mTextUpdateInfo.mFullRelayoutNeeded ) + { + numberOfCharactersToUpdate = mTextUpdateInfo.mPreviousNumberOfCharacters; + } + else + { + numberOfCharactersToUpdate = ( mTextUpdateInfo.mNumberOfCharactersToRemove > 0u ) ? mTextUpdateInfo.mNumberOfCharactersToRemove : 1u; + } + mLogicalModel->FindParagraphs( mTextUpdateInfo.mCharacterIndex, + numberOfCharactersToUpdate, + paragraphsToBeUpdated ); + } + + if( 0u != paragraphsToBeUpdated.Count() ) + { + const ParagraphRunIndex firstParagraphIndex = *( paragraphsToBeUpdated.Begin() ); + const ParagraphRun& firstParagraph = *( mLogicalModel->mParagraphInfo.Begin() + firstParagraphIndex ); + mTextUpdateInfo.mParagraphCharacterIndex = firstParagraph.characterRun.characterIndex; + + ParagraphRunIndex lastParagraphIndex = *( paragraphsToBeUpdated.End() - 1u ); + const ParagraphRun& lastParagraph = *( mLogicalModel->mParagraphInfo.Begin() + lastParagraphIndex ); + + if( ( mTextUpdateInfo.mNumberOfCharactersToRemove > 0u ) && // Some character are removed. + ( lastParagraphIndex < numberOfParagraphs - 1u ) && // There is a next paragraph. + ( ( lastParagraph.characterRun.characterIndex + lastParagraph.characterRun.numberOfCharacters ) == // The last removed character is the new paragraph character. + ( mTextUpdateInfo.mCharacterIndex + mTextUpdateInfo.mNumberOfCharactersToRemove ) ) ) + { + // The new paragraph character of the last updated paragraph has been removed so is going to be merged with the next one. + const ParagraphRun& lastParagraph = *( mLogicalModel->mParagraphInfo.Begin() + lastParagraphIndex + 1u ); + + numberOfCharacters = lastParagraph.characterRun.characterIndex + lastParagraph.characterRun.numberOfCharacters - mTextUpdateInfo.mParagraphCharacterIndex; + } + else + { + numberOfCharacters = lastParagraph.characterRun.characterIndex + lastParagraph.characterRun.numberOfCharacters - mTextUpdateInfo.mParagraphCharacterIndex; + } + } + + mTextUpdateInfo.mRequestedNumberOfCharacters = numberOfCharacters + mTextUpdateInfo.mNumberOfCharactersToAdd - mTextUpdateInfo.mNumberOfCharactersToRemove; + mTextUpdateInfo.mStartGlyphIndex = *( mVisualModel->mCharactersToGlyph.Begin() + mTextUpdateInfo.mParagraphCharacterIndex ); +} + +void Controller::Impl::ClearFullModelData( OperationsMask operations ) +{ + if( NO_OPERATION != ( GET_LINE_BREAKS & operations ) ) + { + mLogicalModel->mLineBreakInfo.Clear(); + mLogicalModel->mParagraphInfo.Clear(); + } + + if( NO_OPERATION != ( GET_WORD_BREAKS & operations ) ) + { + mLogicalModel->mLineBreakInfo.Clear(); + } + + if( NO_OPERATION != ( GET_SCRIPTS & operations ) ) + { + mLogicalModel->mScriptRuns.Clear(); + } + + if( NO_OPERATION != ( VALIDATE_FONTS & operations ) ) + { + mLogicalModel->mFontRuns.Clear(); + } + + if( 0u != mLogicalModel->mBidirectionalParagraphInfo.Count() ) + { + if( NO_OPERATION != ( BIDI_INFO & operations ) ) + { + mLogicalModel->mBidirectionalParagraphInfo.Clear(); + mLogicalModel->mCharacterDirections.Clear(); + } + + if( NO_OPERATION != ( REORDER & operations ) ) + { + // Free the allocated memory used to store the conversion table in the bidirectional line info run. + for( Vector::Iterator it = mLogicalModel->mBidirectionalLineInfo.Begin(), + endIt = mLogicalModel->mBidirectionalLineInfo.End(); + it != endIt; + ++it ) + { + BidirectionalLineInfoRun& bidiLineInfo = *it; + + free( bidiLineInfo.visualToLogicalMap ); + bidiLineInfo.visualToLogicalMap = NULL; + } + mLogicalModel->mBidirectionalLineInfo.Clear(); + } + } + + if( NO_OPERATION != ( SHAPE_TEXT & operations ) ) + { + mVisualModel->mGlyphs.Clear(); + mVisualModel->mGlyphsToCharacters.Clear(); + mVisualModel->mCharactersToGlyph.Clear(); + mVisualModel->mCharactersPerGlyph.Clear(); + mVisualModel->mGlyphsPerCharacter.Clear(); + mVisualModel->mGlyphPositions.Clear(); + } + + if( NO_OPERATION != ( LAYOUT & operations ) ) + { + mVisualModel->mLines.Clear(); + } + + if( NO_OPERATION != ( COLOR & operations ) ) + { + mVisualModel->mColorIndices.Clear(); + } +} + +void Controller::Impl::ClearCharacterModelData( CharacterIndex startIndex, CharacterIndex endIndex, OperationsMask operations ) +{ + const CharacterIndex endIndexPlusOne = endIndex + 1u; + + if( NO_OPERATION != ( GET_LINE_BREAKS & operations ) ) + { + // Clear the line break info. + LineBreakInfo* lineBreakInfoBuffer = mLogicalModel->mLineBreakInfo.Begin(); + + mLogicalModel->mLineBreakInfo.Erase( lineBreakInfoBuffer + startIndex, + lineBreakInfoBuffer + endIndexPlusOne ); + + // Clear the paragraphs. + ClearCharacterRuns( startIndex, + endIndex, + mLogicalModel->mParagraphInfo ); + } + + if( NO_OPERATION != ( GET_WORD_BREAKS & operations ) ) + { + // Clear the word break info. + WordBreakInfo* wordBreakInfoBuffer = mLogicalModel->mWordBreakInfo.Begin(); + + mLogicalModel->mWordBreakInfo.Erase( wordBreakInfoBuffer + startIndex, + wordBreakInfoBuffer + endIndexPlusOne ); + } + + if( NO_OPERATION != ( GET_SCRIPTS & operations ) ) + { + // Clear the scripts. + ClearCharacterRuns( startIndex, + endIndex, + mLogicalModel->mScriptRuns ); + } + + if( NO_OPERATION != ( VALIDATE_FONTS & operations ) ) + { + // Clear the fonts. + ClearCharacterRuns( startIndex, + endIndex, + mLogicalModel->mFontRuns ); + } + + if( 0u != mLogicalModel->mBidirectionalParagraphInfo.Count() ) + { + if( NO_OPERATION != ( BIDI_INFO & operations ) ) + { + // Clear the bidirectional paragraph info. + ClearCharacterRuns( startIndex, + endIndex, + mLogicalModel->mBidirectionalParagraphInfo ); + + // Clear the character's directions. + CharacterDirection* characterDirectionsBuffer = mLogicalModel->mCharacterDirections.Begin(); + + mLogicalModel->mCharacterDirections.Erase( characterDirectionsBuffer + startIndex, + characterDirectionsBuffer + endIndexPlusOne ); + } + + if( NO_OPERATION != ( REORDER & operations ) ) + { + uint32_t startRemoveIndex = mLogicalModel->mBidirectionalLineInfo.Count(); + uint32_t endRemoveIndex = startRemoveIndex; + ClearCharacterRuns( startIndex, + endIndex, + mLogicalModel->mBidirectionalLineInfo, + startRemoveIndex, + endRemoveIndex ); + + BidirectionalLineInfoRun* bidirectionalLineInfoBuffer = mLogicalModel->mBidirectionalLineInfo.Begin(); + + // Free the allocated memory used to store the conversion table in the bidirectional line info run. + for( Vector::Iterator it = bidirectionalLineInfoBuffer + startRemoveIndex, + endIt = bidirectionalLineInfoBuffer + endRemoveIndex; + it != endIt; + ++it ) + { + BidirectionalLineInfoRun& bidiLineInfo = *it; + + free( bidiLineInfo.visualToLogicalMap ); + bidiLineInfo.visualToLogicalMap = NULL; + } + + mLogicalModel->mBidirectionalLineInfo.Erase( bidirectionalLineInfoBuffer + startRemoveIndex, + bidirectionalLineInfoBuffer + endRemoveIndex ); + } + } +} + +void Controller::Impl::ClearGlyphModelData( CharacterIndex startIndex, CharacterIndex endIndex, OperationsMask operations ) +{ + const CharacterIndex endIndexPlusOne = endIndex + 1u; + const Length numberOfCharactersRemoved = endIndexPlusOne - startIndex; + + // Convert the character index to glyph index before deleting the character to glyph and the glyphs per character buffers. + GlyphIndex* charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin(); + Length* glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin(); + + const GlyphIndex endGlyphIndexPlusOne = *( charactersToGlyphBuffer + endIndex ) + *( glyphsPerCharacterBuffer + endIndex ); + const Length numberOfGlyphsRemoved = endGlyphIndexPlusOne - mTextUpdateInfo.mStartGlyphIndex; + + if( NO_OPERATION != ( SHAPE_TEXT & operations ) ) + { + // Update the character to glyph indices. + for( Vector::Iterator it = charactersToGlyphBuffer + endIndexPlusOne, + endIt = charactersToGlyphBuffer + mVisualModel->mCharactersToGlyph.Count(); + it != endIt; + ++it ) + { + CharacterIndex& index = *it; + index -= numberOfGlyphsRemoved; + } + + // Clear the character to glyph conversion table. + mVisualModel->mCharactersToGlyph.Erase( charactersToGlyphBuffer + startIndex, + charactersToGlyphBuffer + endIndexPlusOne ); + + // Clear the glyphs per character table. + mVisualModel->mGlyphsPerCharacter.Erase( glyphsPerCharacterBuffer + startIndex, + glyphsPerCharacterBuffer + endIndexPlusOne ); + + // Clear the glyphs buffer. + GlyphInfo* glyphsBuffer = mVisualModel->mGlyphs.Begin(); + mVisualModel->mGlyphs.Erase( glyphsBuffer + mTextUpdateInfo.mStartGlyphIndex, + glyphsBuffer + endGlyphIndexPlusOne ); + + CharacterIndex* glyphsToCharactersBuffer = mVisualModel->mGlyphsToCharacters.Begin(); + + // Update the glyph to character indices. + for( Vector::Iterator it = glyphsToCharactersBuffer + endGlyphIndexPlusOne, + endIt = glyphsToCharactersBuffer + mVisualModel->mGlyphsToCharacters.Count(); + it != endIt; + ++it ) + { + CharacterIndex& index = *it; + index -= numberOfCharactersRemoved; + } + + // Clear the glyphs to characters buffer. + mVisualModel->mGlyphsToCharacters.Erase( glyphsToCharactersBuffer + mTextUpdateInfo.mStartGlyphIndex, + glyphsToCharactersBuffer + endGlyphIndexPlusOne ); + + // Clear the characters per glyph buffer. + Length* charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin(); + mVisualModel->mCharactersPerGlyph.Erase( charactersPerGlyphBuffer + mTextUpdateInfo.mStartGlyphIndex, + charactersPerGlyphBuffer + endGlyphIndexPlusOne ); + + // Clear the positions buffer. + Vector2* positionsBuffer = mVisualModel->mGlyphPositions.Begin(); + mVisualModel->mGlyphPositions.Erase( positionsBuffer + mTextUpdateInfo.mStartGlyphIndex, + positionsBuffer + endGlyphIndexPlusOne ); + } + + if( NO_OPERATION != ( LAYOUT & operations ) ) + { + // Clear the lines. + uint32_t startRemoveIndex = mVisualModel->mLines.Count(); + uint32_t endRemoveIndex = startRemoveIndex; + ClearCharacterRuns( startIndex, + endIndex, + mVisualModel->mLines, + startRemoveIndex, + endRemoveIndex ); + + // Will update the glyph runs. + startRemoveIndex = mVisualModel->mLines.Count(); + endRemoveIndex = startRemoveIndex; + ClearGlyphRuns( mTextUpdateInfo.mStartGlyphIndex, + endGlyphIndexPlusOne - 1u, + mVisualModel->mLines, + startRemoveIndex, + endRemoveIndex ); + + // Set the line index from where to insert the new laid-out lines. + mTextUpdateInfo.mStartLineIndex = startRemoveIndex; + + LineRun* linesBuffer = mVisualModel->mLines.Begin(); + mVisualModel->mLines.Erase( linesBuffer + startRemoveIndex, + linesBuffer + endRemoveIndex ); + } + + if( NO_OPERATION != ( COLOR & operations ) ) + { + if( 0u != mVisualModel->mColorIndices.Count() ) + { + ColorIndex* colorIndexBuffer = mVisualModel->mColorIndices.Begin(); + mVisualModel->mColorIndices.Erase( colorIndexBuffer + mTextUpdateInfo.mStartGlyphIndex, + colorIndexBuffer + endGlyphIndexPlusOne ); + } + } +} + +void Controller::Impl::ClearModelData( CharacterIndex startIndex, CharacterIndex endIndex, OperationsMask operations ) { + if( mTextUpdateInfo.mClearAll || + ( ( 0u == startIndex ) && + ( mTextUpdateInfo.mPreviousNumberOfCharacters == endIndex + 1u ) ) ) + { + ClearFullModelData( operations ); + } + else + { + // Clear the model data related with characters. + ClearCharacterModelData( startIndex, endIndex, operations ); + + // Clear the model data related with glyphs. + ClearGlyphModelData( startIndex, endIndex, operations ); + } + + // The estimated number of lines. Used to avoid reallocations when layouting. + mTextUpdateInfo.mEstimatedNumberOfLines = std::max( mVisualModel->mLines.Count(), mLogicalModel->mParagraphInfo.Count() ); + + mVisualModel->ClearCaches(); +} + +bool Controller::Impl::UpdateModel( OperationsMask operationsRequired ) +{ + DALI_LOG_INFO( gLogFilter, Debug::General, "Controller::UpdateModel\n" ); + // Calculate the operations to be done. const OperationsMask operations = static_cast( mOperationsPending & operationsRequired ); + if( NO_OPERATION == operations ) + { + // Nothing to do if no operations are pending and required. + return false; + } + Vector& utf32Characters = mLogicalModel->mText; const Length numberOfCharacters = utf32Characters.Count(); + // Index to the first character of the first paragraph to be updated. + CharacterIndex startIndex = 0u; + // Number of characters of the paragraphs to be removed. + Length paragraphCharacters = 0u; + + CalculateTextUpdateIndices( paragraphCharacters ); + startIndex = mTextUpdateInfo.mParagraphCharacterIndex; + + if( mTextUpdateInfo.mClearAll || + ( 0u != paragraphCharacters ) ) + { + ClearModelData( startIndex, startIndex + ( ( paragraphCharacters > 0u ) ? paragraphCharacters - 1u : 0u ), operations ); + } + + mTextUpdateInfo.mClearAll = false; + + // Whether the model is updated. + bool updated = false; + Vector& lineBreakInfo = mLogicalModel->mLineBreakInfo; - if( GET_LINE_BREAKS & operations ) + const Length requestedNumberOfCharacters = mTextUpdateInfo.mRequestedNumberOfCharacters; + + if( NO_OPERATION != ( GET_LINE_BREAKS & operations ) ) { // Retrieves the line break info. The line break info is used to split the text in 'paragraphs' to // calculate the bidirectional info for each 'paragraph'. @@ -296,21 +675,31 @@ void Controller::Impl::UpdateModel( OperationsMask operationsRequired ) lineBreakInfo.Resize( numberOfCharacters, TextAbstraction::LINE_NO_BREAK ); SetLineBreakInfo( utf32Characters, + startIndex, + requestedNumberOfCharacters, lineBreakInfo ); + + // Create the paragraph info. + mLogicalModel->CreateParagraphInfo( startIndex, + requestedNumberOfCharacters ); + updated = true; } Vector& wordBreakInfo = mLogicalModel->mWordBreakInfo; - if( GET_WORD_BREAKS & operations ) + if( NO_OPERATION != ( GET_WORD_BREAKS & operations ) ) { // Retrieves the word break info. The word break info is used to layout the text (where to wrap the text in lines). wordBreakInfo.Resize( numberOfCharacters, TextAbstraction::WORD_NO_BREAK ); SetWordBreakInfo( utf32Characters, + startIndex, + requestedNumberOfCharacters, wordBreakInfo ); + updated = true; } - const bool getScripts = GET_SCRIPTS & operations; - const bool validateFonts = VALIDATE_FONTS & operations; + const bool getScripts = NO_OPERATION != ( GET_SCRIPTS & operations ); + const bool validateFonts = NO_OPERATION != ( VALIDATE_FONTS & operations ); Vector& scripts = mLogicalModel->mScriptRuns; Vector& validFonts = mLogicalModel->mFontRuns; @@ -325,43 +714,37 @@ void Controller::Impl::UpdateModel( OperationsMask operationsRequired ) { // Retrieves the scripts used in the text. multilanguageSupport.SetScripts( utf32Characters, + startIndex, + requestedNumberOfCharacters, scripts ); } if( validateFonts ) { - if( 0u == validFonts.Count() ) - { - // Copy the requested font defaults received via the property system. - // These may not be valid i.e. may not contain glyphs for the necessary scripts. - GetDefaultFonts( validFonts, numberOfCharacters ); - } + // Validate the fonts set through the mark-up string. + Vector& fontDescriptionRuns = mLogicalModel->mFontDescriptionRuns; + + // Get the default font id. + const FontId defaultFontId = ( NULL == mFontDefaults ) ? 0u : mFontDefaults->GetFontId( mFontClient ); // Validates the fonts. If there is a character with no assigned font it sets a default one. // After this call, fonts are validated. multilanguageSupport.ValidateFonts( utf32Characters, scripts, + fontDescriptionRuns, + defaultFontId, + startIndex, + requestedNumberOfCharacters, validFonts ); } + updated = true; } Vector mirroredUtf32Characters; bool textMirrored = false; - Length numberOfParagraphs = 0u; - if( BIDI_INFO & operations ) + const Length numberOfParagraphs = mLogicalModel->mParagraphInfo.Count(); + if( NO_OPERATION != ( BIDI_INFO & operations ) ) { - // Count the number of LINE_NO_BREAK to reserve some space for the vector of paragraph's - // bidirectional info. - - const TextAbstraction::LineBreakInfo* lineBreakInfoBuffer = lineBreakInfo.Begin(); - for( Length index = 0u; index < numberOfCharacters; ++index ) - { - if( TextAbstraction::LINE_NO_BREAK == *( lineBreakInfoBuffer + index ) ) - { - ++numberOfParagraphs; - } - } - Vector& bidirectionalInfo = mLogicalModel->mBidirectionalParagraphInfo; bidirectionalInfo.Reserve( numberOfParagraphs ); @@ -369,29 +752,36 @@ void Controller::Impl::UpdateModel( OperationsMask operationsRequired ) SetBidirectionalInfo( utf32Characters, scripts, lineBreakInfo, + startIndex, + requestedNumberOfCharacters, bidirectionalInfo ); if( 0u != bidirectionalInfo.Count() ) { - // This paragraph has right to left text. Some characters may need to be mirrored. - // TODO: consider if the mirrored string can be stored as well. - - textMirrored = GetMirroredText( utf32Characters, - mirroredUtf32Characters, - bidirectionalInfo ); - // Only set the character directions if there is right to left characters. Vector& directions = mLogicalModel->mCharacterDirections; - directions.Resize( numberOfCharacters ); - GetCharactersDirection( bidirectionalInfo, + numberOfCharacters, + startIndex, + requestedNumberOfCharacters, directions ); + + // This paragraph has right to left text. Some characters may need to be mirrored. + // TODO: consider if the mirrored string can be stored as well. + + textMirrored = GetMirroredText( utf32Characters, + directions, + bidirectionalInfo, + startIndex, + requestedNumberOfCharacters, + mirroredUtf32Characters ); } else { // There is no right to left characters. Clear the directions vector. mLogicalModel->mCharacterDirections.Clear(); } + updated = true; } Vector& glyphs = mVisualModel->mGlyphs; @@ -400,7 +790,8 @@ void Controller::Impl::UpdateModel( OperationsMask operationsRequired ) Vector newParagraphGlyphs; newParagraphGlyphs.Reserve( numberOfParagraphs ); - if( SHAPE_TEXT & operations ) + const Length currentNumberOfGlyphs = glyphs.Count(); + if( NO_OPERATION != ( SHAPE_TEXT & operations ) ) { const Vector& textToShape = textMirrored ? mirroredUtf32Characters : utf32Characters; // Shapes the text. @@ -408,22 +799,26 @@ void Controller::Impl::UpdateModel( OperationsMask operationsRequired ) lineBreakInfo, scripts, validFonts, + startIndex, + mTextUpdateInfo.mStartGlyphIndex, + requestedNumberOfCharacters, glyphs, glyphsToCharactersMap, charactersPerGlyph, newParagraphGlyphs ); // Create the 'number of glyphs' per character and the glyph to character conversion tables. - mVisualModel->CreateGlyphsPerCharacterTable( numberOfCharacters ); - mVisualModel->CreateCharacterToGlyphTable( numberOfCharacters ); + mVisualModel->CreateGlyphsPerCharacterTable( startIndex, mTextUpdateInfo.mStartGlyphIndex, requestedNumberOfCharacters ); + mVisualModel->CreateCharacterToGlyphTable( startIndex, mTextUpdateInfo.mStartGlyphIndex, requestedNumberOfCharacters ); + updated = true; } - const Length numberOfGlyphs = glyphs.Count(); + const Length numberOfGlyphs = glyphs.Count() - currentNumberOfGlyphs; - if( GET_GLYPH_METRICS & operations ) + if( NO_OPERATION != ( GET_GLYPH_METRICS & operations ) ) { GlyphInfo* glyphsBuffer = glyphs.Begin(); - mFontClient.GetGlyphMetrics( glyphsBuffer, numberOfGlyphs ); + mMetrics->GetGlyphMetrics( glyphsBuffer + mTextUpdateInfo.mStartGlyphIndex, numberOfGlyphs ); // Update the width and advance of all new paragraph characters. for( Vector::ConstIterator it = newParagraphGlyphs.Begin(), endIt = newParagraphGlyphs.End(); it != endIt; ++it ) @@ -435,20 +830,104 @@ void Controller::Impl::UpdateModel( OperationsMask operationsRequired ) glyph.width = 0.f; glyph.advance = 0.f; } + updated = true; + } + + if( NO_OPERATION != ( COLOR & operations ) ) + { + // Set the color runs in glyphs. + SetColorSegmentationInfo( mLogicalModel->mColorRuns, + mVisualModel->mCharactersToGlyph, + mVisualModel->mGlyphsPerCharacter, + startIndex, + mTextUpdateInfo.mStartGlyphIndex, + requestedNumberOfCharacters, + mVisualModel->mColors, + mVisualModel->mColorIndices ); + + updated = true; + } + + if( ( NULL != mEventData ) && + mEventData->mPreEditFlag && + ( 0u != mVisualModel->mCharactersToGlyph.Count() ) ) + { + // Add the underline for the pre-edit text. + const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin(); + const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin(); + + const GlyphIndex glyphStart = *( charactersToGlyphBuffer + mEventData->mPreEditStartPosition ); + const CharacterIndex lastPreEditCharacter = mEventData->mPreEditStartPosition + ( ( mEventData->mPreEditLength > 0u ) ? mEventData->mPreEditLength - 1u : 0u ); + const Length numberOfGlyphsLastCharacter = *( glyphsPerCharacterBuffer + lastPreEditCharacter ); + const GlyphIndex glyphEnd = *( charactersToGlyphBuffer + lastPreEditCharacter ) + ( numberOfGlyphsLastCharacter > 1u ? numberOfGlyphsLastCharacter - 1u : 0u ); + + GlyphRun underlineRun; + underlineRun.glyphIndex = glyphStart; + underlineRun.numberOfGlyphs = 1u + glyphEnd - glyphStart; + + // TODO: At the moment the underline runs are only for pre-edit. + mVisualModel->mUnderlineRuns.PushBack( underlineRun ); } + + // The estimated number of lines. Used to avoid reallocations when layouting. + mTextUpdateInfo.mEstimatedNumberOfLines = std::max( mVisualModel->mLines.Count(), mLogicalModel->mParagraphInfo.Count() ); + + // Set the previous number of characters for the next time the text is updated. + mTextUpdateInfo.mPreviousNumberOfCharacters = numberOfCharacters; + + return updated; } -void Controller::Impl::GetDefaultFonts( Vector& fonts, Length numberOfCharacters ) +void Controller::Impl::RetrieveDefaultInputStyle( InputStyle& inputStyle ) { + // Sets the default text's color. + inputStyle.textColor = mTextColor; + inputStyle.isDefaultColor = true; + + inputStyle.familyName.clear(); + inputStyle.weight = TextAbstraction::FontWeight::NORMAL; + inputStyle.width = TextAbstraction::FontWidth::NORMAL; + inputStyle.slant = TextAbstraction::FontSlant::NORMAL; + inputStyle.size = 0.f; + + inputStyle.familyDefined = false; + inputStyle.weightDefined = false; + inputStyle.widthDefined = false; + inputStyle.slantDefined = false; + inputStyle.sizeDefined = false; + + // Sets the default font's family name, weight, width, slant and size. if( mFontDefaults ) { - FontRun fontRun; - fontRun.characterRun.characterIndex = 0; - fontRun.characterRun.numberOfCharacters = numberOfCharacters; - fontRun.fontId = mFontDefaults->GetFontId( mFontClient ); - fontRun.isDefault = true; + if( mFontDefaults->familyDefined ) + { + inputStyle.familyName = mFontDefaults->mFontDescription.family; + inputStyle.familyDefined = true; + } - fonts.PushBack( fontRun ); + if( mFontDefaults->weightDefined ) + { + inputStyle.weight = mFontDefaults->mFontDescription.weight; + inputStyle.weightDefined = true; + } + + if( mFontDefaults->widthDefined ) + { + inputStyle.width = mFontDefaults->mFontDescription.width; + inputStyle.widthDefined = true; + } + + if( mFontDefaults->slantDefined ) + { + inputStyle.slant = mFontDefaults->mFontDescription.slant; + inputStyle.slantDefined = true; + } + + if( mFontDefaults->sizeDefined ) + { + inputStyle.size = mFontDefaults->mDefaultPointSize; + inputStyle.sizeDefined = true; + } } } @@ -457,8 +936,8 @@ float Controller::Impl::GetDefaultFontLineHeight() FontId defaultFontId = 0u; if( NULL == mFontDefaults ) { - defaultFontId = mFontClient.GetFontId( EMPTY_STRING, - EMPTY_STRING ); + TextAbstraction::FontDescription fontDescription; + defaultFontId = mFontClient.GetFontId( fontDescription ); } else { @@ -466,7 +945,7 @@ float Controller::Impl::GetDefaultFontLineHeight() } Text::FontMetrics fontMetrics; - mFontClient.GetFontMetrics( defaultFontId, fontMetrics ); + mMetrics->GetFontMetrics( defaultFontId, fontMetrics ); return( fontMetrics.ascender - fontMetrics.descender ); } @@ -505,6 +984,7 @@ void Controller::Impl::OnCursorKeyEvent( const Event& event ) } mEventData->mUpdateCursorPosition = true; + mEventData->mUpdateInputStyle = true; mEventData->mScrollAfterUpdatePosition = true; } @@ -516,13 +996,16 @@ void Controller::Impl::OnTapEvent( const Event& event ) if( 1u == tapCount ) { - if( ! IsShowingPlaceholderText() ) + if( IsShowingRealText() ) { const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x; const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y; mEventData->mPrimaryCursorPosition = GetClosestCursorIndex( xPosition, yPosition ); + + // When the cursor position is changing, delay cursor blinking + mEventData->mDecorator->DelayCursorBlink(); } else { @@ -531,6 +1014,14 @@ void Controller::Impl::OnTapEvent( const Event& event ) mEventData->mUpdateCursorPosition = true; mEventData->mScrollAfterUpdatePosition = true; + mEventData->mUpdateInputStyle = true; + + // Notify the cursor position to the imf manager. + if( mEventData->mImfManager ) + { + mEventData->mImfManager.SetCursorPosition( mEventData->mPrimaryCursorPosition ); + mEventData->mImfManager.NotifyCursorPosition(); + } } } } @@ -548,7 +1039,7 @@ void Controller::Impl::OnPanEvent( const Event& event ) if( Gesture::Started == state || Gesture::Continuing == state ) { - const Vector2& actualSize = mVisualModel->GetActualSize(); + const Vector2& actualSize = mVisualModel->GetLayoutSize(); const Vector2 currentScroll = mEventData->mScrollPosition; if( mEventData->mHorizontalScrollingEnabled ) @@ -576,7 +1067,9 @@ void Controller::Impl::OnPanEvent( const Event& event ) void Controller::Impl::OnLongPressEvent( const Event& event ) { - if ( EventData::EDITING == mEventData->mState ) + DALI_LOG_INFO( gLogFilter, Debug::General, "Controller::OnLongPressEvent\n" ); + + if( EventData::EDITING == mEventData->mState ) { ChangeState ( EventData::EDITING_WITH_POPUP ); mEventData->mDecoratorUpdated = true; @@ -621,9 +1114,6 @@ void Controller::Impl::OnHandleEvent( const Event& event ) { mEventData->mLeftSelectionPosition = handleNewPosition; - RepositionSelectionHandles( mEventData->mLeftSelectionPosition, - mEventData->mRightSelectionPosition ); - mEventData->mUpdateLeftSelectionPosition = true; } } @@ -636,9 +1126,6 @@ void Controller::Impl::OnHandleEvent( const Event& event ) { mEventData->mRightSelectionPosition = handleNewPosition; - RepositionSelectionHandles( mEventData->mLeftSelectionPosition, - mEventData->mRightSelectionPosition ); - mEventData->mUpdateRightSelectionPosition = true; } } @@ -659,8 +1146,12 @@ void Controller::Impl::OnHandleEvent( const Event& event ) if( Event::GRAB_HANDLE_EVENT == event.type ) { mEventData->mUpdateCursorPosition = true; + mEventData->mUpdateInputStyle = true; - ChangeState( EventData::EDITING_WITH_POPUP ); + if( !IsClipboardEmpty() ) + { + ChangeState( EventData::EDITING_WITH_PASTE_POPUP ); // Moving grabhandle will show Paste Popup + } if( handleStopScrolling ) { @@ -680,9 +1171,6 @@ void Controller::Impl::OnHandleEvent( const Event& event ) if( mEventData->mUpdateLeftSelectionPosition ) { mEventData->mLeftSelectionPosition = handlePosition; - - RepositionSelectionHandles( mEventData->mLeftSelectionPosition, - mEventData->mRightSelectionPosition ); } } } @@ -697,8 +1185,6 @@ void Controller::Impl::OnHandleEvent( const Event& event ) if( mEventData->mUpdateRightSelectionPosition ) { mEventData->mRightSelectionPosition = handlePosition; - RepositionSelectionHandles( mEventData->mLeftSelectionPosition, - mEventData->mRightSelectionPosition ); } } } @@ -708,7 +1194,7 @@ void Controller::Impl::OnHandleEvent( const Event& event ) else if( HANDLE_SCROLLING == state ) { const float xSpeed = event.p2.mFloat; - const Vector2& actualSize = mVisualModel->GetActualSize(); + const Vector2& actualSize = mVisualModel->GetLayoutSize(); const Vector2 currentScrollPosition = mEventData->mScrollPosition; mEventData->mScrollPosition.x += xSpeed; @@ -747,6 +1233,7 @@ void Controller::Impl::OnHandleEvent( const Event& event ) mEventData->mUpdateCursorPosition = mEventData->mPrimaryCursorPosition != handlePosition; mEventData->mScrollAfterUpdatePosition = mEventData->mUpdateCursorPosition; mEventData->mPrimaryCursorPosition = handlePosition; + mEventData->mUpdateInputStyle = mEventData->mUpdateCursorPosition; } else if( leftSelectionHandleEvent || rightSelectionHandleEvent ) { @@ -786,8 +1273,7 @@ void Controller::Impl::OnHandleEvent( const Event& event ) if( mEventData->mUpdateLeftSelectionPosition || mEventData->mUpdateRightSelectionPosition ) { - RepositionSelectionHandles( mEventData->mLeftSelectionPosition, - mEventData->mRightSelectionPosition ); + RepositionSelectionHandles(); mEventData->mScrollAfterUpdatePosition = true; } @@ -810,22 +1296,21 @@ void Controller::Impl::OnSelectEvent( const Event& event ) const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x; const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y; - const CharacterIndex leftPosition = mEventData->mLeftSelectionPosition; - const CharacterIndex rightPosition = mEventData->mRightSelectionPosition; - + // Calculates the logical position from the x,y coords. RepositionSelectionHandles( xPosition, yPosition ); - mEventData->mUpdateLeftSelectionPosition = leftPosition != mEventData->mLeftSelectionPosition; - mEventData->mUpdateRightSelectionPosition = rightPosition != mEventData->mRightSelectionPosition; + mEventData->mUpdateLeftSelectionPosition = true; + mEventData->mUpdateRightSelectionPosition = true; - mEventData->mScrollAfterUpdatePosition = ( ( mEventData->mUpdateLeftSelectionPosition || mEventData->mUpdateRightSelectionPosition ) && - ( mEventData->mLeftSelectionPosition != mEventData->mRightSelectionPosition ) ); + mEventData->mScrollAfterUpdatePosition = ( mEventData->mLeftSelectionPosition != mEventData->mRightSelectionPosition ); } } void Controller::Impl::OnSelectAllEvent() { + DALI_LOG_INFO( gLogFilter, Debug::Verbose, "OnSelectAllEvent mEventData->mSelectionEnabled%s \n", mEventData->mSelectionEnabled?"true":"false"); + if( NULL == mEventData ) { // Nothing to do if there is no text. @@ -834,8 +1319,8 @@ void Controller::Impl::OnSelectAllEvent() if( mEventData->mSelectionEnabled ) { - RepositionSelectionHandles( 0u, - mLogicalModel->mText.Count() ); + mEventData->mLeftSelectionPosition = 0u; + mEventData->mRightSelectionPosition = mLogicalModel->mText.Count(); mEventData->mScrollAfterUpdatePosition = true; mEventData->mUpdateLeftSelectionPosition = true; @@ -843,45 +1328,61 @@ void Controller::Impl::OnSelectAllEvent() } } -void Controller::Impl::RetrieveSelection( std::string& selectedText, bool deleteAfterRetreival ) +void Controller::Impl::RetrieveSelection( std::string& selectedText, bool deleteAfterRetrieval ) { - if( mEventData->mLeftSelectionPosition == mEventData->mRightSelectionPosition ) + if( mEventData->mLeftSelectionPosition == mEventData->mRightSelectionPosition ) { // Nothing to select if handles are in the same place. - selectedText=""; + selectedText.clear(); return; } + const bool handlesCrossed = mEventData->mLeftSelectionPosition > mEventData->mRightSelectionPosition; + //Get start and end position of selection - uint32_t startOfSelectedText = mEventData->mLeftSelectionPosition; - uint32_t lengthOfSelectedText = mEventData->mRightSelectionPosition - startOfSelectedText; + const CharacterIndex startOfSelectedText = handlesCrossed ? mEventData->mRightSelectionPosition : mEventData->mLeftSelectionPosition; + const Length lengthOfSelectedText = ( handlesCrossed ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition ) - startOfSelectedText; + + Vector& utf32Characters = mLogicalModel->mText; + const Length numberOfCharacters = utf32Characters.Count(); // Validate the start and end selection points - if( ( startOfSelectedText >= 0 ) && ( ( startOfSelectedText + lengthOfSelectedText ) <= mLogicalModel->mText.Count() ) ) + if( ( startOfSelectedText + lengthOfSelectedText ) <= numberOfCharacters ) { //Get text as a UTF8 string - Vector& utf32Characters = mLogicalModel->mText; - Utf32ToUtf8( &utf32Characters[startOfSelectedText], lengthOfSelectedText, selectedText ); - if ( deleteAfterRetreival ) // Only delete text if copied successfully + if( deleteAfterRetrieval ) // Only delete text if copied successfully { - // Delete text between handles - Vector& currentText = mLogicalModel->mText; + // Set as input style the style of the first deleted character. + mLogicalModel->RetrieveStyle( startOfSelectedText, mEventData->mInputStyle ); + + mLogicalModel->UpdateTextStyleRuns( startOfSelectedText, -static_cast( lengthOfSelectedText ) ); + + // Mark the paragraphs to be updated. + mTextUpdateInfo.mCharacterIndex = startOfSelectedText; + mTextUpdateInfo.mNumberOfCharactersToRemove = lengthOfSelectedText; - Vector::Iterator first = currentText.Begin() + startOfSelectedText; + // Delete text between handles + Vector::Iterator first = utf32Characters.Begin() + startOfSelectedText; Vector::Iterator last = first + lengthOfSelectedText; - currentText.Erase( first, last ); + utf32Characters.Erase( first, last ); + + // Scroll after delete. + mEventData->mPrimaryCursorPosition = handlesCrossed ? mEventData->mRightSelectionPosition : mEventData->mLeftSelectionPosition; + mEventData->mScrollAfterDelete = true; } - mEventData->mPrimaryCursorPosition = mEventData->mLeftSelectionPosition; - mEventData->mScrollAfterDelete = true; + // Udpade the cursor position and the decorator. + // Scroll after the position is updated if is not scrolling after delete. + mEventData->mUpdateCursorPosition = true; + mEventData->mScrollAfterUpdatePosition = !mEventData->mScrollAfterDelete; mEventData->mDecoratorUpdated = true; } } void Controller::Impl::ShowClipboard() { - if ( mClipboard ) + if( mClipboard ) { mClipboard.ShowClipboard(); } @@ -889,12 +1390,17 @@ void Controller::Impl::ShowClipboard() void Controller::Impl::HideClipboard() { - if ( mClipboard ) + if( mClipboard && mClipboardHideEnabled ) { mClipboard.HideClipboard(); } } +void Controller::Impl::SetClipboardHideEnable(bool enable) +{ + mClipboardHideEnabled = enable; +} + bool Controller::Impl::CopyStringToClipboard( std::string& source ) { //Send string to clipboard @@ -909,16 +1415,19 @@ void Controller::Impl::SendSelectionToClipboard( bool deleteAfterSending ) ChangeState( EventData::EDITING ); } -void Controller::Impl::GetTextFromClipboard( unsigned int itemIndex, std::string& retreivedString ) +void Controller::Impl::GetTextFromClipboard( unsigned int itemIndex, std::string& retrievedString ) { if ( mClipboard ) { - retreivedString = mClipboard.GetItem( itemIndex ); + retrievedString = mClipboard.GetItem( itemIndex ); } } -void Controller::Impl::RepositionSelectionHandles( CharacterIndex selectionStart, CharacterIndex selectionEnd ) +void Controller::Impl::RepositionSelectionHandles() { + CharacterIndex selectionStart = mEventData->mLeftSelectionPosition; + CharacterIndex selectionEnd = mEventData->mRightSelectionPosition; + if( selectionStart == selectionEnd ) { // Nothing to select if handles are in the same place. @@ -927,9 +1436,6 @@ void Controller::Impl::RepositionSelectionHandles( CharacterIndex selectionStart mEventData->mDecorator->ClearHighlights(); - mEventData->mLeftSelectionPosition = selectionStart; - mEventData->mRightSelectionPosition = selectionEnd; - const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin(); const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin(); const GlyphInfo* const glyphsBuffer = mVisualModel->mGlyphs.Begin(); @@ -946,8 +1452,16 @@ void Controller::Impl::RepositionSelectionHandles( CharacterIndex selectionStart const LineRun& firstLine = *lines.Begin(); const float height = firstLine.ascender + -firstLine.descender; + const bool isLastCharacter = selectionEnd >= mLogicalModel->mText.Count(); + const bool startDirection = ( ( NULL == modelCharacterDirectionsBuffer ) ? false : *( modelCharacterDirectionsBuffer + selectionStart ) ); + const bool endDirection = ( ( NULL == modelCharacterDirectionsBuffer ) ? false : *( modelCharacterDirectionsBuffer + ( selectionEnd - ( isLastCharacter ? 1u : 0u ) ) ) ); + // Swap the indices if the start is greater than the end. - const bool indicesSwapped = ( selectionStart > selectionEnd ); + const bool indicesSwapped = selectionStart > selectionEnd; + + // Tell the decorator to flip the selection handles if needed. + mEventData->mDecorator->SetSelectionHandleFlipState( indicesSwapped, startDirection, endDirection ); + if( indicesSwapped ) { std::swap( selectionStart, selectionEnd ); @@ -967,9 +1481,6 @@ void Controller::Impl::RepositionSelectionHandles( CharacterIndex selectionStart const Length numberOfCharactersEnd = *( charactersPerGlyphBuffer + glyphEnd ); bool splitEndGlyph = ( glyphStart != glyphEnd ) && ( numberOfCharactersEnd > 1u ) && HasLigatureMustBreak( mLogicalModel->GetScript( selectionEndMinusOne ) ); - // Tell the decorator to swap the selection handles if needed. - mEventData->mDecorator->SwapSelectionHandlesEnabled( firstLine.direction != indicesSwapped ); - const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset; // Traverse the glyphs. @@ -1049,9 +1560,18 @@ void Controller::Impl::RepositionSelectionHandles( CharacterIndex selectionStart const Vector2 primaryPosition = primaryCursorInfo.primaryPosition + offset; const Vector2 secondaryPosition = secondaryCursorInfo.primaryPosition + offset; - mEventData->mDecorator->SetPosition( LEFT_SELECTION_HANDLE, primaryPosition.x, primaryPosition.y, primaryCursorInfo.lineHeight ); + mEventData->mDecorator->SetPosition( LEFT_SELECTION_HANDLE, + primaryPosition.x, + primaryCursorInfo.lineOffset + offset.y, + primaryCursorInfo.lineHeight ); - mEventData->mDecorator->SetPosition( RIGHT_SELECTION_HANDLE, secondaryPosition.x, secondaryPosition.y, secondaryCursorInfo.lineHeight ); + mEventData->mDecorator->SetPosition( RIGHT_SELECTION_HANDLE, + secondaryPosition.x, + secondaryCursorInfo.lineOffset + offset.y, + secondaryCursorInfo.lineHeight ); + + // Cursor to be positioned at end of selection so if selection interrupted and edit mode restarted the cursor will be at end of selection + mEventData->mPrimaryCursorPosition = ( indicesSwapped ) ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition; // Set the flag to update the decorator. mEventData->mDecoratorUpdated = true; @@ -1073,8 +1593,8 @@ void Controller::Impl::RepositionSelectionHandles( float visualX, float visualY const Length numberOfGlyphs = mVisualModel->mGlyphs.Count(); const Length numberOfLines = mVisualModel->mLines.Count(); - if( 0 == numberOfGlyphs || - 0 == numberOfLines ) + if( ( 0 == numberOfGlyphs ) || + ( 0 == numberOfLines ) ) { // Nothing to do if there is no text. return; @@ -1093,7 +1613,8 @@ void Controller::Impl::RepositionSelectionHandles( float visualX, float visualY return; } - RepositionSelectionHandles( selectionStart, selectionEnd ); + mEventData->mLeftSelectionPosition = selectionStart; + mEventData->mRightSelectionPosition = selectionEnd; } void Controller::Impl::SetPopupButtons() @@ -1108,28 +1629,36 @@ void Controller::Impl::SetPopupButtons() TextSelectionPopup::Buttons buttonsToShow = TextSelectionPopup::NONE; - if ( ( EventData::SELECTING == mEventData->mState ) || ( EventData::SELECTION_CHANGED == mEventData->mState ) ) + if( EventData::SELECTING == mEventData->mState ) { buttonsToShow = TextSelectionPopup::Buttons( TextSelectionPopup::CUT | TextSelectionPopup::COPY ); - if ( !IsClipboardEmpty() ) + if( !IsClipboardEmpty() ) { buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) ); buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::CLIPBOARD ) ); } - if ( !mEventData->mAllTextSelected ) + if( !mEventData->mAllTextSelected ) { buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::SELECT_ALL ) ); } } - else if ( EventData::EDITING_WITH_POPUP == mEventData->mState ) + else if( EventData::EDITING_WITH_POPUP == mEventData->mState ) { - if ( mLogicalModel->mText.Count() && !IsShowingPlaceholderText()) + if( mLogicalModel->mText.Count() && !IsShowingPlaceholderText() ) { buttonsToShow = TextSelectionPopup::Buttons( TextSelectionPopup::SELECT | TextSelectionPopup::SELECT_ALL ); } + if( !IsClipboardEmpty() ) + { + buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) ); + buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::CLIPBOARD ) ); + } + } + else if( EventData::EDITING_WITH_PASTE_POPUP == mEventData->mState ) + { if ( !IsClipboardEmpty() ) { buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) ); @@ -1148,6 +1677,8 @@ void Controller::Impl::ChangeState( EventData::State newState ) return; } + DALI_LOG_INFO( gLogFilter, Debug::General, "ChangeState state:%d newstate:%d\n", mEventData->mState, newState ); + if( mEventData->mState != newState ) { mEventData->mState = newState; @@ -1163,7 +1694,7 @@ void Controller::Impl::ChangeState( EventData::State newState ) mEventData->mDecoratorUpdated = true; HideClipboard(); } - else if ( EventData::INTERRUPTED == mEventData->mState) + else if( EventData::INTERRUPTED == mEventData->mState) { mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false ); mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false ); @@ -1172,7 +1703,7 @@ void Controller::Impl::ChangeState( EventData::State newState ) mEventData->mDecoratorUpdated = true; HideClipboard(); } - else if ( EventData::SELECTING == mEventData->mState ) + else if( EventData::SELECTING == mEventData->mState ) { mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE ); mEventData->mDecorator->StopCursorBlink(); @@ -1186,16 +1717,6 @@ void Controller::Impl::ChangeState( EventData::State newState ) } mEventData->mDecoratorUpdated = true; } - else if ( EventData::SELECTION_CHANGED == mEventData->mState ) - { - if( mEventData->mGrabHandlePopupEnabled ) - { - SetPopupButtons(); - mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE ); - mEventData->mDecorator->SetPopupActive( true ); - } - mEventData->mDecoratorUpdated = true; - } else if( EventData::EDITING == mEventData->mState ) { mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY ); @@ -1216,6 +1737,8 @@ void Controller::Impl::ChangeState( EventData::State newState ) } else if( EventData::EDITING_WITH_POPUP == mEventData->mState ) { + DALI_LOG_INFO( gLogFilter, Debug::Verbose, "EDITING_WITH_POPUP \n", newState ); + mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY ); if( mEventData->mCursorBlinkEnabled ) { @@ -1240,6 +1763,8 @@ void Controller::Impl::ChangeState( EventData::State newState ) } else if( EventData::EDITING_WITH_GRAB_HANDLE == mEventData->mState ) { + DALI_LOG_INFO( gLogFilter, Debug::Verbose, "EDITING_WITH_GRAB_HANDLE \n", newState ); + mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY ); if( mEventData->mCursorBlinkEnabled ) { @@ -1256,7 +1781,7 @@ void Controller::Impl::ChangeState( EventData::State newState ) mEventData->mDecoratorUpdated = true; HideClipboard(); } - else if ( EventData::SELECTION_HANDLE_PANNING == mEventData->mState ) + else if( EventData::SELECTION_HANDLE_PANNING == mEventData->mState ) { mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE ); mEventData->mDecorator->StopCursorBlink(); @@ -1269,8 +1794,10 @@ void Controller::Impl::ChangeState( EventData::State newState ) } mEventData->mDecoratorUpdated = true; } - else if ( EventData::GRAB_HANDLE_PANNING == mEventData->mState ) + else if( EventData::GRAB_HANDLE_PANNING == mEventData->mState ) { + DALI_LOG_INFO( gLogFilter, Debug::Verbose, "GRAB_HANDLE_PANNING \n", newState ); + mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY ); if( mEventData->mCursorBlinkEnabled ) { @@ -1285,6 +1812,28 @@ void Controller::Impl::ChangeState( EventData::State newState ) } mEventData->mDecoratorUpdated = true; } + else if( EventData::EDITING_WITH_PASTE_POPUP == mEventData->mState ) + { + DALI_LOG_INFO( gLogFilter, Debug::Verbose, "EDITING_WITH_PASTE_POPUP \n", newState ); + + mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY ); + if( mEventData->mCursorBlinkEnabled ) + { + mEventData->mDecorator->StartCursorBlink(); + } + + mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true ); + mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false ); + mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false ); + + if( mEventData->mGrabHandlePopupEnabled ) + { + SetPopupButtons(); + mEventData->mDecorator->SetPopupActive( true ); + } + HideClipboard(); + mEventData->mDecoratorUpdated = true; + } } } @@ -1317,34 +1866,45 @@ LineIndex Controller::Impl::GetClosestLine( float y ) const void Controller::Impl::FindSelectionIndices( float visualX, float visualY, CharacterIndex& startIndex, CharacterIndex& endIndex ) { CharacterIndex hitCharacter = GetClosestCursorIndex( visualX, visualY ); + DALI_ASSERT_DEBUG( hitCharacter <= mLogicalModel->mText.Count() && "GetClosestCursorIndex returned out of bounds index" ); + + if( mLogicalModel->mText.Count() == 0 ) + { + return; // if model empty + } + if( hitCharacter >= mLogicalModel->mText.Count() ) { - // Selection out of bounds. - return; + // Closest hit character is the last character. + if( hitCharacter == mLogicalModel->mText.Count() ) + { + hitCharacter--; //Hit character index set to last character in logical model + } + else + { + // hitCharacter is out of bounds + return; + } } startIndex = hitCharacter; endIndex = hitCharacter; + bool isHitCharacterWhitespace = TextAbstraction::IsWhiteSpace( mLogicalModel->mText[hitCharacter] ); - if( !TextAbstraction::IsWhiteSpace( mLogicalModel->mText[hitCharacter] ) ) + // Find the start and end of the text + for( startIndex = hitCharacter; startIndex > 0; --startIndex ) { - // Find the start and end of the text - for( startIndex = hitCharacter; startIndex > 0; --startIndex ) + if( isHitCharacterWhitespace != TextAbstraction::IsWhiteSpace( mLogicalModel->mText[ startIndex-1 ] ) ) { - Character charCode = mLogicalModel->mText[ startIndex-1 ]; - if( TextAbstraction::IsWhiteSpace( charCode ) ) - { - break; - } + break; } - const CharacterIndex pastTheEnd = mLogicalModel->mText.Count(); - for( endIndex = hitCharacter + 1u; endIndex < pastTheEnd; ++endIndex ) + } + const CharacterIndex pastTheEnd = mLogicalModel->mText.Count(); + for( endIndex = hitCharacter + 1u; endIndex < pastTheEnd; ++endIndex ) + { + if( isHitCharacterWhitespace != TextAbstraction::IsWhiteSpace( mLogicalModel->mText[ endIndex ] ) ) { - Character charCode = mLogicalModel->mText[ endIndex ]; - if( TextAbstraction::IsWhiteSpace( charCode ) ) - { - break; - } + break; } } } @@ -1352,6 +1912,8 @@ void Controller::Impl::FindSelectionIndices( float visualX, float visualY, Chara CharacterIndex Controller::Impl::GetClosestCursorIndex( float visualX, float visualY ) { + DALI_LOG_INFO( gLogFilter, Debug::Verbose, "GetClosestCursorIndex %p closest visualX %f visualY %f\n", this, visualX, visualY ); + if( NULL == mEventData ) { // Nothing to do if there is no text input. @@ -1362,8 +1924,8 @@ CharacterIndex Controller::Impl::GetClosestCursorIndex( float visualX, const Length numberOfGlyphs = mVisualModel->mGlyphs.Count(); const Length numberOfLines = mVisualModel->mLines.Count(); - if( 0 == numberOfGlyphs || - 0 == numberOfLines ) + if( ( 0 == numberOfGlyphs ) || + ( 0 == numberOfLines ) ) { return logicalIndex; } @@ -1376,19 +1938,14 @@ CharacterIndex Controller::Impl::GetClosestCursorIndex( float visualX, const Vector& positions = mVisualModel->mGlyphPositions; const Vector2* const positionsBuffer = positions.Begin(); - // Get the visual to logical conversion tables. - const CharacterIndex* const visualToLogicalBuffer = ( 0u != mLogicalModel->mVisualToLogicalMap.Count() ) ? mLogicalModel->mVisualToLogicalMap.Begin() : NULL; - const CharacterIndex* const visualToLogicalCursorBuffer = mLogicalModel->mVisualToLogicalCursorMap.Begin(); - // Get the character to glyph conversion table. const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin(); // Get the glyphs per character table. const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin(); - const Length* const charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin(); - // If the vector is void, there is no right to left characters. - const bool hasRightToLeftCharacters = NULL != visualToLogicalBuffer; + // Get the glyph's info buffer. + const GlyphInfo* const glyphInfoBuffer = mVisualModel->mGlyphs.Begin(); const CharacterIndex startCharacter = line.characterRun.characterIndex; const CharacterIndex endCharacter = line.characterRun.characterIndex + line.characterRun.numberOfCharacters; @@ -1399,50 +1956,65 @@ CharacterIndex Controller::Impl::GetClosestCursorIndex( float visualX, // Traverses glyphs in visual order. To do that use the visual to logical conversion table. CharacterIndex visualIndex = startCharacter; + Length numberOfCharacters = 0u; for( ; !matched && ( visualIndex < endCharacter ); ++visualIndex ) { // The character in logical order. - const CharacterIndex characterLogicalOrderIndex = hasRightToLeftCharacters ? *( visualToLogicalBuffer + visualIndex ) : visualIndex; + const CharacterIndex characterLogicalOrderIndex = mLogicalModel->GetLogicalCharacterIndex( visualIndex ); // Get the script of the character. const Script script = mLogicalModel->GetScript( characterLogicalOrderIndex ); - // The first glyph for that character in logical order. - const GlyphIndex glyphLogicalOrderIndex = *( charactersToGlyphBuffer + characterLogicalOrderIndex ); // The number of glyphs for that character const Length numberOfGlyphs = *( glyphsPerCharacterBuffer + characterLogicalOrderIndex ); + ++numberOfCharacters; - // Get the metrics for the group of glyphs. - GlyphMetrics glyphMetrics; - GetGlyphsMetrics( glyphLogicalOrderIndex, - numberOfGlyphs, - glyphMetrics, - mVisualModel, - mFontClient ); - - const Vector2& position = *( positionsBuffer + glyphLogicalOrderIndex ); - // Prevents to jump the whole Latin ligatures like fi, ff, or Arabic ï»»... - const Length numberOfCharactersInLigature = HasLigatureMustBreak( script ) ? *( charactersPerGlyphBuffer + glyphLogicalOrderIndex ) : 1u; - const float glyphAdvance = glyphMetrics.advance / static_cast( numberOfCharactersInLigature ); - - for( GlyphIndex index = 0u; !matched && ( index < numberOfCharactersInLigature ); ++index ) + if( 0u != numberOfGlyphs ) { - // Find the mid-point of the area containing the glyph - const float glyphCenter = -glyphMetrics.xBearing + position.x + ( static_cast( index ) + 0.5f ) * glyphAdvance; + // Get the first character/glyph of the group of glyphs. + const CharacterIndex firstVisualCharacterIndex = 1u + visualIndex - numberOfCharacters; + const CharacterIndex firstLogicalCharacterIndex = mLogicalModel->GetLogicalCharacterIndex( firstVisualCharacterIndex ); + const GlyphIndex firstLogicalGlyphIndex = *( charactersToGlyphBuffer + firstLogicalCharacterIndex ); + + // Get the metrics for the group of glyphs. + GlyphMetrics glyphMetrics; + GetGlyphsMetrics( firstLogicalGlyphIndex, + numberOfGlyphs, + glyphMetrics, + glyphInfoBuffer, + mMetrics ); + + // Get the position of the first glyph. + const Vector2& position = *( positionsBuffer + firstLogicalGlyphIndex ); + + // Whether the glyph can be split, like Latin ligatures fi, ff or Arabic ï»». + const bool isInterglyphIndex = ( numberOfCharacters > numberOfGlyphs ) && HasLigatureMustBreak( script ); + const Length numberOfBlocks = isInterglyphIndex ? numberOfCharacters : 1u; + const float glyphAdvance = glyphMetrics.advance / static_cast( numberOfBlocks ); + + GlyphIndex index = 0u; + for( ; !matched && ( index < numberOfBlocks ); ++index ) + { + // Find the mid-point of the area containing the glyph + const float glyphCenter = -glyphMetrics.xBearing + position.x + ( static_cast( index ) + 0.5f ) * glyphAdvance; + + if( visualX < glyphCenter ) + { + matched = true; + break; + } + } - if( visualX < glyphCenter ) + if( matched ) { - visualIndex += index; - matched = true; + visualIndex = firstVisualCharacterIndex + index; break; } - } - if( matched ) - { - break; + numberOfCharacters = 0u; } + } // Return the logical position of the cursor in characters. @@ -1452,9 +2024,11 @@ CharacterIndex Controller::Impl::GetClosestCursorIndex( float visualX, visualIndex = endCharacter; } - logicalIndex = hasRightToLeftCharacters ? *( visualToLogicalCursorBuffer + visualIndex ) : visualIndex; + logicalIndex = mLogicalModel->GetLogicalCursorIndex( visualIndex ); DALI_LOG_INFO( gLogFilter, Debug::Verbose, "%p closest visualIndex %d logicalIndex %d\n", this, visualIndex, logicalIndex ); + DALI_ASSERT_DEBUG( ( logicalIndex <= mLogicalModel->mText.Count() && logicalIndex >= 0 ) && "GetClosestCursorIndex - Out of bounds index" ); + return logicalIndex; } @@ -1463,23 +2037,65 @@ void Controller::Impl::GetCursorPosition( CharacterIndex logical, { // TODO: Check for multiline with \n, etc... - // Check if the logical position is the first or the last one of the text. - const bool isFirstPosition = 0u == logical; - const bool isLastPosition = mLogicalModel->mText.Count() == logical; - - if( isFirstPosition && isLastPosition ) + const Length numberOfCharacters = mLogicalModel->mText.Count(); + if( !IsShowingRealText() ) { - // There is zero characters. Get the default font's line height. + // Do not want to use the place-holder text to set the cursor position. + + // Use the line's height of the font's family set to set the cursor's size. + // If there is no font's family set, use the default font. + // Use the current alignment to place the cursor at the beginning, center or end of the box. + + cursorInfo.lineOffset = 0.f; cursorInfo.lineHeight = GetDefaultFontLineHeight(); cursorInfo.primaryCursorHeight = cursorInfo.lineHeight; - cursorInfo.primaryPosition.x = mEventData->mDecorator->GetCursorWidth(); - cursorInfo.primaryPosition.y = 0.f; + switch( mLayoutEngine.GetHorizontalAlignment() ) + { + case LayoutEngine::HORIZONTAL_ALIGN_BEGIN: + { + cursorInfo.primaryPosition.x = 0.f; + break; + } + case LayoutEngine::HORIZONTAL_ALIGN_CENTER: + { + cursorInfo.primaryPosition.x = floorf( 0.5f * mVisualModel->mControlSize.width ); + break; + } + case LayoutEngine::HORIZONTAL_ALIGN_END: + { + cursorInfo.primaryPosition.x = mVisualModel->mControlSize.width - mEventData->mDecorator->GetCursorWidth(); + break; + } + } + + switch( mLayoutEngine.GetVerticalAlignment() ) + { + case LayoutEngine::VERTICAL_ALIGN_TOP: + { + cursorInfo.primaryPosition.y = 0.f; + break; + } + case LayoutEngine::VERTICAL_ALIGN_CENTER: + { + cursorInfo.primaryPosition.y = floorf( 0.5f * ( mVisualModel->mControlSize.height - cursorInfo.lineHeight ) ); + break; + } + case LayoutEngine::VERTICAL_ALIGN_BOTTOM: + { + cursorInfo.primaryPosition.y = mVisualModel->mControlSize.height - cursorInfo.lineHeight; + break; + } + } // Nothing else to do. return; } + // Check if the logical position is the first or the last one of the text. + const bool isFirstPosition = 0u == logical; + const bool isLastPosition = numberOfCharacters == logical; + // 'logical' is the logical 'cursor' index. // Get the next and current logical 'character' index. const CharacterIndex nextCharacterIndex = logical; @@ -1510,7 +2126,8 @@ void Controller::Impl::GetCursorPosition( CharacterIndex logical, cursorInfo.isSecondaryCursor = ( !isLastPosition && ( isCurrentRightToLeft != isNextRightToLeft ) ) || ( isLastPosition && ( isRightToLeftParagraph != isCurrentRightToLeft ) ); - // Set the line height. + // Set the line offset and height. + cursorInfo.lineOffset = 0.f; cursorInfo.lineHeight = line.ascender + -line.descender; // Calculate the primary cursor. @@ -1543,6 +2160,7 @@ void Controller::Impl::GetCursorPosition( CharacterIndex logical, const Length* const charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin(); const CharacterIndex* const glyphsToCharactersBuffer = mVisualModel->mGlyphsToCharacters.Begin(); const Vector2* const glyphPositionsBuffer = mVisualModel->mGlyphPositions.Begin(); + const GlyphInfo* const glyphInfoBuffer = mVisualModel->mGlyphs.Begin(); // Convert the cursor position into the glyph position. const GlyphIndex primaryGlyphIndex = *( charactersToGlyphBuffer + index ); @@ -1554,8 +2172,8 @@ void Controller::Impl::GetCursorPosition( CharacterIndex logical, GetGlyphsMetrics( primaryGlyphIndex, primaryNumberOfGlyphs, glyphMetrics, - mVisualModel, - mFontClient ); + glyphInfoBuffer, + mMetrics ); // Whether to add the glyph's advance to the cursor position. // i.e if the paragraph is left to right and the logical cursor is zero, the position is the position of the first glyph and the advance is not added, @@ -1644,13 +2262,32 @@ void Controller::Impl::GetCursorPosition( CharacterIndex logical, GetGlyphsMetrics( secondaryGlyphIndex, secondaryNumberOfGlyphs, glyphMetrics, - mVisualModel, - mFontClient ); + glyphInfoBuffer, + mMetrics ); // Set the secondary cursor's position. cursorInfo.secondaryPosition.x = -glyphMetrics.xBearing + secondaryPosition.x + ( isCurrentRightToLeft ? 0.f : glyphMetrics.advance ); cursorInfo.secondaryPosition.y = cursorInfo.lineHeight - cursorInfo.secondaryCursorHeight - line.descender - ( glyphMetrics.fontHeight - glyphMetrics.ascender ); } + + if( LayoutEngine::MULTI_LINE_BOX == mLayoutEngine.GetLayout() ) + { + // If the text is editable and multi-line, the cursor position after a white space shouldn't exceed the boundaries of the text control. + + // Note the white spaces laid-out at the end of the line might exceed the boundaries of the control. + // The reason is a wrapped line must not start with a white space so they are laid-out at the end of the line. + + if( 0.f > cursorInfo.primaryPosition.x ) + { + cursorInfo.primaryPosition.x = 0.f; + } + + const float edgeWidth = mVisualModel->mControlSize.width - static_cast( mEventData->mDecorator->GetCursorWidth() ); + if( cursorInfo.primaryPosition.x > edgeWidth ) + { + cursorInfo.primaryPosition.x = edgeWidth; + } + } } CharacterIndex Controller::Impl::CalculateNewCursorIndex( CharacterIndex index ) const @@ -1699,7 +2336,7 @@ CharacterIndex Controller::Impl::CalculateNewCursorIndex( CharacterIndex index ) return cursorIndex; } -void Controller::Impl::UpdateCursorPosition() +void Controller::Impl::UpdateCursorPosition( const CursorInfo& cursorInfo ) { DALI_LOG_INFO( gLogFilter, Debug::Verbose, "-->Controller::UpdateCursorPosition %p\n", this ); if( NULL == mEventData ) @@ -1709,121 +2346,55 @@ void Controller::Impl::UpdateCursorPosition() return; } - if( IsShowingPlaceholderText() || ( 0u == mLogicalModel->mText.Count() ) ) - { - // Do not want to use the place-holder text to set the cursor position. - - // Use the line's height of the font's family set to set the cursor's size. - // If there is no font's family set, use the default font. - // Use the current alignment to place the cursor at the beginning, center or end of the box. - - float lineHeight = 0.f; - - FontId defaultFontId = 0u; - if( NULL == mFontDefaults ) - { - defaultFontId = mFontClient.GetFontId( EMPTY_STRING, - EMPTY_STRING ); - } - else - { - defaultFontId = mFontDefaults->GetFontId( mFontClient ); - } - - Text::FontMetrics fontMetrics; - mFontClient.GetFontMetrics( defaultFontId, fontMetrics ); - - lineHeight = fontMetrics.ascender - fontMetrics.descender; - - - Vector2 cursorPosition; + const Vector2 offset = mEventData->mScrollPosition + ( IsShowingRealText() ? mAlignmentOffset : Vector2::ZERO ); + const Vector2 cursorPosition = cursorInfo.primaryPosition + offset; - switch( mLayoutEngine.GetHorizontalAlignment() ) - { - case LayoutEngine::HORIZONTAL_ALIGN_BEGIN: - { - cursorPosition.x = mEventData->mDecorator->GetCursorWidth(); - break; - } - case LayoutEngine::HORIZONTAL_ALIGN_CENTER: - { - cursorPosition.x = floor( 0.5f * mVisualModel->mControlSize.width ); - break; - } - case LayoutEngine::HORIZONTAL_ALIGN_END: - { - cursorPosition.x = mVisualModel->mControlSize.width; - break; - } - } + // Sets the cursor position. + mEventData->mDecorator->SetPosition( PRIMARY_CURSOR, + cursorPosition.x, + cursorPosition.y, + cursorInfo.primaryCursorHeight, + cursorInfo.lineHeight ); + DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Primary cursor position: %f,%f\n", cursorPosition.x, cursorPosition.y ); - switch( mLayoutEngine.GetVerticalAlignment() ) - { - case LayoutEngine::VERTICAL_ALIGN_TOP: - { - cursorPosition.y = 0.f; - break; - } - case LayoutEngine::VERTICAL_ALIGN_CENTER: - { - cursorPosition.y = floorf( 0.5f * ( mVisualModel->mControlSize.height - lineHeight ) ); - break; - } - case LayoutEngine::VERTICAL_ALIGN_BOTTOM: - { - cursorPosition.y = mVisualModel->mControlSize.height - lineHeight; - break; - } - } + // Sets the grab handle position. + mEventData->mDecorator->SetPosition( GRAB_HANDLE, + cursorPosition.x, + cursorInfo.lineOffset + offset.y, + cursorInfo.lineHeight ); - mEventData->mDecorator->SetPosition( PRIMARY_CURSOR, - cursorPosition.x, - cursorPosition.y, - lineHeight, - lineHeight ); - } - else + if( cursorInfo.isSecondaryCursor ) { - CursorInfo cursorInfo; - GetCursorPosition( mEventData->mPrimaryCursorPosition, - cursorInfo ); - - const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset; - const Vector2 cursorPosition = cursorInfo.primaryPosition + offset; - - // Sets the cursor position. - mEventData->mDecorator->SetPosition( PRIMARY_CURSOR, - cursorPosition.x, - cursorPosition.y, - cursorInfo.primaryCursorHeight, - cursorInfo.lineHeight ); - DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Primary cursor position: %f,%f\n", cursorPosition.x, cursorPosition.y ); - - // Sets the grab handle position. - mEventData->mDecorator->SetPosition( GRAB_HANDLE, - cursorPosition.x, - cursorPosition.y, + mEventData->mDecorator->SetPosition( SECONDARY_CURSOR, + cursorInfo.secondaryPosition.x + offset.x, + cursorInfo.secondaryPosition.y + offset.y, + cursorInfo.secondaryCursorHeight, cursorInfo.lineHeight ); + DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Secondary cursor position: %f,%f\n", cursorInfo.secondaryPosition.x + offset.x, cursorInfo.secondaryPosition.y + offset.y ); + } + // Set which cursors are active according the state. + if( EventData::IsEditingState( mEventData->mState ) || ( EventData::GRAB_HANDLE_PANNING == mEventData->mState ) ) + { if( cursorInfo.isSecondaryCursor ) { mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_BOTH ); - mEventData->mDecorator->SetPosition( SECONDARY_CURSOR, - cursorInfo.secondaryPosition.x + offset.x, - cursorInfo.secondaryPosition.y + offset.y, - cursorInfo.secondaryCursorHeight, - cursorInfo.lineHeight ); - DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Secondary cursor position: %f,%f\n", cursorInfo.secondaryPosition.x + offset.x, cursorInfo.secondaryPosition.y + offset.y ); } else { mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY ); } } + else + { + mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE ); + } + DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition\n" ); } -void Controller::Impl::UpdateSelectionHandle( HandleType handleType ) +void Controller::Impl::UpdateSelectionHandle( HandleType handleType, + const CursorInfo& cursorInfo ) { if( ( LEFT_SELECTION_HANDLE != handleType ) && ( RIGHT_SELECTION_HANDLE != handleType ) ) @@ -1831,20 +2402,13 @@ void Controller::Impl::UpdateSelectionHandle( HandleType handleType ) return; } - const bool leftSelectionHandle = LEFT_SELECTION_HANDLE == handleType; - const CharacterIndex index = leftSelectionHandle ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition; - - CursorInfo cursorInfo; - GetCursorPosition( index, - cursorInfo ); - const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset; const Vector2 cursorPosition = cursorInfo.primaryPosition + offset; - // Sets the grab handle position. + // Sets the handle's position. mEventData->mDecorator->SetPosition( handleType, cursorPosition.x, - cursorPosition.y, + cursorInfo.lineOffset + offset.y, cursorInfo.lineHeight ); // If selection handle at start of the text and other at end of the text then all text is selected. @@ -1856,6 +2420,7 @@ void Controller::Impl::UpdateSelectionHandle( HandleType handleType ) void Controller::Impl::ClampHorizontalScroll( const Vector2& actualSize ) { // Clamp between -space & 0 (and the text alignment). + if( actualSize.width > mVisualModel->mControlSize.width ) { const float space = ( actualSize.width - mVisualModel->mControlSize.width ) + mAlignmentOffset.x; @@ -1889,88 +2454,34 @@ void Controller::Impl::ClampVerticalScroll( const Vector2& actualSize ) void Controller::Impl::ScrollToMakePositionVisible( const Vector2& position ) { - Vector2 offset; - bool updateDecorator = false; - if( position.x < 0.f ) - { - offset.x = -position.x; - mEventData->mScrollPosition.x += offset.x; - updateDecorator = true; - } - else if( position.x > mVisualModel->mControlSize.width ) + // position is in actor's coords. + const float positionEnd = position.x + ( mEventData->mDecorator ? mEventData->mDecorator->GetCursorWidth() : 0.f ); + + // Transform the position to decorator coords. + const float alignment = IsShowingRealText() ? mAlignmentOffset.x : 0.f; + const float offset = mEventData->mScrollPosition.x + alignment; + const float decoratorPositionBegin = position.x + offset; + const float decoratorPositionEnd = positionEnd + offset; + + if( decoratorPositionBegin < 0.f ) { - offset.x = mVisualModel->mControlSize.width - position.x; - mEventData->mScrollPosition.x += offset.x; - updateDecorator = true; + mEventData->mScrollPosition.x = -position.x - alignment; } - - if( updateDecorator && mEventData->mDecorator ) + else if( decoratorPositionEnd > mVisualModel->mControlSize.width ) { - mEventData->mDecorator->UpdatePositions( offset ); + mEventData->mScrollPosition.x = mVisualModel->mControlSize.width - positionEnd - alignment; } - - // TODO : calculate the vertical scroll. } -void Controller::Impl::ScrollTextToMatchCursor() +void Controller::Impl::ScrollTextToMatchCursor( const CursorInfo& cursorInfo ) { // Get the current cursor position in decorator coords. const Vector2& currentCursorPosition = mEventData->mDecorator->GetPosition( PRIMARY_CURSOR ); - // Calculate the new cursor position. - CursorInfo cursorInfo; - GetCursorPosition( mEventData->mPrimaryCursorPosition, - cursorInfo ); - // Calculate the offset to match the cursor position before the character was deleted. mEventData->mScrollPosition.x = currentCursorPosition.x - cursorInfo.primaryPosition.x - mAlignmentOffset.x; - ClampHorizontalScroll( mVisualModel->GetActualSize() ); - - const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset; - const Vector2 cursorPosition = cursorInfo.primaryPosition + offset; - - // Sets the cursor position. - mEventData->mDecorator->SetPosition( PRIMARY_CURSOR, - cursorPosition.x, - cursorPosition.y, - cursorInfo.primaryCursorHeight, - cursorInfo.lineHeight ); - - // Sets the grab handle position. - mEventData->mDecorator->SetPosition( GRAB_HANDLE, - cursorPosition.x, - cursorPosition.y, - cursorInfo.lineHeight ); - - if( cursorInfo.isSecondaryCursor ) - { - mEventData->mDecorator->SetPosition( SECONDARY_CURSOR, - cursorInfo.secondaryPosition.x + offset.x, - cursorInfo.secondaryPosition.y + offset.y, - cursorInfo.secondaryCursorHeight, - cursorInfo.lineHeight ); - DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Secondary cursor position: %f,%f\n", cursorInfo.secondaryPosition.x + offset.x, cursorInfo.secondaryPosition.y + offset.y ); - } - - // Set which cursors are active according the state. - if( ( EventData::EDITING == mEventData->mState ) || - ( EventData::EDITING_WITH_POPUP == mEventData->mState ) || - ( EventData::GRAB_HANDLE_PANNING == mEventData->mState ) ) - { - if( cursorInfo.isSecondaryCursor ) - { - mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_BOTH ); - } - else - { - mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY ); - } - } - else - { - mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE ); - } + ClampHorizontalScroll( mVisualModel->GetLayoutSize() ); } void Controller::Impl::RequestRelayout()