Update double tap and long press behaviour. 52/117252/4
authorVictor Cebollada <v.cebollada@samsung.com>
Tue, 28 Feb 2017 12:24:10 +0000 (12:24 +0000)
committerVictor Cebollada <v.cebollada@samsung.com>
Thu, 16 Mar 2017 11:51:19 +0000 (11:51 +0000)
It updates the double tap and long press event on a text area with no text.
i.e. On a position clearly after the last character of the text.

* Double Tap : The cursor is placed at the beginning or at the end of the text.
* Long Press : The cursor is placed at the beginning or at the end of the text and shows the text's selection popup.

Change-Id: Iaf9ea817a515781d20c87f60991e1996d5440a62
Signed-off-by: Victor Cebollada <v.cebollada@samsung.com>
automated-tests/src/dali-toolkit-internal/utc-Dali-Text-Controller.cpp
automated-tests/src/dali-toolkit-internal/utc-Dali-Text-Cursor.cpp
automated-tests/src/dali-toolkit/utc-Dali-TextEditor.cpp
automated-tests/src/dali-toolkit/utc-Dali-TextField.cpp
dali-toolkit/internal/controls/text-controls/text-field-impl.cpp
dali-toolkit/internal/text/cursor-helper-functions.cpp
dali-toolkit/internal/text/cursor-helper-functions.h
dali-toolkit/internal/text/text-controller-impl.cpp
dali-toolkit/internal/text/text-controller-impl.h
dali-toolkit/internal/text/text-controller.cpp
dali-toolkit/internal/text/text-controller.h

index 9b8217b..d39a88f 100644 (file)
@@ -470,3 +470,42 @@ int UtcDaliTextControllerSetGetCheckProperty(void)
   tet_result(TET_PASS);
   END_TEST;
 }
+
+int UtcDaliTextControllerSetGetTapLongPressAction(void)
+{
+  tet_infoline(" UtcDaliTextControllerSetGetTapLongPressAction");
+  ToolkitTestApplication application;
+
+  // Creates a text controller.
+  ControllerPtr controller = Controller::New();
+
+  DALI_TEST_CHECK( controller );
+
+  // Test first with no decorator.
+
+  DALI_TEST_EQUALS( Controller::NoTextTap::NO_ACTION, controller->GetNoTextDoubleTapAction(), TEST_LOCATION );
+  controller->SetNoTextDoubleTapAction( Controller::NoTextTap::HIGHLIGHT );
+  DALI_TEST_EQUALS( Controller::NoTextTap::NO_ACTION, controller->GetNoTextDoubleTapAction(), TEST_LOCATION );
+
+  DALI_TEST_EQUALS( Controller::NoTextTap::NO_ACTION, controller->GetNoTextLongPressAction(), TEST_LOCATION );
+  controller->SetNoTextLongPressAction( Controller::NoTextTap::HIGHLIGHT );
+  DALI_TEST_EQUALS( Controller::NoTextTap::NO_ACTION, controller->GetNoTextLongPressAction(), TEST_LOCATION );
+
+  // Add a decorator and re-test.
+
+  // Creates a decorator.
+  Text::DecoratorPtr decorator = Text::Decorator::New( *controller, *controller );
+
+  // Enables the text input.
+  controller->EnableTextInput( decorator );
+
+  DALI_TEST_EQUALS( Controller::NoTextTap::NO_ACTION, controller->GetNoTextDoubleTapAction(), TEST_LOCATION );
+  controller->SetNoTextDoubleTapAction( Controller::NoTextTap::HIGHLIGHT );
+  DALI_TEST_EQUALS( Controller::NoTextTap::HIGHLIGHT, controller->GetNoTextDoubleTapAction(), TEST_LOCATION );
+
+  DALI_TEST_EQUALS( Controller::NoTextTap::SHOW_SELECTION_POPUP, controller->GetNoTextLongPressAction(), TEST_LOCATION ); // The default is SHOW_SELECTION_POPUP
+  controller->SetNoTextLongPressAction( Controller::NoTextTap::HIGHLIGHT );
+  DALI_TEST_EQUALS( Controller::NoTextTap::HIGHLIGHT, controller->GetNoTextLongPressAction(), TEST_LOCATION );
+
+  END_TEST;
+}
index 1755b8b..1e2b72d 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2016 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2017 Samsung Electronics Co., Ltd.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -32,12 +32,15 @@ using namespace Text;
 // Tests the following functions.
 //
 // LineIndex GetClosestLine( VisualModelPtr visualModel,
-//                           float visualY )
+//                           float visualY,
+//                           bool& isLineHit )
 // CharacterIndex GetClosestCursorIndex( VisualModelPtr visualModel,
 //                                       LogicalModelPtr logicalModel,
 //                                       MetricsPtr metrics,
 //                                       float visualX,
-//                                       float visualY )
+//                                       float visualY,
+//                                       CharacterHitTest::Mode mode,
+//                                       bool& isCharacterHit )
 
 //////////////////////////////////////////////////////////
 
@@ -51,16 +54,42 @@ struct GetClosestLineData
   unsigned int   numberOfTests;                  ///< The number of tests.
   float*         visualY;                        ///< The visual 'y' position for each test.
   LineIndex*     lineIndex;                      ///< The expected line index for each test.
+  bool*          isLineHit;                      ///< The expected line hit value for each test.
 };
 
 struct GetClosestCursorIndexData
 {
+  std::string             description;           ///< Description of the test.
+  std::string             text;                  ///< Input text.
+  unsigned int            numberOfTests;         ///< The number of tests.
+  float*                  visualX;               ///< The visual 'x' position for each test.
+  float*                  visualY;               ///< The visual 'y' position for each test.
+  CharacterHitTest::Mode* mode;                  ///< The type of hit test.
+  CharacterIndex*         logicalIndex;          ///< The expected logical cursor index for each test.
+  bool*                   isCharacterHit;        ///< The expected character hit value for each test.
+};
+
+struct GetCursorPositionData
+{
+  std::string     description;                    ///< Description of the test.
+  std::string     text;                           ///< Input text.
+  unsigned int    numberOfTests;                  ///< The number of tests.
+  CharacterIndex* logicalIndex;                   ///< The logical cursor index for each test.
+  float*          visualX;                        ///< The expected visual 'x' position for each test.
+  float*          visualY;                        ///< The expected visual 'y' position for each test.
+};
+
+struct FindSelectionIndicesData
+{
   std::string     description;                    ///< Description of the test.
   std::string     text;                           ///< Input text.
   unsigned int    numberOfTests;                  ///< The number of tests.
   float*          visualX;                        ///< The visual 'x' position for each test.
   float*          visualY;                        ///< The visual 'y' position for each test.
-  CharacterIndex* logicalIndex;                   ///< The expected logical cursor index for each test.
+  bool*           found;                          ///< Whether selection indices are found.
+  CharacterIndex* startIndex;                     ///< The expected start cursor index for each test.
+  CharacterIndex* endIndex;                       ///< The expected end cursor index for each test.
+  CharacterIndex* noTextHitIndex;                 ///< The expected character index when there is no hit.
 };
 
 bool GetClosestLineTest( const GetClosestLineData& data )
@@ -85,16 +114,23 @@ bool GetClosestLineTest( const GetClosestLineData& data )
                    visualModel,
                    metrics );
 
-  for( unsigned int index = 0u; index < data.numberOfTests; ++index )
+  for( unsigned int index = 0; index < data.numberOfTests; ++index )
   {
+    bool isLineHit = false;
     const LineIndex lineIndex = GetClosestLine( visualModel,
-                                                data.visualY[index] );
+                                                data.visualY[index],
+                                                isLineHit );
 
     if( lineIndex != data.lineIndex[index] )
     {
       std::cout << "  test " << index << " failed. Different line index : " << lineIndex << ", expected : " << data.lineIndex[index] << std::endl;
       return false;
     }
+    if( isLineHit != data.isLineHit[index] )
+    {
+      std::cout << "  test " << index << " failed. Different line hit value : " << isLineHit << ", expected : " << data.isLineHit[index] << std::endl;
+      return false;
+    }
   }
 
   return true;
@@ -122,24 +158,138 @@ bool GetClosestCursorIndexTest( const GetClosestCursorIndexData& data )
                    visualModel,
                    metrics );
 
-  for( unsigned int index = 0u; index < data.numberOfTests; ++index )
+  for( unsigned int index = 0; index < data.numberOfTests; ++index )
   {
+    bool isCharacterHit = false;
     const CharacterIndex logicalCursorIndex = GetClosestCursorIndex( visualModel,
                                                                      logicalModel,
                                                                      metrics,
                                                                      data.visualX[index],
-                                                                     data.visualY[index] );
+                                                                     data.visualY[index],
+                                                                     data.mode[index],
+                                                                     isCharacterHit );
 
     if( logicalCursorIndex != data.logicalIndex[index] )
     {
       std::cout << "  test " << index << " failed. Different logical cursor index : " << logicalCursorIndex << ", expected : " << data.logicalIndex[index] << std::endl;
       return false;
     }
