From: Seoyeon Kim Date: Tue, 23 Nov 2021 08:59:25 +0000 (+0000) Subject: Merge "[ATSPI] Implementation of Hypertext and Hyperlink in text controls" into devel... X-Git-Tag: dali_2.1.0~5 X-Git-Url: http://review.tizen.org/git/?p=platform%2Fcore%2Fuifw%2Fdali-toolkit.git;a=commitdiff_plain;h=79f571c4268280bc8384dae746193742f713e745;hp=bad241077bd728d9a5ef25d59398eae889b359a1 Merge "[ATSPI] Implementation of Hypertext and Hyperlink in text controls" into devel/master --- 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()