Support anchor to TextLabel, TextField, TextEditor 97/253197/27
authorBowon Ryu <bowon.ryu@samsung.com>
Fri, 5 Feb 2021 10:42:37 +0000 (19:42 +0900)
committerBowon Ryu <bowon.ryu@samsung.com>
Wed, 7 Apr 2021 08:44:46 +0000 (17:44 +0900)
example: <a href='https://wwww.tizen.org'>TIZEN</a>

using AnchorClickedSignal(),
users can get the anchor's href when clicking on the anchor.

Change-Id: I1c61c285c95295aea3c27de2bedd586211a0f036
Signed-off-by: Bowon Ryu <bowon.ryu@samsung.com>
35 files changed:
automated-tests/src/dali-toolkit-internal/dali-toolkit-test-utils/toolkit-text-utils.cpp
automated-tests/src/dali-toolkit-internal/utc-Dali-Text-Markup.cpp
automated-tests/src/dali-toolkit/utc-Dali-TextEditor.cpp
automated-tests/src/dali-toolkit/utc-Dali-TextField.cpp
automated-tests/src/dali-toolkit/utc-Dali-TextLabel.cpp
dali-toolkit/devel-api/controls/text-controls/text-editor-devel.cpp
dali-toolkit/devel-api/controls/text-controls/text-editor-devel.h
dali-toolkit/devel-api/controls/text-controls/text-field-devel.cpp
dali-toolkit/devel-api/controls/text-controls/text-field-devel.h
dali-toolkit/devel-api/controls/text-controls/text-label-devel.cpp [new file with mode: 0644]
dali-toolkit/devel-api/controls/text-controls/text-label-devel.h
dali-toolkit/devel-api/file.list
dali-toolkit/devel-api/text/text-utils-devel.cpp
dali-toolkit/internal/controls/text-controls/text-editor-impl.cpp
dali-toolkit/internal/controls/text-controls/text-editor-impl.h
dali-toolkit/internal/controls/text-controls/text-field-impl.cpp
dali-toolkit/internal/controls/text-controls/text-field-impl.h
dali-toolkit/internal/controls/text-controls/text-label-impl.cpp
dali-toolkit/internal/controls/text-controls/text-label-impl.h
dali-toolkit/internal/file.list
dali-toolkit/internal/text/anchor.h [new file with mode: 0644]
dali-toolkit/internal/text/logical-model-impl.cpp
dali-toolkit/internal/text/logical-model-impl.h
dali-toolkit/internal/text/markup-processor-anchor.cpp [new file with mode: 0644]
dali-toolkit/internal/text/markup-processor-anchor.h [new file with mode: 0644]
dali-toolkit/internal/text/markup-processor.cpp
dali-toolkit/internal/text/markup-processor.h
dali-toolkit/internal/text/text-anchor-control-interface.h [new file with mode: 0644]
dali-toolkit/internal/text/text-controller-event-handler.cpp
dali-toolkit/internal/text/text-controller-event-handler.h
dali-toolkit/internal/text/text-controller-impl.h
dali-toolkit/internal/text/text-controller-text-updater.cpp
dali-toolkit/internal/text/text-controller-text-updater.h
dali-toolkit/internal/text/text-controller.cpp
dali-toolkit/internal/text/text-controller.h

index b7a1122..d98f397 100755 (executable)
@@ -108,7 +108,8 @@ void CreateTextModel( const std::string& text,
 
   MarkupProcessData markupProcessData( logicalModel->mColorRuns,
                                        logicalModel->mFontDescriptionRuns,
-                                       logicalModel->mEmbeddedItems );
+                                       logicalModel->mEmbeddedItems,
+                                       logicalModel->mAnchors );
 
   Length textSize = 0u;
   const uint8_t* utf8 = NULL;
index 779eb56..6e2d632 100755 (executable)
@@ -184,7 +184,8 @@ namespace
     Vector<ColorRun> colorRuns;
     Vector<FontDescriptionRun> fontRuns;
     Vector<EmbeddedItem> items;
-    MarkupProcessData markupProcessData( colorRuns, fontRuns, items );
+    Vector<Anchor> anchors;
+    MarkupProcessData markupProcessData( colorRuns, fontRuns, items, anchors );
     ProcessMarkupString( data.xHTMLEntityString, markupProcessData );
 
     for( Vector<EmbeddedItem>::Iterator it = items.Begin(),
index af5a453..4e26365 100644 (file)
@@ -130,6 +130,8 @@ const char* HANDLE_RIGHT_SELECTION_FILE_NAME = TEST_RESOURCE_DIR "/selection_han
 
 const std::string DEFAULT_DEVICE_NAME("hwKeyboard");
 
+static bool gAnchorClickedCallBackCalled;
+static bool gAnchorClickedCallBackNotCalled;
 static bool gTextChangedCallBackCalled;
 static bool gInputStyleChangedCallbackCalled;
 static bool gMaxCharactersCallBackCalled;
@@ -149,6 +151,18 @@ struct CallbackFunctor
   bool* mCallbackFlag;
 };
 
+static void TestAnchorClickedCallback(TextEditor control, const char* href, unsigned int hrefLength)
+{
+  tet_infoline(" TestAnchorClickedCallback");
+
+  gAnchorClickedCallBackNotCalled = false;
+
+  if (!strcmp(href, "https://www.tizen.org") && hrefLength == strlen(href))
+  {
+    gAnchorClickedCallBackCalled = true;
+  }
+}
+
 static void TestTextChangedCallback( TextEditor control )
 {
   tet_infoline(" TestTextChangedCallback");
@@ -952,6 +966,52 @@ int utcDaliTextEditorAtlasRenderP(void)
   END_TEST;
 }
 
+// Positive test for the anchorClicked signal.
+int utcDaliTextEditorAnchorClickedP(void)
+{
+  ToolkitTestApplication application;
+  tet_infoline(" utcDaliTextEditorAnchorClickedP");
+  TextEditor editor = TextEditor::New();
+  DALI_TEST_CHECK(editor);
+
+  application.GetScene().Add(editor);
+
+  // connect to the anchor clicked signal.
+  ConnectionTracker* testTracker = new ConnectionTracker();
+  DevelTextEditor::AnchorClickedSignal(editor).Connect(&TestAnchorClickedCallback);
+  bool anchorClickedSignal = false;
+  editor.ConnectSignal(testTracker, "anchorClicked", CallbackFunctor(&anchorClickedSignal));
+
+  gAnchorClickedCallBackCalled = false;
+  editor.SetProperty(TextEditor::Property::TEXT, "<a href='https://www.tizen.org'>TIZEN</a>");
+  editor.SetProperty(TextEditor::Property::ENABLE_MARKUP, true);
+  editor.SetProperty(Actor::Property::SIZE, Vector2(100.f, 50.f));
+  editor.SetProperty(Actor::Property::PARENT_ORIGIN, ParentOrigin::TOP_LEFT);
+  editor.SetProperty(Actor::Property::ANCHOR_POINT, AnchorPoint::TOP_LEFT);
+
+  application.SendNotification();
+  application.Render();
+  editor.SetKeyInputFocus();
+
+  // Create a tap event to touch the text editor.
+  TestGenerateTap(application, 5.0f, 5.0f);
+  application.SendNotification();
+  application.Render();
+
+  DALI_TEST_CHECK(gAnchorClickedCallBackCalled);
+  DALI_TEST_CHECK(anchorClickedSignal);
+
+  gAnchorClickedCallBackNotCalled = true;
+  // Tap the outside of anchor, callback should not be called.
+  TestGenerateTap(application, 150.f, 100.f);
+  application.SendNotification();
+  application.Render();
+
+  DALI_TEST_CHECK(gAnchorClickedCallBackNotCalled);
+
+  END_TEST;
+}
+
 // Positive test for the textChanged signal.
 int utcDaliTextEditorTextChangedP(void)
 {
index 05d0009..aa25cf2 100644 (file)
@@ -121,6 +121,8 @@ const int KEY_D_CODE = 40;
 
 const std::string DEFAULT_DEVICE_NAME("hwKeyboard");
 
+static bool gAnchorClickedCallBackCalled;
+static bool gAnchorClickedCallBackNotCalled;
 static bool gTextChangedCallBackCalled;
 static bool gMaxCharactersCallBackCalled;
 static bool gInputStyleChangedCallbackCalled;
@@ -206,6 +208,18 @@ struct CallbackFunctor
   bool* mCallbackFlag;
 };
 
+static void TestAnchorClickedCallback(TextField control, const char* href, unsigned int hrefLength)
+{
+  tet_infoline(" TestAnchorClickedCallback");
+
+  gAnchorClickedCallBackNotCalled = false;
+
+  if (!strcmp(href, "https://www.tizen.org") && hrefLength == strlen(href))
+  {
+    gAnchorClickedCallBackCalled = true;
+  }
+}
+
 static void TestTextChangedCallback( TextField control )
 {
   tet_infoline(" TestTextChangedCallback");
@@ -1022,6 +1036,303 @@ int utcDaliTextFieldAtlasRenderP(void)
   END_TEST;
 }
 