+    if( isCharacterHit != data.isCharacterHit[index] )
+    {
+      std::cout << "  test " << index << " failed. Different character hit value : " << isCharacterHit << ", expected : " << data.isCharacterHit[index] << std::endl;
+      return false;
+    }
+  }
+
+  return true;
+}
+
+bool GetCursorPositionTest( const GetCursorPositionData& data )
+{
+  std::cout << "  testing : " << data.description << std::endl;
+
+  // 1) Create the model.
+  LogicalModelPtr logicalModel;
+  VisualModelPtr visualModel;
+  MetricsPtr metrics;
+  Size textArea(400.f, 600.f);
+  Size layoutSize;
+
+  Vector<FontDescriptionRun> fontDescriptionRuns;
+  LayoutOptions options;
+  CreateTextModel( data.text,
+                   textArea,
+                   fontDescriptionRuns,
+                   options,
+                   layoutSize,
+                   logicalModel,
+                   visualModel,
+                   metrics );
+
+  for( unsigned int index = 0; index < data.numberOfTests; ++index )
+  {
+    CursorInfo cursorInfo;
+    GetCursorPosition( visualModel,
+                       logicalModel,
+                       metrics,
+                       data.logicalIndex[index],
+                       cursorInfo );
+
+    if( cursorInfo.primaryPosition.x != data.visualX[index] )
+    {
+      std::cout << "  test " << index << " failed. Different 'x' cursor position : " << cursorInfo.primaryPosition.x << ", expected : " << data.visualX[index] << std::endl;
+      return false;
+    }
+    if( cursorInfo.primaryPosition.y != data.visualY[index] )
+    {
+      std::cout << "  test " << index << " failed. Different 'y' cursor position : " << cursorInfo.primaryPosition.y << ", expected : " << data.visualY[index] << std::endl;
+       return false;
+    }
   }
 
   return true;
 }
 
+bool FindSelectionIndicesTest( const FindSelectionIndicesData& data )
+{
+  std::cout << "  testing : " << data.description << std::endl;
+
+  // 1) Create the model.
+  LogicalModelPtr logicalModel;
+  VisualModelPtr visualModel;
+  MetricsPtr metrics;
+  Size textArea(400.f, 600.f);
+  Size layoutSize;
+
+  Vector<FontDescriptionRun> fontDescriptionRuns;
+  LayoutOptions options;
+  CreateTextModel( data.text,
+                   textArea,
+                   fontDescriptionRuns,
+                   options,
+                   layoutSize,
+                   logicalModel,
+                   visualModel,
+                   metrics );
+
+  for( unsigned int index = 0; index < data.numberOfTests; ++index )
+  {
+    CharacterIndex startIndex = 0;
+    CharacterIndex endIndex = 0;
+    CharacterIndex noTextHitIndex = 0;
+    const bool found = FindSelectionIndices( visualModel,
+                                             logicalModel,
+                                             metrics,
+                                             data.visualX[index],
+                                             data.visualY[index],
+                                             startIndex,
+                                             endIndex,
+                                             noTextHitIndex );
+
+    if( found != data.found[index] )
+    {
+      std::cout << "  test " << index << " failed. Different found value : " << found << ", expected : " <<  data.found[index] << std::endl;
+      return false;
+    }
+    if( startIndex != data.startIndex[index] )
+    {
+      std::cout << "  test " << index << " failed. Different start index : " << startIndex << ", expected : " << data.startIndex[index] << std::endl;
+      return false;
+    }
+    if( endIndex != data.endIndex[index] )
+    {
+      std::cout << "  test " << index << " failed. Different end index : " << endIndex << ", expected : " << data.endIndex[index] << std::endl;
+      return false;
+    }
+    if( noTextHitIndex != data.noTextHitIndex[index] )
+    {
+      std::cout << "  test " << index << " failed. Different no text hit index : " << noTextHitIndex << ", expected : " << data.noTextHitIndex[index] << std::endl;
+      return false;
+    }
+  }
+  return true;
+}
+
 } // namespace
 
 //////////////////////////////////////////////////////////
@@ -154,13 +304,16 @@ int UtcDaliGetClosestLine(void)
   tet_infoline(" UtcDaliGetClosestLine");
 
   float visualY01[] = { -4.f, 3.f, 1000.f };
-  LineIndex lineIndices01[] = { 0u, 0u, 0u };
+  LineIndex lineIndices01[] = { 0, 0, 0 };
+  bool isLineHit01[] = { false, false, false };
 
   float visualY02[] = { -4.f, 3.f, 1000.f };
-  LineIndex lineIndices02[] = { 0u, 0u, 0u };
+  LineIndex lineIndices02[] = { 0, 0, 0 };
+  bool isLineHit02[] = { false, true, false };
 
   float visualY03[] = { -4.f, 11.f, 30.f, 51.f, 68.f, 87.f, 109.f, 130.f };
-  LineIndex lineIndices03[] = { 0u, 0u, 1u, 2u, 3u, 4u, 5u, 5u };
+  LineIndex lineIndices03[] = { 0, 0, 1u, 2u, 3u, 4u, 5u, 5u };
+  bool isLineHit03[] = { false, true, true, true, true, true, true, false };
 
   struct GetClosestLineData data[] =
   {
@@ -170,6 +323,7 @@ int UtcDaliGetClosestLine(void)
       3u,
       visualY01,
       lineIndices01,
+      isLineHit01
     },
     {
       "Single line text.",
@@ -177,6 +331,7 @@ int UtcDaliGetClosestLine(void)
       3u,
       visualY02,
       lineIndices02,
+      isLineHit02
     },
     {
       "Multi-line text.",
@@ -188,12 +343,13 @@ int UtcDaliGetClosestLine(void)
       "חלךmnoצמםpqrפרףstuדאוvwxהסתyzטזץ",
       8u,
       visualY03,
-      lineIndices03
+      lineIndices03,
+      isLineHit03
     }
   };
   const unsigned int numberOfTests = 3u;
 
