Revert to tizen branch.
[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/public-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/pan-gesture.h>
34 #include <dali/public-api/events/pan-gesture-detector.h>
35 #include <dali/public-api/geometry/mesh.h>
36 #include <dali/public-api/geometry/mesh-data.h>
37 #include <dali/public-api/images/resource-image.h>
38 #include <dali/public-api/math/rect.h>
39 #include <dali/public-api/math/vector2.h>
40 #include <dali/public-api/math/vector4.h>
41 #include <dali/public-api/object/property-notification.h>
42 #include <dali/public-api/signals/connection-tracker.h>
43
44 // INTERNAL INCLUDES
45 #include <dali-toolkit/public-api/controls/control.h>
46 #include <dali-toolkit/public-api/controls/control-impl.h>
47 #include <dali-toolkit/public-api/controls/buttons/push-button.h>
48 #include <dali-toolkit/public-api/controls/default-controls/solid-color-actor.h>
49 #include <dali-toolkit/public-api/controls/text-controls/text-label.h>
50 #include <dali-toolkit/public-api/controls/text-controls/text-selection-popup.h>
51
52 #ifdef DEBUG_ENABLED
53 #define DECORATOR_DEBUG
54
55 #endif
56
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( DALI_IMAGE_DIR "insertpoint-icon.png" );
77 const char* DEFAULT_SELECTION_HANDLE_ONE( DALI_IMAGE_DIR "text-input-selection-handle-left.png" );
78 const char* DEFAULT_SELECTION_HANDLE_TWO( DALI_IMAGE_DIR "text-input-selection-handle-right.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_PRESSED( DALI_IMAGE_DIR "text-input-selection-handle-right-press.png" );
81
82 const Dali::Vector3 DEFAULT_GRAB_HANDLE_RELATIVE_SIZE( 1.5f, 2.0f, 1.0f );
83 const Dali::Vector3 DEFAULT_SELECTION_HANDLE_RELATIVE_SIZE( 1.5f, 1.5f, 1.0f );
84
85 const std::size_t CURSOR_BLINK_INTERVAL = 500; // Cursor blink interval
86 const std::size_t MILLISECONDS = 1000;
87
88 const float DISPLAYED_HIGHLIGHT_Z_OFFSET( -0.05f );
89
90 /**
91  * structure to hold coordinates of each quad, which will make up the mesh.
92  */
93 struct QuadCoordinates
94 {
95   /**
96    * Default constructor
97    */
98   QuadCoordinates()
99   {
100   }
101
102   /**
103    * Constructor
104    * @param[in] x1 left co-ordinate
105    * @param[in] y1 top co-ordinate
106    * @param[in] x2 right co-ordinate
107    * @param[in] y2 bottom co-ordinate
108    */
109   QuadCoordinates(float x1, float y1, float x2, float y2)
110   : min(x1, y1),
111     max(x2, y2)
112   {
113   }
114
115   Dali::Vector2 min;                          ///< top-left (minimum) position of quad
116   Dali::Vector2 max;                          ///< bottom-right (maximum) position of quad
117 };
118
119 typedef std::vector<QuadCoordinates> QuadContainer;
120
121 /**
122  * @brief Takes a bounding rectangle in the local coordinates of an actor and returns the world coordinates Bounding Box.
123  * @param[in] boundingRectangle local bounding
124  * @param[out] Vector4 World coordinate bounding Box.
125  */
126 void LocalToWorldCoordinatesBoundingBox( const Dali::Rect<int>& boundingRectangle, Dali::Vector4& boundingBox )
127 {
128   // Convert to world coordinates and store as a Vector4 to be compatible with Property Notifications.
129   Dali::Vector2 stageSize = Dali::Stage::GetCurrent().GetSize();
130
131   const float originX = boundingRectangle.x - 0.5f * stageSize.width;
132   const float originY = boundingRectangle.y - 0.5f * stageSize.height;
133
134   boundingBox = Dali::Vector4( originX,
135                                originY,
136                                originX + boundingRectangle.width,
137                                originY + boundingRectangle.height );
138 }
139
140
141 } // end of namespace
142
143 namespace Dali
144 {
145
146 namespace Toolkit
147 {
148
149 namespace Text
150 {
151
152 struct Decorator::Impl : public ConnectionTracker
153 {
154   struct CursorImpl
155   {
156     CursorImpl()
157     : x(0.0f),
158       y(0.0f),
159       cursorHeight(0.0f),
160       lineHeight(0.0f),
161       color(Dali::Color::WHITE)
162     {
163     }
164
165     float x;
166     float y;
167     float cursorHeight;
168     float lineHeight;
169
170     Vector4 color;
171   };
172
173   struct SelectionHandleImpl
174   {
175     SelectionHandleImpl()
176     : x(0.0f),
177       y(0.0f),
178       lineHeight(0.0f),
179       flipped(false)
180     {
181     }
182
183     float x;
184     float y;
185     float lineHeight; ///< Not the handle height
186     bool flipped;
187
188     ImageActor actor;
189     Actor grabArea;
190
191     Image pressedImage;
192     Image releasedImage;
193   };
194
195   Impl( Dali::Toolkit::Internal::Control& parent, Observer& observer )
196   : mTextControlParent(parent),
197     mObserver(observer),
198     mActiveCursor(ACTIVE_CURSOR_NONE),
199     mActiveGrabHandle(false),
200     mActiveSelection( false ),
201     mActiveCopyPastePopup( false ),
202     mCursorBlinkInterval( CURSOR_BLINK_INTERVAL ),
203     mCursorBlinkDuration( 0.0f ),
204     mCursorBlinkStatus( true ),
205     mGrabDisplacementX( 0.0f ),
206     mGrabDisplacementY( 0.0f ),
207     mHighlightColor( 0.07f, 0.41f, 0.59f, 1.0f ), // light blue
208     mBoundingBox( Rect<int>() )
209   {
210   }
211
212   /**
213    * Relayout of the decorations owned by the decorator.
214    * @param[in] size The Size of the UI control the decorater is adding it's decorations to.
215    */
216   void Relayout( const Vector2& size, const Vector2& scrollPosition )
217   {
218     // TODO - Remove this if nothing is active
219     CreateActiveLayer();
220
221     // Show or hide the cursors
222     CreateCursors();
223     if( mPrimaryCursor )
224     {
225       mPrimaryCursor.SetPosition( mCursor[PRIMARY_CURSOR].x + scrollPosition.x,
226                                   mCursor[PRIMARY_CURSOR].y + scrollPosition.y );
227       mPrimaryCursor.SetSize( Size( 1.0f, mCursor[PRIMARY_CURSOR].cursorHeight ) );
228     }
229     if( mSecondaryCursor )
230     {
231       mSecondaryCursor.SetPosition( mCursor[SECONDARY_CURSOR].x + scrollPosition.x,
232                                     mCursor[SECONDARY_CURSOR].y + scrollPosition.y );
233       mSecondaryCursor.SetSize( Size( 1.0f, mCursor[SECONDARY_CURSOR].cursorHeight ) );
234     }
235
236     // Show or hide the grab handle
237     if( mActiveGrabHandle )
238     {
239       SetupTouchEvents();
240
241       CreateGrabHandle();
242
243       mGrabHandle.SetPosition( mCursor[PRIMARY_CURSOR].x + scrollPosition.x,
244                                mCursor[PRIMARY_CURSOR].lineHeight + scrollPosition.y );
245     }
246     else if( mGrabHandle )
247     {
248       UnparentAndReset( mGrabHandle );
249     }
250
251     // Show or hide the selection handles/highlight
252     if( mActiveSelection )
253     {
254       SetupTouchEvents();
255
256       CreateSelectionHandles();
257
258       SelectionHandleImpl& primary = mSelectionHandle[ PRIMARY_SELECTION_HANDLE ];
259       primary.actor.SetPosition( primary.x + scrollPosition.x,
260                                  primary.lineHeight + scrollPosition.y );
261
262       SelectionHandleImpl& secondary = mSelectionHandle[ SECONDARY_SELECTION_HANDLE ];
263       secondary.actor.SetPosition( secondary.x + scrollPosition.x,
264                                    secondary.lineHeight + scrollPosition.y );
265
266       CreateHighlight();
267       UpdateHighlight();
268     }
269     else
270     {
271       UnparentAndReset( mSelectionHandle[ PRIMARY_SELECTION_HANDLE ].actor );
272       UnparentAndReset( mSelectionHandle[ SECONDARY_SELECTION_HANDLE ].actor );
273       UnparentAndReset( mHighlightMeshActor );
274     }
275
276     if ( mActiveCopyPastePopup )
277     {
278       if ( !mCopyPastePopup )
279       {
280         mCopyPastePopup = TextSelectionPopup::New();
281 #ifdef DECORATOR_DEBUG
282         mCopyPastePopup.SetName("mCopyPastePopup");
283 #endif
284         mCopyPastePopup.SetAnchorPoint( AnchorPoint::CENTER );
285         mCopyPastePopup.OnRelayoutSignal().Connect( this,  &Decorator::Impl::PopUpRelayoutComplete  ); // Position popup after size negotiation
286         mActiveLayer.Add ( mCopyPastePopup );
287       }
288     }
289     else
290     {
291      if ( mCopyPastePopup )
292      {
293        UnparentAndReset( mCopyPastePopup );
294      }
295     }
296   }
297
298   void PopUpRelayoutComplete( Actor actor )
299   {
300     // Size negotiation for CopyPastePopup complete so can get the size and constrain position within bounding box.
301
302     mCopyPastePopup.OnRelayoutSignal().Disconnect( this, &Decorator::Impl::PopUpRelayoutComplete  );
303
304     Vector3 popupPosition( mCursor[PRIMARY_CURSOR].x, mCursor[PRIMARY_CURSOR].y -100.0f , 0.0f); //todo 100 to be an offset Property
305
306     Vector3 popupSize = Vector3( mCopyPastePopup.GetRelayoutSize( Dimension::WIDTH ), mCopyPastePopup.GetRelayoutSize( Dimension::HEIGHT ), 0.0f );
307
308     GetConstrainedPopupPosition( popupPosition, popupSize, AnchorPoint::CENTER, mActiveLayer, mBoundingBox );
309
310     SetUpPopUpPositionNotifications();
311
312     mCopyPastePopup.SetPosition( popupPosition ); //todo grabhandle(cursor) or selection handle positions to be used
313   }
314
315   void CreateCursor( ImageActor& cursor )
316   {
317     cursor = CreateSolidColorActor( Color::WHITE );
318     cursor.SetParentOrigin( ParentOrigin::TOP_LEFT ); // Need to set the default parent origin as CreateSolidColorActor() sets a different one.
319     cursor.SetAnchorPoint( AnchorPoint::TOP_CENTER );
320     cursor.SetRelayoutEnabled( false );
321   }
322
323   // Add or Remove cursor(s) from parent
324   void CreateCursors()
325   {
326     if( mActiveCursor == ACTIVE_CURSOR_NONE )
327     {
328       UnparentAndReset( mPrimaryCursor );
329       UnparentAndReset( mSecondaryCursor );
330     }
331     else
332     {
333       /* Create Primary and or Secondary Cursor(s) if active and add to parent */
334       if ( mActiveCursor == ACTIVE_CURSOR_PRIMARY ||
335            mActiveCursor == ACTIVE_CURSOR_BOTH )
336       {
337         if ( !mPrimaryCursor )
338         {
339           CreateCursor( mPrimaryCursor );
340 #ifdef DECORATOR_DEBUG
341           mPrimaryCursor.SetName( "PrimaryCursorActor" );
342 #endif
343           mActiveLayer.Add( mPrimaryCursor);
344         }
345       }
346
347       if ( mActiveCursor == ACTIVE_CURSOR_BOTH )
348       {
349         if ( !mSecondaryCursor )
350         {
351           CreateCursor( mSecondaryCursor );
352 #ifdef DECORATOR_DEBUG
353           mSecondaryCursor.SetName( "SecondaryCursorActor" );
354 #endif
355           mActiveLayer.Add( mSecondaryCursor);
356         }
357       }
358       else
359       {
360         UnparentAndReset( mSecondaryCursor );
361       }
362     }
363   }
364
365   bool OnCursorBlinkTimerTick()
366   {
367     // Cursor blinking
368     if ( mPrimaryCursor )
369     {
370       mPrimaryCursor.SetVisible( mCursorBlinkStatus );
371     }
372     if ( mSecondaryCursor )
373     {
374       mSecondaryCursor.SetVisible( mCursorBlinkStatus );
375     }
376
377     mCursorBlinkStatus = !mCursorBlinkStatus;
378
379     return true;
380   }
381
382   void SetupTouchEvents()
383   {
384     if ( !mTapDetector )
385     {
386       mTapDetector = TapGestureDetector::New();
387       mTapDetector.DetectedSignal().Connect( this, &Decorator::Impl::OnTap );
388     }
389
390     if ( !mPanGestureDetector )
391     {
392       mPanGestureDetector = PanGestureDetector::New();
393       mPanGestureDetector.DetectedSignal().Connect( this, &Decorator::Impl::OnPan );
394     }
395   }
396
397   void CreateActiveLayer()
398   {
399     if( !mActiveLayer )
400     {
401       Actor parent = mTextControlParent.Self();
402
403       mActiveLayer = Layer::New();
404 #ifdef DECORATOR_DEBUG
405       mActiveLayer.SetName ( "ActiveLayerActor" );
406 #endif
407
408       mActiveLayer.SetParentOrigin( ParentOrigin::CENTER );
409       mActiveLayer.SetResizePolicy( ResizePolicy::FILL_TO_PARENT, Dimension::ALL_DIMENSIONS );
410       mActiveLayer.SetPositionInheritanceMode( USE_PARENT_POSITION );
411
412       parent.Add( mActiveLayer );
413     }
414
415     mActiveLayer.RaiseToTop();
416   }
417
418   void CreateGrabHandle()
419   {
420     if( !mGrabHandle )
421     {
422       if ( !mGrabHandleImage )
423       {
424         mGrabHandleImage = ResourceImage::New( DEFAULT_GRAB_HANDLE_IMAGE );
425       }
426
427       mGrabHandle = ImageActor::New( mGrabHandleImage );
428       mGrabHandle.SetAnchorPoint( AnchorPoint::TOP_CENTER );
429       mGrabHandle.SetDrawMode( DrawMode::OVERLAY );
430       // Area that Grab handle responds to, larger than actual handle so easier to move
431 #ifdef DECORATOR_DEBUG
432       mGrabHandle.SetName( "GrabHandleActor" );
433       if ( Dali::Internal::gLogFilter->IsEnabledFor( Debug::Verbose ) )
434       {
435         mGrabArea = Toolkit::CreateSolidColorActor( Vector4(0.0f, 0.0f, 0.0f, 0.0f), true, Color::RED, 1 );
436         mGrabArea.SetName( "GrabArea" );
437       }
438       else
439       {
440         mGrabArea = Actor::New();
441         mGrabArea.SetRelayoutEnabled( true );
442         mGrabArea.SetName( "GrabArea" );
443       }
444 #else
445       mGrabArea = Actor::New();
446       mGrabArea.SetRelayoutEnabled( true );
447 #endif
448
449       mGrabArea.SetParentOrigin( ParentOrigin::TOP_CENTER );
450       mGrabArea.SetAnchorPoint( AnchorPoint::TOP_CENTER );
451       mGrabArea.SetResizePolicy( ResizePolicy::SIZE_RELATIVE_TO_PARENT, Dimension::ALL_DIMENSIONS );
452       mGrabArea.SetSizeModeFactor( DEFAULT_GRAB_HANDLE_RELATIVE_SIZE );
453       mGrabHandle.Add( mGrabArea );
454
455       mTapDetector.Attach( mGrabArea );
456       mPanGestureDetector.Attach( mGrabArea );
457
458       mActiveLayer.Add( mGrabHandle );
459     }
460   }
461
462   void CreateSelectionHandles()
463   {
464     SelectionHandleImpl& primary = mSelectionHandle[ PRIMARY_SELECTION_HANDLE ];
465     if ( !primary.actor )
466     {
467       if ( !primary.releasedImage )
468       {
469         primary.releasedImage = ResourceImage::New( DEFAULT_SELECTION_HANDLE_ONE );
470       }
471
472       primary.actor = ImageActor::New( primary.releasedImage );
473 #ifdef DECORATOR_DEBUG
474       primary.actor.SetName("SelectionHandleOne");
475 #endif
476       primary.actor.SetAnchorPoint( AnchorPoint::TOP_RIGHT ); // Change to BOTTOM_RIGHT if Look'n'Feel requires handle above text.
477       primary.actor.SetDrawMode( DrawMode::OVERLAY ); // ensure grab handle above text
478       primary.flipped = false;
479
480       primary.grabArea = Actor::New(); // Area that Grab handle responds to, larger than actual handle so easier to move
481       primary.grabArea.SetRelayoutEnabled( true );
482 #ifdef DECORATOR_DEBUG
483       primary.grabArea.SetName("SelectionHandleOneGrabArea");
484 #endif
485       primary.grabArea.SetResizePolicy( ResizePolicy::SIZE_RELATIVE_TO_PARENT, Dimension::ALL_DIMENSIONS );
486       primary.grabArea.SetSizeModeFactor( DEFAULT_SELECTION_HANDLE_RELATIVE_SIZE );
487       primary.grabArea.SetPositionInheritanceMode( Dali::USE_PARENT_POSITION );
488
489       mTapDetector.Attach( primary.grabArea );
490       mPanGestureDetector.Attach( primary.grabArea );
491       primary.grabArea.TouchedSignal().Connect( this, &Decorator::Impl::OnHandleOneTouched );
492
493       primary.actor.Add( primary.grabArea );
494       mActiveLayer.Add( primary.actor );
495     }
496
497     SelectionHandleImpl& secondary = mSelectionHandle[ SECONDARY_SELECTION_HANDLE ];
498     if ( !secondary.actor )
499     {
500       if ( !secondary.releasedImage )
501       {
502         secondary.releasedImage = ResourceImage::New( DEFAULT_SELECTION_HANDLE_TWO );
503       }
504
505       secondary.actor = ImageActor::New( secondary.releasedImage );
506 #ifdef DECORATOR_DEBUG
507       secondary.actor.SetName("SelectionHandleTwo");
508 #endif
509       secondary.actor.SetAnchorPoint( AnchorPoint::TOP_LEFT ); // Change to BOTTOM_LEFT if Look'n'Feel requires handle above text.
510       secondary.actor.SetDrawMode( DrawMode::OVERLAY ); // ensure grab handle above text
511       secondary.flipped = false;
512
513       secondary.grabArea = Actor::New(); // Area that Grab handle responds to, larger than actual handle so easier to move
514       secondary.grabArea.SetRelayoutEnabled( true );
515 #ifdef DECORATOR_DEBUG
516       secondary.grabArea.SetName("SelectionHandleTwoGrabArea");
517 #endif
518       secondary.grabArea.SetResizePolicy( ResizePolicy::SIZE_RELATIVE_TO_PARENT, Dimension::ALL_DIMENSIONS );
519       secondary.grabArea.SetSizeModeFactor( DEFAULT_SELECTION_HANDLE_RELATIVE_SIZE );
520       secondary.grabArea.SetPositionInheritanceMode( Dali::USE_PARENT_POSITION );
521
522       mTapDetector.Attach( secondary.grabArea );
523       mPanGestureDetector.Attach( secondary.grabArea );
524       secondary.grabArea.TouchedSignal().Connect( this, &Decorator::Impl::OnHandleTwoTouched );
525
526       secondary.actor.Add( secondary.grabArea );
527       mActiveLayer.Add( secondary.actor );
528     }
529
530     //SetUpHandlePropertyNotifications(); TODO
531   }
532
533   void CreateHighlight()
534   {
535     if ( !mHighlightMeshActor )
536     {
537       mHighlightMaterial = Material::New( "HighlightMaterial" );
538       mHighlightMaterial.SetDiffuseColor( mHighlightColor );
539
540       mHighlightMeshData.SetMaterial( mHighlightMaterial );
541       mHighlightMeshData.SetHasNormals( true );
542
543       mHighlightMesh = Mesh::New( mHighlightMeshData );
544
545       mHighlightMeshActor = MeshActor::New( mHighlightMesh );
546 #ifdef DECORATOR_DEBUG
547       mHighlightMeshActor.SetName( "HighlightMeshActor" );
548 #endif
549       mHighlightMeshActor.SetAnchorPoint( AnchorPoint::TOP_LEFT );
550       mHighlightMeshActor.SetPosition( 0.0f, 0.0f, DISPLAYED_HIGHLIGHT_Z_OFFSET );
551
552       Actor parent = mTextControlParent.Self();
553       parent.Add( mHighlightMeshActor );
554     }
555   }
556
557   void UpdateHighlight()
558   {
559     //  Construct a Mesh with a texture to be used as the highlight 'box' for selected text
560     //
561     //  Example scenarios where mesh is made from 3, 1, 2, 2 ,3 or 3 quads.
562     //
563     //   [ TOP   ]  [ TOP ]      [TOP ]  [ TOP    ]      [ TOP  ]      [ TOP  ]
564     //  [ MIDDLE ]             [BOTTOM]  [BOTTOM]      [ MIDDLE ]   [ MIDDLE  ]
565     //  [ BOTTOM]                                      [ MIDDLE ]   [ MIDDLE  ]
566     //                                                 [BOTTOM]     [ MIDDLE  ]
567     //                                                              [BOTTOM]
568     //
569     //  Each quad is created as 2 triangles.
570     //  Middle is just 1 quad regardless of its size.
571     //
572     //  (0,0)         (0,0)
573     //     0*    *2     0*       *2
574     //     TOP          TOP
575     //     3*    *1     3*       *1
576     //  4*       *1     4*     *6
577     //     MIDDLE         BOTTOM
578     //  6*       *5     7*     *5
579     //  6*    *8
580     //   BOTTOM
581     //  9*    *7
582     //
583
584     if ( mHighlightMesh && mHighlightMaterial && !mHighlightQuadList.empty() )
585     {
586       MeshData::VertexContainer vertices;
587       Dali::MeshData::FaceIndices faceIndices;
588
589       std::vector<QuadCoordinates>::iterator iter = mHighlightQuadList.begin();
590       std::vector<QuadCoordinates>::iterator endIter = mHighlightQuadList.end();
591
592       // vertex position defaults to (0 0 0)
593       MeshData::Vertex vertex;
594       // set normal for all vertices as (0 0 1) pointing outward from TextInput Actor.
595       vertex.nZ = 1.0f;
596
597       for(std::size_t v = 0; iter != endIter; ++iter,v+=4 )
598       {
599         // Add each quad geometry (a sub-selection) to the mesh data.
600
601         // 0-----1
602         // |\    |
603         // | \ A |
604         // |  \  |
605         // | B \ |
606         // |    \|
607         // 2-----3
608
609         QuadCoordinates& quad = *iter;
610         // top-left (v+0)
611         vertex.x = quad.min.x;
612         vertex.y = quad.min.y;
613         vertices.push_back( vertex );
614
615         // top-right (v+1)
616         vertex.x = quad.max.x;
617         vertex.y = quad.min.y;
618         vertices.push_back( vertex );
619
620         // bottom-left (v+2)
621         vertex.x = quad.min.x;
622         vertex.y = quad.max.y;
623         vertices.push_back( vertex );
624
625         // bottom-right (v+3)
626         vertex.x = quad.max.x;
627         vertex.y = quad.max.y;
628         vertices.push_back( vertex );
629
630         // triangle A (3, 1, 0)
631         faceIndices.push_back( v + 3 );
632         faceIndices.push_back( v + 1 );
633         faceIndices.push_back( v );
634
635         // triangle B (0, 2, 3)
636         faceIndices.push_back( v );
637         faceIndices.push_back( v + 2 );
638         faceIndices.push_back( v + 3 );
639
640         mHighlightMeshData.SetFaceIndices( faceIndices );
641       }
642
643       BoneContainer bones(0); // passed empty as bones not required
644       mHighlightMeshData.SetData( vertices, faceIndices, bones, mHighlightMaterial );
645       mHighlightMesh.UpdateMeshData( mHighlightMeshData );
646     }
647   }
648
649   void OnTap( Actor actor, const TapGesture& tap )
650   {
651     if( actor == mGrabHandle )
652     {
653       // TODO
654     }
655   }
656
657   void OnPan( Actor actor, const PanGesture& gesture )
658   {
659     if( actor == mGrabArea )
660     {
661       if( Gesture::Started == gesture.state )
662       {
663         mGrabDisplacementX = mGrabDisplacementY = 0;
664       }
665
666       mGrabDisplacementX += gesture.displacement.x;
667       mGrabDisplacementY += gesture.displacement.y;
668
669       float x = mCursor[PRIMARY_CURSOR].x + mGrabDisplacementX;
670       float y = mCursor[PRIMARY_CURSOR].y + mCursor[PRIMARY_CURSOR].lineHeight*0.5f + mGrabDisplacementY;
671
672       if( Gesture::Started    == gesture.state ||
673           Gesture::Continuing == gesture.state )
674       {
675         mObserver.GrabHandleEvent( GRAB_HANDLE_PRESSED, x, y );
676       }
677       else if( Gesture::Finished  == gesture.state ||
678                Gesture::Cancelled == gesture.state )
679       {
680         mObserver.GrabHandleEvent( GRAB_HANDLE_RELEASED, x, y );
681       }
682     }
683   }
684
685   bool OnHandleOneTouched( Actor actor, const TouchEvent& touch )
686   {
687     // TODO
688     return false;
689   }
690
691   bool OnHandleTwoTouched( Actor actor, const TouchEvent& touch )
692   {
693     // TODO
694     return false;
695   }
696
697   // Popup
698
699   float AlternatePopUpPositionRelativeToCursor()
700   {
701     float alternativePosition=0.0f;;
702
703     if ( mPrimaryCursor ) // Secondary cursor not used for paste
704     {
705       Cursor cursor = PRIMARY_CURSOR;
706       alternativePosition = mCursor[cursor].y;
707     }
708
709     const float popupHeight = 120.0f; // todo Set as a MaxSize Property in Control or retrieve from CopyPastePopup class.
710
711     if (  mActiveGrabHandle )
712     {
713       // If grab handle enabled then position pop-up below the grab handle.
714       const Vector2 grabHandleSize( 59.0f, 56.0f ); // todo
715       const float BOTTOM_HANDLE_BOTTOM_OFFSET = 1.5; //todo Should be a property
716       alternativePosition +=  grabHandleSize.height  + popupHeight + BOTTOM_HANDLE_BOTTOM_OFFSET ;
717     }
718     else
719     {
720       alternativePosition += popupHeight;
721     }
722
723     return alternativePosition;
724   }
725
726   void PopUpLeavesVerticalBoundary( PropertyNotification& source )
727   {
728     float alternativeYPosition=0.0f;
729   // todo
730   //  if( mHighlightMeshActor ) // Text Selection mode
731   //  {
732   //    alternativePosition = AlternatePopUpPositionRelativeToSelectionHandles();
733   //  }
734   //  else // Not in Text Selection mode
735   //  {
736     // if can't be positioned above, then position below row.
737     alternativeYPosition = AlternatePopUpPositionRelativeToCursor();
738    // }
739     mCopyPastePopup.SetY( alternativeYPosition );
740   }
741
742
743   void SetUpPopUpPositionNotifications( )
744   {
745     // Note Property notifications ignore any set anchor point so conditions must allow for this.  Default is Top Left.
746
747     // Exceeding vertical boundary
748
749     Vector4 worldCoordinatesBoundingBox;
750     LocalToWorldCoordinatesBoundingBox( mBoundingBox, worldCoordinatesBoundingBox );
751
752     float popupHeight = mCopyPastePopup.GetRelayoutSize( Dimension::HEIGHT);
753
754     PropertyNotification verticalExceedNotification = mCopyPastePopup.AddPropertyNotification( Actor::Property::WORLD_POSITION_Y,
755                                                       OutsideCondition( worldCoordinatesBoundingBox.y + popupHeight/2,
756                                                                         worldCoordinatesBoundingBox.w - popupHeight/2 ) );
757
758     verticalExceedNotification.NotifySignal().Connect( this, &Decorator::Impl::PopUpLeavesVerticalBoundary );
759   }
760
761   void GetConstrainedPopupPosition( Vector3& requiredPopupPosition, Vector3& popupSize, Vector3 anchorPoint, Actor& parent, Rect<int>& boundingBox )
762   {
763     DALI_ASSERT_DEBUG ( "Popup parent not on stage" && parent.OnStage() )
764
765     // Parent must already by added to Stage for these Get calls to work
766     Vector3 parentAnchorPoint = parent.GetCurrentAnchorPoint();
767     Vector3 parentWorldPositionLeftAnchor = parent.GetCurrentWorldPosition() - parent.GetCurrentSize()*parentAnchorPoint;
768     Vector3 popupWorldPosition = parentWorldPositionLeftAnchor + requiredPopupPosition;  // Parent World position plus popup local position gives World Position
769     Vector3 popupDistanceFromAnchorPoint = popupSize*anchorPoint;
770
771     // Bounding rectangle is supplied as screen coordinates, bounding will be done in world coordinates.
772     Vector4 boundingRectangleWorld;
773     LocalToWorldCoordinatesBoundingBox( boundingBox, boundingRectangleWorld );
774
775     // Calculate distance to move popup (in local space) so fits within the boundary
776     float xOffSetToKeepWithinBounds = 0.0f;
777     if( popupWorldPosition.x - popupDistanceFromAnchorPoint.x < boundingRectangleWorld.x )
778     {
779       xOffSetToKeepWithinBounds = boundingRectangleWorld.x - ( popupWorldPosition.x - popupDistanceFromAnchorPoint.x );
780     }
781     else if ( popupWorldPosition.x +  popupDistanceFromAnchorPoint.x > boundingRectangleWorld.z )
782     {
783       xOffSetToKeepWithinBounds = boundingRectangleWorld.z - ( popupWorldPosition.x +  popupDistanceFromAnchorPoint.x );
784     }
785
786     // Ensure initial display of Popup is in alternative position if can not fit above. As Property notification will be a frame behind.
787     if ( popupWorldPosition.y - popupDistanceFromAnchorPoint.y < boundingRectangleWorld.y )
788     {
789       requiredPopupPosition.y = AlternatePopUpPositionRelativeToCursor();
790     }
791
792     requiredPopupPosition.x = requiredPopupPosition.x + xOffSetToKeepWithinBounds;
793   }
794
795   Internal::Control& mTextControlParent;
796   Observer& mObserver;
797
798   Layer mActiveLayer; // Layer for active handles and alike that ensures they are above all else.
799
800   unsigned int mActiveCursor;
801   bool         mActiveGrabHandle;
802   bool         mActiveSelection;
803   bool         mActiveCopyPastePopup;
804
805   CursorImpl mCursor[CURSOR_COUNT];
806
807   Timer mCursorBlinkTimer; // Timer to signal cursor to blink
808   unsigned int mCursorBlinkInterval;
809   float mCursorBlinkDuration;
810   bool mCursorBlinkStatus; // Flag to switch between blink on and blink off
811
812   ImageActor mPrimaryCursor;
813   ImageActor mSecondaryCursor;
814
815   ImageActor mGrabHandle;
816   Actor mGrabArea;
817   float mGrabDisplacementX;
818   float mGrabDisplacementY;
819
820   SelectionHandleImpl mSelectionHandle[SELECTION_HANDLE_COUNT];
821
822   MeshActor         mHighlightMeshActor;        ///< Mesh Actor to display highlight
823   Mesh              mHighlightMesh;             ///< Mesh for highlight
824   MeshData          mHighlightMeshData;         ///< Mesh Data for highlight
825   Material          mHighlightMaterial;         ///< Material used for highlight
826   Vector4           mHighlightColor;            ///< Color of the highlight
827   QuadContainer     mHighlightQuadList;         ///< Sub-selections that combine to create the complete selection highlight
828
829   TextSelectionPopup mCopyPastePopup;
830
831   Image mCursorImage;
832   Image mGrabHandleImage;
833
834   TapGestureDetector mTapDetector;
835   PanGestureDetector mPanGestureDetector;
836
837   Rect<int> mBoundingBox;
838 };
839
840 DecoratorPtr Decorator::New( Internal::Control& parent, Observer& observer )
841 {
842   return DecoratorPtr( new Decorator(parent, observer) );
843 }
844
845 void Decorator::SetBoundingBox( const Rect<int>& boundingBox )
846 {
847   mImpl->mBoundingBox = boundingBox;
848 }
849
850 const Rect<int>& Decorator::GetBoundingBox() const
851 {
852   return mImpl->mBoundingBox;
853 }
854
855 void Decorator::Relayout( const Vector2& size, const Vector2& scrollPosition )
856 {
857   mImpl->Relayout( size, scrollPosition );
858 }
859
860 /** Cursor **/
861
862 void Decorator::SetActiveCursor( ActiveCursor activeCursor )
863 {
864   mImpl->mActiveCursor = activeCursor;
865 }
866
867 unsigned int Decorator::GetActiveCursor() const
868 {
869   return mImpl->mActiveCursor;
870 }
871
872 void Decorator::SetPosition( Cursor cursor, float x, float y, float cursorHeight, float lineHeight )
873 {
874   // Adjust grab handle displacement
875   mImpl->mGrabDisplacementX -= x - mImpl->mCursor[cursor].x;
876   mImpl->mGrabDisplacementY -= y - mImpl->mCursor[cursor].y;
877
878   mImpl->mCursor[cursor].x = x;
879   mImpl->mCursor[cursor].y = y;
880   mImpl->mCursor[cursor].cursorHeight = cursorHeight;
881   mImpl->mCursor[cursor].lineHeight = lineHeight;
882 }
883
884 void Decorator::GetPosition( Cursor cursor, float& x, float& y, float& cursorHeight, float& lineHeight ) const
885 {
886   x = mImpl->mCursor[cursor].x;
887   y = mImpl->mCursor[cursor].y;
888   cursorHeight = mImpl->mCursor[cursor].cursorHeight;
889   lineHeight = mImpl->mCursor[cursor].lineHeight;
890 }
891
892 void Decorator::SetColor( Cursor cursor, const Dali::Vector4& color )
893 {
894   mImpl->mCursor[cursor].color = color;
895 }
896
897 const Dali::Vector4& Decorator::GetColor( Cursor cursor ) const
898 {
899   return mImpl->mCursor[cursor].color;
900 }
901
902 void Decorator::StartCursorBlink()
903 {
904   if ( !mImpl->mCursorBlinkTimer )
905   {
906     mImpl->mCursorBlinkTimer = Timer::New( mImpl->mCursorBlinkInterval );
907     mImpl->mCursorBlinkTimer.TickSignal().Connect( mImpl, &Decorator::Impl::OnCursorBlinkTimerTick );
908   }
909
910   if ( !mImpl->mCursorBlinkTimer.IsRunning() )
911   {
912     mImpl->mCursorBlinkTimer.Start();
913   }
914 }
915
916 void Decorator::StopCursorBlink()
917 {
918   if ( mImpl->mCursorBlinkTimer )
919   {
920     mImpl->mCursorBlinkTimer.Stop();
921   }
922 }
923
924 void Decorator::SetCursorBlinkInterval( float seconds )
925 {
926   mImpl->mCursorBlinkInterval = seconds*MILLISECONDS; // Convert to milliseconds
927 }
928
929 float Decorator::GetCursorBlinkInterval() const
930 {
931   return mImpl->mCursorBlinkInterval;
932 }
933
934 void Decorator::SetCursorBlinkDuration( float seconds )
935 {
936   mImpl->mCursorBlinkDuration = seconds;
937 }
938
939 float Decorator::GetCursorBlinkDuration() const
940 {
941   return mImpl->mCursorBlinkDuration;
942 }
943
944 /** GrabHandle **/
945
946 void Decorator::SetGrabHandleActive( bool active )
947 {
948   mImpl->mActiveGrabHandle = active;
949 }
950
951 bool Decorator::IsGrabHandleActive() const
952 {
953   return mImpl->mActiveGrabHandle;
954 }
955
956 void Decorator::SetGrabHandleImage( Dali::Image image )
957 {
958   mImpl->mGrabHandleImage = image;
959 }
960
961 Dali::Image Decorator::GetGrabHandleImage() const
962 {
963   return mImpl->mGrabHandleImage;
964 }
965
966 /** Selection **/
967
968 void Decorator::SetSelectionActive( bool active )
969 {
970   mImpl->mActiveSelection = active;
971 }
972
973 bool Decorator::IsSelectionActive() const
974 {
975   return mImpl->mActiveSelection;
976 }
977
978 void Decorator::SetPosition( SelectionHandle handle, float x, float y, float height )
979 {
980   mImpl->mSelectionHandle[handle].x = x;
981   mImpl->mSelectionHandle[handle].y = y;
982   mImpl->mSelectionHandle[handle].lineHeight = height;
983 }
984
985 void Decorator::GetPosition( SelectionHandle handle, float& x, float& y, float& height ) const
986 {
987   x = mImpl->mSelectionHandle[handle].x;
988   y = mImpl->mSelectionHandle[handle].y;
989   height = mImpl->mSelectionHandle[handle].lineHeight;
990 }
991
992 void Decorator::SetImage( SelectionHandle handle, SelectionHandleState state, Dali::Image image )
993 {
994   if( SELECTION_HANDLE_PRESSED == state )
995   {
996     mImpl->mSelectionHandle[handle].pressedImage = image;
997   }
998   else
999   {
1000     mImpl->mSelectionHandle[handle].releasedImage = image;
1001   }
1002 }
1003
1004 Dali::Image Decorator::GetImage( SelectionHandle handle, SelectionHandleState state ) const
1005 {
1006   if( SELECTION_HANDLE_PRESSED == state )
1007   {
1008     return mImpl->mSelectionHandle[handle].pressedImage;
1009   }
1010
1011   return mImpl->mSelectionHandle[handle].releasedImage;
1012 }
1013
1014 void Decorator::AddHighlight( float x1, float y1, float x2, float y2 )
1015 {
1016   mImpl->mHighlightQuadList.push_back( QuadCoordinates(x1, y1, x2, y2) );
1017 }
1018
1019 void Decorator::ClearHighlights()
1020 {
1021   mImpl->mHighlightQuadList.clear();
1022 }
1023
1024 void Decorator::SetPopupActive( bool active )
1025 {
1026   mImpl->mActiveCopyPastePopup = active;
1027 }
1028
1029 bool Decorator::IsPopupActive() const
1030 {
1031   return mImpl->mActiveCopyPastePopup ;
1032 }
1033
1034 Decorator::~Decorator()
1035 {
1036   delete mImpl;
1037 }
1038
1039 Decorator::Decorator( Dali::Toolkit::Internal::Control& parent, Observer& observer )
1040 : mImpl( NULL )
1041 {
1042   mImpl = new Decorator::Impl( parent, observer );
1043 }
1044
1045 } // namespace Text
1046
1047 } // namespace Toolkit
1048
1049 } // namespace Dali