+// Positive test for the anchorClicked signal.
+int utcDaliTextFieldAnchorClicked01(void)
+{
+  ToolkitTestApplication application;
+  tet_infoline(" utcDaliTextFieldAnchorClicked01");
+  TextField field = TextField::New();
+  DALI_TEST_CHECK(field);
+
+  application.GetScene().Add(field);
+
+  // connect to the anchor clicked signal.
+  ConnectionTracker* testTracker = new ConnectionTracker();
+  DevelTextField::AnchorClickedSignal(field).Connect(&TestAnchorClickedCallback);
+  bool anchorClickedSignal = false;
+  field.ConnectSignal(testTracker, "anchorClicked", CallbackFunctor(&anchorClickedSignal));
+
+  gAnchorClickedCallBackCalled = false;
+  field.SetProperty(TextField::Property::TEXT, "<a href='https://www.tizen.org'>TIZEN</a>");
+  field.SetProperty(TextField::Property::ENABLE_MARKUP, true);
+  field.SetProperty(Actor::Property::SIZE, Vector2(100.f, 50.f));
+  field.SetProperty(Actor::Property::PARENT_ORIGIN, ParentOrigin::TOP_LEFT);
+  field.SetProperty(Actor::Property::ANCHOR_POINT, AnchorPoint::TOP_LEFT);
+
+  application.SendNotification();
+  application.Render();
+  field.SetKeyInputFocus();
+
+  // Create a tap event to touch the text field.
+  TestGenerateTap(application, 5.0f, 25.0f);
+  application.SendNotification();
+  application.Render();
+
+  DALI_TEST_CHECK(gAnchorClickedCallBackCalled);
+  DALI_TEST_CHECK(anchorClickedSignal);
+
+  gAnchorClickedCallBackNotCalled = true;
+  // Tap the outside of anchor, callback should not be called.
+  TestGenerateTap(application, 150.f, 100.f);
+  application.SendNotification();
+  application.Render();
+
+  DALI_TEST_CHECK(gAnchorClickedCallBackNotCalled);
+
+  END_TEST;
+}
+
+// Positive test for the anchorClicked signal.
+int utcDaliTextFieldAnchorClicked02(void)
+{
+  ToolkitTestApplication application;
+  tet_infoline(" utcDaliTextFieldAnchorClicked02");
+  TextField field = TextField::New();
+  DALI_TEST_CHECK(field);
+
+  application.GetScene().Add(field);
+
+  // connect to the anchor clicked signal.
+  ConnectionTracker* testTracker = new ConnectionTracker();
+  DevelTextField::AnchorClickedSignal(field).Connect(&TestAnchorClickedCallback);
+  bool anchorClickedSignal = false;
+  field.ConnectSignal(testTracker, "anchorClicked", CallbackFunctor(&anchorClickedSignal));
+
+  gAnchorClickedCallBackCalled = false;
+  field.SetProperty(TextField::Property::TEXT, "<a href='https://www.tizen.org'>TIZEN</a>");
+  field.SetProperty(TextField::Property::ENABLE_MARKUP, true);
+  field.SetProperty(Actor::Property::SIZE, Vector2(100.f, 50.f));
+  field.SetProperty(Actor::Property::PARENT_ORIGIN, ParentOrigin::TOP_LEFT);
+  field.SetProperty(Actor::Property::ANCHOR_POINT, AnchorPoint::TOP_LEFT);
+
+  application.SendNotification();
+  application.Render();
+  field.SetKeyInputFocus();
+
+  // Avoid a crash when core load gl resources.
+  application.GetGlAbstraction().SetCheckFramebufferStatusResult(GL_FRAMEBUFFER_COMPLETE);
+
+  // Create a tap event to touch the text field.
+  TestGenerateTap(application, 30.0f, 25.0f);
+  application.SendNotification();
+  application.Render();
+
+  DALI_TEST_CHECK(gAnchorClickedCallBackCalled);
+  DALI_TEST_CHECK(anchorClickedSignal);
+
+
+  // For coverage InsertTextAnchor, RemoveTextAnchor
+  // first index insert
+  field.SetProperty(TextField::Property::TEXT, "<a href='https://www.tizen.org'>TIZEN</a>");
+  field.SetProperty(DevelTextField::Property::PRIMARY_CURSOR_POSITION, 0);
+  application.SendNotification();
+  application.Render();
+
+  application.ProcessEvent( GenerateKey( "D", "", "D", KEY_D_CODE, 0, 0, Integration::KeyEvent::DOWN, "D", DEFAULT_DEVICE_NAME, Device::Class::NONE, Device::Subclass::NONE ) );
+  application.SendNotification();
+  application.Render();
+
+  gAnchorClickedCallBackCalled = false;
+  // Create a tap event to touch the text field.
+  TestGenerateTap(application, 30.0f, 25.0f);
+  application.SendNotification();
+  field.SetKeyInputFocus();
+
+  DALI_TEST_CHECK(gAnchorClickedCallBackCalled);
+
+  // last index insert
+  field.SetProperty(TextField::Property::TEXT, "<a href='https://www.tizen.org'>TIZEN</a>");
+  field.SetProperty(DevelTextField::Property::PRIMARY_CURSOR_POSITION, 5);
+  application.SendNotification();
+  application.Render();
+
+  application.ProcessEvent( GenerateKey( "D", "", "D", KEY_D_CODE, 0, 0, Integration::KeyEvent::DOWN, "D", DEFAULT_DEVICE_NAME, Device::Class::NONE, Device::Subclass::NONE ) );
+  application.SendNotification();
+  application.Render();
+
+  gAnchorClickedCallBackCalled = false;
+  // Create a tap event to touch the text field.
+  TestGenerateTap(application, 30.0f, 25.0f);
+  application.SendNotification();
+  field.SetKeyInputFocus();
+
+  DALI_TEST_CHECK(gAnchorClickedCallBackCalled);
+
+  // mid index insert
+  field.SetProperty(TextField::Property::TEXT, "<a href='https://www.tizen.org'>TIZEN</a>");
+  field.SetProperty(DevelTextField::Property::PRIMARY_CURSOR_POSITION, 2);
+  application.SendNotification();
+  application.Render();
+
+  application.ProcessEvent( GenerateKey( "D", "", "D", KEY_D_CODE, 0, 0, Integration::KeyEvent::DOWN, "D", DEFAULT_DEVICE_NAME, Device::Class::NONE, Device::Subclass::NONE ) );
+  application.SendNotification();
+  application.Render();
+
+  gAnchorClickedCallBackCalled = false;
+  // Create a tap event to touch the text field.
+  TestGenerateTap(application, 30.0f, 25.0f);
+  application.SendNotification();
+  field.SetKeyInputFocus();
+
+  DALI_TEST_CHECK(gAnchorClickedCallBackCalled);
+
+  // first index remove
+  field.SetProperty(TextField::Property::TEXT, "<a href='https://www.tizen.org'>TIZEN</a>");
+  field.SetProperty(DevelTextField::Property::PRIMARY_CURSOR_POSITION, 0);
+  application.SendNotification();
+  application.Render();
+
+  application.ProcessEvent(GenerateKey("", "", "", DALI_KEY_BACKSPACE, 0, 0, Integration::KeyEvent::DOWN, "", DEFAULT_DEVICE_NAME, Device::Class::NONE, Device::Subclass::NONE));
+  application.ProcessEvent(GenerateKey("Delete", "", "Delete", Dali::DevelKey::DALI_KEY_DELETE, 0, 0, Integration::KeyEvent::DOWN, "Delete", DEFAULT_DEVICE_NAME, Device::Class::NONE, Device::Subclass::NONE));
+  application.SendNotification();
+  application.Render();
+
+  gAnchorClickedCallBackCalled = false;
+  // Create a tap event to touch the text field.
+  TestGenerateTap(application, 30.0f, 25.0f);
+  application.SendNotification();
+  field.SetKeyInputFocus();
+
+  DALI_TEST_CHECK(gAnchorClickedCallBackCalled);
+
+  // last index remove
+  field.SetProperty(TextField::Property::TEXT, "<a href='https://www.tizen.org'>TIZEN</a>");
+  field.SetProperty(DevelTextField::Property::PRIMARY_CURSOR_POSITION, 5);
+  application.SendNotification();
+  application.Render();
+
+  application.ProcessEvent(GenerateKey("", "", "", DALI_KEY_BACKSPACE, 0, 0, Integration::KeyEvent::DOWN, "", DEFAULT_DEVICE_NAME, Device::Class::NONE, Device::Subclass::NONE));
+  application.ProcessEvent(GenerateKey("Delete", "", "Delete", Dali::DevelKey::DALI_KEY_DELETE, 0, 0, Integration::KeyEvent::DOWN, "Delete", DEFAULT_DEVICE_NAME, Device::Class::NONE, Device::Subclass::NONE));
+  application.SendNotification();
+  application.Render();
+
+  gAnchorClickedCallBackCalled = false;
+  // Create a tap event to touch the text field.
+  TestGenerateTap(application, 30.0f, 25.0f);
+  application.SendNotification();
+  application.Render();
+
+  DALI_TEST_CHECK(gAnchorClickedCallBackCalled);
+
+  // middle index
+  field.SetProperty(TextField::Property::TEXT, "<a href='https://www.tizen.org'>TIZEN</a>");
+  field.SetProperty(DevelTextField::Property::PRIMARY_CURSOR_POSITION, 2);
+  application.SendNotification();
+  application.Render();
+
+  application.ProcessEvent(GenerateKey("", "", "", DALI_KEY_BACKSPACE, 0, 0, Integration::KeyEvent::DOWN, "", DEFAULT_DEVICE_NAME, Device::Class::NONE, Device::Subclass::NONE));
+  application.ProcessEvent(GenerateKey("Delete", "", "Delete", Dali::DevelKey::DALI_KEY_DELETE, 0, 0, Integration::KeyEvent::DOWN, "Delete", DEFAULT_DEVICE_NAME, Device::Class::NONE, Device::Subclass::NONE));
+  application.SendNotification();
+  application.Render();
+
+  gAnchorClickedCallBackCalled = false;
+  // Create a tap event to touch the text field.
+  TestGenerateTap(application, 30.0f, 25.0f);
+  application.SendNotification();
+  application.Render();
+
+  DALI_TEST_CHECK(gAnchorClickedCallBackCalled);
+
+  // 0 ~ 1 index remove
+  field.SetProperty(TextField::Property::TEXT, "<a href='https://www.tizen.org'>TIZEN</a>");
+  field.SetProperty( DevelTextField::Property::SELECTED_TEXT_START, 0);
+  field.SetProperty( DevelTextField::Property::SELECTED_TEXT_END, 1);
+  application.SendNotification();
+  application.Render();
+
+  application.ProcessEvent(GenerateKey("", "", "", DALI_KEY_BACKSPACE, 0, 0, Integration::KeyEvent::DOWN, "", DEFAULT_DEVICE_NAME, Device::Class::NONE, Device::Subclass::NONE));
+  application.SendNotification();
+  application.Render();
+
+  gAnchorClickedCallBackCalled = false;
+  // Create a tap event to touch the text field.
+  TestGenerateTap(application, 30.0f, 25.0f);
+  application.SendNotification();
+  application.Render();
+
+  DALI_TEST_CHECK(gAnchorClickedCallBackCalled);
+
+  // 1 ~ 3 index remove
+  field.SetProperty(TextField::Property::TEXT, "<a href='https://www.tizen.org'>TIZEN</a>");
+  field.SetProperty( DevelTextField::Property::SELECTED_TEXT_START, 1);
+  field.SetProperty( DevelTextField::Property::SELECTED_TEXT_END, 3);
+  application.SendNotification();
+  application.Render();
+
+  application.ProcessEvent(GenerateKey("", "", "", DALI_KEY_BACKSPACE, 0, 0, Integration::KeyEvent::DOWN, "", DEFAULT_DEVICE_NAME, Device::Class::NONE, Device::Subclass::NONE));
+  application.SendNotification();
+  application.Render();
+
+  gAnchorClickedCallBackCalled = false;
+  // Create a tap event to touch the text field.
+  TestGenerateTap(application, 30.0f, 25.0f);
+  application.SendNotification();
+  application.Render();
+
+  DALI_TEST_CHECK(gAnchorClickedCallBackCalled);
+
+  // 3 ~ 4 index remove
+  field.SetProperty(TextField::Property::TEXT, "<a href='https://www.tizen.org'>TIZEN</a>");
+  field.SetProperty( DevelTextField::Property::SELECTED_TEXT_START, 3);
+  field.SetProperty( DevelTextField::Property::SELECTED_TEXT_END, 4);
+  application.SendNotification();
+  application.Render();
+
+  application.ProcessEvent(GenerateKey("", "", "", DALI_KEY_BACKSPACE, 0, 0, Integration::KeyEvent::DOWN, "", DEFAULT_DEVICE_NAME, Device::Class::NONE, Device::Subclass::NONE));
+  application.SendNotification();
+  application.Render();
+
+  gAnchorClickedCallBackCalled = false;
+  // Create a tap event to touch the text field.
+  TestGenerateTap(application, 30.0f, 25.0f);
+  application.SendNotification();
+  application.Render();
+
+  DALI_TEST_CHECK(gAnchorClickedCallBackCalled);
+
+  // Remove front of anchor
+  field.SetProperty(TextField::Property::TEXT, "TIZEN<a href='https://www.tizen.org'>TIZEN</a>");
+  field.SetProperty(DevelTextField::Property::PRIMARY_CURSOR_POSITION, 3);
+  application.SendNotification();
+  application.Render();
+
+  application.ProcessEvent(GenerateKey("", "", "", DALI_KEY_BACKSPACE, 0, 0, Integration::KeyEvent::DOWN, "", DEFAULT_DEVICE_NAME, Device::Class::NONE, Device::Subclass::NONE));
+  application.SendNotification();
+  application.Render();
+
+  // Remove whole text
+  field.SetProperty(TextField::Property::TEXT, "<a href='https://www.tizen.org'>TIZEN</a>");
+  DevelTextField::SelectWholeText(field);
+  application.SendNotification();
+  application.Render();
+
+  application.ProcessEvent(GenerateKey("", "", "", DALI_KEY_BACKSPACE, 0, 0, Integration::KeyEvent::DOWN, "", DEFAULT_DEVICE_NAME, Device::Class::NONE, Device::Subclass::NONE));
+  application.SendNotification();
+  application.Render();
+
+  // Remove all with backspace
+  field.SetProperty(TextField::Property::TEXT, "<a href='https://www.tizen.org'>T</a>");
+  field.SetProperty(DevelTextField::Property::PRIMARY_CURSOR_POSITION, 1);
+  application.SendNotification();
+  application.Render();
+
+  application.ProcessEvent(GenerateKey("", "", "", DALI_KEY_BACKSPACE, 0, 0, Integration::KeyEvent::DOWN, "", DEFAULT_DEVICE_NAME, Device::Class::NONE, Device::Subclass::NONE));
+  application.SendNotification();
+  application.Render();
+
+  // Remove all with delete
+  field.SetProperty(TextField::Property::TEXT, "<a href='https://www.tizen.org'>T</a>");
+  field.SetProperty(DevelTextField::Property::PRIMARY_CURSOR_POSITION, 0);
+  application.SendNotification();
+  application.Render();
+
+  application.ProcessEvent(GenerateKey("Delete", "", "Delete", Dali::DevelKey::DALI_KEY_DELETE, 0, 0, Integration::KeyEvent::DOWN, "Delete", DEFAULT_DEVICE_NAME, Device::Class::NONE, Device::Subclass::NONE));
+  application.SendNotification();
+  application.Render();
+
+  END_TEST;
+}
+
 // Positive test for the textChanged signal.
 int utcDaliTextFieldTextChangedP(void)
 {
index 57862f4..ecefef7 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2020 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2021 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.
@@ -77,6 +77,35 @@ const char* const PROPERTY_NAME_FONT_SIZE_SCALE = "fontSizeScale";
 const std::string DEFAULT_FONT_DIR( "/resources/fonts" );
 const unsigned int EMOJI_FONT_SIZE = 3840u; // 60 * 64
 
+static bool gAnchorClickedCallBackCalled;
+static bool gAnchorClickedCallBackNotCalled;
+
+struct CallbackFunctor
+{
+  CallbackFunctor(bool* callbackFlag)
+  : mCallbackFlag( callbackFlag )
+  {
+  }
+
+  void operator()()
+  {
+    *mCallbackFlag = true;
+  }
+  bool* mCallbackFlag;
+};
+
+static void TestAnchorClickedCallback(TextLabel control, const char* href, unsigned int hrefLength)
+{
+  tet_infoline(" TestAnchorClickedCallback");
+
+  gAnchorClickedCallBackNotCalled = false;
+
+  if (!strcmp(href, "https://www.tizen.org") && hrefLength == strlen(href))
+  {
+    gAnchorClickedCallBackCalled = true;
+  }
+}
+
 bool DaliTestCheckMaps( const Property::Map& mapGet, const Property::Map& mapSet, const std::vector<std::string>& indexConversionTable = std::vector<std::string>() )
 {
   const Property::Map::SizeType size = mapGet.Count();
@@ -1727,3 +1756,49 @@ int UtcDaliToolkitTextlabelFontSizeScale(void)
 
   END_TEST;
 }
+
+// Positive test for the anchorClicked signal.
+int UtcDaliToolkitTextlabelAnchorClicked(void)
+{
+  ToolkitTestApplication application;
+  tet_infoline(" UtcDaliToolkitTextlabelAnchorClicked");
+  TextLabel label = TextLabel::New();
+  DALI_TEST_CHECK(label);
+
+  application.GetScene().Add(label);
+
+  // connect to the anchor clicked signal.
+  ConnectionTracker* testTracker = new ConnectionTracker();
+  DevelTextLabel::AnchorClickedSignal(label).Connect(&TestAnchorClickedCallback);
+  bool anchorClickedSignal = false;
+  label.ConnectSignal(testTracker, "anchorClicked", CallbackFunctor(&anchorClickedSignal));
+
+  gAnchorClickedCallBackCalled = false;
+  label.SetProperty(TextLabel::Property::TEXT, "<a href='https://www.tizen.org'>TIZEN</a>");
+  label.SetProperty(TextLabel::Property::ENABLE_MARKUP, true);
+  label.SetProperty(Actor::Property::SIZE, Vector2(100.f, 50.f));
+  label.SetProperty(Actor::Property::PARENT_ORIGIN, ParentOrigin::TOP_LEFT);
+  label.SetProperty(Actor::Property::ANCHOR_POINT, AnchorPoint::TOP_LEFT);
+
+  application.SendNotification();
+  application.Render();
+
+  // Create a tap event to touch the text label.
+  TestGenerateTap(application, 5.0f, 25.0f);
+  application.SendNotification();
+  application.Render();
+
+  DALI_TEST_CHECK(gAnchorClickedCallBackCalled);
+  DALI_TEST_CHECK(anchorClickedSignal);
+
+
+  gAnchorClickedCallBackNotCalled = true;
+  // Tap the outside of anchor, callback should not be called.
+  TestGenerateTap(application, 150.f, 100.f);
+  application.SendNotification();
+  application.Render();
+
+  DALI_TEST_CHECK(gAnchorClickedCallBackNotCalled);
+
+  END_TEST;
+}
index abe3389..37678d8 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2020 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2021 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.
@@ -35,6 +35,11 @@ MaxLengthReachedSignalType& MaxLengthReachedSignal(TextEditor textEditor)
   return GetImpl(textEditor).MaxLengthReachedSignal();
 }
 
