Fixed broken Doxygen build
[platform/core/uifw/dali-toolkit.git] / base / dali-toolkit / internal / controls / text-input / text-input-decorator-impl.cpp
1 /*
2  * Copyright (c) 2014 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/controls/text-input/text-input-decorator-impl.h>
20
21 // EXTERNAL INCLUDES
22 #include <dali/public-api/adaptor-framework/clipboard.h>
23 #include <dali/public-api/common/stage.h>
24 #include <dali/public-api/events/pan-gesture.h>
25 #include <dali/public-api/object/property-notification.h>
26 #include <dali/integration-api/debug.h>
27
28 // INTERNAL INCLUDES
29 #include <dali-toolkit/internal/controls/text-input/text-input-handles-impl.h>
30
31 using namespace Dali;
32
33 namespace
34 {
35 #if defined(DEBUG_ENABLED)
36   Debug::Filter* gLogFilter = Debug::Filter::New(Debug::NoLogging, false, "LOG_TEXT_INPUT_DECORATOR");
37 #endif
38
39 const Vector3 DEFAULT_SELECTION_HANDLE_SIZE( 51.0f, 79.0f, 0.0f );
40 const float TOP_HANDLE_TOP_OFFSET(-1.5f);                                   // Offset between top handle and cutCopyPaste pop-up
41 const float BOTTOM_HANDLE_BOTTOM_OFFSET(1.5f);                              // Offset between bottom handle and cutCopyPaste pop-up
42 const float UI_Z_OFFSET( 0.2f );                                            // Text Selection Handles/Cursor z-offset.
43 const Vector3 UI_OFFSET(0.0f, 0.0f, UI_Z_OFFSET);                           // Text Selection Handles/Cursor offset.
44 const char* DEFAULT_CURSOR( DALI_IMAGE_DIR "cursor.png" );
45 const Vector4 DEFAULT_CURSOR_IMAGE_9_BORDER( 2.0f, 2.0f, 2.0f, 2.0f );
46 const std::size_t CURSOR_BLINK_INTERVAL = 500;                              // Cursor blink interval
47 const float CURSOR_THICKNESS(6.0f);
48 const Degree CURSOR_ANGLE_OFFSET(2.0f);                                     // Offset from the angle
49
50 const unsigned int SCROLL_TICK_INTERVAL = 50u;
51 const float SCROLL_THRESHOLD = 10.f;
52 const float SCROLL_SPEED = 15.f;
53
54 /**
55  * Whether the given position plus the cursor size offset is inside the given boundary.
56  *
57  * @param[in] position The given position.
58  * @param[in] cursorSize The cursor size.
59  * @param[in] controlSize The given boundary.
60  * @param[in] threshold imaginary indent around boundary that will trigger the position to be outside of control.
61  *
62  * @return whether the given position is inside the given boundary.
63  */
64 bool IsPositionWithinControl( const Vector3& position, const Size& cursorSize, const Vector3& controlSize, const Vector2 threshold = Vector2::ZERO )
65 {
66   return ( position.x >= -Math::MACHINE_EPSILON_1000 + threshold.x ) &&
67          ( position.x <= controlSize.width - threshold.x + Math::MACHINE_EPSILON_1000 ) &&
68          ( position.y - cursorSize.height >= -Math::MACHINE_EPSILON_1000 + threshold.y ) &&
69          ( position.y <= controlSize.height + Math::MACHINE_EPSILON_1000 - threshold.y);
70 }
71
72 }
73
74 namespace Dali
75 {
76
77 namespace Toolkit
78 {
79
80 namespace Internal
81 {
82
83 Decorator::Decorator( TextViewCharacterPositioning& textViewManager, TextInputTextStyle& textStyle ):
84   mTextViewCharacterPositioning( textViewManager ),
85   mTextStyle( textStyle ),
86   mSelectionHandleOnePosition(0),
87   mSelectionHandleTwoPosition(0),
88   mGrabHandlePosition(0),
89   mCursorPosition( 0 ),
90   mTextHighlight( textViewManager ),
91   mCursorBlinkStatus( true ),
92   mCursorVisibility( true ),
93   mCursorRTLEnabled( false ),
94   mIsGrabHandleInScrollArea( false ),
95   mIsCursorInScrollArea( false ),
96   mGrabHandleEnabled( true )
97 {
98 }
99
100 Decorator::~Decorator()
101 {
102 }
103
104 /**
105  * Bounding Box
106  */
107 void Decorator::SetBoundingBox( const Rect<float>& boundingRectangle )
108 {
109   // Convert to world coordinates and store as a Vector4 to be compatible with Property Notifications.
110   Vector2 stageSize = Dali::Stage::GetCurrent().GetSize();
111
112   const float originX = boundingRectangle.x - 0.5f * stageSize.width;
113   const float originY = boundingRectangle.y - 0.5f * stageSize.height;
114
115   const Vector4 boundary( originX,
116                           originY,
117                           originX + boundingRectangle.width,
118                           originY + boundingRectangle.height );
119
120   mBoundingRectangleWorldCoordinates = boundary;
121 }
122
123 Vector4 Decorator::GetBoundingBox() const
124 {
125   return mBoundingRectangleWorldCoordinates;
126 }
127
128 /**
129  * Selection Handles
130  */
131 void Decorator::OnHandlePan(Actor actor, const PanGesture& gesture)
132 {
133   Actor selectionHandleOne = mTextInputHandles.GetSelectionHandleOne();
134   Actor selectionHandleTwo = mTextInputHandles.GetSelectionHandleTwo();
135
136   switch (gesture.state)
137   {
138     case Gesture::Started:
139     // fall through so code not duplicated
140     case Gesture::Continuing:
141     {
142       if ( actor.GetParent() == mTextInputHandles.GetSelectionHandleOne() )
143       {
144         MoveSelectionHandle( selectionHandleOne, mSelectionHandleOneActualPosition, mSelectionHandleOnePosition, gesture.displacement );
145         HidePopUp();
146       }
147       else if ( actor.GetParent() == mTextInputHandles.GetSelectionHandleTwo() )
148       {
149         MoveSelectionHandle( selectionHandleTwo, mSelectionHandleTwoActualPosition, mSelectionHandleTwoPosition, gesture.displacement );
150         HidePopUp();
151       }
152       else if ( actor.GetParent() == mTextInputHandles.GetGrabHandle() )
153       {
154         SetCursorVisibility( true );
155         ShowGrabHandle( mGrabHandleVisibility && mIsGrabHandleInScrollArea );
156         MoveGrabHandle( gesture.displacement );
157         HidePopUp(); // Do not show popup while handle is moving
158       }
159     }
160     break;
161
162     case Gesture::Finished:
163     {
164       // Revert back to non-pressed selection handle images
165       if ( actor.GetParent() == mTextInputHandles.GetSelectionHandleOne() )
166       {
167          mSelectionHandleOneActualPosition = MoveSelectionHandle( selectionHandleOne, mSelectionHandleOneActualPosition, mSelectionHandleOnePosition, gesture.displacement );
168         ShowPopupCutCopyPaste();
169       }
170       else if ( actor.GetParent() == mTextInputHandles.GetSelectionHandleTwo() )
171       {
172         mSelectionHandleTwoActualPosition = MoveSelectionHandle( selectionHandleTwo, mSelectionHandleTwoActualPosition, mSelectionHandleTwoPosition, gesture.displacement );
173         ShowPopupCutCopyPaste();
174       }
175       else if ( actor.GetParent() == mTextInputHandles.GetGrabHandle() )
176       {
177         MoveGrabHandle( gesture.displacement );
178         SetCursorVisibility( true );
179         ShowPopupCutCopyPaste();
180       }
181     }
182     break;
183     default:
184       break;
185   }
186 }
187
188 void Decorator::CreateSelectionHandles( Actor targetParent )
189 {
190   if ( !mPanGestureDetector )
191   {
192     mPanGestureDetector = PanGestureDetector::New();
193     mPanGestureDetector.DetectedSignal().Connect(this, &Decorator::OnHandlePan);
194   }
195
196   if ( !mTextInputHandles.GetSelectionHandleOne() )
197   {
198     mTextInputHandles.CreateSelectionHandles();
199
200     mTextInputHandles.AttachSelectionHandlesToGivenPanGesture( mPanGestureDetector );
201
202     targetParent.Add( mTextInputHandles.GetSelectionHandleOne() );
203     targetParent.Add( mTextInputHandles.GetSelectionHandleTwo() );
204
205     SetUpHandlePropertyNotifications();
206   }
207 }
208
209 void Decorator::RemoveSelectionHandles()
210 {
211   mTextInputHandles.DestorySelectionHandles();
212 }
213
214 Vector3 Decorator::GetSelectionHandleSize()
215 {
216   return DEFAULT_SELECTION_HANDLE_SIZE;
217 }
218
219 std::size_t Decorator::GetHandleOnePosition() const
220 {
221   return mSelectionHandleOnePosition;
222 }
223
224 std::size_t Decorator::GetHandleTwoPosition() const
225 {
226   return mSelectionHandleTwoPosition;
227 }
228
229 Vector3 Decorator::PositionSelectionHandle( Actor selectionHandle, std::size_t position )
230 {
231   bool direction(false);
232   Vector3 alternatePosition;
233   bool alternatePositionValid(false);
234
235   Vector3 actualPositionOfSelectionHandle = mTextViewCharacterPositioning.GetActualPositionFromCharacterPosition( position, direction, alternatePosition,alternatePositionValid );
236
237   return PositionSelectionHandle( selectionHandle, actualPositionOfSelectionHandle, position );
238
239 }
240
241 Vector3 Decorator::PositionSelectionHandle( Actor selectionHandle, Vector3& actualPosition, std::size_t position )
242 {
243   const Vector3 DEFAULT_HANDLE_OFFSET(0.0f, -5.0f, 0.0f);
244
245   selectionHandle.SetPosition( actualPosition += DEFAULT_HANDLE_OFFSET );
246
247   return actualPosition;
248 }
249
250 void Decorator::SetSelectionHandlesVisibility(bool visible )
251 {
252   mTextInputHandles.SetSelectionHandleOneVisibility( visible );
253   mTextInputHandles.SetSelectionHandleTwoVisibility( visible );
254 }
255
256 void Decorator::PositionSelectionHandles( std::size_t start, std::size_t end )
257 {
258   mSelectionHandleOnePosition = start;
259   mSelectionHandleTwoPosition = end;
260
261   mTextViewCharacterPositioning.UpdateTextLayoutInfo();
262
263   mSelectionHandleOneActualPosition = PositionSelectionHandle( mTextInputHandles.GetSelectionHandleOne(), mSelectionHandleOnePosition );
264   mSelectionHandleTwoActualPosition = PositionSelectionHandle( mTextInputHandles.GetSelectionHandleTwo(), mSelectionHandleTwoPosition );
265 }
266
267 Vector3 Decorator::MoveSelectionHandle( Actor selectionHandle,
268                                                           Vector3& actualSelectionHandlePosition,
269                                                           std::size_t& currentSelectionHandlePosition,
270                                                           const Vector2& displacement )
271 {
272   Vector3 actualHandlePosition;
273   actualSelectionHandlePosition.x += displacement.x * selectionHandle.GetCurrentScale().x;
274   actualSelectionHandlePosition.y += displacement.y * selectionHandle.GetCurrentScale().y;
275
276   // Selection handles should jump to the nearest character
277   std::size_t newHandlePosition = 0;
278   newHandlePosition = mTextViewCharacterPositioning.ReturnClosestIndex( actualSelectionHandlePosition.GetVectorXY() );
279
280   bool direction(false);
281   Vector3 alternatePosition;
282   bool alternatePositionValid(false);
283   actualHandlePosition = mTextViewCharacterPositioning.GetActualPositionFromCharacterPosition( newHandlePosition,direction, alternatePosition, alternatePositionValid  );
284
285   bool handleVisible = true;
286
287   if ( handleVisible &&                                          // Ensure the handle is visible.
288      ( newHandlePosition != currentSelectionHandlePosition ) &&  // Ensure the handle has moved.
289      ( newHandlePosition != mSelectionHandleTwoPosition ) &&     // Ensure new handle position not the same position as an existing handle.
290      ( newHandlePosition != mSelectionHandleOnePosition ) )
291   {
292     DALI_LOG_INFO(gLogFilter, Debug::Verbose, "TextInputDecorationLayouter::MoveSelectionHandle Handle visible and moved]\n");
293
294     currentSelectionHandlePosition = newHandlePosition;
295
296     PositionSelectionHandle( selectionHandle, actualHandlePosition, newHandlePosition );
297
298     ShowUpdatedHighlight();
299
300     // Set Active Style to that of first character in selection
301     std::size_t firstHandleInSelection = std::min( mSelectionHandleOnePosition, mSelectionHandleTwoPosition );
302
303     const TextStyle inputStyle = mTextViewCharacterPositioning.GetStyleAt( firstHandleInSelection );
304     mTextStyle.SetInputStyle( inputStyle );
305   }
306   return actualHandlePosition; // Returns Handle position passed in if new value not assigned.
307 }
308
309 /**
310  * GrabHandle
311  */
312 void Decorator::PositionGrabHandle( std::size_t positionInText )
313 {
314   bool direction(false);
315   Vector3 alternatePosition;
316   bool alternatePositionValid(false);
317
318   mGrabHandlePosition = positionInText;
319
320   mTextViewCharacterPositioning.UpdateTextLayoutInfo();
321   mActualGrabHandlePosition = mTextViewCharacterPositioning.GetActualPositionFromCharacterPosition( positionInText, direction, alternatePosition,alternatePositionValid );
322
323   mTextInputHandles.GetGrabHandle().SetPosition( mActualGrabHandlePosition );
324 }
325
326 void Decorator::MoveGrabHandle( const Vector2& displacement /*, std::size_t currentHandlePosition */)
327 {
328   mActualGrabHandlePosition.x += displacement.x;
329   mActualGrabHandlePosition.y += displacement.y;
330
331   // Grab handle should jump to the nearest character and take cursor with it
332   std::size_t newHandlePosition = mTextViewCharacterPositioning.ReturnClosestIndex( mActualGrabHandlePosition.GetVectorXY() );
333
334   Vector3 actualHandlePosition = mTextViewCharacterPositioning.GetActualPositionFromCharacterPosition( newHandlePosition );
335
336   bool handleVisible = true;
337
338   if( ( newHandlePosition != mGrabHandlePosition ) &&                           // Only redraw cursor and do updates if position changed
339         ( handleVisible ) )// and the new position is visible (if scroll is not enabled, it's always true).
340     {
341       mActualGrabHandlePosition = actualHandlePosition;
342       mTextInputHandles.GetGrabHandle().SetPosition( mActualGrabHandlePosition );
343
344       //PositionGrabHandle( newHandlePosition );
345       mGrabHandlePosition = newHandlePosition;
346       SetCurrentCursorPosition( mGrabHandlePosition );
347       DrawCursor( mGrabHandlePosition );
348
349       const std::size_t cursorPosition = GetCurrentCursorPosition();
350
351       // Let keyboard know the new cursor position so can 're-capture' for prediction.
352       mCursorRePositionedSignal.Emit();
353
354       // Set Input Style to that of cursor position
355       if ( !mTextViewCharacterPositioning.IsStyledTextEmpty()  && ( cursorPosition > 0 ) )
356       {
357         DALI_ASSERT_DEBUG( ( 0 <= cursorPosition-1 ) && ( cursorPosition-1 < mTextViewCharacterPositioning.StyledTextSize() ) );
358       }
359     }
360 }
361
362 void Decorator::ShowGrabHandle( bool visible )
363 {
364   mGrabHandleVisibility = visible;
365   mTextInputHandles.SetGrabHandleVisibility( visible );
366 }
367
368 void Decorator::CreateGrabHandle( Actor targetParent )
369 {
370   if ( !mPanGestureDetector )
371   {
372     mPanGestureDetector = PanGestureDetector::New();
373     mPanGestureDetector.DetectedSignal().Connect(this, &Decorator::OnHandlePan);
374   }
375
376   if ( !mTextInputHandles.GetGrabHandle() )
377   {
378     mTextInputHandles.CreateGrabHandle();
379     mTextInputHandles.AttachGrabHandleToGivenPanGesture( mPanGestureDetector );
380     targetParent.Add( mTextInputHandles.GetGrabHandle() );
381   }
382 }
383
384 void Decorator::SetGrabHandleImage( Image image )
385 {
386   mTextInputHandles.SetGrabHandleImage( image );
387 }
388
389 void Decorator::EnableGrabHandle( bool toggle)
390 {
391   // enables grab handle with will in turn de-activate magnifier
392   mGrabHandleEnabled = toggle;
393 }
394
395 bool Decorator::IsGrabHandleEnabled()
396 {
397   // if false then magnifier will be shown instead.
398   return mGrabHandleEnabled;
399 }
400
401 /**
402  * Cursor
403  */
404 std::size_t Decorator::GetCurrentCursorPosition() const
405 {
406   return mCursorPosition;
407 }
408
409 void Decorator::SetCurrentCursorPosition( std::size_t newCursorPosition )
410 {
411   mCursorPosition = newCursorPosition;
412 }
413
414 void Decorator::SetCursorVisibility( bool visible )
415 {
416   DALI_LOG_INFO(gLogFilter, Debug::Verbose, "TextInputDecorationLayouter::SetCursorVisibility[%s]\n", ( visible )?"true":"false");
417
418   mCursorVisibility = visible;
419   mCursor.SetVisible( mCursorVisibility && mIsCursorInScrollArea );
420   mCursorRTL.SetVisible( mCursorVisibility && mCursorRTLEnabled );
421 }
422
423 void Decorator::DrawCursor(const std::size_t nthChar)
424 {
425   std::size_t cursorPosition = GetCurrentCursorPosition();
426
427   // Get height of cursor and set its size
428   Size size( CURSOR_THICKNESS, 0.0f );
429
430   Vector2 min, max; // out parameters for GetRowRectFromCharacterPosition
431   size.height = mTextViewCharacterPositioning.GetRowRectFromCharacterPosition( mTextViewCharacterPositioning.GetVisualPosition( cursorPosition ), min, max  ).height;
432
433   mCursor.SetSize(size);
434
435   // If the character is italic then the cursor also tilts.
436   if ( !mTextViewCharacterPositioning.IsStyledTextEmpty()  && ( cursorPosition > 0 ) )
437   {
438     DALI_ASSERT_DEBUG( ( 0 <= cursorPosition-1 ) && ( cursorPosition-1 < mTextViewCharacterPositioning.StyledTextSize() ) );
439     const TextStyle styleAtCursor = mTextViewCharacterPositioning.GetStyleAt( cursorPosition-1 );
440     mCursor.SetRotation( styleAtCursor.IsItalicsEnabled() ? Degree( styleAtCursor.GetItalicsAngle() - CURSOR_ANGLE_OFFSET ) : Degree( 0.f ), Vector3::ZAXIS );
441   }
442
443   DALI_ASSERT_DEBUG( cursorPosition <= mTextViewCharacterPositioning.GetNumberOfCharactersInText() );
444   if ( ( cursorPosition <= mTextViewCharacterPositioning.GetNumberOfCharactersInText() ) )
445   {
446     Vector3 altPosition;    // Alternate (i.e. opposite direction) cursor position.
447     bool altPositionValid( false );  // Alternate cursor validity flag.
448     bool directionRTL( false );      // Need to know direction of primary cursor (in case we have 2 cursors and need to show them differently)
449     Vector3 position = mTextViewCharacterPositioning.GetActualPositionFromCharacterPosition( cursorPosition, directionRTL, altPosition, altPositionValid );
450
451     SetAltCursorEnabled( altPositionValid );
452
453     mCursor.SetPosition( position + UI_OFFSET );
454   }
455 }
456
457 void Decorator::SetAltCursorEnabled( bool enabled )
458 {
459   mCursorRTLEnabled = enabled;
460   mCursorRTL.SetVisible( mCursorVisibility && mCursorRTLEnabled );
461 }
462
463 void Decorator::SetCursorImage(Dali::Image image, const Vector4& border )
464 {
465   DALI_ASSERT_DEBUG ( image && "Create cursor image invalid")
466
467   if ( image )
468   {
469     mCursor.SetImage( image );
470     mCursor.SetNinePatchBorder( border );
471   }
472 }
473
474 void Decorator::SetRTLCursorImage( Image image, const Vector4& border )
475 {
476   DALI_ASSERT_DEBUG ( image && "Create cursor image invalid")
477
478   if ( image )
479   {
480     mCursorRTL.SetImage( image );
481     mCursorRTL.SetNinePatchBorder(  border );
482   }
483 }
484
485 ImageActor Decorator::CreateCursor( Image cursorImage, const Vector4& border, const std::string& cursorName )
486 {
487   ImageActor cursor;
488
489   if ( cursorImage )
490   {
491     cursor = ImageActor::New( cursorImage );
492   }
493   else
494   {
495     cursor = ImageActor::New( Image::New( DEFAULT_CURSOR ) );
496   }
497
498   cursor.SetStyle(ImageActor::STYLE_NINE_PATCH);
499   cursor.SetNinePatchBorder( border );
500   cursor.SetAnchorPoint(AnchorPoint::BOTTOM_CENTER);
501   cursor.SetVisible(false);
502   cursor.SetName( cursorName );
503   return cursor;
504 }
505
506 void Decorator::CreateCursors( Actor targetParent )
507 {
508   Image mCursorImage = Image::New( DEFAULT_CURSOR );
509   mCursor = CreateCursor (mCursorImage, DEFAULT_CURSOR_IMAGE_9_BORDER , "mainCursor");
510   mCursorRTL = CreateCursor ( mCursorImage, DEFAULT_CURSOR_IMAGE_9_BORDER, "rtlCursor");
511   targetParent.Add( mCursor );
512   targetParent.Add( mCursorRTL );
513 }
514
515 Size Decorator::GetCursorSizeAt( std::size_t positionWithinTextToGetCursorSize )
516 {
517   std::size_t visualPosition = mTextViewCharacterPositioning.GetVisualPosition( positionWithinTextToGetCursorSize );
518
519   Vector2 min, max;
520
521   const Size cursorSize( CURSOR_THICKNESS,
522       mTextViewCharacterPositioning.GetRowRectFromCharacterPosition( visualPosition, min, max ).height );
523
524   return cursorSize;
525 }
526
527 void Decorator::StartCursorBlinkTimer()
528 {
529   if ( !mCursorBlinkTimer )
530   {
531     mCursorBlinkTimer = Timer::New( CURSOR_BLINK_INTERVAL );
532     mCursorBlinkTimer.TickSignal().Connect( this, &Decorator::OnCursorBlinkTimerTick );
533   }
534
535   if ( !mCursorBlinkTimer.IsRunning() )
536   {
537     mCursorBlinkTimer.Start();
538   }
539 }
540
541 void Decorator::StopCursorBlinkTimer()
542 {
543   if ( mCursorBlinkTimer )
544   {
545     mCursorBlinkTimer.Stop();
546   }
547 }
548
549 bool Decorator::OnCursorBlinkTimerTick()
550 {
551   // Cursor blinking
552   mCursor.SetVisible( mCursorVisibility && mIsCursorInScrollArea && mCursorBlinkStatus );
553   if ( mCursorRTLEnabled )
554   {
555     mCursorRTL.SetVisible( mCursorVisibility && mIsCursorInScrollArea && mCursorBlinkStatus );
556   }
557   mCursorBlinkStatus = !mCursorBlinkStatus;
558
559   return true;
560 }
561
562 /**
563  * Highlight
564  */
565 void Decorator::ShowUpdatedHighlight()
566 {
567   Toolkit::TextView::TextLayoutInfo textLayoutInfo = mTextViewCharacterPositioning.GetLayoutInfo();
568   TextHighlight::HighlightInfo highlightInfo = mTextHighlight.CalculateHighlightInfo( mSelectionHandleOnePosition, mSelectionHandleTwoPosition, textLayoutInfo );
569
570   // Clamp highlightInfo so they don't exceed the boundary of the control.
571   const Vector3& controlSize = mTextViewCharacterPositioning.GetTextView().GetCurrentSize();
572   highlightInfo.Clamp2D( Vector2::ZERO, Vector2(controlSize.x, controlSize.y) );
573
574   mTextHighlight.UpdateHighlight( highlightInfo );
575 }
576
577 void Decorator::CreateHighlight( Actor parent )
578 {
579   DALI_ASSERT_DEBUG( parent && "Highlight target parent does not exist" );
580
581   if ( !mHighlightMeshActor )
582   {
583     mHighlightMeshActor = MeshActor::New( mTextHighlight.CreateHighLightMesh() );
584     mHighlightMeshActor.SetName( "HighlightMeshActor" );
585     mHighlightMeshActor.SetAffectedByLighting(false);
586     parent.Add( mHighlightMeshActor );
587   }
588 }
589
590 void Decorator::RemoveHighlight()
591 {
592   if ( mHighlightMeshActor )
593   {
594     mHighlightMeshActor.Unparent();
595     mHighlightMeshActor.Reset();
596     // NOTE: We cannot dereference mHighlightMesh, due to a how the scene-graph MeshRenderer uses the Mesh data.
597   }
598 }
599
600 void Decorator::HighlightVisibility( bool visiblility )
601 {
602   if ( mHighlightMeshActor )
603   {
604     mHighlightMeshActor.SetVisible( visiblility );
605   }
606 }
607
608 /**
609  *  Callbacks connected to be Property notifications for Boundary checking.
610  */
611 // Note If PropertyNotification signal definition included Actor we would not need to duplicate functions.
612 void Decorator::OnHandleOneLeavesBoundary( PropertyNotification& source)
613 {
614   mTextInputHandles.GetSelectionHandleOne().SetOpacity(0.0f);
615 }
616
617 void Decorator::OnHandleOneWithinBoundary(PropertyNotification& source)
618 {
619   mTextInputHandles.GetSelectionHandleOne().SetOpacity(1.0f);
620 }
621
622 void Decorator::OnHandleTwoLeavesBoundary( PropertyNotification& source)
623 {
624   mTextInputHandles.GetSelectionHandleTwo().SetOpacity(0.0f);
625 }
626
627 void Decorator::OnHandleTwoWithinBoundary(PropertyNotification& source)
628 {
629   mTextInputHandles.GetSelectionHandleTwo().SetOpacity(1.0f);
630 }
631
632 void Decorator::OnLeftBoundaryExceeded(PropertyNotification& source)
633 {
634   DALI_LOG_INFO(gLogFilter, Debug::General, "TextInputDecorationLayouter::OnLeftBoundaryExceeded\n");
635   Actor selectionHandleOne = mTextInputHandles.GetSelectionHandleOne();
636   selectionHandleOne.SetScale( -1.0f, 1.0f, 1.0f );
637   selectionHandleOne.SetAnchorPoint( AnchorPoint::TOP_LEFT);
638 }
639
640 void Decorator::OnReturnToLeftBoundary(PropertyNotification& source)
641 {
642   DALI_LOG_INFO(gLogFilter, Debug::General, "TextInputDecorationLayouter::OnReturnToLeftBoundary\n");
643   Actor selectionHandleOne = mTextInputHandles.GetSelectionHandleOne();
644   selectionHandleOne.SetScale( 1.0f, 1.0f, 1.0f );
645   selectionHandleOne.SetAnchorPoint( AnchorPoint::TOP_RIGHT);
646 }
647
648 void Decorator::OnRightBoundaryExceeded(PropertyNotification& source)
649 {
650   Actor selectionHandleTwo = mTextInputHandles.GetSelectionHandleTwo();
651   selectionHandleTwo.SetScale( -1.0f, 1.0f, 1.0f );
652   selectionHandleTwo.SetAnchorPoint( AnchorPoint::TOP_RIGHT);
653 }
654
655 void Decorator::OnReturnToRightBoundary(PropertyNotification& source)
656 {
657   Actor selectionHandleTwo = mTextInputHandles.GetSelectionHandleTwo();
658   selectionHandleTwo.SetScale( 1.0f, 1.0f, 1.0f );
659   selectionHandleTwo.SetAnchorPoint( AnchorPoint::TOP_LEFT);
660 }
661
662 void Decorator::SetUpHandlePropertyNotifications()
663 {
664   /* Property notifications for handles exceeding the boundary and returning back within boundary */
665
666   Vector3 handlesize = GetSelectionHandleSize();
667
668   Actor selectionHandleOne = mTextInputHandles.GetSelectionHandleOne();
669   Actor selectionHandleTwo = mTextInputHandles.GetSelectionHandleTwo();
670
671   // Exceeding horizontal boundary
672   PropertyNotification leftNotification = selectionHandleOne.AddPropertyNotification( Actor::WORLD_POSITION_X, LessThanCondition( mBoundingRectangleWorldCoordinates.x + handlesize.x) );
673   leftNotification.NotifySignal().Connect( this, &Decorator::OnLeftBoundaryExceeded );
674
675   PropertyNotification rightNotification = selectionHandleTwo.AddPropertyNotification( Actor::WORLD_POSITION_X, GreaterThanCondition( mBoundingRectangleWorldCoordinates.z - handlesize.x ) );
676   rightNotification.NotifySignal().Connect( this, &Decorator::OnRightBoundaryExceeded );
677
678   // Within horizontal boundary
679   PropertyNotification leftLeaveNotification = selectionHandleOne.AddPropertyNotification( Actor::WORLD_POSITION_X, GreaterThanCondition( mBoundingRectangleWorldCoordinates.x + 2*handlesize.x ) );
680   leftLeaveNotification.NotifySignal().Connect( this, &Decorator::OnReturnToLeftBoundary );
681
682   PropertyNotification rightLeaveNotification = selectionHandleTwo.AddPropertyNotification( Actor::WORLD_POSITION_X, LessThanCondition( mBoundingRectangleWorldCoordinates.z - 2*handlesize.x ) );
683   rightLeaveNotification.NotifySignal().Connect( this, &Decorator::OnReturnToRightBoundary );
684
685   // Exceeding vertical boundary
686   PropertyNotification verticalExceedNotificationOne = selectionHandleOne.AddPropertyNotification( Actor::WORLD_POSITION_Y,
687                                                        OutsideCondition( mBoundingRectangleWorldCoordinates.y + handlesize.y,
688                                                                          mBoundingRectangleWorldCoordinates.w - handlesize.y ) );
689   verticalExceedNotificationOne.NotifySignal().Connect( this, &Decorator::OnHandleOneLeavesBoundary );
690
691   PropertyNotification verticalExceedNotificationTwo = selectionHandleTwo.AddPropertyNotification( Actor::WORLD_POSITION_Y,
692                                                        OutsideCondition( mBoundingRectangleWorldCoordinates.y + handlesize.y,
693                                                                          mBoundingRectangleWorldCoordinates.w - handlesize.y ) );
694   verticalExceedNotificationTwo.NotifySignal().Connect( this, &Decorator::OnHandleTwoLeavesBoundary );
695
696   // Within vertical boundary
697   PropertyNotification verticalWithinNotificationOne = selectionHandleOne.AddPropertyNotification( Actor::WORLD_POSITION_Y,
698                                                        InsideCondition( mBoundingRectangleWorldCoordinates.y + handlesize.y,
699                                                                         mBoundingRectangleWorldCoordinates.w - handlesize.y ) );
700   verticalWithinNotificationOne.NotifySignal().Connect( this, &Decorator::OnHandleOneWithinBoundary );
701
702   PropertyNotification verticalWithinNotificationTwo = selectionHandleTwo.AddPropertyNotification( Actor::WORLD_POSITION_Y,
703                                                        InsideCondition( mBoundingRectangleWorldCoordinates.y + handlesize.y,
704                                                                         mBoundingRectangleWorldCoordinates.w - handlesize.y ) );
705   verticalWithinNotificationTwo.NotifySignal().Connect( this, &Decorator::OnHandleTwoWithinBoundary );
706 }
707
708 /**
709  * PopUp
710  */
711 Vector3 Decorator::PositionOfPopUpRelativeToSelectionHandles()
712 {
713   Vector3 position;
714   Vector2 min, max;
715   Vector3 topHandle;
716   Size rowSize;
717
718   // When text is selected, show popup above top handle (and text), or below bottom handle.
719
720   // topHandle: referring to the top most point of the handle or the top line of selection.
721   if ( mSelectionHandleTwoActualPosition.y > mSelectionHandleOneActualPosition.y ) // Handle may switch positions so calculate which is top.
722   {
723     topHandle = mSelectionHandleOneActualPosition;
724     rowSize= mTextViewCharacterPositioning.GetRowRectFromCharacterPosition( mSelectionHandleOnePosition, min, max );
725   }
726   else
727   {
728     topHandle = mSelectionHandleTwoActualPosition;
729     rowSize = mTextViewCharacterPositioning.GetRowRectFromCharacterPosition( mSelectionHandleTwoPosition, min, max );
730   }
731   topHandle.y += TOP_HANDLE_TOP_OFFSET - rowSize.height;
732   position = Vector3(topHandle.x, topHandle.y, 0.0f);
733
734   return position;
735 }
736
737 Vector3 Decorator::AlternatePopUpPositionRelativeToSelectionHandles()
738 {
739   // alternativePosition: referring to the bottom most point of the handle or the bottom line of selection.
740   Vector3 alternativePosition;
741   alternativePosition.y = std::max ( mSelectionHandleTwoActualPosition.y , mSelectionHandleOneActualPosition.y );
742   alternativePosition.y += GetSelectionHandleSize().y + mPopUpPanel.GetSize().y + BOTTOM_HANDLE_BOTTOM_OFFSET;
743
744   return alternativePosition;
745 }
746
747 Vector3 Decorator::PositionOfPopUpRelativeToCursor()
748 {
749   // When no text is selected, show PopUp at position of cursor
750   Vector3 position;
751   Vector2 min, max;
752   std::size_t cursorPosition = GetCurrentCursorPosition();
753   position = mTextViewCharacterPositioning.GetActualPositionFromCharacterPosition( cursorPosition );
754   const Size rowSize = mTextViewCharacterPositioning.GetRowRectFromCharacterPosition( cursorPosition, min, max );
755   position.y -= rowSize.height;
756
757   return position;
758 }
759
760 Vector3 Decorator::AlternatePopUpPositionRelativeToCursor()
761 {
762   std::size_t cursorPosition = GetCurrentCursorPosition();
763   Vector3 alternativePosition = mTextViewCharacterPositioning.GetActualPositionFromCharacterPosition( cursorPosition );
764
765   if (  mTextInputHandles.GetGrabHandle() )
766   {
767     // If grab handle enabled then position pop-up below the grab handle.
768     alternativePosition.y +=  mTextInputHandles.GetGrabHandle().GetCurrentSize().height  + mPopUpPanel.GetSize().y + BOTTOM_HANDLE_BOTTOM_OFFSET ;
769   }
770   else
771   {
772     alternativePosition.y += mPopUpPanel.GetSize().y;
773   }
774
775   return alternativePosition;
776
777 }
778
779 Vector3 Decorator::PositionOfPopUpRelativeToGrabHandle()
780 {
781    return Vector3::ZERO;
782 }
783
784 void Decorator::ShowPopUp()
785 {
786   Vector3 position;
787   Vector3 alternativePosition;
788   Size rowSize;
789
790   DALI_ASSERT_DEBUG( mPopUpTarget && "PopUp Target Actor does not exist" );
791
792   if( mHighlightMeshActor ) // Text Selection mode
793   {
794     position = PositionOfPopUpRelativeToSelectionHandles();
795   }
796   else // Not in Text Selection mode so position relative to cursor.
797   {
798     position = PositionOfPopUpRelativeToCursor();
799   }
800
801   // reposition popup above the desired cursor position.
802   mPopUpPanel.Show( mPopUpTarget, true );
803   mPopUpPanel.Self().SetPosition( position );
804   mPopUpPanel.PressedSignal().Connect( this, &Decorator::OnPopupButtonPressed );
805
806   SetUpPopUpPositionNotifications();
807   mPopUpPanel.ApplyConfinementConstraint( mBoundingRectangleWorldCoordinates );
808 }
809
810 void Decorator::ShowPopUp( Actor target )
811 {
812   mPopUpTarget = target;
813   ShowPopupCutCopyPaste();
814 }
815
816 void Decorator::ShowPopupCutCopyPaste()
817 {
818   bool isAllTextSelectedAlready = ( mTextViewCharacterPositioning.StyledTextSize() == GetSelectedText().size() );
819   bool isTextEmpty = mTextViewCharacterPositioning.IsStyledTextEmpty() ;
820   bool isSubsetOfTextAlreadySelected = ( !isAllTextSelectedAlready ) && mHighlightMeshActor;
821
822   Clipboard clipboard = Clipboard::Get();
823   bool hasClipboardGotContent = clipboard.NumberOfItems();
824
825   mPopUpPanel.CreateCutCopyPastePopUp( isAllTextSelectedAlready, isTextEmpty, hasClipboardGotContent, isSubsetOfTextAlreadySelected );
826   ShowPopUp();
827 }
828
829 void Decorator::HidePopUp( bool animate, bool signalFinished )
830 {
831 }
832
833 void Decorator::AddPopupOption(const std::string& name, const std::string& caption, const Image icon, bool finalOption)
834 {
835   mPopUpPanel.AddButton(name, caption, icon, finalOption);
836 }
837
838 void Decorator::ClearPopup()
839 {
840   mPopUpPanel.Clear();
841 }
842
843 void Decorator::PopUpLeavesVerticalBoundary( PropertyNotification& source)
844 {
845   Vector3 position, alternativePosition;
846
847   if( mHighlightMeshActor ) // Text Selection mode
848   {
849     alternativePosition = AlternatePopUpPositionRelativeToSelectionHandles();
850   }
851   else // Not in Text Selection mode
852   {
853     alternativePosition = AlternatePopUpPositionRelativeToCursor();
854     // if can't be positioned above, then position below row.
855   }
856   // reposition popup above the desired cursor position.
857   mPopUpPanel.Self().SetPosition( alternativePosition );
858 }
859
860 void Decorator::SetUpPopUpPositionNotifications( )
861 {
862   // Note Property notifications ignore any set anchor point so conditions must allow for this.  Default is Top Left.
863
864   // Exceeding vertical boundary
865   PropertyNotification verticalExceedNotificationOne = mPopUpPanel.Self().AddPropertyNotification( Actor::WORLD_POSITION_Y,
866                                                        OutsideCondition( mBoundingRectangleWorldCoordinates.y + mPopUpPanel.GetSize().y/2,
867                                                                          mBoundingRectangleWorldCoordinates.w - mPopUpPanel.GetSize().y/2 ) );
868   verticalExceedNotificationOne.NotifySignal().Connect( this, &Decorator::PopUpLeavesVerticalBoundary );
869 }
870
871 bool Decorator::OnPopupButtonPressed( Toolkit::Button button )
872 {
873   mPopUpButtonPressedSignal.Emit( button );
874   return false;
875 }
876
877 Decorator::PressedSignal& Decorator::PopUpButtonPressedSignal()
878 {
879   return mPopUpButtonPressedSignal;
880 }
881
882 Decorator::CursorPositionedSignal& Decorator::CursorRePositionedSignal()
883 {
884   return mCursorRePositionedSignal;
885 }
886
887 /**
888  *  Decoration Positioning during Scrolling
889  */
890 void Decorator::TextViewScrolled( Toolkit::TextView textView, Vector2 scrollPosition )
891 {
892   DALI_LOG_INFO(gLogFilter, Debug::Verbose, "TextInputDecorationLayouter::TextViewScrolled\n");
893
894   const Vector3& controlSize = mTextViewCharacterPositioning.GetTextView().GetCurrentSize(); // todo Could store size and only update in Control Size change.
895   Size cursorSize( CURSOR_THICKNESS, 0.f );
896
897   // Updates the cursor and grab handle position and visibility.
898   if( mTextInputHandles.GetGrabHandle() || mCursor )
899   {
900     Vector2 min, max;
901     size_t cursorTextPosition = GetCurrentCursorPosition();
902     cursorSize.height  = mTextViewCharacterPositioning.GetRowRectFromCharacterPosition( cursorTextPosition, min, max ).height;
903
904     const Vector3 cursorPosition = mTextViewCharacterPositioning.GetActualPositionFromCharacterPosition( cursorTextPosition );
905
906     bool mIsCursorInScrollArea = IsPositionWithinControl( cursorPosition, cursorSize, controlSize );
907     bool mIsGrabHandleInScrollArea = mIsCursorInScrollArea;
908
909     Vector2 actualGrabHandlePosition = cursorPosition.GetVectorXY();
910
911     if( mTextInputHandles.GetGrabHandle() )
912     {
913       ShowGrabHandle( mGrabHandleVisibility && mIsGrabHandleInScrollArea );
914       PositionGrabHandle( cursorTextPosition );
915     }
916
917     if( mCursor )
918     {
919       mCursor.SetVisible( mCursorVisibility && mIsCursorInScrollArea );
920       DrawCursor( cursorTextPosition );
921       mCursor.SetPosition( Vector3(actualGrabHandlePosition) + UI_OFFSET );
922     }
923   }
924
925   Actor selectionHandleOne = mTextInputHandles.GetSelectionHandleOne();
926   Actor selectionHandleTwo = mTextInputHandles.GetSelectionHandleTwo();
927
928   // Updates the selection handles and highlighted text position and visibility.
929   if( mTextInputHandles.GetSelectionHandleOne() && mTextInputHandles.GetSelectionHandleTwo() )
930   {
931     const Vector3 cursorPositionOne = mTextViewCharacterPositioning.GetActualPositionFromCharacterPosition( mSelectionHandleOnePosition );
932     const Vector3 cursorPositionTwo = mTextViewCharacterPositioning.GetActualPositionFromCharacterPosition( mSelectionHandleTwoPosition );
933
934     Size cursorSize( GetCursorSizeAt( mSelectionHandleOnePosition ) );
935     const bool isSelectionHandleOneVisible = IsPositionWithinControl( cursorPositionOne, cursorSize, controlSize );
936
937     cursorSize =  GetCursorSizeAt( mSelectionHandleTwoPosition );
938     const bool isSelectionHandleTwoVisible = IsPositionWithinControl( cursorPositionTwo, cursorSize, controlSize );
939
940     mSelectionHandleOneActualPosition = cursorPositionOne.GetVectorXY();
941     mSelectionHandleTwoActualPosition = cursorPositionTwo.GetVectorXY();
942
943     selectionHandleOne.SetVisible( isSelectionHandleOneVisible );
944     selectionHandleTwo.SetVisible( isSelectionHandleTwoVisible );
945
946     PositionSelectionHandle( selectionHandleOne, mSelectionHandleOneActualPosition, mSelectionHandleOnePosition );
947     PositionSelectionHandle( selectionHandleTwo, mSelectionHandleTwoActualPosition, mSelectionHandleTwoPosition );
948
949     if( mHighlightMeshActor )
950     {
951       mHighlightMeshActor.SetVisible( true );
952       ShowUpdatedHighlight();
953     }
954   }
955 }
956
957 void Decorator::StartScrollTimer()
958 {
959   if( !mScrollTimer )
960   {
961     mScrollTimer = Timer::New( SCROLL_TICK_INTERVAL );
962     mScrollTimer.TickSignal().Connect( this, &Decorator::OnScrollTimerTick );
963   }
964
965   if( !mScrollTimer.IsRunning() )
966   {
967     DALI_LOG_INFO(gLogFilter, Debug::Verbose, "TextInputDecorationLayouter::StartScrollTimer\n");
968     mScrollTimer.Start();
969   }
970 }
971
972 void Decorator::StopScrollTimer()
973 {
974   if( mScrollTimer )
975   {
976     DALI_LOG_INFO(gLogFilter, Debug::Verbose, "TextInputDecorationLayouter::StopScrollTimer\n");
977
978     mScrollTimer.Stop();
979     mScrollTimer.Reset();
980   }
981 }
982
983 bool Decorator::OnScrollTimerTick()
984 {
985   DALI_LOG_INFO(gLogFilter, Debug::Verbose, "TextInputDecorationLayouter::OnScrollTimerTick\n");
986
987   if ( mGrabHandleVisibility && mTextInputHandles.GetGrabHandle() )
988   {
989     std::size_t newGrabHandlePosition = mTextViewCharacterPositioning.ReturnClosestIndex( mActualGrabHandlePosition.GetVectorXY() );
990     if ( mGrabHandlePosition != newGrabHandlePosition )
991     {
992       Vector2 scrollPosition = mTextViewCharacterPositioning.GetScrollPosition();
993       Vector2 scrollDelta = ( mActualGrabHandlePosition - mCurrentHandlePosition ).GetVectorXY();
994       DALI_LOG_INFO(gLogFilter, Debug::Verbose, "TextInputDecorationLayouter::OnScrollTimerTick scrollPosition(%f) scrollDelta(%f)\n", scrollPosition.x, scrollDelta.x);
995       scrollPosition += scrollDelta;
996       mTextViewCharacterPositioning.SetScrollPosition( scrollPosition );
997
998       mActualGrabHandlePosition = mTextViewCharacterPositioning.GetActualPositionFromCharacterPosition( newGrabHandlePosition ).GetVectorXY();
999     }
1000   }
1001
1002   Actor selectionHandleOne = mTextInputHandles.GetSelectionHandleOne();
1003   Actor selectionHandleTwo = mTextInputHandles.GetSelectionHandleTwo();
1004
1005   if ( selectionHandleOne && selectionHandleTwo )
1006   {
1007     std::size_t newHandleOnePosition = mTextViewCharacterPositioning.ReturnClosestIndex( mSelectionHandleOneActualPosition.GetVectorXY() );
1008
1009     // todo duplicated code should be a function
1010
1011     if ( mSelectionHandleOnePosition != newHandleOnePosition )
1012     {
1013       const Vector3 actualPosition = mTextViewCharacterPositioning.GetActualPositionFromCharacterPosition( newHandleOnePosition );
1014
1015       Vector2 scrollDelta = ( actualPosition - mSelectionHandleOneActualPosition ).GetVectorXY();
1016
1017       Vector2 scrollPosition = mTextViewCharacterPositioning.GetScrollPosition();
1018       scrollPosition += scrollDelta;
1019       mTextViewCharacterPositioning.SetScrollPosition( scrollPosition );
1020
1021       mSelectionHandleOnePosition = newHandleOnePosition;
1022       mSelectionHandleOneActualPosition = mTextViewCharacterPositioning.GetActualPositionFromCharacterPosition( mSelectionHandleOnePosition ).GetVectorXY();
1023     }
1024     else
1025     {
1026       mSelectionHandleOneActualPosition.x += mScrollDisplacement.x;
1027       mSelectionHandleOneActualPosition.y += mScrollDisplacement.y;
1028     }
1029
1030     std::size_t newHandleTwoPosition = mTextViewCharacterPositioning.ReturnClosestIndex( mSelectionHandleTwoActualPosition.GetVectorXY() );
1031
1032     if ( mSelectionHandleTwoPosition != newHandleTwoPosition )
1033     {
1034       const Vector3 actualPosition = mTextViewCharacterPositioning.GetActualPositionFromCharacterPosition( newHandleTwoPosition );
1035
1036       Vector2 scrollDelta = ( actualPosition - mSelectionHandleTwoActualPosition ).GetVectorXY();
1037
1038       Vector2 scrollPosition = mTextViewCharacterPositioning.GetScrollPosition();
1039       scrollPosition += scrollDelta;
1040       mTextViewCharacterPositioning.SetScrollPosition( scrollPosition );
1041
1042       mSelectionHandleTwoPosition = newHandleTwoPosition;
1043       mCurrentHandlePosition = mTextViewCharacterPositioning.GetActualPositionFromCharacterPosition( mSelectionHandleTwoPosition ).GetVectorXY();
1044
1045     }
1046     else
1047     {
1048       mSelectionHandleTwoActualPosition.x += mScrollDisplacement.x;
1049       mSelectionHandleTwoActualPosition.y += mScrollDisplacement.y;
1050     }
1051   }
1052
1053   return true;
1054 }
1055
1056 /**
1057  *  Text Selection
1058  */
1059 MarkupProcessor::StyledTextArray Decorator::GetSelectedText()
1060 {
1061   MarkupProcessor::StyledTextArray currentSelectedText;
1062
1063   if ( mHighlightMeshActor ) // Text Selected
1064   {
1065     MarkupProcessor::StyledTextArray::iterator it = mTextViewCharacterPositioning.GetStyledTextArray().begin() + std::min(mSelectionHandleOnePosition, mSelectionHandleTwoPosition);
1066     MarkupProcessor::StyledTextArray::iterator end = mTextViewCharacterPositioning.GetStyledTextArray().begin() + std::max(mSelectionHandleOnePosition, mSelectionHandleTwoPosition);
1067
1068     for(; it != end; ++it)
1069     {
1070       MarkupProcessor::StyledText& styledText( *it );
1071       currentSelectedText.push_back( styledText );
1072     }
1073   }
1074   return currentSelectedText;
1075 }
1076
1077 } // Internal
1078
1079 } // namespace Toolkit
1080
1081 } // namespace Dali
1082