31c62c5c937b23322703020c41ba2a1854f6c6c3
[platform/core/uifw/dali-toolkit.git] / dali-toolkit / internal / text / decorator / text-decorator.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/decorator/text-decorator.h>
20
21 // EXTERNAL INCLUDES
22 #include <dali/integration-api/debug.h>
23 #include <dali/public-api/actors/actor.h>
24 #include <dali/public-api/adaptor-framework/timer.h>
25 #include <dali/public-api/actors/image-actor.h>
26 #include <dali/public-api/actors/layer.h>
27 #include <dali/devel-api/actors/mesh-actor.h>
28 #include <dali/public-api/animation/constraint.h>
29 #include <dali/public-api/common/constants.h>
30 #include <dali/public-api/common/stage.h>
31 #include <dali/public-api/events/tap-gesture.h>
32 #include <dali/public-api/events/tap-gesture-detector.h>
33 #include <dali/public-api/events/touch-event.h>
34 #include <dali/public-api/events/pan-gesture.h>
35 #include <dali/public-api/events/pan-gesture-detector.h>
36 #include <dali/devel-api/geometry/mesh.h>
37 #include <dali/devel-api/geometry/mesh-data.h>
38 #include <dali/public-api/images/resource-image.h>
39 #include <dali/public-api/math/rect.h>
40 #include <dali/public-api/math/vector2.h>
41 #include <dali/public-api/math/vector4.h>
42 #include <dali/public-api/object/property-notification.h>
43 #include <dali/public-api/signals/connection-tracker.h>
44
45 // INTERNAL INCLUDES
46 #include <dali-toolkit/public-api/controls/control.h>
47 #include <dali-toolkit/public-api/controls/control-impl.h>
48 #include <dali-toolkit/public-api/controls/buttons/push-button.h>
49 #include <dali-toolkit/public-api/controls/default-controls/solid-color-actor.h>
50 #include <dali-toolkit/public-api/controls/text-controls/text-label.h>
51 #include <dali-toolkit/devel-api/controls/text-controls/text-selection-popup.h>
52
53 #ifdef DEBUG_ENABLED
54 #define DECORATOR_DEBUG
55
56 #endif
57
58 namespace Dali
59 {
60 namespace Internal
61 {
62 namespace
63 {
64 #ifdef DECORATOR_DEBUG
65 Integration::Log::Filter* gLogFilter( Integration::Log::Filter::New(Debug::NoLogging, false, "LOG_TEXT_DECORATOR") );
66 #endif
67 }
68 }
69 }
70
71
72 // Local Data
73 namespace
74 {
75
76 const char* DEFAULT_GRAB_HANDLE_IMAGE_RELEASED( DALI_IMAGE_DIR "insertpoint-icon.png" );
77 const char* DEFAULT_GRAB_HANDLE_IMAGE_PRESSED( DALI_IMAGE_DIR "insertpoint-icon-pressed.png" );
78 const char* DEFAULT_SELECTION_HANDLE_ONE_RELEASED( DALI_IMAGE_DIR "text-input-selection-handle-left.png" );
79 const char* DEFAULT_SELECTION_HANDLE_ONE_PRESSED( DALI_IMAGE_DIR "text-input-selection-handle-left-press.png" );
80 const char* DEFAULT_SELECTION_HANDLE_TWO_RELEASED( DALI_IMAGE_DIR "text-input-selection-handle-right.png" );
81 const char* DEFAULT_SELECTION_HANDLE_TWO_PRESSED( DALI_IMAGE_DIR "text-input-selection-handle-right-press.png" );
82
83 const int DEFAULT_POPUP_OFFSET( -100.0f ); // Vertical offset of Popup from cursor or handles position.
84
85 const Dali::Vector3 DEFAULT_GRAB_HANDLE_RELATIVE_SIZE( 1.25f, 1.5f, 1.0f );
86 const Dali::Vector3 DEFAULT_SELECTION_HANDLE_RELATIVE_SIZE( 1.25f, 1.5f, 1.0f );
87
88 const Dali::Vector4 LIGHT_BLUE( (0xb2 / 255.0f), (0xeb / 255.0f), (0xf2 / 255.0f), 0.5f ); // The text highlight color.
89
90 const unsigned int CURSOR_BLINK_INTERVAL = 500u; // Cursor blink interval
91 const float TO_MILLISECONDS = 1000.f;
92 const float TO_SECONDS = 1.f / TO_MILLISECONDS;
93
94 const float DISPLAYED_HIGHLIGHT_Z_OFFSET( -0.05f );
95
96 const unsigned int SCROLL_TICK_INTERVAL = 50u;
97
98 const float SCROLL_THRESHOLD = 10.f;
99 const float SCROLL_SPEED = 300.f;
100 const float SCROLL_DISTANCE = SCROLL_SPEED * SCROLL_TICK_INTERVAL * TO_SECONDS;
101
102 /**
103  * structure to hold coordinates of each quad, which will make up the mesh.
104  */
105 struct QuadCoordinates
106 {
107   /**
108    * Default constructor
109    */
110   QuadCoordinates()
111   {
112   }
113
114   /**
115    * Constructor
116    * @param[in] x1 left co-ordinate
117    * @param[in] y1 top co-ordinate
118    * @param[in] x2 right co-ordinate
119    * @param[in] y2 bottom co-ordinate
120    */
121   QuadCoordinates(float x1, float y1, float x2, float y2)
122   : min(x1, y1),
123     max(x2, y2)
124   {
125   }
126
127   Dali::Vector2 min;                          ///< top-left (minimum) position of quad
128   Dali::Vector2 max;                          ///< bottom-right (maximum) position of quad
129 };
130
131 typedef std::vector<QuadCoordinates> QuadContainer;
132
133 /**
134  * @brief Takes a bounding rectangle in the local coordinates of an actor and returns the world coordinates Bounding Box.
135  * @param[in] boundingRectangle local bounding
136  * @param[out] Vector4 World coordinate bounding Box.
137  */
138 void LocalToWorldCoordinatesBoundingBox( const Dali::Rect<int>& boundingRectangle, Dali::Vector4& boundingBox )
139 {
140   // Convert to world coordinates and store as a Vector4 to be compatible with Property Notifications.
141   Dali::Vector2 stageSize = Dali::Stage::GetCurrent().GetSize();
142
143   const float originX = boundingRectangle.x - 0.5f * stageSize.width;
144   const float originY = boundingRectangle.y - 0.5f * stageSize.height;
145
146   boundingBox = Dali::Vector4( originX,
147                                originY,
148                                originX + boundingRectangle.width,
149                                originY + boundingRectangle.height );
150 }
151
152
153 } // end of namespace
154
155 namespace Dali
156 {
157
158 namespace Toolkit
159 {
160
161 namespace Text
162 {
163
164 struct Decorator::Impl : public ConnectionTracker
165 {
166   enum ScrollDirection
167   {
168     SCROLL_NONE,
169     SCROLL_RIGHT,
170     SCROLL_LEFT,
171     SCROLL_TOP,
172     SCROLL_BOTTOM
173   };
174
175   struct CursorImpl
176   {
177     CursorImpl()
178     : color( Dali::Color::BLACK ),
179       position(),
180       cursorHeight( 0.0f ),
181       lineHeight( 0.0f )
182     {
183     }
184
185     Vector4 color;
186     Vector2 position;
187     float cursorHeight;
188     float lineHeight;
189   };
190
191   struct HandleImpl
192   {
193     HandleImpl()
194     : position(),
195       lineHeight( 0.0f ),
196       grabDisplacementX( 0.f ),
197       grabDisplacementY( 0.f ),
198       active( false ),
199       visible( false ),
200       pressed( false ),
201       flipped( false )
202     {
203     }
204
205     ImageActor actor;
206     Actor grabArea;
207
208     Vector2 position;
209     float lineHeight; ///< Not the handle height
210     float grabDisplacementX;
211     float grabDisplacementY;
212     bool active  : 1;
213     bool visible : 1;
214     bool pressed : 1;
215     bool flipped : 1;
216   };
217
218   struct PopupImpl
219   {
220     PopupImpl()
221     : position(),
222       offset( DEFAULT_POPUP_OFFSET )
223     {
224     }
225
226     TextSelectionPopup actor;
227     Vector3 position;
228     int offset;
229   };
230
231   Impl( ControllerInterface& controller,
232         TextSelectionPopupCallbackInterface& callbackInterface )
233   : mController( controller ),
234     mEnabledPopupButtons( TextSelectionPopup::NONE ),
235     mTextSelectionPopupCallbackInterface( callbackInterface ),
236     mBoundingBox( Rect<int>() ),
237     mHighlightColor( LIGHT_BLUE ),
238     mHighlightPosition( Vector2::ZERO ),
239     mActiveCursor( ACTIVE_CURSOR_NONE ),
240     mCursorBlinkInterval( CURSOR_BLINK_INTERVAL ),
241     mCursorBlinkDuration( 0.0f ),
242     mHandleScrolling( HANDLE_TYPE_COUNT ),
243     mScrollDirection( SCROLL_NONE ),
244     mScrollThreshold( SCROLL_THRESHOLD ),
245     mScrollSpeed( SCROLL_SPEED ),
246     mScrollDistance( SCROLL_DISTANCE ),
247     mActiveCopyPastePopup( false ),
248     mCursorBlinkStatus( true ),
249     mPrimaryCursorVisible( false ),
250     mSecondaryCursorVisible( false ),
251     mSwapSelectionHandles( false ),
252     mNotifyEndOfScroll( false )
253   {
254   }
255
256   /**
257    * Relayout of the decorations owned by the decorator.
258    * @param[in] size The Size of the UI control the decorator is adding it's decorations to.
259    */
260   void Relayout( const Vector2& size )
261   {
262     // TODO - Remove this if nothing is active
263     CreateActiveLayer();
264
265     // Show or hide the cursors
266     CreateCursors();
267     if( mPrimaryCursor )
268     {
269       const CursorImpl& cursor = mCursor[PRIMARY_CURSOR];
270       mPrimaryCursorVisible = ( cursor.position.x <= size.width ) && ( cursor.position.x >= 0.f );
271       if( mPrimaryCursorVisible )
272       {
273         Vector2 position = cursor.position;
274
275         mPrimaryCursor.SetPosition( position.x,
276                                     position.y );
277         mPrimaryCursor.SetSize( Size( 1.0f, cursor.cursorHeight ) );
278       }
279       mPrimaryCursor.SetVisible( mPrimaryCursorVisible );
280     }
281     if( mSecondaryCursor )
282     {
283       const CursorImpl& cursor = mCursor[SECONDARY_CURSOR];
284       mSecondaryCursorVisible = ( cursor.position.x <= size.width ) && ( cursor.position.x >= 0.f );
285       if( mSecondaryCursorVisible )
286       {
287         mSecondaryCursor.SetPosition( cursor.position.x,
288                                       cursor.position.y );
289         mSecondaryCursor.SetSize( Size( 1.0f, cursor.cursorHeight ) );
290       }
291       mSecondaryCursor.SetVisible( mSecondaryCursorVisible );
292     }
293
294     // Show or hide the grab handle
295     HandleImpl& grabHandle = mHandle[GRAB_HANDLE];
296     if( grabHandle.active )
297     {
298       Vector2 position = grabHandle.position;
299
300       const bool isVisible = ( position.x <= size.width ) && ( position.x >= 0.f );
301
302       if( isVisible )
303       {
304         SetupTouchEvents();
305
306         CreateGrabHandle();
307
308         grabHandle.actor.SetPosition( position.x,
309                                       position.y + grabHandle.lineHeight );
310       }
311       grabHandle.actor.SetVisible( isVisible );
312     }
313     else if( grabHandle.actor )
314     {
315       UnparentAndReset( grabHandle.actor );
316     }
317
318     // Show or hide the selection handles/highlight
319     HandleImpl& primary = mHandle[ LEFT_SELECTION_HANDLE ];
320     HandleImpl& secondary = mHandle[ RIGHT_SELECTION_HANDLE ];
321     if( primary.active || secondary.active )
322     {
323       Vector2 primaryPosition = primary.position;
324       Vector2 secondaryPosition = secondary.position;
325
326       const bool isPrimaryVisible = ( primaryPosition.x <= size.width ) && ( primaryPosition.x >= 0.f );
327       const bool isSecondaryVisible = ( secondaryPosition.x <= size.width ) && ( secondaryPosition.x >= 0.f );
328
329       if( isPrimaryVisible || isSecondaryVisible )
330       {
331         SetupTouchEvents();
332
333         CreateSelectionHandles();
334
335         if( isPrimaryVisible )
336         {
337           primary.actor.SetPosition( primaryPosition.x,
338                                      primaryPosition.y + primary.lineHeight );
339         }
340
341         if( isSecondaryVisible )
342         {
343           secondary.actor.SetPosition( secondaryPosition.x,
344                                        secondaryPosition.y + secondary.lineHeight );
345         }
346       }
347       primary.actor.SetVisible( isPrimaryVisible );
348       secondary.actor.SetVisible( isSecondaryVisible );
349
350       CreateHighlight();
351       UpdateHighlight();
352     }
353     else
354     {
355       UnparentAndReset( primary.actor );
356       UnparentAndReset( secondary.actor );
357       UnparentAndReset( mHighlightMeshActor );
358     }
359
360     if ( mActiveCopyPastePopup )
361     {
362       // todo Swap UnparentAndReset for DeterminePositionPopup() if mCopyPastePopup.actor valid Once the issue with the labels disappearing is fixed.
363       UnparentAndReset( mCopyPastePopup.actor );
364       if ( !mCopyPastePopup.actor )
365       {
366         mCopyPastePopup.actor = TextSelectionPopup::New( mEnabledPopupButtons, &mTextSelectionPopupCallbackInterface );
367 #ifdef DECORATOR_DEBUG
368         mCopyPastePopup.actor.SetName("mCopyPastePopup");
369 #endif
370         mCopyPastePopup.actor.SetAnchorPoint( AnchorPoint::CENTER );
371         mCopyPastePopup.actor.OnRelayoutSignal().Connect( this,  &Decorator::Impl::PopupRelayoutComplete  ); // Position popup after size negotiation
372         mActiveLayer.Add ( mCopyPastePopup.actor );
373       }
374     }
375     else
376     {
377      if ( mCopyPastePopup.actor )
378      {
379        UnparentAndReset( mCopyPastePopup.actor );
380      }
381     }
382   }
383
384   void UpdatePositions( const Vector2& scrollOffset )
385   {
386     mCursor[PRIMARY_CURSOR].position += scrollOffset;
387     mCursor[SECONDARY_CURSOR].position += scrollOffset;
388     mHandle[ GRAB_HANDLE ].position += scrollOffset;
389     mHandle[ LEFT_SELECTION_HANDLE ].position += scrollOffset;
390     mHandle[ RIGHT_SELECTION_HANDLE ].position += scrollOffset;
391     mHighlightPosition += scrollOffset;
392   }
393
394   void DeterminePositionPopup()
395   {
396     if ( !mActiveCopyPastePopup )
397     {
398       return;
399     }
400
401     if ( mHandle[LEFT_SELECTION_HANDLE].active || mHandle[RIGHT_SELECTION_HANDLE].active )
402     {
403       float minHandleXPosition = std::min (  mHandle[LEFT_SELECTION_HANDLE].position.x, mHandle[RIGHT_SELECTION_HANDLE].position.x );
404       float maxHandleXPosition = std::max (  mHandle[LEFT_SELECTION_HANDLE].position.x, mHandle[RIGHT_SELECTION_HANDLE].position.x );
405
406       float minHandleYPosition = std::min (  mHandle[LEFT_SELECTION_HANDLE].position.y, mHandle[RIGHT_SELECTION_HANDLE].position.y );
407
408       mCopyPastePopup.position.x = minHandleXPosition + ( ( maxHandleXPosition - minHandleXPosition ) *0.5f );
409       mCopyPastePopup.position.y = minHandleYPosition + mCopyPastePopup.offset;
410     }
411     else
412     {
413       mCopyPastePopup.position = Vector3( mCursor[PRIMARY_CURSOR].position.x, mCursor[PRIMARY_CURSOR].position.y -100.0f , 0.0f ); //todo 100 to be an offset Property
414     }
415
416     Vector3 popupSize = Vector3( mCopyPastePopup.actor.GetRelayoutSize( Dimension::WIDTH ), mCopyPastePopup.actor.GetRelayoutSize( Dimension::HEIGHT ), 0.0f );
417
418     GetConstrainedPopupPosition( mCopyPastePopup.position, popupSize, AnchorPoint::CENTER, mActiveLayer, mBoundingBox );
419
420     SetUpPopupPositionNotifications();
421
422     mCopyPastePopup.actor.SetPosition( mCopyPastePopup.position );
423   }
424
425   void PopupRelayoutComplete( Actor actor )
426   {
427     // Size negotiation for CopyPastePopup complete so can get the size and constrain position within bounding box.
428     mCopyPastePopup.actor.OnRelayoutSignal().Disconnect( this, &Decorator::Impl::PopupRelayoutComplete  );
429
430     DeterminePositionPopup();
431   }
432
433   void CreateCursor( ImageActor& cursor, const Vector4& color )
434   {
435     cursor = CreateSolidColorActor( color );
436     cursor.SetParentOrigin( ParentOrigin::TOP_LEFT ); // Need to set the default parent origin as CreateSolidColorActor() sets a different one.
437     cursor.SetAnchorPoint( AnchorPoint::TOP_CENTER );
438   }
439
440   // Add or Remove cursor(s) from parent
441   void CreateCursors()
442   {
443     if( mActiveCursor == ACTIVE_CURSOR_NONE )
444     {
445       UnparentAndReset( mPrimaryCursor );
446       UnparentAndReset( mSecondaryCursor );
447     }
448     else
449     {
450       /* Create Primary and or Secondary Cursor(s) if active and add to parent */
451       if ( mActiveCursor == ACTIVE_CURSOR_PRIMARY ||
452            mActiveCursor == ACTIVE_CURSOR_BOTH )
453       {
454         if ( !mPrimaryCursor )
455         {
456           CreateCursor( mPrimaryCursor, mCursor[PRIMARY_CURSOR].color );
457 #ifdef DECORATOR_DEBUG
458           mPrimaryCursor.SetName( "PrimaryCursorActor" );
459 #endif
460           mActiveLayer.Add( mPrimaryCursor );
461         }
462       }
463
464       if ( mActiveCursor == ACTIVE_CURSOR_BOTH )
465       {
466         if ( !mSecondaryCursor )
467         {
468           CreateCursor( mSecondaryCursor, mCursor[SECONDARY_CURSOR].color );
469 #ifdef DECORATOR_DEBUG
470           mSecondaryCursor.SetName( "SecondaryCursorActor" );
471 #endif
472           mActiveLayer.Add( mSecondaryCursor );
473         }
474       }
475       else
476       {
477         UnparentAndReset( mSecondaryCursor );
478       }
479     }
480   }
481
482   bool OnCursorBlinkTimerTick()
483   {
484     // Cursor blinking
485     if ( mPrimaryCursor )
486     {
487       mPrimaryCursor.SetVisible( mPrimaryCursorVisible && mCursorBlinkStatus );
488     }
489     if ( mSecondaryCursor )
490     {
491       mSecondaryCursor.SetVisible( mSecondaryCursorVisible && mCursorBlinkStatus );
492     }
493
494     mCursorBlinkStatus = !mCursorBlinkStatus;
495
496     return true;
497   }
498
499   void SetupTouchEvents()
500   {
501     if ( !mTapDetector )
502     {
503       mTapDetector = TapGestureDetector::New();
504       mTapDetector.DetectedSignal().Connect( this, &Decorator::Impl::OnTap );
505     }
506
507     if ( !mPanGestureDetector )
508     {
509       mPanGestureDetector = PanGestureDetector::New();
510       mPanGestureDetector.DetectedSignal().Connect( this, &Decorator::Impl::OnPan );
511     }
512   }
513
514   void CreateActiveLayer()
515   {
516     if( !mActiveLayer )
517     {
518       mActiveLayer = Layer::New();
519 #ifdef DECORATOR_DEBUG
520       mActiveLayer.SetName ( "ActiveLayerActor" );
521 #endif
522
523       mActiveLayer.SetParentOrigin( ParentOrigin::CENTER );
524       mActiveLayer.SetResizePolicy( ResizePolicy::FILL_TO_PARENT, Dimension::ALL_DIMENSIONS );
525       mActiveLayer.SetPositionInheritanceMode( USE_PARENT_POSITION );
526
527       // Add the active layer telling the controller it doesn't need clipping.
528       mController.AddDecoration( mActiveLayer, false );
529     }
530
531     mActiveLayer.RaiseToTop();
532   }
533
534   void CreateGrabHandle()
535   {
536     HandleImpl& grabHandle = mHandle[GRAB_HANDLE];
537     if( !grabHandle.actor )
538     {
539       if( !mHandleImages[GRAB_HANDLE][HANDLE_IMAGE_RELEASED] )
540       {
541         mHandleImages[GRAB_HANDLE][HANDLE_IMAGE_RELEASED] = ResourceImage::New( DEFAULT_GRAB_HANDLE_IMAGE_RELEASED );
542       }
543       if( !mHandleImages[GRAB_HANDLE][HANDLE_IMAGE_PRESSED] )
544       {
545         mHandleImages[GRAB_HANDLE][HANDLE_IMAGE_PRESSED] = ResourceImage::New( DEFAULT_GRAB_HANDLE_IMAGE_PRESSED );
546       }
547
548       grabHandle.actor = ImageActor::New( mHandleImages[GRAB_HANDLE][HANDLE_IMAGE_RELEASED] );
549       grabHandle.actor.SetAnchorPoint( AnchorPoint::TOP_CENTER );
550       grabHandle.actor.SetDrawMode( DrawMode::OVERLAY );
551       // Area that Grab handle responds to, larger than actual handle so easier to move
552 #ifdef DECORATOR_DEBUG
553       grabHandle.actor.SetName( "GrabHandleActor" );
554       if ( Dali::Internal::gLogFilter->IsEnabledFor( Debug::Verbose ) )
555       {
556         grabHandle.grabArea = Toolkit::CreateSolidColorActor( Vector4(0.0f, 0.0f, 0.0f, 0.0f), true, Color::RED, 1 );
557         grabHandle.grabArea.SetName( "GrabArea" );
558       }
559       else
560       {
561         grabHandle.grabArea = Actor::New();
562         grabHandle.grabArea.SetName( "GrabArea" );
563       }
564 #else
565       grabHandle.grabArea = Actor::New();
566 #endif
567
568       grabHandle.grabArea.SetParentOrigin( ParentOrigin::TOP_CENTER );
569       grabHandle.grabArea.SetAnchorPoint( AnchorPoint::TOP_CENTER );
570       grabHandle.grabArea.SetResizePolicy( ResizePolicy::SIZE_RELATIVE_TO_PARENT, Dimension::ALL_DIMENSIONS );
571       grabHandle.grabArea.SetSizeModeFactor( DEFAULT_GRAB_HANDLE_RELATIVE_SIZE );
572       grabHandle.actor.Add( grabHandle.grabArea );
573
574       grabHandle.grabArea.TouchedSignal().Connect( this, &Decorator::Impl::OnGrabHandleTouched );
575       mTapDetector.Attach( grabHandle.grabArea );
576       mPanGestureDetector.Attach( grabHandle.grabArea );
577
578       mActiveLayer.Add( grabHandle.actor );
579     }
580   }
581
582   void CreateSelectionHandles()
583   {
584     HandleImpl& primary = mHandle[ LEFT_SELECTION_HANDLE ];
585     if( !primary.actor )
586     {
587       if( !mHandleImages[LEFT_SELECTION_HANDLE][HANDLE_IMAGE_RELEASED] )
588       {
589         mHandleImages[LEFT_SELECTION_HANDLE][HANDLE_IMAGE_RELEASED] = ResourceImage::New( DEFAULT_SELECTION_HANDLE_ONE_RELEASED );
590       }
591       if( !mHandleImages[LEFT_SELECTION_HANDLE][HANDLE_IMAGE_PRESSED] )
592       {
593         mHandleImages[LEFT_SELECTION_HANDLE][HANDLE_IMAGE_PRESSED] = ResourceImage::New( DEFAULT_SELECTION_HANDLE_ONE_PRESSED );
594       }
595
596       primary.actor = ImageActor::New( mHandleImages[LEFT_SELECTION_HANDLE][HANDLE_IMAGE_RELEASED] );
597 #ifdef DECORATOR_DEBUG
598       primary.actor.SetName("SelectionHandleOne");
599 #endif
600       primary.actor.SetAnchorPoint( AnchorPoint::TOP_RIGHT ); // Change to BOTTOM_RIGHT if Look'n'Feel requires handle above text.
601       primary.actor.SetDrawMode( DrawMode::OVERLAY ); // ensure grab handle above text
602       primary.flipped = false;
603
604       primary.grabArea = Actor::New(); // Area that Grab handle responds to, larger than actual handle so easier to move
605 #ifdef DECORATOR_DEBUG
606       primary.grabArea.SetName("SelectionHandleOneGrabArea");
607 #endif
608       primary.grabArea.SetResizePolicy( ResizePolicy::SIZE_RELATIVE_TO_PARENT, Dimension::ALL_DIMENSIONS );
609       primary.grabArea.SetParentOrigin( ParentOrigin::TOP_CENTER );
610       primary.grabArea.SetAnchorPoint( AnchorPoint::TOP_CENTER );
611       primary.grabArea.SetSizeModeFactor( DEFAULT_SELECTION_HANDLE_RELATIVE_SIZE );
612
613       mTapDetector.Attach( primary.grabArea );
614       mPanGestureDetector.Attach( primary.grabArea );
615       primary.grabArea.TouchedSignal().Connect( this, &Decorator::Impl::OnHandleOneTouched );
616
617       primary.actor.Add( primary.grabArea );
618       mActiveLayer.Add( primary.actor );
619     }
620
621     HandleImpl& secondary = mHandle[ RIGHT_SELECTION_HANDLE ];
622     if( !secondary.actor )
623     {
624       if( !mHandleImages[RIGHT_SELECTION_HANDLE][HANDLE_IMAGE_RELEASED] )
625       {
626         mHandleImages[RIGHT_SELECTION_HANDLE][HANDLE_IMAGE_RELEASED] = ResourceImage::New( DEFAULT_SELECTION_HANDLE_TWO_RELEASED );
627       }
628       if( !mHandleImages[RIGHT_SELECTION_HANDLE][HANDLE_IMAGE_PRESSED] )
629       {
630         mHandleImages[RIGHT_SELECTION_HANDLE][HANDLE_IMAGE_PRESSED] = ResourceImage::New( DEFAULT_SELECTION_HANDLE_TWO_PRESSED );
631       }
632
633       secondary.actor = ImageActor::New( mHandleImages[RIGHT_SELECTION_HANDLE][HANDLE_IMAGE_RELEASED] );
634 #ifdef DECORATOR_DEBUG
635       secondary.actor.SetName("SelectionHandleTwo");
636 #endif
637       secondary.actor.SetAnchorPoint( AnchorPoint::TOP_LEFT ); // Change to BOTTOM_LEFT if Look'n'Feel requires handle above text.
638       secondary.actor.SetDrawMode( DrawMode::OVERLAY ); // ensure grab handle above text
639       secondary.flipped = false;
640
641       secondary.grabArea = Actor::New(); // Area that Grab handle responds to, larger than actual handle so easier to move
642 #ifdef DECORATOR_DEBUG
643       secondary.grabArea.SetName("SelectionHandleTwoGrabArea");
644 #endif
645       secondary.grabArea.SetResizePolicy( ResizePolicy::SIZE_RELATIVE_TO_PARENT, Dimension::ALL_DIMENSIONS );
646       secondary.grabArea.SetParentOrigin( ParentOrigin::TOP_CENTER );
647       secondary.grabArea.SetAnchorPoint( AnchorPoint::TOP_CENTER );
648       secondary.grabArea.SetSizeModeFactor( DEFAULT_SELECTION_HANDLE_RELATIVE_SIZE );
649
650       mTapDetector.Attach( secondary.grabArea );
651       mPanGestureDetector.Attach( secondary.grabArea );
652       secondary.grabArea.TouchedSignal().Connect( this, &Decorator::Impl::OnHandleTwoTouched );
653
654       secondary.actor.Add( secondary.grabArea );
655       mActiveLayer.Add( secondary.actor );
656     }
657   }
658
659   void CreateHighlight()
660   {
661     if ( !mHighlightMeshActor )
662     {
663       mHighlightMaterial = Material::New( "HighlightMaterial" );
664       mHighlightMaterial.SetDiffuseColor( mHighlightColor );
665
666       mHighlightMeshData.SetMaterial( mHighlightMaterial );
667       mHighlightMeshData.SetHasNormals( true );
668
669       mHighlightMesh = Mesh::New( mHighlightMeshData );
670
671       mHighlightMeshActor = MeshActor::New( mHighlightMesh );
672 #ifdef DECORATOR_DEBUG
673       mHighlightMeshActor.SetName( "HighlightMeshActor" );
674 #endif
675       mHighlightMeshActor.SetAnchorPoint( AnchorPoint::TOP_LEFT );
676
677       // Add the highlight box telling the controller it needs clipping.
678       mController.AddDecoration( mHighlightMeshActor, true );
679     }
680
681     mHighlightMeshActor.SetPosition( mHighlightPosition.x, mHighlightPosition.y, DISPLAYED_HIGHLIGHT_Z_OFFSET );
682   }
683
684   void UpdateHighlight()
685   {
686     //  Construct a Mesh with a texture to be used as the highlight 'box' for selected text
687     //
688     //  Example scenarios where mesh is made from 3, 1, 2, 2 ,3 or 3 quads.
689     //
690     //   [ TOP   ]  [ TOP ]      [TOP ]  [ TOP    ]      [ TOP  ]      [ TOP  ]
691     //  [ MIDDLE ]             [BOTTOM]  [BOTTOM]      [ MIDDLE ]   [ MIDDLE  ]
692     //  [ BOTTOM]                                      [ MIDDLE ]   [ MIDDLE  ]
693     //                                                 [BOTTOM]     [ MIDDLE  ]
694     //                                                              [BOTTOM]
695     //
696     //  Each quad is created as 2 triangles.
697     //  Middle is just 1 quad regardless of its size.
698     //
699     //  (0,0)         (0,0)
700     //     0*    *2     0*       *2
701     //     TOP          TOP
702     //     3*    *1     3*       *1
703     //  4*       *1     4*     *6
704     //     MIDDLE         BOTTOM
705     //  6*       *5     7*     *5
706     //  6*    *8
707     //   BOTTOM
708     //  9*    *7
709     //
710
711     if ( mHighlightMesh && mHighlightMaterial && !mHighlightQuadList.empty() )
712     {
713       MeshData::VertexContainer vertices;
714       Dali::MeshData::FaceIndices faceIndices;
715
716       std::vector<QuadCoordinates>::iterator iter = mHighlightQuadList.begin();
717       std::vector<QuadCoordinates>::iterator endIter = mHighlightQuadList.end();
718
719       // vertex position defaults to (0 0 0)
720       MeshData::Vertex vertex;
721       // set normal for all vertices as (0 0 1) pointing outward from TextInput Actor.
722       vertex.nZ = 1.0f;
723
724       for(std::size_t v = 0; iter != endIter; ++iter,v+=4 )
725       {
726         // Add each quad geometry (a sub-selection) to the mesh data.
727
728         // 0-----1
729         // |\    |
730         // | \ A |
731         // |  \  |
732         // | B \ |
733         // |    \|
734         // 2-----3
735
736         QuadCoordinates& quad = *iter;
737         // top-left (v+0)
738         vertex.x = quad.min.x;
739         vertex.y = quad.min.y;
740         vertices.push_back( vertex );
741
742         // top-right (v+1)
743         vertex.x = quad.max.x;
744         vertex.y = quad.min.y;
745         vertices.push_back( vertex );
746
747         // bottom-left (v+2)
748         vertex.x = quad.min.x;
749         vertex.y = quad.max.y;
750         vertices.push_back( vertex );
751
752         // bottom-right (v+3)
753         vertex.x = quad.max.x;
754         vertex.y = quad.max.y;
755         vertices.push_back( vertex );
756
757         // triangle A (3, 1, 0)
758         faceIndices.push_back( v + 3 );
759         faceIndices.push_back( v + 1 );
760         faceIndices.push_back( v );
761
762         // triangle B (0, 2, 3)
763         faceIndices.push_back( v );
764         faceIndices.push_back( v + 2 );
765         faceIndices.push_back( v + 3 );
766
767         mHighlightMeshData.SetFaceIndices( faceIndices );
768       }
769
770       BoneContainer bones(0); // passed empty as bones not required
771       mHighlightMeshData.SetData( vertices, faceIndices, bones, mHighlightMaterial );
772       mHighlightMesh.UpdateMeshData( mHighlightMeshData );
773     }
774   }
775
776   void OnTap( Actor actor, const TapGesture& tap )
777   {
778     if( actor == mHandle[GRAB_HANDLE].actor )
779     {
780       // TODO
781     }
782   }
783
784   void DoPan( HandleImpl& handle, HandleType type, const PanGesture& gesture )
785   {
786     if( Gesture::Started == gesture.state )
787     {
788       handle.grabDisplacementX = handle.grabDisplacementY = 0;
789     }
790
791     handle.grabDisplacementX += gesture.displacement.x;
792     handle.grabDisplacementY += gesture.displacement.y;
793
794     const float x = handle.position.x + handle.grabDisplacementX;
795     const float y = handle.position.y + handle.lineHeight*0.5f + handle.grabDisplacementY;
796
797     if( Gesture::Started    == gesture.state ||
798         Gesture::Continuing == gesture.state )
799     {
800       Vector2 targetSize;
801       mController.GetTargetSize( targetSize );
802
803       if( x < mScrollThreshold )
804       {
805         mScrollDirection = SCROLL_RIGHT;
806         mHandleScrolling = type;
807         StartScrollTimer();
808       }
809       else if( x > targetSize.width - mScrollThreshold )
810       {
811         mScrollDirection = SCROLL_LEFT;
812         mHandleScrolling = type;
813         StartScrollTimer();
814       }
815       else
816       {
817         mHandleScrolling = HANDLE_TYPE_COUNT;
818         StopScrollTimer();
819         mController.DecorationEvent( type, HANDLE_PRESSED, x, y );
820       }
821     }
822     else if( Gesture::Finished  == gesture.state ||
823              Gesture::Cancelled == gesture.state )
824     {
825       if( mScrollTimer &&
826           ( mScrollTimer.IsRunning() || mNotifyEndOfScroll ) )
827       {
828         mNotifyEndOfScroll = false;
829         mHandleScrolling = HANDLE_TYPE_COUNT;
830         StopScrollTimer();
831         mController.DecorationEvent( type, HANDLE_STOP_SCROLLING, x, y );
832       }
833       else
834       {
835         mController.DecorationEvent( type, HANDLE_RELEASED, x, y );
836       }
837
838       if( GRAB_HANDLE == type )
839       {
840         handle.actor.SetImage( mHandleImages[type][HANDLE_IMAGE_RELEASED] );
841       }
842       else
843       {
844         HandleType selectionHandleType = type;
845
846         if( mSwapSelectionHandles != handle.flipped )
847         {
848           selectionHandleType = ( LEFT_SELECTION_HANDLE == type ) ? RIGHT_SELECTION_HANDLE : LEFT_SELECTION_HANDLE;
849         }
850
851         handle.actor.SetImage( mHandleImages[selectionHandleType][HANDLE_IMAGE_RELEASED] );
852       }
853       handle.pressed = false;
854     }
855   }
856
857   void OnPan( Actor actor, const PanGesture& gesture )
858   {
859     HandleImpl& grabHandle = mHandle[GRAB_HANDLE];
860     HandleImpl& primarySelectionHandle = mHandle[LEFT_SELECTION_HANDLE];
861     HandleImpl& secondarySelectionHandle = mHandle[RIGHT_SELECTION_HANDLE];
862
863     if( actor == grabHandle.grabArea )
864     {
865       DoPan( grabHandle, GRAB_HANDLE, gesture );
866     }
867     else if( actor == primarySelectionHandle.grabArea )
868     {
869       DoPan( primarySelectionHandle, LEFT_SELECTION_HANDLE, gesture );
870     }
871     else if( actor == secondarySelectionHandle.grabArea )
872     {
873       DoPan( secondarySelectionHandle, RIGHT_SELECTION_HANDLE, gesture );
874     }
875   }
876
877   bool OnGrabHandleTouched( Actor actor, const TouchEvent& event )
878   {
879     // Switch between pressed/release grab-handle images
880     if( event.GetPointCount() > 0 &&
881         mHandle[GRAB_HANDLE].actor )
882     {
883       const TouchPoint& point = event.GetPoint(0);
884
885       if( TouchPoint::Down == point.state )
886       {
887         mHandle[GRAB_HANDLE].pressed = true;
888         Image imagePressed = mHandleImages[GRAB_HANDLE][HANDLE_IMAGE_PRESSED];
889         if( imagePressed )
890         {
891           mHandle[GRAB_HANDLE].actor.SetImage( imagePressed );
892         }
893       }
894       else if( TouchPoint::Up == point.state )
895       {
896         mHandle[GRAB_HANDLE].pressed = false;
897         Image imageReleased = mHandleImages[GRAB_HANDLE][HANDLE_IMAGE_RELEASED];
898         if( imageReleased )
899         {
900           mHandle[GRAB_HANDLE].actor.SetImage( imageReleased );
901         }
902       }
903     }
904
905     // Consume to avoid pop-ups accidentally closing, when handle is outside of pop-up area
906     return true;
907   }
908
909   bool OnHandleOneTouched( Actor actor, const TouchEvent& event )
910   {
911     // Switch between pressed/release selection handle images
912     if( event.GetPointCount() > 0 &&
913         mHandle[LEFT_SELECTION_HANDLE].actor )
914     {
915       const TouchPoint& point = event.GetPoint(0);
916
917       const bool flip = mSwapSelectionHandles != mHandle[LEFT_SELECTION_HANDLE].flipped;
918       if( TouchPoint::Down == point.state )
919       {
920         mHandle[LEFT_SELECTION_HANDLE].pressed = true;
921         Image imagePressed = mHandleImages[flip ? RIGHT_SELECTION_HANDLE : LEFT_SELECTION_HANDLE][HANDLE_IMAGE_PRESSED];
922         if( imagePressed )
923         {
924           mHandle[LEFT_SELECTION_HANDLE].actor.SetImage( imagePressed );
925         }
926       }
927       else if( TouchPoint::Up == point.state )
928       {
929         mHandle[LEFT_SELECTION_HANDLE].pressed = false;
930         Image imageReleased = mHandleImages[flip ? RIGHT_SELECTION_HANDLE : LEFT_SELECTION_HANDLE][HANDLE_IMAGE_RELEASED];
931         if( imageReleased )
932         {
933           mHandle[LEFT_SELECTION_HANDLE].actor.SetImage( imageReleased );
934         }
935       }
936     }
937
938     // Consume to avoid pop-ups accidentally closing, when handle is outside of pop-up area
939     return true;
940   }
941
942   bool OnHandleTwoTouched( Actor actor, const TouchEvent& event )
943   {
944     // Switch between pressed/release selection handle images
945     if( event.GetPointCount() > 0 &&
946         mHandle[RIGHT_SELECTION_HANDLE].actor )
947     {
948       const TouchPoint& point = event.GetPoint(0);
949
950       const bool flip = mSwapSelectionHandles != mHandle[RIGHT_SELECTION_HANDLE].flipped;
951       if( TouchPoint::Down == point.state )
952       {
953         Image imagePressed = mHandleImages[flip ? LEFT_SELECTION_HANDLE : RIGHT_SELECTION_HANDLE][HANDLE_IMAGE_PRESSED];
954         mHandle[RIGHT_SELECTION_HANDLE].pressed = true;
955         if( imagePressed )
956         {
957           mHandle[RIGHT_SELECTION_HANDLE].actor.SetImage( imagePressed );
958         }
959       }
960       else if( TouchPoint::Up == point.state )
961       {
962         Image imageReleased = mHandleImages[flip ? LEFT_SELECTION_HANDLE : RIGHT_SELECTION_HANDLE][HANDLE_IMAGE_RELEASED];
963         mHandle[RIGHT_SELECTION_HANDLE].pressed = false;
964         if( imageReleased )
965         {
966           mHandle[RIGHT_SELECTION_HANDLE].actor.SetImage( imageReleased );
967         }
968       }
969     }
970
971     // Consume to avoid pop-ups accidentally closing, when handle is outside of pop-up area
972     return true;
973   }
974
975   // Popup
976
977   float AlternatePopUpPositionRelativeToCursor()
978   {
979     float alternativePosition=0.0f;;
980
981     if ( mPrimaryCursor ) // Secondary cursor not used for paste
982     {
983       Cursor cursor = PRIMARY_CURSOR;
984       alternativePosition = mCursor[cursor].position.y;
985     }
986
987     const float popupHeight = 120.0f; // todo Set as a MaxSize Property in Control or retrieve from CopyPastePopup class.
988
989     if( mHandle[GRAB_HANDLE].active )
990     {
991       // If grab handle enabled then position pop-up below the grab handle.
992       const Vector2 grabHandleSize( 59.0f, 56.0f ); // todo
993       const float BOTTOM_HANDLE_BOTTOM_OFFSET = 1.5; //todo Should be a property
994       alternativePosition +=  grabHandleSize.height  + popupHeight + BOTTOM_HANDLE_BOTTOM_OFFSET ;
995     }
996     else
997     {
998       alternativePosition += popupHeight;
999     }
1000
1001     return alternativePosition;
1002   }
1003
1004   void PopUpLeavesVerticalBoundary( PropertyNotification& source )
1005   {
1006     float alternativeYPosition=0.0f;
1007     // todo use AlternatePopUpPositionRelativeToSelectionHandles() if text is highlighted
1008     // if can't be positioned above, then position below row.
1009     alternativeYPosition = AlternatePopUpPositionRelativeToCursor();
1010
1011     mCopyPastePopup.actor.SetY( alternativeYPosition );
1012   }
1013
1014
1015   void SetUpPopupPositionNotifications( )
1016   {
1017     // Note Property notifications ignore any set anchor point so conditions must allow for this.  Default is Top Left.
1018
1019     // Exceeding vertical boundary
1020
1021     Vector4 worldCoordinatesBoundingBox;
1022     LocalToWorldCoordinatesBoundingBox( mBoundingBox, worldCoordinatesBoundingBox );
1023
1024     float popupHeight = mCopyPastePopup.actor.GetRelayoutSize( Dimension::HEIGHT);
1025
1026     PropertyNotification verticalExceedNotification = mCopyPastePopup.actor.AddPropertyNotification( Actor::Property::WORLD_POSITION_Y,
1027                                                       OutsideCondition( worldCoordinatesBoundingBox.y + popupHeight * 0.5f,
1028                                                                         worldCoordinatesBoundingBox.w - popupHeight * 0.5f ) );
1029
1030     verticalExceedNotification.NotifySignal().Connect( this, &Decorator::Impl::PopUpLeavesVerticalBoundary );
1031   }
1032
1033   void GetConstrainedPopupPosition( Vector3& requiredPopupPosition, Vector3& popupSize, Vector3 anchorPoint, Actor& parent, Rect<int>& boundingBox )
1034   {
1035     DALI_ASSERT_DEBUG ( "Popup parent not on stage" && parent.OnStage() )
1036
1037     // Parent must already by added to Stage for these Get calls to work
1038     Vector3 parentAnchorPoint = parent.GetCurrentAnchorPoint();
1039     Vector3 parentWorldPositionLeftAnchor = parent.GetCurrentWorldPosition() - parent.GetCurrentSize()*parentAnchorPoint;
1040     Vector3 popupWorldPosition = parentWorldPositionLeftAnchor + requiredPopupPosition;  // Parent World position plus popup local position gives World Position
1041     Vector3 popupDistanceFromAnchorPoint = popupSize*anchorPoint;
1042
1043     // Bounding rectangle is supplied as screen coordinates, bounding will be done in world coordinates.
1044     Vector4 boundingRectangleWorld;
1045     LocalToWorldCoordinatesBoundingBox( boundingBox, boundingRectangleWorld );
1046
1047     // Calculate distance to move popup (in local space) so fits within the boundary
1048     float xOffSetToKeepWithinBounds = 0.0f;
1049     if( popupWorldPosition.x - popupDistanceFromAnchorPoint.x < boundingRectangleWorld.x )
1050     {
1051       xOffSetToKeepWithinBounds = boundingRectangleWorld.x - ( popupWorldPosition.x - popupDistanceFromAnchorPoint.x );
1052     }
1053     else if ( popupWorldPosition.x +  popupDistanceFromAnchorPoint.x > boundingRectangleWorld.z )
1054     {
1055       xOffSetToKeepWithinBounds = boundingRectangleWorld.z - ( popupWorldPosition.x +  popupDistanceFromAnchorPoint.x );
1056     }
1057
1058     // Ensure initial display of Popup is in alternative position if can not fit above. As Property notification will be a frame behind.
1059     if ( popupWorldPosition.y - popupDistanceFromAnchorPoint.y < boundingRectangleWorld.y )
1060     {
1061       requiredPopupPosition.y = AlternatePopUpPositionRelativeToCursor();
1062     }
1063
1064     requiredPopupPosition.x = requiredPopupPosition.x + xOffSetToKeepWithinBounds;
1065
1066     // Prevent pixel mis-alignment by rounding down.
1067     requiredPopupPosition.x = static_cast<int>( requiredPopupPosition.x );
1068     requiredPopupPosition.y = static_cast<int>( requiredPopupPosition.y );
1069
1070   }
1071
1072   void FlipSelectionHandleImages()
1073   {
1074     SetupTouchEvents();
1075
1076     CreateSelectionHandles();
1077
1078     HandleImpl& leftHandle = mHandle[LEFT_SELECTION_HANDLE];
1079     HandleImpl& rightHandle = mHandle[RIGHT_SELECTION_HANDLE];
1080
1081     const HandleImageType leftImageType = leftHandle.pressed ? HANDLE_IMAGE_PRESSED : HANDLE_IMAGE_RELEASED;
1082     const HandleImageType rightImageType = rightHandle.pressed ? HANDLE_IMAGE_PRESSED : HANDLE_IMAGE_RELEASED;
1083     const bool leftFlipped = mSwapSelectionHandles != leftHandle.flipped;
1084     const bool rightFlipped = mSwapSelectionHandles != rightHandle.flipped;
1085
1086     leftHandle.actor.SetImage( leftFlipped ? mHandleImages[RIGHT_SELECTION_HANDLE][leftImageType] : mHandleImages[LEFT_SELECTION_HANDLE][leftImageType] );
1087
1088     leftHandle.actor.SetAnchorPoint( leftFlipped ? AnchorPoint::TOP_LEFT : AnchorPoint::TOP_RIGHT );
1089
1090     rightHandle.actor.SetImage( rightFlipped ? mHandleImages[LEFT_SELECTION_HANDLE][rightImageType] : mHandleImages[RIGHT_SELECTION_HANDLE][rightImageType] );
1091
1092     rightHandle.actor.SetAnchorPoint( rightFlipped ? AnchorPoint::TOP_RIGHT : AnchorPoint::TOP_LEFT );
1093   }
1094
1095   void SetScrollThreshold( float threshold )
1096   {
1097     mScrollThreshold = threshold;
1098   }
1099
1100   float GetScrollThreshold() const
1101   {
1102     return mScrollThreshold;
1103   }
1104
1105   void SetScrollSpeed( float speed )
1106   {
1107     mScrollSpeed = speed;
1108     mScrollDistance = speed * SCROLL_TICK_INTERVAL * TO_SECONDS;
1109   }
1110
1111   float GetScrollSpeed() const
1112   {
1113     return mScrollSpeed;
1114   }
1115
1116   void NotifyEndOfScroll()
1117   {
1118     StopScrollTimer();
1119
1120     if( mScrollTimer )
1121     {
1122       mNotifyEndOfScroll = true;
1123     }
1124   }
1125
1126   /**
1127    * Creates and starts a timer to scroll the text when handles are close to the edges of the text.
1128    *
1129    * It only starts the timer if it's already created.
1130    */
1131   void StartScrollTimer()
1132   {
1133     if( !mScrollTimer )
1134     {
1135       mScrollTimer = Timer::New( SCROLL_TICK_INTERVAL );
1136       mScrollTimer.TickSignal().Connect( this, &Decorator::Impl::OnScrollTimerTick );
1137     }
1138
1139     if( !mScrollTimer.IsRunning() )
1140     {
1141       mScrollTimer.Start();
1142     }
1143   }
1144
1145   /**
1146    * Stops the timer used to scroll the text.
1147    */
1148   void StopScrollTimer()
1149   {
1150     if( mScrollTimer )
1151     {
1152       mScrollTimer.Stop();
1153     }
1154   }
1155
1156   /**
1157    * Callback called by the timer used to scroll the text.
1158    *
1159    * It calculates and sets a new scroll position.
1160    */
1161   bool OnScrollTimerTick()
1162   {
1163     if( HANDLE_TYPE_COUNT != mHandleScrolling )
1164     {
1165       mController.DecorationEvent( mHandleScrolling,
1166                                    HANDLE_SCROLLING,
1167                                    mScrollDirection == SCROLL_RIGHT ? mScrollDistance : -mScrollDistance,
1168                                    0.f );
1169     }
1170
1171     return true;
1172   }
1173
1174   ControllerInterface& mController;
1175
1176   TapGestureDetector  mTapDetector;
1177   PanGestureDetector  mPanGestureDetector;
1178   Timer               mCursorBlinkTimer;          ///< Timer to signal cursor to blink
1179   Timer               mScrollTimer;               ///< Timer used to scroll the text when the grab handle is moved close to the edges.
1180
1181   Layer               mActiveLayer;               ///< Layer for active handles and alike that ensures they are above all else.
1182   ImageActor          mPrimaryCursor;
1183   ImageActor          mSecondaryCursor;
1184   MeshActor           mHighlightMeshActor;        ///< Mesh Actor to display highlight
1185
1186   PopupImpl           mCopyPastePopup;
1187   TextSelectionPopup::Buttons mEnabledPopupButtons; /// Bit mask of currently enabled Popup buttons
1188   TextSelectionPopupCallbackInterface& mTextSelectionPopupCallbackInterface;
1189
1190   Image               mHandleImages[HANDLE_TYPE_COUNT][HANDLE_IMAGE_TYPE_COUNT];
1191   Image               mCursorImage;
1192   Mesh                mHighlightMesh;             ///< Mesh for highlight
1193   MeshData            mHighlightMeshData;         ///< Mesh Data for highlight
1194   Material            mHighlightMaterial;         ///< Material used for highlight
1195
1196   CursorImpl          mCursor[CURSOR_COUNT];
1197   HandleImpl          mHandle[HANDLE_TYPE_COUNT];
1198   QuadContainer       mHighlightQuadList;         ///< Sub-selections that combine to create the complete selection highlight
1199
1200   Rect<int>           mBoundingBox;
1201   Vector4             mHighlightColor;            ///< Color of the highlight
1202   Vector2             mHighlightPosition;         ///< The position of the highlight actor.
1203
1204   unsigned int        mActiveCursor;
1205   unsigned int        mCursorBlinkInterval;
1206   float               mCursorBlinkDuration;
1207   HandleType          mHandleScrolling;         ///< The handle which is scrolling.
1208   ScrollDirection     mScrollDirection;         ///< The direction of the scroll.
1209   float               mScrollThreshold;         ///< Defines a square area inside the control, close to the edge. A cursor entering this area will trigger scroll events.
1210   float               mScrollSpeed;             ///< The scroll speed in pixels per second.
1211   float               mScrollDistance;          ///< Distance the text scrolls during a scroll interval.
1212
1213   bool                mActiveCopyPastePopup   : 1;
1214   bool                mCursorBlinkStatus      : 1; ///< Flag to switch between blink on and blink off.
1215   bool                mPrimaryCursorVisible   : 1; ///< Whether the primary cursor is visible.
1216   bool                mSecondaryCursorVisible : 1; ///< Whether the secondary cursor is visible.
1217   bool                mSwapSelectionHandles   : 1; ///< Whether to swap the selection handle images.
1218   bool                mNotifyEndOfScroll      : 1; ///< Whether to notify the end of the scroll.
1219 };
1220
1221 DecoratorPtr Decorator::New( ControllerInterface& controller,
1222                              TextSelectionPopupCallbackInterface& callbackInterface )
1223 {
1224   return DecoratorPtr( new Decorator( controller,
1225                                       callbackInterface ) );
1226 }
1227
1228 void Decorator::SetBoundingBox( const Rect<int>& boundingBox )
1229 {
1230   mImpl->mBoundingBox = boundingBox;
1231 }
1232
1233 const Rect<int>& Decorator::GetBoundingBox() const
1234 {
1235   return mImpl->mBoundingBox;
1236 }
1237
1238 void Decorator::Relayout( const Vector2& size )
1239 {
1240   mImpl->Relayout( size );
1241 }
1242
1243 void Decorator::UpdatePositions( const Vector2& scrollOffset )
1244 {
1245   mImpl->UpdatePositions( scrollOffset );
1246 }
1247
1248 /** Cursor **/
1249
1250 void Decorator::SetActiveCursor( ActiveCursor activeCursor )
1251 {
1252   mImpl->mActiveCursor = activeCursor;
1253 }
1254
1255 unsigned int Decorator::GetActiveCursor() const
1256 {
1257   return mImpl->mActiveCursor;
1258 }
1259
1260 void Decorator::SetPosition( Cursor cursor, float x, float y, float cursorHeight, float lineHeight )
1261 {
1262   mImpl->mCursor[cursor].position.x = x;
1263   mImpl->mCursor[cursor].position.y = y;
1264   mImpl->mCursor[cursor].cursorHeight = cursorHeight;
1265   mImpl->mCursor[cursor].lineHeight = lineHeight;
1266 }
1267
1268 void Decorator::GetPosition( Cursor cursor, float& x, float& y, float& cursorHeight, float& lineHeight ) const
1269 {
1270   x = mImpl->mCursor[cursor].position.x;
1271   y = mImpl->mCursor[cursor].position.y;
1272   cursorHeight = mImpl->mCursor[cursor].cursorHeight;
1273   lineHeight = mImpl->mCursor[cursor].lineHeight;
1274 }
1275
1276 const Vector2& Decorator::GetPosition( Cursor cursor ) const
1277 {
1278   return mImpl->mCursor[cursor].position;
1279 }
1280
1281 void Decorator::SetColor( Cursor cursor, const Dali::Vector4& color )
1282 {
1283   mImpl->mCursor[cursor].color = color;
1284 }
1285
1286 const Dali::Vector4& Decorator::GetColor( Cursor cursor ) const
1287 {
1288   return mImpl->mCursor[cursor].color;
1289 }
1290
1291 void Decorator::StartCursorBlink()
1292 {
1293   if ( !mImpl->mCursorBlinkTimer )
1294   {
1295     mImpl->mCursorBlinkTimer = Timer::New( mImpl->mCursorBlinkInterval );
1296     mImpl->mCursorBlinkTimer.TickSignal().Connect( mImpl, &Decorator::Impl::OnCursorBlinkTimerTick );
1297   }
1298
1299   if ( !mImpl->mCursorBlinkTimer.IsRunning() )
1300   {
1301     mImpl->mCursorBlinkTimer.Start();
1302   }
1303 }
1304
1305 void Decorator::StopCursorBlink()
1306 {
1307   if ( mImpl->mCursorBlinkTimer )
1308   {
1309     mImpl->mCursorBlinkTimer.Stop();
1310   }
1311 }
1312
1313 void Decorator::SetCursorBlinkInterval( float seconds )
1314 {
1315   mImpl->mCursorBlinkInterval = static_cast<unsigned int>( seconds * TO_MILLISECONDS ); // Convert to milliseconds
1316 }
1317
1318 float Decorator::GetCursorBlinkInterval() const
1319 {
1320   return static_cast<float>( mImpl->mCursorBlinkInterval ) * TO_SECONDS;
1321 }
1322
1323 void Decorator::SetCursorBlinkDuration( float seconds )
1324 {
1325   mImpl->mCursorBlinkDuration = seconds;
1326 }
1327
1328 float Decorator::GetCursorBlinkDuration() const
1329 {
1330   return mImpl->mCursorBlinkDuration;
1331 }
1332
1333 /** Handles **/
1334
1335 void Decorator::SetHandleActive( HandleType handleType, bool active )
1336 {
1337   mImpl->mHandle[handleType].active = active;
1338 }
1339
1340 bool Decorator::IsHandleActive( HandleType handleType ) const
1341 {
1342   return mImpl->mHandle[handleType].active ;
1343 }
1344
1345 void Decorator::SetHandleImage( HandleType handleType, HandleImageType handleImageType, Dali::Image image )
1346 {
1347   mImpl->mHandleImages[handleType][handleImageType] = image;
1348 }
1349
1350 Dali::Image Decorator::GetHandleImage( HandleType handleType, HandleImageType handleImageType ) const
1351 {
1352   return mImpl->mHandleImages[handleType][handleImageType];
1353 }
1354
1355 void Decorator::SetPosition( HandleType handleType, float x, float y, float height )
1356 {
1357   // Adjust grab handle displacement
1358   Impl::HandleImpl& handle = mImpl->mHandle[handleType];
1359
1360   handle.grabDisplacementX -= x - handle.position.x;
1361   handle.grabDisplacementY -= y - handle.position.y;
1362
1363   handle.position.x = x;
1364   handle.position.y = y;
1365   handle.lineHeight = height;
1366 }
1367
1368 void Decorator::GetPosition( HandleType handleType, float& x, float& y, float& height ) const
1369 {
1370   Impl::HandleImpl& handle = mImpl->mHandle[handleType];
1371
1372   x = handle.position.x;
1373   y = handle.position.y;
1374   height = handle.lineHeight;
1375 }
1376
1377 const Vector2& Decorator::GetPosition( HandleType handleType ) const
1378 {
1379   return mImpl->mHandle[handleType].position;
1380 }
1381
1382 void Decorator::SwapSelectionHandlesEnabled( bool enable )
1383 {
1384   mImpl->mSwapSelectionHandles = enable;
1385
1386   mImpl->FlipSelectionHandleImages();
1387 }
1388
1389 void Decorator::AddHighlight( float x1, float y1, float x2, float y2 )
1390 {
1391   mImpl->mHighlightQuadList.push_back( QuadCoordinates(x1, y1, x2, y2) );
1392 }
1393
1394 void Decorator::ClearHighlights()
1395 {
1396   mImpl->mHighlightQuadList.clear();
1397   mImpl->mHighlightPosition = Vector2::ZERO;
1398 }
1399
1400 void Decorator::SetHighlightColor( const Vector4& color )
1401 {
1402   mImpl->mHighlightColor = color;
1403 }
1404
1405 const Vector4& Decorator::GetHighlightColor() const
1406 {
1407   return mImpl->mHighlightColor;
1408 }
1409
1410 void Decorator::SetPopupActive( bool active )
1411 {
1412   mImpl->mActiveCopyPastePopup = active;
1413 }
1414
1415 bool Decorator::IsPopupActive() const
1416 {
1417   return mImpl->mActiveCopyPastePopup ;
1418 }
1419
1420 void Decorator::SetEnabledPopupButtons( TextSelectionPopup::Buttons& enabledButtonsBitMask )
1421 {
1422    mImpl->mEnabledPopupButtons = enabledButtonsBitMask;
1423 }
1424
1425 TextSelectionPopup::Buttons& Decorator::GetEnabledPopupButtons()
1426 {
1427   return mImpl->mEnabledPopupButtons;
1428 }
1429
1430 /** Scroll **/
1431
1432 void Decorator::SetScrollThreshold( float threshold )
1433 {
1434   mImpl->SetScrollThreshold( threshold );
1435 }
1436
1437 float Decorator::GetScrollThreshold() const
1438 {
1439   return mImpl->GetScrollThreshold();
1440 }
1441
1442 void Decorator::SetScrollSpeed( float speed )
1443 {
1444   mImpl->SetScrollSpeed( speed );
1445 }
1446
1447 float Decorator::GetScrollSpeed() const
1448 {
1449   return mImpl->GetScrollSpeed();
1450 }
1451
1452 void Decorator::NotifyEndOfScroll()
1453 {
1454   mImpl->NotifyEndOfScroll();
1455 }
1456
1457 Decorator::~Decorator()
1458 {
1459   delete mImpl;
1460 }
1461
1462 Decorator::Decorator( ControllerInterface& controller,
1463                       TextSelectionPopupCallbackInterface& callbackInterface )
1464 : mImpl( NULL )
1465 {
1466   mImpl = new Decorator::Impl( controller, callbackInterface );
1467 }
1468
1469 } // namespace Text
1470
1471 } // namespace Toolkit
1472
1473 } // namespace Dali