+AnchorClickedSignalType& AnchorClickedSignal(TextEditor textEditor)
+{
+  return GetImpl(textEditor).AnchorClickedSignal();
+}
+
 void SelectWholeText(TextEditor textEditor)
 {
   GetImpl(textEditor).SelectWholeText();
index 77ed805..c73c582 100644 (file)
@@ -217,6 +217,27 @@ using MaxLengthReachedSignalType = Signal<void(TextEditor)>;
 DALI_TOOLKIT_API MaxLengthReachedSignalType& MaxLengthReachedSignal(TextEditor textEditor);
 
 /**
+ * @brief Anchor clicked signal type.
+ *
+ * @note Signal
+ *  - const char* : href of clicked anchor.
+ *  - uint32_t    : length of href.
+ */
+using AnchorClickedSignalType = Signal<void(TextEditor, const char*, uint32_t)>;
+
+/**
+ * @brief This signal is emitted when the anchor is clicked.
+ *
+ * A callback of the following type may be connected:
+ * @code
+ *   void YourCallbackName(TextEditor textEditor, const char* href, uint32_t hrefLength);
+ * @endcode
+ * @param[in] textEditor The instance of TextEditor.
+ * @return The signal to connect to.
+ */
+DALI_TOOLKIT_API AnchorClickedSignalType& AnchorClickedSignal(TextEditor textEditor);
+
+/**
  * @brief Select the whole text of TextEditor.
  *
  * @param[in] textEditor The instance of TextEditor.
index 29c3730..fdfac30 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2020 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2021 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.
@@ -30,6 +30,11 @@ InputMethodContext GetInputMethodContext(TextField textField)
   return GetImpl(textField).GetInputMethodContext();
 }
 
+AnchorClickedSignalType& AnchorClickedSignal(TextField textField)
+{
+  return GetImpl(textField).AnchorClickedSignal();
+}
+
 void SelectWholeText(TextField textField)
 {
   GetImpl(textField).SelectWholeText();
index ac239ef..3367bec 100644 (file)
@@ -185,6 +185,27 @@ enum
 DALI_TOOLKIT_API InputMethodContext GetInputMethodContext(TextField textField);
 
 /**
+ * @brief Anchor clicked signal type.
+ *
+ * @note Signal
+ *  - const char* : href of clicked anchor.
+ *  - uint32_t    : length of href.
+ */
+using AnchorClickedSignalType = Signal<void(TextField, const char*, uint32_t)>;
+
+/**
+ * @brief This signal is emitted when the anchor is clicked.
+ *
+ * A callback of the following type may be connected:
+ * @code
+ *   void YourCallbackName(TextField textField, const char* href, uint32_t hrefLength);
+ * @endcode
+ * @param[in] textField The instance of TextField.
+ * @return The signal to connect to.
+ */
+DALI_TOOLKIT_API AnchorClickedSignalType& AnchorClickedSignal(TextField textField);
+
+/**
  * @brief Select the whole text of TextField.
  *
  * @param[in] textField The instance of TextField.
diff --git a/dali-toolkit/devel-api/controls/text-controls/text-label-devel.cpp b/dali-toolkit/devel-api/controls/text-controls/text-label-devel.cpp
new file mode 100644 (file)
index 0000000..0d6cd0b
--- /dev/null
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2021 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.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+// INTERNAL INCLUDES
+#include <dali-toolkit/devel-api/controls/text-controls/text-label-devel.h>
+#include <dali-toolkit/internal/controls/text-controls/text-label-impl.h>
+
+namespace Dali
+{
+namespace Toolkit
+{
+namespace DevelTextLabel
+{
+AnchorClickedSignalType& AnchorClickedSignal(TextLabel textLabel)
+{
+  return GetImpl(textLabel).AnchorClickedSignal();
+}
+
+} // namespace DevelTextLabel
+
+} // namespace Toolkit
+
+} // namespace Dali
index 9b3e4db..efdad10 100644 (file)
@@ -2,7 +2,7 @@
 #define DALI_TOOLKIT_TEXT_LABEL_DEVEL_H
 
 /*
- * Copyright (c) 2020 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2021 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.
@@ -158,6 +158,27 @@ enum Type
 
 } // namespace Property
 
+/**
+ * @brief Anchor clicked signal type.
+ *
+ * @note Signal
+ *  - const char* : href of clicked anchor.
+ *  - uint32_t    : length of href.
+ */
+using AnchorClickedSignalType = Signal<void(TextLabel, const char*, uint32_t)>;
+
+/**
+ * @brief This signal is emitted when the anchor is clicked.
+ *
+ * A callback of the following type may be connected:
+ * @code
+ *   void YourCallbackName(TextLabel textLabel, const char* href, uint32_t hrefLength);
+ * @endcode
+ * @param[in] textLabel The instance of TextLabel.
+ * @return The signal to connect to.
+ */
+DALI_TOOLKIT_API AnchorClickedSignalType& AnchorClickedSignal(TextLabel textLabel);
+
 } // namespace DevelTextLabel
 
 } // namespace Toolkit
