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