-  for( unsigned int index = 0u; index < numberOfTests; ++index )
+  for( unsigned int index = 0; index < numberOfTests; ++index )
   {
     ToolkitTestApplication application;
     if( !GetClosestLineTest( data[index] ) )
@@ -209,17 +365,24 @@ int UtcDaliGetClosestLine(void)
 int UtcDaliGetClosestCursorIndex(void)
 {
   tet_infoline(" UtcDaliGetClosestCursorIndex");
+
   float visualX01[] = { -100.f };
   float visualY01[] = { -100.f };
-  CharacterIndex logicalIndex01[] = { 0u };
+  CharacterHitTest::Mode mode01[] = { CharacterHitTest::TAP };
+  CharacterIndex logicalIndex01[] = { 0 };
+  bool isCharacterHit01[] = { false };
 
   float visualX02[] = { -100.f, 1000.f, 60.f, 79.f, 83.f, 148.f, 99.f };
   float visualY02[] = { -100.f, 1000.f, 12.f, 12.f, 12.f, 12.f, 12.f };
-  CharacterIndex logicalIndex02[] = { 0u, 21u, 7u, 10u, 11u, 13u, 20u };
+  CharacterHitTest::Mode mode02[] = { CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP };
+  CharacterIndex logicalIndex02[] = { 0, 21u, 7u, 10u, 11u, 13u, 20u };
+  bool isCharacterHit02[] = { false, false, true, true, true, true, true  };
 
   float visualX03[] = { 19.f, 104.f, -2.f, 127.f };
   float visualY03[] = { 12.f, 12.f, 12.f, 12.f };
-  CharacterIndex logicalIndex03[] = { 3u, 12u, 0u, 18u };
+  CharacterHitTest::Mode mode03[] = { CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP };
+  CharacterIndex logicalIndex03[] = { 3u, 12u, 0, 18u };
+  bool isCharacterHit03[] = { true, true, false, false };
 
   //  0     5 _ 6     11  12
   //   Hello     world  \n
@@ -233,10 +396,16 @@ int UtcDaliGetClosestCursorIndex(void)
   float visualY04[] = { -100.f, 12.f, 12.f, 12.f, 12.f,
                           30.f, 30.f, 30.f, 30.f, 30.f,
                           50.f, 50.f, 50.f, 50.f, 50.f, 50.f, 50.f };
-  CharacterIndex logicalIndex04[] = {  0u,  5u,  6u, 11u, 11u,
+  CharacterHitTest::Mode mode04[] = { CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP,
+                                      CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP,
+                                      CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP };
+  CharacterIndex logicalIndex04[] = {    0,  5u,  6u, 11u, 11u,
                                        12u, 16u, 17u, 21u, 21u,
                                        22u, 25u, 31u, 32u, 34u, 40u, 40u,
                                        41u };
+  bool isCharacterHit04[] = { false, true, true, false, false,
+                              false, true, true, true, false,
+                              false, true, true, true, true, true, false };
 
   //   0           10           20            30           40      46
   //    abcשנבdefג   קכghiעיןjk   lחלךmnoצמם   pqrפרףstuד   אוvwxה
@@ -263,12 +432,24 @@ int UtcDaliGetClosestCursorIndex(void)
                         67.f, 67.f, 67.f, 67.f, 67.f, 67.f,
                         87.f, 87.f, 87.f, 87.f, 87.f, 87.f,
                         107.f, 107.f, 107.f, 107.f, 107.f };
-  CharacterIndex logicalIndex05[] = {   0u,  10u,  20u,  30u,  40u,  45u,
+  CharacterHitTest::Mode mode05[] = { CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP,
+                                      CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP,
+                                      CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP,
+                                      CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP,
+                                      CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP,
+                                      CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP };
+  CharacterIndex logicalIndex05[] = {    0,  10u,  20u,  30u,  40u,  45u,
                                        46u,  50u,  60u,  70u,  80u,  92u,
                                        93u, 100u, 110u, 120u, 130u, 138u,
                                       139u, 150u, 160u, 170u, 180u, 185u,
                                       186u, 190u, 200u, 210u, 220u, 232u,
                                       233u, 240u, 250u, 260u, 265u };
+  bool isCharacterHit05[] = { false, true, true, true, true, false,
+                              false, true, true, true, true, false,
+                              false, true, true, true, true, false,
+                              false, true, true, true, true, false,
+                              false, true, true, true, true, false,
+                              false, true, true, true, false };
 
   //   0            10           20           30           40        46
   //    שנבabcגקכd    efעיןghiחל   ךjklצמםmno   פרףpqrדאוs   tuהסתv
@@ -295,12 +476,36 @@ int UtcDaliGetClosestCursorIndex(void)
                         67.f, 67.f, 67.f, 67.f, 67.f, 67.f,
                         87.f, 87.f, 87.f, 87.f, 87.f, 87.f,
                         107.f, 107.f, 107.f, 107.f, 107.f };
-  CharacterIndex logicalIndex06[] = {   0u,  10u,  20u,  30u,  40u,  45u,
+  CharacterHitTest::Mode mode06[] = { CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP,
+                                      CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP,
+                                      CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP,
+                                      CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP,
+                                      CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP,
+                                      CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP };
+  CharacterIndex logicalIndex06[] = {    0,  10u,  20u,  30u,  40u,  45u,
                                        46u,  50u,  60u,  70u,  80u,  92u,
                                        93u, 100u, 110u, 120u, 130u, 138u,
                                       139u, 150u, 160u, 170u, 180u, 185u,
                                       186u, 190u, 200u, 210u, 220u, 231u,
                                       232u, 240u, 250u, 260u, 265u  };
+  bool isCharacterHit06[] = { false, true, true, true, true, false,
+                              false, true, true, true, true, false,
+                              false, true, true, true, true, false,
+                              false, true, true, true, true, false,
+                              false, true, true, true, true, false,
+                              false, true, true, true, false };
+
+  float visualX07[] = { 395.f };
+  float visualY07[] = { 12.f };
+  CharacterHitTest::Mode mode07[] = { CharacterHitTest::TAP };
+  CharacterIndex logicalIndex07[] = { 1u };
+  bool isCharacterHit07[] = { true };
+
+  float visualX08[] = { 7.f };
+  float visualY08[] = { 12.f };
+  CharacterHitTest::Mode mode08[] = { CharacterHitTest::TAP };
+  CharacterIndex logicalIndex08[] = { 1u };
+  bool isCharacterHit08[] = { true };
 
   struct GetClosestCursorIndexData data[] =
   {
@@ -310,7 +515,9 @@ int UtcDaliGetClosestCursorIndex(void)
       1u,
       visualX01,
       visualY01,
-      logicalIndex01
+      mode01,
+      logicalIndex01,
+      isCharacterHit01
     },
     {
       "Single line text.",
@@ -318,7 +525,9 @@ int UtcDaliGetClosestCursorIndex(void)
       7u,
       visualX02,
       visualY02,
-      logicalIndex02
+      mode02,
+      logicalIndex02,
+      isCharacterHit02
     },
     {
       "Single line with ligatures",
@@ -326,7 +535,9 @@ int UtcDaliGetClosestCursorIndex(void)
       4u,
       visualX03,
       visualY03,
-      logicalIndex03
+      mode03,
+      logicalIndex03,
+      isCharacterHit03
     },
     {
       "Multiline. Single line paragraphs",
@@ -336,7 +547,9 @@ int UtcDaliGetClosestCursorIndex(void)
       17u,
       visualX04,
       visualY04,
-      logicalIndex04
+      mode04,
+      logicalIndex04,
+      isCharacterHit04
     },
     {
       "Multiline. Single bidirectional paragraph, starts LTR, wrapped lines",
@@ -349,7 +562,9 @@ int UtcDaliGetClosestCursorIndex(void)
       35u,
       visualX05,
       visualY05,
-      logicalIndex05
+      mode05,
+      logicalIndex05,
+      isCharacterHit05
     },
     {
       "Multiline. Single bidirectional paragraph, starts RTL, wrapped lines",
@@ -362,12 +577,34 @@ int UtcDaliGetClosestCursorIndex(void)
       35u,
       visualX06,
       visualY06,
-      logicalIndex06
+      mode06,
+      logicalIndex06,
+      isCharacterHit06
+    },
+    {
+      "Testing complex characters. Arabic ligatures",
+      "الأَبْجَدِيَّة العَرَبِيَّة",
+      1u,
+      visualX07,
+      visualY07,
+      mode07,
+      logicalIndex07,
+      isCharacterHit07
+    },
+    {
+      "Testing complex characters. Latin ligatures",
+      "fi ligature",
+      1u,
+      visualX08,
+      visualY08,
+      mode08,
+      logicalIndex08,
+      isCharacterHit08
     }
   };
-  const unsigned int numberOfTests = 6u;
+  const unsigned int numberOfTests = 8u;
 
-  for( unsigned int index = 0u; index < numberOfTests; ++index )
+  for( unsigned int index = 0; index < numberOfTests; ++index )
   {
     ToolkitTestApplication application;
     if( !GetClosestCursorIndexTest( data[index] ) )
@@ -379,3 +616,167 @@ int UtcDaliGetClosestCursorIndex(void)
   tet_result(TET_PASS);
   END_TEST;
 }
+
+int UtcDaliGetCursorPosition(void)
+{
+  tet_infoline(" UtcDaliGetCursorPosition");
+
+  float visualX08[] = { 5.f };
+  float visualY08[] = { 0.f };
+  CharacterIndex logicalIndex08[] = { 1u };
+
+  struct GetCursorPositionData data[] =
+  {
+    {
+      "Testing complex characters. Latin ligatures",
+      "fi ligature",
+      1u,
+      logicalIndex08,
+      visualX08,
+      visualY08,
+    }
+  };
+  const unsigned int numberOfTests = 1u;
+
+  for( unsigned int index = 0; index < numberOfTests; ++index )
+  {
+    ToolkitTestApplication application;
+    if( !GetCursorPositionTest( data[index] ) )
+    {
+      tet_result(TET_FAIL);
+    }
+  }
+
+  tet_result(TET_PASS);
+  END_TEST;
+}
+
+int UtcDaliFindSelectionIndices(void)
+{
+  tet_infoline(" UtcDaliFindSelectionIndices");
+
+  float visualX01[] = { -100.f };
+  float visualY01[] = { -100.f };
+  bool found01[] = { false };
+  CharacterIndex startIndex01[] = { 0 };
+  CharacterIndex endIndex01[] = { 0 };
+  CharacterIndex noHitText01[] = { 0 };
+
+  float visualX02[] = { -100.f, 1000.f, 1000.f };
+  float visualY02[] = { -100.f, 12.f, 1000.f };
+  bool found02[] = { false, false, false };
+  CharacterIndex startIndex02[] = { 0, 6u, 6u };
+  CharacterIndex endIndex02[] = { 5u, 11u, 11u };
+  CharacterIndex noHitText02[] = { 0, 11u, 11u };
+
+  float visualX03[] = { 70.f };
+  float visualY03[] = { 12.f };
+  bool found03[] = { true };
+  CharacterIndex startIndex03[] = { 6u };
+  CharacterIndex endIndex03[] = { 11u };
+  CharacterIndex noHitText03[] = { 0u };
+
+  float visualX04[] = { 132.f };
+  float visualY04[] = { 12.f };
+  bool found04[] = { true };
+  CharacterIndex startIndex04[] = { 12u };
+  CharacterIndex endIndex04[] = { 16u };
+  CharacterIndex noHitText04[] = { 0u };
+
+  float visualX05[] = { 1.f };
+  float visualY05[] = { 12.f };
+  bool found05[] = { true };
+  CharacterIndex startIndex05[] = { 0 };
+  CharacterIndex endIndex05[] = { 1u };
+  CharacterIndex noHitText05[] = { 0 };
+
+  float visualX06[] = { 10.f };
+  float visualY06[] = { 12.f };
+  bool found06[] = { true };
+  CharacterIndex startIndex06[] = { 0 };
+  CharacterIndex endIndex06[] = { 1u };
+  CharacterIndex noHitText06[] = { 0u };
+
+  struct FindSelectionIndicesData data[] =
+  {
+    {
+      "void text",
+      "",
+      1u,
+      visualX01,
+      visualY01,
+      found01,
+      startIndex01,
+      endIndex01,
+      noHitText01
+    },
+    {
+      "touch out of text's boundaries",
+      "Hello world",
+      3u,
+      visualX02,
+      visualY02,
+      found02,
+      startIndex02,
+      endIndex02,
+      noHitText02
+    },
+    {
+      "touch on the text",
+      "Hello world demo",
+      1u,
+      visualX03,
+      visualY03,
+      found03,
+      startIndex03,
+      endIndex03,
+      noHitText03
+    },
+    {
+      "touch on the new paragraph character at the end of line",
+      "Hello world demo\n",
+      1u,
+      visualX04,
+      visualY04,
+      found04,
+      startIndex04,
+      endIndex04,
+      noHitText04
+    },
+    {
+      "touch on a white space character. is the unique character of the line",
+      " ",
+      1u,
+      visualX05,
+      visualY05,
+      found05,
+      startIndex05,
+      endIndex05,
+      noHitText05
+    },
+    {
+      "touch on a white space character. is between two words",
+      "h ello",
+      1u,
+      visualX06,
+      visualY06,
+      found06,
+      startIndex06,
+      endIndex06,
+      noHitText06
+    },
+  };
+  const unsigned int numberOfTests = 6u;
+
+  for( unsigned int index = 0; index < numberOfTests; ++index )
+  {
+    ToolkitTestApplication application;
+    if( !FindSelectionIndicesTest( data[index] ) )
+    {
+      tet_result(TET_FAIL);
+    }
+  }
+
+  tet_result(TET_PASS);
+  END_TEST;
+}
index aee8e65..18628a7 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2016 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2017 Samsung Electronics Co., Ltd.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -22,7 +22,9 @@
 #include <dali/public-api/rendering/renderer.h>
 #include <dali/devel-api/adaptor-framework/clipboard.h>
 #include <dali/integration-api/events/key-event-integ.h>
+#include <dali/integration-api/events/touch-event-integ.h>
 #include <dali/integration-api/events/tap-gesture-event.h>
+#include <dali/integration-api/events/pan-gesture-event.h>
 #include <dali-toolkit-test-suite-utils.h>
 #include <dali-toolkit/dali-toolkit.h>
 #include <dali-toolkit/devel-api/controls/text-controls/text-editor-devel.h>
@@ -98,6 +100,7 @@ const Dali::Vector4 LIGHT_BLUE( 0.75f, 0.96f, 1.f, 1.f ); // The text highlight
 const unsigned int CURSOR_BLINK_INTERVAL = 500u; // Cursor blink interval
 const float TO_MILLISECONDS = 1000.f;
 const float TO_SECONDS = 1.f / TO_MILLISECONDS;
+const float RENDER_FRAME_INTERVAL = 16.66f;
 
 const float SCROLL_THRESHOLD = 10.f;
 const float SCROLL_SPEED = 300.f;
@@ -109,6 +112,8 @@ const int KEY_A_CODE = 38;
 const int KEY_D_CODE = 40;
 const int KEY_WHITE_SPACE_CODE = 65;
 
+const char* HANDLE_IMAGE_FILE_NAME = TEST_RESOURCE_DIR "/insertpoint-icon.png";
+
 static bool gTextChangedCallBackCalled;
 static bool gInputStyleChangedCallbackCalled;
 static Dali::Toolkit::TextEditor::InputStyle::Mask gInputStyleMask;
@@ -158,6 +163,23 @@ Integration::TapGestureEvent GenerateTap(
   return tap;
 }
 
+// Generate a PanGestureEvent to send to Core
+Integration::PanGestureEvent GeneratePan( Gesture::State state,
+                                          const Vector2& previousPosition,
+                                          const Vector2& currentPosition,
+                                          unsigned long timeDelta,
+                                          unsigned int numberOfTouches = 1u )
+{
+  Integration::PanGestureEvent pan(state);
+
+  pan.previousPosition = previousPosition;
+  pan.currentPosition = currentPosition;
+  pan.timeDelta = timeDelta;
+  pan.numberOfTouches = numberOfTouches;
+
+  return pan;
+}
+
 // Generate a KeyEvent to send to Core.
 Integration::KeyEvent GenerateKey( const std::string& keyName,
                                    const std::string& keyString,
@@ -174,6 +196,69 @@ Integration::KeyEvent GenerateKey( const std::string& keyName,
                                 keyState );
 }
 
+/**
+ * Helper to generate PanGestureEvent
+ *
+ * @param[in] application Application instance
+ * @param[in] state The Gesture State
+ * @param[in] pos The current position of touch.
+ */
+static void SendPan(ToolkitTestApplication& application, Gesture::State state, const Vector2& pos)
+{
+  static Vector2 last;
+
+  if( (state == Gesture::Started) ||
+      (state == Gesture::Possible) )
+  {
+    last.x = pos.x;
+    last.y = pos.y;
+  }
+
+  application.ProcessEvent( GeneratePan( state, last, pos, 16 ) );
+
+  last.x = pos.x;
+  last.y = pos.y;
+}
+
+/*
+ * Simulate time passed by.
+ *
+ * @note this will always process at least 1 frame (1/60 sec)
+ *
+ * @param application Test application instance
+ * @param duration Time to pass in milliseconds.
+ * @return The actual time passed in milliseconds
+ */
+static int Wait(ToolkitTestApplication& application, int duration = 0)
+{
+  int time = 0;
+
+  for(int i = 0; i <= ( duration / RENDER_FRAME_INTERVAL); i++)
+  {
+    application.SendNotification();
+    application.Render(RENDER_FRAME_INTERVAL);
+    time += RENDER_FRAME_INTERVAL;
+  }
+
+  return time;
+}
+
+Dali::Integration::Point GetPointDownInside( Vector2& pos )
+{
+  Dali::Integration::Point point;
+  point.SetState( PointState::DOWN );
+  point.SetScreenPosition( pos );
+  return point;
+}
+
+Dali::Integration::Point GetPointUpInside( Vector2& pos )
+{
+  Dali::Integration::Point point;
+  point.SetState( PointState::UP );
+  point.SetScreenPosition( pos );
+  return point;
+}
+
 bool DaliTestCheckMaps( const Property::Map& fontStyleMapGet, const Property::Map& fontStyleMapSet )
 {
   if( fontStyleMapGet.Count() == fontStyleMapSet.Count() )
@@ -1463,6 +1548,25 @@ int utcDaliTextEditorEvent03(void)
   Renderer highlight = stencil.GetChildAt( 1u ).GetRendererAt( 0u );
   DALI_TEST_CHECK( highlight );
 
+  // Double tap out of bounds
+  application.ProcessEvent( GenerateTap( Gesture::Possible, 2u, 1u, Vector2( 29.f, 25.0f ) ) );
+  application.ProcessEvent( GenerateTap( Gesture::Started, 2u, 1u, Vector2( 29.f, 25.0f ) ) );
+
+  // Render and notify
+  application.SendNotification();
+  application.Render();
+
+  // The stencil actor should have one actors: the renderer actor.
+  stencil = editor.GetChildAt( 0u );
+
+  // The stencil actor has a container with all the actors which contain the text renderers.
+  container = stencil.GetChildAt( 0u );
+  for( unsigned int index = 0; index < container.GetChildCount(); ++index )
+  {
+    Renderer renderer = container.GetChildAt( index ).GetRendererAt( 0u );
+    DALI_TEST_CHECK( renderer );
+  }
+
   END_TEST;
 }
 
@@ -1638,3 +1742,96 @@ int utcDaliTextEditorEvent05(void)
 
   END_TEST;
 }
+
+int utcDaliTextEditorHandles(void)
+{
+  ToolkitTestApplication application;
+  tet_infoline(" utcDaliTextEditorHandles");
+
+  TextEditor editor = TextEditor::New();
+  DALI_TEST_CHECK( editor );
+
+  Stage::GetCurrent().Add( editor );
+
+  editor.SetProperty( TextEditor::Property::TEXT, "This is a long text for the size of the text-editor." );
+  editor.SetProperty( TextEditor::Property::POINT_SIZE, 10.f );
+  editor.SetProperty( TextEditor::Property::GRAB_HANDLE_IMAGE, HANDLE_IMAGE_FILE_NAME );
+  editor.SetProperty( DevelTextEditor::Property::SMOOTH_SCROLL, true );
+
+  editor.SetSize( 30.f, 500.f );
+  editor.SetParentOrigin( ParentOrigin::TOP_LEFT );
+  editor.SetAnchorPoint( AnchorPoint::TOP_LEFT );
+
+  // Avoid a crash when core load gl resources.
+  application.GetGlAbstraction().SetCheckFramebufferStatusResult( GL_FRAMEBUFFER_COMPLETE );
+
+  // Render and notify
+  application.SendNotification();
+  application.Render();
+
+  // Tap first to get the focus.
+  application.ProcessEvent( GenerateTap( Gesture::Possible, 1u, 1u, Vector2( 3.f, 25.0f ) ) );
+  application.ProcessEvent( GenerateTap( Gesture::Started, 1u, 1u, Vector2( 3.f, 25.0f ) ) );
+
+  // Render and notify
+  application.SendNotification();
+  application.Render();
+
+  // Tap to create the grab handle.
+  application.ProcessEvent( GenerateTap( Gesture::Possible, 1u, 1u, Vector2( 3.f, 25.0f ) ) );
+  application.ProcessEvent( GenerateTap( Gesture::Started, 1u, 1u, Vector2( 3.f, 25.0f ) ) );
+
+  // Render and notify
+  application.SendNotification();
+  application.Render();
+
+  // Get the active layer where the text's decoration is added.
+  Actor activeLayer = editor.GetChildAt( 1u );
+
+  // Get the handle's actor.
+  Actor handle = activeLayer.GetChildAt( 1u );
+  handle.SetSize( 100.f, 100.f );
+
+  // Render and notify
+  application.SendNotification();
+  application.Render();
+
+  // Touch the grab handle to set it as pressed.
+  Vector2 touchPos( 10.0f, 50.0f );
+  Dali::Integration::TouchEvent event;
+  event = Dali::Integration::TouchEvent();
+  event.AddPoint( GetPointDownInside( touchPos ) );
+  application.ProcessEvent( event );
+
+  // Render and notify
+  application.SendNotification();
+  application.Render();
+
+  // drag grab handle right
+  SendPan(application, Gesture::Possible, touchPos);
+  SendPan(application, Gesture::Started, touchPos);
+  touchPos.x += 5.0f;
+  Wait(application, 100);
+
+  for(int i = 0;i<20;i++)
+  {
+    SendPan(application, Gesture::Continuing, touchPos);
+    touchPos.x += 5.0f;
+    Wait(application);
+  }
+
+  SendPan(application, Gesture::Finished, touchPos);
+  Wait(application);
+
+  // Release the grab handle.
+  event = Dali::Integration::TouchEvent();
+  event.AddPoint( GetPointUpInside( touchPos ) );
+  application.ProcessEvent( event );
+
+  // Render and notify
+  application.SendNotification();
+  application.Render();
+
+  END_TEST;
+}
+
index f551dad..e4926c6 100644 (file)
@@ -1930,6 +1930,16 @@ int utcDaliTextFieldEvent08(void)
 
   Wait(application, 500);
 
+  // Long Press
+  application.ProcessEvent( GenerateLongPress( Gesture::Possible, 1u, Vector2( 1.f, 25.0f ) ) );
+  application.ProcessEvent( GenerateLongPress( Gesture::Started,  1u, Vector2( 1.f, 25.0f ) ) );
+
+  // Render and notify
+  application.SendNotification();
+  application.Render();
+
+  Wait(application, 500);
+
   Stage stage = Stage::GetCurrent();
   Layer layer = stage.GetRootLayer();
   Actor actor = layer.FindChildByName("optionPaste");
@@ -1950,6 +1960,7 @@ int utcDaliTextFieldEvent08(void)
     application.ProcessEvent( event );
   }
   DALI_TEST_EQUALS( field.GetProperty<std::string>( TextEditor::Property::TEXT ), std::string("testTextFieldEvent"), TEST_LOCATION );