index cc7dd45..5516ac8 100755 (executable)
@@ -34,6 +34,7 @@ SET( devel_api_src_files
   ${devel_api_src_dir}/controls/table-view/table-view.cpp
   ${devel_api_src_dir}/controls/text-controls/text-editor-devel.cpp
   ${devel_api_src_dir}/controls/text-controls/text-field-devel.cpp
+  ${devel_api_src_dir}/controls/text-controls/text-label-devel.cpp
   ${devel_api_src_dir}/controls/text-controls/text-selection-popup.cpp
   ${devel_api_src_dir}/controls/text-controls/text-selection-toolbar.cpp
   ${devel_api_src_dir}/controls/tool-bar/tool-bar.cpp
index 0112e32..1441dc4 100644 (file)
@@ -172,7 +172,8 @@ void ShapeTextPreprocess(const RendererParameters& textParameters, TextAbstracti
 
   MarkupProcessData markupProcessData(colorRuns,
                                       fontDescriptionRuns,
-                                      textModel->mLogicalModel->mEmbeddedItems);
+                                      textModel->mLogicalModel->mEmbeddedItems,
+                                      textModel->mLogicalModel->mAnchors);
 
   if(textParameters.markupEnabled)
   {
index 82f6975..75677ca 100644 (file)
@@ -151,6 +151,7 @@ DALI_DEVEL_PROPERTY_REGISTRATION(Toolkit,           TextEditor, "grabHandleColor
 DALI_SIGNAL_REGISTRATION(Toolkit, TextEditor, "textChanged",        SIGNAL_TEXT_CHANGED       )
 DALI_SIGNAL_REGISTRATION(Toolkit, TextEditor, "inputStyleChanged",  SIGNAL_INPUT_STYLE_CHANGED)
 DALI_SIGNAL_REGISTRATION(Toolkit, TextEditor, "maxLengthReached",   SIGNAL_MAX_LENGTH_REACHED )
+DALI_SIGNAL_REGISTRATION(Toolkit, TextEditor, "anchorClicked",      SIGNAL_ANCHOR_CLICKED     )
 
 DALI_TYPE_REGISTRATION_END()
 // clang-format on
@@ -1204,6 +1205,11 @@ DevelTextEditor::MaxLengthReachedSignalType& TextEditor::MaxLengthReachedSignal(
   return mMaxLengthReachedSignal;
 }
 
+DevelTextEditor::AnchorClickedSignalType& TextEditor::AnchorClickedSignal()
+{
+  return mAnchorClickedSignal;
+}
+
 Text::ControllerPtr TextEditor::getController()
 {
   return mController;
@@ -1232,6 +1238,14 @@ bool TextEditor::DoConnectSignal(BaseObject* object, ConnectionTrackerInterface*
       editorImpl.MaxLengthReachedSignal().Connect(tracker, functor);
     }
   }
+  else if(0 == strcmp(signalName.c_str(), SIGNAL_ANCHOR_CLICKED))
+  {
+    if(editor)
+    {
+      Internal::TextEditor& editorImpl(GetImpl(editor));
+      editorImpl.AnchorClickedSignal().Connect(tracker, functor);
+    }
+  }
   else
   {
     // signalName does not match any signal
@@ -1260,7 +1274,7 @@ void TextEditor::OnInitialize()
 {
   Actor self = Self();
 
-  mController = Text::Controller::New(this, this, this);
+  mController = Text::Controller::New(this, this, this, this);
 
   mDecorator = Text::Decorator::New(*mController,
                                     *mController);
@@ -1610,6 +1624,7 @@ void TextEditor::OnTap(const TapGesture& gesture)
   padding                   = Self().GetProperty<Extents>(Toolkit::Control::Property::PADDING);
   const Vector2& localPoint = gesture.GetLocalPoint();
   mController->TapEvent(gesture.GetNumberOfTaps(), localPoint.x - padding.start, localPoint.y - padding.top);
+  mController->AnchorEvent(localPoint.x - padding.start, localPoint.y - padding.top);
 
   SetKeyInputFocus();
 }
@@ -1759,6 +1774,12 @@ void TextEditor::InputStyleChanged(Text::InputStyle::Mask inputStyleMask)
   mInputStyleChangedSignal.Emit(handle, editorInputStyleMask);
 }
 
+void TextEditor::AnchorClicked(const std::string& href)
+{
+  Dali::Toolkit::TextEditor handle(GetOwner());
+  mAnchorClickedSignal.Emit(handle, href.c_str(), href.length());
+}
+
 void TextEditor::AddDecoration(Actor& actor, bool needsClipping)
 {
   if(actor)
index a8b9a0c..d0609b6 100644 (file)
@@ -31,6 +31,7 @@
 #include <dali-toolkit/internal/controls/control/control-data-impl.h>
 #include <dali-toolkit/internal/text/decorator/text-decorator.h>
 #include <dali-toolkit/internal/text/rendering/text-renderer.h>
+#include <dali-toolkit/internal/text/text-anchor-control-interface.h>
 #include <dali-toolkit/internal/text/text-control-interface.h>
 #include <dali-toolkit/internal/text/text-controller.h>
 #include <dali-toolkit/internal/text/text-editable-control-interface.h>
@@ -48,7 +49,7 @@ namespace Internal
 /**
  * @brief A control which renders a long text string with styles.
  */
-class TextEditor : public Control, public Text::ControlInterface, public Text::EditableControlInterface, public Text::SelectableControlInterface
+class TextEditor : public Control, public Text::ControlInterface, public Text::EditableControlInterface, public Text::SelectableControlInterface, public Text::AnchorControlInterface
 {
 public:
   /**
@@ -87,6 +88,11 @@ public:
   DevelTextEditor::MaxLengthReachedSignalType& MaxLengthReachedSignal();
 
   /**
+   * @copydoc Dali::Toollkit::TextEditor::AnchorClickedSignal()
+   */
+  DevelTextEditor::AnchorClickedSignalType& AnchorClickedSignal();
+
+  /**
    * Connects a callback function with the object's signals.
    * @param[in] object The object providing the signal.
    * @param[in] tracker Used to disconnect the signal.
@@ -276,7 +282,15 @@ public:
   /**
    * @copydoc Text::EditableControlInterface::SetEditable()
    */
-  void                SetEditable(bool editable) override;
+  void SetEditable(bool editable) override;
+
+  // From AnchorControlInterface
+
+  /**
+   * @copydoc Text::AnchorControlInterface::AnchorClicked()
+   */
+  void AnchorClicked(const std::string& href) override;
+
   Text::ControllerPtr getController();
 
 private: // Implementation
@@ -383,6 +397,7 @@ private: // Data
   Toolkit::TextEditor::InputStyleChangedSignalType     mInputStyleChangedSignal;
   Toolkit::TextEditor::ScrollStateChangedSignalType    mScrollStateChangedSignal;
   Toolkit::DevelTextEditor::MaxLengthReachedSignalType mMaxLengthReachedSignal;
+  Toolkit::DevelTextEditor::AnchorClickedSignalType    mAnchorClickedSignal;
 
   InputMethodContext            mInputMethodContext;
   Text::ControllerPtr           mController;
index d12a708..6025a55 100644 (file)
@@ -142,6 +142,7 @@ DALI_DEVEL_PROPERTY_REGISTRATION(Toolkit,           TextField, "grabHandleColor"
 DALI_SIGNAL_REGISTRATION(Toolkit, TextField, "textChanged",       SIGNAL_TEXT_CHANGED       )
 DALI_SIGNAL_REGISTRATION(Toolkit, TextField, "maxLengthReached",  SIGNAL_MAX_LENGTH_REACHED )
 DALI_SIGNAL_REGISTRATION(Toolkit, TextField, "inputStyleChanged", SIGNAL_INPUT_STYLE_CHANGED)
+DALI_SIGNAL_REGISTRATION(Toolkit, TextField, "anchorClicked",     SIGNAL_ANCHOR_CLICKED     )
 
 DALI_TYPE_REGISTRATION_END()
 // clang-format on
@@ -1195,6 +1196,14 @@ bool TextField::DoConnectSignal(BaseObject* object, ConnectionTrackerInterface*
   {
     field.InputStyleChangedSignal().Connect(tracker, functor);
   }
+  else if(0 == strcmp(signalName.c_str(), SIGNAL_ANCHOR_CLICKED))
+  {
+    if(field)
+    {
+      Internal::TextField& fieldImpl(GetImpl(field));
+      fieldImpl.AnchorClickedSignal().Connect(tracker, functor);
+    }
+  }
   else
   {
     // signalName does not match any signal
@@ -1219,11 +1228,16 @@ Toolkit::TextField::InputStyleChangedSignalType& TextField::InputStyleChangedSig
   return mInputStyleChangedSignal;
 }
 
+DevelTextField::AnchorClickedSignalType& TextField::AnchorClickedSignal()
+{
+  return mAnchorClickedSignal;
+}
+
 void TextField::OnInitialize()
 {
   Actor self = Self();
 
-  mController = Text::Controller::New(this, this, this);
+  mController = Text::Controller::New(this, this, this, this);
 
   // When using the vector-based rendering, the size of the GLyphs are different
   TextAbstraction::GlyphType glyphType = (DevelText::RENDERING_VECTOR_BASED == mRenderingBackend) ? TextAbstraction::VECTOR_GLYPH : TextAbstraction::BITMAP_GLYPH;
@@ -1619,6 +1633,7 @@ void TextField::OnTap(const TapGesture& gesture)
   padding                   = Self().GetProperty<Extents>(Toolkit::Control::Property::PADDING);
   const Vector2& localPoint = gesture.GetLocalPoint();
   mController->TapEvent(gesture.GetNumberOfTaps(), localPoint.x - padding.start, localPoint.y - padding.top);
+  mController->AnchorEvent(localPoint.x - padding.start, localPoint.y - padding.top);
 
   SetKeyInputFocus();
 }
@@ -1783,6 +1798,12 @@ void TextField::InputStyleChanged(Text::InputStyle::Mask inputStyleMask)
   mInputStyleChangedSignal.Emit(handle, fieldInputStyleMask);
 }
 
+void TextField::AnchorClicked(const std::string& href)
+{
+  Dali::Toolkit::TextField handle(GetOwner());
+  mAnchorClickedSignal.Emit(handle, href.c_str(), href.length());
+}
+
 void TextField::AddDecoration(Actor& actor, bool needsClipping)
 {
   if(actor)
index 8ddc227..670b81c 100644 (file)
 
 // INTERNAL INCLUDES
 #include <dali-toolkit/devel-api/controls/control-devel.h>
+#include <dali-toolkit/devel-api/controls/text-controls/text-field-devel.h>
 #include <dali-toolkit/internal/controls/control/control-data-impl.h>
 #include <dali-toolkit/internal/text/decorator/text-decorator.h>
 #include <dali-toolkit/internal/text/rendering/text-renderer.h>
+#include <dali-toolkit/internal/text/text-anchor-control-interface.h>
 #include <dali-toolkit/internal/text/text-control-interface.h>
 #include <dali-toolkit/internal/text/text-controller.h>
 #include <dali-toolkit/internal/text/text-editable-control-interface.h>
@@ -43,7 +45,7 @@ namespace Internal
 /**
  * @brief A control which renders a short text string.
  */
-class TextField : public Control, public Text::ControlInterface, public Text::EditableControlInterface, public Text::SelectableControlInterface
+class TextField : public Control, public Text::ControlInterface, public Text::EditableControlInterface, public Text::SelectableControlInterface, public Text::AnchorControlInterface
 {
 public:
   /**
@@ -102,6 +104,11 @@ public:
    */
   Toolkit::TextField::InputStyleChangedSignalType& InputStyleChangedSignal();
 
+  /**
+   * @copydoc TextField::AnchorClickedSignal()
+   */
+  DevelTextField::AnchorClickedSignalType& AnchorClickedSignal();
+
   Text::ControllerPtr getController();
 
 private: // From Control
@@ -251,6 +258,13 @@ public:
    */
   void SetEditable(bool editable) override;
 
+  // From AnchorControlInterface
+
+  /**
+   * @copydoc Text::AnchorControlInterface::AnchorClicked()
+   */
+  void AnchorClicked(const std::string& href) override;
+
 private: // Implementation
   /**
    * @copydoc Dali::Toolkit::Text::Controller::(InputMethodContext& inputMethodContext, const InputMethodContext::EventData& inputMethodContextEvent)
@@ -341,9 +355,10 @@ public: // For UTC only
 
 private: // Data
   // Signals
-  Toolkit::TextField::TextChangedSignalType       mTextChangedSignal;
-  Toolkit::TextField::MaxLengthReachedSignalType  mMaxLengthReachedSignal;
-  Toolkit::TextField::InputStyleChangedSignalType mInputStyleChangedSignal;
+  Toolkit::TextField::TextChangedSignalType        mTextChangedSignal;
+  Toolkit::TextField::MaxLengthReachedSignalType   mMaxLengthReachedSignal;
+  Toolkit::TextField::InputStyleChangedSignalType  mInputStyleChangedSignal;
+  Toolkit::DevelTextField::AnchorClickedSignalType mAnchorClickedSignal;
 
   InputMethodContext       mInputMethodContext;
   Text::ControllerPtr      mController;
index 3ee507d..2e8c7ae 100644 (file)
@@ -139,6 +139,9 @@ DALI_ANIMATABLE_PROPERTY_COMPONENT_REGISTRATION(Toolkit,    TextLabel, "textColo
 DALI_ANIMATABLE_PROPERTY_COMPONENT_REGISTRATION(Toolkit,    TextLabel, "textColorGreen", TEXT_COLOR_GREEN, TEXT_COLOR, 1)
 DALI_ANIMATABLE_PROPERTY_COMPONENT_REGISTRATION(Toolkit,    TextLabel, "textColorBlue",  TEXT_COLOR_BLUE,  TEXT_COLOR, 2)
 DALI_ANIMATABLE_PROPERTY_COMPONENT_REGISTRATION(Toolkit,    TextLabel, "textColorAlpha", TEXT_COLOR_ALPHA, TEXT_COLOR, 3)
+
+DALI_SIGNAL_REGISTRATION(Toolkit, TextLabel, "anchorClicked", SIGNAL_ANCHOR_CLICKED)
+
 DALI_TYPE_REGISTRATION_END()
 // clang-format on
 
@@ -720,6 +723,35 @@ Property::Value TextLabel::GetProperty(BaseObject* object, Property::Index index
   return value;
 }
 
+bool TextLabel::DoConnectSignal(BaseObject* object, ConnectionTrackerInterface* tracker, const std::string& signalName, FunctorDelegate* functor)
+{
+  Dali::BaseHandle handle(object);
+
+  bool               connected(true);
+  Toolkit::TextLabel label = Toolkit::TextLabel::DownCast(handle);
+
+  if(0 == strcmp(signalName.c_str(), SIGNAL_ANCHOR_CLICKED))
+  {
+    if(label)
+    {
+      Internal::TextLabel& labelImpl(GetImpl(label));
+      labelImpl.AnchorClickedSignal().Connect(tracker, functor);
+    }
+  }
+  else
+  {
+    // signalName does not match any signal
+    connected = false;
+  }
+
+  return connected;
+}
+
+DevelTextLabel::AnchorClickedSignalType& TextLabel::AnchorClickedSignal()
+{
+  return mAnchorClickedSignal;
+}
+
 void TextLabel::OnInitialize()
 {
   Actor self = Self();
@@ -736,6 +768,7 @@ void TextLabel::OnInitialize()
   DALI_ASSERT_DEBUG(mController && "Invalid Text Controller")
 
   mController->SetControlInterface(this);
+  mController->SetAnchorControlInterface(this);
 
   // Use height-for-width negotiation by default
   self.SetResizePolicy(ResizePolicy::FILL_TO_PARENT, Dimension::WIDTH);
@@ -752,6 +785,10 @@ void TextLabel::OnInitialize()
   Dali::LayoutDirection::Type layoutDirection = static_cast<Dali::LayoutDirection::Type>(stage.GetRootLayer().GetProperty(Dali::Actor::Property::LAYOUT_DIRECTION).Get<int>());
   mController->SetLayoutDirection(layoutDirection);
 
+  // Forward input events to controller
+  EnableGestureDetection(static_cast<GestureType::Value>(GestureType::TAP));
+  GetTapGestureDetector().SetMaximumTapsRequired(1);
+
   Layout::Engine& engine = mController->GetLayoutEngine();
   engine.SetCursorWidth(0u); // Do not layout space for the cursor.
 
@@ -793,6 +830,23 @@ void TextLabel::OnStyleChange(Toolkit::StyleManager styleManager, StyleChange::T
   Control::OnStyleChange(styleManager, change);
 }
 
+void TextLabel::OnTap(const TapGesture& gesture)
+{
+  DALI_LOG_INFO(gLogFilter, Debug::Verbose, "TextLabel::OnTap %p\n", mController.Get());
+
+  // Deliver the tap before the focus event to controller; this allows us to detect when focus is gained due to tap-gestures
+  Extents padding;
+  padding                   = Self().GetProperty<Extents>(Toolkit::Control::Property::PADDING);
+  const Vector2& localPoint = gesture.GetLocalPoint();
+  mController->AnchorEvent(localPoint.x - padding.start, localPoint.y - padding.top);
+}
+
+void TextLabel::AnchorClicked(const std::string& href)
+{
+  Dali::Toolkit::TextLabel handle(GetOwner());
+  mAnchorClickedSignal.Emit(handle, href.c_str(), href.length());
+}
+
 Vector3 TextLabel::GetNaturalSize()
 {
   Extents padding;
index e9ad7a7..a204be6 100644 (file)
@@ -24,6 +24,7 @@
 // INTERNAL INCLUDES
 #include <dali-toolkit/internal/controls/control/control-data-impl.h>
 #include <dali-toolkit/internal/text/rendering/text-renderer.h>
+#include <dali-toolkit/internal/text/text-anchor-control-interface.h>
 #include <dali-toolkit/internal/text/text-control-interface.h>
 #include <dali-toolkit/internal/text/text-controller.h>
 #include <dali-toolkit/internal/text/text-scroller-interface.h>
@@ -41,7 +42,7 @@ namespace Internal
 /**
  * @brief A control which renders a short text string.
  */
-class TextLabel : public Control, public Text::ControlInterface, public Text::ScrollerInterface
+class TextLabel : public Control, public Text::ControlInterface, public Text::ScrollerInterface, public Text::AnchorControlInterface
 {
 public:
   /**
@@ -69,6 +70,22 @@ public:
    */
   static Property::Value GetProperty(BaseObject* object, Property::Index index);
 
+  /**
+   * @copydoc Dali::Toollkit::TextLabel::AnchorClickedSignal()
+   */
+  DevelTextLabel::AnchorClickedSignalType& AnchorClickedSignal();
+
+  /**
+   * Connects a callback function with the object's signals.
+   * @param[in] object The object providing the signal.
+   * @param[in] tracker Used to disconnect the signal.
+   * @param[in] signalName The signal to connect to.
+   * @param[in] functor A newly allocated FunctorDelegate.
+   * @return True if the signal was connected.
+   * @post If a signal was connected, ownership of functor was passed to CallbackBase. Otherwise the caller is responsible for deleting the unused functor.
+   */
+  static bool DoConnectSignal(BaseObject* object, ConnectionTrackerInterface* tracker, const std::string& signalName, FunctorDelegate* functor);
+
   Text::ControllerPtr getController();
 
 private: // From Control
@@ -88,6 +105,11 @@ private: // From Control
   void OnRelayout(const Vector2& size, RelayoutContainer& container) override;
 
   /**
+   * @copydoc Control::OnTap()
+   */
+  void OnTap(const TapGesture& tap) override;
+
+  /**
    * @copydoc Control::GetNaturalSize()
    */
   Vector3 GetNaturalSize() override;
@@ -115,6 +137,12 @@ private: // from TextScroller
    */
   void ScrollingFinished() override;
 
+public: // From AnchorControlInterface
+  /**
+   * @copydoc Text::AnchorControlInterface::AnchorClicked()
+   */
+  void AnchorClicked(const std::string& href) override;
+
 private: // Implementation
   /**
    * Construct a new TextLabel.
@@ -155,6 +183,9 @@ private: // Data
 
   Toolkit::Visual::Base mVisual;
 
+  // Signals
+  Toolkit::DevelTextLabel::AnchorClickedSignalType mAnchorClickedSignal;
+
   int  mRenderingBackend;
   bool mTextUpdateNeeded : 1;
 
index 4ac8dea..ede26c1 100644 (file)
@@ -134,6 +134,7 @@ SET( toolkit_src_files
    ${toolkit_src_dir}/text/markup-processor.cpp
    ${toolkit_src_dir}/text/markup-processor-color.cpp
    ${toolkit_src_dir}/text/markup-processor-embedded-item.cpp
+   ${toolkit_src_dir}/text/markup-processor-anchor.cpp
    ${toolkit_src_dir}/text/markup-processor-font.cpp
    ${toolkit_src_dir}/text/markup-processor-helper-functions.cpp
    ${toolkit_src_dir}/text/multi-language-support.cpp
diff --git a/dali-toolkit/internal/text/anchor.h b/dali-toolkit/internal/text/anchor.h
new file mode 100644 (file)
index 0000000..d2c7384
--- /dev/null
@@ -0,0 +1,46 @@
+#ifndef DALI_TOOLKIT_TEXT_ANCHOR_H
+#define DALI_TOOLKIT_TEXT_ANCHOR_H
+
+/*
+ * Copyright (c) 2021 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.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+// INTERNAL INCLUDES
+#include <dali-toolkit/internal/text/text-definitions.h>
+
+namespace Dali
+{
+namespace Toolkit
+{
+namespace Text
+{
+/**
+ * @brief An anchor within the text.
+ */
+struct Anchor
+{
+  CharacterIndex startIndex; ///< The character's start index of the anchor within the string.
+  CharacterIndex endIndex;   ///< The character's end index of the anchor within the string.
+  char*          href;       ///< The url path
+};
+
+} // namespace Text
+
+} // namespace Toolkit
+
+} // namespace Dali
+
+#endif // DALI_TOOLKIT_TEXT_ANCHOR_H
\ No newline at end of file
index 8ecab1f..12929fc 100644 (file)
@@ -55,6 +55,16 @@ void FreeEmbeddedItems(Vector<EmbeddedItem>& embeddedItem)
   embeddedItem.Clear();
 }
 
+void FreeAnchors(Vector<Anchor>& anchors)
+{
+  for(auto&& anchor : anchors)
+  {
+    delete[] anchor.href;
+  }
+
+  anchors.Clear();
+}
+
 LogicalModelPtr LogicalModel::New()
 {
   return LogicalModelPtr(new LogicalModel());
@@ -573,6 +583,11 @@ void LogicalModel::ClearEmbeddedImages()
   FreeEmbeddedItems(mEmbeddedItems);
 }
 
+void LogicalModel::ClearAnchors()
+{
+  FreeAnchors(mAnchors);
+}
+
 LogicalModel::~LogicalModel()
 {
   ClearFontDescriptionRuns();
index 4702c38..921d911 100644 (file)
@@ -24,6 +24,7 @@
 #include <dali/public-api/object/ref-object.h>
 
 // INTERNAL INCLUDES
+#include <dali-toolkit/internal/text/anchor.h>
 #include <dali-toolkit/internal/text/bidirectional-line-info-run.h>
 #include <dali-toolkit/internal/text/bidirectional-paragraph-info-run.h>
 #include <dali-toolkit/internal/text/color-run.h>
@@ -181,6 +182,11 @@ public:
    */
   void ClearEmbeddedImages();
 
+  /**
+   * @brief Clears the anchors.
+   */
+  void ClearAnchors();
+
 protected:
   /**
    * @brief A reference counted object may only be deleted by calling Unreference().
@@ -212,6 +218,7 @@ public:
   Vector<CharacterDirection>            mCharacterDirections; ///< For each character, whether is right to left. ( @e flase is left to right, @e true right to left ).
   Vector<BidirectionalLineInfoRun>      mBidirectionalLineInfo;
   Vector<EmbeddedItem>                  mEmbeddedItems;
+  Vector<Anchor>                        mAnchors;
 
   BidirectionalLineRunIndex mBidirectionalLineIndex; ///< The last fetched bidirectional line info.
 };
diff --git a/dali-toolkit/internal/text/markup-processor-anchor.cpp b/dali-toolkit/internal/text/markup-processor-anchor.cpp
new file mode 100644 (file)
index 0000000..4c4bb29
--- /dev/null
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2021 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.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+// FILE HEADER
+#include <dali-toolkit/internal/text/markup-processor-anchor.h>
+
+// EXTERNAL INCLUDES
+#include <dali/public-api/common/dali-vector.h>
+#include <memory.h>
+
+// INTERNAL INCLUDES
+#include <dali-toolkit/internal/text/anchor.h>
+#include <dali-toolkit/internal/text/markup-processor-helper-functions.h>
+
+namespace Dali
+{
+namespace Toolkit
+{
+namespace Text
+{
+namespace
+{
+const std::string XHTML_HREF_ATTRIBUTE("href");
+} // namespace
+
+void ProcessAnchor(const Tag& tag, Anchor& anchor)
+{
+  anchor.href = nullptr;
+
+  for(auto&& attribute : tag.attributes)
+  {
+    if(TokenComparison(XHTML_HREF_ATTRIBUTE, attribute.nameBuffer, attribute.nameLength))
+    {
+      Length hrefLength = attribute.valueLength + 1;
+      anchor.href       = new char[hrefLength];
+      memcpy(anchor.href, attribute.valueBuffer, hrefLength);
+      anchor.href[hrefLength - 1] = '\0';
+      // The memory is freed when the font run is removed from the logical model.
+    }
+  }
+}
+
+} // namespace Text
+
+} // namespace Toolkit
+
+} // namespace Dali
\ No newline at end of file
diff --git a/dali-toolkit/internal/text/markup-processor-anchor.h b/dali-toolkit/internal/text/markup-processor-anchor.h
new file mode 100644 (file)
index 0000000..7c1d181
--- /dev/null
@@ -0,0 +1,47 @@
+#ifndef DALI_TOOLKIT_TEXT_MARKUP_PROCESSOR_ANCHOR_H
+#define DALI_TOOLKIT_TEXT_MARKUP_PROCESSOR_ANCHOR_H
+
+/*
+ * Copyright (c) 2021 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.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+namespace Dali
+{
+
+namespace Toolkit
+{
+
+namespace Text
+{
+
+struct Tag;
+struct Anchor;
+
+/**
+ * @brief Retrieves the @e anchor from the @p tag.
+ *
+ * @param[in] tag The anchor tag and its attributes.
+ * @param[in,out] anchor The anchor.
+ */
+void ProcessAnchor( const Tag& tag, Anchor& anchor );
+
+} // namespace Text
+
+} // namespace Toolkit
+
+} // namespace Dali
+
+#endif // DALI_TOOLKIT_TEXT_MARKUP_PROCESSOR_ANCHOR_H
\ No newline at end of file
index 20a5825..a8a6ecc 100644 (file)
@@ -25,6 +25,7 @@
 
 // INTERNAL INCLUDES
 #include <dali-toolkit/internal/text/character-set-conversion.h>
+#include <dali-toolkit/internal/text/markup-processor-anchor.h>
 #include <dali-toolkit/internal/text/markup-processor-color.h>
 #include <dali-toolkit/internal/text/markup-processor-embedded-item.h>
 #include <dali-toolkit/internal/text/markup-processor-font.h>
@@ -51,6 +52,7 @@ const std::string XHTML_SHADOW_TAG("shadow");
 const std::string XHTML_GLOW_TAG("glow");
 const std::string XHTML_OUTLINE_TAG("outline");
 const std::string XHTML_ITEM_TAG("item");
+const std::string XHTML_ANCHOR_TAG("a");
 
 const char LESS_THAN      = '<';
 const char GREATER_THAN   = '>';
@@ -580,6 +582,38 @@ void ProcessItemTag(
 }
 
 /**
+ * @brief Processes the anchor tag
+ *
+ * @param[in/out] markupProcessData The markup process data
+ * @param[in] tag The current tag
+ * @param[in/out] characterIndex The current character index
+ */
+void ProcessAnchorTag(
+  MarkupProcessData& markupProcessData,
+  const Tag          tag,
+  CharacterIndex&    characterIndex)
+{
+  if(!tag.isEndTag)
+  {
+    // Create an anchor instance.
+    Anchor anchor;
+    anchor.startIndex = characterIndex;
+    anchor.endIndex   = 0u;
+    ProcessAnchor(tag, anchor);
+    markupProcessData.anchors.PushBack(anchor);
+  }
+  else
+  {
+    // Update end index.
+    unsigned int count = markupProcessData.anchors.Count();
+    if(count > 0)
+    {
+      markupProcessData.anchors[count - 1].endIndex = characterIndex;
+    }
+  }
+}
+
+/**
  * @brief Resizes the model's vectors
  *
  * @param[in/out] markupProcessData The markup process data
@@ -763,6 +797,18 @@ void ProcessMarkupString(const std::string& markupString, MarkupProcessData& mar
         ProcessTagForRun<FontDescriptionRun>(
           markupProcessData.fontRuns, styleStack, tag, characterIndex, fontRunIndex, fontTagReference, [](const Tag& tag, FontDescriptionRun& fontRun) { ProcessFontTag(tag, fontRun); });
       } // <font></font>
+      else if(TokenComparison(XHTML_ANCHOR_TAG, tag.buffer, tag.length))
+      {
+        /* Anchor */
+        ProcessAnchorTag(markupProcessData, tag, characterIndex);
+        /* Color */
+        ProcessTagForRun<ColorRun>(
+          markupProcessData.colorRuns, styleStack, tag, characterIndex, colorRunIndex, colorTagReference, [](const Tag& tag, ColorRun& run) {
+            run.color = Color::BLUE;
+            ProcessColorTag(tag, run);
+          });
+        /* TODO - underline */
+      } // <a href=https://www.tizen.org>tizen</a>
       else if(TokenComparison(XHTML_SHADOW_TAG, tag.buffer, tag.length))
       {
         // TODO: If !tag.isEndTag, then create a new shadow run.
index fee7ae1..c75ca0c 100644 (file)
@@ -23,6 +23,7 @@
 #include <string>
 
 // INTERNAL INCLUDES
+#include <dali-toolkit/internal/text/anchor.h>
 #include <dali-toolkit/internal/text/color-run.h>
 #include <dali-toolkit/internal/text/embedded-item.h>
 #include <dali-toolkit/internal/text/font-description-run.h>
@@ -40,10 +41,12 @@ struct MarkupProcessData
 {
   MarkupProcessData(Vector<ColorRun>&           colorRuns,
                     Vector<FontDescriptionRun>& fontRuns,
-                    Vector<EmbeddedItem>&       items)
+                    Vector<EmbeddedItem>&       items,
+                    Vector<Anchor>&             anchors)
   : colorRuns(colorRuns),
     fontRuns(fontRuns),
     items(items),
+    anchors(anchors),
     markupProcessedText()
   {
   }
@@ -51,6 +54,7 @@ struct MarkupProcessData
   Vector<ColorRun>&           colorRuns;           ///< The color runs.
   Vector<FontDescriptionRun>& fontRuns;            ///< The font description runs.
   Vector<EmbeddedItem>&       items;               ///< The embedded items.
+  Vector<Anchor>&             anchors;             ///< The anchors.
   std::string                 markupProcessedText; ///< The mark-up string.
 };
 
diff --git a/dali-toolkit/internal/text/text-anchor-control-interface.h b/dali-toolkit/internal/text/text-anchor-control-interface.h
new file mode 100644 (file)
index 0000000..5c6a84f
--- /dev/null
@@ -0,0 +1,50 @@
+#ifndef DALI_TOOLKIT_TEXT_ANCHOR_CONTROL_INTERFACE_H
+#define DALI_TOOLKIT_TEXT_ANCHOR_CONTROL_INTERFACE_H
+
+/*
+ * Copyright (c) 2021 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.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+namespace Dali
+{
+namespace Toolkit
+{
+namespace Text
+{
+/**
+ * @brief An interface that the Text::Controller used for anchor functionality.
+ */
+class AnchorControlInterface
+{
+public:
+  /**
+   * @brief Virtual destructor.
+   */
+  virtual ~AnchorControlInterface() = default;
+
+  /**
+   * @brief Called to signal that anchor has been clicked.
+   */
+  virtual void AnchorClicked(const std::string& href) = 0;
+};
+
+} // namespace Text
+
+} // namespace Toolkit
+
+} // namespace Dali
+
+#endif // DALI_TOOLKIT_TEXT_ANCHOR_CONTROL_INTERFACE_H
index ce0bf59..c17c820 100644 (file)
@@ -24,6 +24,7 @@
 #include <dali/integration-api/debug.h>
 
 // INTERNAL INCLUDES
