Merge "Delete view from toolkit and move cluster into demo" 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
24 namespace
25 {
26
27 /**
28  * @brief Some characters can be shaped in more than one glyph.
29  * This struct is used to retrieve metrics from these group of glyphs.
30  */
31 struct GlyphMetrics
32 {
33   GlyphMetrics()
34   : fontHeight( 0.f ),
35     advance( 0.f ),
36     ascender( 0.f ),
37     xBearing( 0.f )
38   {}
39
40   ~GlyphMetrics()
41   {}
42
43   float fontHeight; ///< The font's height of that glyphs.
44   float advance;    ///< The sum of all the advances of all the glyphs.
45   float ascender;   ///< The font's ascender.
46   float xBearing;   ///< The x bearing of the first glyph.
47 };
48
49 const std::string EMPTY_STRING("");
50
51 } // namespace
52
53 namespace Dali
54 {
55
56 namespace Toolkit
57 {
58
59 namespace Text
60 {
61
62 /**
63  * @brief Get some glyph's metrics of a group of glyphs formed as a result of shaping one character.
64  *
65  * @param[in] glyphIndex The index to the first glyph.
66  * @param[in] numberOfGlyphs The number of glyphs.
67  * @param[out] glyphMetrics Some glyph metrics (font height, advance, ascender and x bearing).
68  * @param[in]
69  * @param[in]
70  */
71 void GetGlyphsMetrics( GlyphIndex glyphIndex,
72                        Length numberOfGlyphs,
73                        GlyphMetrics& glyphMetrics,
74                        VisualModelPtr visualModel,
75                        TextAbstraction::FontClient& fontClient )
76 {
77   const GlyphInfo* glyphsBuffer = visualModel->mGlyphs.Begin();
78
79   const GlyphInfo& firstGlyph = *( glyphsBuffer + glyphIndex );
80
81   Text::FontMetrics fontMetrics;
82   fontClient.GetFontMetrics( firstGlyph.fontId, fontMetrics );
83
84   glyphMetrics.fontHeight = fontMetrics.height;
85   glyphMetrics.advance = firstGlyph.advance;
86   glyphMetrics.ascender = fontMetrics.ascender;
87   glyphMetrics.xBearing = firstGlyph.xBearing;
88
89   for( unsigned int i = 1u; i < numberOfGlyphs; ++i )
90   {
91     const GlyphInfo& glyphInfo = *( glyphsBuffer + glyphIndex + i );
92
93     glyphMetrics.advance += glyphInfo.advance;
94   }
95 }
96
97 EventData::EventData( DecoratorPtr decorator )
98 : mDecorator( decorator ),
99   mPlaceholderText(),
100   mEventQueue(),
101   mScrollPosition(),
102   mState( INACTIVE ),
103   mPrimaryCursorPosition( 0u ),
104   mSecondaryCursorPosition( 0u ),
105   mDecoratorUpdated( false ),
106   mCursorBlinkEnabled( true ),
107   mGrabHandleEnabled( true ),
108   mGrabHandlePopupEnabled( false ),
109   mSelectionEnabled( false ),
110   mHorizontalScrollingEnabled( true ),
111   mVerticalScrollingEnabled( false ),
112   mUpdateCursorPosition( false ),
113   mScrollAfterUpdateCursorPosition( false )
114 {}
115
116 EventData::~EventData()
117 {}
118
119 bool Controller::Impl::ProcessInputEvents()
120 {
121   if( NULL == mEventData )
122   {
123     // Nothing to do if there is no text input.
124     return false;
125   }
126
127   mEventData->mDecoratorUpdated = false;
128
129   if( mEventData->mDecorator )
130   {
131     for( std::vector<Event>::iterator iter = mEventData->mEventQueue.begin();
132          iter != mEventData->mEventQueue.end();
133          ++iter )
134     {
135       switch( iter->type )
136       {
137       case Event::KEYBOARD_FOCUS_GAIN_EVENT:
138       {
139         OnKeyboardFocus( true );
140         break;
141       }
142       case Event::KEYBOARD_FOCUS_LOST_EVENT:
143       {
144         OnKeyboardFocus( false );
145         break;
146       }
147       case Event::CURSOR_KEY_EVENT:
148       {
149         OnCursorKeyEvent( *iter );
150         break;
151       }
152       case Event::TAP_EVENT:
153       {
154         OnTapEvent( *iter );
155         break;
156       }
157       case Event::PAN_EVENT:
158       {
159         OnPanEvent( *iter );
160         break;
161       }
162       case Event::GRAB_HANDLE_EVENT:
163       {
164         OnGrabHandleEvent( *iter );
165         break;
166       }
167       }
168     }
169   }
170
171   // The cursor must also be repositioned after inserts into the model
172   if( mEventData->mUpdateCursorPosition )
173   {
174     // Updates the cursor position and scrolls the text to make it visible.
175
176     UpdateCursorPosition();
177
178     if( mEventData->mScrollAfterUpdateCursorPosition )
179     {
180       ScrollToMakeCursorVisible();
181       mEventData->mScrollAfterUpdateCursorPosition = false;
182     }
183
184     mEventData->mUpdateCursorPosition = false;
185   }
186
187   mEventData->mEventQueue.clear();
188
189   return mEventData->mDecoratorUpdated;
190 }
191
192 void Controller::Impl::OnKeyboardFocus( bool hasFocus )
193 {
194   if( NULL == mEventData )
195   {
196     // Nothing to do if there is no text input.
197     return;
198   }
199
200   if( !hasFocus )
201   {
202     ChangeState( EventData::INACTIVE );
203   }
204   else
205   {
206     ChangeState( EventData::EDITING );
207   }
208 }
209
210 void Controller::Impl::OnCursorKeyEvent( const Event& event )
211 {
212   if( NULL == mEventData )
213   {
214     // Nothing to do if there is no text input.
215     return;
216   }
217
218   int keyCode = event.p1.mInt;
219
220   if( Dali::DALI_KEY_CURSOR_LEFT == keyCode )
221   {
222     if( mEventData->mPrimaryCursorPosition > 0u )
223     {
224       mEventData->mPrimaryCursorPosition = CalculateNewCursorIndex( mEventData->mPrimaryCursorPosition - 1u );
225     }
226   }
227   else if( Dali::DALI_KEY_CURSOR_RIGHT == keyCode )
228   {
229     if( mLogicalModel->GetNumberOfCharacters() > mEventData->mPrimaryCursorPosition )
230     {
231       mEventData->mPrimaryCursorPosition = CalculateNewCursorIndex( mEventData->mPrimaryCursorPosition );
232     }
233   }
234   else if( Dali::DALI_KEY_CURSOR_UP == keyCode )
235   {
236     // TODO
237   }
238   else if(   Dali::DALI_KEY_CURSOR_DOWN == keyCode )
239   {
240     // TODO
241   }
242
243   mEventData->mUpdateCursorPosition = true;
244   mEventData->mScrollAfterUpdateCursorPosition = true;
245 }
246
247 void Controller::Impl::HandleCursorKey( int keyCode )
248 {
249   // TODO
250   if( NULL == mEventData )
251   {
252     // Nothing to do if there is no text input.
253     return;
254   }
255 }
256
257 void Controller::Impl::OnTapEvent( const Event& event )
258 {
259   if( NULL == mEventData )
260   {
261     // Nothing to do if there is no text input.
262     return;
263   }
264
265   const unsigned int tapCount = event.p1.mUint;
266
267   if( 1u == tapCount )
268   {
269     ChangeState( EventData::EDITING );
270
271     const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
272     const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
273
274     mEventData->mPrimaryCursorPosition = GetClosestCursorIndex( xPosition,
275                                                                 yPosition );
276
277     mEventData->mUpdateCursorPosition = true;
278     mEventData->mScrollAfterUpdateCursorPosition = true;
279   }
280   else if( mEventData->mSelectionEnabled &&
281            ( 2u == tapCount ) )
282   {
283     ChangeState( EventData::SELECTING );
284
285     RepositionSelectionHandles( event.p2.mFloat, event.p3.mFloat );
286   }
287 }
288
289 void Controller::Impl::OnPanEvent( const Event& event )
290 {
291   if( NULL == mEventData )
292   {
293     // Nothing to do if there is no text input.
294     return;
295   }
296
297   int state = event.p1.mInt;
298
299   if( Gesture::Started    == state ||
300       Gesture::Continuing == state )
301   {
302     const Vector2& actualSize = mVisualModel->GetActualSize();
303     const Vector2 currentScroll = mEventData->mScrollPosition;
304
305     if( mEventData->mHorizontalScrollingEnabled )
306     {
307       const float displacementX = event.p2.mFloat;
308       mEventData->mScrollPosition.x += displacementX;
309
310       ClampHorizontalScroll( actualSize );
311     }
312
313     if( mEventData->mVerticalScrollingEnabled )
314     {
315       const float displacementY = event.p3.mFloat;
316       mEventData->mScrollPosition.y += displacementY;
317
318       ClampVerticalScroll( actualSize );
319     }
320
321     if( mEventData->mDecorator )
322     {
323       mEventData->mDecorator->UpdatePositions( mEventData->mScrollPosition - currentScroll );
324     }
325   }
326 }
327
328 void Controller::Impl::OnGrabHandleEvent( const Event& event )
329 {
330   if( NULL == mEventData )
331   {
332     // Nothing to do if there is no text input.
333     return;
334   }
335
336   unsigned int state = event.p1.mUint;
337
338   if( GRAB_HANDLE_PRESSED == state )
339   {
340     // The event.p2 and event.p3 are in decorator coords. Need to transforms to text coords.
341     const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
342     const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
343
344     //mDecorator->HidePopup();
345     ChangeState ( EventData::EDITING );
346
347     const CharacterIndex newCursorPosition = GetClosestCursorIndex( xPosition, yPosition );
348
349     if( newCursorPosition != mEventData->mPrimaryCursorPosition )
350     {
351       mEventData->mPrimaryCursorPosition = newCursorPosition;
352       mEventData->mUpdateCursorPosition = true;
353     }
354   }
355   else if( mEventData->mGrabHandlePopupEnabled &&
356            ( ( GRAB_HANDLE_RELEASED == state ) ||
357              ( GRAB_HANDLE_STOP_SCROLLING == state ) ) )
358   {
359     //mDecorator->ShowPopup();
360     ChangeState ( EventData::EDITING_WITH_POPUP );
361     mEventData->mUpdateCursorPosition = true;
362     mEventData->mDecoratorUpdated = true;
363
364     if( GRAB_HANDLE_STOP_SCROLLING == state )
365     {
366       // The event.p2 and event.p3 are in decorator coords. Need to transforms to text coords.
367       const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
368       const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
369
370       mEventData->mPrimaryCursorPosition = GetClosestCursorIndex( xPosition, yPosition );
371
372       mEventData->mScrollAfterUpdateCursorPosition = true;
373     }
374   }
375   else if( GRAB_HANDLE_SCROLLING == state )
376   {
377     const float xSpeed = event.p2.mFloat;
378     const Vector2& actualSize = mVisualModel->GetActualSize();
379
380     mEventData->mScrollPosition.x += xSpeed;
381
382     ClampHorizontalScroll( actualSize );
383   }
384 }
385
386 void Controller::Impl::RepositionSelectionHandles( float visualX, float visualY )
387 {
388   if( NULL == mEventData )
389   {
390     // Nothing to do if there is no text input.
391     return;
392   }
393
394   // TODO - Find which word was selected
395
396   const Vector<GlyphInfo>& glyphs = mVisualModel->mGlyphs;
397   const Vector<Vector2>::SizeType glyphCount = glyphs.Count();
398
399   const Vector<Vector2>& positions = mVisualModel->mGlyphPositions;
400   const Vector<Vector2>::SizeType positionCount = positions.Count();
401
402   // Guard against glyphs which did not fit inside the layout
403   const Vector<Vector2>::SizeType count = (positionCount < glyphCount) ? positionCount : glyphCount;
404
405   if( count )
406   {
407     float primaryX   = positions[0].x + mEventData->mScrollPosition.x;
408     float secondaryX = positions[count-1].x + glyphs[count-1].width + mEventData->mScrollPosition.x;
409
410     // TODO - multi-line selection
411     const Vector<LineRun>& lines = mVisualModel->mLines;
412     float height = lines.Count() ? lines[0].ascender + -lines[0].descender : 0.0f;
413
414     mEventData->mDecorator->SetPosition( PRIMARY_SELECTION_HANDLE,     primaryX, mEventData->mScrollPosition.y, height );
415     mEventData->mDecorator->SetPosition( SECONDARY_SELECTION_HANDLE, secondaryX, mEventData->mScrollPosition.y, height );
416
417     mEventData->mDecorator->ClearHighlights();
418     mEventData->mDecorator->AddHighlight( primaryX, mEventData->mScrollPosition.y, secondaryX, height + mEventData->mScrollPosition.y );
419   }
420 }
421
422 void Controller::Impl::ChangeState( EventData::State newState )
423 {
424   if( NULL == mEventData )
425   {
426     // Nothing to do if there is no text input.
427     return;
428   }
429
430   if( mEventData->mState != newState )
431   {
432     mEventData->mState = newState;
433
434     if( EventData::INACTIVE == mEventData->mState )
435     {
436       mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
437       mEventData->mDecorator->StopCursorBlink();
438       mEventData->mDecorator->SetGrabHandleActive( false );
439       mEventData->mDecorator->SetSelectionActive( false );
440       mEventData->mDecorator->SetPopupActive( false );
441       mEventData->mDecoratorUpdated = true;
442     }
443     else if ( EventData::SELECTING == mEventData->mState )
444     {
445       mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
446       mEventData->mDecorator->StopCursorBlink();
447       mEventData->mDecorator->SetGrabHandleActive( false );
448       mEventData->mDecorator->SetSelectionActive( true );
449       mEventData->mDecoratorUpdated = true;
450     }
451     else if( EventData::EDITING == mEventData->mState )
452     {
453       mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
454       if( mEventData->mCursorBlinkEnabled )
455       {
456         mEventData->mDecorator->StartCursorBlink();
457       }
458       if( mEventData->mGrabHandleEnabled )
459       {
460         mEventData->mDecorator->SetGrabHandleActive( true );
461       }
462       if( mEventData->mGrabHandlePopupEnabled )
463       {
464         mEventData->mDecorator->SetPopupActive( false );
465       }
466       mEventData->mDecorator->SetSelectionActive( false );
467       mEventData->mDecoratorUpdated = true;
468     }
469     else if( EventData::EDITING_WITH_POPUP == mEventData->mState )
470     {
471       mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
472       if( mEventData->mCursorBlinkEnabled )
473       {
474         mEventData->mDecorator->StartCursorBlink();
475       }
476       if( mEventData->mGrabHandleEnabled )
477       {
478         mEventData->mDecorator->SetGrabHandleActive( true );
479       }
480       if( mEventData->mGrabHandlePopupEnabled )
481       {
482         mEventData->mDecorator->SetPopupActive( true );
483       }
484       mEventData->mDecorator->SetSelectionActive( false );
485       mEventData->mDecoratorUpdated = true;
486     }
487   }
488 }
489
490 LineIndex Controller::Impl::GetClosestLine( float y ) const
491 {
492   float totalHeight = 0.f;
493   LineIndex lineIndex = 0u;
494
495   const Vector<LineRun>& lines = mVisualModel->mLines;
496   for( LineIndex endLine = lines.Count();
497        lineIndex < endLine;
498        ++lineIndex )
499   {
500     const LineRun& lineRun = lines[lineIndex];
501     totalHeight += lineRun.ascender + -lineRun.descender;
502     if( y < totalHeight )
503     {
504       return lineIndex;
505     }
506   }
507
508   return lineIndex-1;
509 }
510
511 CharacterIndex Controller::Impl::GetClosestCursorIndex( float visualX,
512                                                         float visualY )
513 {
514   if( NULL == mEventData )
515   {
516     // Nothing to do if there is no text input.
517     return 0u;
518   }
519
520   CharacterIndex logicalIndex = 0u;
521
522   const Length numberOfGlyphs = mVisualModel->mGlyphs.Count();
523   const Length numberOfLines  = mVisualModel->mLines.Count();
524   if( 0 == numberOfGlyphs ||
525       0 == numberOfLines )
526   {
527     return logicalIndex;
528   }
529
530   // Find which line is closest
531   const LineIndex lineIndex = GetClosestLine( visualY );
532   const LineRun& line = mVisualModel->mLines[lineIndex];
533
534   // Get the positions of the glyphs.
535   const Vector<Vector2>& positions = mVisualModel->mGlyphPositions;
536   const Vector2* const positionsBuffer = positions.Begin();
537
538   // Get the visual to logical conversion tables.
539   const CharacterIndex* const visualToLogicalBuffer = ( 0u != mLogicalModel->mVisualToLogicalMap.Count() ) ? mLogicalModel->mVisualToLogicalMap.Begin() : NULL;
540   const CharacterIndex* const visualToLogicalCursorBuffer = mLogicalModel->mVisualToLogicalCursorMap.Begin();
541
542   // Get the character to glyph conversion table.
543   const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
544
545   // Get the glyphs per character table.
546   const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
547
548   // If the vector is void, there is no right to left characters.
549   const bool hasRightToLeftCharacters = NULL != visualToLogicalBuffer;
550
551   const CharacterIndex startCharacter = line.characterRun.characterIndex;
552   const CharacterIndex endCharacter   = line.characterRun.characterIndex + line.characterRun.numberOfCharacters;
553   DALI_ASSERT_DEBUG( endCharacter <= mLogicalModel->mText.Count() && "Invalid line info" );
554
555   // Whether there is a hit on a glyph.
556   bool matched = false;
557
558   // Traverses glyphs in visual order. To do that use the visual to logical conversion table.
559   CharacterIndex visualIndex = startCharacter;
560   for( ; !matched && ( visualIndex < endCharacter ); ++visualIndex )
561   {
562     // The character in logical order.
563     const CharacterIndex characterLogicalOrderIndex = hasRightToLeftCharacters ? *( visualToLogicalBuffer + visualIndex ) : visualIndex;
564
565     // The first glyph for that character in logical order.
566     const GlyphIndex glyphLogicalOrderIndex = *( charactersToGlyphBuffer + characterLogicalOrderIndex );
567
568     // The number of glyphs for that character
569     const Length numberOfGlyphs = *( glyphsPerCharacterBuffer + characterLogicalOrderIndex );
570
571     // Get the metrics for the group of glyphs.
572     GlyphMetrics glyphMetrics;
573     GetGlyphsMetrics( glyphLogicalOrderIndex,
574                       numberOfGlyphs,
575                       glyphMetrics,
576                       mVisualModel,
577                       mFontClient );
578
579     const Vector2& position = *( positionsBuffer + glyphLogicalOrderIndex );
580
581     const float glyphX = -glyphMetrics.xBearing + position.x + 0.5f * glyphMetrics.advance;
582
583     if( visualX < glyphX )
584     {
585       matched = true;
586       break;
587     }
588   }
589
590   // Return the logical position of the cursor in characters.
591
592   if( !matched )
593   {
594     visualIndex = endCharacter;
595   }
596
597   return hasRightToLeftCharacters ? *( visualToLogicalCursorBuffer + visualIndex ) : visualIndex;
598 }
599
600 void Controller::Impl::GetCursorPosition( CharacterIndex logical,
601                                           CursorInfo& cursorInfo )
602 {
603   // TODO: Check for multiline with \n, etc...
604
605   // Check if the logical position is the first or the last one of the text.
606   const bool isFirstPosition = 0u == logical;
607   const bool isLastPosition = mLogicalModel->GetNumberOfCharacters() == logical;
608
609   if( isFirstPosition && isLastPosition )
610   {
611     // There is zero characters. Get the default font.
612
613     FontId defaultFontId = 0u;
614     if( NULL == mFontDefaults )
615     {
616       defaultFontId = mFontClient.GetFontId( EMPTY_STRING,
617                                              EMPTY_STRING );
618     }
619     else
620     {
621       defaultFontId = mFontDefaults->GetFontId( mFontClient );
622     }
623
624     Text::FontMetrics fontMetrics;
625     mFontClient.GetFontMetrics( defaultFontId, fontMetrics );
626
627     cursorInfo.lineHeight = fontMetrics.ascender - fontMetrics.descender;
628     cursorInfo.primaryCursorHeight = cursorInfo.lineHeight;
629
630     cursorInfo.primaryPosition.x = 0.f;
631     cursorInfo.primaryPosition.y = 0.f;
632
633     // Nothing else to do.
634     return;
635   }
636
637   // Get the previous logical index.
638   const CharacterIndex previousLogical = isFirstPosition ? 0u : logical - 1u;
639
640   // Decrease the logical index if it's the last one.
641   if( isLastPosition )
642   {
643     --logical;
644   }
645
646   // Get the direction of the character and the previous one.
647   const CharacterDirection* const modelCharacterDirectionsBuffer = ( 0u != mLogicalModel->mCharacterDirections.Count() ) ? mLogicalModel->mCharacterDirections.Begin() : NULL;
648
649   CharacterDirection isCurrentRightToLeft = false;
650   CharacterDirection isPreviousRightToLeft = false;
651   if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
652   {
653     isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + logical );
654     isPreviousRightToLeft = *( modelCharacterDirectionsBuffer + previousLogical );
655   }
656
657   // Get the line where the character is laid-out.
658   const LineRun* modelLines = mVisualModel->mLines.Begin();
659
660   const LineIndex lineIndex = mVisualModel->GetLineOfCharacter( logical );
661   const LineRun& line = *( modelLines + lineIndex );
662
663   // Get the paragraph's direction.
664   const CharacterDirection isRightToLeftParagraph = line.direction;
665
666   // Check whether there is an alternative position:
667
668   cursorInfo.isSecondaryCursor = ( isCurrentRightToLeft != isPreviousRightToLeft ) ||
669     ( isLastPosition && ( isRightToLeftParagraph != isCurrentRightToLeft ) );
670
671   // Set the line height.
672   cursorInfo.lineHeight = line.ascender + -line.descender;
673
674   // Convert the cursor position into the glyph position.
675   CharacterIndex characterIndex = logical;
676   if( cursorInfo.isSecondaryCursor &&
677       ( isRightToLeftParagraph != isCurrentRightToLeft ) )
678   {
679     characterIndex = previousLogical;
680   }
681
682   const GlyphIndex currentGlyphIndex = *( mVisualModel->mCharactersToGlyph.Begin() + characterIndex );
683   const Length numberOfGlyphs = *( mVisualModel->mGlyphsPerCharacter.Begin() + characterIndex );
684   const Length numberOfCharacters = *( mVisualModel->mCharactersPerGlyph.Begin() +currentGlyphIndex );
685
686   // Get the metrics for the group of glyphs.
687   GlyphMetrics glyphMetrics;
688   GetGlyphsMetrics( currentGlyphIndex,
689                     numberOfGlyphs,
690                     glyphMetrics,
691                     mVisualModel,
692                     mFontClient );
693
694   float interGlyphAdvance = 0.f;
695   if( !isLastPosition &&
696       ( numberOfCharacters > 1u ) )
697   {
698     const CharacterIndex firstIndex = *( mVisualModel->mGlyphsToCharacters.Begin() + currentGlyphIndex );
699     interGlyphAdvance = static_cast<float>( characterIndex - firstIndex ) * glyphMetrics.advance / static_cast<float>( numberOfCharacters );
700   }
701
702   // Get the glyph position and x bearing.
703   const Vector2& currentPosition = *( mVisualModel->mGlyphPositions.Begin() + currentGlyphIndex );
704
705   // Set the cursor's height.
706   cursorInfo.primaryCursorHeight = glyphMetrics.fontHeight;
707
708   // Set the position.
709   cursorInfo.primaryPosition.x = -glyphMetrics.xBearing + currentPosition.x + ( isCurrentRightToLeft ? glyphMetrics.advance : interGlyphAdvance );
710   cursorInfo.primaryPosition.y = line.ascender - glyphMetrics.ascender;
711
712   if( isLastPosition )
713   {
714     // The position of the cursor after the last character needs special
715     // care depending on its direction and the direction of the paragraph.
716
717     if( cursorInfo.isSecondaryCursor )
718     {
719       // Need to find the first character after the last character with the paragraph's direction.
720       // i.e l0 l1 l2 r0 r1 should find r0.
721
722       // TODO: check for more than one line!
723       characterIndex = isRightToLeftParagraph ? line.characterRun.characterIndex : line.characterRun.characterIndex + line.characterRun.numberOfCharacters - 1u;
724       characterIndex = mLogicalModel->GetLogicalCharacterIndex( characterIndex );
725
726       const GlyphIndex glyphIndex = *( mVisualModel->mCharactersToGlyph.Begin() + characterIndex );
727       const Length numberOfGlyphs = *( mVisualModel->mGlyphsPerCharacter.Begin() + characterIndex );
728
729       const Vector2& position = *( mVisualModel->mGlyphPositions.Begin() + glyphIndex );
730
731       // Get the metrics for the group of glyphs.
732       GlyphMetrics glyphMetrics;
733       GetGlyphsMetrics( glyphIndex,
734                         numberOfGlyphs,
735                         glyphMetrics,
736                         mVisualModel,
737                         mFontClient );
738
739       cursorInfo.primaryPosition.x = -glyphMetrics.xBearing + position.x + ( isRightToLeftParagraph ? 0.f : glyphMetrics.advance );
740
741       cursorInfo.primaryPosition.y = line.ascender - glyphMetrics.ascender;
742     }
743     else
744     {
745       if( !isCurrentRightToLeft )
746       {
747         cursorInfo.primaryPosition.x += glyphMetrics.advance;
748       }
749       else
750       {
751         cursorInfo.primaryPosition.x -= glyphMetrics.advance;
752       }
753     }
754   }
755
756   // Set the alternative cursor position.
757   if( cursorInfo.isSecondaryCursor )
758   {
759     // Convert the cursor position into the glyph position.
760     const CharacterIndex previousCharacterIndex = ( ( isRightToLeftParagraph != isCurrentRightToLeft ) ? logical : previousLogical );
761     const GlyphIndex previousGlyphIndex = *( mVisualModel->mCharactersToGlyph.Begin() + previousCharacterIndex );
762     const Length numberOfGlyphs = *( mVisualModel->mGlyphsPerCharacter.Begin() + previousCharacterIndex );
763
764     // Get the glyph position.
765     const Vector2& previousPosition = *( mVisualModel->mGlyphPositions.Begin() + previousGlyphIndex );
766
767     // Get the metrics for the group of glyphs.
768     GlyphMetrics glyphMetrics;
769     GetGlyphsMetrics( previousGlyphIndex,
770                       numberOfGlyphs,
771                       glyphMetrics,
772                       mVisualModel,
773                       mFontClient );
774
775     // Set the cursor position and height.
776     cursorInfo.secondaryPosition.x = -glyphMetrics.xBearing + previousPosition.x + ( ( ( isLastPosition && !isCurrentRightToLeft ) ||
777                                                                                        ( !isLastPosition && isCurrentRightToLeft )    ) ? glyphMetrics.advance : 0.f );
778
779     cursorInfo.secondaryCursorHeight = 0.5f * glyphMetrics.fontHeight;
780
781     cursorInfo.secondaryPosition.y = cursorInfo.lineHeight - cursorInfo.secondaryCursorHeight - line.descender - ( glyphMetrics.fontHeight - glyphMetrics.ascender );
782
783     // Update the primary cursor height as well.
784     cursorInfo.primaryCursorHeight *= 0.5f;
785   }
786 }
787
788 CharacterIndex Controller::Impl::CalculateNewCursorIndex( CharacterIndex index ) const
789 {
790   if( NULL == mEventData )
791   {
792     // Nothing to do if there is no text input.
793     return 0u;
794   }
795
796   CharacterIndex cursorIndex = mEventData->mPrimaryCursorPosition;
797
798   const Script script = mLogicalModel->GetScript( index );
799   const GlyphIndex* charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
800   const Length* charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
801
802   Length numberOfCharacters = 0u;
803   if( TextAbstraction::LATIN == script )
804   {
805     // Prevents to jump the whole Latin ligatures like fi, ff, ...
806     numberOfCharacters = 1u;
807   }
808   else
809   {
810     GlyphIndex glyphIndex = *( charactersToGlyphBuffer + index );
811     numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
812
813     while( 0u == numberOfCharacters )
814     {
815       numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
816       ++glyphIndex;
817     }
818   }
819
820   if( index < mEventData->mPrimaryCursorPosition )
821   {
822     cursorIndex -= numberOfCharacters;
823   }
824   else
825   {
826     cursorIndex += numberOfCharacters;
827   }
828
829   return cursorIndex;
830 }
831
832 void Controller::Impl::UpdateCursorPosition()
833 {
834   if( NULL == mEventData )
835   {
836     // Nothing to do if there is no text input.
837     return;
838   }
839
840   CursorInfo cursorInfo;
841   GetCursorPosition( mEventData->mPrimaryCursorPosition,
842                      cursorInfo );
843
844   mEventData->mDecorator->SetPosition( PRIMARY_CURSOR,
845                                        cursorInfo.primaryPosition.x + mEventData->mScrollPosition.x + mAlignmentOffset.x,
846                                        cursorInfo.primaryPosition.y + mEventData->mScrollPosition.y + mAlignmentOffset.y,
847                                        cursorInfo.primaryCursorHeight,
848                                        cursorInfo.lineHeight );
849
850   if( cursorInfo.isSecondaryCursor )
851   {
852     mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_BOTH );
853     mEventData->mDecorator->SetPosition( SECONDARY_CURSOR,
854                                          cursorInfo.secondaryPosition.x + mEventData->mScrollPosition.x + mAlignmentOffset.x,
855                                          cursorInfo.secondaryPosition.y + mEventData->mScrollPosition.y + mAlignmentOffset.y,
856                                          cursorInfo.secondaryCursorHeight,
857                                          cursorInfo.lineHeight );
858   }
859   else
860   {
861     mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
862   }
863
864   mEventData->mUpdateCursorPosition = false;
865   mEventData->mDecoratorUpdated = true;
866 }
867
868 void Controller::Impl::ClampHorizontalScroll( const Vector2& actualSize )
869 {
870   // Clamp between -space & 0 (and the text alignment).
871   if( actualSize.width > mControlSize.width )
872   {
873     const float space = ( actualSize.width - mControlSize.width ) + mAlignmentOffset.x;
874     mEventData->mScrollPosition.x = ( mEventData->mScrollPosition.x < -space ) ? -space : mEventData->mScrollPosition.x;
875     mEventData->mScrollPosition.x = ( mEventData->mScrollPosition.x > -mAlignmentOffset.x ) ? -mAlignmentOffset.x : mEventData->mScrollPosition.x;
876
877     mEventData->mDecoratorUpdated = true;
878   }
879   else
880   {
881     mEventData->mScrollPosition.x = 0.f;
882   }
883 }
884
885 void Controller::Impl::ClampVerticalScroll( const Vector2& actualSize )
886 {
887   // Clamp between -space & 0 (and the text alignment).
888   if( actualSize.height > mControlSize.height )
889   {
890     const float space = ( actualSize.height - mControlSize.height ) + mAlignmentOffset.y;
891     mEventData->mScrollPosition.y = ( mEventData->mScrollPosition.y < -space ) ? -space : mEventData->mScrollPosition.y;
892     mEventData->mScrollPosition.y = ( mEventData->mScrollPosition.y > -mAlignmentOffset.y ) ? -mAlignmentOffset.y : mEventData->mScrollPosition.y;
893
894     mEventData->mDecoratorUpdated = true;
895   }
896   else
897   {
898     mEventData->mScrollPosition.y = 0.f;
899   }
900 }
901
902 void Controller::Impl::ScrollToMakeCursorVisible()
903 {
904   if( NULL == mEventData )
905   {
906     // Nothing to do if there is no text input.
907     return;
908   }
909
910   const Vector2& primaryCursorPosition = mEventData->mDecorator->GetPosition( PRIMARY_CURSOR );
911
912   Vector2 offset;
913   bool updateDecorator = false;
914   if( primaryCursorPosition.x < 0.f )
915   {
916     offset.x = -primaryCursorPosition.x;
917     mEventData->mScrollPosition.x += offset.x;
918     updateDecorator = true;
919   }
920   else if( primaryCursorPosition.x > mControlSize.width )
921   {
922     offset.x = mControlSize.width - primaryCursorPosition.x;
923     mEventData->mScrollPosition.x += offset.x;
924     updateDecorator = true;
925   }
926
927   if( updateDecorator && mEventData->mDecorator )
928   {
929     mEventData->mDecorator->UpdatePositions( offset );
930   }
931
932   // TODO : calculate the vertical scroll.
933 }
934
935 void Controller::Impl::RequestRelayout()
936 {
937   mControlInterface.RequestTextRelayout();
938 }
939
940 } // namespace Text
941
942 } // namespace Toolkit
943
944 } // namespace Dali