+
   END_TEST;
 }
 
index 3e2c606..240dfde 100644 (file)
@@ -1162,6 +1162,9 @@ void TextField::OnInitialize()
   // Disable the smooth handle panning.
   mController->SetSmoothHandlePanEnabled( false );
 
+  mController->SetNoTextDoubleTapAction( Controller::NoTextTap::NO_ACTION );
+  mController->SetNoTextLongPressAction( Controller::NoTextTap::SHOW_SELECTION_POPUP );
+
   // Forward input events to controller
   EnableGestureDetection( static_cast<Gesture::Type>( Gesture::Tap | Gesture::Pan | Gesture::LongPress ) );
   GetTapGestureDetector().SetMaximumTapsRequired( 2 );
index 1fbca0d..b847996 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2016 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2017 Samsung Electronics Co., Ltd.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -43,7 +43,7 @@ struct FindWordData
   : textBuffer( textBuffer ),
     totalNumberOfCharacters( totalNumberOfCharacters ),
     hitCharacter( hitCharacter ),
-    foundIndex( 0u ),
+    foundIndex( 0 ),
     isWhiteSpace( isWhiteSpace ),
     isNewParagraph( isNewParagraph )
   {}
@@ -136,10 +136,17 @@ namespace Text
 {
 
 LineIndex GetClosestLine( VisualModelPtr visualModel,
-                          float visualY )
+                          float visualY,
+                          bool& matchedLine )
 {
   float totalHeight = 0.f;
-  LineIndex lineIndex = 0u;
+  LineIndex lineIndex = 0;
+  matchedLine = false;
+
+  if( visualY < 0.f )
+  {
+    return 0;
+  }
 
   const Vector<LineRun>& lines = visualModel->mLines;
 
@@ -156,16 +163,17 @@ LineIndex GetClosestLine( VisualModelPtr visualModel,
 
     if( visualY < totalHeight )
     {
+      matchedLine = true;
       return lineIndex;
     }
   }
 
-  if( lineIndex == 0u )
+  if( lineIndex == 0 )
   {
     return 0;
   }
 
-  return lineIndex-1;
+  return lineIndex - 1u;
 }
 
 float CalculateLineOffset( const Vector<LineRun>& lines,
@@ -192,11 +200,16 @@ CharacterIndex GetClosestCursorIndex( VisualModelPtr visualModel,
                                       LogicalModelPtr logicalModel,
                                       MetricsPtr metrics,
                                       float visualX,
-                                      float visualY )
+                                      float visualY,
+                                      CharacterHitTest::Mode mode,
+                                      bool& matchedCharacter )
 {
   DALI_LOG_INFO( gLogFilter, Debug::Verbose, "GetClosestCursorIndex, closest visualX %f visualY %f\n", visualX, visualY );
 
-  CharacterIndex logicalIndex = 0u;
+  // Whether there is a hit on a glyph.
+  matchedCharacter = false;
+
+  CharacterIndex logicalIndex = 0;
 
   const Length totalNumberOfGlyphs = visualModel->mGlyphs.Count();
   const Length totalNumberOfLines  = visualModel->mLines.Count();
@@ -206,9 +219,19 @@ CharacterIndex GetClosestCursorIndex( VisualModelPtr visualModel,
     return logicalIndex;
   }
 
+  // Whether there is a hit on a line.
+  bool matchedLine = false;
+
   // Find which line is closest.
   const LineIndex lineIndex = Text::GetClosestLine( visualModel,
-                                                    visualY );
+                                                    visualY,
+                                                    matchedLine );
+
+  if( !matchedLine && ( CharacterHitTest::TAP == mode ) )
+  {
+    // Return the first or the last character if the touch point doesn't hit a line.
+    return ( visualY < 0.f ) ? 0 : logicalModel->mText.Count();
+  }
 
   // Convert from text's coords to line's coords.
   const LineRun& line = *( visualModel->mLines.Begin() + lineIndex );