+#include <dali-toolkit/internal/text/cursor-helper-functions.h>
 #include <dali-toolkit/internal/text/text-controller-impl.h>
 #include <dali-toolkit/internal/text/text-editable-control-interface.h>
 
@@ -293,6 +294,46 @@ bool Controller::EventHandler::KeyEvent(Controller& controller, const Dali::KeyE
   return true;
 }
 
+void Controller::EventHandler::AnchorEvent(Controller& controller, float x, float y)
+{
+  if(!controller.mImpl->mMarkupProcessorEnabled ||
+     !controller.mImpl->mModel->mLogicalModel->mAnchors.Count() ||
+     !controller.mImpl->IsShowingRealText())
+  {
+    return;
+  }
+
+  CharacterIndex cursorPosition = 0u;
+
+  // Convert from control's coords to text's coords.
+  const float xPosition = x - controller.mImpl->mModel->mScrollPosition.x;
+  const float yPosition = y - controller.mImpl->mModel->mScrollPosition.y;
+
+  // Whether to touch point hits on a glyph.
+  bool matchedCharacter = false;
+  cursorPosition        = Text::GetClosestCursorIndex(controller.mImpl->mModel->mVisualModel,
+                                               controller.mImpl->mModel->mLogicalModel,
+                                               controller.mImpl->mMetrics,
+                                               xPosition,
+                                               yPosition,
+                                               CharacterHitTest::TAP,
+                                               matchedCharacter);
+
+  for(const auto& anchor : controller.mImpl->mModel->mLogicalModel->mAnchors)
+  {
+    // Anchor clicked if the calculated cursor position is within the range of anchor.
+    if(cursorPosition >= anchor.startIndex && cursorPosition < anchor.endIndex)
+    {
+      if(controller.mImpl->mAnchorControlInterface && anchor.href)
+      {
+        std::string href(anchor.href);
+        controller.mImpl->mAnchorControlInterface->AnchorClicked(href);
+        break;
+      }
+    }
+  }
+}
+
 void Controller::EventHandler::TapEvent(Controller& controller, unsigned int tapCount, float x, float y)
 {
   DALI_ASSERT_DEBUG(controller.mImpl->mEventData && "Unexpected TapEvent");
index 67902fb..092ac58 100644 (file)
@@ -40,6 +40,7 @@ struct Controller::EventHandler
   static void KeyboardFocusGainEvent(Controller& controller);
   static void KeyboardFocusLostEvent(Controller& controller);
   static bool KeyEvent(Controller& controller, const Dali::KeyEvent& keyEvent);
+  static void AnchorEvent(Controller& controller, float x, float y);
   static void TapEvent(Controller& controller, unsigned int tapCount, float x, float y);
   static void PanEvent(Controller& controller, GestureState state, const Vector2& displacement);
   static void LongPressEvent(Controller& controller, GestureState state, float x, float y);
index 414faf5..ef4bb69 100644 (file)
@@ -49,6 +49,7 @@ struct ControllerImplEventHandler;
 struct SelectionHandleController;
 
 class SelectableControlInterface;
+class AnchorControlInterface;
 
 struct Event
 {
@@ -314,10 +315,12 @@ struct Controller::Impl
 {
   Impl(ControlInterface*           controlInterface,
        EditableControlInterface*   editableControlInterface,
-       SelectableControlInterface* selectableControlInterface)
+       SelectableControlInterface* selectableControlInterface,
+       AnchorControlInterface*     anchorControlInterface)
   : mControlInterface(controlInterface),
     mEditableControlInterface(editableControlInterface),
     mSelectableControlInterface(selectableControlInterface),
+    mAnchorControlInterface(anchorControlInterface),
     mModel(),
     mFontDefaults(NULL),
     mUnderlineDefaults(NULL),
@@ -786,6 +789,7 @@ public:
   ControlInterface*           mControlInterface;           ///< Reference to the text controller.
   EditableControlInterface*   mEditableControlInterface;   ///< Reference to the editable text controller.
   SelectableControlInterface* mSelectableControlInterface; ///< Reference to the selectable text controller.
+  AnchorControlInterface*     mAnchorControlInterface;     ///< Reference to the anchor controller.
   ModelPtr                    mModel;                      ///< Pointer to the text's model.
   FontDefaults*               mFontDefaults;               ///< Avoid allocating this when the user does not specify a font.
   UnderlineDefaults*          mUnderlineDefaults;          ///< Avoid allocating this when the user does not specify underline parameters.
index 6fb472e..69e05bf 100644 (file)
@@ -81,7 +81,8 @@ void Controller::TextUpdater::SetText(Controller& controller, const std::string&
 
     MarkupProcessData markupProcessData(logicalModel->mColorRuns,
                                         logicalModel->mFontDescriptionRuns,
-                                        logicalModel->mEmbeddedItems);
+                                        logicalModel->mEmbeddedItems,
+                                        logicalModel->mAnchors);
 
     Length         textSize = 0u;
     const uint8_t* utf8     = NULL;
@@ -377,6 +378,11 @@ void Controller::TextUpdater::InsertText(Controller& controller, const std::stri
       textUpdateInfo.mNumberOfCharactersToAdd += maxSizeOfNewText;
     }
 
+    if(impl.mMarkupProcessorEnabled)
+    {
+      InsertTextAnchor(controller, maxSizeOfNewText, cursorIndex);
+    }
+
     // Update the cursor index.
     cursorIndex += maxSizeOfNewText;
 
@@ -461,8 +467,8 @@ bool Controller::TextUpdater::RemoveText(
   if(!impl.IsShowingPlaceholderText())
   {
     // Delete at current cursor position
-    Vector<Character>& currentText    = logicalModel->mText;
-    CharacterIndex&    oldCursorIndex = eventData->mPrimaryCursorPosition;
+    Vector<Character>& currentText         = logicalModel->mText;
+    CharacterIndex&    previousCursorIndex = eventData->mPrimaryCursorPosition;
 
     CharacterIndex cursorIndex = 0;
 
@@ -547,8 +553,13 @@ bool Controller::TextUpdater::RemoveText(
 
       currentText.Erase(first, last);
 
+      if(impl.mMarkupProcessorEnabled)
+      {
+        RemoveTextAnchor(controller, cursorOffset, numberOfCharacters, previousCursorIndex);
+      }
+
       // Cursor position retreat
-      oldCursorIndex = cursorIndex;
+      previousCursorIndex = cursorIndex;
 
       eventData->mScrollAfterDelete = true;
 
@@ -580,6 +591,16 @@ bool Controller::TextUpdater::RemoveSelectedText(Controller& controller)
     {
       textRemoved = true;
       impl.ChangeState(EventData::EDITING);
+
+      if(impl.mMarkupProcessorEnabled)
+      {
+        int             cursorOffset        = -1;
+        int             numberOfCharacters  = removedString.length();
+        CharacterIndex& cursorIndex         = impl.mEventData->mPrimaryCursorPosition;
+        CharacterIndex  previousCursorIndex = cursorIndex + numberOfCharacters;
+
+        RemoveTextAnchor(controller, cursorOffset, numberOfCharacters, previousCursorIndex);
+      }
     }
   }
 
@@ -597,6 +618,9 @@ void Controller::TextUpdater::ResetText(Controller& controller)
   // Reset the embedded images buffer.
   logicalModel->ClearEmbeddedImages();
 
+  // Reset the anchors buffer.
+  logicalModel->ClearAnchors();
+
   // We have cleared everything including the placeholder-text
   impl.PlaceholderCleared();
 
@@ -617,6 +641,128 @@ void Controller::TextUpdater::ResetText(Controller& controller)
   impl.mOperationsPending = ALL_OPERATIONS;
 }
 
+void Controller::TextUpdater::InsertTextAnchor(Controller& controller, int numberOfCharacters, CharacterIndex previousCursorIndex)
+{
+  Controller::Impl& impl         = *controller.mImpl;
+  ModelPtr&         model        = impl.mModel;
+  LogicalModelPtr&  logicalModel = model->mLogicalModel;
+
+  for(auto& anchor : logicalModel->mAnchors)
+  {
+    if(anchor.endIndex < previousCursorIndex) //      [anchor]  CUR
+    {
+      continue;
+    }
+    if(anchor.startIndex < previousCursorIndex) //      [anCURr]
+    {
+      anchor.endIndex += numberOfCharacters;
+    }
+    else // CUR  [anchor]
+    {
+      anchor.startIndex += numberOfCharacters;
+      anchor.endIndex += numberOfCharacters;
+    }
+    DALI_LOG_INFO(gLogFilter, Debug::General, "Controller::InsertTextAnchor[%p] Anchor[%s] start[%d] end[%d]\n", &controller, anchor.href, anchor.startIndex, anchor.endIndex);
+  }
+}
+
+void Controller::TextUpdater::RemoveTextAnchor(Controller& controller, int cursorOffset, int numberOfCharacters, CharacterIndex previousCursorIndex)
+{
+  Controller::Impl&        impl         = *controller.mImpl;
+  ModelPtr&                model        = impl.mModel;
+  LogicalModelPtr&         logicalModel = model->mLogicalModel;
+  Vector<Anchor>::Iterator it           = logicalModel->mAnchors.Begin();
+
+  while(it != logicalModel->mAnchors.End())
+  {
+    Anchor& anchor = *it;
+
+    if(anchor.endIndex <= previousCursorIndex && cursorOffset == 0) // [anchor]    CUR >>
+    {
+      // Nothing happens.
+    }
+    else if(anchor.endIndex <= previousCursorIndex && cursorOffset == -1) // [anchor] << CUR
+    {
+      int endIndex = anchor.endIndex;
+      int offset   = previousCursorIndex - endIndex;
+      int index    = endIndex - (numberOfCharacters - offset);
+
+      if(index < endIndex)
+      {
+        endIndex = index;
+      }
+
+      if((int)anchor.startIndex >= endIndex)
+      {
+        if(anchor.href)
+        {
+          delete[] anchor.href;
+        }
+        it = logicalModel->mAnchors.Erase(it);
+        continue;
+      }
+      else
+      {
+        anchor.endIndex = endIndex;
+      }
+    }
+    else if(anchor.startIndex >= previousCursorIndex && cursorOffset == -1) // << CUR    [anchor]
+    {
+      anchor.startIndex -= numberOfCharacters;
+      anchor.endIndex -= numberOfCharacters;
+    }
+    else if(anchor.startIndex >= previousCursorIndex && cursorOffset == 0) //    CUR >> [anchor]
+    {
+      int startIndex = anchor.startIndex;
+      int endIndex   = anchor.endIndex;
+      int index      = previousCursorIndex + numberOfCharacters - 1;
+
+      if(startIndex > index)
+      {
+        anchor.startIndex -= numberOfCharacters;
+        anchor.endIndex -= numberOfCharacters;
+      }
+      else if(endIndex > index + 1)
+      {
+        anchor.endIndex -= numberOfCharacters;
+      }
+      else
+      {
+        if(anchor.href)
+        {
+          delete[] anchor.href;
+        }
+        it = logicalModel->mAnchors.Erase(it);
+        continue;
+      }
+    }
+    else if(cursorOffset == -1) // [<< CUR]
+    {
+      int startIndex = anchor.startIndex;
+      int index      = previousCursorIndex - numberOfCharacters;
+
+      if(startIndex >= index)
+      {
+        anchor.startIndex = index;
+      }
+      anchor.endIndex -= numberOfCharacters;
+    }
+    else if(cursorOffset == 0) // [CUR >>]
+    {
+      anchor.endIndex -= numberOfCharacters;
+    }
+    else
+    {
+      // When this condition is reached, someting is wrong.
+      DALI_LOG_ERROR("Controller::RemoveTextAnchor[%p] Invaild state cursorOffset[%d]\n", &controller, cursorOffset);
+    }
+
+    DALI_LOG_INFO(gLogFilter, Debug::General, "Controller::RemoveTextAnchor[%p] Anchor[%s] start[%d] end[%d]\n", &controller, anchor.href, anchor.startIndex, anchor.endIndex);
+
+    it++;
+  }
+}
+
 } // namespace Text
 
 } // namespace Toolkit
index ba7e3b9..f512940 100644 (file)
@@ -58,6 +58,14 @@ struct Controller::TextUpdater
   /// @copydoc Text::Contoller::ResetText
   /// @param[in] controller The controller
   static void ResetText(Controller& controller);
+
+  /// @copydoc Text::Contoller::InsertTextAnchor
+  /// @param[in] controller The controller
+  static void InsertTextAnchor(Controller& controller, int numberOfCharacters, CharacterIndex previousCursorIndex);
+
+  /// @copydoc Text::Contoller::RemoveTextAnchor
+  /// @param[in] controller The controller
+  static void RemoveTextAnchor(Controller& controller, int cursorOffset, int numberOfCharacters, CharacterIndex previousCursorIndex);
 };
 
 } // namespace Text
