From 3feac03362a8bb2d90a8b3e5defe9d2d704d2d3c Mon Sep 17 00:00:00 2001 From: Bowon Ryu Date: Fri, 5 Feb 2021 19:42:37 +0900 Subject: [PATCH] Support anchor to TextLabel, TextField, TextEditor example: TIZEN using AnchorClickedSignal(), users can get the anchor's href when clicking on the anchor. Change-Id: I1c61c285c95295aea3c27de2bedd586211a0f036 Signed-off-by: Bowon Ryu --- .../dali-toolkit-test-utils/toolkit-text-utils.cpp | 3 +- .../dali-toolkit-internal/utc-Dali-Text-Markup.cpp | 3 +- .../src/dali-toolkit/utc-Dali-TextEditor.cpp | 60 ++++ .../src/dali-toolkit/utc-Dali-TextField.cpp | 311 +++++++++++++++++++++ .../src/dali-toolkit/utc-Dali-TextLabel.cpp | 77 ++++- .../controls/text-controls/text-editor-devel.cpp | 7 +- .../controls/text-controls/text-editor-devel.h | 21 ++ .../controls/text-controls/text-field-devel.cpp | 7 +- .../controls/text-controls/text-field-devel.h | 21 ++ .../controls/text-controls/text-label-devel.cpp | 37 +++ .../controls/text-controls/text-label-devel.h | 23 +- dali-toolkit/devel-api/file.list | 1 + dali-toolkit/devel-api/text/text-utils-devel.cpp | 3 +- .../controls/text-controls/text-editor-impl.cpp | 23 +- .../controls/text-controls/text-editor-impl.h | 19 +- .../controls/text-controls/text-field-impl.cpp | 23 +- .../controls/text-controls/text-field-impl.h | 23 +- .../controls/text-controls/text-label-impl.cpp | 54 ++++ .../controls/text-controls/text-label-impl.h | 33 ++- dali-toolkit/internal/file.list | 1 + dali-toolkit/internal/text/anchor.h | 46 +++ dali-toolkit/internal/text/logical-model-impl.cpp | 15 + dali-toolkit/internal/text/logical-model-impl.h | 7 + .../internal/text/markup-processor-anchor.cpp | 61 ++++ .../internal/text/markup-processor-anchor.h | 47 ++++ dali-toolkit/internal/text/markup-processor.cpp | 46 +++ dali-toolkit/internal/text/markup-processor.h | 6 +- .../internal/text/text-anchor-control-interface.h | 50 ++++ .../text/text-controller-event-handler.cpp | 41 +++ .../internal/text/text-controller-event-handler.h | 1 + dali-toolkit/internal/text/text-controller-impl.h | 6 +- .../internal/text/text-controller-text-updater.cpp | 154 +++++++++- .../internal/text/text-controller-text-updater.h | 8 + dali-toolkit/internal/text/text-controller.cpp | 38 ++- dali-toolkit/internal/text/text-controller.h | 42 ++- 35 files changed, 1288 insertions(+), 30 deletions(-) create mode 100644 dali-toolkit/devel-api/controls/text-controls/text-label-devel.cpp create mode 100644 dali-toolkit/internal/text/anchor.h create mode 100644 dali-toolkit/internal/text/markup-processor-anchor.cpp create mode 100644 dali-toolkit/internal/text/markup-processor-anchor.h create mode 100644 dali-toolkit/internal/text/text-anchor-control-interface.h diff --git a/automated-tests/src/dali-toolkit-internal/dali-toolkit-test-utils/toolkit-text-utils.cpp b/automated-tests/src/dali-toolkit-internal/dali-toolkit-test-utils/toolkit-text-utils.cpp index b7a1122..d98f397 100755 --- a/automated-tests/src/dali-toolkit-internal/dali-toolkit-test-utils/toolkit-text-utils.cpp +++ b/automated-tests/src/dali-toolkit-internal/dali-toolkit-test-utils/toolkit-text-utils.cpp @@ -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; diff --git a/automated-tests/src/dali-toolkit-internal/utc-Dali-Text-Markup.cpp b/automated-tests/src/dali-toolkit-internal/utc-Dali-Text-Markup.cpp index 779eb56..6e2d632 100755 --- a/automated-tests/src/dali-toolkit-internal/utc-Dali-Text-Markup.cpp +++ b/automated-tests/src/dali-toolkit-internal/utc-Dali-Text-Markup.cpp @@ -184,7 +184,8 @@ namespace Vector colorRuns; Vector fontRuns; Vector items; - MarkupProcessData markupProcessData( colorRuns, fontRuns, items ); + Vector anchors; + MarkupProcessData markupProcessData( colorRuns, fontRuns, items, anchors ); ProcessMarkupString( data.xHTMLEntityString, markupProcessData ); for( Vector::Iterator it = items.Begin(), diff --git a/automated-tests/src/dali-toolkit/utc-Dali-TextEditor.cpp b/automated-tests/src/dali-toolkit/utc-Dali-TextEditor.cpp index af5a453..4e26365 100644 --- a/automated-tests/src/dali-toolkit/utc-Dali-TextEditor.cpp +++ b/automated-tests/src/dali-toolkit/utc-Dali-TextEditor.cpp @@ -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, "TIZEN"); + 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) { diff --git a/automated-tests/src/dali-toolkit/utc-Dali-TextField.cpp b/automated-tests/src/dali-toolkit/utc-Dali-TextField.cpp index 05d0009..aa25cf2 100644 --- a/automated-tests/src/dali-toolkit/utc-Dali-TextField.cpp +++ b/automated-tests/src/dali-toolkit/utc-Dali-TextField.cpp @@ -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, "TIZEN"); + 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, "TIZEN"); + 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, "TIZEN"); + 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, "TIZEN"); + 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, "TIZEN"); + 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, "TIZEN"); + 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, "TIZEN"); + 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, "TIZEN"); + 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, "TIZEN"); + 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, "TIZEN"); + 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, "TIZEN"); + 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, "TIZENTIZEN"); + 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, "TIZEN"); + 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, "T"); + 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, "T"); + 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) { diff --git a/automated-tests/src/dali-toolkit/utc-Dali-TextLabel.cpp b/automated-tests/src/dali-toolkit/utc-Dali-TextLabel.cpp index 57862f4..ecefef7 100644 --- a/automated-tests/src/dali-toolkit/utc-Dali-TextLabel.cpp +++ b/automated-tests/src/dali-toolkit/utc-Dali-TextLabel.cpp @@ -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& indexConversionTable = std::vector() ) { 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, "TIZEN"); + 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; +} diff --git a/dali-toolkit/devel-api/controls/text-controls/text-editor-devel.cpp b/dali-toolkit/devel-api/controls/text-controls/text-editor-devel.cpp index abe3389..37678d8 100644 --- a/dali-toolkit/devel-api/controls/text-controls/text-editor-devel.cpp +++ b/dali-toolkit/devel-api/controls/text-controls/text-editor-devel.cpp @@ -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(); diff --git a/dali-toolkit/devel-api/controls/text-controls/text-editor-devel.h b/dali-toolkit/devel-api/controls/text-controls/text-editor-devel.h index 77ed805..c73c582 100644 --- a/dali-toolkit/devel-api/controls/text-controls/text-editor-devel.h +++ b/dali-toolkit/devel-api/controls/text-controls/text-editor-devel.h @@ -217,6 +217,27 @@ using MaxLengthReachedSignalType = Signal; 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; + +/** + * @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. diff --git a/dali-toolkit/devel-api/controls/text-controls/text-field-devel.cpp b/dali-toolkit/devel-api/controls/text-controls/text-field-devel.cpp index 29c3730..fdfac30 100644 --- a/dali-toolkit/devel-api/controls/text-controls/text-field-devel.cpp +++ b/dali-toolkit/devel-api/controls/text-controls/text-field-devel.cpp @@ -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(); diff --git a/dali-toolkit/devel-api/controls/text-controls/text-field-devel.h b/dali-toolkit/devel-api/controls/text-controls/text-field-devel.h index ac239ef..3367bec 100644 --- a/dali-toolkit/devel-api/controls/text-controls/text-field-devel.h +++ b/dali-toolkit/devel-api/controls/text-controls/text-field-devel.h @@ -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; + +/** + * @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 index 0000000..0d6cd0b --- /dev/null +++ b/dali-toolkit/devel-api/controls/text-controls/text-label-devel.cpp @@ -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 +#include + +namespace Dali +{ +namespace Toolkit +{ +namespace DevelTextLabel +{ +AnchorClickedSignalType& AnchorClickedSignal(TextLabel textLabel) +{ + return GetImpl(textLabel).AnchorClickedSignal(); +} + +} // namespace DevelTextLabel + +} // namespace Toolkit + +} // namespace Dali diff --git a/dali-toolkit/devel-api/controls/text-controls/text-label-devel.h b/dali-toolkit/devel-api/controls/text-controls/text-label-devel.h index 9b3e4db..efdad10 100644 --- a/dali-toolkit/devel-api/controls/text-controls/text-label-devel.h +++ b/dali-toolkit/devel-api/controls/text-controls/text-label-devel.h @@ -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; + +/** + * @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 diff --git a/dali-toolkit/devel-api/file.list b/dali-toolkit/devel-api/file.list index cc7dd45..5516ac8 100755 --- a/dali-toolkit/devel-api/file.list +++ b/dali-toolkit/devel-api/file.list @@ -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 diff --git a/dali-toolkit/devel-api/text/text-utils-devel.cpp b/dali-toolkit/devel-api/text/text-utils-devel.cpp index 0112e32..1441dc4 100644 --- a/dali-toolkit/devel-api/text/text-utils-devel.cpp +++ b/dali-toolkit/devel-api/text/text-utils-devel.cpp @@ -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) { diff --git a/dali-toolkit/internal/controls/text-controls/text-editor-impl.cpp b/dali-toolkit/internal/controls/text-controls/text-editor-impl.cpp index 82f6975..75677ca 100644 --- a/dali-toolkit/internal/controls/text-controls/text-editor-impl.cpp +++ b/dali-toolkit/internal/controls/text-controls/text-editor-impl.cpp @@ -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(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) diff --git a/dali-toolkit/internal/controls/text-controls/text-editor-impl.h b/dali-toolkit/internal/controls/text-controls/text-editor-impl.h index a8b9a0c..d0609b6 100644 --- a/dali-toolkit/internal/controls/text-controls/text-editor-impl.h +++ b/dali-toolkit/internal/controls/text-controls/text-editor-impl.h @@ -31,6 +31,7 @@ #include #include #include +#include #include #include #include @@ -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; diff --git a/dali-toolkit/internal/controls/text-controls/text-field-impl.cpp b/dali-toolkit/internal/controls/text-controls/text-field-impl.cpp index d12a708..6025a55 100644 --- a/dali-toolkit/internal/controls/text-controls/text-field-impl.cpp +++ b/dali-toolkit/internal/controls/text-controls/text-field-impl.cpp @@ -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(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) diff --git a/dali-toolkit/internal/controls/text-controls/text-field-impl.h b/dali-toolkit/internal/controls/text-controls/text-field-impl.h index 8ddc227..670b81c 100644 --- a/dali-toolkit/internal/controls/text-controls/text-field-impl.h +++ b/dali-toolkit/internal/controls/text-controls/text-field-impl.h @@ -24,9 +24,11 @@ // INTERNAL INCLUDES #include +#include #include #include #include +#include #include #include #include @@ -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; diff --git a/dali-toolkit/internal/controls/text-controls/text-label-impl.cpp b/dali-toolkit/internal/controls/text-controls/text-label-impl.cpp index 3ee507d..2e8c7ae 100644 --- a/dali-toolkit/internal/controls/text-controls/text-label-impl.cpp +++ b/dali-toolkit/internal/controls/text-controls/text-label-impl.cpp @@ -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(stage.GetRootLayer().GetProperty(Dali::Actor::Property::LAYOUT_DIRECTION).Get()); mController->SetLayoutDirection(layoutDirection); + // Forward input events to controller + EnableGestureDetection(static_cast(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(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; diff --git a/dali-toolkit/internal/controls/text-controls/text-label-impl.h b/dali-toolkit/internal/controls/text-controls/text-label-impl.h index e9ad7a7..a204be6 100644 --- a/dali-toolkit/internal/controls/text-controls/text-label-impl.h +++ b/dali-toolkit/internal/controls/text-controls/text-label-impl.h @@ -24,6 +24,7 @@ // INTERNAL INCLUDES #include #include +#include #include #include #include @@ -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; diff --git a/dali-toolkit/internal/file.list b/dali-toolkit/internal/file.list index 4ac8dea..ede26c1 100644 --- a/dali-toolkit/internal/file.list +++ b/dali-toolkit/internal/file.list @@ -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 index 0000000..d2c7384 --- /dev/null +++ b/dali-toolkit/internal/text/anchor.h @@ -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 + +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 diff --git a/dali-toolkit/internal/text/logical-model-impl.cpp b/dali-toolkit/internal/text/logical-model-impl.cpp index 8ecab1f..12929fc 100644 --- a/dali-toolkit/internal/text/logical-model-impl.cpp +++ b/dali-toolkit/internal/text/logical-model-impl.cpp @@ -55,6 +55,16 @@ void FreeEmbeddedItems(Vector& embeddedItem) embeddedItem.Clear(); } +void FreeAnchors(Vector& 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(); diff --git a/dali-toolkit/internal/text/logical-model-impl.h b/dali-toolkit/internal/text/logical-model-impl.h index 4702c38..921d911 100644 --- a/dali-toolkit/internal/text/logical-model-impl.h +++ b/dali-toolkit/internal/text/logical-model-impl.h @@ -24,6 +24,7 @@ #include // INTERNAL INCLUDES +#include #include #include #include @@ -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 mCharacterDirections; ///< For each character, whether is right to left. ( @e flase is left to right, @e true right to left ). Vector mBidirectionalLineInfo; Vector mEmbeddedItems; + Vector 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 index 0000000..4c4bb29 --- /dev/null +++ b/dali-toolkit/internal/text/markup-processor-anchor.cpp @@ -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 + +// EXTERNAL INCLUDES +#include +#include + +// INTERNAL INCLUDES +#include +#include + +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 index 0000000..7c1d181 --- /dev/null +++ b/dali-toolkit/internal/text/markup-processor-anchor.h @@ -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 diff --git a/dali-toolkit/internal/text/markup-processor.cpp b/dali-toolkit/internal/text/markup-processor.cpp index 20a5825..a8a6ecc 100644 --- a/dali-toolkit/internal/text/markup-processor.cpp +++ b/dali-toolkit/internal/text/markup-processor.cpp @@ -25,6 +25,7 @@ // INTERNAL INCLUDES #include +#include #include #include #include @@ -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( markupProcessData.fontRuns, styleStack, tag, characterIndex, fontRunIndex, fontTagReference, [](const Tag& tag, FontDescriptionRun& fontRun) { ProcessFontTag(tag, fontRun); }); } // + else if(TokenComparison(XHTML_ANCHOR_TAG, tag.buffer, tag.length)) + { + /* Anchor */ + ProcessAnchorTag(markupProcessData, tag, characterIndex); + /* Color */ + ProcessTagForRun( + markupProcessData.colorRuns, styleStack, tag, characterIndex, colorRunIndex, colorTagReference, [](const Tag& tag, ColorRun& run) { + run.color = Color::BLUE; + ProcessColorTag(tag, run); + }); + /* TODO - underline */ + } // tizen else if(TokenComparison(XHTML_SHADOW_TAG, tag.buffer, tag.length)) { // TODO: If !tag.isEndTag, then create a new shadow run. diff --git a/dali-toolkit/internal/text/markup-processor.h b/dali-toolkit/internal/text/markup-processor.h index fee7ae1..c75ca0c 100644 --- a/dali-toolkit/internal/text/markup-processor.h +++ b/dali-toolkit/internal/text/markup-processor.h @@ -23,6 +23,7 @@ #include // INTERNAL INCLUDES +#include #include #include #include @@ -40,10 +41,12 @@ struct MarkupProcessData { MarkupProcessData(Vector& colorRuns, Vector& fontRuns, - Vector& items) + Vector& items, + Vector& anchors) : colorRuns(colorRuns), fontRuns(fontRuns), items(items), + anchors(anchors), markupProcessedText() { } @@ -51,6 +54,7 @@ struct MarkupProcessData Vector& colorRuns; ///< The color runs. Vector& fontRuns; ///< The font description runs. Vector& items; ///< The embedded items. + Vector& 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 index 0000000..5c6a84f --- /dev/null +++ b/dali-toolkit/internal/text/text-anchor-control-interface.h @@ -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 diff --git a/dali-toolkit/internal/text/text-controller-event-handler.cpp b/dali-toolkit/internal/text/text-controller-event-handler.cpp index ce0bf59..c17c820 100644 --- a/dali-toolkit/internal/text/text-controller-event-handler.cpp +++ b/dali-toolkit/internal/text/text-controller-event-handler.cpp @@ -24,6 +24,7 @@ #include // INTERNAL INCLUDES +#include #include #include @@ -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"); diff --git a/dali-toolkit/internal/text/text-controller-event-handler.h b/dali-toolkit/internal/text/text-controller-event-handler.h index 67902fb..092ac58 100644 --- a/dali-toolkit/internal/text/text-controller-event-handler.h +++ b/dali-toolkit/internal/text/text-controller-event-handler.h @@ -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); diff --git a/dali-toolkit/internal/text/text-controller-impl.h b/dali-toolkit/internal/text/text-controller-impl.h index 414faf5..ef4bb69 100644 --- a/dali-toolkit/internal/text/text-controller-impl.h +++ b/dali-toolkit/internal/text/text-controller-impl.h @@ -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. diff --git a/dali-toolkit/internal/text/text-controller-text-updater.cpp b/dali-toolkit/internal/text/text-controller-text-updater.cpp index 6fb472e..69e05bf 100644 --- a/dali-toolkit/internal/text/text-controller-text-updater.cpp +++ b/dali-toolkit/internal/text/text-controller-text-updater.cpp @@ -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& currentText = logicalModel->mText; - CharacterIndex& oldCursorIndex = eventData->mPrimaryCursorPosition; + Vector& 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::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 diff --git a/dali-toolkit/internal/text/text-controller-text-updater.h b/dali-toolkit/internal/text/text-controller-text-updater.h index ba7e3b9..f512940 100644 --- a/dali-toolkit/internal/text/text-controller-text-updater.h +++ b/dali-toolkit/internal/text/text-controller-text-updater.h @@ -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 diff --git a/dali-toolkit/internal/text/text-controller.cpp b/dali-toolkit/internal/text/text-controller.cpp index f29ab22..15db50b 100644 --- a/dali-toolkit/internal/text/text-controller.cpp +++ b/dali-toolkit/internal/text/text-controller.cpp @@ -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)) { } diff --git a/dali-toolkit/internal/text/text-controller.h b/dali-toolkit/internal/text/text-controller.h index 40286ef..aaa3a66 100644 --- a/dali-toolkit/internal/text/text-controller.h +++ b/dali-toolkit/internal/text/text-controller.h @@ -29,6 +29,7 @@ #include #include #include +#include #include #include #include @@ -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); -- 2.7.4