@@ -241,12 +264,12 @@ CharacterIndex GetClosestCursorIndex( VisualModelPtr visualModel,
   // The character's direction buffer.
   const CharacterDirection* const directionsBuffer = bidiLineFetched ? logicalModel->mCharacterDirections.Begin() : NULL;
 
-  // Whether there is a hit on a glyph.
-  bool matched = false;
+  // Whether the touch point if before the first glyph.
+  bool isBeforeFirstGlyph = false;
 
   // Traverses glyphs in visual order. To do that use the visual to logical conversion table.
   CharacterIndex visualIndex = startCharacter;
-  Length numberOfVisualCharacters = 0u;
+  Length numberOfVisualCharacters = 0;
   for( ; visualIndex < endCharacter; ++visualIndex )
   {
     // The character in logical order.
@@ -257,7 +280,7 @@ CharacterIndex GetClosestCursorIndex( VisualModelPtr visualModel,
     const Length numberOfGlyphs = *( glyphsPerCharacterBuffer + characterLogicalOrderIndex );
     ++numberOfVisualCharacters;
 
-    if( 0u != numberOfGlyphs )
+    if( 0 != numberOfGlyphs )
     {
       // Get the first character/glyph of the group of glyphs.
       const CharacterIndex firstVisualCharacterIndex = 1u + visualIndex - numberOfVisualCharacters;
@@ -275,6 +298,17 @@ CharacterIndex GetClosestCursorIndex( VisualModelPtr visualModel,
       // Get the position of the first glyph.
       const Vector2& position = *( positionsBuffer + firstLogicalGlyphIndex );
 
+      if( startCharacter == visualIndex )
+      {
+        const float glyphPosition = -glyphMetrics.xBearing + position.x;
+
+        if( visualX < glyphPosition )
+        {
+          isBeforeFirstGlyph = true;
+          break;
+        }
+      }
+
       // Whether the glyph can be split, like Latin ligatures fi, ff or Arabic (ل + ا).
       Length numberOfCharacters = *( charactersPerGlyphBuffer + firstLogicalGlyphIndex );
       if( direction != LTR )
@@ -284,7 +318,7 @@ CharacterIndex GetClosestCursorIndex( VisualModelPtr visualModel,
         // number of glyphs in the table is found first.
         // Jump the number of characters to the next glyph is needed.
 
-        if( 0u == numberOfCharacters )
+        if( 0 == numberOfCharacters )
         {
           // TODO: This is a workaround to fix an issue with complex characters in the arabic
           // script like i.e. رّ or الأَبْجَدِيَّة العَرَبِيَّة
@@ -298,7 +332,7 @@ CharacterIndex GetClosestCursorIndex( VisualModelPtr visualModel,
 
           // Find the number of characters.
           for( GlyphIndex index = firstLogicalGlyphIndex + 1u;
-               ( 0u == numberOfCharacters ) && ( index < totalNumberOfGlyphs ) ;
+               ( 0 == numberOfCharacters ) && ( index < totalNumberOfGlyphs );
                ++index )
           {
             numberOfCharacters = *( charactersPerGlyphBuffer + index );
@@ -322,7 +356,7 @@ CharacterIndex GetClosestCursorIndex( VisualModelPtr visualModel,
       const Length numberOfBlocks = isInterglyphIndex ? numberOfCharacters : 1u;
       const float glyphAdvance = glyphMetrics.advance / static_cast<float>( numberOfBlocks );
 
-      CharacterIndex index = 0u;
+      CharacterIndex index = 0;
       for( ; index < numberOfBlocks; ++index )
       {
         // Find the mid-point of the area containing the glyph
@@ -330,31 +364,39 @@ CharacterIndex GetClosestCursorIndex( VisualModelPtr visualModel,
 
         if( visualX < glyphCenter )
         {
-          matched = true;
+          matchedCharacter = true;
           break;
         }
       }
 
-      if( matched )
+      if( matchedCharacter )
       {
         // If the glyph is shaped from more than one character, it matches the character of the glyph.
         visualIndex = firstVisualCharacterIndex + index;
         break;
       }
 
-      numberOfVisualCharacters = 0u;
+      numberOfVisualCharacters = 0;
     }
-  }
+  } // for characters in visual order.
 
   // The number of characters of the whole text.
   const Length totalNumberOfCharacters = logicalModel->mText.Count();
 
   // Return the logical position of the cursor in characters.
 
-  if( !matched )
+  if( !matchedCharacter )
   {
-    // If no character is matched, then the last character (in visual order) of the line is used.
-    visualIndex = endCharacter;
+    if( isBeforeFirstGlyph )
+    {
+      // If no character is matched, then the first character (in visual order) of the line is used.
+      visualIndex = startCharacter;
+    }
+    else
+    {
+      // If no character is matched, then the last character (in visual order) of the line is used.
+      visualIndex = endCharacter;
+    }
   }
 
   // Get the paragraph direction.
@@ -611,7 +653,7 @@ void GetCursorPosition( VisualModelPtr visualModel,
         isCurrentRightToLeft = *( directionsBuffer + index );
       }
 
-      Length numberOfGlyphAdvance = ( isFirstPositionOfLine ? 0u : 1u ) + characterIndex - firstIndex;
+      Length numberOfGlyphAdvance = ( isFirstPositionOfLine ? 0 : 1u ) + characterIndex - firstIndex;
       if( isCurrentRightToLeft )
       {
         numberOfGlyphAdvance = primaryNumberOfCharacters - numberOfGlyphAdvance;
@@ -692,9 +734,9 @@ bool FindSelectionIndices( VisualModelPtr visualModel,
                            float visualX,
                            float visualY,
                            CharacterIndex& startIndex,
-                           CharacterIndex& endIndex )
+                           CharacterIndex& endIndex,
+                           CharacterIndex& noTextHitIndex )
 {
-
 /*
   Hit character                                           Select
 |-------------------------------------------------------|------------------------------------------|
@@ -705,23 +747,33 @@ bool FindSelectionIndices( VisualModelPtr visualModel,
 | On a new paragraph character                          | The word or group of white spaces before |
 |-------------------------------------------------------|------------------------------------------|
 */
+  const Length totalNumberOfCharacters = logicalModel->mText.Count();
+  startIndex = 0;
+  endIndex = 0;
+  noTextHitIndex = 0;
 
+  if( 0 == totalNumberOfCharacters )
+  {
+    // Nothing to do if the model is empty.
+    return false;
+  }
+
+  bool matchedCharacter = false;
   CharacterIndex hitCharacter = Text::GetClosestCursorIndex( visualModel,
                                                              logicalModel,
                                                              metrics,
                                                              visualX,
-                                                             visualY );
-
-  const Length totalNumberOfCharacters = logicalModel->mText.Count();
-
-  DALI_ASSERT_DEBUG( ( hitCharacter <= totalNumberOfCharacters ) && "GetClosestCursorIndex returned out of bounds index" );
+                                                             visualY,
+                                                             CharacterHitTest::TAP,
+                                                             matchedCharacter );
 
-  if( 0u == totalNumberOfCharacters )
+  if( !matchedCharacter )
   {
-    // Nothing to do if the model is empty.
-    return false;
+    noTextHitIndex = hitCharacter;
   }
 
+  DALI_ASSERT_DEBUG( ( hitCharacter <= totalNumberOfCharacters ) && "GetClosestCursorIndex returned out of bounds index" );
+
   if( hitCharacter >= totalNumberOfCharacters )
   {
     // Closest hit character is the last character.
@@ -757,7 +809,7 @@ bool FindSelectionIndices( VisualModelPtr visualModel,
   {
     // Find the first character before the hit one which is not a new paragraph character.
 
-    if( hitCharacter > 0u )
+    if( hitCharacter > 0 )
     {
       endIndex = hitCharacter - 1u;
       for( ; endIndex > 0; --endIndex )
@@ -790,13 +842,13 @@ bool FindSelectionIndices( VisualModelPtr visualModel,
     {
       // Select the word before or after the white space
 
-      if( 0u == hitCharacter )
+      if( 0 == hitCharacter )
       {
         data.isWhiteSpace = false;
         FindEndOfWord( data );
         endIndex = data.foundIndex;
       }
-      else if( hitCharacter > 0u )
+      else if( hitCharacter > 0 )
       {
         // Find the start of the word.
         data.hitCharacter = hitCharacter - 1u;
@@ -809,7 +861,7 @@ bool FindSelectionIndices( VisualModelPtr visualModel,
     }
   }
 
-  return true;
+  return matchedCharacter;
 }
 
 } // namespace Text
index 5896b4d..f62165b 100644 (file)
@@ -1,8 +1,8 @@
-#ifndef __DALI_TOOLKIT_TEXT_CURSOR_HELPER_FUNCTIONS_H__
-#define __DALI_TOOLKIT_TEXT_CURSOR_HELPER_FUNCTIONS_H__
+#ifndef DALI_TOOLKIT_TEXT_CURSOR_HELPER_FUNCTIONS_H
+#define DALI_TOOLKIT_TEXT_CURSOR_HELPER_FUNCTIONS_H
 
 /*
- * Copyright (c) 2016 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2017 Samsung Electronics Co., Ltd.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -32,6 +32,18 @@ namespace Toolkit
 namespace Text
 {
 
+struct CharacterHitTest
+{
+  /**
+   * @brief Enumeration of the types of hit test.
+   */
+  enum Mode
+  {
+    TAP,   ///< Retrieves the first or last character of the line if the touch point is outside of the boundaries of the text.
+    SCROLL ///< Retrieves the character above or below to the touch point if it's outside of the boundaries of the text.
+  };
+};
+
 struct CursorInfo
 {
   CursorInfo()
@@ -63,11 +75,13 @@ struct CursorInfo
  *
  * @param[in] visualModel The visual model.
  * @param[in] visualY The touch point 'y' in text's coords.
+ * @param[out] matchedLine Whether the touch point actually hits a line.
  *
  * @return A line index.
  */
 LineIndex GetClosestLine( VisualModelPtr visualModel,
-                          float visualY );
+                          float visualY,
+                          bool& matchedLine );
 
 /**
  * @brief Calculates the vertical line's offset for a given line.
@@ -85,11 +99,18 @@ float CalculateLineOffset( const Vector<LineRun>& lines,
 /**
  * @brief Retrieves the cursor's logical position for a given touch point x,y
  *
+ * There are two types of hit test: CharacterHitTest::TAP retrieves the first or
+ * last character of a line if the touch point is outside the boundaries of the
+ * text, CharacterHitTest::SCROLL retrieves the character above or below to the
+ * touch point if it's outside the boundaries of the text.
+ *
  * @param[in] visualModel The visual model.
  * @param[in] logicalModel The logical model.
  * @param[in] metrics A wrapper around FontClient used to get metrics.
  * @param[in] visualX The touch point 'x' in text's coords.
  * @param[in] visualY The touch point 'y' in text's coords.
+ * @param[in] mode The type of hit test.
+ * @param[out] matchedCharacter Whether the touch point actually hits a character.
  *
  * @return The logical cursor position (in characters). 0 is just before the first character, a value equal to the number of characters is just after the last character.
  */
@@ -97,8 +118,9 @@ CharacterIndex GetClosestCursorIndex( VisualModelPtr visualModel,
                                       LogicalModelPtr logicalModel,
                                       MetricsPtr metrics,
                                       float visualX,
-                                      float visualY );
-
+                                      float visualY,
+                                      CharacterHitTest::Mode mode,
+                                      bool& matchedCharacter );
 
 /**
  * @brief Calculates the cursor's position for a given character index in the logical order.
@@ -128,8 +150,9 @@ void GetCursorPosition( VisualModelPtr visualModel,
  * @param[in] visualY The touch point 'y' in text's coords.
  * @param[out] startIndex Index to the first character of the selected word.
  * @param[out] endIndex Index to the last character of the selected word.
+ * @param[out] noTextHitIndex Index to the nearest character when there is no hit.
  *
- * @return @e true if the indices are found.
+ * @return @e true if the touch point hits a character.
  */
 bool FindSelectionIndices( VisualModelPtr visualModel,
                            LogicalModelPtr logicalModel,
@@ -137,7 +160,8 @@ bool FindSelectionIndices( VisualModelPtr visualModel,
                            float visualX,
                            float visualY,
                            CharacterIndex& startIndex,
-                           CharacterIndex& endIndex );
+                           CharacterIndex& endIndex,
+                           CharacterIndex& noTextHitIndex );
 
 } // namespace Text
 
@@ -145,4 +169,4 @@ bool FindSelectionIndices( VisualModelPtr visualModel,
 
 } // namespace Dali
 
-#endif // __DALI_TOOLKIT_TEXT_CURSOR_HELPER_FUNCTIONS_H__
+#endif // DALI_TOOLKIT_TEXT_CURSOR_HELPER_FUNCTIONS_H
index da7d929..05f3536 100644 (file)
@@ -84,6 +84,8 @@ EventData::EventData( DecoratorPtr decorator )
   mPreEditStartPosition( 0u ),
   mPreEditLength( 0u ),
   mCursorHookPositionX( 0.f ),
+  mDoubleTapAction( Controller::NoTextTap::NO_ACTION ),
+  mLongPressAction( Controller::NoTextTap::SHOW_SELECTION_POPUP ),
   mIsShowingPlaceholderText( false ),
   mPreEditFlag( false ),
   mDecoratorUpdated( false ),
@@ -97,6 +99,7 @@ EventData::EventData( DecoratorPtr decorator )
   mUpdateLeftSelectionPosition( false ),
   mUpdateRightSelectionPosition( false ),
   mIsLeftHandleSelected( false ),
+  mIsRightHandleSelected( false ),
   mUpdateHighlightBox( false ),
   mScrollAfterUpdatePosition( false ),
   mScrollAfterDelete( false ),
@@ -227,10 +230,25 @@ bool Controller::Impl::ProcessInputEvents()
 
       if( mEventData->mScrollAfterUpdatePosition && ( mEventData->mIsLeftHandleSelected ? mEventData->mUpdateLeftSelectionPosition : mEventData->mUpdateRightSelectionPosition ) )
       {
-        CursorInfo& info = mEventData->mIsLeftHandleSelected ? leftHandleInfo : rightHandleInfo;
+        if( mEventData->mIsLeftHandleSelected && mEventData->mIsRightHandleSelected )
+        {
+          CursorInfo& infoLeft = leftHandleInfo;
+
+          const Vector2 currentCursorPositionLeft( infoLeft.primaryPosition.x, infoLeft.lineOffset );
+          ScrollToMakePositionVisible( currentCursorPositionLeft, infoLeft.lineHeight );
+
+          CursorInfo& infoRight = rightHandleInfo;
 
-        const Vector2 currentCursorPosition( info.primaryPosition.x, info.lineOffset );
-        ScrollToMakePositionVisible( currentCursorPosition, info.lineHeight );
+          const Vector2 currentCursorPositionRight( infoRight.primaryPosition.x, infoRight.lineOffset );
+          ScrollToMakePositionVisible( currentCursorPositionRight, infoRight.lineHeight );
+        }
+        else
+        {
+          CursorInfo& info = mEventData->mIsLeftHandleSelected ? leftHandleInfo : rightHandleInfo;
+
+          const Vector2 currentCursorPosition( info.primaryPosition.x, info.lineOffset );
+          ScrollToMakePositionVisible( currentCursorPosition, info.lineHeight );
+        }
       }
     }
 
@@ -261,6 +279,8 @@ bool Controller::Impl::ProcessInputEvents()
       mEventData->mUpdateLeftSelectionPosition = false;
       mEventData->mUpdateRightSelectionPosition = false;
       mEventData->mUpdateHighlightBox = false;
+      mEventData->mIsLeftHandleSelected = false;
+      mEventData->mIsRightHandleSelected = false;
     }
 
     mEventData->mScrollAfterUpdatePosition = false;
@@ -1165,13 +1185,16 @@ void Controller::Impl::OnCursorKeyEvent( const Event& event )
     const float hitPointY = cursorInfo.lineOffset - 0.5f * ( line.ascender - line.descender );
 
     // Use the cursor hook position 'x' and the next hit 'y' position to calculate the new cursor index.
+    bool matchedCharacter = false;
     mEventData->mPrimaryCursorPosition = Text::GetClosestCursorIndex( mModel->mVisualModel,
                                                                       mModel->mLogicalModel,
                                                                       mMetrics,
                                                                       mEventData->mCursorHookPositionX,
-                                                                      hitPointY );
+                                                                      hitPointY,
+                                                                      CharacterHitTest::TAP,
+                                                                      matchedCharacter );
   }
-  else if(   Dali::DALI_KEY_CURSOR_DOWN == keyCode )
+  else if( Dali::DALI_KEY_CURSOR_DOWN == keyCode )
   {
     // Get first the line index of the current cursor position index.
     CharacterIndex characterIndex = 0u;
@@ -1197,11 +1220,14 @@ void Controller::Impl::OnCursorKeyEvent( const Event& event )
       const float hitPointY = cursorInfo.lineOffset + cursorInfo.lineHeight + 0.5f * ( line.ascender - line.descender );
 
       // Use the cursor hook position 'x' and the next hit 'y' position to calculate the new cursor index.
+      bool matchedCharacter = false;
       mEventData->mPrimaryCursorPosition = Text::GetClosestCursorIndex( mModel->mVisualModel,
                                                                         mModel->mLogicalModel,
                                                                         mMetrics,
                                                                         mEventData->mCursorHookPositionX,
-                                                                        hitPointY );
+                                                                        hitPointY,
+                                                                        CharacterHitTest::TAP,
+                                                                        matchedCharacter );
     }
   }
 
@@ -1227,11 +1253,15 @@ void Controller::Impl::OnTapEvent( const Event& event )
         // Keep the tap 'x' position. Used to move the cursor.
         mEventData->mCursorHookPositionX = xPosition;
 
+        // Whether to touch point hits on a glyph.
+        bool matchedCharacter = false;
         mEventData->mPrimaryCursorPosition = Text::GetClosestCursorIndex( mModel->mVisualModel,
                                                                           mModel->mLogicalModel,
                                                                           mMetrics,
                                                                           xPosition,
-                                                                          yPosition );
+                                                                          yPosition,
+                                                                          CharacterHitTest::TAP,
+                                                                          matchedCharacter );
 
         // When the cursor position is changing, delay cursor blinking
         mEventData->mDecorator->DelayCursorBlink();
@@ -1253,6 +1283,20 @@ void Controller::Impl::OnTapEvent( const Event& event )
         mEventData->mImfManager.NotifyCursorPosition();
       }
     }
