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