Make cursor invisible when exceeds the boundaries of the Text Control.
[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( true ),
109   mSelectionEnabled( true ),
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   {
358     //mDecorator->ShowPopup();
359     ChangeState ( EventData::EDITING_WITH_POPUP );
360     mEventData->mUpdateCursorPosition = true;
361     mEventData->mDecoratorUpdated = true;
362   }
363 }
364
365 void Controller::Impl::RepositionSelectionHandles( float visualX, float visualY )
366 {
367   if( NULL == mEventData )
368   {
369     // Nothing to do if there is no text input.
370     return;
371   }
372
373   // TODO - Find which word was selected
374
375   const Vector<GlyphInfo>& glyphs = mVisualModel->mGlyphs;
376   const Vector<Vector2>::SizeType glyphCount = glyphs.Count();
377
378   const Vector<Vector2>& positions = mVisualModel->mGlyphPositions;
379   const Vector<Vector2>::SizeType positionCount = positions.Count();
380
381   // Guard against glyphs which did not fit inside the layout
382   const Vector<Vector2>::SizeType count = (positionCount < glyphCount) ? positionCount : glyphCount;
383
384   if( count )
385   {
386     float primaryX   = positions[0].x + mEventData->mScrollPosition.x;
387     float secondaryX = positions[count-1].x + glyphs[count-1].width + mEventData->mScrollPosition.x;
388
389     // TODO - multi-line selection
390     const Vector<LineRun>& lines = mVisualModel->mLines;
391     float height = lines.Count() ? lines[0].ascender + -lines[0].descender : 0.0f;
392
393     mEventData->mDecorator->SetPosition( PRIMARY_SELECTION_HANDLE,     primaryX, mEventData->mScrollPosition.y, height );
394     mEventData->mDecorator->SetPosition( SECONDARY_SELECTION_HANDLE, secondaryX, mEventData->mScrollPosition.y, height );
395
396     mEventData->mDecorator->ClearHighlights();
397     mEventData->mDecorator->AddHighlight( primaryX, mEventData->mScrollPosition.y, secondaryX, height + mEventData->mScrollPosition.y );
398   }
399 }
400
401 void Controller::Impl::ChangeState( EventData::State newState )
402 {
403   if( NULL == mEventData )
404   {
405     // Nothing to do if there is no text input.
406     return;
407   }
408
409   if( mEventData->mState != newState )
410   {
411     mEventData->mState = newState;
412
413     if( EventData::INACTIVE == mEventData->mState )
414     {
415       mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
416       mEventData->mDecorator->StopCursorBlink();
417       mEventData->mDecorator->SetGrabHandleActive( false );
418       mEventData->mDecorator->SetSelectionActive( false );
419       mEventData->mDecorator->SetPopupActive( false );
420       mEventData->mDecoratorUpdated = true;
421     }
422     else if ( EventData::SELECTING == mEventData->mState )
423     {
424       mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
425       mEventData->mDecorator->StopCursorBlink();
426       mEventData->mDecorator->SetGrabHandleActive( false );
427       mEventData->mDecorator->SetSelectionActive( true );
428       mEventData->mDecoratorUpdated = true;
429     }
430     else if( EventData::EDITING == mEventData->mState )
431     {
432       mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
433       if( mEventData->mCursorBlinkEnabled )
434       {
435         mEventData->mDecorator->StartCursorBlink();
436       }
437       if( mEventData->mGrabHandleEnabled )
438       {
439         mEventData->mDecorator->SetGrabHandleActive( true );
440       }
441       if( mEventData->mGrabHandlePopupEnabled )
442       {
443         mEventData->mDecorator->SetPopupActive( false );
444       }
445       mEventData->mDecorator->SetSelectionActive( false );
446       mEventData->mDecoratorUpdated = true;
447     }
448     else if( EventData::EDITING_WITH_POPUP == mEventData->mState )
449     {
450       mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
451       if( mEventData->mCursorBlinkEnabled )
452       {
453         mEventData->mDecorator->StartCursorBlink();
454       }
455       if( mEventData->mGrabHandleEnabled )
456       {
457         mEventData->mDecorator->SetGrabHandleActive( true );
458       }
459       if( mEventData->mGrabHandlePopupEnabled )
460       {
461         mEventData->mDecorator->SetPopupActive( true );
462       }
463       mEventData->mDecorator->SetSelectionActive( false );
464       mEventData->mDecoratorUpdated = true;
465     }
466   }
467 }
468
469 LineIndex Controller::Impl::GetClosestLine( float y ) const
470 {
471   float totalHeight = 0.f;
472   LineIndex lineIndex = 0u;
473
474   const Vector<LineRun>& lines = mVisualModel->mLines;
475   for( LineIndex endLine = lines.Count();
476        lineIndex < endLine;
477        ++lineIndex )
478   {
479     const LineRun& lineRun = lines[lineIndex];
480     totalHeight += lineRun.ascender + -lineRun.descender;
481     if( y < totalHeight )
482     {
483       return lineIndex;
484     }
485   }
486
487   return lineIndex-1;
488 }
489
490 CharacterIndex Controller::Impl::GetClosestCursorIndex( float visualX,
491                                                         float visualY )
492 {
493   if( NULL == mEventData )
494   {
495     // Nothing to do if there is no text input.
496     return 0u;
497   }
498
499   CharacterIndex logicalIndex = 0u;
500
501   const Length numberOfGlyphs = mVisualModel->mGlyphs.Count();
502   const Length numberOfLines  = mVisualModel->mLines.Count();
503   if( 0 == numberOfGlyphs ||
504       0 == numberOfLines )
505   {
506     return logicalIndex;
507   }
508
509   // Find which line is closest
510   const LineIndex lineIndex = GetClosestLine( visualY );
511   const LineRun& line = mVisualModel->mLines[lineIndex];
512
513   // Get the positions of the glyphs.
514   const Vector<Vector2>& positions = mVisualModel->mGlyphPositions;
515   const Vector2* const positionsBuffer = positions.Begin();
516
517   // Get the visual to logical conversion tables.
518   const CharacterIndex* const visualToLogicalBuffer = ( 0u != mLogicalModel->mVisualToLogicalMap.Count() ) ? mLogicalModel->mVisualToLogicalMap.Begin() : NULL;
519   const CharacterIndex* const visualToLogicalCursorBuffer = mLogicalModel->mVisualToLogicalCursorMap.Begin();
520
521   // Get the character to glyph conversion table.
522   const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
523
524   // Get the glyphs per character table.
525   const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
526
527   // If the vector is void, there is no right to left characters.
528   const bool hasRightToLeftCharacters = NULL != visualToLogicalBuffer;
529
530   const CharacterIndex startCharacter = line.characterRun.characterIndex;
531   const CharacterIndex endCharacter   = line.characterRun.characterIndex + line.characterRun.numberOfCharacters;
532   DALI_ASSERT_DEBUG( endCharacter <= mLogicalModel->mText.Count() && "Invalid line info" );
533
534   // Whether there is a hit on a glyph.
535   bool matched = false;
536
537   // Traverses glyphs in visual order. To do that use the visual to logical conversion table.
538   CharacterIndex visualIndex = startCharacter;
539   for( ; !matched && ( visualIndex < endCharacter ); ++visualIndex )
540   {
541     // The character in logical order.
542     const CharacterIndex characterLogicalOrderIndex = hasRightToLeftCharacters ? *( visualToLogicalBuffer + visualIndex ) : visualIndex;
543
544     // The first glyph for that character in logical order.
545     const GlyphIndex glyphLogicalOrderIndex = *( charactersToGlyphBuffer + characterLogicalOrderIndex );
546
547     // The number of glyphs for that character
548     const Length numberOfGlyphs = *( glyphsPerCharacterBuffer + characterLogicalOrderIndex );
549
550     // Get the metrics for the group of glyphs.
551     GlyphMetrics glyphMetrics;
552     GetGlyphsMetrics( glyphLogicalOrderIndex,
553                       numberOfGlyphs,
554                       glyphMetrics,
555                       mVisualModel,
556                       mFontClient );
557
558     const Vector2& position = *( positionsBuffer + glyphLogicalOrderIndex );
559
560     const float glyphX = -glyphMetrics.xBearing + position.x + 0.5f * glyphMetrics.advance;
561
562     if( visualX < glyphX )
563     {
564       matched = true;
565       break;
566     }
567   }
568
569   // Return the logical position of the cursor in characters.
570
571   if( !matched )
572   {
573     visualIndex = endCharacter;
574   }
575
576   return hasRightToLeftCharacters ? *( visualToLogicalCursorBuffer + visualIndex ) : visualIndex;
577 }
578
579 void Controller::Impl::GetCursorPosition( CharacterIndex logical,
580                                           CursorInfo& cursorInfo )
581 {
582   // TODO: Check for multiline with \n, etc...
583
584   // Check if the logical position is the first or the last one of the text.
585   const bool isFirstPosition = 0u == logical;
586   const bool isLastPosition = mLogicalModel->GetNumberOfCharacters() == logical;
587
588   if( isFirstPosition && isLastPosition )
589   {
590     // There is zero characters. Get the default font.
591
592     FontId defaultFontId = 0u;
593     if( NULL == mFontDefaults )
594     {
595       defaultFontId = mFontClient.GetFontId( EMPTY_STRING,
596                                              EMPTY_STRING );
597     }
598     else
599     {
600       defaultFontId = mFontDefaults->GetFontId( mFontClient );
601     }
602
603     Text::FontMetrics fontMetrics;
604     mFontClient.GetFontMetrics( defaultFontId, fontMetrics );
605
606     cursorInfo.lineHeight = fontMetrics.ascender - fontMetrics.descender;
607     cursorInfo.primaryCursorHeight = cursorInfo.lineHeight;
608
609     cursorInfo.primaryPosition.x = 0.f;
610     cursorInfo.primaryPosition.y = 0.f;
611
612     // Nothing else to do.
613     return;
614   }
615
616   // Get the previous logical index.
617   const CharacterIndex previousLogical = isFirstPosition ? 0u : logical - 1u;
618
619   // Decrease the logical index if it's the last one.
620   if( isLastPosition )
621   {
622     --logical;
623   }
624
625   // Get the direction of the character and the previous one.
626   const CharacterDirection* const modelCharacterDirectionsBuffer = ( 0u != mLogicalModel->mCharacterDirections.Count() ) ? mLogicalModel->mCharacterDirections.Begin() : NULL;
627
628   CharacterDirection isCurrentRightToLeft = false;
629   CharacterDirection isPreviousRightToLeft = false;
630   if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
631   {
632     isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + logical );
633     isPreviousRightToLeft = *( modelCharacterDirectionsBuffer + previousLogical );
634   }
635
636   // Get the line where the character is laid-out.
637   const LineRun* modelLines = mVisualModel->mLines.Begin();
638
639   const LineIndex lineIndex = mVisualModel->GetLineOfCharacter( logical );
640   const LineRun& line = *( modelLines + lineIndex );
641
642   // Get the paragraph's direction.
643   const CharacterDirection isRightToLeftParagraph = line.direction;
644
645   // Check whether there is an alternative position:
646
647   cursorInfo.isSecondaryCursor = ( isCurrentRightToLeft != isPreviousRightToLeft ) ||
648     ( isLastPosition && ( isRightToLeftParagraph != isCurrentRightToLeft ) );
649
650   // Set the line height.
651   cursorInfo.lineHeight = line.ascender + -line.descender;
652
653   // Convert the cursor position into the glyph position.
654   CharacterIndex characterIndex = logical;
655   if( cursorInfo.isSecondaryCursor &&
656       ( isRightToLeftParagraph != isCurrentRightToLeft ) )
657   {
658     characterIndex = previousLogical;
659   }
660
661   const GlyphIndex currentGlyphIndex = *( mVisualModel->mCharactersToGlyph.Begin() + characterIndex );
662   const Length numberOfGlyphs = *( mVisualModel->mGlyphsPerCharacter.Begin() + characterIndex );
663   const Length numberOfCharacters = *( mVisualModel->mCharactersPerGlyph.Begin() +currentGlyphIndex );
664
665   // Get the metrics for the group of glyphs.
666   GlyphMetrics glyphMetrics;
667   GetGlyphsMetrics( currentGlyphIndex,
668                     numberOfGlyphs,
669                     glyphMetrics,
670                     mVisualModel,
671                     mFontClient );
672
673   float interGlyphAdvance = 0.f;
674   if( !isLastPosition &&
675       ( numberOfCharacters > 1u ) )
676   {
677     const CharacterIndex firstIndex = *( mVisualModel->mGlyphsToCharacters.Begin() + currentGlyphIndex );
678     interGlyphAdvance = static_cast<float>( characterIndex - firstIndex ) * glyphMetrics.advance / static_cast<float>( numberOfCharacters );
679   }
680
681   // Get the glyph position and x bearing.
682   const Vector2& currentPosition = *( mVisualModel->mGlyphPositions.Begin() + currentGlyphIndex );
683
684   // Set the cursor's height.
685   cursorInfo.primaryCursorHeight = glyphMetrics.fontHeight;
686
687   // Set the position.
688   cursorInfo.primaryPosition.x = -glyphMetrics.xBearing + currentPosition.x + ( isCurrentRightToLeft ? glyphMetrics.advance : interGlyphAdvance );
689   cursorInfo.primaryPosition.y = line.ascender - glyphMetrics.ascender;
690
691   if( isLastPosition )
692   {
693     // The position of the cursor after the last character needs special
694     // care depending on its direction and the direction of the paragraph.
695
696     if( cursorInfo.isSecondaryCursor )
697     {
698       // Need to find the first character after the last character with the paragraph's direction.
699       // i.e l0 l1 l2 r0 r1 should find r0.
700
701       // TODO: check for more than one line!
702       characterIndex = isRightToLeftParagraph ? line.characterRun.characterIndex : line.characterRun.characterIndex + line.characterRun.numberOfCharacters - 1u;
703       characterIndex = mLogicalModel->GetLogicalCharacterIndex( characterIndex );
704
705       const GlyphIndex glyphIndex = *( mVisualModel->mCharactersToGlyph.Begin() + characterIndex );
706       const Length numberOfGlyphs = *( mVisualModel->mGlyphsPerCharacter.Begin() + characterIndex );
707
708       const Vector2& position = *( mVisualModel->mGlyphPositions.Begin() + glyphIndex );
709
710       // Get the metrics for the group of glyphs.
711       GlyphMetrics glyphMetrics;
712       GetGlyphsMetrics( glyphIndex,
713                         numberOfGlyphs,
714                         glyphMetrics,
715                         mVisualModel,
716                         mFontClient );
717
718       cursorInfo.primaryPosition.x = -glyphMetrics.xBearing + position.x + ( isRightToLeftParagraph ? 0.f : glyphMetrics.advance );
719
720       cursorInfo.primaryPosition.y = line.ascender - glyphMetrics.ascender;
721     }
722     else
723     {
724       if( !isCurrentRightToLeft )
725       {
726         cursorInfo.primaryPosition.x += glyphMetrics.advance;
727       }
728       else
729       {
730         cursorInfo.primaryPosition.x -= glyphMetrics.advance;
731       }
732     }
733   }
734
735   // Set the alternative cursor position.
736   if( cursorInfo.isSecondaryCursor )
737   {
738     // Convert the cursor position into the glyph position.
739     const CharacterIndex previousCharacterIndex = ( ( isRightToLeftParagraph != isCurrentRightToLeft ) ? logical : previousLogical );
740     const GlyphIndex previousGlyphIndex = *( mVisualModel->mCharactersToGlyph.Begin() + previousCharacterIndex );
741     const Length numberOfGlyphs = *( mVisualModel->mGlyphsPerCharacter.Begin() + previousCharacterIndex );
742
743     // Get the glyph position.
744     const Vector2& previousPosition = *( mVisualModel->mGlyphPositions.Begin() + previousGlyphIndex );
745
746     // Get the metrics for the group of glyphs.
747     GlyphMetrics glyphMetrics;
748     GetGlyphsMetrics( previousGlyphIndex,
749                       numberOfGlyphs,
750                       glyphMetrics,
751                       mVisualModel,
752                       mFontClient );
753
754     // Set the cursor position and height.
755     cursorInfo.secondaryPosition.x = -glyphMetrics.xBearing + previousPosition.x + ( ( ( isLastPosition && !isCurrentRightToLeft ) ||
756                                                                                        ( !isLastPosition && isCurrentRightToLeft )    ) ? glyphMetrics.advance : 0.f );
757
758     cursorInfo.secondaryCursorHeight = 0.5f * glyphMetrics.fontHeight;
759
760     cursorInfo.secondaryPosition.y = cursorInfo.lineHeight - cursorInfo.secondaryCursorHeight - line.descender - ( glyphMetrics.fontHeight - glyphMetrics.ascender );
761
762     // Update the primary cursor height as well.
763     cursorInfo.primaryCursorHeight *= 0.5f;
764   }
765 }
766
767 CharacterIndex Controller::Impl::CalculateNewCursorIndex( CharacterIndex index ) const
768 {
769   if( NULL == mEventData )
770   {
771     // Nothing to do if there is no text input.
772     return 0u;
773   }
774
775   CharacterIndex cursorIndex = mEventData->mPrimaryCursorPosition;
776
777   const Script script = mLogicalModel->GetScript( index );
778   const GlyphIndex* charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
779   const Length* charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
780
781   Length numberOfCharacters = 0u;
782   if( TextAbstraction::LATIN == script )
783   {
784     // Prevents to jump the whole Latin ligatures like fi, ff, ...
785     numberOfCharacters = 1u;
786   }
787   else
788   {
789     GlyphIndex glyphIndex = *( charactersToGlyphBuffer + index );
790     numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
791
792     while( 0u == numberOfCharacters )
793     {
794       numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
795       ++glyphIndex;
796     }
797   }
798
799   if( index < mEventData->mPrimaryCursorPosition )
800   {
801     cursorIndex -= numberOfCharacters;
802   }
803   else
804   {
805     cursorIndex += numberOfCharacters;
806   }
807
808   return cursorIndex;
809 }
810
811 void Controller::Impl::UpdateCursorPosition()
812 {
813   if( NULL == mEventData )
814   {
815     // Nothing to do if there is no text input.
816     return;
817   }
818
819   CursorInfo cursorInfo;
820   GetCursorPosition( mEventData->mPrimaryCursorPosition,
821                      cursorInfo );
822
823   mEventData->mDecorator->SetPosition( PRIMARY_CURSOR,
824                                        cursorInfo.primaryPosition.x + mEventData->mScrollPosition.x + mAlignmentOffset.x,
825                                        cursorInfo.primaryPosition.y + mEventData->mScrollPosition.y + mAlignmentOffset.y,
826                                        cursorInfo.primaryCursorHeight,
827                                        cursorInfo.lineHeight );
828
829   if( cursorInfo.isSecondaryCursor )
830   {
831     mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_BOTH );
832     mEventData->mDecorator->SetPosition( SECONDARY_CURSOR,
833                                          cursorInfo.secondaryPosition.x + mEventData->mScrollPosition.x + mAlignmentOffset.x,
834                                          cursorInfo.secondaryPosition.y + mEventData->mScrollPosition.y + mAlignmentOffset.y,
835                                          cursorInfo.secondaryCursorHeight,
836                                          cursorInfo.lineHeight );
837   }
838   else
839   {
840     mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
841   }
842
843   mEventData->mUpdateCursorPosition = false;
844   mEventData->mDecoratorUpdated = true;
845 }
846
847 void Controller::Impl::ClampHorizontalScroll( const Vector2& actualSize )
848 {
849   // Clamp between -space & 0 (and the text alignment).
850   if( actualSize.width > mControlSize.width )
851   {
852     const float space = ( actualSize.width - mControlSize.width ) + mAlignmentOffset.x;
853     mEventData->mScrollPosition.x = ( mEventData->mScrollPosition.x < -space ) ? -space : mEventData->mScrollPosition.x;
854     mEventData->mScrollPosition.x = ( mEventData->mScrollPosition.x > -mAlignmentOffset.x ) ? -mAlignmentOffset.x : mEventData->mScrollPosition.x;
855
856     mEventData->mDecoratorUpdated = true;
857   }
858   else
859   {
860     mEventData->mScrollPosition.x = 0.f;
861   }
862 }
863
864 void Controller::Impl::ClampVerticalScroll( const Vector2& actualSize )
865 {
866   // Clamp between -space & 0 (and the text alignment).
867   if( actualSize.height > mControlSize.height )
868   {
869     const float space = ( actualSize.height - mControlSize.height ) + mAlignmentOffset.y;
870     mEventData->mScrollPosition.y = ( mEventData->mScrollPosition.y < -space ) ? -space : mEventData->mScrollPosition.y;
871     mEventData->mScrollPosition.y = ( mEventData->mScrollPosition.y > -mAlignmentOffset.y ) ? -mAlignmentOffset.y : mEventData->mScrollPosition.y;
872
873     mEventData->mDecoratorUpdated = true;
874   }
875   else
876   {
877     mEventData->mScrollPosition.y = 0.f;
878   }
879 }
880
881 void Controller::Impl::ScrollToMakeCursorVisible()
882 {
883   if( NULL == mEventData )
884   {
885     // Nothing to do if there is no text input.
886     return;
887   }
888
889   const Vector2& primaryCursorPosition = mEventData->mDecorator->GetPosition( PRIMARY_CURSOR );
890
891   Vector2 offset;
892   bool updateDecorator = false;
893   if( primaryCursorPosition.x < 0.f )
894   {
895     offset.x = -primaryCursorPosition.x;
896     mEventData->mScrollPosition.x += offset.x;
897     updateDecorator = true;
898   }
899   else if( primaryCursorPosition.x > mControlSize.width )
900   {
901     offset.x = mControlSize.width - primaryCursorPosition.x;
902     mEventData->mScrollPosition.x += offset.x;
903     updateDecorator = true;
904   }
905
906   if( updateDecorator && mEventData->mDecorator )
907   {
908     mEventData->mDecorator->UpdatePositions( offset );
909   }
910
911   // TODO : calculate the Y scroll.
912 }
913
914 void Controller::Impl::RequestRelayout()
915 {
916   mControlInterface.RequestTextRelayout();
917 }
918
919 } // namespace Text
920
921 } // namespace Toolkit
922
923 } // namespace Dali