+    else if( 2u == tapCount )
+    {
+      if( mEventData->mSelectionEnabled )
+      {
+        // Convert from control's coords to text's coords.
+        const float xPosition = event.p2.mFloat - mModel->mScrollPosition.x;
+        const float yPosition = event.p3.mFloat - mModel->mScrollPosition.y;
+
+        // Calculates the logical position from the x,y coords.
+        RepositionSelectionHandles( xPosition,
+                                    yPosition,
+                                    mEventData->mDoubleTapAction );
+      }
+    }
   }
 }
 
@@ -1323,12 +1367,26 @@ void Controller::Impl::OnLongPressEvent( const Event& event )
 {
   DALI_LOG_INFO( gLogFilter, Debug::General, "Controller::OnLongPressEvent\n" );
 
-  if( EventData::EDITING == mEventData->mState )
+  if( !IsShowingRealText() && ( EventData::EDITING == mEventData->mState ) )
   {
-    ChangeState ( EventData::EDITING_WITH_POPUP );
+    ChangeState( EventData::EDITING_WITH_POPUP );
     mEventData->mDecoratorUpdated = true;
     mEventData->mUpdateInputStyle = true;
   }
+  else
+  {
+    if( mEventData->mSelectionEnabled )
+    {
+      // Convert from control's coords to text's coords.
+      const float xPosition = event.p2.mFloat - mModel->mScrollPosition.x;
+      const float yPosition = event.p3.mFloat - mModel->mScrollPosition.y;
+
+      // Calculates the logical position from the x,y coords.
+      RepositionSelectionHandles( xPosition,
+                                  yPosition,
+                                  mEventData->mLongPressAction );
+    }
+  }
 }
 
 void Controller::Impl::OnHandleEvent( const Event& event )