index f29ab22..15db50b 100644 (file)
@@ -75,11 +75,13 @@ ControllerPtr Controller::New(ControlInterface* controlInterface)
 
 ControllerPtr Controller::New(ControlInterface*           controlInterface,
                               EditableControlInterface*   editableControlInterface,
-                              SelectableControlInterface* selectableControlInterface)
+                              SelectableControlInterface* selectableControlInterface,
+                              AnchorControlInterface*     anchorControlInterface)
 {
   return ControllerPtr(new Controller(controlInterface,
                                       editableControlInterface,
-                                      selectableControlInterface));
+                                      selectableControlInterface,
+                                      anchorControlInterface));
 }
 
 // public : Configure the text controller.
@@ -1722,6 +1724,11 @@ bool Controller::KeyEvent(const Dali::KeyEvent& keyEvent)
   return EventHandler::KeyEvent(*this, keyEvent);
 }
 
+void Controller::AnchorEvent(float x, float y)
+{
+  EventHandler::AnchorEvent(*this, x, y);
+}
+
 void Controller::TapEvent(unsigned int tapCount, float x, float y)
 {
   EventHandler::TapEvent(*this, tapCount, x, y);
@@ -1936,6 +1943,19 @@ bool Controller::RemoveSelectedText()
   return TextUpdater::RemoveSelectedText(*this);
 }
 
