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=78bd7e907aded5dc4fccf77ab26c7e8bcce46b4b;hp=b262e99cef30dfd538ea0587042c090d16bf3a74;hb=0b2e209ecfb3cedba510f53a4af58d7d8a2b3876;hpb=ed318176506ba6763f189728d7c14c00e9e0b909 diff --git a/dali-toolkit/internal/text/text-controller-impl.cpp b/dali-toolkit/internal/text/text-controller-impl.cpp index b262e99..78bd7e9 100644 --- a/dali-toolkit/internal/text/text-controller-impl.cpp +++ b/dali-toolkit/internal/text/text-controller-impl.cpp @@ -25,6 +25,7 @@ // INTERNAL INCLUDES #include #include +#include #include #include #include @@ -132,7 +133,8 @@ EventData::EventData( DecoratorPtr decorator ) mUpdateRightSelectionPosition( false ), mScrollAfterUpdatePosition( false ), mScrollAfterDelete( false ), - mAllTextSelected( false ) + mAllTextSelected( false ), + mUpdateInputStyle( false ) { mImfManager = ImfManager::Get(); } @@ -287,6 +289,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" ); @@ -309,6 +325,8 @@ void Controller::Impl::UpdateModel( OperationsMask operationsRequired ) const Length numberOfCharacters = utf32Characters.Count(); Vector& lineBreakInfo = mLogicalModel->mLineBreakInfo; + CharacterIndex startIndex = 0u; + Length requestedNumberOfCharacters = numberOfCharacters; if( GET_LINE_BREAKS & operations ) { // Retrieves the line break info. The line break info is used to split the text in 'paragraphs' to @@ -318,7 +336,13 @@ 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 ); } Vector& wordBreakInfo = mLogicalModel->mWordBreakInfo; @@ -328,6 +352,8 @@ void Controller::Impl::UpdateModel( OperationsMask operationsRequired ) wordBreakInfo.Resize( numberOfCharacters, TextAbstraction::WORD_NO_BREAK ); SetWordBreakInfo( utf32Characters, + startIndex, + requestedNumberOfCharacters, wordBreakInfo ); } @@ -347,22 +373,27 @@ 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 ); } } @@ -391,23 +422,29 @@ 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 { @@ -422,6 +459,7 @@ void Controller::Impl::UpdateModel( OperationsMask operationsRequired ) Vector newParagraphGlyphs; newParagraphGlyphs.Reserve( numberOfParagraphs ); + GlyphIndex startGlyphIndex = 0u; if( SHAPE_TEXT & operations ) { const Vector& textToShape = textMirrored ? mirroredUtf32Characters : utf32Characters; @@ -430,21 +468,24 @@ void Controller::Impl::UpdateModel( OperationsMask operationsRequired ) lineBreakInfo, scripts, validFonts, + startIndex, + startGlyphIndex, + 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, numberOfCharacters ); + mVisualModel->CreateCharacterToGlyphTable( startIndex, numberOfCharacters ); } const Length numberOfGlyphs = glyphs.Count(); if( GET_GLYPH_METRICS & operations ) { - GlyphInfo* glyphsBuffer = glyphs.Begin(); + GlyphInfo* glyphsBuffer = glyphs.Begin() + startGlyphIndex; mMetrics->GetGlyphMetrics( glyphsBuffer, numberOfGlyphs ); // Update the width and advance of all new paragraph characters. @@ -459,7 +500,7 @@ void Controller::Impl::UpdateModel( OperationsMask operationsRequired ) } } - if( mEventData && + if( ( NULL != mEventData ) && mEventData->mPreEditFlag && ( 0u != mVisualModel->mCharactersToGlyph.Count() ) ) { @@ -481,18 +522,57 @@ void Controller::Impl::UpdateModel( OperationsMask operationsRequired ) } } -void Controller::Impl::GetDefaultFonts( Vector& fonts, Length numberOfCharacters ) +bool Controller::Impl::UpdateModelStyle( OperationsMask operationsRequired ) +{ + bool updated = false; + + if( COLOR & operationsRequired ) + { + // Set the color runs in glyphs. + SetColorSegmentationInfo( mLogicalModel->mColorRuns, + mVisualModel->mCharactersToGlyph, + mVisualModel->mGlyphsPerCharacter, + mVisualModel->mColorRuns ); + + updated = true; + } + + return updated; +} + +void Controller::Impl::RetrieveDefaultInputStyle( InputStyle& inputStyle ) { + // Sets the default text's color. + inputStyle.textColor = mTextColor; + + // Sets the default font's family name, weight, width, slant and size. if( mFontDefaults ) { - DALI_LOG_INFO( gLogFilter, Debug::Concise, "Controller::GetDefaultFonts font family(%s)\n", mFontDefaults->mFontDescription.family.c_str() ); - FontRun fontRun; - fontRun.characterRun.characterIndex = 0; - fontRun.characterRun.numberOfCharacters = numberOfCharacters; - fontRun.fontId = mFontDefaults->GetFontId( mFontClient ); - fontRun.isDefault = true; + inputStyle.familyName = mFontDefaults->mFontDescription.family; + inputStyle.weight = mFontDefaults->mFontDescription.weight; + inputStyle.width = mFontDefaults->mFontDescription.width; + inputStyle.slant = mFontDefaults->mFontDescription.slant; + inputStyle.size = mFontDefaults->mDefaultPointSize; - fonts.PushBack( fontRun ); + inputStyle.familyDefined = mFontDefaults->familyDefined; + inputStyle.weightDefined = mFontDefaults->weightDefined; + inputStyle.widthDefined = mFontDefaults->widthDefined; + inputStyle.slantDefined = mFontDefaults->slantDefined; + inputStyle.sizeDefined = mFontDefaults->sizeDefined; + } + else + { + 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; } } @@ -549,6 +629,7 @@ void Controller::Impl::OnCursorKeyEvent( const Event& event ) } mEventData->mUpdateCursorPosition = true; + mEventData->mUpdateInputStyle = true; mEventData->mScrollAfterUpdatePosition = true; } @@ -578,6 +659,7 @@ 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 ) @@ -602,7 +684,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 ) @@ -630,7 +712,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; @@ -707,8 +791,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 ) { @@ -751,7 +839,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; @@ -790,6 +878,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 ) { @@ -865,6 +954,8 @@ void Controller::Impl::OnSelectEvent( const Event& event ) 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. @@ -882,31 +973,36 @@ 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 = handlesCrossed ? mEventData->mRightSelectionPosition : mEventData->mLeftSelectionPosition; - uint32_t lengthOfSelectedText = ( handlesCrossed ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition ) - startOfSelectedText; + const CharacterIndex startOfSelectedText = handlesCrossed ? mEventData->mRightSelectionPosition : mEventData->mLeftSelectionPosition; + const Length lengthOfSelectedText = ( handlesCrossed ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition ) - startOfSelectedText; // Validate the start and end selection points - if( ( startOfSelectedText + lengthOfSelectedText ) <= mLogicalModel->mText.Count() ) + if( ( startOfSelectedText + lengthOfSelectedText ) <= mLogicalModel->mText.Count() ) { //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 { + // Set as input style the style of the first deleted character. + mLogicalModel->RetrieveStyle( startOfSelectedText, mEventData->mInputStyle ); + + mLogicalModel->UpdateTextStyleRuns( startOfSelectedText, -static_cast( lengthOfSelectedText ) ); + // Delete text between handles Vector& currentText = mLogicalModel->mText; @@ -928,7 +1024,7 @@ void Controller::Impl::RetrieveSelection( std::string& selectedText, bool delete void Controller::Impl::ShowClipboard() { - if ( mClipboard ) + if( mClipboard ) { mClipboard.ShowClipboard(); } @@ -936,7 +1032,7 @@ void Controller::Impl::ShowClipboard() void Controller::Impl::HideClipboard() { - if ( mClipboard ) + if( mClipboard ) { mClipboard.HideClipboard(); } @@ -956,11 +1052,11 @@ 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 ); } } @@ -1101,9 +1197,15 @@ void Controller::Impl::RepositionSelectionHandles() 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; @@ -1128,8 +1230,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; @@ -1168,24 +1270,32 @@ void Controller::Impl::SetPopupButtons() { 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 ) ); @@ -1204,6 +1314,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; @@ -1219,7 +1331,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 ); @@ -1228,7 +1340,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(); @@ -1262,6 +1374,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 ) { @@ -1286,6 +1400,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 ) { @@ -1302,7 +1418,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(); @@ -1315,8 +1431,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 ) { @@ -1331,6 +1449,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; + } } } @@ -1365,7 +1505,7 @@ void Controller::Impl::FindSelectionIndices( float visualX, float visualY, Chara CharacterIndex hitCharacter = GetClosestCursorIndex( visualX, visualY ); DALI_ASSERT_DEBUG( hitCharacter <= mLogicalModel->mText.Count() && "GetClosestCursorIndex returned out of bounds index" ); - if ( mLogicalModel->mText.Count() == 0 ) + if( mLogicalModel->mText.Count() == 0 ) { return; // if model empty } @@ -1373,7 +1513,7 @@ void Controller::Impl::FindSelectionIndices( float visualX, float visualY, Chara if( hitCharacter >= mLogicalModel->mText.Count() ) { // Closest hit character is the last character. - if ( hitCharacter == mLogicalModel->mText.Count() ) + if( hitCharacter == mLogicalModel->mText.Count() ) { hitCharacter--; //Hit character index set to last character in logical model } @@ -1386,26 +1526,22 @@ void Controller::Impl::FindSelectionIndices( float visualX, float visualY, Chara 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; } } } @@ -1425,8 +1561,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; } @@ -1493,7 +1629,7 @@ CharacterIndex Controller::Impl::GetClosestCursorIndex( float visualX, // 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 ï»». + // 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 ); @@ -1552,6 +1688,7 @@ void Controller::Impl::GetCursorPosition( CharacterIndex logical, // 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; @@ -1631,7 +1768,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. @@ -1772,6 +1910,25 @@ void Controller::Impl::GetCursorPosition( CharacterIndex logical, 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 @@ -1844,7 +2001,7 @@ void Controller::Impl::UpdateCursorPosition( const CursorInfo& cursorInfo ) // Sets the grab handle position. mEventData->mDecorator->SetPosition( GRAB_HANDLE, cursorPosition.x, - cursorPosition.y, + cursorInfo.lineOffset + offset.y, cursorInfo.lineHeight ); if( cursorInfo.isSecondaryCursor ) @@ -1858,10 +2015,7 @@ void Controller::Impl::UpdateCursorPosition( const CursorInfo& cursorInfo ) } // Set which cursors are active according the state. - if( ( EventData::EDITING == mEventData->mState ) || - ( EventData::EDITING_WITH_POPUP == mEventData->mState ) || - ( EventData::EDITING_WITH_GRAB_HANDLE == mEventData->mState ) || - ( EventData::GRAB_HANDLE_PANNING == mEventData->mState ) ) + if( EventData::IsEditingState( mEventData->mState ) || ( EventData::GRAB_HANDLE_PANNING == mEventData->mState ) ) { if( cursorInfo.isSecondaryCursor ) { @@ -1889,12 +2043,13 @@ void Controller::Impl::UpdateSelectionHandle( HandleType handleType, return; } - const Vector2 cursorPosition = cursorInfo.primaryPosition + mEventData->mScrollPosition + mAlignmentOffset; + 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. @@ -1944,17 +2099,18 @@ void Controller::Impl::ScrollToMakePositionVisible( const Vector2& position ) const float positionEnd = position.x + ( mEventData->mDecorator ? mEventData->mDecorator->GetCursorWidth() : 0.f ); // Transform the position to decorator coords. - const float offset = mEventData->mScrollPosition.x + mAlignmentOffset.x; + 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 ) { - mEventData->mScrollPosition.x = -position.x - mAlignmentOffset.x; + mEventData->mScrollPosition.x = -position.x - alignment; } else if( decoratorPositionEnd > mVisualModel->mControlSize.width ) { - mEventData->mScrollPosition.x = mVisualModel->mControlSize.width - positionEnd - mAlignmentOffset.x; + mEventData->mScrollPosition.x = mVisualModel->mControlSize.width - positionEnd - alignment; } } @@ -1966,7 +2122,7 @@ void Controller::Impl::ScrollTextToMatchCursor( const CursorInfo& 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() ); + ClampHorizontalScroll( mVisualModel->GetLayoutSize() ); } void Controller::Impl::RequestRelayout()