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