@@ -1350,11 +1408,14 @@ void Controller::Impl::OnHandleEvent( const Event& event )
     const float yPosition = event.p3.mFloat - mModel->mScrollPosition.y;
 
     // Need to calculate the handle's new position.
+    bool matchedCharacter = false;
     const CharacterIndex handleNewPosition = Text::GetClosestCursorIndex( mModel->mVisualModel,
                                                                           mModel->mLogicalModel,
                                                                           mMetrics,
                                                                           xPosition,
-                                                                          yPosition );
+                                                                          yPosition,
+                                                                          CharacterHitTest::SCROLL,
+                                                                          matchedCharacter );
 
     if( Event::GRAB_HANDLE_EVENT == event.type )
     {
@@ -1391,6 +1452,7 @@ void Controller::Impl::OnHandleEvent( const Event& event )
 
       // Will define the order to scroll the text to match the handle position.
       mEventData->mIsLeftHandleSelected = true;
+      mEventData->mIsRightHandleSelected = false;
     }
     else if( Event::RIGHT_SELECTION_HANDLE_EVENT == event.type )
     {
@@ -1411,6 +1473,7 @@ void Controller::Impl::OnHandleEvent( const Event& event )
 
       // Will define the order to scroll the text to match the handle position.
       mEventData->mIsLeftHandleSelected = false;
+      mEventData->mIsRightHandleSelected = true;
     }
   } // end ( HANDLE_PRESSED == state )
   else if( ( HANDLE_RELEASED == state ) ||
@@ -1423,11 +1486,14 @@ void Controller::Impl::OnHandleEvent( const Event& event )
       const float xPosition = event.p2.mFloat - mModel->mScrollPosition.x;
       const float yPosition = event.p3.mFloat - mModel->mScrollPosition.y;
 
+      bool matchedCharacter = false;
       handlePosition = Text::GetClosestCursorIndex( mModel->mVisualModel,
                                                     mModel->mLogicalModel,
                                                     mMetrics,
                                                     xPosition,
-                                                    yPosition );
+                                                    yPosition,
+                                                    CharacterHitTest::SCROLL,
+                                                    matchedCharacter );
     }
 
     if( Event::GRAB_HANDLE_EVENT == event.type )
@@ -1539,11 +1605,14 @@ void Controller::Impl::OnHandleEvent( const Event& event )
 
       // Get the new handle position.
       // The grab handle's position is in decorator's coords. Need to transforms to text's coords.
+      bool matchedCharacter = false;
       const CharacterIndex handlePosition = Text::GetClosestCursorIndex( mModel->mVisualModel,
                                                                          mModel->mLogicalModel,
                                                                          mMetrics,
                                                                          position.x - mModel->mScrollPosition.x,
-                                                                         position.y - mModel->mScrollPosition.y );
+                                                                         position.y - mModel->mScrollPosition.y,
+                                                                         CharacterHitTest::SCROLL,
+                                                                         matchedCharacter );
 
       if( mEventData->mPrimaryCursorPosition != handlePosition )
       {
@@ -1580,11 +1649,14 @@ void Controller::Impl::OnHandleEvent( const Event& event )
 
       // Get the new handle position.
       // The selection handle's position is in decorator's coords. Need to transform to text's coords.
+      bool matchedCharacter = false;
       const CharacterIndex handlePosition = Text::GetClosestCursorIndex( mModel->mVisualModel,
                                                                          mModel->mLogicalModel,
                                                                          mMetrics,
                                                                          position.x - mModel->mScrollPosition.x,
-                                                                         position.y - mModel->mScrollPosition.y );
+                                                                         position.y - mModel->mScrollPosition.y,
+                                                                         CharacterHitTest::SCROLL,
+                                                                         matchedCharacter );
 
       if( leftSelectionHandleEvent )
       {
@@ -1637,7 +1709,8 @@ void Controller::Impl::OnSelectEvent( const Event& event )
 
     // Calculates the logical position from the x,y coords.
     RepositionSelectionHandles( xPosition,
-                                yPosition );
+                                yPosition,
+                                Controller::NoTextTap::HIGHLIGHT );
   }
 }
 
@@ -2168,7 +2241,7 @@ void Controller::Impl::RepositionSelectionHandles()
   mEventData->mDecoratorUpdated = true;
 }
 
