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