+void Controller::InsertTextAnchor(int            numberOfCharacters,
+                                  CharacterIndex previousCursorIndex)
+{
+  TextUpdater::InsertTextAnchor(*this, numberOfCharacters, previousCursorIndex);
+}
+
+void Controller::RemoveTextAnchor(int            cursorOffset,
+                                  int            numberOfCharacters,
+                                  CharacterIndex previousCursorIndex)
+{
+  TextUpdater::RemoveTextAnchor(*this, cursorOffset, numberOfCharacters, previousCursorIndex);
+}
+
 // private : Relayout.
 
 bool Controller::DoRelayout(const Size&    size,
@@ -2060,6 +2080,11 @@ void Controller::SetControlInterface(ControlInterface* controlInterface)
   mImpl->mControlInterface = controlInterface;
 }
 
+void Controller::SetAnchorControlInterface(AnchorControlInterface* anchorControlInterface)
+{
+  mImpl->mAnchorControlInterface = anchorControlInterface;
+}
+
 bool Controller::ShouldClearFocusOnEscape() const
 {
   return mImpl->mShouldClearFocusOnEscape;
@@ -2073,19 +2098,20 @@ Actor Controller::CreateBackgroundActor()
 // private : Private contructors & copy operator.
 
 Controller::Controller()
-: Controller(nullptr, nullptr, nullptr)
+: Controller(nullptr, nullptr, nullptr, nullptr)
 {
 }
 
 Controller::Controller(ControlInterface* controlInterface)
-: Controller(controlInterface, nullptr, nullptr)
+: Controller(controlInterface, nullptr, nullptr, nullptr)
 {
 }
 
 Controller::Controller(ControlInterface*           controlInterface,
                        EditableControlInterface*   editableControlInterface,
-                       SelectableControlInterface* selectableControlInterface)
-: mImpl(new Controller::Impl(controlInterface, editableControlInterface, selectableControlInterface))
+                       SelectableControlInterface* selectableControlInterface,
+                       AnchorControlInterface*     anchorControlInterface)
+: mImpl(new Controller::Impl(controlInterface, editableControlInterface, selectableControlInterface, anchorControlInterface))
 {
 }
 