-void Controller::Impl::RepositionSelectionHandles( float visualX, float visualY )
+void Controller::Impl::RepositionSelectionHandles( float visualX, float visualY, Controller::NoTextTap::Action action )
 {
   if( NULL == mEventData )
   {
@@ -2194,16 +2267,18 @@ void Controller::Impl::RepositionSelectionHandles( float visualX, float visualY
   // Find which word was selected
   CharacterIndex selectionStart( 0 );
   CharacterIndex selectionEnd( 0 );
-  const bool indicesFound = FindSelectionIndices( mModel->mVisualModel,
+  CharacterIndex noTextHitIndex( 0 );
+  const bool characterHit = FindSelectionIndices( mModel->mVisualModel,
                                                   mModel->mLogicalModel,
                                                   mMetrics,
                                                   visualX,
                                                   visualY,
                                                   selectionStart,
-                                                  selectionEnd );
+                                                  selectionEnd,
+                                                  noTextHitIndex );
   DALI_LOG_INFO( gLogFilter, Debug::Verbose, "%p selectionStart %d selectionEnd %d\n", this, selectionStart, selectionEnd );
 
-  if( indicesFound )
+  if( characterHit || ( Controller::NoTextTap::HIGHLIGHT == action ) )
   {
     ChangeState( EventData::SELECTING );
 
@@ -2222,12 +2297,22 @@ void Controller::Impl::RepositionSelectionHandles( float visualX, float visualY
 
     mEventData->mScrollAfterUpdatePosition = ( mEventData->mLeftSelectionPosition != mEventData->mRightSelectionPosition );
   }
-  else
+  else if( Controller::NoTextTap::SHOW_SELECTION_POPUP == action )
   {
     // Nothing to select. i.e. a white space, out of bounds
-    ChangeState( EventData::EDITING );
+    ChangeState( EventData::EDITING_WITH_POPUP );
 
-    mEventData->mPrimaryCursorPosition = selectionEnd;
+    mEventData->mPrimaryCursorPosition = noTextHitIndex;
+
+    mEventData->mUpdateCursorPosition = true;
+    mEventData->mUpdateGrabHandlePosition = true;
+    mEventData->mScrollAfterUpdatePosition = true;
+    mEventData->mUpdateInputStyle = true;
+  }
+  else if( Controller::NoTextTap::NO_ACTION == action )
+  {
+    // Nothing to select. i.e. a white space, out of bounds
+    mEventData->mPrimaryCursorPosition = noTextHitIndex;
 
     mEventData->mUpdateCursorPosition = true;
     mEventData->mUpdateGrabHandlePosition = true;
index 0eb8451..37e5e5f 100644 (file)
@@ -2,7 +2,7 @@
 #define DALI_TOOLKIT_TEXT_CONTROLLER_IMPL_H
 
 /*
- * Copyright (c) 2016 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2017 Samsung Electronics Co., Ltd.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -130,6 +130,9 @@ struct EventData
 
   float              mCursorHookPositionX;     ///< Used to move the cursor with the keys or when scrolling the text vertically with the handles.
 
+  Controller::NoTextTap::Action mDoubleTapAction; ///< Action to be done when there is a double tap on top of 'no text'
+  Controller::NoTextTap::Action mLongPressAction; ///< Action to be done when there is a long press on top of 'no text'
+
   bool mIsShowingPlaceholderText        : 1;   ///< True if the place-holder text is being displayed.
   bool mPreEditFlag                     : 1;   ///< True if the model contains text in pre-edit state.
   bool mDecoratorUpdated                : 1;   ///< True if the decorator was updated during event processing.
@@ -143,6 +146,7 @@ struct EventData
   bool mUpdateLeftSelectionPosition     : 1;   ///< True if the visual position of the left selection handle must be recalculated.
   bool mUpdateRightSelectionPosition    : 1;   ///< True if the visual position of the right selection handle must be recalculated.
   bool mIsLeftHandleSelected            : 1;   ///< Whether is the left handle the one which is selected.
+  bool mIsRightHandleSelected           : 1;   ///< Whether is the right handle the one which is selected.
   bool mUpdateHighlightBox              : 1;   ///< True if the text selection high light box must be updated.
   bool mScrollAfterUpdatePosition       : 1;   ///< Whether to scroll after the cursor position is updated.
   bool mScrollAfterDelete               : 1;   ///< Whether to scroll after delete characters.
@@ -594,7 +598,7 @@ struct Controller::Impl
   void RequestGetTextFromClipboard();
 
   void RepositionSelectionHandles();
-  void RepositionSelectionHandles( float visualX, float visualY );
+  void RepositionSelectionHandles( float visualX, float visualY, Controller::NoTextTap::Action action );
 
   void SetPopupButtons();
 
index 4eb5662..55f40e9 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2016 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2017 Samsung Electronics Co., Ltd.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -1330,6 +1330,46 @@ bool Controller::IsInputModePassword()
   return false;
 }
 
+void Controller::SetNoTextDoubleTapAction( NoTextTap::Action action )
+{
+  if( NULL != mImpl->mEventData )
+  {
+    mImpl->mEventData->mDoubleTapAction = action;
+  }
+}
+
+Controller::NoTextTap::Action Controller::GetNoTextDoubleTapAction() const
+{
+  NoTextTap::Action action = NoTextTap::NO_ACTION;
+
+  if( NULL != mImpl->mEventData )
+  {
+    action = mImpl->mEventData->mDoubleTapAction;
+  }
+
+  return action;
+}
+
+void Controller::SetNoTextLongPressAction( NoTextTap::Action action )
+{
+  if( NULL != mImpl->mEventData )
+  {
+    mImpl->mEventData->mLongPressAction = action;
+  }
+}
+
+Controller::NoTextTap::Action Controller::GetNoTextLongPressAction() const
+{
+  NoTextTap::Action action = NoTextTap::NO_ACTION;
+
+  if( NULL != mImpl->mEventData )
+  {
+    action = mImpl->mEventData->mLongPressAction;
+  }
+
+  return action;
+}
+
 // public : Queries & retrieves.
 
 Layout::Engine& Controller::GetLayoutEngine()
@@ -1873,9 +1913,12 @@ void Controller::TapEvent( unsigned int tapCount, float x, float y )
       if( mImpl->mEventData->mSelectionEnabled &&
           mImpl->IsShowingRealText() )
       {
-        SelectEvent( x, y, false );
+        relayoutNeeded = true;
+        mImpl->mEventData->mIsLeftHandleSelected = true;
+        mImpl->mEventData->mIsRightHandleSelected = true;
       }
     }
+
     // Handles & cursors must be repositioned after Relayout() i.e. after the Model has been updated
     if( relayoutNeeded )
     {
@@ -1916,35 +1959,42 @@ void Controller::LongPressEvent( Gesture::State state, float x, float y  )
   if( ( state == Gesture::Started ) &&
       ( NULL != mImpl->mEventData ) )
   {
-    if( !mImpl->IsShowingRealText() )
+    // The 1st long-press on inactive text-field is treated as tap
+    if( EventData::INACTIVE == mImpl->mEventData->mState )
+    {
+      mImpl->ChangeState( EventData::EDITING );
+
+      Event event( Event::TAP_EVENT );
+      event.p1.mUint = 1;
+      event.p2.mFloat = x;
+      event.p3.mFloat = y;
+      mImpl->mEventData->mEventQueue.push_back( event );
+
+      mImpl->RequestRelayout();
+    }
+    else if( !mImpl->IsShowingRealText() )
     {
       Event event( Event::LONG_PRESS_EVENT );
       event.p1.mInt = state;
+      event.p2.mFloat = x;
+      event.p3.mFloat = y;
       mImpl->mEventData->mEventQueue.push_back( event );
       mImpl->RequestRelayout();
     }
-    else
+    else if( !mImpl->IsClipboardVisible() )
     {
-      // The 1st long-press on inactive text-field is treated as tap
-      if( EventData::INACTIVE == mImpl->mEventData->mState )
-      {
-        mImpl->ChangeState( EventData::EDITING );
+      // Reset the imf manager to commit the pre-edit before selecting the text.
+      mImpl->ResetImfManager();
 
-        Event event( Event::TAP_EVENT );
-        event.p1.mUint = 1;
-        event.p2.mFloat = x;
-        event.p3.mFloat = y;
-        mImpl->mEventData->mEventQueue.push_back( event );
-
-        mImpl->RequestRelayout();
-      }
-      else
-      {
-        // Reset the imf manger to commit the pre-edit before selecting the text.
-        mImpl->ResetImfManager();
+      Event event( Event::LONG_PRESS_EVENT );
+      event.p1.mInt = state;
+      event.p2.mFloat = x;
+      event.p3.mFloat = y;
+      mImpl->mEventData->mEventQueue.push_back( event );
+      mImpl->RequestRelayout();
 
-        SelectEvent( x, y, false );
-      }
+      mImpl->mEventData->mIsLeftHandleSelected = true;
+      mImpl->mEventData->mIsRightHandleSelected = true;
     }
   }
 }
@@ -2912,6 +2962,8 @@ void Controller::SelectEvent( float x, float y, bool selectAll )
     }
 
     mImpl->mEventData->mCheckScrollAmount = true;
+    mImpl->mEventData->mIsLeftHandleSelected = true;
+    mImpl->mEventData->mIsRightHandleSelected = true;
     mImpl->RequestRelayout();
   }
 }
index 9612b1a..5005d8e 100644 (file)
@@ -2,7 +2,7 @@
 #define DALI_TOOLKIT_TEXT_CONTROLLER_H
 
 /*
- * Copyright (c) 2016 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2017 Samsung Electronics Co., Ltd.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -120,6 +120,16 @@ public: // Enumerated types.
     PLACEHOLDER_TYPE_INACTIVE,
   };
 
+  struct NoTextTap
+  {
+    enum Action
+    {
+      NO_ACTION,           ///< Does no action if there is a tap on top of an area with no text.
+      HIGHLIGHT,           ///< Highlights the nearest text (at the beginning or end of the text) and shows the text's selection popup.
+      SHOW_SELECTION_POPUP ///< Shows the text's selection popup.
+    };
+  };
+
 public: // Constructor.
 
   /**
@@ -350,6 +360,34 @@ public: // Configure the text controller.
    */
   bool IsInputModePassword();
 
+  /**
+   * @brief Sets the action when there is a double tap event on top of a text area with no text.
+   *
+   * @param[in] action The action to do.
+   */
+  void SetNoTextDoubleTapAction( NoTextTap::Action action );
+
+  /**
+   * @brief Retrieves the action when there is a double tap event on top of a text area with no text.
+   *
+   * @return The action to do.
+   */
+  NoTextTap::Action GetNoTextDoubleTapAction() const;
+
+  /**
+   * @briefSets the action when there is a long press event on top of a text area with no text.
+   *
+   * @param[in] action The action to do.
+   */
+  void SetNoTextLongPressAction( NoTextTap::Action action );
+
+  /**
+   * @brief Retrieves the action when there is a long press event on top of a text area with no text.
+   *
+   * @return The action to do.
+   */
+  NoTextTap::Action GetNoTextLongPressAction() const;
+
 public: // Update.
 
   /**