From c6aa87aaec61d4ab3638c397b3ba4bd08ecb13a6 Mon Sep 17 00:00:00 2001 From: Lukasz Oleksak Date: Thu, 30 Sep 2021 14:24:44 +0200 Subject: [PATCH] [ATSPI] Implementation of Hypertext and Hyperlink in text controls This patch exposes on dbus ATSPI Hypertext interface for the following text controls: TextEditor, TextField and TextLabel. Also it brings new class TextAnchor inheriting from Control which marks the geometry of an anchor inside the text controls mentioned above and which exposes on dbus ATSPI Hyperlink interface. Change-Id: Ic46bcf7a3ddfe49b1723ebf8025fba6779fda05d --- .../utc-Dali-Accessibility-Controls.cpp | 168 +++++++++++++++ dali-toolkit/devel-api/controls/accessible-impl.h | 2 +- .../controls/text-controls/text-anchor-devel.cpp | 75 +++++++ .../controls/text-controls/text-anchor-devel.h | 140 +++++++++++++ dali-toolkit/devel-api/file.list | 2 + .../controls/text-controls/common-text-utils.cpp | 42 +++- .../controls/text-controls/common-text-utils.h | 15 +- .../controls/text-controls/text-anchor-impl.cpp | 231 +++++++++++++++++++++ .../controls/text-controls/text-anchor-impl.h | 167 +++++++++++++++ .../controls/text-controls/text-editor-impl.cpp | 34 ++- .../controls/text-controls/text-editor-impl.h | 44 ++-- .../text-controls/text-editor-property-handler.cpp | 2 + .../controls/text-controls/text-field-impl.cpp | 34 ++- .../controls/text-controls/text-field-impl.h | 36 +++- .../text-controls/text-field-property-handler.cpp | 2 + .../controls/text-controls/text-label-impl.cpp | 51 ++++- .../controls/text-controls/text-label-impl.h | 21 +- dali-toolkit/internal/file.list | 1 + .../internal/text/text-controller-impl.cpp | 54 +++++ dali-toolkit/internal/text/text-controller-impl.h | 43 ++++ dali-toolkit/internal/text/text-controller.cpp | 10 + dali-toolkit/internal/text/text-controller.h | 17 ++ 22 files changed, 1150 insertions(+), 41 deletions(-) create mode 100644 dali-toolkit/devel-api/controls/text-controls/text-anchor-devel.cpp create mode 100644 dali-toolkit/devel-api/controls/text-controls/text-anchor-devel.h create mode 100644 dali-toolkit/internal/controls/text-controls/text-anchor-impl.cpp create mode 100644 dali-toolkit/internal/controls/text-controls/text-anchor-impl.h diff --git a/automated-tests/src/dali-toolkit-internal/utc-Dali-Accessibility-Controls.cpp b/automated-tests/src/dali-toolkit-internal/utc-Dali-Accessibility-Controls.cpp index 9243a0b..a30078a 100644 --- a/automated-tests/src/dali-toolkit-internal/utc-Dali-Accessibility-Controls.cpp +++ b/automated-tests/src/dali-toolkit-internal/utc-Dali-Accessibility-Controls.cpp @@ -516,6 +516,39 @@ int UtcDaliAccessibilityBloomViewConstructor(void) } #include +#include +int UtcDaliAccessibilityTextAnchor(void) +{ + ToolkitTestApplication application; + + auto textanchor = TextAnchor::New(); + DALI_TEST_CHECK( textanchor ); + + auto textlabel = TextLabel::New(); + DALI_TEST_CHECK( textlabel ); + + Dali::Accessibility::TestEnableSC( true ); + + textlabel.Add(textanchor); + auto accessible = Dali::Accessibility::Accessible::Get( textanchor ); + DALI_TEST_CHECK( accessible ); + auto hyperlink = dynamic_cast< Dali::Accessibility::Hyperlink* >( accessible ); + DALI_TEST_CHECK( hyperlink ); + textanchor.SetProperty( Toolkit::TextAnchor::Property::URI, "https://www.tizen.org" ); + DALI_TEST_EQUALS( hyperlink->IsValid(), true, TEST_LOCATION ); + auto action = dynamic_cast( accessible ); + // activation of valid hyperlink + DALI_TEST_CHECK( action->DoAction( "accessibilityActivated" ) ); + // making hyperlink invalid + textanchor.SetProperty( Toolkit::TextAnchor::Property::URI, "" ); + DALI_TEST_EQUALS( hyperlink->IsValid(), false, TEST_LOCATION ); + DALI_TEST_CHECK( !action->DoAction( "accessibilityActivated" ) ); + + Dali::Accessibility::TestEnableSC( false ); + + END_TEST; +} + int UtcDaliAccessibilityTextField(void) { ToolkitTestApplication application; @@ -566,6 +599,51 @@ int UtcDaliAccessibilityTextField(void) DALI_TEST_EQUALS(editabletext->DeleteText(1, 5), true, TEST_LOCATION); DALI_TEST_EQUALS(text->GetText(0, 2), "af", TEST_LOCATION); + auto hypertext = dynamic_cast< Dali::Accessibility::Hypertext* >( accessible ); + DALI_TEST_CHECK( hypertext ); + // text without the anchors markup and ENABLE_MARKUP property set (by default) to false + DALI_TEST_EQUALS( hypertext->GetLinkCount(), 0, TEST_LOCATION ); + DALI_TEST_EQUALS( hypertext->GetLinkIndex( -1 ), -1, TEST_LOCATION ); + DALI_TEST_EQUALS( hypertext->GetLinkIndex( 0 ), -1, TEST_LOCATION ); + DALI_TEST_EQUALS( hypertext->GetLinkIndex( 5 ), -1, TEST_LOCATION ); + DALI_TEST_EQUALS( hypertext->GetLink( -1 ) == nullptr, true, TEST_LOCATION ); + DALI_TEST_EQUALS( hypertext->GetLink( 0 ) == nullptr, true, TEST_LOCATION ); + DALI_TEST_EQUALS( hypertext->GetLink( 5 ) == nullptr, true, TEST_LOCATION ); + // text with the anchors markup and ENABLE_MARKUP property set (by default) to false + textfield.SetProperty( Toolkit::TextField::Property::TEXT, "12345anchor112345veryveryveryveryveryveryveryverylonganchor212345anchor312345" ); + DALI_TEST_EQUALS( hypertext->GetLinkCount(), 0, TEST_LOCATION ); + DALI_TEST_EQUALS( hypertext->GetLinkIndex( -1 ), -1, TEST_LOCATION ); + DALI_TEST_EQUALS( hypertext->GetLinkIndex( 0 ), -1, TEST_LOCATION ); + DALI_TEST_EQUALS( hypertext->GetLinkIndex( 5 ), -1, TEST_LOCATION ); + DALI_TEST_EQUALS( hypertext->GetLink( -1 ) == nullptr, true, TEST_LOCATION ); + DALI_TEST_EQUALS( hypertext->GetLink( 0 ) == nullptr, true, TEST_LOCATION ); + DALI_TEST_EQUALS( hypertext->GetLink( 5 ) == nullptr, true, TEST_LOCATION ); + // text with the anchors markup and ENABLE_MARKUP property set to true + textfield.SetProperty( Toolkit::TextField::Property::ENABLE_MARKUP, true); + DALI_TEST_EQUALS( hypertext->GetLinkCount(), 3, TEST_LOCATION ); + DALI_TEST_EQUALS( hypertext->GetLinkIndex( -1 ), -1, TEST_LOCATION ); + DALI_TEST_EQUALS( hypertext->GetLinkIndex( 0 ), -1, TEST_LOCATION ); + DALI_TEST_EQUALS( hypertext->GetLinkIndex( 5 ), 0, TEST_LOCATION ); //1st anchor index + DALI_TEST_EQUALS( hypertext->GetLinkIndex( 17 ), 1, TEST_LOCATION ); //2nd anchor index + DALI_TEST_EQUALS( hypertext->GetLinkIndex( 66 ), 2, TEST_LOCATION ); //3rd anchor index + DALI_TEST_EQUALS( hypertext->GetLink( -1 ) == nullptr, true, TEST_LOCATION ); + auto hyperlink = hypertext->GetLink( 0 ); + DALI_TEST_CHECK ( hyperlink ); + DALI_TEST_EQUALS( hyperlink->GetStartIndex(), 5, TEST_LOCATION ); + DALI_TEST_EQUALS( hyperlink->GetEndIndex(), 12, TEST_LOCATION ); + DALI_TEST_EQUALS( hyperlink->GetAnchorCount(), 1, TEST_LOCATION ); + DALI_TEST_EQUALS( hyperlink->GetAnchorUri( 0 ), "https://www.tizen.org", TEST_LOCATION ); + auto anchorAccessible = hyperlink->GetAnchorAccessible( 0 ); + DALI_TEST_EQUALS( hyperlink == anchorAccessible, true, TEST_LOCATION ); + hyperlink = hypertext->GetLink( 1 ); + DALI_TEST_CHECK ( hyperlink ); + DALI_TEST_EQUALS( hyperlink->GetStartIndex(), 17, TEST_LOCATION ); + DALI_TEST_EQUALS( hyperlink->GetEndIndex(), 60, TEST_LOCATION ); + hyperlink = hypertext->GetLink( 2 ); + DALI_TEST_CHECK ( hyperlink ); + DALI_TEST_EQUALS( hyperlink->GetStartIndex(), 65, TEST_LOCATION ); + DALI_TEST_EQUALS( hyperlink->GetEndIndex(), 72, TEST_LOCATION ); + Dali::Accessibility::TestEnableSC( false ); END_TEST; @@ -622,6 +700,51 @@ int UtcDaliAccessibilityTextEditor(void) DALI_TEST_EQUALS(editabletext->DeleteText(1, 5), true, TEST_LOCATION); DALI_TEST_EQUALS(text->GetText(0, 2), "af", TEST_LOCATION); + auto hypertext = dynamic_cast< Dali::Accessibility::Hypertext* >( accessible ); + DALI_TEST_CHECK( hypertext ); + // text without the anchors markup and ENABLE_MARKUP property set (by default) to false + DALI_TEST_EQUALS( hypertext->GetLinkCount(), 0, TEST_LOCATION ); + DALI_TEST_EQUALS( hypertext->GetLinkIndex( -1 ), -1, TEST_LOCATION ); + DALI_TEST_EQUALS( hypertext->GetLinkIndex( 0 ), -1, TEST_LOCATION ); + DALI_TEST_EQUALS( hypertext->GetLinkIndex( 5 ), -1, TEST_LOCATION ); + DALI_TEST_EQUALS( hypertext->GetLink( -1 ) == nullptr, true, TEST_LOCATION ); + DALI_TEST_EQUALS( hypertext->GetLink( 0 ) == nullptr, true, TEST_LOCATION ); + DALI_TEST_EQUALS( hypertext->GetLink( 5 ) == nullptr, true, TEST_LOCATION ); + // text with the anchors markup and ENABLE_MARKUP property set (by default) to false + texteditor.SetProperty( Toolkit::TextEditor::Property::TEXT, "12345anchor112345veryveryveryveryveryveryveryverylonganchor212345anchor312345" ); + DALI_TEST_EQUALS( hypertext->GetLinkCount(), 0, TEST_LOCATION ); + DALI_TEST_EQUALS( hypertext->GetLinkIndex( -1 ), -1, TEST_LOCATION ); + DALI_TEST_EQUALS( hypertext->GetLinkIndex( 0 ), -1, TEST_LOCATION ); + DALI_TEST_EQUALS( hypertext->GetLinkIndex( 5 ), -1, TEST_LOCATION ); + DALI_TEST_EQUALS( hypertext->GetLink( -1 ) == nullptr, true, TEST_LOCATION ); + DALI_TEST_EQUALS( hypertext->GetLink( 0 ) == nullptr, true, TEST_LOCATION ); + DALI_TEST_EQUALS( hypertext->GetLink( 5 ) == nullptr, true, TEST_LOCATION ); + // text with the anchors markup and ENABLE_MARKUP property set to true + texteditor.SetProperty( Toolkit::TextEditor::Property::ENABLE_MARKUP, true); + DALI_TEST_EQUALS( hypertext->GetLinkCount(), 3, TEST_LOCATION ); + DALI_TEST_EQUALS( hypertext->GetLinkIndex( -1 ), -1, TEST_LOCATION ); + DALI_TEST_EQUALS( hypertext->GetLinkIndex( 0 ), -1, TEST_LOCATION ); + DALI_TEST_EQUALS( hypertext->GetLinkIndex( 5 ), 0, TEST_LOCATION ); //1st anchor index + DALI_TEST_EQUALS( hypertext->GetLinkIndex( 17 ), 1, TEST_LOCATION ); //2nd anchor index + DALI_TEST_EQUALS( hypertext->GetLinkIndex( 66 ), 2, TEST_LOCATION ); //3rd anchor index + DALI_TEST_EQUALS( hypertext->GetLink( -1 ) == nullptr, true, TEST_LOCATION ); + auto hyperlink = hypertext->GetLink( 0 ); + DALI_TEST_CHECK ( hyperlink ); + DALI_TEST_EQUALS( hyperlink->GetStartIndex(), 5, TEST_LOCATION ); + DALI_TEST_EQUALS( hyperlink->GetEndIndex(), 12, TEST_LOCATION ); + DALI_TEST_EQUALS( hyperlink->GetAnchorCount(), 1, TEST_LOCATION ); + DALI_TEST_EQUALS( hyperlink->GetAnchorUri( 0 ), "https://www.tizen.org", TEST_LOCATION ); + auto anchorAccessible = hyperlink->GetAnchorAccessible( 0 ); + DALI_TEST_EQUALS( hyperlink == anchorAccessible, true, TEST_LOCATION ); + hyperlink = hypertext->GetLink( 1 ); + DALI_TEST_CHECK ( hyperlink ); + DALI_TEST_EQUALS( hyperlink->GetStartIndex(), 17, TEST_LOCATION ); + DALI_TEST_EQUALS( hyperlink->GetEndIndex(), 60, TEST_LOCATION ); + hyperlink = hypertext->GetLink( 2 ); + DALI_TEST_CHECK ( hyperlink ); + DALI_TEST_EQUALS( hyperlink->GetStartIndex(), 65, TEST_LOCATION ); + DALI_TEST_EQUALS( hyperlink->GetEndIndex(), 72, TEST_LOCATION ); + Dali::Accessibility::TestEnableSC( false ); END_TEST; @@ -660,6 +783,51 @@ int UtcDaliAccessibilityTextLabel(void) DALI_TEST_EQUALS( text->SetRangeOfSelection( 1, 0, 1 ), false, TEST_LOCATION ); DALI_TEST_EQUALS( text->RemoveSelection( 1 ), false, TEST_LOCATION ); + auto hypertext = dynamic_cast< Dali::Accessibility::Hypertext* >( accessible ); + DALI_TEST_CHECK( hypertext ); + // text without the anchors markup and ENABLE_MARKUP property set (by default) to false + DALI_TEST_EQUALS( hypertext->GetLinkCount(), 0, TEST_LOCATION ); + DALI_TEST_EQUALS( hypertext->GetLinkIndex( -1 ), -1, TEST_LOCATION ); + DALI_TEST_EQUALS( hypertext->GetLinkIndex( 0 ), -1, TEST_LOCATION ); + DALI_TEST_EQUALS( hypertext->GetLinkIndex( 5 ), -1, TEST_LOCATION ); + DALI_TEST_EQUALS( hypertext->GetLink( -1 ) == nullptr, true, TEST_LOCATION ); + DALI_TEST_EQUALS( hypertext->GetLink( 0 ) == nullptr, true, TEST_LOCATION ); + DALI_TEST_EQUALS( hypertext->GetLink( 5 ) == nullptr, true, TEST_LOCATION ); + // text with the anchors markup and ENABLE_MARKUP property set (by default) to false + textlabel.SetProperty( Toolkit::TextLabel::Property::TEXT, "12345anchor112345veryveryveryveryveryveryveryverylonganchor212345anchor312345" ); + DALI_TEST_EQUALS( hypertext->GetLinkCount(), 0, TEST_LOCATION ); + DALI_TEST_EQUALS( hypertext->GetLinkIndex( -1 ), -1, TEST_LOCATION ); + DALI_TEST_EQUALS( hypertext->GetLinkIndex( 0 ), -1, TEST_LOCATION ); + DALI_TEST_EQUALS( hypertext->GetLinkIndex( 5 ), -1, TEST_LOCATION ); + DALI_TEST_EQUALS( hypertext->GetLink( -1 ) == nullptr, true, TEST_LOCATION ); + DALI_TEST_EQUALS( hypertext->GetLink( 0 ) == nullptr, true, TEST_LOCATION ); + DALI_TEST_EQUALS( hypertext->GetLink( 5 ) == nullptr, true, TEST_LOCATION ); + // text with the anchors markup and ENABLE_MARKUP property set to true + textlabel.SetProperty( Toolkit::TextLabel::Property::ENABLE_MARKUP, true); + DALI_TEST_EQUALS( hypertext->GetLinkCount(), 3, TEST_LOCATION ); + DALI_TEST_EQUALS( hypertext->GetLinkIndex( -1 ), -1, TEST_LOCATION ); + DALI_TEST_EQUALS( hypertext->GetLinkIndex( 0 ), -1, TEST_LOCATION ); + DALI_TEST_EQUALS( hypertext->GetLinkIndex( 5 ), 0, TEST_LOCATION ); //1st anchor index + DALI_TEST_EQUALS( hypertext->GetLinkIndex( 17 ), 1, TEST_LOCATION ); //2nd anchor index + DALI_TEST_EQUALS( hypertext->GetLinkIndex( 66 ), 2, TEST_LOCATION ); //3rd anchor index + DALI_TEST_EQUALS( hypertext->GetLink( -1 ) == nullptr, true, TEST_LOCATION ); + auto hyperlink = hypertext->GetLink( 0 ); + DALI_TEST_CHECK ( hyperlink ); + DALI_TEST_EQUALS( hyperlink->GetStartIndex(), 5, TEST_LOCATION ); + DALI_TEST_EQUALS( hyperlink->GetEndIndex(), 12, TEST_LOCATION ); + DALI_TEST_EQUALS( hyperlink->GetAnchorCount(), 1, TEST_LOCATION ); + DALI_TEST_EQUALS( hyperlink->GetAnchorUri( 0 ), "https://www.tizen.org", TEST_LOCATION ); + auto anchorAccessible = hyperlink->GetAnchorAccessible( 0 ); + DALI_TEST_EQUALS( hyperlink == anchorAccessible, true, TEST_LOCATION ); + hyperlink = hypertext->GetLink( 1 ); + DALI_TEST_CHECK ( hyperlink ); + DALI_TEST_EQUALS( hyperlink->GetStartIndex(), 17, TEST_LOCATION ); + DALI_TEST_EQUALS( hyperlink->GetEndIndex(), 60, TEST_LOCATION ); + hyperlink = hypertext->GetLink( 2 ); + DALI_TEST_CHECK ( hyperlink ); + DALI_TEST_EQUALS( hyperlink->GetStartIndex(), 65, TEST_LOCATION ); + DALI_TEST_EQUALS( hyperlink->GetEndIndex(), 72, TEST_LOCATION ); + Dali::Accessibility::TestEnableSC( false ); END_TEST; diff --git a/dali-toolkit/devel-api/controls/accessible-impl.h b/dali-toolkit/devel-api/controls/accessible-impl.h index 8bd0714..ea80eef 100644 --- a/dali-toolkit/devel-api/controls/accessible-impl.h +++ b/dali-toolkit/devel-api/controls/accessible-impl.h @@ -53,7 +53,7 @@ protected: bool mIsModal = false; bool mIsRoot = false; - Dali::Actor Self() + Dali::Actor Self() const { auto handle = mSelf.GetHandle(); diff --git a/dali-toolkit/devel-api/controls/text-controls/text-anchor-devel.cpp b/dali-toolkit/devel-api/controls/text-controls/text-anchor-devel.cpp new file mode 100644 index 0000000..9b72f8a --- /dev/null +++ b/dali-toolkit/devel-api/controls/text-controls/text-anchor-devel.cpp @@ -0,0 +1,75 @@ +/* + * 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. + * + */ + +// CLASS HEADER +#include + +// INTERNAL INCLUDES +#include + +using namespace Dali; + +namespace Dali +{ +namespace Toolkit +{ +TextAnchor TextAnchor::New() +{ + return Internal::TextAnchor::New(); +} + +TextAnchor::TextAnchor() +{ +} + +TextAnchor::TextAnchor(const TextAnchor& handle) +: Control(handle) +{ +} + +TextAnchor& TextAnchor::operator=(const TextAnchor& handle) +{ + if(&handle != this) + { + Control::operator=(handle); + } + return *this; +} + +TextAnchor::~TextAnchor() +{ +} + +TextAnchor TextAnchor::DownCast(BaseHandle handle) +{ + return Control::DownCast(handle); +} + +TextAnchor::TextAnchor(Internal::TextAnchor& implementation) +: Control(implementation) +{ +} + +TextAnchor::TextAnchor(Dali::Internal::CustomActor* internal) +: Control(internal) +{ + VerifyCustomActorPointer(internal); +} + +} // namespace Toolkit + +} // namespace Dali diff --git a/dali-toolkit/devel-api/controls/text-controls/text-anchor-devel.h b/dali-toolkit/devel-api/controls/text-controls/text-anchor-devel.h new file mode 100644 index 0000000..83fbc76 --- /dev/null +++ b/dali-toolkit/devel-api/controls/text-controls/text-anchor-devel.h @@ -0,0 +1,140 @@ +#ifndef DALI_TOOLKIT_TEXT_ANCHOR_DEVEL_H +#define DALI_TOOLKIT_TEXT_ANCHOR_DEVEL_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 Internal DALI_INTERNAL +{ +class TextAnchor; +} + +/** + * @brief A control which renders anchor (hyperlink) in hypertext. + */ +class DALI_TOOLKIT_API TextAnchor : public Control +{ +public: + /** + * @brief The start and end property ranges for this control. + */ + enum PropertyRange + { + PROPERTY_START_INDEX = Control::CONTROL_PROPERTY_END_INDEX + 1, + PROPERTY_END_INDEX = PROPERTY_START_INDEX + 1000 ///< Reserve property indices + }; + + /** + * @brief An enumeration of properties belonging to the TextAnchor class. + */ + struct Property + { + enum + { + /** + * @brief The index of a character in text at which an anchor starts. + * @details Name "startCharacterIndex", type INTEGER. + */ + START_CHARACTER_INDEX = PROPERTY_START_INDEX, + + /** + * @brief The index of a character in text that stands one position after the anchor's last character. + * @details Name "endCharacterIndex", type INTEGER. + */ + END_CHARACTER_INDEX, + + /** + * @brief The URI associated with an anchor. + * @details Name "uri", type STRING. + */ + URI + }; + }; + + /** + * @brief Creates the TextAnchor control. + * @return A handle to the TextAnchor control. + */ + static TextAnchor New(); + + /** + * @brief Creates an empty handle. + */ + TextAnchor(); + + /** + * @brief Copy constructor. + * + * @param[in] handle The handle to copy from. + */ + TextAnchor(const TextAnchor& handle); + + /** + * @brief Assignment operator. + * + * @param[in] handle The handle to copy from. + * @return A reference to this. + */ + TextAnchor& operator=(const TextAnchor& handle); + + /** + * @brief Destructor + * + * This is non-virtual since derived Handle types must not contain data or virtual methods. + */ + ~TextAnchor(); + + /** + * @brief Downcast a handle to TextAnchor. + * + * If the BaseHandle points is a TextAnchor the downcast returns a valid handle. + * If not the returned handle is left empty. + * + * @param[in] handle Handle to an object + * @return handle to a TextAnchor or an empty handle + */ + static TextAnchor DownCast(BaseHandle handle); + +public: // Not intended for application developers + /** + * @brief Creates a handle using the Toolkit::Internal implementation. + * + * @param[in] implementation The Control implementation. + */ + DALI_INTERNAL TextAnchor(Internal::TextAnchor& implementation); + + /** + * @brief Allows the creation of this Control from an Internal::CustomActor pointer. + * + * @param[in] internal A pointer to the internal CustomActor. + */ + explicit DALI_INTERNAL TextAnchor(Dali::Internal::CustomActor* internal); + +}; // Class TextAnchor + +} // namespace Toolkit + +} // namespace Dali + +#endif // DALI_TOOLKIT_TEXT_ANCHOR_DEVEL_H diff --git a/dali-toolkit/devel-api/file.list b/dali-toolkit/devel-api/file.list index c88b7a0..3a40bc9 100755 --- a/dali-toolkit/devel-api/file.list +++ b/dali-toolkit/devel-api/file.list @@ -32,6 +32,7 @@ SET( devel_api_src_files ${devel_api_src_dir}/controls/shadow-view/shadow-view.cpp ${devel_api_src_dir}/controls/super-blur-view/super-blur-view.cpp ${devel_api_src_dir}/controls/table-view/table-view.cpp + ${devel_api_src_dir}/controls/text-controls/text-anchor-devel.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 @@ -217,6 +218,7 @@ SET( devel_api_super_blur_view_header_files ) SET( devel_api_text_controls_header_files + ${devel_api_src_dir}/controls/text-controls/text-anchor-devel.h ${devel_api_src_dir}/controls/text-controls/text-editor-devel.h ${devel_api_src_dir}/controls/text-controls/text-field-devel.h ${devel_api_src_dir}/controls/text-controls/text-label-devel.h diff --git a/dali-toolkit/internal/controls/text-controls/common-text-utils.cpp b/dali-toolkit/internal/controls/text-controls/common-text-utils.cpp index 7033e6d..696b44a 100644 --- a/dali-toolkit/internal/controls/text-controls/common-text-utils.cpp +++ b/dali-toolkit/internal/controls/text-controls/common-text-utils.cpp @@ -17,22 +17,43 @@ #include #include +#include #include #include namespace Dali::Toolkit::Internal { +void CommonTextUtils::SynchronizeTextAnchorsInParent( + Actor parent, + Text::ControllerPtr controller, + std::vector& anchorActors) +{ + for(auto& anchorActor : anchorActors) + { + parent.Remove(anchorActor); + } + if(Dali::Accessibility::IsUp()) + { + controller->GetAnchorActors(anchorActors); + for(auto& anchorActor : anchorActors) + { + parent.Add(anchorActor); + } + } +} + void CommonTextUtils::RenderText( - Actor textActor, - Text::RendererPtr renderer, - Text::ControllerPtr controller, - Text::DecoratorPtr decorator, - float& alignmentOffset, - Actor& renderableActor, - Actor& backgroundActor, - Toolkit::Control& stencil, - std::vector& clippingDecorationActors, - Text::Controller::UpdateTextType updateTextType) + Actor textActor, + Text::RendererPtr renderer, + Text::ControllerPtr controller, + Text::DecoratorPtr decorator, + float& alignmentOffset, + Actor& renderableActor, + Actor& backgroundActor, + Toolkit::Control& stencil, + std::vector& clippingDecorationActors, + std::vector& anchorActors, + Text::Controller::UpdateTextType updateTextType) { Actor newRenderableActor; @@ -126,6 +147,7 @@ void CommonTextUtils::RenderText( backgroundActor.LowerToBottom(); } } + SynchronizeTextAnchorsInParent(textActor, controller, anchorActors); } } diff --git a/dali-toolkit/internal/controls/text-controls/common-text-utils.h b/dali-toolkit/internal/controls/text-controls/common-text-utils.h index f9069a2..212f162 100644 --- a/dali-toolkit/internal/controls/text-controls/common-text-utils.h +++ b/dali-toolkit/internal/controls/text-controls/common-text-utils.h @@ -16,7 +16,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - +#include #include #include #include @@ -42,6 +42,7 @@ public: * @param[in,out] backgroundActor Actor for rendering background * @param[in,out] stencil Clipping actor * @param[in,out] clippingDecorationActors Clipping decoration actors + * @param[in,out] anchorActors Anchor actors * @param[in] updateTextType How the text has been updated */ static void RenderText( @@ -54,7 +55,19 @@ public: Actor& backgroundActor, Toolkit::Control& stencil, std::vector& clippingDecorationActors, + std::vector& anchorActors, Text::Controller::UpdateTextType updateTextType); + + /** + * Common method to synchronize TextAnchor actors with Anchor objects in text's logical model. + * @param[in] parent The actor that is a parent of anchor actors + * @param[in] controller pointer to the text controller + * @param[in,out] anchorActors Anchor actors + */ + static void SynchronizeTextAnchorsInParent( + Actor parent, + Text::ControllerPtr controller, + std::vector& anchorActors); }; } // namespace Dali::Toolkit::Internal diff --git a/dali-toolkit/internal/controls/text-controls/text-anchor-impl.cpp b/dali-toolkit/internal/controls/text-controls/text-anchor-impl.cpp new file mode 100644 index 0000000..2fde91c --- /dev/null +++ b/dali-toolkit/internal/controls/text-controls/text-anchor-impl.cpp @@ -0,0 +1,231 @@ +/* + * 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. + * + */ + +// CLASS HEADER +#include + +// EXTERNAL INCLUDES +#include +#include +#include +#include +#include + +// INTERNAL INCLUDES + +// DEVEL INCLUDES +#include + +using namespace Dali::Toolkit::Text; + +namespace Dali +{ +namespace Toolkit +{ +namespace Internal +{ +namespace +{ +#if defined(DEBUG_ENABLED) +Debug::Filter* gLogFilter = Debug::Filter::New(Debug::NoLogging, true, "LOG_TEXT_CONTROLS"); +#endif + +// Type registration +BaseHandle Create() +{ + return Toolkit::TextAnchor::New(); +} + +// clang-format off +// Setup properties, signals and actions using the type-registry. +DALI_TYPE_REGISTRATION_BEGIN(Toolkit::TextAnchor, Toolkit::Control, Create); + +DALI_PROPERTY_REGISTRATION(Toolkit, TextAnchor, "startCharacterIndex", INTEGER, START_CHARACTER_INDEX) +DALI_PROPERTY_REGISTRATION(Toolkit, TextAnchor, "endCharacterIndex", INTEGER, END_CHARACTER_INDEX ) +DALI_PROPERTY_REGISTRATION(Toolkit, TextAnchor, "uri", STRING, URI ) + +DALI_TYPE_REGISTRATION_END() +// clang-format on + +} // namespace + +Toolkit::TextAnchor TextAnchor::New() +{ + // Create the implementation, temporarily owned by this handle on stack + IntrusivePtr impl = new TextAnchor(); + + // Pass ownership to CustomActor handle + Toolkit::TextAnchor handle(*impl); + + // Second-phase init of the implementation + // This can only be done after the CustomActor connection has been made... + impl->Initialize(); + + return handle; +} + +Property::Value TextAnchor::GetProperty(BaseObject* object, Property::Index index) +{ + Property::Value value; + + Toolkit::TextAnchor anchor = Toolkit::TextAnchor::DownCast(Dali::BaseHandle(object)); + + if(anchor) + { + TextAnchor& impl(GetImpl(anchor)); + + switch(index) + { + case Toolkit::TextAnchor::Property::START_CHARACTER_INDEX: + { + value = impl.mStartCharacterIndex; + break; + } + case Toolkit::TextAnchor::Property::END_CHARACTER_INDEX: + { + value = impl.mEndCharacterIndex; + break; + } + case Toolkit::TextAnchor::Property::URI: + { + value = impl.mUri; + break; + } + } + } + + return value; +} + +void TextAnchor::SetProperty(BaseObject* object, Property::Index index, const Property::Value& value) +{ + Toolkit::TextAnchor anchor = Toolkit::TextAnchor::DownCast(Dali::BaseHandle(object)); + + if(anchor) + { + TextAnchor& impl(GetImpl(anchor)); + switch(index) + { + case Toolkit::TextAnchor::Property::START_CHARACTER_INDEX: + { + value.Get(impl.mStartCharacterIndex); + break; + } + + case Toolkit::TextAnchor::Property::END_CHARACTER_INDEX: + { + value.Get(impl.mEndCharacterIndex); + break; + } + + case Toolkit::TextAnchor::Property::URI: + { + value.Get(impl.mUri); + break; + } + } + } +} + +void TextAnchor::OnInitialize() +{ + Actor self = Self(); + + // Enable highlightability + self.SetProperty(Toolkit::DevelControl::Property::ACCESSIBILITY_HIGHLIGHTABLE, true); + + DevelControl::SetAccessibilityConstructor(self, [](Dali::Actor actor) { + return std::unique_ptr( + new AccessibleImpl(actor, Dali::Accessibility::Role::LINK)); + }); +} + +TextAnchor::TextAnchor() +: Control(ControlBehaviour(CONTROL_BEHAVIOUR_DEFAULT)) +{ +} + +TextAnchor::~TextAnchor() +{ +} + +int32_t TextAnchor::AccessibleImpl::GetEndIndex() const +{ + auto self = Toolkit::TextAnchor::DownCast(Self()); + return self.GetProperty(Toolkit::TextAnchor::Property::END_CHARACTER_INDEX).Get(); +} + +int32_t TextAnchor::AccessibleImpl::GetStartIndex() const +{ + auto self = Toolkit::TextAnchor::DownCast(Self()); + return self.GetProperty(Toolkit::TextAnchor::Property::START_CHARACTER_INDEX).Get(); +} + +int32_t TextAnchor::AccessibleImpl::GetAnchorCount() const +{ + return 1; +} + +Dali::Accessibility::Accessible* TextAnchor::AccessibleImpl::GetAnchorAccessible(int32_t anchorIndex) const +{ + return Control::Impl::GetAccessibilityObject(Self()); +} + +std::string TextAnchor::AccessibleImpl::GetAnchorUri(int32_t anchorIndex) const +{ + auto self = Toolkit::TextAnchor::DownCast(Self()); + return self.GetProperty(Toolkit::TextAnchor::Property::URI).Get(); +} + +bool TextAnchor::AccessibleImpl::IsValid() const +{ + return !GetAnchorUri(0).empty(); +} + +bool TextAnchor::OnAccessibilityActivated() +{ + auto uri = Self().GetProperty(Toolkit::TextAnchor::Property::URI).Get(); + if(!uri.empty()) + { + Dali::Actor current = Self(); + Dali::Toolkit::Text::AnchorControlInterface* parentImplementationAnchorInterface = nullptr; + while(!current.GetProperty(Actor::Property::IS_ROOT) && !parentImplementationAnchorInterface) + { + Dali::Actor parentAsActor = current.GetParent(); + Dali::CustomActor parentAsCustomActor = Dali::CustomActor::DownCast(parentAsActor); + Dali::CustomActorImpl& parentImplementation = parentAsCustomActor.GetImplementation(); + parentImplementationAnchorInterface = dynamic_cast(&parentImplementation); + current = parentAsActor; + } + if(parentImplementationAnchorInterface) + { + parentImplementationAnchorInterface->AnchorClicked(uri); + return true; + } + else + { + DALI_LOG_ERROR("TextAnchor::OnAccessibilityActivate cannot find ancestor actor implementing Dali::Toolkit::Text::AnchorControlInterface.\n"); + } + } + return false; +} + +} // namespace Internal + +} // namespace Toolkit + +} // namespace Dali diff --git a/dali-toolkit/internal/controls/text-controls/text-anchor-impl.h b/dali-toolkit/internal/controls/text-controls/text-anchor-impl.h new file mode 100644 index 0000000..b590862 --- /dev/null +++ b/dali-toolkit/internal/controls/text-controls/text-anchor-impl.h @@ -0,0 +1,167 @@ +#ifndef DALI_TOOLKIT_INTERNAL_TEXT_ANCHOR_H +#define DALI_TOOLKIT_INTERNAL_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. + * + */ + +// EXTERNAL INCLUDES +#include + +// INTERNAL INCLUDES +#include +#include +#include + +namespace Dali +{ +namespace Toolkit +{ +namespace Internal +{ +/** + * @brief A control which renders anchor (hyperlink) in hypertext. + */ +class TextAnchor : public Control +{ +public: + /** + * @copydoc Dali::Toollkit::TextAnchor::New() + */ + static Toolkit::TextAnchor New(); + + // Properties + + /** + * @brief Called when a property of an object of this type is set. + * + * @param[in] object The object whose property is set. + * @param[in] index The property index. + * @param[in] value The new property value. + */ + static void SetProperty(BaseObject* object, Property::Index index, const Property::Value& value); + + /** + * @brief Called to retrieve a property of an object of this type. + * + * @param[in] object The object whose property is to be retrieved. + * @param[in] index The property index. + * @return The current value of the property. + */ + static Property::Value GetProperty(BaseObject* object, Property::Index index); + +private: // From Control + /** + * @copydoc Control::OnInitialize() + */ + void OnInitialize() override; + + /** + * @copydoc Control::OnPropertySet() + */ + // void OnPropertySet(Property::Index index, const Property::Value& propertyValue) override; + + /** + * @copydoc Control::OnAccessibilityActivated() + */ + bool OnAccessibilityActivated() override; + +private: // Implementation + /** + * Construct a new TextAnchor. + */ + TextAnchor(); + + /** + * A reference counted object may only be deleted by calling Unreference() + */ + virtual ~TextAnchor(); + +private: + // Undefined copy constructor and assignment operators + TextAnchor(const TextAnchor&); + TextAnchor& operator=(const TextAnchor& rhs); + + //Data + int mStartCharacterIndex; + int mEndCharacterIndex; + std::string mUri; + +protected: + /** + * @brief This structure is to connect TextAnchor with Accessible functions. + */ + struct AccessibleImpl : public DevelControl::AccessibleImpl, + public virtual Dali::Accessibility::Hyperlink + { + using DevelControl::AccessibleImpl::AccessibleImpl; + /** + * @copydoc Dali::Accessibility::Hyperlink::GetEndIndex() + */ + int32_t GetEndIndex() const override; + + /** + * @copydoc Dali::Accessibility::Hyperlink::GetStartIndex() + */ + int32_t GetStartIndex() const override; + + /** + * @copydoc Dali::Accessibility::Hyperlink::GetAnchorCount() + */ + int32_t GetAnchorCount() const override; + + /** + * @copydoc Dali::Accessibility::Hyperlink::GetAnchorAccessible() + */ + Accessible* GetAnchorAccessible(int32_t anchorIndex) const override; + + /** + * @copydoc Dali::Accessibility::Hyperlink::GetAnchorUri() + */ + std::string GetAnchorUri(int32_t anchorIndex) const override; + + /** + * @copydoc Dali::Accessibility::Hyperlink::IsValid() + */ + bool IsValid() const override; + }; +}; + +inline Toolkit::Internal::TextAnchor& GetImpl(Toolkit::TextAnchor& textAnchor) +{ + DALI_ASSERT_ALWAYS(textAnchor); + + Dali::RefObject& handle = textAnchor.GetImplementation(); + + return static_cast(handle); +} + +inline const Toolkit::Internal::TextAnchor& GetImpl(const Toolkit::TextAnchor& textAnchor) +{ + DALI_ASSERT_ALWAYS(textAnchor); + + const Dali::RefObject& handle = textAnchor.GetImplementation(); + + return static_cast(handle); +} + +} // namespace Internal + +} // namespace Toolkit + +} // namespace Dali + +#endif // DALI_TOOLKIT_INTERNAL_TEXT_ANCHOR_H 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 0259471..44c1176 100644 --- a/dali-toolkit/internal/controls/text-controls/text-editor-impl.cpp +++ b/dali-toolkit/internal/controls/text-controls/text-editor-impl.cpp @@ -478,6 +478,11 @@ Toolkit::TextEditor::ScrollStateChangedSignalType& TextEditor::ScrollStateChange return mScrollStateChangedSignal; } +void TextEditor::OnAccessibilityStatusChanged() +{ + CommonTextUtils::SynchronizeTextAnchorsInParent(Self(), mController, mAnchorActors); +} + void TextEditor::OnInitialize() { Actor self = Self(); @@ -568,6 +573,9 @@ void TextEditor::OnInitialize() return std::unique_ptr( new AccessibleImpl(actor, Dali::Accessibility::Role::ENTRY)); }); + + Accessibility::Bridge::EnabledSignal().Connect(this, &TextEditor::OnAccessibilityStatusChanged); + Accessibility::Bridge::DisabledSignal().Connect(this, &TextEditor::OnAccessibilityStatusChanged); } void TextEditor::OnStyleChange(Toolkit::StyleManager styleManager, StyleChange::Type change) @@ -724,7 +732,7 @@ void TextEditor::OnRelayout(const Vector2& size, RelayoutContainer& container) void TextEditor::RenderText(Text::Controller::UpdateTextType updateTextType) { - CommonTextUtils::RenderText(Self(), mRenderer, mController, mDecorator, mAlignmentOffset, mRenderableActor, mBackgroundActor, mStencil, mClippingDecorationActors, updateTextType); + CommonTextUtils::RenderText(Self(), mRenderer, mController, mDecorator, mAlignmentOffset, mRenderableActor, mBackgroundActor, mStencil, mClippingDecorationActors, mAnchorActors, updateTextType); if(mRenderableActor) { ApplyScrollPosition(); @@ -1540,6 +1548,30 @@ bool TextEditor::AccessibleImpl::SetTextContents(std::string newContents) return true; } +int32_t TextEditor::AccessibleImpl::GetLinkCount() const +{ + auto self = Toolkit::TextEditor::DownCast(Self()); + return Dali::Toolkit::GetImpl(self).mAnchorActors.size(); +} + +Accessibility::Hyperlink* TextEditor::AccessibleImpl::GetLink(int32_t linkIndex) const +{ + if(linkIndex < 0 || linkIndex >= GetLinkCount()) + { + return nullptr; + } + auto self = Toolkit::TextEditor::DownCast(Self()); + auto anchorActor = Dali::Toolkit::GetImpl(self).mAnchorActors[linkIndex]; + return dynamic_cast(Dali::Accessibility::Accessible::Get(anchorActor)); +} + +int32_t TextEditor::AccessibleImpl::GetLinkIndex(int32_t characterOffset) const +{ + auto self = Toolkit::TextEditor::DownCast(Self()); + auto controller = Dali::Toolkit::GetImpl(self).GetTextController(); + return controller->GetAnchorIndex(static_cast(characterOffset)); +} + } // namespace Internal } // namespace Toolkit 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 8c5d7be..e4b5fa5 100644 --- a/dali-toolkit/internal/controls/text-controls/text-editor-impl.h +++ b/dali-toolkit/internal/controls/text-controls/text-editor-impl.h @@ -473,6 +473,9 @@ private: // Implementation // Connection needed to re-render text, when a text editor returns to the scene. void OnSceneConnect(Dali::Actor actor); + // Needed to synchronize TextAnchor actors with Anchor objects in text's logical model + void OnAccessibilityStatusChanged(); + private: // Data // Signals Toolkit::TextEditor::TextChangedSignalType mTextChangedSignal; @@ -485,17 +488,18 @@ private: // Data Toolkit::DevelTextEditor::SelectionChangedSignalType mSelectionChangedSignal; Toolkit::DevelTextEditor::SelectionClearedSignalType mSelectionClearedSignal; - InputMethodContext mInputMethodContext; - Text::ControllerPtr mController; - Text::RendererPtr mRenderer; - Text::DecoratorPtr mDecorator; - Text::TextVerticalScrollerPtr mTextVerticalScroller; - Toolkit::Control mStencil; - Toolkit::ScrollBar mScrollBar; - Dali::Animation mAnimation; ///< Scroll indicator Show/Hide Animation. - Dali::TimePeriod mAnimationPeriod; - std::vector mClippingDecorationActors; ///< Decoration actors which need clipping. - Dali::InputMethodOptions mInputMethodOptions; + InputMethodContext mInputMethodContext; + Text::ControllerPtr mController; + Text::RendererPtr mRenderer; + Text::DecoratorPtr mDecorator; + Text::TextVerticalScrollerPtr mTextVerticalScroller; + Toolkit::Control mStencil; + Toolkit::ScrollBar mScrollBar; + Dali::Animation mAnimation; ///< Scroll indicator Show/Hide Animation. + Dali::TimePeriod mAnimationPeriod; + std::vector mClippingDecorationActors; ///< Decoration actors which need clipping. + std::vector mAnchorActors; + Dali::InputMethodOptions mInputMethodOptions; Actor mRenderableActor; Actor mActiveLayer; @@ -529,7 +533,8 @@ private: // Data */ struct AccessibleImpl : public DevelControl::AccessibleImpl, public virtual Dali::Accessibility::Text, - public virtual Dali::Accessibility::EditableText + public virtual Dali::Accessibility::EditableText, + public virtual Dali::Accessibility::Hypertext { using DevelControl::AccessibleImpl::AccessibleImpl; @@ -607,6 +612,21 @@ private: // Data * @copydoc Dali::Accessibility::EditableText::DeleteText() */ bool DeleteText(size_t startPosition, size_t endPosition) override; + + /** + * @copydoc Dali::Accessibility::Hypertext::GetLink() + */ + Accessibility::Hyperlink* GetLink(int32_t linkIndex) const override; + + /** + * @copydoc Dali::Accessibility::Hypertext::GetLinkIndex() + */ + int32_t GetLinkIndex(int32_t characterOffset) const override; + + /** + * @copydoc Dali::Accessibility::Hypertext::GetLinkCount() + */ + int32_t GetLinkCount() const override; }; }; diff --git a/dali-toolkit/internal/controls/text-controls/text-editor-property-handler.cpp b/dali-toolkit/internal/controls/text-controls/text-editor-property-handler.cpp index 62dfbda..f35c169 100644 --- a/dali-toolkit/internal/controls/text-controls/text-editor-property-handler.cpp +++ b/dali-toolkit/internal/controls/text-controls/text-editor-property-handler.cpp @@ -15,6 +15,7 @@ */ #include +#include #include @@ -311,6 +312,7 @@ void TextEditor::PropertyHandler::SetProperty(Toolkit::TextEditor textEditor, Pr DALI_LOG_INFO(gTextEditorLogFilter, Debug::General, "TextEditor %p ENABLE_MARKUP %d\n", impl.mController.Get(), enableMarkup); impl.mController->SetMarkupProcessorEnabled(enableMarkup); + CommonTextUtils::SynchronizeTextAnchorsInParent(textEditor, impl.mController, impl.mAnchorActors); break; } case Toolkit::TextEditor::Property::INPUT_COLOR: 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 b48faa0..2f4ef60 100644 --- a/dali-toolkit/internal/controls/text-controls/text-field-impl.cpp +++ b/dali-toolkit/internal/controls/text-controls/text-field-impl.cpp @@ -463,6 +463,11 @@ DevelTextField::SelectionClearedSignalType& TextField::SelectionClearedSignal() return mSelectionClearedSignal; } +void TextField::OnAccessibilityStatusChanged() +{ + CommonTextUtils::SynchronizeTextAnchorsInParent(Self(), mController, mAnchorActors); +} + void TextField::OnInitialize() { Actor self = Self(); @@ -541,6 +546,9 @@ void TextField::OnInitialize() return std::unique_ptr( new AccessibleImpl(actor, Dali::Accessibility::Role::ENTRY)); }); + + Accessibility::Bridge::EnabledSignal().Connect(this, &TextField::OnAccessibilityStatusChanged); + Accessibility::Bridge::DisabledSignal().Connect(this, &TextField::OnAccessibilityStatusChanged); } void TextField::OnStyleChange(Toolkit::StyleManager styleManager, StyleChange::Type change) @@ -702,7 +710,7 @@ Text::ControllerPtr TextField::GetTextController() void TextField::RenderText(Text::Controller::UpdateTextType updateTextType) { - CommonTextUtils::RenderText(Self(), mRenderer, mController, mDecorator, mAlignmentOffset, mRenderableActor, mBackgroundActor, mStencil, mClippingDecorationActors, updateTextType); + CommonTextUtils::RenderText(Self(), mRenderer, mController, mDecorator, mAlignmentOffset, mRenderableActor, mBackgroundActor, mStencil, mClippingDecorationActors, mAnchorActors, updateTextType); } void TextField::OnKeyInputFocusGained() @@ -1414,6 +1422,30 @@ bool TextField::AccessibleImpl::SetTextContents(std::string newContents) return true; } +int32_t TextField::AccessibleImpl::GetLinkCount() const +{ + auto self = Toolkit::TextField::DownCast(Self()); + return Dali::Toolkit::GetImpl(self).mAnchorActors.size(); +} + +Accessibility::Hyperlink* TextField::AccessibleImpl::GetLink(int32_t linkIndex) const +{ + if(linkIndex < 0 || linkIndex >= GetLinkCount()) + { + return nullptr; + } + auto self = Toolkit::TextField::DownCast(Self()); + auto anchorActor = Dali::Toolkit::GetImpl(self).mAnchorActors[linkIndex]; + return dynamic_cast(Dali::Accessibility::Accessible::Get(anchorActor)); +} + +int32_t TextField::AccessibleImpl::GetLinkIndex(int32_t characterOffset) const +{ + auto self = Toolkit::TextField::DownCast(Self()); + auto controller = Dali::Toolkit::GetImpl(self).GetTextController(); + return controller->GetAnchorIndex(static_cast(characterOffset)); +} + } // namespace Internal } // namespace Toolkit 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 b65e953..7e18a82 100644 --- a/dali-toolkit/internal/controls/text-controls/text-field-impl.h +++ b/dali-toolkit/internal/controls/text-controls/text-field-impl.h @@ -429,6 +429,9 @@ private: // Implementation // Connection needed to re-render text, when a Text Field returns to the scene. void OnSceneConnect(Dali::Actor actor); + // Needed to synchronize TextAnchor actors with Anchor objects in text's logical model + void OnAccessibilityStatusChanged(); + private: // Data // Signals Toolkit::TextField::TextChangedSignalType mTextChangedSignal; @@ -440,13 +443,14 @@ private: // Data Toolkit::DevelTextField::SelectionChangedSignalType mSelectionChangedSignal; Toolkit::DevelTextField::SelectionClearedSignalType mSelectionClearedSignal; - InputMethodContext mInputMethodContext; - Text::ControllerPtr mController; - Text::RendererPtr mRenderer; - Text::DecoratorPtr mDecorator; - Toolkit::Control mStencil; ///< For EXCEED_POLICY_CLIP - std::vector mClippingDecorationActors; ///< Decoration actors which need clipping. - Dali::InputMethodOptions mInputMethodOptions; + InputMethodContext mInputMethodContext; + Text::ControllerPtr mController; + Text::RendererPtr mRenderer; + Text::DecoratorPtr mDecorator; + Toolkit::Control mStencil; ///< For EXCEED_POLICY_CLIP + std::vector mClippingDecorationActors; ///< Decoration actors which need clipping. + std::vector mAnchorActors; + Dali::InputMethodOptions mInputMethodOptions; Actor mRenderableActor; Actor mActiveLayer; @@ -477,7 +481,8 @@ protected: */ struct AccessibleImpl : public DevelControl::AccessibleImpl, public virtual Dali::Accessibility::Text, - public virtual Dali::Accessibility::EditableText + public virtual Dali::Accessibility::EditableText, + public virtual Dali::Accessibility::Hypertext { using DevelControl::AccessibleImpl::AccessibleImpl; @@ -555,6 +560,21 @@ protected: * @copydoc Dali::Accessibility::EditableText::DeleteText() */ bool DeleteText(size_t startPosition, size_t endPosition) override; + + /** + * @copydoc Dali::Accessibility::Hypertext::GetLink() + */ + Accessibility::Hyperlink* GetLink(int32_t linkIndex) const override; + + /** + * @copydoc Dali::Accessibility::Hypertext::GetLinkIndex() + */ + int32_t GetLinkIndex(int32_t characterOffset) const override; + + /** + * @copydoc Dali::Accessibility::Hypertext::GetLinkCount() + */ + int32_t GetLinkCount() const override; }; }; diff --git a/dali-toolkit/internal/controls/text-controls/text-field-property-handler.cpp b/dali-toolkit/internal/controls/text-controls/text-field-property-handler.cpp index db08d3ae..dc253f0 100644 --- a/dali-toolkit/internal/controls/text-controls/text-field-property-handler.cpp +++ b/dali-toolkit/internal/controls/text-controls/text-field-property-handler.cpp @@ -15,6 +15,7 @@ */ #include +#include #include #include @@ -408,6 +409,7 @@ void TextField::PropertyHandler::SetProperty(Toolkit::TextField textField, Prope DALI_LOG_INFO(gTextFieldLogFilter, Debug::General, "TextField %p ENABLE_MARKUP %d\n", impl.mController.Get(), enableMarkup); impl.mController->SetMarkupProcessorEnabled(enableMarkup); + CommonTextUtils::SynchronizeTextAnchorsInParent(textField, impl.mController, impl.mAnchorActors); break; } case Toolkit::TextField::Property::INPUT_FONT_FAMILY: 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 a98ef9b..22990d9 100644 --- a/dali-toolkit/internal/controls/text-controls/text-label-impl.cpp +++ b/dali-toolkit/internal/controls/text-controls/text-label-impl.cpp @@ -37,6 +37,7 @@ #include #include #include +#include #include #include @@ -850,6 +851,9 @@ void TextLabel::OnInitialize() return std::unique_ptr( new AccessibleImpl(actor, Dali::Accessibility::Role::LABEL)); }); + + Accessibility::Bridge::EnabledSignal().Connect(this, &TextLabel::OnAccessibilityStatusChanged); + Accessibility::Bridge::DisabledSignal().Connect(this, &TextLabel::OnAccessibilityStatusChanged); } void TextLabel::OnStyleChange(Toolkit::StyleManager styleManager, StyleChange::Type change) @@ -937,6 +941,12 @@ void TextLabel::OnPropertySet(Property::Index index, const Property::Value& prop } break; } + case Toolkit::TextLabel::Property::TEXT: + case Toolkit::TextLabel::Property::ENABLE_MARKUP: + { + CommonTextUtils::SynchronizeTextAnchorsInParent(Self(), mController, mAnchorActors); + break; + } default: { Control::OnPropertySet(index, propertyValue); // up call to control for non-handled properties @@ -1110,6 +1120,11 @@ void TextLabel::EmitTextFitChangedSignal() mTextFitChangedSignal.Emit(handle); } +void TextLabel::OnAccessibilityStatusChanged() +{ + CommonTextUtils::SynchronizeTextAnchorsInParent(Self(), mController, mAnchorActors); +} + TextLabel::TextLabel() : Control(ControlBehaviour(CONTROL_BEHAVIOUR_DEFAULT)), mRenderingBackend(DEFAULT_RENDERING_BACKEND), @@ -1170,8 +1185,8 @@ bool TextLabel::AccessibleImpl::SetCursorOffset(size_t offset) Dali::Accessibility::Range TextLabel::AccessibleImpl::GetTextAtOffset(size_t offset, Dali::Accessibility::TextBoundary boundary) { - auto self = Toolkit::TextLabel::DownCast(Self()); - auto text = self.GetProperty(Toolkit::TextLabel::Property::TEXT).Get(); + auto self = Toolkit::TextLabel::DownCast(Self()); + auto text = self.GetProperty(Toolkit::TextLabel::Property::TEXT).Get(); auto textSize = text.size(); auto range = Dali::Accessibility::Range{}; @@ -1192,7 +1207,7 @@ Dali::Accessibility::Range TextLabel::AccessibleImpl::GetTextAtOffset(size_t off case Dali::Accessibility::TextBoundary::LINE: { auto textString = text.c_str(); - auto breaks = std::vector(textSize, 0); + auto breaks = std::vector(textSize, 0); if(boundary == Dali::Accessibility::TextBoundary::WORD) { @@ -1203,7 +1218,7 @@ Dali::Accessibility::Range TextLabel::AccessibleImpl::GetTextAtOffset(size_t off Accessibility::Accessible::FindLineSeparationsUtf8(reinterpret_cast(textString), textSize, "", breaks.data()); } - auto index = 0u; + auto index = 0u; auto counter = 0u; while(index < textSize && counter <= offset) { @@ -1267,8 +1282,8 @@ Dali::Accessibility::Range TextLabel::AccessibleImpl::GetRangeOfSelection(size_t return {}; } - auto self = Toolkit::TextLabel::DownCast(Self()); - auto controller = Dali::Toolkit::GetImpl(self).GetTextController(); + auto self = Toolkit::TextLabel::DownCast(Self()); + auto controller = Dali::Toolkit::GetImpl(self).GetTextController(); std::string value{}; controller->RetrieveSelection(value); auto indices = controller->GetSelectionIndexes(); @@ -1302,6 +1317,30 @@ bool TextLabel::AccessibleImpl::SetRangeOfSelection(size_t selectionIndex, size_ return true; } +int32_t TextLabel::AccessibleImpl::GetLinkCount() const +{ + auto self = Toolkit::TextLabel::DownCast(Self()); + return Dali::Toolkit::GetImpl(self).mAnchorActors.size(); +} + +Accessibility::Hyperlink* TextLabel::AccessibleImpl::GetLink(int32_t linkIndex) const +{ + if(linkIndex < 0 || linkIndex >= GetLinkCount()) + { + return nullptr; + } + auto self = Toolkit::TextLabel::DownCast(Self()); + auto anchorActor = Dali::Toolkit::GetImpl(self).mAnchorActors[linkIndex]; + return dynamic_cast(Dali::Accessibility::Accessible::Get(anchorActor)); +} + +int32_t TextLabel::AccessibleImpl::GetLinkIndex(int32_t characterOffset) const +{ + auto self = Toolkit::TextLabel::DownCast(Self()); + auto controller = Dali::Toolkit::GetImpl(self).GetTextController(); + return controller->GetAnchorIndex(static_cast(characterOffset)); +} + } // namespace Internal } // namespace Toolkit 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 d345d21..5c9a6b1 100644 --- a/dali-toolkit/internal/controls/text-controls/text-label-impl.h +++ b/dali-toolkit/internal/controls/text-controls/text-label-impl.h @@ -198,6 +198,7 @@ private: * @brief Emits TextFitChanged signal. */ void EmitTextFitChangedSignal(); + void OnAccessibilityStatusChanged(); private: // Data Text::ControllerPtr mController; @@ -205,6 +206,8 @@ private: // Data Toolkit::Visual::Base mVisual; + std::vector mAnchorActors; + // Signals Toolkit::DevelTextLabel::AnchorClickedSignalType mAnchorClickedSignal; Toolkit::DevelTextLabel::TextFitChangedSignalType mTextFitChangedSignal; @@ -217,7 +220,8 @@ protected: * @brief This structure is to connect TextLabel with Accessible functions. */ struct AccessibleImpl : public DevelControl::AccessibleImpl, - public virtual Dali::Accessibility::Text + public virtual Dali::Accessibility::Text, + public virtual Dali::Accessibility::Hypertext { using DevelControl::AccessibleImpl::AccessibleImpl; @@ -270,6 +274,21 @@ protected: * @copydoc Dali::Accessibility::Text::GetNamePropertyIndex() */ Property::Index GetNamePropertyIndex() override; + + /** + * @copydoc Dali::Accessibility::Hypertext::GetLink() + */ + Accessibility::Hyperlink* GetLink(int32_t linkIndex) const override; + + /** + * @copydoc Dali::Accessibility::Hypertext::GetLinkIndex() + */ + int32_t GetLinkIndex(int32_t characterOffset) const override; + + /** + * @copydoc Dali::Accessibility::Hypertext::GetLinkCount() + */ + int32_t GetLinkCount() const override; }; }; diff --git a/dali-toolkit/internal/file.list b/dali-toolkit/internal/file.list index 855df54..b9c7d23 100644 --- a/dali-toolkit/internal/file.list +++ b/dali-toolkit/internal/file.list @@ -102,6 +102,7 @@ SET( toolkit_src_files ${toolkit_src_dir}/controls/super-blur-view/super-blur-view-impl.cpp ${toolkit_src_dir}/controls/table-view/table-view-impl.cpp ${toolkit_src_dir}/controls/text-controls/common-text-utils.cpp + ${toolkit_src_dir}/controls/text-controls/text-anchor-impl.cpp ${toolkit_src_dir}/controls/text-controls/text-editor-impl.cpp ${toolkit_src_dir}/controls/text-controls/text-editor-property-handler.cpp ${toolkit_src_dir}/controls/text-controls/text-field-impl.cpp diff --git a/dali-toolkit/internal/text/text-controller-impl.cpp b/dali-toolkit/internal/text/text-controller-impl.cpp index 8a1dd76..fcd3bed 100644 --- a/dali-toolkit/internal/text/text-controller-impl.cpp +++ b/dali-toolkit/internal/text/text-controller-impl.cpp @@ -1619,6 +1619,60 @@ float Controller::Impl::GetVerticalScrollPosition() return mEventData ? -mModel->mScrollPosition.y : 0.0f; } +Vector3 Controller::Impl::GetAnchorPosition(Anchor anchor) const +{ + //TODO + return Vector3(10.f, 10.f, 10.f); +} + +Vector2 Controller::Impl::GetAnchorSize(Anchor anchor) const +{ + //TODO + return Vector2(10.f, 10.f); +} + +Toolkit::TextAnchor Controller::Impl::CreateAnchorActor(Anchor anchor) +{ + auto actor = Toolkit::TextAnchor::New(); + actor.SetProperty(Actor::Property::PARENT_ORIGIN, ParentOrigin::TOP_LEFT); + actor.SetProperty(Actor::Property::ANCHOR_POINT, AnchorPoint::TOP_LEFT); + const Vector3 anchorPosition = GetAnchorPosition(anchor); + actor.SetProperty(Actor::Property::POSITION, anchorPosition); + const Vector2 anchorSize = GetAnchorSize(anchor); + actor.SetProperty(Actor::Property::SIZE, anchorSize); + std::string anchorText(mModel->mLogicalModel->mText.Begin() + anchor.startIndex, mModel->mLogicalModel->mText.Begin() + anchor.endIndex); + actor.SetProperty(Actor::Property::NAME, anchorText); + actor.SetProperty(Toolkit::TextAnchor::Property::URI, std::string(anchor.href)); + actor.SetProperty(Toolkit::TextAnchor::Property::START_CHARACTER_INDEX, static_cast(anchor.startIndex)); + actor.SetProperty(Toolkit::TextAnchor::Property::END_CHARACTER_INDEX, static_cast(anchor.endIndex)); + return actor; +} + +void Controller::Impl::GetAnchorActors(std::vector& anchorActors) +{ + /* TODO: Now actors are created/destroyed in every "RenderText" function call. Even when we add just 1 character, + we need to create and destroy potentially many actors. Some optimization can be considered here. + Maybe a "dirty" flag in mLogicalModel? */ + anchorActors.clear(); + for(auto& anchor : mModel->mLogicalModel->mAnchors) + { + auto actor = CreateAnchorActor(anchor); + anchorActors.push_back(actor); + } +} + +int32_t Controller::Impl::GetAnchorIndex(size_t characterOffset) const +{ + Vector::Iterator it = mModel->mLogicalModel->mAnchors.Begin(); + + while(it != mModel->mLogicalModel->mAnchors.End() && (it->startIndex > characterOffset || it->endIndex <= characterOffset)) + { + it++; + } + + return it == mModel->mLogicalModel->mAnchors.End() ? -1 : it - mModel->mLogicalModel->mAnchors.Begin(); +} + void Controller::Impl::CopyUnderlinedFromLogicalToVisualModels(bool shouldClearPreUnderlineRuns) { //Underlined character runs for markup-processor diff --git a/dali-toolkit/internal/text/text-controller-impl.h b/dali-toolkit/internal/text/text-controller-impl.h index 814f3a8..e49971a 100644 --- a/dali-toolkit/internal/text/text-controller-impl.h +++ b/dali-toolkit/internal/text/text-controller-impl.h @@ -841,6 +841,49 @@ struct Controller::Impl */ void ResetScrollPosition(); + /** + * @brief Resets a provided vector with actors that marks the position of anchors in markup enabled text + * + * @param[out] anchorActors the vector of actor (empty collection if no anchors available). + */ + void GetAnchorActors(std::vector& anchorActors); + + /** + * @brief Return an index of first anchor in the anchor vector whose boundaries includes given character offset + * + * @param[in] characterOffset A position in text coords. + * + * @return the 0-based index in anchor vector (-1 if an anchor not found) + */ + int32_t GetAnchorIndex(size_t characterOffset) const; + + /** + * @brief Return the geometrical position of an anchor relative to the parent origin point. + * + * @param[in] anchor An anchor. + * + * @return The x, y, z coordinates of an anchor. + */ + Vector3 GetAnchorPosition(Anchor anchor) const; + + /** + * @brief Return the size of an anchor expresed as a vector containing anchor's width and height. + * + * @param[in] anchor An anchor. + * + * @return The width and height of an anchor. + */ + Vector2 GetAnchorSize(Anchor anchor) const; + + /** + * @brief Return the actor representing an anchor. + * + * @param[in] anchor An anchor. + * + * @return The actor representing an anchor. + */ + Toolkit::TextAnchor CreateAnchorActor(Anchor anchor); + public: /** * @brief Gets implementation from the controller handle. diff --git a/dali-toolkit/internal/text/text-controller.cpp b/dali-toolkit/internal/text/text-controller.cpp index 4ec306b..88eb9ef 100644 --- a/dali-toolkit/internal/text/text-controller.cpp +++ b/dali-toolkit/internal/text/text-controller.cpp @@ -1644,6 +1644,16 @@ Actor Controller::CreateBackgroundActor() return mImpl->CreateBackgroundActor(); } +void Controller::GetAnchorActors(std::vector& anchorActors) +{ + mImpl->GetAnchorActors(anchorActors); +} + +int Controller::GetAnchorIndex(size_t characterOffset) +{ + return mImpl->GetAnchorIndex(characterOffset); +} + Controller::Controller(ControlInterface* controlInterface, EditableControlInterface* editableControlInterface, SelectableControlInterface* selectableControlInterface, diff --git a/dali-toolkit/internal/text/text-controller.h b/dali-toolkit/internal/text/text-controller.h index a5bc8b4..1752e3a 100644 --- a/dali-toolkit/internal/text/text-controller.h +++ b/dali-toolkit/internal/text/text-controller.h @@ -25,6 +25,7 @@ // INTERNAL INCLUDES #include #include +#include #include #include #include @@ -1757,6 +1758,22 @@ public: // Text-input Event Queuing. */ CharacterIndex GetCursorPosition(); + /** + * @brief Resets a provided vector with actors that marks the position of anchors in markup enabled text + * + * @param[out] anchorActors the vector of actor (empty collection if no anchors available). + */ + void GetAnchorActors(std::vector& anchorActors); + + /** + * @brief Return an index of first anchor in the anchor vector whose boundaries includes given character offset + * + * @param[in] characterOffset A position in text coords. + * + * @return the index in anchor vector (-1 if an anchor not found) + */ + int GetAnchorIndex(size_t characterOffset); + protected: // Inherit from Text::Decorator::ControllerInterface. /** * @copydoc Dali::Toolkit::Text::Decorator::ControllerInterface::GetTargetSize() -- 2.7.4