Merge "TextField added to programming guide" into tizen
[platform/core/uifw/dali-toolkit.git] / dali-toolkit / internal / text / text-controller-impl.cpp
1 /*
2  * Copyright (c) 2015 Samsung Electronics Co., Ltd.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  *
16  */
17
18 // CLASS HEADER
19 #include <dali-toolkit/internal/text/text-controller-impl.h>
20
21 // EXTERNAL INCLUDES
22 #include <dali/public-api/adaptor-framework/key.h>
23 #include <dali/integration-api/debug.h>
24
25 // INTERNAL INCLUDES
26 #include <dali-toolkit/internal/text/bidirectional-support.h>
27 #include <dali-toolkit/internal/text/character-set-conversion.h>
28 #include <dali-toolkit/internal/text/layouts/layout-parameters.h>
29 #include <dali-toolkit/internal/text/multi-language-support.h>
30 #include <dali-toolkit/internal/text/script-run.h>
31 #include <dali-toolkit/internal/text/segmentation.h>
32 #include <dali-toolkit/internal/text/shaper.h>
33 #include <dali-toolkit/internal/text/text-io.h>
34 #include <dali-toolkit/internal/text/text-view.h>
35
36 namespace
37 {
38
39 #if defined(DEBUG_ENABLED)
40   Debug::Filter* gLogFilter = Debug::Filter::New(Debug::Concise, true, "LOG_TEXT_CONTROLS");
41 #endif
42
43 /**
44  * @brief Some characters can be shaped in more than one glyph.
45  * This struct is used to retrieve metrics from these group of glyphs.
46  */
47 struct GlyphMetrics
48 {
49   GlyphMetrics()
50   : fontHeight( 0.f ),
51     advance( 0.f ),
52     ascender( 0.f ),
53     xBearing( 0.f )
54   {}
55
56   ~GlyphMetrics()
57   {}
58
59   float fontHeight; ///< The font's height of that glyphs.
60   float advance;    ///< The sum of all the advances of all the glyphs.
61   float ascender;   ///< The font's ascender.
62   float xBearing;   ///< The x bearing of the first glyph.
63 };
64
65 const std::string EMPTY_STRING("");
66
67 } // namespace
68
69 namespace Dali
70 {
71
72 namespace Toolkit
73 {
74
75 namespace Text
76 {
77
78 /**
79  * @brief Get some glyph's metrics of a group of glyphs formed as a result of shaping one character.
80  *
81  * @param[in] glyphIndex The index to the first glyph.
82  * @param[in] numberOfGlyphs The number of glyphs.
83  * @param[out] glyphMetrics Some glyph metrics (font height, advance, ascender and x bearing).
84  * @param[in]
85  * @param[in]
86  */
87 void GetGlyphsMetrics( GlyphIndex glyphIndex,
88                        Length numberOfGlyphs,
89                        GlyphMetrics& glyphMetrics,
90                        VisualModelPtr visualModel,
91                        TextAbstraction::FontClient& fontClient )
92 {
93   const GlyphInfo* glyphsBuffer = visualModel->mGlyphs.Begin();
94
95   const GlyphInfo& firstGlyph = *( glyphsBuffer + glyphIndex );
96
97   Text::FontMetrics fontMetrics;
98   fontClient.GetFontMetrics( firstGlyph.fontId, fontMetrics );
99
100   glyphMetrics.fontHeight = fontMetrics.height;
101   glyphMetrics.advance = firstGlyph.advance;
102   glyphMetrics.ascender = fontMetrics.ascender;
103   glyphMetrics.xBearing = firstGlyph.xBearing;
104
105   for( unsigned int i = 1u; i < numberOfGlyphs; ++i )
106   {
107     const GlyphInfo& glyphInfo = *( glyphsBuffer + glyphIndex + i );
108
109     glyphMetrics.advance += glyphInfo.advance;
110   }
111 }
112
113 EventData::EventData( DecoratorPtr decorator )
114 : mDecorator( decorator ),
115   mPlaceholderTextActive(),
116   mPlaceholderTextInactive(),
117   mPlaceholderTextColor( 0.8f, 0.8f, 0.8f, 0.8f ),
118   mEventQueue(),
119   mScrollPosition(),
120   mState( INACTIVE ),
121   mPrimaryCursorPosition( 0u ),
122   mLeftSelectionPosition( 0u ),
123   mRightSelectionPosition( 0u ),
124   mPreEditStartPosition( 0u ),
125   mPreEditLength( 0u ),
126   mIsShowingPlaceholderText( false ),
127   mPreEditFlag( false ),
128   mDecoratorUpdated( false ),
129   mCursorBlinkEnabled( true ),
130   mGrabHandleEnabled( true ),
131   mGrabHandlePopupEnabled( false ),
132   mSelectionEnabled( false ),
133   mHorizontalScrollingEnabled( true ),
134   mVerticalScrollingEnabled( false ),
135   mUpdateCursorPosition( false ),
136   mUpdateLeftSelectionPosition( false ),
137   mUpdateRightSelectionPosition( false ),
138   mScrollAfterUpdateCursorPosition( false )
139 {}
140
141 EventData::~EventData()
142 {}
143
144 bool Controller::Impl::ProcessInputEvents()
145 {
146   DALI_LOG_INFO( gLogFilter, Debug::Verbose, "-->Controller::ProcessInputEvents\n" );
147   if( NULL == mEventData )
148   {
149     // Nothing to do if there is no text input.
150     DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::ProcessInputEvents no event data\n" );
151     return false;
152   }
153
154   if( mEventData->mDecorator )
155   {
156     for( std::vector<Event>::iterator iter = mEventData->mEventQueue.begin();
157          iter != mEventData->mEventQueue.end();
158          ++iter )
159     {
160       switch( iter->type )
161       {
162       case Event::CURSOR_KEY_EVENT:
163       {
164         OnCursorKeyEvent( *iter );
165         break;
166       }
167       case Event::TAP_EVENT:
168       {
169         OnTapEvent( *iter );
170         break;
171       }
172       case Event::PAN_EVENT:
173       {
174         OnPanEvent( *iter );
175         break;
176       }
177       case Event::GRAB_HANDLE_EVENT:
178       case Event::LEFT_SELECTION_HANDLE_EVENT:
179       case Event::RIGHT_SELECTION_HANDLE_EVENT: // Fall through
180       {
181         OnHandleEvent( *iter );
182         break;
183       }
184       }
185     }
186   }
187
188   // The cursor must also be repositioned after inserts into the model
189   if( mEventData->mUpdateCursorPosition )
190   {
191     // Updates the cursor position and scrolls the text to make it visible.
192
193     UpdateCursorPosition();
194
195     if( mEventData->mScrollAfterUpdateCursorPosition )
196     {
197       ScrollToMakeCursorVisible();
198       mEventData->mScrollAfterUpdateCursorPosition = false;
199     }
200
201     mEventData->mDecoratorUpdated = true;
202     mEventData->mUpdateCursorPosition = false;
203   }
204   else if( mEventData->mUpdateLeftSelectionPosition )
205   {
206     UpdateSelectionHandle( LEFT_SELECTION_HANDLE );
207
208     if( mEventData->mScrollAfterUpdateCursorPosition )
209     {
210       ScrollToMakeCursorVisible();
211       mEventData->mScrollAfterUpdateCursorPosition = false;
212     }
213
214     mEventData->mDecoratorUpdated = true;
215     mEventData->mUpdateLeftSelectionPosition = false;
216   }
217   else if( mEventData->mUpdateRightSelectionPosition )
218   {
219     UpdateSelectionHandle( RIGHT_SELECTION_HANDLE );
220
221     if( mEventData->mScrollAfterUpdateCursorPosition )
222     {
223       ScrollToMakeCursorVisible();
224       mEventData->mScrollAfterUpdateCursorPosition = false;
225     }
226
227     mEventData->mDecoratorUpdated = true;
228     mEventData->mUpdateRightSelectionPosition = false;
229   }
230
231   mEventData->mEventQueue.clear();
232
233   DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::ProcessInputEvents\n" );
234
235   bool decoratorUpdated = mEventData->mDecoratorUpdated;
236   mEventData->mDecoratorUpdated = false;
237   return decoratorUpdated;
238 }
239
240 void Controller::Impl::UpdateModel( OperationsMask operationsRequired )
241 {
242   // Calculate the operations to be done.
243   const OperationsMask operations = static_cast<OperationsMask>( mOperationsPending & operationsRequired );
244
245   Vector<Character>& utf32Characters = mLogicalModel->mText;
246
247   const Length numberOfCharacters = utf32Characters.Count();
248
249   Vector<LineBreakInfo>& lineBreakInfo = mLogicalModel->mLineBreakInfo;
250   if( GET_LINE_BREAKS & operations )
251   {
252     // Retrieves the line break info. The line break info is used to split the text in 'paragraphs' to
253     // calculate the bidirectional info for each 'paragraph'.
254     // It's also used to layout the text (where it should be a new line) or to shape the text (text in different lines
255     // is not shaped together).
256     lineBreakInfo.Resize( numberOfCharacters, TextAbstraction::LINE_NO_BREAK );
257
258     SetLineBreakInfo( utf32Characters,
259                       lineBreakInfo );
260   }
261
262   Vector<WordBreakInfo>& wordBreakInfo = mLogicalModel->mWordBreakInfo;
263   if( GET_WORD_BREAKS & operations )
264   {
265     // Retrieves the word break info. The word break info is used to layout the text (where to wrap the text in lines).
266     wordBreakInfo.Resize( numberOfCharacters, TextAbstraction::WORD_NO_BREAK );
267
268     SetWordBreakInfo( utf32Characters,
269                       wordBreakInfo );
270   }
271
272   const bool getScripts = GET_SCRIPTS & operations;
273   const bool validateFonts = VALIDATE_FONTS & operations;
274
275   Vector<ScriptRun>& scripts = mLogicalModel->mScriptRuns;
276   Vector<FontRun>& validFonts = mLogicalModel->mFontRuns;
277
278   if( getScripts || validateFonts )
279   {
280     // Validates the fonts assigned by the application or assigns default ones.
281     // It makes sure all the characters are going to be rendered by the correct font.
282     MultilanguageSupport multilanguageSupport = MultilanguageSupport::Get();
283
284     if( getScripts )
285     {
286       // Retrieves the scripts used in the text.
287       multilanguageSupport.SetScripts( utf32Characters,
288                                        lineBreakInfo,
289                                        scripts );
290     }
291
292     if( validateFonts )
293     {
294       if( 0u == validFonts.Count() )
295       {
296         // Copy the requested font defaults received via the property system.
297         // These may not be valid i.e. may not contain glyphs for the necessary scripts.
298         GetDefaultFonts( validFonts, numberOfCharacters );
299       }
300
301       // Validates the fonts. If there is a character with no assigned font it sets a default one.
302       // After this call, fonts are validated.
303       multilanguageSupport.ValidateFonts( utf32Characters,
304                                           scripts,
305                                           validFonts );
306     }
307   }
308
309   Vector<Character> mirroredUtf32Characters;
310   bool textMirrored = false;
311   if( BIDI_INFO & operations )
312   {
313     // Count the number of LINE_NO_BREAK to reserve some space for the vector of paragraph's
314     // bidirectional info.
315
316     Length numberOfParagraphs = 0u;
317
318     const TextAbstraction::LineBreakInfo* lineBreakInfoBuffer = lineBreakInfo.Begin();
319     for( Length index = 0u; index < numberOfCharacters; ++index )
320     {
321       if( TextAbstraction::LINE_NO_BREAK == *( lineBreakInfoBuffer + index ) )
322       {
323         ++numberOfParagraphs;
324       }
325     }
326
327     Vector<BidirectionalParagraphInfoRun>& bidirectionalInfo = mLogicalModel->mBidirectionalParagraphInfo;
328     bidirectionalInfo.Reserve( numberOfParagraphs );
329
330     // Calculates the bidirectional info for the whole paragraph if it contains right to left scripts.
331     SetBidirectionalInfo( utf32Characters,
332                           scripts,
333                           lineBreakInfo,
334                           bidirectionalInfo );
335
336     if( 0u != bidirectionalInfo.Count() )
337     {
338       // This paragraph has right to left text. Some characters may need to be mirrored.
339       // TODO: consider if the mirrored string can be stored as well.
340
341       textMirrored = GetMirroredText( utf32Characters, mirroredUtf32Characters );
342
343       // Only set the character directions if there is right to left characters.
344       Vector<CharacterDirection>& directions = mLogicalModel->mCharacterDirections;
345       directions.Resize( numberOfCharacters );
346
347       GetCharactersDirection( bidirectionalInfo,
348                               directions );
349     }
350     else
351     {
352       // There is no right to left characters. Clear the directions vector.
353       mLogicalModel->mCharacterDirections.Clear();
354     }
355
356    }
357
358   Vector<GlyphInfo>& glyphs = mVisualModel->mGlyphs;
359   Vector<CharacterIndex>& glyphsToCharactersMap = mVisualModel->mGlyphsToCharacters;
360   Vector<Length>& charactersPerGlyph = mVisualModel->mCharactersPerGlyph;
361   if( SHAPE_TEXT & operations )
362   {
363     const Vector<Character>& textToShape = textMirrored ? mirroredUtf32Characters : utf32Characters;
364     // Shapes the text.
365     ShapeText( textToShape,
366                lineBreakInfo,
367                scripts,
368                validFonts,
369                glyphs,
370                glyphsToCharactersMap,
371                charactersPerGlyph );
372
373     // Create the 'number of glyphs' per character and the glyph to character conversion tables.
374     mVisualModel->CreateGlyphsPerCharacterTable( numberOfCharacters );
375     mVisualModel->CreateCharacterToGlyphTable( numberOfCharacters );
376   }
377
378   const Length numberOfGlyphs = glyphs.Count();
379
380   if( GET_GLYPH_METRICS & operations )
381   {
382     mFontClient.GetGlyphMetrics( glyphs.Begin(), numberOfGlyphs );
383   }
384 }
385
386 void Controller::Impl::GetDefaultFonts( Vector<FontRun>& fonts, Length numberOfCharacters )
387 {
388   if( mFontDefaults )
389   {
390     FontRun fontRun;
391     fontRun.characterRun.characterIndex = 0;
392     fontRun.characterRun.numberOfCharacters = numberOfCharacters;
393     fontRun.fontId = mFontDefaults->GetFontId( mFontClient );
394     fontRun.isDefault = true;
395
396     fonts.PushBack( fontRun );
397   }
398 }
399
400 void Controller::Impl::OnCursorKeyEvent( const Event& event )
401 {
402   if( NULL == mEventData )
403   {
404     // Nothing to do if there is no text input.
405     return;
406   }
407
408   int keyCode = event.p1.mInt;
409
410   if( Dali::DALI_KEY_CURSOR_LEFT == keyCode )
411   {
412     if( mEventData->mPrimaryCursorPosition > 0u )
413     {
414       mEventData->mPrimaryCursorPosition = CalculateNewCursorIndex( mEventData->mPrimaryCursorPosition - 1u );
415     }
416   }
417   else if( Dali::DALI_KEY_CURSOR_RIGHT == keyCode )
418   {
419     if( mLogicalModel->GetNumberOfCharacters() > mEventData->mPrimaryCursorPosition )
420     {
421       mEventData->mPrimaryCursorPosition = CalculateNewCursorIndex( mEventData->mPrimaryCursorPosition );
422     }
423   }
424   else if( Dali::DALI_KEY_CURSOR_UP == keyCode )
425   {
426     // TODO
427   }
428   else if(   Dali::DALI_KEY_CURSOR_DOWN == keyCode )
429   {
430     // TODO
431   }
432
433   mEventData->mUpdateCursorPosition = true;
434   mEventData->mScrollAfterUpdateCursorPosition = true;
435 }
436
437 void Controller::Impl::OnTapEvent( const Event& event )
438 {
439   if( NULL != mEventData )
440   {
441     const unsigned int tapCount = event.p1.mUint;
442
443     if( 1u == tapCount )
444     {
445       if( ! IsShowingPlaceholderText() )
446       {
447         const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
448         const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
449
450         mEventData->mPrimaryCursorPosition = GetClosestCursorIndex( xPosition,
451                                                                     yPosition );
452       }
453       else
454       {
455         mEventData->mPrimaryCursorPosition = 0u;
456       }
457
458       mEventData->mUpdateCursorPosition = true;
459       mEventData->mScrollAfterUpdateCursorPosition = true;
460     }
461     else if( mEventData->mSelectionEnabled &&
462              ( 2u == tapCount ) )
463     {
464       RepositionSelectionHandles( event.p2.mFloat, event.p3.mFloat );
465     }
466   }
467 }
468
469 void Controller::Impl::OnPanEvent( const Event& event )
470 {
471   if( NULL == mEventData )
472   {
473     // Nothing to do if there is no text input.
474     return;
475   }
476
477   int state = event.p1.mInt;
478
479   if( Gesture::Started    == state ||
480       Gesture::Continuing == state )
481   {
482     const Vector2& actualSize = mVisualModel->GetActualSize();
483     const Vector2 currentScroll = mEventData->mScrollPosition;
484
485     if( mEventData->mHorizontalScrollingEnabled )
486     {
487       const float displacementX = event.p2.mFloat;
488       mEventData->mScrollPosition.x += displacementX;
489
490       ClampHorizontalScroll( actualSize );
491     }
492
493     if( mEventData->mVerticalScrollingEnabled )
494     {
495       const float displacementY = event.p3.mFloat;
496       mEventData->mScrollPosition.y += displacementY;
497
498       ClampVerticalScroll( actualSize );
499     }
500
501     if( mEventData->mDecorator )
502     {
503       mEventData->mDecorator->UpdatePositions( mEventData->mScrollPosition - currentScroll );
504     }
505   }
506 }
507
508 void Controller::Impl::OnHandleEvent( const Event& event )
509 {
510   if( NULL == mEventData )
511   {
512     // Nothing to do if there is no text input.
513     return;
514   }
515
516   const unsigned int state = event.p1.mUint;
517
518   if( HANDLE_PRESSED == state )
519   {
520     // The event.p2 and event.p3 are in decorator coords. Need to transforms to text coords.
521     const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
522     const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
523
524     const CharacterIndex handleNewPosition = GetClosestCursorIndex( xPosition, yPosition );
525
526     if( Event::GRAB_HANDLE_EVENT == event.type )
527     {
528       ChangeState ( EventData::EDITING );
529
530       if( handleNewPosition != mEventData->mPrimaryCursorPosition )
531       {
532         mEventData->mPrimaryCursorPosition = handleNewPosition;
533         mEventData->mUpdateCursorPosition = true;
534       }
535     }
536     else if( Event::LEFT_SELECTION_HANDLE_EVENT == event.type )
537     {
538       if( handleNewPosition != mEventData->mLeftSelectionPosition )
539       {
540         mEventData->mLeftSelectionPosition = handleNewPosition;
541         mEventData->mUpdateLeftSelectionPosition = true;
542       }
543     }
544     else if( Event::RIGHT_SELECTION_HANDLE_EVENT == event.type )
545     {
546       if( handleNewPosition != mEventData->mRightSelectionPosition )
547       {
548         mEventData->mRightSelectionPosition = handleNewPosition;
549         mEventData->mUpdateRightSelectionPosition = true;
550       }
551     }
552   }
553   else if( ( HANDLE_RELEASED == state ) ||
554            ( HANDLE_STOP_SCROLLING == state ) )
555   {
556     if( mEventData->mGrabHandlePopupEnabled )
557     {
558       ChangeState( EventData::EDITING_WITH_POPUP );
559     }
560     if( Event::GRAB_HANDLE_EVENT == event.type )
561     {
562       mEventData->mUpdateCursorPosition = true;
563
564       if( HANDLE_STOP_SCROLLING == state )
565       {
566         // The event.p2 and event.p3 are in decorator coords. Need to transforms to text coords.
567         const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
568         const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
569
570         mEventData->mPrimaryCursorPosition = GetClosestCursorIndex( xPosition, yPosition );
571
572         mEventData->mScrollAfterUpdateCursorPosition = true;
573       }
574     }
575     mEventData->mDecoratorUpdated = true;
576   }
577   else if( HANDLE_SCROLLING == state )
578   {
579     const float xSpeed = event.p2.mFloat;
580     const Vector2& actualSize = mVisualModel->GetActualSize();
581
582     mEventData->mScrollPosition.x += xSpeed;
583
584     ClampHorizontalScroll( actualSize );
585
586    mEventData->mDecoratorUpdated = true;
587   }
588 }
589
590 void Controller::Impl::RepositionSelectionHandles( float visualX, float visualY )
591 {
592   if( NULL == mEventData )
593   {
594     // Nothing to do if there is no text input.
595     return;
596   }
597
598   // TODO - Find which word was selected
599
600   const Vector<GlyphInfo>& glyphs = mVisualModel->mGlyphs;
601   const Vector<Vector2>::SizeType glyphCount = glyphs.Count();
602
603   const Vector<Vector2>& positions = mVisualModel->mGlyphPositions;
604   const Vector<Vector2>::SizeType positionCount = positions.Count();
605
606   // Guard against glyphs which did not fit inside the layout
607   const Vector<Vector2>::SizeType count = (positionCount < glyphCount) ? positionCount : glyphCount;
608
609   if( count )
610   {
611     float primaryX   = positions[0].x + mEventData->mScrollPosition.x;
612     float secondaryX = positions[count-1].x + glyphs[count-1].width + mEventData->mScrollPosition.x;
613
614     // TODO - multi-line selection
615     const Vector<LineRun>& lines = mVisualModel->mLines;
616     float height = lines.Count() ? lines[0].ascender + -lines[0].descender : 0.0f;
617
618     mEventData->mDecorator->SetPosition( LEFT_SELECTION_HANDLE,     primaryX, mEventData->mScrollPosition.y, height );
619     mEventData->mDecorator->SetPosition( RIGHT_SELECTION_HANDLE, secondaryX, mEventData->mScrollPosition.y, height );
620
621     mEventData->mDecorator->ClearHighlights();
622     mEventData->mDecorator->AddHighlight( primaryX, mEventData->mScrollPosition.y, secondaryX, height + mEventData->mScrollPosition.y );
623   }
624 }
625
626 void Controller::Impl::ChangeState( EventData::State newState )
627 {
628   if( NULL == mEventData )
629   {
630     // Nothing to do if there is no text input.
631     return;
632   }
633
634   if( mEventData->mState != newState )
635   {
636     mEventData->mState = newState;
637
638     if( EventData::INACTIVE == mEventData->mState )
639     {
640       mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
641       mEventData->mDecorator->StopCursorBlink();
642       mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
643       mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
644       mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
645       mEventData->mDecorator->SetPopupActive( false );
646       mEventData->mDecoratorUpdated = true;
647     }
648     else if ( EventData::SELECTING == mEventData->mState )
649     {
650       mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
651       mEventData->mDecorator->StopCursorBlink();
652       mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
653       mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true );
654       mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true );
655       mEventData->mDecoratorUpdated = true;
656     }
657     else if( EventData::EDITING == mEventData->mState )
658     {
659       mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
660       if( mEventData->mCursorBlinkEnabled )
661       {
662         mEventData->mDecorator->StartCursorBlink();
663       }
664       // Grab handle is not shown until a tap is received whilst EDITING
665       mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
666       mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
667       mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
668       mEventData->mDecoratorUpdated = true;
669     }
670     else if( EventData::EDITING_WITH_POPUP == mEventData->mState )
671     {
672       mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
673       if( mEventData->mCursorBlinkEnabled )
674       {
675         mEventData->mDecorator->StartCursorBlink();
676       }
677       mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
678       if( mEventData->mSelectionEnabled )
679       {
680         mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true );
681         mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true );
682       }
683       if( mEventData->mGrabHandlePopupEnabled )
684       {
685         mEventData->mDecorator->SetPopupActive( true );
686       }
687       mEventData->mDecoratorUpdated = true;
688     }
689   }
690 }
691
692 LineIndex Controller::Impl::GetClosestLine( float y ) const
693 {
694   float totalHeight = 0.f;
695   LineIndex lineIndex = 0u;
696
697   const Vector<LineRun>& lines = mVisualModel->mLines;
698   for( LineIndex endLine = lines.Count();
699        lineIndex < endLine;
700        ++lineIndex )
701   {
702     const LineRun& lineRun = lines[lineIndex];
703     totalHeight += lineRun.ascender + -lineRun.descender;
704     if( y < totalHeight )
705     {
706       return lineIndex;
707     }
708   }
709
710   return lineIndex-1;
711 }
712
713 CharacterIndex Controller::Impl::GetClosestCursorIndex( float visualX,
714                                                         float visualY )
715 {
716   if( NULL == mEventData )
717   {
718     // Nothing to do if there is no text input.
719     return 0u;
720   }
721
722   CharacterIndex logicalIndex = 0u;
723
724   const Length numberOfGlyphs = mVisualModel->mGlyphs.Count();
725   const Length numberOfLines  = mVisualModel->mLines.Count();
726   if( 0 == numberOfGlyphs ||
727       0 == numberOfLines )
728   {
729     return logicalIndex;
730   }
731
732   // Find which line is closest
733   const LineIndex lineIndex = GetClosestLine( visualY );
734   const LineRun& line = mVisualModel->mLines[lineIndex];
735
736   // Get the positions of the glyphs.
737   const Vector<Vector2>& positions = mVisualModel->mGlyphPositions;
738   const Vector2* const positionsBuffer = positions.Begin();
739
740   // Get the visual to logical conversion tables.
741   const CharacterIndex* const visualToLogicalBuffer = ( 0u != mLogicalModel->mVisualToLogicalMap.Count() ) ? mLogicalModel->mVisualToLogicalMap.Begin() : NULL;
742   const CharacterIndex* const visualToLogicalCursorBuffer = mLogicalModel->mVisualToLogicalCursorMap.Begin();
743
744   // Get the character to glyph conversion table.
745   const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
746
747   // Get the glyphs per character table.
748   const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
749
750   // If the vector is void, there is no right to left characters.
751   const bool hasRightToLeftCharacters = NULL != visualToLogicalBuffer;
752
753   const CharacterIndex startCharacter = line.characterRun.characterIndex;
754   const CharacterIndex endCharacter   = line.characterRun.characterIndex + line.characterRun.numberOfCharacters;
755   DALI_ASSERT_DEBUG( endCharacter <= mLogicalModel->mText.Count() && "Invalid line info" );
756
757   // Whether there is a hit on a glyph.
758   bool matched = false;
759
760   // Traverses glyphs in visual order. To do that use the visual to logical conversion table.
761   CharacterIndex visualIndex = startCharacter;
762   for( ; !matched && ( visualIndex < endCharacter ); ++visualIndex )
763   {
764     // The character in logical order.
765     const CharacterIndex characterLogicalOrderIndex = hasRightToLeftCharacters ? *( visualToLogicalBuffer + visualIndex ) : visualIndex;
766
767     // The first glyph for that character in logical order.
768     const GlyphIndex glyphLogicalOrderIndex = *( charactersToGlyphBuffer + characterLogicalOrderIndex );
769
770     // The number of glyphs for that character
771     const Length numberOfGlyphs = *( glyphsPerCharacterBuffer + characterLogicalOrderIndex );
772
773     // Get the metrics for the group of glyphs.
774     GlyphMetrics glyphMetrics;
775     GetGlyphsMetrics( glyphLogicalOrderIndex,
776                       numberOfGlyphs,
777                       glyphMetrics,
778                       mVisualModel,
779                       mFontClient );
780
781     const Vector2& position = *( positionsBuffer + glyphLogicalOrderIndex );
782
783     const float glyphX = -glyphMetrics.xBearing + position.x + 0.5f * glyphMetrics.advance;
784
785     if( visualX < glyphX )
786     {
787       matched = true;
788       break;
789     }
790   }
791
792   // Return the logical position of the cursor in characters.
793
794   if( !matched )
795   {
796     visualIndex = endCharacter;
797   }
798
799   return hasRightToLeftCharacters ? *( visualToLogicalCursorBuffer + visualIndex ) : visualIndex;
800 }
801
802 void Controller::Impl::GetCursorPosition( CharacterIndex logical,
803                                           CursorInfo& cursorInfo )
804 {
805   // TODO: Check for multiline with \n, etc...
806
807   // Check if the logical position is the first or the last one of the text.
808   const bool isFirstPosition = 0u == logical;
809   const bool isLastPosition = mLogicalModel->GetNumberOfCharacters() == logical;
810
811   if( isFirstPosition && isLastPosition )
812   {
813     // There is zero characters. Get the default font.
814
815     FontId defaultFontId = 0u;
816     if( NULL == mFontDefaults )
817     {
818       defaultFontId = mFontClient.GetFontId( EMPTY_STRING,
819                                              EMPTY_STRING );
820     }
821     else
822     {
823       defaultFontId = mFontDefaults->GetFontId( mFontClient );
824     }
825
826     Text::FontMetrics fontMetrics;
827     mFontClient.GetFontMetrics( defaultFontId, fontMetrics );
828
829     cursorInfo.lineHeight = fontMetrics.ascender - fontMetrics.descender;
830     cursorInfo.primaryCursorHeight = cursorInfo.lineHeight;
831
832     cursorInfo.primaryPosition.x = 0.f;
833     cursorInfo.primaryPosition.y = 0.f;
834
835     // Nothing else to do.
836     return;
837   }
838
839   // Get the previous logical index.
840   const CharacterIndex previousLogical = isFirstPosition ? 0u : logical - 1u;
841
842   // Decrease the logical index if it's the last one.
843   if( isLastPosition )
844   {
845     --logical;
846   }
847
848   // Get the direction of the character and the previous one.
849   const CharacterDirection* const modelCharacterDirectionsBuffer = ( 0u != mLogicalModel->mCharacterDirections.Count() ) ? mLogicalModel->mCharacterDirections.Begin() : NULL;
850
851   CharacterDirection isCurrentRightToLeft = false;
852   CharacterDirection isPreviousRightToLeft = false;
853   if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
854   {
855     isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + logical );
856     isPreviousRightToLeft = *( modelCharacterDirectionsBuffer + previousLogical );
857   }
858
859   // Get the line where the character is laid-out.
860   const LineRun* modelLines = mVisualModel->mLines.Begin();
861
862   const LineIndex lineIndex = mVisualModel->GetLineOfCharacter( logical );
863   const LineRun& line = *( modelLines + lineIndex );
864
865   // Get the paragraph's direction.
866   const CharacterDirection isRightToLeftParagraph = line.direction;
867
868   // Check whether there is an alternative position:
869
870   cursorInfo.isSecondaryCursor = ( isCurrentRightToLeft != isPreviousRightToLeft ) ||
871     ( isLastPosition && ( isRightToLeftParagraph != isCurrentRightToLeft ) );
872
873   // Set the line height.
874   cursorInfo.lineHeight = line.ascender + -line.descender;
875
876   // Convert the cursor position into the glyph position.
877   CharacterIndex characterIndex = logical;
878   if( cursorInfo.isSecondaryCursor &&
879       ( isRightToLeftParagraph != isCurrentRightToLeft ) )
880   {
881     characterIndex = previousLogical;
882   }
883
884   const GlyphIndex currentGlyphIndex = *( mVisualModel->mCharactersToGlyph.Begin() + characterIndex );
885   const Length numberOfGlyphs = *( mVisualModel->mGlyphsPerCharacter.Begin() + characterIndex );
886   const Length numberOfCharacters = *( mVisualModel->mCharactersPerGlyph.Begin() +currentGlyphIndex );
887
888   // Get the metrics for the group of glyphs.
889   GlyphMetrics glyphMetrics;
890   GetGlyphsMetrics( currentGlyphIndex,
891                     numberOfGlyphs,
892                     glyphMetrics,
893                     mVisualModel,
894                     mFontClient );
895
896   float interGlyphAdvance = 0.f;
897   if( !isLastPosition &&
898       ( numberOfCharacters > 1u ) )
899   {
900     const CharacterIndex firstIndex = *( mVisualModel->mGlyphsToCharacters.Begin() + currentGlyphIndex );
901     interGlyphAdvance = static_cast<float>( characterIndex - firstIndex ) * glyphMetrics.advance / static_cast<float>( numberOfCharacters );
902   }
903
904   // Get the glyph position and x bearing.
905   const Vector2& currentPosition = *( mVisualModel->mGlyphPositions.Begin() + currentGlyphIndex );
906
907   // Set the cursor's height.
908   cursorInfo.primaryCursorHeight = glyphMetrics.fontHeight;
909
910   // Set the position.
911   cursorInfo.primaryPosition.x = -glyphMetrics.xBearing + currentPosition.x + ( isCurrentRightToLeft ? glyphMetrics.advance : interGlyphAdvance );
912   cursorInfo.primaryPosition.y = line.ascender - glyphMetrics.ascender;
913
914   if( isLastPosition )
915   {
916     // The position of the cursor after the last character needs special
917     // care depending on its direction and the direction of the paragraph.
918
919     if( cursorInfo.isSecondaryCursor )
920     {
921       // Need to find the first character after the last character with the paragraph's direction.
922       // i.e l0 l1 l2 r0 r1 should find r0.
923
924       // TODO: check for more than one line!
925       characterIndex = isRightToLeftParagraph ? line.characterRun.characterIndex : line.characterRun.characterIndex + line.characterRun.numberOfCharacters - 1u;
926       characterIndex = mLogicalModel->GetLogicalCharacterIndex( characterIndex );
927
928       const GlyphIndex glyphIndex = *( mVisualModel->mCharactersToGlyph.Begin() + characterIndex );
929       const Length numberOfGlyphs = *( mVisualModel->mGlyphsPerCharacter.Begin() + characterIndex );
930
931       const Vector2& position = *( mVisualModel->mGlyphPositions.Begin() + glyphIndex );
932
933       // Get the metrics for the group of glyphs.
934       GlyphMetrics glyphMetrics;
935       GetGlyphsMetrics( glyphIndex,
936                         numberOfGlyphs,
937                         glyphMetrics,
938                         mVisualModel,
939                         mFontClient );
940
941       cursorInfo.primaryPosition.x = -glyphMetrics.xBearing + position.x + ( isRightToLeftParagraph ? 0.f : glyphMetrics.advance );
942
943       cursorInfo.primaryPosition.y = line.ascender - glyphMetrics.ascender;
944     }
945     else
946     {
947       if( !isCurrentRightToLeft )
948       {
949         cursorInfo.primaryPosition.x += glyphMetrics.advance;
950       }
951       else
952       {
953         cursorInfo.primaryPosition.x -= glyphMetrics.advance;
954       }
955     }
956   }
957
958   // Set the alternative cursor position.
959   if( cursorInfo.isSecondaryCursor )
960   {
961     // Convert the cursor position into the glyph position.
962     const CharacterIndex previousCharacterIndex = ( ( isRightToLeftParagraph != isCurrentRightToLeft ) ? logical : previousLogical );
963     const GlyphIndex previousGlyphIndex = *( mVisualModel->mCharactersToGlyph.Begin() + previousCharacterIndex );
964     const Length numberOfGlyphs = *( mVisualModel->mGlyphsPerCharacter.Begin() + previousCharacterIndex );
965
966     // Get the glyph position.
967     const Vector2& previousPosition = *( mVisualModel->mGlyphPositions.Begin() + previousGlyphIndex );
968
969     // Get the metrics for the group of glyphs.
970     GlyphMetrics glyphMetrics;
971     GetGlyphsMetrics( previousGlyphIndex,
972                       numberOfGlyphs,
973                       glyphMetrics,
974                       mVisualModel,
975                       mFontClient );
976
977     // Set the cursor position and height.
978     cursorInfo.secondaryPosition.x = -glyphMetrics.xBearing + previousPosition.x + ( ( ( isLastPosition && !isCurrentRightToLeft ) ||
979                                                                                        ( !isLastPosition && isCurrentRightToLeft )    ) ? glyphMetrics.advance : 0.f );
980
981     cursorInfo.secondaryCursorHeight = 0.5f * glyphMetrics.fontHeight;
982
983     cursorInfo.secondaryPosition.y = cursorInfo.lineHeight - cursorInfo.secondaryCursorHeight - line.descender - ( glyphMetrics.fontHeight - glyphMetrics.ascender );
984
985     // Update the primary cursor height as well.
986     cursorInfo.primaryCursorHeight *= 0.5f;
987   }
988 }
989
990 CharacterIndex Controller::Impl::CalculateNewCursorIndex( CharacterIndex index ) const
991 {
992   if( NULL == mEventData )
993   {
994     // Nothing to do if there is no text input.
995     return 0u;
996   }
997
998   CharacterIndex cursorIndex = mEventData->mPrimaryCursorPosition;
999
1000   const Script script = mLogicalModel->GetScript( index );
1001   const GlyphIndex* charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
1002   const Length* charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
1003
1004   Length numberOfCharacters = 0u;
1005   if( TextAbstraction::LATIN == script )
1006   {
1007     // Prevents to jump the whole Latin ligatures like fi, ff, ...
1008     numberOfCharacters = 1u;
1009   }
1010   else
1011   {
1012     GlyphIndex glyphIndex = *( charactersToGlyphBuffer + index );
1013     numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
1014
1015     while( 0u == numberOfCharacters )
1016     {
1017       numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
1018       ++glyphIndex;
1019     }
1020   }
1021
1022   if( index < mEventData->mPrimaryCursorPosition )
1023   {
1024     cursorIndex -= numberOfCharacters;
1025   }
1026   else
1027   {
1028     cursorIndex += numberOfCharacters;
1029   }
1030
1031   return cursorIndex;
1032 }
1033
1034 void Controller::Impl::UpdateCursorPosition()
1035 {
1036   DALI_LOG_INFO( gLogFilter, Debug::Verbose, "-->Controller::UpdateCursorPosition %p\n", this );
1037   if( NULL == mEventData )
1038   {
1039     // Nothing to do if there is no text input.
1040     DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition no event data\n" );
1041     return;
1042   }
1043
1044   if( IsShowingPlaceholderText() )
1045   {
1046     // Do not want to use the place-holder text to set the cursor position.
1047
1048     // Use the line's height of the font's family set to set the cursor's size.
1049     // If there is no font's family set, use the default font.
1050     // Use the current alignment to place the cursor at the beginning, center or end of the box.
1051
1052     float lineHeight = 0.f;
1053
1054     FontId defaultFontId = 0u;
1055     if( NULL == mFontDefaults )
1056     {
1057       defaultFontId = mFontClient.GetFontId( EMPTY_STRING,
1058                                              EMPTY_STRING );
1059     }
1060     else
1061     {
1062       defaultFontId = mFontDefaults->GetFontId( mFontClient );
1063     }
1064
1065     Text::FontMetrics fontMetrics;
1066     mFontClient.GetFontMetrics( defaultFontId, fontMetrics );
1067
1068     lineHeight = fontMetrics.ascender - fontMetrics.descender;
1069
1070
1071     Vector2 cursorPosition;
1072
1073     switch( mLayoutEngine.GetHorizontalAlignment() )
1074     {
1075       case LayoutEngine::HORIZONTAL_ALIGN_BEGIN:
1076       {
1077         cursorPosition.x = 1.f;
1078         break;
1079       }
1080       case LayoutEngine::HORIZONTAL_ALIGN_CENTER:
1081       {
1082         cursorPosition.x = floor( 0.5f * mControlSize.width );
1083         break;
1084       }
1085       case LayoutEngine::HORIZONTAL_ALIGN_END:
1086       {
1087         cursorPosition.x = mControlSize.width;
1088         break;
1089       }
1090     }
1091
1092     switch( mLayoutEngine.GetVerticalAlignment() )
1093     {
1094       case LayoutEngine::VERTICAL_ALIGN_TOP:
1095       {
1096         cursorPosition.y = 0.f;
1097         break;
1098       }
1099       case LayoutEngine::VERTICAL_ALIGN_CENTER:
1100       {
1101         cursorPosition.y = floorf( 0.5f * ( mControlSize.height - lineHeight ) );
1102         break;
1103       }
1104       case LayoutEngine::VERTICAL_ALIGN_BOTTOM:
1105       {
1106         cursorPosition.y = mControlSize.height - lineHeight;
1107         break;
1108       }
1109     }
1110
1111     mEventData->mDecorator->SetPosition( PRIMARY_CURSOR,
1112                                          cursorPosition.x,
1113                                          cursorPosition.y,
1114                                          lineHeight,
1115                                          lineHeight );
1116   }
1117   else
1118   {
1119     CursorInfo cursorInfo;
1120     GetCursorPosition( mEventData->mPrimaryCursorPosition,
1121                        cursorInfo );
1122
1123     const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
1124     const Vector2 cursorPosition = cursorInfo.primaryPosition + offset;
1125
1126     // Sets the cursor position.
1127     mEventData->mDecorator->SetPosition( PRIMARY_CURSOR,
1128                                          cursorPosition.x,
1129                                          cursorPosition.y,
1130                                          cursorInfo.primaryCursorHeight,
1131                                          cursorInfo.lineHeight );
1132     DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Primary cursor position: %f,%f\n", cursorPosition.x, cursorPosition.y );
1133
1134     // Sets the grab handle position.
1135     mEventData->mDecorator->SetPosition( GRAB_HANDLE,
1136                                          cursorPosition.x,
1137                                          cursorPosition.y,
1138                                          cursorInfo.lineHeight );
1139
1140     if( cursorInfo.isSecondaryCursor )
1141     {
1142       mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_BOTH );
1143       mEventData->mDecorator->SetPosition( SECONDARY_CURSOR,
1144                                            cursorInfo.secondaryPosition.x + offset.x,
1145                                            cursorInfo.secondaryPosition.y + offset.y,
1146                                            cursorInfo.secondaryCursorHeight,
1147                                            cursorInfo.lineHeight );
1148       DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Secondary cursor position: %f,%f\n", cursorInfo.secondaryPosition.x + offset.x, cursorInfo.secondaryPosition.y + offset.y );
1149     }
1150     else
1151     {
1152       mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1153     }
1154   }
1155   DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition\n" );
1156 }
1157
1158 void Controller::Impl::UpdateSelectionHandle( HandleType handleType )
1159 {
1160   if( ( LEFT_SELECTION_HANDLE != handleType ) &&
1161       ( RIGHT_SELECTION_HANDLE != handleType ) )
1162   {
1163     return;
1164   }
1165
1166   const bool leftSelectionHandle = LEFT_SELECTION_HANDLE == handleType;
1167   const CharacterIndex index = leftSelectionHandle ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition;
1168
1169   CursorInfo cursorInfo;
1170   GetCursorPosition( index,
1171                      cursorInfo );
1172
1173   const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
1174   const Vector2 cursorPosition = cursorInfo.primaryPosition + offset;
1175
1176   // Sets the grab handle position.
1177   mEventData->mDecorator->SetPosition( handleType,
1178                                        cursorPosition.x,
1179                                        cursorPosition.y,
1180                                        cursorInfo.lineHeight );
1181 }
1182
1183 void Controller::Impl::ClampHorizontalScroll( const Vector2& actualSize )
1184 {
1185   // Clamp between -space & 0 (and the text alignment).
1186   if( actualSize.width > mControlSize.width )
1187   {
1188     const float space = ( actualSize.width - mControlSize.width ) + mAlignmentOffset.x;
1189     mEventData->mScrollPosition.x = ( mEventData->mScrollPosition.x < -space ) ? -space : mEventData->mScrollPosition.x;
1190     mEventData->mScrollPosition.x = ( mEventData->mScrollPosition.x > -mAlignmentOffset.x ) ? -mAlignmentOffset.x : mEventData->mScrollPosition.x;
1191
1192     mEventData->mDecoratorUpdated = true;
1193   }
1194   else
1195   {
1196     mEventData->mScrollPosition.x = 0.f;
1197   }
1198 }
1199
1200 void Controller::Impl::ClampVerticalScroll( const Vector2& actualSize )
1201 {
1202   // Clamp between -space & 0 (and the text alignment).
1203   if( actualSize.height > mControlSize.height )
1204   {
1205     const float space = ( actualSize.height - mControlSize.height ) + mAlignmentOffset.y;
1206     mEventData->mScrollPosition.y = ( mEventData->mScrollPosition.y < -space ) ? -space : mEventData->mScrollPosition.y;
1207     mEventData->mScrollPosition.y = ( mEventData->mScrollPosition.y > -mAlignmentOffset.y ) ? -mAlignmentOffset.y : mEventData->mScrollPosition.y;
1208
1209     mEventData->mDecoratorUpdated = true;
1210   }
1211   else
1212   {
1213     mEventData->mScrollPosition.y = 0.f;
1214   }
1215 }
1216
1217 void Controller::Impl::ScrollToMakeCursorVisible()
1218 {
1219   if( NULL == mEventData )
1220   {
1221     // Nothing to do if there is no text input.
1222     return;
1223   }
1224
1225   const Vector2& primaryCursorPosition = mEventData->mDecorator->GetPosition( PRIMARY_CURSOR );
1226
1227   Vector2 offset;
1228   bool updateDecorator = false;
1229   if( primaryCursorPosition.x < 0.f )
1230   {
1231     offset.x = -primaryCursorPosition.x;
1232     mEventData->mScrollPosition.x += offset.x;
1233     updateDecorator = true;
1234   }
1235   else if( primaryCursorPosition.x > mControlSize.width )
1236   {
1237     offset.x = mControlSize.width - primaryCursorPosition.x;
1238     mEventData->mScrollPosition.x += offset.x;
1239     updateDecorator = true;
1240   }
1241
1242   if( updateDecorator && mEventData->mDecorator )
1243   {
1244     mEventData->mDecorator->UpdatePositions( offset );
1245   }
1246
1247   // TODO : calculate the vertical scroll.
1248 }
1249
1250 void Controller::Impl::RequestRelayout()
1251 {
1252   mControlInterface.RequestTextRelayout();
1253 }
1254
1255 } // namespace Text
1256
1257 } // namespace Toolkit
1258
1259 } // namespace Dali