index 40286ef..aaa3a66 100644 (file)
@@ -29,6 +29,7 @@
 #include <dali-toolkit/internal/text/decorator/text-decorator.h>
 #include <dali-toolkit/internal/text/hidden-text.h>
 #include <dali-toolkit/internal/text/layouts/layout-engine.h>
+#include <dali-toolkit/internal/text/text-anchor-control-interface.h>
 #include <dali-toolkit/internal/text/text-model-interface.h>
 #include <dali-toolkit/internal/text/text-selectable-control-interface.h>
 #include <dali-toolkit/public-api/text/text-enumerations.h>
@@ -184,12 +185,14 @@ public: // Constructor.
    * @param[in] controlInterface The control's interface.
    * @param[in] editableControlInterface The editable control's interface.
    * @param[in] selectableControlInterface The selectable control's interface.
+   * @param[in] anchorControlInterface The anchor control's interface.
    *
    * @return A pointer to a new Controller.
    */
   static ControllerPtr New(ControlInterface*           controlInterface,
                            EditableControlInterface*   editableControlInterface,
-                           SelectableControlInterface* selectableControlInterface);
+                           SelectableControlInterface* selectableControlInterface,
+                           AnchorControlInterface*     anchorControlInterface);
 
 public: // Configure the text controller.
   /**
@@ -1295,6 +1298,13 @@ public: // Default style & Input style
    */
   void SetControlInterface(ControlInterface* controlInterface);
 
+  /**
+   * @brief Set the anchor control's interface.
+   *
+   * @param[in] anchorControlInterface The control's interface.
+   */
+  void SetAnchorControlInterface(AnchorControlInterface* anchorControlInterface);
+
 public: // Queries & retrieves.
   /**
    * @brief Return the layout engine.
@@ -1494,6 +1504,13 @@ public: // Text-input Event Queuing.
   bool KeyEvent(const Dali::KeyEvent& event);
 
   /**
+   * @brief Called by anchor when a tap gesture occurs.
+   * @param[in] x The x position relative to the top-left of the parent control.
+   * @param[in] y The y position relative to the top-left of the parent control.
+   */
+  void AnchorEvent(float x, float y);
+
+  /**
    * @brief Called by editable UI controls when a tap gesture occurs.
    * @param[in] tapCount The number of taps.
    * @param[in] x The x position relative to the top-left of the parent control.
@@ -1700,6 +1717,26 @@ private: // Update.
    */
   bool RemoveSelectedText();
 
+  /**
+   * @brief Update anchor position from given number of inserted characters.
+   *
+   * @param[in] numberOfCharacters The number of inserted characters.
+   * @param[in] previousCursorIndex A cursor position before event occurs.
+   */
+  void InsertTextAnchor(int            numberOfCharacters,
+                        CharacterIndex previousCursorIndex);
+
+  /**
+   * @brief Update anchor position from given number of removed characters.
+   *
+   * @param[in] cursorOffset Start position from the current cursor position to start deleting characters.
+   * @param[in] numberOfCharacters The number of removed characters.
+   * @param[in] previousCursorIndex A cursor position before event occurs.
+   */
+  void RemoveTextAnchor(int            cursorOffset,
+                        int            numberOfCharacters,
+                        CharacterIndex previousCursorIndex);
+
 private: // Relayout.
   /**
    * @brief Lays-out the text.
@@ -1792,7 +1829,8 @@ private: // Private contructors & copy operator.
    */
   Controller(ControlInterface*           controlInterface,
              EditableControlInterface*   editableControlInterface,
-             SelectableControlInterface* selectableControlInterface);
+             SelectableControlInterface* selectableControlInterface,
+             AnchorControlInterface*     anchorControlInterface);
 
   // Undefined
   Controller(const Controller& handle);