Fix for Text Selection issues.
[platform/core/uifw/dali-toolkit.git] / dali-toolkit / internal / text / text-controller-impl.cpp
1 /*
2  * Copyright (c) 2015 Samsung Electronics Co., Ltd.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  *
16  */
17
18 // CLASS HEADER
19 #include <dali-toolkit/internal/text/text-controller-impl.h>
20
21 // EXTERNAL INCLUDES
22 #include <dali/public-api/adaptor-framework/key.h>
23 #include <dali/integration-api/debug.h>
24
25 // INTERNAL INCLUDES
26 #include <dali-toolkit/internal/text/bidirectional-support.h>
27 #include <dali-toolkit/internal/text/character-set-conversion.h>
28 #include <dali-toolkit/internal/text/layouts/layout-parameters.h>
29 #include <dali-toolkit/internal/text/multi-language-support.h>
30 #include <dali-toolkit/internal/text/script-run.h>
31 #include <dali-toolkit/internal/text/segmentation.h>
32 #include <dali-toolkit/internal/text/shaper.h>
33 #include <dali-toolkit/internal/text/text-io.h>
34 #include <dali-toolkit/internal/text/text-view.h>
35
36 namespace
37 {
38
39 #if defined(DEBUG_ENABLED)
40   Debug::Filter* gLogFilter = Debug::Filter::New(Debug::Concise, true, "LOG_TEXT_CONTROLS");
41 #endif
42
43 /**
44  * @brief Some characters can be shaped in more than one glyph.
45  * This struct is used to retrieve metrics from these group of glyphs.
46  */
47 struct GlyphMetrics
48 {
49   GlyphMetrics()
50   : fontHeight( 0.f ),
51     advance( 0.f ),
52     ascender( 0.f ),
53     xBearing( 0.f )
54   {}
55
56   ~GlyphMetrics()
57   {}
58
59   float fontHeight; ///< The font's height of that glyphs.
60   float advance;    ///< The sum of all the advances of all the glyphs.
61   float ascender;   ///< The font's ascender.
62   float xBearing;   ///< The x bearing of the first glyph.
63 };
64
65 const std::string EMPTY_STRING("");
66
67 } // namespace
68
69 namespace Dali
70 {
71
72 namespace Toolkit
73 {
74
75 namespace Text
76 {
77
78 /**
79  * @brief Get some glyph's metrics of a group of glyphs formed as a result of shaping one character.
80  *
81  * @param[in] glyphIndex The index to the first glyph.
82  * @param[in] numberOfGlyphs The number of glyphs.
83  * @param[out] glyphMetrics Some glyph metrics (font height, advance, ascender and x bearing).
84  * @param[in]
85  * @param[in]
86  */
87 void GetGlyphsMetrics( GlyphIndex glyphIndex,
88                        Length numberOfGlyphs,
89                        GlyphMetrics& glyphMetrics,
90                        VisualModelPtr visualModel,
91                        TextAbstraction::FontClient& fontClient )
92 {
93   const GlyphInfo* glyphsBuffer = visualModel->mGlyphs.Begin();
94
95   const GlyphInfo& firstGlyph = *( glyphsBuffer + glyphIndex );
96
97   Text::FontMetrics fontMetrics;
98   fontClient.GetFontMetrics( firstGlyph.fontId, fontMetrics );
99
100   glyphMetrics.fontHeight = fontMetrics.height;
101   glyphMetrics.advance = firstGlyph.advance;
102   glyphMetrics.ascender = fontMetrics.ascender;
103   glyphMetrics.xBearing = firstGlyph.xBearing;
104
105   for( unsigned int i = 1u; i < numberOfGlyphs; ++i )
106   {
107     const GlyphInfo& glyphInfo = *( glyphsBuffer + glyphIndex + i );
108
109     glyphMetrics.advance += glyphInfo.advance;
110   }
111 }
112
113 EventData::EventData( DecoratorPtr decorator )
114 : mDecorator( decorator ),
115   mPlaceholderTextActive(),
116   mPlaceholderTextInactive(),
117   mPlaceholderTextColor( 0.8f, 0.8f, 0.8f, 0.8f ),
118   mEventQueue(),
119   mScrollPosition(),
120   mState( INACTIVE ),
121   mPrimaryCursorPosition( 0u ),
122   mLeftSelectionPosition( 0u ),
123   mRightSelectionPosition( 0u ),
124   mPreEditStartPosition( 0u ),
125   mPreEditLength( 0u ),
126   mIsShowingPlaceholderText( false ),
127   mPreEditFlag( false ),
128   mDecoratorUpdated( false ),
129   mCursorBlinkEnabled( true ),
130   mGrabHandleEnabled( true ),
131   mGrabHandlePopupEnabled( true ),
132   mSelectionEnabled( true ),
133   mHorizontalScrollingEnabled( true ),
134   mVerticalScrollingEnabled( false ),
135   mUpdateCursorPosition( false ),
136   mUpdateLeftSelectionPosition( false ),
137   mUpdateRightSelectionPosition( false ),
138   mScrollAfterUpdatePosition( false ),
139   mScrollAfterDelete( false )
140 {}
141
142 EventData::~EventData()
143 {}
144
145 bool Controller::Impl::ProcessInputEvents()
146 {
147   DALI_LOG_INFO( gLogFilter, Debug::Verbose, "-->Controller::ProcessInputEvents\n" );
148   if( NULL == mEventData )
149   {
150     // Nothing to do if there is no text input.
151     DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::ProcessInputEvents no event data\n" );
152     return false;
153   }
154
155   if( mEventData->mDecorator )
156   {
157     for( std::vector<Event>::iterator iter = mEventData->mEventQueue.begin();
158          iter != mEventData->mEventQueue.end();
159          ++iter )
160     {
161       switch( iter->type )
162       {
163         case Event::CURSOR_KEY_EVENT:
164         {
165           OnCursorKeyEvent( *iter );
166           break;
167         }
168         case Event::TAP_EVENT:
169         {
170           OnTapEvent( *iter );
171           break;
172         }
173         case Event::PAN_EVENT:
174         {
175           OnPanEvent( *iter );
176           break;
177         }
178         case Event::GRAB_HANDLE_EVENT:
179         case Event::LEFT_SELECTION_HANDLE_EVENT:
180         case Event::RIGHT_SELECTION_HANDLE_EVENT: // Fall through
181         {
182           OnHandleEvent( *iter );
183           break;
184         }
185         case Event::SELECT:
186         {
187           OnSelectEvent( *iter );
188           break;
189         }
190         case Event::SELECT_ALL:
191         {
192           OnSelectAllEvent();
193           break;
194         }
195       }
196     }
197   }
198
199   // The cursor must also be repositioned after inserts into the model
200   if( mEventData->mUpdateCursorPosition )
201   {
202     // Updates the cursor position and scrolls the text to make it visible.
203
204     UpdateCursorPosition();
205
206     if( mEventData->mScrollAfterUpdatePosition )
207     {
208       const Vector2& primaryCursorPosition = mEventData->mDecorator->GetPosition( PRIMARY_CURSOR );
209
210       ScrollToMakePositionVisible( primaryCursorPosition );
211       mEventData->mScrollAfterUpdatePosition = false;
212     }
213
214     mEventData->mDecoratorUpdated = true;
215     mEventData->mUpdateCursorPosition = false;
216   }
217   else if( mEventData->mScrollAfterDelete )
218   {
219     ScrollTextToMatchCursor();
220     mEventData->mDecoratorUpdated = true;
221     mEventData->mScrollAfterDelete = false;
222   }
223   else
224   {
225     bool leftScroll = false;
226     bool rightScroll = false;
227
228     if( mEventData->mUpdateLeftSelectionPosition )
229     {
230       UpdateSelectionHandle( LEFT_SELECTION_HANDLE );
231
232       if( mEventData->mScrollAfterUpdatePosition )
233       {
234         const Vector2& leftHandlePosition = mEventData->mDecorator->GetPosition( LEFT_SELECTION_HANDLE );
235
236         ScrollToMakePositionVisible( leftHandlePosition );
237         leftScroll = true;
238       }
239
240       mEventData->mDecoratorUpdated = true;
241       mEventData->mUpdateLeftSelectionPosition = false;
242     }
243
244     if( mEventData->mUpdateRightSelectionPosition )
245     {
246       UpdateSelectionHandle( RIGHT_SELECTION_HANDLE );
247
248       if( mEventData->mScrollAfterUpdatePosition )
249       {
250         const Vector2& rightHandlePosition = mEventData->mDecorator->GetPosition( RIGHT_SELECTION_HANDLE );
251
252         ScrollToMakePositionVisible( rightHandlePosition );
253         rightScroll = true;
254       }
255
256       mEventData->mDecoratorUpdated = true;
257       mEventData->mUpdateRightSelectionPosition = false;
258     }
259
260     if( leftScroll || rightScroll )
261     {
262       mEventData->mScrollAfterUpdatePosition = false;
263     }
264   }
265
266   mEventData->mEventQueue.clear();
267
268   DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::ProcessInputEvents\n" );
269
270   const bool decoratorUpdated = mEventData->mDecoratorUpdated;
271   mEventData->mDecoratorUpdated = false;
272
273   return decoratorUpdated;
274 }
275
276 void Controller::Impl::UpdateModel( OperationsMask operationsRequired )
277 {
278   // Calculate the operations to be done.
279   const OperationsMask operations = static_cast<OperationsMask>( mOperationsPending & operationsRequired );
280
281   Vector<Character>& utf32Characters = mLogicalModel->mText;
282
283   const Length numberOfCharacters = utf32Characters.Count();
284
285   Vector<LineBreakInfo>& lineBreakInfo = mLogicalModel->mLineBreakInfo;
286   if( GET_LINE_BREAKS & operations )
287   {
288     // Retrieves the line break info. The line break info is used to split the text in 'paragraphs' to
289     // calculate the bidirectional info for each 'paragraph'.
290     // It's also used to layout the text (where it should be a new line) or to shape the text (text in different lines
291     // is not shaped together).
292     lineBreakInfo.Resize( numberOfCharacters, TextAbstraction::LINE_NO_BREAK );
293
294     SetLineBreakInfo( utf32Characters,
295                       lineBreakInfo );
296   }
297
298   Vector<WordBreakInfo>& wordBreakInfo = mLogicalModel->mWordBreakInfo;
299   if( GET_WORD_BREAKS & operations )
300   {
301     // Retrieves the word break info. The word break info is used to layout the text (where to wrap the text in lines).
302     wordBreakInfo.Resize( numberOfCharacters, TextAbstraction::WORD_NO_BREAK );
303
304     SetWordBreakInfo( utf32Characters,
305                       wordBreakInfo );
306   }
307
308   const bool getScripts = GET_SCRIPTS & operations;
309   const bool validateFonts = VALIDATE_FONTS & operations;
310
311   Vector<ScriptRun>& scripts = mLogicalModel->mScriptRuns;
312   Vector<FontRun>& validFonts = mLogicalModel->mFontRuns;
313
314   if( getScripts || validateFonts )
315   {
316     // Validates the fonts assigned by the application or assigns default ones.
317     // It makes sure all the characters are going to be rendered by the correct font.
318     MultilanguageSupport multilanguageSupport = MultilanguageSupport::Get();
319
320     if( getScripts )
321     {
322       // Retrieves the scripts used in the text.
323       multilanguageSupport.SetScripts( utf32Characters,
324                                        lineBreakInfo,
325                                        scripts );
326     }
327
328     if( validateFonts )
329     {
330       if( 0u == validFonts.Count() )
331       {
332         // Copy the requested font defaults received via the property system.
333         // These may not be valid i.e. may not contain glyphs for the necessary scripts.
334         GetDefaultFonts( validFonts, numberOfCharacters );
335       }
336
337       // Validates the fonts. If there is a character with no assigned font it sets a default one.
338       // After this call, fonts are validated.
339       multilanguageSupport.ValidateFonts( utf32Characters,
340                                           scripts,
341                                           validFonts );
342     }
343   }
344
345   Vector<Character> mirroredUtf32Characters;
346   bool textMirrored = false;
347   if( BIDI_INFO & operations )
348   {
349     // Count the number of LINE_NO_BREAK to reserve some space for the vector of paragraph's
350     // bidirectional info.
351
352     Length numberOfParagraphs = 0u;
353
354     const TextAbstraction::LineBreakInfo* lineBreakInfoBuffer = lineBreakInfo.Begin();
355     for( Length index = 0u; index < numberOfCharacters; ++index )
356     {
357       if( TextAbstraction::LINE_NO_BREAK == *( lineBreakInfoBuffer + index ) )
358       {
359         ++numberOfParagraphs;
360       }
361     }
362
363     Vector<BidirectionalParagraphInfoRun>& bidirectionalInfo = mLogicalModel->mBidirectionalParagraphInfo;
364     bidirectionalInfo.Reserve( numberOfParagraphs );
365
366     // Calculates the bidirectional info for the whole paragraph if it contains right to left scripts.
367     SetBidirectionalInfo( utf32Characters,
368                           scripts,
369                           lineBreakInfo,
370                           bidirectionalInfo );
371
372     if( 0u != bidirectionalInfo.Count() )
373     {
374       // This paragraph has right to left text. Some characters may need to be mirrored.
375       // TODO: consider if the mirrored string can be stored as well.
376
377       textMirrored = GetMirroredText( utf32Characters, mirroredUtf32Characters );
378
379       // Only set the character directions if there is right to left characters.
380       Vector<CharacterDirection>& directions = mLogicalModel->mCharacterDirections;
381       directions.Resize( numberOfCharacters );
382
383       GetCharactersDirection( bidirectionalInfo,
384                               directions );
385     }
386     else
387     {
388       // There is no right to left characters. Clear the directions vector.
389       mLogicalModel->mCharacterDirections.Clear();
390     }
391
392    }
393
394   Vector<GlyphInfo>& glyphs = mVisualModel->mGlyphs;
395   Vector<CharacterIndex>& glyphsToCharactersMap = mVisualModel->mGlyphsToCharacters;
396   Vector<Length>& charactersPerGlyph = mVisualModel->mCharactersPerGlyph;
397   if( SHAPE_TEXT & operations )
398   {
399     const Vector<Character>& textToShape = textMirrored ? mirroredUtf32Characters : utf32Characters;
400     // Shapes the text.
401     ShapeText( textToShape,
402                lineBreakInfo,
403                scripts,
404                validFonts,
405                glyphs,
406                glyphsToCharactersMap,
407                charactersPerGlyph );
408
409     // Create the 'number of glyphs' per character and the glyph to character conversion tables.
410     mVisualModel->CreateGlyphsPerCharacterTable( numberOfCharacters );
411     mVisualModel->CreateCharacterToGlyphTable( numberOfCharacters );
412   }
413
414   const Length numberOfGlyphs = glyphs.Count();
415
416   if( GET_GLYPH_METRICS & operations )
417   {
418     mFontClient.GetGlyphMetrics( glyphs.Begin(), numberOfGlyphs );
419   }
420 }
421
422 void Controller::Impl::GetDefaultFonts( Vector<FontRun>& fonts, Length numberOfCharacters )
423 {
424   if( mFontDefaults )
425   {
426     FontRun fontRun;
427     fontRun.characterRun.characterIndex = 0;
428     fontRun.characterRun.numberOfCharacters = numberOfCharacters;
429     fontRun.fontId = mFontDefaults->GetFontId( mFontClient );
430     fontRun.isDefault = true;
431
432     fonts.PushBack( fontRun );
433   }
434 }
435
436 void Controller::Impl::OnCursorKeyEvent( const Event& event )
437 {
438   if( NULL == mEventData )
439   {
440     // Nothing to do if there is no text input.
441     return;
442   }
443
444   int keyCode = event.p1.mInt;
445
446   if( Dali::DALI_KEY_CURSOR_LEFT == keyCode )
447   {
448     if( mEventData->mPrimaryCursorPosition > 0u )
449     {
450       mEventData->mPrimaryCursorPosition = CalculateNewCursorIndex( mEventData->mPrimaryCursorPosition - 1u );
451     }
452   }
453   else if( Dali::DALI_KEY_CURSOR_RIGHT == keyCode )
454   {
455     if( mLogicalModel->mText.Count() > mEventData->mPrimaryCursorPosition )
456     {
457       mEventData->mPrimaryCursorPosition = CalculateNewCursorIndex( mEventData->mPrimaryCursorPosition );
458     }
459   }
460   else if( Dali::DALI_KEY_CURSOR_UP == keyCode )
461   {
462     // TODO
463   }
464   else if(   Dali::DALI_KEY_CURSOR_DOWN == keyCode )
465   {
466     // TODO
467   }
468
469   mEventData->mUpdateCursorPosition = true;
470   mEventData->mScrollAfterUpdatePosition = true;
471 }
472
473 void Controller::Impl::OnTapEvent( const Event& event )
474 {
475   if( NULL != mEventData )
476   {
477     const unsigned int tapCount = event.p1.mUint;
478
479     if( 1u == tapCount )
480     {
481       if( ! IsShowingPlaceholderText() )
482       {
483         const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
484         const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
485
486         mEventData->mPrimaryCursorPosition = GetClosestCursorIndex( xPosition,
487                                                                     yPosition );
488       }
489       else
490       {
491         mEventData->mPrimaryCursorPosition = 0u;
492       }
493
494       mEventData->mUpdateCursorPosition = true;
495       mEventData->mScrollAfterUpdatePosition = true;
496     }
497   }
498 }
499
500 void Controller::Impl::OnPanEvent( const Event& event )
501 {
502   if( NULL == mEventData )
503   {
504     // Nothing to do if there is no text input.
505     return;
506   }
507
508   int state = event.p1.mInt;
509
510   if( Gesture::Started    == state ||
511       Gesture::Continuing == state )
512   {
513     const Vector2& actualSize = mVisualModel->GetActualSize();
514     const Vector2 currentScroll = mEventData->mScrollPosition;
515
516     if( mEventData->mHorizontalScrollingEnabled )
517     {
518       const float displacementX = event.p2.mFloat;
519       mEventData->mScrollPosition.x += displacementX;
520
521       ClampHorizontalScroll( actualSize );
522     }
523
524     if( mEventData->mVerticalScrollingEnabled )
525     {
526       const float displacementY = event.p3.mFloat;
527       mEventData->mScrollPosition.y += displacementY;
528
529       ClampVerticalScroll( actualSize );
530     }
531
532     if( mEventData->mDecorator )
533     {
534       mEventData->mDecorator->UpdatePositions( mEventData->mScrollPosition - currentScroll );
535     }
536   }
537 }
538
539 void Controller::Impl::OnHandleEvent( const Event& event )
540 {
541   if( NULL == mEventData )
542   {
543     // Nothing to do if there is no text input.
544     return;
545   }
546
547   const unsigned int state = event.p1.mUint;
548   const bool handleStopScrolling = ( HANDLE_STOP_SCROLLING == state );
549
550   if( HANDLE_PRESSED == state )
551   {
552     // The event.p2 and event.p3 are in decorator coords. Need to transforms to text coords.
553     const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
554     const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
555
556     const CharacterIndex handleNewPosition = GetClosestCursorIndex( xPosition, yPosition );
557
558     if( Event::GRAB_HANDLE_EVENT == event.type )
559     {
560       ChangeState ( EventData::GRAB_HANDLE_PANNING );
561
562       if( handleNewPosition != mEventData->mPrimaryCursorPosition )
563       {
564         mEventData->mPrimaryCursorPosition = handleNewPosition;
565         mEventData->mUpdateCursorPosition = true;
566       }
567     }
568     else if( Event::LEFT_SELECTION_HANDLE_EVENT == event.type )
569     {
570       ChangeState ( EventData::SELECTION_HANDLE_PANNING );
571
572       if( ( handleNewPosition != mEventData->mLeftSelectionPosition ) &&
573           ( handleNewPosition != mEventData->mRightSelectionPosition ) )
574       {
575         mEventData->mLeftSelectionPosition = handleNewPosition;
576
577         RepositionSelectionHandles( mEventData->mLeftSelectionPosition,
578                                     mEventData->mRightSelectionPosition );
579
580         mEventData->mUpdateLeftSelectionPosition = true;
581       }
582     }
583     else if( Event::RIGHT_SELECTION_HANDLE_EVENT == event.type )
584     {
585       ChangeState ( EventData::SELECTION_HANDLE_PANNING );
586
587       if( ( handleNewPosition != mEventData->mRightSelectionPosition ) &&
588           ( handleNewPosition != mEventData->mLeftSelectionPosition ) )
589       {
590         mEventData->mRightSelectionPosition = handleNewPosition;
591
592         RepositionSelectionHandles( mEventData->mLeftSelectionPosition,
593                                     mEventData->mRightSelectionPosition );
594
595         mEventData->mUpdateRightSelectionPosition = true;
596       }
597     }
598   } // end ( HANDLE_PRESSED == state )
599   else if( ( HANDLE_RELEASED == state ) ||
600            handleStopScrolling )
601   {
602     CharacterIndex handlePosition = 0u;
603     if( handleStopScrolling )
604     {
605       // The event.p2 and event.p3 are in decorator coords. Need to transforms to text coords.
606       const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
607       const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
608
609       handlePosition = GetClosestCursorIndex( xPosition, yPosition );
610     }
611
612     if( Event::GRAB_HANDLE_EVENT == event.type )
613     {
614       mEventData->mUpdateCursorPosition = true;
615
616       ChangeState( EventData::EDITING_WITH_POPUP );
617
618       if( handleStopScrolling )
619       {
620         mEventData->mScrollAfterUpdatePosition = mEventData->mPrimaryCursorPosition != handlePosition;
621         mEventData->mPrimaryCursorPosition = handlePosition;
622       }
623     }
624     else if( Event::LEFT_SELECTION_HANDLE_EVENT == event.type )
625     {
626       ChangeState( EventData::SELECTING );
627
628       if( handleStopScrolling )
629       {
630         mEventData->mUpdateLeftSelectionPosition = ( mEventData->mLeftSelectionPosition != handlePosition ) && ( mEventData->mRightSelectionPosition != handlePosition);
631         mEventData->mScrollAfterUpdatePosition = mEventData->mUpdateLeftSelectionPosition;
632
633         if( mEventData->mUpdateLeftSelectionPosition )
634         {
635           mEventData->mLeftSelectionPosition = handlePosition;
636
637           RepositionSelectionHandles( mEventData->mLeftSelectionPosition,
638                                       mEventData->mRightSelectionPosition );
639         }
640       }
641     }
642     else if( Event::RIGHT_SELECTION_HANDLE_EVENT == event.type )
643     {
644       ChangeState( EventData::SELECTING );
645
646       if( handleStopScrolling )
647       {
648         mEventData->mUpdateRightSelectionPosition = ( mEventData->mRightSelectionPosition != handlePosition ) && ( mEventData->mLeftSelectionPosition != handlePosition );
649         mEventData->mScrollAfterUpdatePosition = mEventData->mUpdateRightSelectionPosition;
650
651         if( mEventData->mUpdateRightSelectionPosition )
652         {
653           mEventData->mRightSelectionPosition = handlePosition;
654           RepositionSelectionHandles( mEventData->mLeftSelectionPosition,
655                                       mEventData->mRightSelectionPosition );
656         }
657       }
658     }
659
660     mEventData->mDecoratorUpdated = true;
661   } // end ( ( HANDLE_RELEASED == state ) || ( HANDLE_STOP_SCROLLING == state ) )
662   else if( HANDLE_SCROLLING == state )
663   {
664     const float xSpeed = event.p2.mFloat;
665     const Vector2& actualSize = mVisualModel->GetActualSize();
666     const Vector2 currentScrollPosition = mEventData->mScrollPosition;
667
668     mEventData->mScrollPosition.x += xSpeed;
669
670     ClampHorizontalScroll( actualSize );
671
672     if( Vector2::ZERO == ( currentScrollPosition - mEventData->mScrollPosition ) )
673     {
674       // Notify the decorator there is no more text to scroll.
675       // The decorator won't send more scroll events.
676       mEventData->mDecorator->NotifyEndOfScroll();
677     }
678     else
679     {
680       const bool scrollRightDirection = xSpeed > 0.f;
681       const bool leftSelectionHandleEvent = Event::LEFT_SELECTION_HANDLE_EVENT == event.type;
682       const bool rightSelectionHandleEvent = Event::RIGHT_SELECTION_HANDLE_EVENT == event.type;
683
684       if( Event::GRAB_HANDLE_EVENT == event.type )
685       {
686         ChangeState( EventData::GRAB_HANDLE_PANNING );
687
688         Vector2 position = mEventData->mDecorator->GetPosition( GRAB_HANDLE );
689
690         // Position the grag handle close to either the left or right edge.
691         position.x = scrollRightDirection ? 0.f : mControlSize.width;
692
693         // Get the new handle position.
694         // The grab handle's position is in decorator coords. Need to transforms to text coords.
695         const CharacterIndex handlePosition = GetClosestCursorIndex( position.x - mEventData->mScrollPosition.x - mAlignmentOffset.x,
696                                                                      position.y - mEventData->mScrollPosition.y - mAlignmentOffset.y );
697
698         mEventData->mUpdateCursorPosition = mEventData->mPrimaryCursorPosition != handlePosition;
699         mEventData->mScrollAfterUpdatePosition = mEventData->mUpdateCursorPosition;
700         mEventData->mPrimaryCursorPosition = handlePosition;
701       }
702       else if( leftSelectionHandleEvent || rightSelectionHandleEvent )
703       {
704         // TODO: This is recalculating the selection box every time the text is scrolled with the selection handles.
705         //       Think if something can be done to save power.
706
707         ChangeState( EventData::SELECTION_HANDLE_PANNING );
708
709         Vector2 position = mEventData->mDecorator->GetPosition( leftSelectionHandleEvent ? Text::LEFT_SELECTION_HANDLE : Text::RIGHT_SELECTION_HANDLE );
710
711         // Position the selection handle close to either the left or right edge.
712         position.x = scrollRightDirection ? 0.f : mControlSize.width;
713
714         // Get the new handle position.
715         // The selection handle's position is in decorator coords. Need to transforms to text coords.
716         const CharacterIndex handlePosition = GetClosestCursorIndex( position.x - mEventData->mScrollPosition.x - mAlignmentOffset.x,
717                                                                      position.y - mEventData->mScrollPosition.y - mAlignmentOffset.y );
718
719         if( leftSelectionHandleEvent )
720         {
721           mEventData->mUpdateLeftSelectionPosition = handlePosition != mEventData->mLeftSelectionPosition;
722           mEventData->mLeftSelectionPosition = handlePosition;
723         }
724         else
725         {
726           mEventData->mUpdateRightSelectionPosition = handlePosition != mEventData->mRightSelectionPosition;
727           mEventData->mRightSelectionPosition = handlePosition;
728         }
729
730         if( mEventData->mUpdateLeftSelectionPosition || mEventData->mUpdateRightSelectionPosition )
731         {
732           RepositionSelectionHandles( mEventData->mLeftSelectionPosition,
733                                       mEventData->mRightSelectionPosition );
734
735           mEventData->mScrollAfterUpdatePosition = true;
736         }
737       }
738       mEventData->mDecoratorUpdated = true;
739     }
740   } // end ( HANDLE_SCROLLING == state )
741 }
742
743 void Controller::Impl::OnSelectEvent( const Event& event )
744 {
745   if( NULL == mEventData )
746   {
747     // Nothing to do if there is no text.
748     return;
749   }
750
751   if( mEventData->mSelectionEnabled )
752   {
753     // The event.p2 and event.p3 are in decorator coords. Need to transforms to text coords.
754     const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
755     const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
756
757     const CharacterIndex leftPosition = mEventData->mLeftSelectionPosition;
758     const CharacterIndex rightPosition = mEventData->mRightSelectionPosition;
759
760     RepositionSelectionHandles( xPosition,
761                                 yPosition );
762
763     mEventData->mUpdateLeftSelectionPosition = leftPosition != mEventData->mLeftSelectionPosition;
764     mEventData->mUpdateRightSelectionPosition = rightPosition != mEventData->mRightSelectionPosition;
765
766     mEventData->mScrollAfterUpdatePosition = ( ( mEventData->mUpdateLeftSelectionPosition || mEventData->mUpdateRightSelectionPosition ) &&
767                                                ( mEventData->mLeftSelectionPosition != mEventData->mRightSelectionPosition ) );
768   }
769 }
770
771 void Controller::Impl::OnSelectAllEvent()
772 {
773   if( NULL == mEventData )
774   {
775     // Nothing to do if there is no text.
776     return;
777   }
778
779   if( mEventData->mSelectionEnabled )
780   {
781     RepositionSelectionHandles( 0u,
782                                 mLogicalModel->mText.Count() );
783
784     mEventData->mScrollAfterUpdatePosition = true;
785     mEventData->mUpdateLeftSelectionPosition = true;
786     mEventData->mUpdateRightSelectionPosition = true;
787   }
788 }
789
790 void Controller::Impl::RetreiveSelection( std::string& selectedText, bool deleteAfterRetreival )
791 {
792   if( mEventData->mLeftSelectionPosition ==  mEventData->mRightSelectionPosition )
793   {
794     // Nothing to select if handles are in the same place.
795     selectedText="";
796     return;
797   }
798
799   //Get start and end position of selection
800   uint32_t startOfSelectedText = mEventData->mLeftSelectionPosition;
801   uint32_t lengthOfSelectedText =  mEventData->mRightSelectionPosition - startOfSelectedText;
802
803   // Validate the start and end selection points
804   if( ( startOfSelectedText >= 0 ) && (  ( startOfSelectedText + lengthOfSelectedText ) <=  mLogicalModel->mText.Count() ) )
805   {
806     //Get text as a UTF8 string
807     Vector<Character>& utf32Characters = mLogicalModel->mText;
808
809     Utf32ToUtf8( &utf32Characters[startOfSelectedText], lengthOfSelectedText, selectedText );
810
811     if ( deleteAfterRetreival  ) // Only delete text if copied successfully
812     {
813       // Delete text between handles
814       Vector<Character>& currentText = mLogicalModel->mText;
815
816       Vector<Character>::Iterator first = currentText.Begin() + startOfSelectedText;
817       Vector<Character>::Iterator last  = first + lengthOfSelectedText;
818       currentText.Erase( first, last );
819     }
820     mEventData->mPrimaryCursorPosition = mEventData->mLeftSelectionPosition;
821     mEventData->mScrollAfterDelete = true;
822     mEventData->mDecoratorUpdated = true;
823   }
824 }
825
826 bool Controller::Impl::CopyStringToClipboard( std::string& source )
827 {
828   //Send string to clipboard
829   return ( mClipboard && mClipboard.SetItem( source ) );
830 }
831
832 void Controller::Impl::SendSelectionToClipboard( bool deleteAfterSending )
833 {
834   std::string selectedText;
835   RetreiveSelection( selectedText, deleteAfterSending );
836   CopyStringToClipboard( selectedText );
837   ChangeState( EventData::EDITING );
838 }
839
840 void Controller::Impl::GetTextFromClipboard( unsigned int itemIndex, std::string& retreivedString )
841 {
842   if ( mClipboard )
843   {
844     retreivedString =  mClipboard.GetItem( itemIndex );
845   }
846 }
847
848 void Controller::Impl::RepositionSelectionHandles( CharacterIndex selectionStart, CharacterIndex selectionEnd )
849 {
850   if( selectionStart == selectionEnd )
851   {
852     // Nothing to select if handles are in the same place.
853     return;
854   }
855
856   mEventData->mDecorator->ClearHighlights();
857
858   mEventData->mLeftSelectionPosition = selectionStart;
859   mEventData->mRightSelectionPosition = selectionEnd;
860
861   const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
862   const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
863   const GlyphInfo* const glyphsBuffer = mVisualModel->mGlyphs.Begin();
864   const Vector2* const positionsBuffer = mVisualModel->mGlyphPositions.Begin();
865
866   // TODO: Better algorithm to create the highlight box.
867   // TODO: Multi-line.
868
869   const Vector<LineRun>& lines = mVisualModel->mLines;
870   const LineRun& firstLine = *lines.Begin();
871   const float height = firstLine.ascender + -firstLine.descender;
872
873   const bool indicesSwapped = ( selectionStart > selectionEnd );
874   if( indicesSwapped )
875   {
876     std::swap( selectionStart, selectionEnd );
877   }
878
879   GlyphIndex glyphStart = *( charactersToGlyphBuffer + selectionStart );
880   GlyphIndex glyphEnd = *( charactersToGlyphBuffer + ( selectionEnd - 1u ) ) + *( glyphsPerCharacterBuffer + ( selectionEnd - 1u ) ) - 1u;
881
882   mEventData->mDecorator->SwapSelectionHandlesEnabled( firstLine.direction != indicesSwapped );
883
884   const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
885
886   for( GlyphIndex index = glyphStart; index <= glyphEnd; ++index )
887   {
888     const GlyphInfo& glyph = *( glyphsBuffer + index );
889     const Vector2& position = *( positionsBuffer + index );
890
891     const float xPosition = position.x - glyph.xBearing + offset.x;
892     mEventData->mDecorator->AddHighlight( xPosition, offset.y, xPosition + glyph.advance, height );
893   }
894
895   CursorInfo primaryCursorInfo;
896   GetCursorPosition( mEventData->mLeftSelectionPosition,
897                      primaryCursorInfo );
898
899   CursorInfo secondaryCursorInfo;
900   GetCursorPosition( mEventData->mRightSelectionPosition,
901                      secondaryCursorInfo );
902
903   const Vector2 primaryPosition = primaryCursorInfo.primaryPosition + offset;
904   const Vector2 secondaryPosition = secondaryCursorInfo.primaryPosition + offset;
905
906   mEventData->mDecorator->SetPosition( LEFT_SELECTION_HANDLE, primaryPosition.x, primaryPosition.y, primaryCursorInfo.lineHeight );
907
908   mEventData->mDecorator->SetPosition( RIGHT_SELECTION_HANDLE, secondaryPosition.x, secondaryPosition.y, secondaryCursorInfo.lineHeight );
909
910   // Set the flag to update the decorator.
911   mEventData->mDecoratorUpdated = true;
912 }
913
914 void Controller::Impl::RepositionSelectionHandles( float visualX, float visualY )
915 {
916   if( NULL == mEventData )
917   {
918     // Nothing to do if there is no text input.
919     return;
920   }
921
922   if( IsShowingPlaceholderText() )
923   {
924     // Nothing to do if there is the place-holder text.
925     return;
926   }
927
928   const Length numberOfGlyphs = mVisualModel->mGlyphs.Count();
929   const Length numberOfLines  = mVisualModel->mLines.Count();
930   if( 0 == numberOfGlyphs ||
931       0 == numberOfLines )
932   {
933     // Nothing to do if there is no text.
934     return;
935   }
936
937   // Find which word was selected
938   CharacterIndex selectionStart( 0 );
939   CharacterIndex selectionEnd( 0 );
940   FindSelectionIndices( visualX, visualY, selectionStart, selectionEnd );
941   DALI_LOG_INFO( gLogFilter, Debug::Verbose, "%p selectionStart %d selectionEnd %d\n", this, selectionStart, selectionEnd );
942
943   if( selectionStart == selectionEnd )
944   {
945     ChangeState( EventData::EDITING );
946     // Nothing to select. i.e. a white space, out of bounds
947     return;
948   }
949
950   RepositionSelectionHandles( selectionStart, selectionEnd );
951 }
952
953 void Controller::Impl::ChangeState( EventData::State newState )
954 {
955   if( NULL == mEventData )
956   {
957     // Nothing to do if there is no text input.
958     return;
959   }
960
961   if( mEventData->mState != newState )
962   {
963     mEventData->mState = newState;
964
965     if( EventData::INACTIVE == mEventData->mState )
966     {
967       mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
968       mEventData->mDecorator->StopCursorBlink();
969       mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
970       mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
971       mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
972       mEventData->mDecorator->SetPopupActive( false );
973       mEventData->mDecoratorUpdated = true;
974     }
975     else if ( EventData::SELECTING == mEventData->mState )
976     {
977       mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
978       mEventData->mDecorator->StopCursorBlink();
979       mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
980       mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true );
981       mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true );
982       if( mEventData->mGrabHandlePopupEnabled )
983       {
984         TextSelectionPopup::Buttons buttonsToShow = TextSelectionPopup::Buttons(  TextSelectionPopup::CUT | TextSelectionPopup::COPY );
985         if ( !IsClipboardEmpty() )
986         {
987           buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
988         }
989
990         mEventData->mDecorator->SetEnabledPopupButtons( buttonsToShow );
991         mEventData->mDecorator->SetPopupActive( true );
992       }
993       mEventData->mDecoratorUpdated = true;
994     }
995     else if ( EventData::SELECTION_CHANGED  == mEventData->mState )
996     {
997       if( mEventData->mGrabHandlePopupEnabled )
998       {
999         TextSelectionPopup::Buttons buttonsToShow = TextSelectionPopup::Buttons(  TextSelectionPopup::CUT | TextSelectionPopup::COPY );
1000         if (  !IsClipboardEmpty() )
1001         {
1002           buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
1003         }
1004         mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
1005         mEventData->mDecorator->SetEnabledPopupButtons( buttonsToShow );
1006         mEventData->mDecorator->SetPopupActive( true );
1007       }
1008       mEventData->mDecoratorUpdated = true;
1009     }
1010     else if( EventData::EDITING == mEventData->mState )
1011     {
1012       mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1013       if( mEventData->mCursorBlinkEnabled )
1014       {
1015         mEventData->mDecorator->StartCursorBlink();
1016       }
1017       // Grab handle is not shown until a tap is received whilst EDITING
1018       mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1019       mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1020       mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1021       if( mEventData->mGrabHandlePopupEnabled )
1022       {
1023         mEventData->mDecorator->SetPopupActive( false );
1024       }
1025       mEventData->mDecoratorUpdated = true;
1026     }
1027     else if( EventData::EDITING_WITH_POPUP == mEventData->mState )
1028     {
1029       mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1030       if( mEventData->mCursorBlinkEnabled )
1031       {
1032         mEventData->mDecorator->StartCursorBlink();
1033       }
1034       if( mEventData->mSelectionEnabled )
1035       {
1036         mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1037         mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1038       }
1039       else
1040       {
1041         mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
1042       }
1043       if( mEventData->mGrabHandlePopupEnabled )
1044       {
1045         TextSelectionPopup::Buttons buttonsToShow = TextSelectionPopup::Buttons( TextSelectionPopup::SELECT | TextSelectionPopup::SELECT_ALL );
1046
1047         if ( !IsClipboardEmpty() )
1048         {
1049           buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
1050         }
1051
1052         mEventData->mDecorator->SetEnabledPopupButtons( buttonsToShow );
1053         mEventData->mDecorator->SetPopupActive( true );
1054       }
1055       mEventData->mDecoratorUpdated = true;
1056     }
1057     else if ( EventData::SELECTION_HANDLE_PANNING == mEventData->mState )
1058     {
1059       mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
1060       mEventData->mDecorator->StopCursorBlink();
1061       mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1062       mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true );
1063       mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true );
1064       if( mEventData->mGrabHandlePopupEnabled )
1065       {
1066         mEventData->mDecorator->SetPopupActive( false );
1067       }
1068       mEventData->mDecoratorUpdated = true;
1069     }
1070     else if ( EventData::GRAB_HANDLE_PANNING == mEventData->mState )
1071     {
1072       mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1073       if( mEventData->mCursorBlinkEnabled )
1074       {
1075         mEventData->mDecorator->StartCursorBlink();
1076       }
1077       mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
1078       mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1079       mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1080       if( mEventData->mGrabHandlePopupEnabled )
1081       {
1082         mEventData->mDecorator->SetPopupActive( false );
1083       }
1084       mEventData->mDecoratorUpdated = true;
1085     }
1086   }
1087 }
1088
1089 LineIndex Controller::Impl::GetClosestLine( float y ) const
1090 {
1091   float totalHeight = 0.f;
1092   LineIndex lineIndex = 0u;
1093
1094   const Vector<LineRun>& lines = mVisualModel->mLines;
1095   for( LineIndex endLine = lines.Count();
1096        lineIndex < endLine;
1097        ++lineIndex )
1098   {
1099     const LineRun& lineRun = lines[lineIndex];
1100     totalHeight += lineRun.ascender + -lineRun.descender;
1101     if( y < totalHeight )
1102     {
1103       return lineIndex;
1104     }
1105   }
1106
1107   if( lineIndex == 0 )
1108   {
1109     return 0;
1110   }
1111
1112   return lineIndex-1;
1113 }
1114
1115 void Controller::Impl::FindSelectionIndices( float visualX, float visualY, CharacterIndex& startIndex, CharacterIndex& endIndex )
1116 {
1117   CharacterIndex hitCharacter = GetClosestCursorIndex( visualX, visualY );
1118   if( hitCharacter >= mLogicalModel->mText.Count() )
1119   {
1120     // Selection out of bounds.
1121     return;
1122   }
1123
1124   startIndex = hitCharacter;
1125   endIndex = hitCharacter;
1126
1127   if( !TextAbstraction::IsWhiteSpace( mLogicalModel->mText[hitCharacter] ) )
1128   {
1129     // Find the start and end of the text
1130     for( startIndex = hitCharacter; startIndex > 0; --startIndex )
1131     {
1132       Character charCode = mLogicalModel->mText[ startIndex-1 ];
1133       if( TextAbstraction::IsWhiteSpace( charCode ) )
1134       {
1135         break;
1136       }
1137     }
1138     const CharacterIndex pastTheEnd = mLogicalModel->mText.Count();
1139     for( endIndex = hitCharacter + 1u; endIndex < pastTheEnd; ++endIndex )
1140     {
1141       Character charCode = mLogicalModel->mText[ endIndex ];
1142       if( TextAbstraction::IsWhiteSpace( charCode ) )
1143       {
1144         break;
1145       }
1146     }
1147   }
1148 }
1149
1150 CharacterIndex Controller::Impl::GetClosestCursorIndex( float visualX,
1151                                                         float visualY )
1152 {
1153   if( NULL == mEventData )
1154   {
1155     // Nothing to do if there is no text input.
1156     return 0u;
1157   }
1158
1159   CharacterIndex logicalIndex = 0u;
1160
1161   const Length numberOfGlyphs = mVisualModel->mGlyphs.Count();
1162   const Length numberOfLines  = mVisualModel->mLines.Count();
1163   if( 0 == numberOfGlyphs ||
1164       0 == numberOfLines )
1165   {
1166     return logicalIndex;
1167   }
1168
1169   // Find which line is closest
1170   const LineIndex lineIndex = GetClosestLine( visualY );
1171   const LineRun& line = mVisualModel->mLines[lineIndex];
1172
1173   // Get the positions of the glyphs.
1174   const Vector<Vector2>& positions = mVisualModel->mGlyphPositions;
1175   const Vector2* const positionsBuffer = positions.Begin();
1176
1177   // Get the visual to logical conversion tables.
1178   const CharacterIndex* const visualToLogicalBuffer = ( 0u != mLogicalModel->mVisualToLogicalMap.Count() ) ? mLogicalModel->mVisualToLogicalMap.Begin() : NULL;
1179   const CharacterIndex* const visualToLogicalCursorBuffer = mLogicalModel->mVisualToLogicalCursorMap.Begin();
1180
1181   // Get the character to glyph conversion table.
1182   const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
1183
1184   // Get the glyphs per character table.
1185   const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
1186
1187   // If the vector is void, there is no right to left characters.
1188   const bool hasRightToLeftCharacters = NULL != visualToLogicalBuffer;
1189
1190   const CharacterIndex startCharacter = line.characterRun.characterIndex;
1191   const CharacterIndex endCharacter   = line.characterRun.characterIndex + line.characterRun.numberOfCharacters;
1192   DALI_ASSERT_DEBUG( endCharacter <= mLogicalModel->mText.Count() && "Invalid line info" );
1193
1194   // Whether there is a hit on a glyph.
1195   bool matched = false;
1196
1197   // Traverses glyphs in visual order. To do that use the visual to logical conversion table.
1198   CharacterIndex visualIndex = startCharacter;
1199   for( ; !matched && ( visualIndex < endCharacter ); ++visualIndex )
1200   {
1201     // The character in logical order.
1202     const CharacterIndex characterLogicalOrderIndex = hasRightToLeftCharacters ? *( visualToLogicalBuffer + visualIndex ) : visualIndex;
1203
1204     // The first glyph for that character in logical order.
1205     const GlyphIndex glyphLogicalOrderIndex = *( charactersToGlyphBuffer + characterLogicalOrderIndex );
1206
1207     // The number of glyphs for that character
1208     const Length numberOfGlyphs = *( glyphsPerCharacterBuffer + characterLogicalOrderIndex );
1209
1210     // Get the metrics for the group of glyphs.
1211     GlyphMetrics glyphMetrics;
1212     GetGlyphsMetrics( glyphLogicalOrderIndex,
1213                       numberOfGlyphs,
1214                       glyphMetrics,
1215                       mVisualModel,
1216                       mFontClient );
1217
1218     const Vector2& position = *( positionsBuffer + glyphLogicalOrderIndex );
1219
1220     // Find the mid-point of the area containing the glyph
1221     const float glyphCenter = -glyphMetrics.xBearing + position.x + 0.5f * glyphMetrics.advance;
1222
1223     if( visualX < glyphCenter )
1224     {
1225       matched = true;
1226       break;
1227     }
1228   }
1229
1230   // Return the logical position of the cursor in characters.
1231
1232   if( !matched )
1233   {
1234     visualIndex = endCharacter;
1235   }
1236
1237   logicalIndex = hasRightToLeftCharacters ? *( visualToLogicalCursorBuffer + visualIndex ) : visualIndex;
1238   DALI_LOG_INFO( gLogFilter, Debug::Verbose, "%p closest visualIndex %d logicalIndex %d\n", this, visualIndex, logicalIndex );
1239   return logicalIndex;
1240 }
1241
1242 void Controller::Impl::GetCursorPosition( CharacterIndex logical,
1243                                           CursorInfo& cursorInfo )
1244 {
1245   // TODO: Check for multiline with \n, etc...
1246
1247   // Check if the logical position is the first or the last one of the text.
1248   const bool isFirstPosition = 0u == logical;
1249   const bool isLastPosition = mLogicalModel->mText.Count() == logical;
1250
1251   if( isFirstPosition && isLastPosition )
1252   {
1253     // There is zero characters. Get the default font.
1254
1255     FontId defaultFontId = 0u;
1256     if( NULL == mFontDefaults )
1257     {
1258       defaultFontId = mFontClient.GetFontId( EMPTY_STRING,
1259                                              EMPTY_STRING );
1260     }
1261     else
1262     {
1263       defaultFontId = mFontDefaults->GetFontId( mFontClient );
1264     }
1265
1266     Text::FontMetrics fontMetrics;
1267     mFontClient.GetFontMetrics( defaultFontId, fontMetrics );
1268
1269     cursorInfo.lineHeight = fontMetrics.ascender - fontMetrics.descender;
1270     cursorInfo.primaryCursorHeight = cursorInfo.lineHeight;
1271
1272     cursorInfo.primaryPosition.x = 0.f;
1273     cursorInfo.primaryPosition.y = 0.f;
1274
1275     // Nothing else to do.
1276     return;
1277   }
1278
1279   // Get the previous logical index.
1280   const CharacterIndex previousLogical = isFirstPosition ? 0u : logical - 1u;
1281
1282   // Decrease the logical index if it's the last one.
1283   if( isLastPosition )
1284   {
1285     --logical;
1286   }
1287
1288   // Get the direction of the character and the previous one.
1289   const CharacterDirection* const modelCharacterDirectionsBuffer = ( 0u != mLogicalModel->mCharacterDirections.Count() ) ? mLogicalModel->mCharacterDirections.Begin() : NULL;
1290
1291   CharacterDirection isCurrentRightToLeft = false;
1292   CharacterDirection isPreviousRightToLeft = false;
1293   if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
1294   {
1295     isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + logical );
1296     isPreviousRightToLeft = *( modelCharacterDirectionsBuffer + previousLogical );
1297   }
1298
1299   // Get the line where the character is laid-out.
1300   const LineRun* modelLines = mVisualModel->mLines.Begin();
1301
1302   const LineIndex lineIndex = mVisualModel->GetLineOfCharacter( logical );
1303   const LineRun& line = *( modelLines + lineIndex );
1304
1305   // Get the paragraph's direction.
1306   const CharacterDirection isRightToLeftParagraph = line.direction;
1307
1308   // Check whether there is an alternative position:
1309
1310   cursorInfo.isSecondaryCursor = ( isCurrentRightToLeft != isPreviousRightToLeft ) ||
1311     ( isLastPosition && ( isRightToLeftParagraph != isCurrentRightToLeft ) );
1312
1313   // Set the line height.
1314   cursorInfo.lineHeight = line.ascender + -line.descender;
1315
1316   // Convert the cursor position into the glyph position.
1317   CharacterIndex characterIndex = logical;
1318   if( cursorInfo.isSecondaryCursor &&
1319       ( isRightToLeftParagraph != isCurrentRightToLeft ) )
1320   {
1321     characterIndex = previousLogical;
1322   }
1323
1324   const GlyphIndex currentGlyphIndex = *( mVisualModel->mCharactersToGlyph.Begin() + characterIndex );
1325   const Length numberOfGlyphs = *( mVisualModel->mGlyphsPerCharacter.Begin() + characterIndex );
1326   const Length numberOfCharacters = *( mVisualModel->mCharactersPerGlyph.Begin() +currentGlyphIndex );
1327
1328   // Get the metrics for the group of glyphs.
1329   GlyphMetrics glyphMetrics;
1330   GetGlyphsMetrics( currentGlyphIndex,
1331                     numberOfGlyphs,
1332                     glyphMetrics,
1333                     mVisualModel,
1334                     mFontClient );
1335
1336   float interGlyphAdvance = 0.f;
1337   if( !isLastPosition &&
1338       ( numberOfCharacters > 1u ) )
1339   {
1340     const CharacterIndex firstIndex = *( mVisualModel->mGlyphsToCharacters.Begin() + currentGlyphIndex );
1341     interGlyphAdvance = static_cast<float>( characterIndex - firstIndex ) * glyphMetrics.advance / static_cast<float>( numberOfCharacters );
1342   }
1343
1344   // Get the glyph position and x bearing.
1345   const Vector2& currentPosition = *( mVisualModel->mGlyphPositions.Begin() + currentGlyphIndex );
1346
1347   // Set the cursor's height.
1348   cursorInfo.primaryCursorHeight = glyphMetrics.fontHeight;
1349
1350   // Set the position.
1351   cursorInfo.primaryPosition.x = -glyphMetrics.xBearing + currentPosition.x + ( isCurrentRightToLeft ? glyphMetrics.advance : interGlyphAdvance );
1352   cursorInfo.primaryPosition.y = line.ascender - glyphMetrics.ascender;
1353
1354   if( isLastPosition )
1355   {
1356     // The position of the cursor after the last character needs special
1357     // care depending on its direction and the direction of the paragraph.
1358
1359     if( cursorInfo.isSecondaryCursor )
1360     {
1361       // Need to find the first character after the last character with the paragraph's direction.
1362       // i.e l0 l1 l2 r0 r1 should find r0.
1363
1364       // TODO: check for more than one line!
1365       characterIndex = isRightToLeftParagraph ? line.characterRun.characterIndex : line.characterRun.characterIndex + line.characterRun.numberOfCharacters - 1u;
1366       characterIndex = mLogicalModel->GetLogicalCharacterIndex( characterIndex );
1367
1368       const GlyphIndex glyphIndex = *( mVisualModel->mCharactersToGlyph.Begin() + characterIndex );
1369       const Length numberOfGlyphs = *( mVisualModel->mGlyphsPerCharacter.Begin() + characterIndex );
1370
1371       const Vector2& position = *( mVisualModel->mGlyphPositions.Begin() + glyphIndex );
1372
1373       // Get the metrics for the group of glyphs.
1374       GlyphMetrics glyphMetrics;
1375       GetGlyphsMetrics( glyphIndex,
1376                         numberOfGlyphs,
1377                         glyphMetrics,
1378                         mVisualModel,
1379                         mFontClient );
1380
1381       cursorInfo.primaryPosition.x = -glyphMetrics.xBearing + position.x + ( isRightToLeftParagraph ? 0.f : glyphMetrics.advance );
1382
1383       cursorInfo.primaryPosition.y = line.ascender - glyphMetrics.ascender;
1384     }
1385     else
1386     {
1387       if( !isCurrentRightToLeft )
1388       {
1389         cursorInfo.primaryPosition.x += glyphMetrics.advance;
1390       }
1391       else
1392       {
1393         cursorInfo.primaryPosition.x -= glyphMetrics.advance;
1394       }
1395     }
1396   }
1397
1398   // Set the alternative cursor position.
1399   if( cursorInfo.isSecondaryCursor )
1400   {
1401     // Convert the cursor position into the glyph position.
1402     const CharacterIndex previousCharacterIndex = ( ( isRightToLeftParagraph != isCurrentRightToLeft ) ? logical : previousLogical );
1403     const GlyphIndex previousGlyphIndex = *( mVisualModel->mCharactersToGlyph.Begin() + previousCharacterIndex );
1404     const Length numberOfGlyphs = *( mVisualModel->mGlyphsPerCharacter.Begin() + previousCharacterIndex );
1405
1406     // Get the glyph position.
1407     const Vector2& previousPosition = *( mVisualModel->mGlyphPositions.Begin() + previousGlyphIndex );
1408
1409     // Get the metrics for the group of glyphs.
1410     GlyphMetrics glyphMetrics;
1411     GetGlyphsMetrics( previousGlyphIndex,
1412                       numberOfGlyphs,
1413                       glyphMetrics,
1414                       mVisualModel,
1415                       mFontClient );
1416
1417     // Set the cursor position and height.
1418     cursorInfo.secondaryPosition.x = -glyphMetrics.xBearing + previousPosition.x + ( ( ( isLastPosition && !isCurrentRightToLeft ) ||
1419                                                                                        ( !isLastPosition && isCurrentRightToLeft )    ) ? glyphMetrics.advance : 0.f );
1420
1421     cursorInfo.secondaryCursorHeight = 0.5f * glyphMetrics.fontHeight;
1422
1423     cursorInfo.secondaryPosition.y = cursorInfo.lineHeight - cursorInfo.secondaryCursorHeight - line.descender - ( glyphMetrics.fontHeight - glyphMetrics.ascender );
1424
1425     // Update the primary cursor height as well.
1426     cursorInfo.primaryCursorHeight *= 0.5f;
1427   }
1428 }
1429
1430 CharacterIndex Controller::Impl::CalculateNewCursorIndex( CharacterIndex index ) const
1431 {
1432   if( NULL == mEventData )
1433   {
1434     // Nothing to do if there is no text input.
1435     return 0u;
1436   }
1437
1438   CharacterIndex cursorIndex = mEventData->mPrimaryCursorPosition;
1439
1440   const Script script = mLogicalModel->GetScript( index );
1441   const GlyphIndex* charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
1442   const Length* charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
1443
1444   Length numberOfCharacters = 0u;
1445   if( TextAbstraction::LATIN == script )
1446   {
1447     // Prevents to jump the whole Latin ligatures like fi, ff, ...
1448     numberOfCharacters = 1u;
1449   }
1450   else
1451   {
1452     GlyphIndex glyphIndex = *( charactersToGlyphBuffer + index );
1453     numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
1454
1455     while( 0u == numberOfCharacters )
1456     {
1457       numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
1458       ++glyphIndex;
1459     }
1460   }
1461
1462   if( index < mEventData->mPrimaryCursorPosition )
1463   {
1464     cursorIndex -= numberOfCharacters;
1465   }
1466   else
1467   {
1468     cursorIndex += numberOfCharacters;
1469   }
1470
1471   return cursorIndex;
1472 }
1473
1474 void Controller::Impl::UpdateCursorPosition()
1475 {
1476   DALI_LOG_INFO( gLogFilter, Debug::Verbose, "-->Controller::UpdateCursorPosition %p\n", this );
1477   if( NULL == mEventData )
1478   {
1479     // Nothing to do if there is no text input.
1480     DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition no event data\n" );
1481     return;
1482   }
1483
1484   if( IsShowingPlaceholderText() )
1485   {
1486     // Do not want to use the place-holder text to set the cursor position.
1487
1488     // Use the line's height of the font's family set to set the cursor's size.
1489     // If there is no font's family set, use the default font.
1490     // Use the current alignment to place the cursor at the beginning, center or end of the box.
1491
1492     float lineHeight = 0.f;
1493
1494     FontId defaultFontId = 0u;
1495     if( NULL == mFontDefaults )
1496     {
1497       defaultFontId = mFontClient.GetFontId( EMPTY_STRING,
1498                                              EMPTY_STRING );
1499     }
1500     else
1501     {
1502       defaultFontId = mFontDefaults->GetFontId( mFontClient );
1503     }
1504
1505     Text::FontMetrics fontMetrics;
1506     mFontClient.GetFontMetrics( defaultFontId, fontMetrics );
1507
1508     lineHeight = fontMetrics.ascender - fontMetrics.descender;
1509
1510
1511     Vector2 cursorPosition;
1512
1513     switch( mLayoutEngine.GetHorizontalAlignment() )
1514     {
1515       case LayoutEngine::HORIZONTAL_ALIGN_BEGIN:
1516       {
1517         cursorPosition.x = 1.f;
1518         break;
1519       }
1520       case LayoutEngine::HORIZONTAL_ALIGN_CENTER:
1521       {
1522         cursorPosition.x = floor( 0.5f * mControlSize.width );
1523         break;
1524       }
1525       case LayoutEngine::HORIZONTAL_ALIGN_END:
1526       {
1527         cursorPosition.x = mControlSize.width;
1528         break;
1529       }
1530     }
1531
1532     switch( mLayoutEngine.GetVerticalAlignment() )
1533     {
1534       case LayoutEngine::VERTICAL_ALIGN_TOP:
1535       {
1536         cursorPosition.y = 0.f;
1537         break;
1538       }
1539       case LayoutEngine::VERTICAL_ALIGN_CENTER:
1540       {
1541         cursorPosition.y = floorf( 0.5f * ( mControlSize.height - lineHeight ) );
1542         break;
1543       }
1544       case LayoutEngine::VERTICAL_ALIGN_BOTTOM:
1545       {
1546         cursorPosition.y = mControlSize.height - lineHeight;
1547         break;
1548       }
1549     }
1550
1551     mEventData->mDecorator->SetPosition( PRIMARY_CURSOR,
1552                                          cursorPosition.x,
1553                                          cursorPosition.y,
1554                                          lineHeight,
1555                                          lineHeight );
1556   }
1557   else
1558   {
1559     CursorInfo cursorInfo;
1560     GetCursorPosition( mEventData->mPrimaryCursorPosition,
1561                        cursorInfo );
1562
1563     const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
1564     const Vector2 cursorPosition = cursorInfo.primaryPosition + offset;
1565
1566     // Sets the cursor position.
1567     mEventData->mDecorator->SetPosition( PRIMARY_CURSOR,
1568                                          cursorPosition.x,
1569                                          cursorPosition.y,
1570                                          cursorInfo.primaryCursorHeight,
1571                                          cursorInfo.lineHeight );
1572     DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Primary cursor position: %f,%f\n", cursorPosition.x, cursorPosition.y );
1573
1574     // Sets the grab handle position.
1575     mEventData->mDecorator->SetPosition( GRAB_HANDLE,
1576                                          cursorPosition.x,
1577                                          cursorPosition.y,
1578                                          cursorInfo.lineHeight );
1579
1580     if( cursorInfo.isSecondaryCursor )
1581     {
1582       mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_BOTH );
1583       mEventData->mDecorator->SetPosition( SECONDARY_CURSOR,
1584                                            cursorInfo.secondaryPosition.x + offset.x,
1585                                            cursorInfo.secondaryPosition.y + offset.y,
1586                                            cursorInfo.secondaryCursorHeight,
1587                                            cursorInfo.lineHeight );
1588       DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Secondary cursor position: %f,%f\n", cursorInfo.secondaryPosition.x + offset.x, cursorInfo.secondaryPosition.y + offset.y );
1589     }
1590     else
1591     {
1592       mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1593     }
1594   }
1595   DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition\n" );
1596 }
1597
1598 void Controller::Impl::UpdateSelectionHandle( HandleType handleType )
1599 {
1600   if( ( LEFT_SELECTION_HANDLE != handleType ) &&
1601       ( RIGHT_SELECTION_HANDLE != handleType ) )
1602   {
1603     return;
1604   }
1605
1606   const bool leftSelectionHandle = LEFT_SELECTION_HANDLE == handleType;
1607   const CharacterIndex index = leftSelectionHandle ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition;
1608
1609   CursorInfo cursorInfo;
1610   GetCursorPosition( index,
1611                      cursorInfo );
1612
1613   const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
1614   const Vector2 cursorPosition = cursorInfo.primaryPosition + offset;
1615
1616   // Sets the grab handle position.
1617   mEventData->mDecorator->SetPosition( handleType,
1618                                        cursorPosition.x,
1619                                        cursorPosition.y,
1620                                        cursorInfo.lineHeight );
1621 }
1622
1623 void Controller::Impl::ClampHorizontalScroll( const Vector2& actualSize )
1624 {
1625   // Clamp between -space & 0 (and the text alignment).
1626   if( actualSize.width > mControlSize.width )
1627   {
1628     const float space = ( actualSize.width - mControlSize.width ) + mAlignmentOffset.x;
1629     mEventData->mScrollPosition.x = ( mEventData->mScrollPosition.x < -space ) ? -space : mEventData->mScrollPosition.x;
1630     mEventData->mScrollPosition.x = ( mEventData->mScrollPosition.x > -mAlignmentOffset.x ) ? -mAlignmentOffset.x : mEventData->mScrollPosition.x;
1631
1632     mEventData->mDecoratorUpdated = true;
1633   }
1634   else
1635   {
1636     mEventData->mScrollPosition.x = 0.f;
1637   }
1638 }
1639
1640 void Controller::Impl::ClampVerticalScroll( const Vector2& actualSize )
1641 {
1642   // Clamp between -space & 0 (and the text alignment).
1643   if( actualSize.height > mControlSize.height )
1644   {
1645     const float space = ( actualSize.height - mControlSize.height ) + mAlignmentOffset.y;
1646     mEventData->mScrollPosition.y = ( mEventData->mScrollPosition.y < -space ) ? -space : mEventData->mScrollPosition.y;
1647     mEventData->mScrollPosition.y = ( mEventData->mScrollPosition.y > -mAlignmentOffset.y ) ? -mAlignmentOffset.y : mEventData->mScrollPosition.y;
1648
1649     mEventData->mDecoratorUpdated = true;
1650   }
1651   else
1652   {
1653     mEventData->mScrollPosition.y = 0.f;
1654   }
1655 }
1656
1657 void Controller::Impl::ScrollToMakePositionVisible( const Vector2& position )
1658 {
1659   Vector2 offset;
1660   bool updateDecorator = false;
1661   if( position.x < 0.f )
1662   {
1663     offset.x = -position.x;
1664     mEventData->mScrollPosition.x += offset.x;
1665     updateDecorator = true;
1666   }
1667   else if( position.x > mControlSize.width )
1668   {
1669     offset.x = mControlSize.width - position.x;
1670     mEventData->mScrollPosition.x += offset.x;
1671     updateDecorator = true;
1672   }
1673
1674   if( updateDecorator && mEventData->mDecorator )
1675   {
1676     mEventData->mDecorator->UpdatePositions( offset );
1677   }
1678
1679   // TODO : calculate the vertical scroll.
1680 }
1681
1682 void Controller::Impl::ScrollTextToMatchCursor()
1683 {
1684   // Get the current cursor position in decorator coords.
1685   const Vector2& currentCursorPosition = mEventData->mDecorator->GetPosition( PRIMARY_CURSOR );
1686
1687   // Calculate the new cursor position.
1688   CursorInfo cursorInfo;
1689   GetCursorPosition( mEventData->mPrimaryCursorPosition,
1690                      cursorInfo );
1691
1692   // Calculate the offset to match the cursor position before the character was deleted.
1693   mEventData->mScrollPosition.x = currentCursorPosition.x - cursorInfo.primaryPosition.x - mAlignmentOffset.x;
1694
1695   ClampHorizontalScroll( mVisualModel->GetActualSize() );
1696   bool updateCursorPosition = true;
1697
1698   const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
1699   const Vector2 cursorPosition = cursorInfo.primaryPosition + offset;
1700
1701   if( updateCursorPosition )
1702   {
1703     // Sets the cursor position.
1704     mEventData->mDecorator->SetPosition( PRIMARY_CURSOR,
1705                                          cursorPosition.x,
1706                                          cursorPosition.y,
1707                                          cursorInfo.primaryCursorHeight,
1708                                          cursorInfo.lineHeight );
1709
1710     // Sets the grab handle position.
1711     mEventData->mDecorator->SetPosition( GRAB_HANDLE,
1712                                          cursorPosition.x,
1713                                          cursorPosition.y,
1714                                          cursorInfo.lineHeight );
1715
1716     if( cursorInfo.isSecondaryCursor )
1717     {
1718       mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_BOTH );
1719       mEventData->mDecorator->SetPosition( SECONDARY_CURSOR,
1720                                            cursorInfo.secondaryPosition.x + offset.x,
1721                                            cursorInfo.secondaryPosition.y + offset.y,
1722                                            cursorInfo.secondaryCursorHeight,
1723                                            cursorInfo.lineHeight );
1724       DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Secondary cursor position: %f,%f\n", cursorInfo.secondaryPosition.x + offset.x, cursorInfo.secondaryPosition.y + offset.y );
1725     }
1726     else
1727     {
1728       mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1729     }
1730   }
1731 }
1732
1733 void Controller::Impl::RequestRelayout()
1734 {
1735   mControlInterface.RequestTextRelayout();
1736 }
1737
1738 } // namespace Text
1739
1740 } // namespace Toolkit
1741
1742 } // namespace Dali