// EXTERNAL INCLUDES
#include <dali/public-api/adaptor-framework/key.h>
+// INTERNAL INCLUDES
+#include <dali-toolkit/internal/text/bidirectional-support.h>
+#include <dali-toolkit/internal/text/character-set-conversion.h>
+#include <dali-toolkit/internal/text/layouts/layout-parameters.h>
+#include <dali-toolkit/internal/text/multi-language-support.h>
+#include <dali-toolkit/internal/text/script-run.h>
+#include <dali-toolkit/internal/text/segmentation.h>
+#include <dali-toolkit/internal/text/shaper.h>
+#include <dali-toolkit/internal/text/text-io.h>
+#include <dali-toolkit/internal/text/text-view.h>
+
namespace
{
EventData::EventData( DecoratorPtr decorator )
: mDecorator( decorator ),
- mPlaceholderText(),
+ mPlaceholderTextActive(),
+ mPlaceholderTextInactive(),
+ mPlaceholderTextColor( 0.8f, 0.8f, 0.8f, 0.8f ),
mEventQueue(),
mScrollPosition(),
mState( INACTIVE ),
mPrimaryCursorPosition( 0u ),
- mSecondaryCursorPosition( 0u ),
+ mLeftSelectionPosition( 0u ),
+ mRightSelectionPosition( 0u ),
+ mPreEditStartPosition( 0u ),
+ mPreEditLength( 0u ),
+ mIsShowingPlaceholderText( false ),
+ mPreEditFlag( false ),
mDecoratorUpdated( false ),
mCursorBlinkEnabled( true ),
mGrabHandleEnabled( true ),
- mGrabHandlePopupEnabled( true ),
- mSelectionEnabled( true ),
+ mGrabHandlePopupEnabled( false ),
+ mSelectionEnabled( false ),
mHorizontalScrollingEnabled( true ),
mVerticalScrollingEnabled( false ),
mUpdateCursorPosition( false ),
+ mUpdateLeftSelectionPosition( false ),
+ mUpdateRightSelectionPosition( false ),
mScrollAfterUpdateCursorPosition( false )
{}
break;
}
case Event::GRAB_HANDLE_EVENT:
+ case Event::LEFT_SELECTION_HANDLE_EVENT:
+ case Event::RIGHT_SELECTION_HANDLE_EVENT: // Fall through
{
- OnGrabHandleEvent( *iter );
+ OnHandleEvent( *iter );
break;
}
}
mEventData->mScrollAfterUpdateCursorPosition = false;
}
+ mEventData->mDecoratorUpdated = true;
mEventData->mUpdateCursorPosition = false;
}
+ else if( mEventData->mUpdateLeftSelectionPosition )
+ {
+ UpdateSelectionHandle( LEFT_SELECTION_HANDLE );
+
+ if( mEventData->mScrollAfterUpdateCursorPosition )
+ {
+ ScrollToMakeCursorVisible();
+ mEventData->mScrollAfterUpdateCursorPosition = false;
+ }
+
+ mEventData->mDecoratorUpdated = true;
+ mEventData->mUpdateLeftSelectionPosition = false;
+ }
+ else if( mEventData->mUpdateRightSelectionPosition )
+ {
+ UpdateSelectionHandle( RIGHT_SELECTION_HANDLE );
+
+ if( mEventData->mScrollAfterUpdateCursorPosition )
+ {
+ ScrollToMakeCursorVisible();
+ mEventData->mScrollAfterUpdateCursorPosition = false;
+ }
+
+ mEventData->mDecoratorUpdated = true;
+ mEventData->mUpdateRightSelectionPosition = false;
+ }
mEventData->mEventQueue.clear();
return mEventData->mDecoratorUpdated;
}
+void Controller::Impl::UpdateModel( OperationsMask operationsRequired )
+{
+ // Calculate the operations to be done.
+ const OperationsMask operations = static_cast<OperationsMask>( mOperationsPending & operationsRequired );
+
+ Vector<Character>& utf32Characters = mLogicalModel->mText;
+
+ const Length numberOfCharacters = mLogicalModel->GetNumberOfCharacters();
+
+ Vector<LineBreakInfo>& lineBreakInfo = mLogicalModel->mLineBreakInfo;
+ if( 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'.
+ // It's also used to layout the text (where it should be a new line) or to shape the text (text in different lines
+ // is not shaped together).
+ lineBreakInfo.Resize( numberOfCharacters, TextAbstraction::LINE_NO_BREAK );
+
+ SetLineBreakInfo( utf32Characters,
+ lineBreakInfo );
+ }
+
+ Vector<WordBreakInfo>& wordBreakInfo = mLogicalModel->mWordBreakInfo;
+ if( 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,
+ wordBreakInfo );
+ }
+
+ const bool getScripts = GET_SCRIPTS & operations;
+ const bool validateFonts = VALIDATE_FONTS & operations;
+
+ Vector<ScriptRun>& scripts = mLogicalModel->mScriptRuns;
+ Vector<FontRun>& validFonts = mLogicalModel->mFontRuns;
+
+ if( getScripts || validateFonts )
+ {
+ // Validates the fonts assigned by the application or assigns default ones.
+ // It makes sure all the characters are going to be rendered by the correct font.
+ MultilanguageSupport multilanguageSupport = MultilanguageSupport::Get();
+
+ if( getScripts )
+ {
+ // Retrieves the scripts used in the text.
+ multilanguageSupport.SetScripts( utf32Characters,
+ lineBreakInfo,
+ 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 );
+ }
+
+ // 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,
+ validFonts );
+ }
+ }
+
+ Vector<Character> mirroredUtf32Characters;
+ bool textMirrored = false;
+ if( BIDI_INFO & operations )
+ {
+ // Count the number of LINE_NO_BREAK to reserve some space for the vector of paragraph's
+ // bidirectional info.
+
+ Length numberOfParagraphs = 0u;
+
+ const TextAbstraction::LineBreakInfo* lineBreakInfoBuffer = lineBreakInfo.Begin();
+ for( Length index = 0u; index < numberOfCharacters; ++index )
+ {
+ if( TextAbstraction::LINE_NO_BREAK == *( lineBreakInfoBuffer + index ) )
+ {
+ ++numberOfParagraphs;
+ }
+ }
+
+ Vector<BidirectionalParagraphInfoRun>& bidirectionalInfo = mLogicalModel->mBidirectionalParagraphInfo;
+ bidirectionalInfo.Reserve( numberOfParagraphs );
+
+ // Calculates the bidirectional info for the whole paragraph if it contains right to left scripts.
+ SetBidirectionalInfo( utf32Characters,
+ scripts,
+ lineBreakInfo,
+ 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 );
+
+ // Only set the character directions if there is right to left characters.
+ Vector<CharacterDirection>& directions = mLogicalModel->mCharacterDirections;
+ directions.Resize( numberOfCharacters );
+
+ GetCharactersDirection( bidirectionalInfo,
+ directions );
+ }
+ else
+ {
+ // There is no right to left characters. Clear the directions vector.
+ mLogicalModel->mCharacterDirections.Clear();
+ }
+
+ }
+
+ Vector<GlyphInfo>& glyphs = mVisualModel->mGlyphs;
+ Vector<CharacterIndex>& glyphsToCharactersMap = mVisualModel->mGlyphsToCharacters;
+ Vector<Length>& charactersPerGlyph = mVisualModel->mCharactersPerGlyph;
+ if( SHAPE_TEXT & operations )
+ {
+ const Vector<Character>& textToShape = textMirrored ? mirroredUtf32Characters : utf32Characters;
+ // Shapes the text.
+ ShapeText( textToShape,
+ lineBreakInfo,
+ scripts,
+ validFonts,
+ glyphs,
+ glyphsToCharactersMap,
+ charactersPerGlyph );
+
+ // Create the 'number of glyphs' per character and the glyph to character conversion tables.
+ mVisualModel->CreateGlyphsPerCharacterTable( numberOfCharacters );
+ mVisualModel->CreateCharacterToGlyphTable( numberOfCharacters );
+ }
+
+ const Length numberOfGlyphs = glyphs.Count();
+
+ if( GET_GLYPH_METRICS & operations )
+ {
+ mFontClient.GetGlyphMetrics( glyphs.Begin(), numberOfGlyphs );
+ }
+}
+
+void Controller::Impl::GetDefaultFonts( Vector<FontRun>& fonts, Length numberOfCharacters )
+{
+ if( mFontDefaults )
+ {
+ FontRun fontRun;
+ fontRun.characterRun.characterIndex = 0;
+ fontRun.characterRun.numberOfCharacters = numberOfCharacters;
+ fontRun.fontId = mFontDefaults->GetFontId( mFontClient );
+ fontRun.isDefault = true;
+
+ fonts.PushBack( fontRun );
+ }
+}
+
void Controller::Impl::OnKeyboardFocus( bool hasFocus )
{
if( NULL == mEventData )
mEventData->mScrollAfterUpdateCursorPosition = true;
}
-void Controller::Impl::HandleCursorKey( int keyCode )
-{
- // TODO
- if( NULL == mEventData )
- {
- // Nothing to do if there is no text input.
- return;
- }
-}
-
void Controller::Impl::OnTapEvent( const Event& event )
{
- if( NULL == mEventData )
- {
- // Nothing to do if there is no text input.
- return;
- }
-
- const unsigned int tapCount = event.p1.mUint;
-
- if( 1u == tapCount )
+ if( NULL != mEventData )
{
- ChangeState( EventData::EDITING );
+ const unsigned int tapCount = event.p1.mUint;
- 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 );
+ if( 1u == tapCount )
+ {
+ if( ! IsShowingPlaceholderText() )
+ {
+ const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
+ const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
- mEventData->mUpdateCursorPosition = true;
- mEventData->mScrollAfterUpdateCursorPosition = true;
- }
- else if( mEventData->mSelectionEnabled &&
- ( 2u == tapCount ) )
- {
- ChangeState( EventData::SELECTING );
+ mEventData->mPrimaryCursorPosition = GetClosestCursorIndex( xPosition,
+ yPosition );
+ }
+ else
+ {
+ mEventData->mPrimaryCursorPosition = 0u;
+ }
- RepositionSelectionHandles( event.p2.mFloat, event.p3.mFloat );
+ mEventData->mUpdateCursorPosition = true;
+ mEventData->mScrollAfterUpdateCursorPosition = true;
+ }
+ else if( mEventData->mSelectionEnabled &&
+ ( 2u == tapCount ) )
+ {
+ RepositionSelectionHandles( event.p2.mFloat, event.p3.mFloat );
+ }
}
}
}
}
-void Controller::Impl::OnGrabHandleEvent( const Event& event )
+void Controller::Impl::OnHandleEvent( const Event& event )
{
if( NULL == mEventData )
{
return;
}
- unsigned int state = event.p1.mUint;
+ const unsigned int state = event.p1.mUint;
- if( GRAB_HANDLE_PRESSED == state )
+ if( HANDLE_PRESSED == state )
{
// The event.p2 and event.p3 are in decorator coords. Need to transforms to text coords.
const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
- //mDecorator->HidePopup();
- ChangeState ( EventData::EDITING );
+ const CharacterIndex handleNewPosition = GetClosestCursorIndex( xPosition, yPosition );
- const CharacterIndex newCursorPosition = GetClosestCursorIndex( xPosition, yPosition );
+ if( Event::GRAB_HANDLE_EVENT == event.type )
+ {
+ ChangeState ( EventData::EDITING );
- if( newCursorPosition != mEventData->mPrimaryCursorPosition )
+ if( handleNewPosition != mEventData->mPrimaryCursorPosition )
+ {
+ mEventData->mPrimaryCursorPosition = handleNewPosition;
+ mEventData->mUpdateCursorPosition = true;
+ }
+ }
+ else if( Event::LEFT_SELECTION_HANDLE_EVENT == event.type )
{
- mEventData->mPrimaryCursorPosition = newCursorPosition;
- mEventData->mUpdateCursorPosition = true;
+ if( handleNewPosition != mEventData->mLeftSelectionPosition )
+ {
+ mEventData->mLeftSelectionPosition = handleNewPosition;
+ mEventData->mUpdateLeftSelectionPosition = true;
+ }
+ }
+ else if( Event::RIGHT_SELECTION_HANDLE_EVENT == event.type )
+ {
+ if( handleNewPosition != mEventData->mRightSelectionPosition )
+ {
+ mEventData->mRightSelectionPosition = handleNewPosition;
+ mEventData->mUpdateRightSelectionPosition = true;
+ }
}
}
- else if( mEventData->mGrabHandlePopupEnabled &&
- ( ( GRAB_HANDLE_RELEASED == state ) ||
- ( GRAB_HANDLE_STOP_SCROLLING == state ) ) )
+ else if( ( HANDLE_RELEASED == state ) ||
+ ( HANDLE_STOP_SCROLLING == state ) )
{
- //mDecorator->ShowPopup();
- ChangeState ( EventData::EDITING_WITH_POPUP );
- mEventData->mUpdateCursorPosition = true;
- mEventData->mDecoratorUpdated = true;
-
- if( GRAB_HANDLE_STOP_SCROLLING == state )
+ if( mEventData->mGrabHandlePopupEnabled )
{
- // The event.p2 and event.p3 are in decorator coords. Need to transforms to text coords.
- const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
- const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
+ ChangeState( EventData::EDITING_WITH_POPUP );
+ }
+ if( Event::GRAB_HANDLE_EVENT == event.type )
+ {
+ mEventData->mUpdateCursorPosition = true;
- mEventData->mPrimaryCursorPosition = GetClosestCursorIndex( xPosition, yPosition );
+ if( HANDLE_STOP_SCROLLING == state )
+ {
+ // The event.p2 and event.p3 are in decorator coords. Need to transforms to text coords.
+ const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
+ const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
- mEventData->mScrollAfterUpdateCursorPosition = true;
+ mEventData->mPrimaryCursorPosition = GetClosestCursorIndex( xPosition, yPosition );
+
+ mEventData->mScrollAfterUpdateCursorPosition = true;
+ }
}
+ mEventData->mDecoratorUpdated = true;
}
- else if( GRAB_HANDLE_SCROLLING == state )
+ else if( HANDLE_SCROLLING == state )
{
const float xSpeed = event.p2.mFloat;
const Vector2& actualSize = mVisualModel->GetActualSize();
mEventData->mScrollPosition.x += xSpeed;
ClampHorizontalScroll( actualSize );
+
+ mEventData->mDecoratorUpdated = true;
}
}
const Vector<LineRun>& lines = mVisualModel->mLines;
float height = lines.Count() ? lines[0].ascender + -lines[0].descender : 0.0f;
- mEventData->mDecorator->SetPosition( PRIMARY_SELECTION_HANDLE, primaryX, mEventData->mScrollPosition.y, height );
- mEventData->mDecorator->SetPosition( SECONDARY_SELECTION_HANDLE, secondaryX, mEventData->mScrollPosition.y, height );
+ mEventData->mDecorator->SetPosition( LEFT_SELECTION_HANDLE, primaryX, mEventData->mScrollPosition.y, height );
+ mEventData->mDecorator->SetPosition( RIGHT_SELECTION_HANDLE, secondaryX, mEventData->mScrollPosition.y, height );
mEventData->mDecorator->ClearHighlights();
mEventData->mDecorator->AddHighlight( primaryX, mEventData->mScrollPosition.y, secondaryX, height + mEventData->mScrollPosition.y );
{
mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
mEventData->mDecorator->StopCursorBlink();
- mEventData->mDecorator->SetGrabHandleActive( false );
- mEventData->mDecorator->SetSelectionActive( false );
+ mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
+ mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
+ mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
mEventData->mDecorator->SetPopupActive( false );
mEventData->mDecoratorUpdated = true;
}
{
mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
mEventData->mDecorator->StopCursorBlink();
- mEventData->mDecorator->SetGrabHandleActive( false );
- mEventData->mDecorator->SetSelectionActive( true );
+ mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
+ mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true );
+ mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true );
mEventData->mDecoratorUpdated = true;
}
else if( EventData::EDITING == mEventData->mState )
{
mEventData->mDecorator->StartCursorBlink();
}
- if( mEventData->mGrabHandleEnabled )
- {
- mEventData->mDecorator->SetGrabHandleActive( true );
- }
- if( mEventData->mGrabHandlePopupEnabled )
- {
- mEventData->mDecorator->SetPopupActive( false );
- }
- mEventData->mDecorator->SetSelectionActive( false );
+ // Grab handle is not shown until a tap is received whilst EDITING
+ mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
+ mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
+ mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
mEventData->mDecoratorUpdated = true;
}
else if( EventData::EDITING_WITH_POPUP == mEventData->mState )
{
mEventData->mDecorator->StartCursorBlink();
}
- if( mEventData->mGrabHandleEnabled )
+ mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
+ if( mEventData->mSelectionEnabled )
{
- mEventData->mDecorator->SetGrabHandleActive( true );
+ mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true );
+ mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true );
}
if( mEventData->mGrabHandlePopupEnabled )
{
mEventData->mDecorator->SetPopupActive( true );
}
- mEventData->mDecorator->SetSelectionActive( false );
mEventData->mDecoratorUpdated = true;
}
}
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,
- cursorInfo.primaryPosition.x + mEventData->mScrollPosition.x + mAlignmentOffset.x,
- cursorInfo.primaryPosition.y + mEventData->mScrollPosition.y + mAlignmentOffset.y,
+ 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->SetActiveCursor( ACTIVE_CURSOR_BOTH );
mEventData->mDecorator->SetPosition( SECONDARY_CURSOR,
- cursorInfo.secondaryPosition.x + mEventData->mScrollPosition.x + mAlignmentOffset.x,
- cursorInfo.secondaryPosition.y + mEventData->mScrollPosition.y + mAlignmentOffset.y,
+ cursorInfo.secondaryPosition.x + offset.x,
+ cursorInfo.secondaryPosition.y + offset.y,
cursorInfo.secondaryCursorHeight,
cursorInfo.lineHeight );
}
{
mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
}
+}
- mEventData->mUpdateCursorPosition = false;
- mEventData->mDecoratorUpdated = true;
+void Controller::Impl::UpdateSelectionHandle( HandleType handleType )
+{
+ if( ( LEFT_SELECTION_HANDLE != handleType ) &&
+ ( RIGHT_SELECTION_HANDLE != 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.
+ mEventData->mDecorator->SetPosition( handleType,
+ cursorPosition.x,
+ cursorPosition.y,
+ cursorInfo.lineHeight );
}
void Controller::Impl::ClampHorizontalScroll( const Vector2& actualSize )