MarkupProcessData markupProcessData( logicalModel->mColorRuns,
logicalModel->mFontDescriptionRuns,
- logicalModel->mEmbeddedItems );
+ logicalModel->mEmbeddedItems,
+ logicalModel->mAnchors );
Length textSize = 0u;
const uint8_t* utf8 = NULL;
Vector<ColorRun> colorRuns;
Vector<FontDescriptionRun> fontRuns;
Vector<EmbeddedItem> items;
- MarkupProcessData markupProcessData( colorRuns, fontRuns, items );
+ Vector<Anchor> anchors;
+ MarkupProcessData markupProcessData( colorRuns, fontRuns, items, anchors );
ProcessMarkupString( data.xHTMLEntityString, markupProcessData );
for( Vector<EmbeddedItem>::Iterator it = items.Begin(),
const std::string DEFAULT_DEVICE_NAME("hwKeyboard");
+static bool gAnchorClickedCallBackCalled;
+static bool gAnchorClickedCallBackNotCalled;
static bool gTextChangedCallBackCalled;
static bool gInputStyleChangedCallbackCalled;
static bool gMaxCharactersCallBackCalled;
bool* mCallbackFlag;
};
+static void TestAnchorClickedCallback(TextEditor control, const char* href, unsigned int hrefLength)
+{
+ tet_infoline(" TestAnchorClickedCallback");
+
+ gAnchorClickedCallBackNotCalled = false;
+
+ if (!strcmp(href, "https://www.tizen.org") && hrefLength == strlen(href))
+ {
+ gAnchorClickedCallBackCalled = true;
+ }
+}
+
static void TestTextChangedCallback( TextEditor control )
{
tet_infoline(" TestTextChangedCallback");
END_TEST;
}
+// Positive test for the anchorClicked signal.
+int utcDaliTextEditorAnchorClickedP(void)
+{
+ ToolkitTestApplication application;
+ tet_infoline(" utcDaliTextEditorAnchorClickedP");
+ TextEditor editor = TextEditor::New();
+ DALI_TEST_CHECK(editor);
+
+ application.GetScene().Add(editor);
+
+ // connect to the anchor clicked signal.
+ ConnectionTracker* testTracker = new ConnectionTracker();
+ DevelTextEditor::AnchorClickedSignal(editor).Connect(&TestAnchorClickedCallback);
+ bool anchorClickedSignal = false;
+ editor.ConnectSignal(testTracker, "anchorClicked", CallbackFunctor(&anchorClickedSignal));
+
+ gAnchorClickedCallBackCalled = false;
+ editor.SetProperty(TextEditor::Property::TEXT, "<a href='https://www.tizen.org'>TIZEN</a>");
+ editor.SetProperty(TextEditor::Property::ENABLE_MARKUP, true);
+ editor.SetProperty(Actor::Property::SIZE, Vector2(100.f, 50.f));
+ editor.SetProperty(Actor::Property::PARENT_ORIGIN, ParentOrigin::TOP_LEFT);
+ editor.SetProperty(Actor::Property::ANCHOR_POINT, AnchorPoint::TOP_LEFT);
+
+ application.SendNotification();
+ application.Render();
+ editor.SetKeyInputFocus();
+
+ // Create a tap event to touch the text editor.
+ TestGenerateTap(application, 5.0f, 5.0f);
+ application.SendNotification();
+ application.Render();
+
+ DALI_TEST_CHECK(gAnchorClickedCallBackCalled);
+ DALI_TEST_CHECK(anchorClickedSignal);
+
+ gAnchorClickedCallBackNotCalled = true;
+ // Tap the outside of anchor, callback should not be called.
+ TestGenerateTap(application, 150.f, 100.f);
+ application.SendNotification();
+ application.Render();
+
+ DALI_TEST_CHECK(gAnchorClickedCallBackNotCalled);
+
+ END_TEST;
+}
+
// Positive test for the textChanged signal.
int utcDaliTextEditorTextChangedP(void)
{
const std::string DEFAULT_DEVICE_NAME("hwKeyboard");
+static bool gAnchorClickedCallBackCalled;
+static bool gAnchorClickedCallBackNotCalled;
static bool gTextChangedCallBackCalled;
static bool gMaxCharactersCallBackCalled;
static bool gInputStyleChangedCallbackCalled;
bool* mCallbackFlag;
};
+static void TestAnchorClickedCallback(TextField control, const char* href, unsigned int hrefLength)
+{
+ tet_infoline(" TestAnchorClickedCallback");
+
+ gAnchorClickedCallBackNotCalled = false;
+
+ if (!strcmp(href, "https://www.tizen.org") && hrefLength == strlen(href))
+ {
+ gAnchorClickedCallBackCalled = true;
+ }
+}
+
static void TestTextChangedCallback( TextField control )
{
tet_infoline(" TestTextChangedCallback");
END_TEST;
}
+// Positive test for the anchorClicked signal.
+int utcDaliTextFieldAnchorClicked01(void)
+{
+ ToolkitTestApplication application;
+ tet_infoline(" utcDaliTextFieldAnchorClicked01");
+ TextField field = TextField::New();
+ DALI_TEST_CHECK(field);
+
+ application.GetScene().Add(field);
+
+ // connect to the anchor clicked signal.
+ ConnectionTracker* testTracker = new ConnectionTracker();
+ DevelTextField::AnchorClickedSignal(field).Connect(&TestAnchorClickedCallback);
+ bool anchorClickedSignal = false;
+ field.ConnectSignal(testTracker, "anchorClicked", CallbackFunctor(&anchorClickedSignal));
+
+ gAnchorClickedCallBackCalled = false;
+ field.SetProperty(TextField::Property::TEXT, "<a href='https://www.tizen.org'>TIZEN</a>");
+ field.SetProperty(TextField::Property::ENABLE_MARKUP, true);
+ field.SetProperty(Actor::Property::SIZE, Vector2(100.f, 50.f));
+ field.SetProperty(Actor::Property::PARENT_ORIGIN, ParentOrigin::TOP_LEFT);
+ field.SetProperty(Actor::Property::ANCHOR_POINT, AnchorPoint::TOP_LEFT);
+
+ application.SendNotification();
+ application.Render();
+ field.SetKeyInputFocus();
+
+ // Create a tap event to touch the text field.
+ TestGenerateTap(application, 5.0f, 25.0f);
+ application.SendNotification();
+ application.Render();
+
+ DALI_TEST_CHECK(gAnchorClickedCallBackCalled);
+ DALI_TEST_CHECK(anchorClickedSignal);
+
+ gAnchorClickedCallBackNotCalled = true;
+ // Tap the outside of anchor, callback should not be called.
+ TestGenerateTap(application, 150.f, 100.f);
+ application.SendNotification();
+ application.Render();
+
+ DALI_TEST_CHECK(gAnchorClickedCallBackNotCalled);
+
+ END_TEST;
+}
+
+// Positive test for the anchorClicked signal.
+int utcDaliTextFieldAnchorClicked02(void)
+{
+ ToolkitTestApplication application;
+ tet_infoline(" utcDaliTextFieldAnchorClicked02");
+ TextField field = TextField::New();
+ DALI_TEST_CHECK(field);
+
+ application.GetScene().Add(field);
+
+ // connect to the anchor clicked signal.
+ ConnectionTracker* testTracker = new ConnectionTracker();
+ DevelTextField::AnchorClickedSignal(field).Connect(&TestAnchorClickedCallback);
+ bool anchorClickedSignal = false;
+ field.ConnectSignal(testTracker, "anchorClicked", CallbackFunctor(&anchorClickedSignal));
+
+ gAnchorClickedCallBackCalled = false;
+ field.SetProperty(TextField::Property::TEXT, "<a href='https://www.tizen.org'>TIZEN</a>");
+ field.SetProperty(TextField::Property::ENABLE_MARKUP, true);
+ field.SetProperty(Actor::Property::SIZE, Vector2(100.f, 50.f));
+ field.SetProperty(Actor::Property::PARENT_ORIGIN, ParentOrigin::TOP_LEFT);
+ field.SetProperty(Actor::Property::ANCHOR_POINT, AnchorPoint::TOP_LEFT);
+
+ application.SendNotification();
+ application.Render();
+ field.SetKeyInputFocus();
+
+ // Avoid a crash when core load gl resources.
+ application.GetGlAbstraction().SetCheckFramebufferStatusResult(GL_FRAMEBUFFER_COMPLETE);
+
+ // Create a tap event to touch the text field.
+ TestGenerateTap(application, 30.0f, 25.0f);
+ application.SendNotification();
+ application.Render();
+
+ DALI_TEST_CHECK(gAnchorClickedCallBackCalled);
+ DALI_TEST_CHECK(anchorClickedSignal);
+
+
+ // For coverage InsertTextAnchor, RemoveTextAnchor
+ // first index insert
+ field.SetProperty(TextField::Property::TEXT, "<a href='https://www.tizen.org'>TIZEN</a>");
+ field.SetProperty(DevelTextField::Property::PRIMARY_CURSOR_POSITION, 0);
+ application.SendNotification();
+ application.Render();
+
+ application.ProcessEvent( GenerateKey( "D", "", "D", KEY_D_CODE, 0, 0, Integration::KeyEvent::DOWN, "D", DEFAULT_DEVICE_NAME, Device::Class::NONE, Device::Subclass::NONE ) );
+ application.SendNotification();
+ application.Render();
+
+ gAnchorClickedCallBackCalled = false;
+ // Create a tap event to touch the text field.
+ TestGenerateTap(application, 30.0f, 25.0f);
+ application.SendNotification();
+ field.SetKeyInputFocus();
+
+ DALI_TEST_CHECK(gAnchorClickedCallBackCalled);
+
+ // last index insert
+ field.SetProperty(TextField::Property::TEXT, "<a href='https://www.tizen.org'>TIZEN</a>");
+ field.SetProperty(DevelTextField::Property::PRIMARY_CURSOR_POSITION, 5);
+ application.SendNotification();
+ application.Render();
+
+ application.ProcessEvent( GenerateKey( "D", "", "D", KEY_D_CODE, 0, 0, Integration::KeyEvent::DOWN, "D", DEFAULT_DEVICE_NAME, Device::Class::NONE, Device::Subclass::NONE ) );
+ application.SendNotification();
+ application.Render();
+
+ gAnchorClickedCallBackCalled = false;
+ // Create a tap event to touch the text field.
+ TestGenerateTap(application, 30.0f, 25.0f);
+ application.SendNotification();
+ field.SetKeyInputFocus();
+
+ DALI_TEST_CHECK(gAnchorClickedCallBackCalled);
+
+ // mid index insert
+ field.SetProperty(TextField::Property::TEXT, "<a href='https://www.tizen.org'>TIZEN</a>");
+ field.SetProperty(DevelTextField::Property::PRIMARY_CURSOR_POSITION, 2);
+ application.SendNotification();
+ application.Render();
+
+ application.ProcessEvent( GenerateKey( "D", "", "D", KEY_D_CODE, 0, 0, Integration::KeyEvent::DOWN, "D", DEFAULT_DEVICE_NAME, Device::Class::NONE, Device::Subclass::NONE ) );
+ application.SendNotification();
+ application.Render();
+
+ gAnchorClickedCallBackCalled = false;
+ // Create a tap event to touch the text field.
+ TestGenerateTap(application, 30.0f, 25.0f);
+ application.SendNotification();
+ field.SetKeyInputFocus();
+
+ DALI_TEST_CHECK(gAnchorClickedCallBackCalled);
+
+ // first index remove
+ field.SetProperty(TextField::Property::TEXT, "<a href='https://www.tizen.org'>TIZEN</a>");
+ field.SetProperty(DevelTextField::Property::PRIMARY_CURSOR_POSITION, 0);
+ application.SendNotification();
+ application.Render();
+
+ application.ProcessEvent(GenerateKey("", "", "", DALI_KEY_BACKSPACE, 0, 0, Integration::KeyEvent::DOWN, "", DEFAULT_DEVICE_NAME, Device::Class::NONE, Device::Subclass::NONE));
+ application.ProcessEvent(GenerateKey("Delete", "", "Delete", Dali::DevelKey::DALI_KEY_DELETE, 0, 0, Integration::KeyEvent::DOWN, "Delete", DEFAULT_DEVICE_NAME, Device::Class::NONE, Device::Subclass::NONE));
+ application.SendNotification();
+ application.Render();
+
+ gAnchorClickedCallBackCalled = false;
+ // Create a tap event to touch the text field.
+ TestGenerateTap(application, 30.0f, 25.0f);
+ application.SendNotification();
+ field.SetKeyInputFocus();
+
+ DALI_TEST_CHECK(gAnchorClickedCallBackCalled);
+
+ // last index remove
+ field.SetProperty(TextField::Property::TEXT, "<a href='https://www.tizen.org'>TIZEN</a>");
+ field.SetProperty(DevelTextField::Property::PRIMARY_CURSOR_POSITION, 5);
+ application.SendNotification();
+ application.Render();
+
+ application.ProcessEvent(GenerateKey("", "", "", DALI_KEY_BACKSPACE, 0, 0, Integration::KeyEvent::DOWN, "", DEFAULT_DEVICE_NAME, Device::Class::NONE, Device::Subclass::NONE));
+ application.ProcessEvent(GenerateKey("Delete", "", "Delete", Dali::DevelKey::DALI_KEY_DELETE, 0, 0, Integration::KeyEvent::DOWN, "Delete", DEFAULT_DEVICE_NAME, Device::Class::NONE, Device::Subclass::NONE));
+ application.SendNotification();
+ application.Render();
+
+ gAnchorClickedCallBackCalled = false;
+ // Create a tap event to touch the text field.
+ TestGenerateTap(application, 30.0f, 25.0f);
+ application.SendNotification();
+ application.Render();
+
+ DALI_TEST_CHECK(gAnchorClickedCallBackCalled);
+
+ // middle index
+ field.SetProperty(TextField::Property::TEXT, "<a href='https://www.tizen.org'>TIZEN</a>");
+ field.SetProperty(DevelTextField::Property::PRIMARY_CURSOR_POSITION, 2);
+ application.SendNotification();
+ application.Render();
+
+ application.ProcessEvent(GenerateKey("", "", "", DALI_KEY_BACKSPACE, 0, 0, Integration::KeyEvent::DOWN, "", DEFAULT_DEVICE_NAME, Device::Class::NONE, Device::Subclass::NONE));
+ application.ProcessEvent(GenerateKey("Delete", "", "Delete", Dali::DevelKey::DALI_KEY_DELETE, 0, 0, Integration::KeyEvent::DOWN, "Delete", DEFAULT_DEVICE_NAME, Device::Class::NONE, Device::Subclass::NONE));
+ application.SendNotification();
+ application.Render();
+
+ gAnchorClickedCallBackCalled = false;
+ // Create a tap event to touch the text field.
+ TestGenerateTap(application, 30.0f, 25.0f);
+ application.SendNotification();
+ application.Render();
+
+ DALI_TEST_CHECK(gAnchorClickedCallBackCalled);
+
+ // 0 ~ 1 index remove
+ field.SetProperty(TextField::Property::TEXT, "<a href='https://www.tizen.org'>TIZEN</a>");
+ field.SetProperty( DevelTextField::Property::SELECTED_TEXT_START, 0);
+ field.SetProperty( DevelTextField::Property::SELECTED_TEXT_END, 1);
+ application.SendNotification();
+ application.Render();
+
+ application.ProcessEvent(GenerateKey("", "", "", DALI_KEY_BACKSPACE, 0, 0, Integration::KeyEvent::DOWN, "", DEFAULT_DEVICE_NAME, Device::Class::NONE, Device::Subclass::NONE));
+ application.SendNotification();
+ application.Render();
+
+ gAnchorClickedCallBackCalled = false;
+ // Create a tap event to touch the text field.
+ TestGenerateTap(application, 30.0f, 25.0f);
+ application.SendNotification();
+ application.Render();
+
+ DALI_TEST_CHECK(gAnchorClickedCallBackCalled);
+
+ // 1 ~ 3 index remove
+ field.SetProperty(TextField::Property::TEXT, "<a href='https://www.tizen.org'>TIZEN</a>");
+ field.SetProperty( DevelTextField::Property::SELECTED_TEXT_START, 1);
+ field.SetProperty( DevelTextField::Property::SELECTED_TEXT_END, 3);
+ application.SendNotification();
+ application.Render();
+
+ application.ProcessEvent(GenerateKey("", "", "", DALI_KEY_BACKSPACE, 0, 0, Integration::KeyEvent::DOWN, "", DEFAULT_DEVICE_NAME, Device::Class::NONE, Device::Subclass::NONE));
+ application.SendNotification();
+ application.Render();
+
+ gAnchorClickedCallBackCalled = false;
+ // Create a tap event to touch the text field.
+ TestGenerateTap(application, 30.0f, 25.0f);
+ application.SendNotification();
+ application.Render();
+
+ DALI_TEST_CHECK(gAnchorClickedCallBackCalled);
+
+ // 3 ~ 4 index remove
+ field.SetProperty(TextField::Property::TEXT, "<a href='https://www.tizen.org'>TIZEN</a>");
+ field.SetProperty( DevelTextField::Property::SELECTED_TEXT_START, 3);
+ field.SetProperty( DevelTextField::Property::SELECTED_TEXT_END, 4);
+ application.SendNotification();
+ application.Render();
+
+ application.ProcessEvent(GenerateKey("", "", "", DALI_KEY_BACKSPACE, 0, 0, Integration::KeyEvent::DOWN, "", DEFAULT_DEVICE_NAME, Device::Class::NONE, Device::Subclass::NONE));
+ application.SendNotification();
+ application.Render();
+
+ gAnchorClickedCallBackCalled = false;
+ // Create a tap event to touch the text field.
+ TestGenerateTap(application, 30.0f, 25.0f);
+ application.SendNotification();
+ application.Render();
+
+ DALI_TEST_CHECK(gAnchorClickedCallBackCalled);
+
+ // Remove front of anchor
+ field.SetProperty(TextField::Property::TEXT, "TIZEN<a href='https://www.tizen.org'>TIZEN</a>");
+ field.SetProperty(DevelTextField::Property::PRIMARY_CURSOR_POSITION, 3);
+ application.SendNotification();
+ application.Render();
+
+ application.ProcessEvent(GenerateKey("", "", "", DALI_KEY_BACKSPACE, 0, 0, Integration::KeyEvent::DOWN, "", DEFAULT_DEVICE_NAME, Device::Class::NONE, Device::Subclass::NONE));
+ application.SendNotification();
+ application.Render();
+
+ // Remove whole text
+ field.SetProperty(TextField::Property::TEXT, "<a href='https://www.tizen.org'>TIZEN</a>");
+ DevelTextField::SelectWholeText(field);
+ application.SendNotification();
+ application.Render();
+
+ application.ProcessEvent(GenerateKey("", "", "", DALI_KEY_BACKSPACE, 0, 0, Integration::KeyEvent::DOWN, "", DEFAULT_DEVICE_NAME, Device::Class::NONE, Device::Subclass::NONE));
+ application.SendNotification();
+ application.Render();
+
+ // Remove all with backspace
+ field.SetProperty(TextField::Property::TEXT, "<a href='https://www.tizen.org'>T</a>");
+ field.SetProperty(DevelTextField::Property::PRIMARY_CURSOR_POSITION, 1);
+ application.SendNotification();
+ application.Render();
+
+ application.ProcessEvent(GenerateKey("", "", "", DALI_KEY_BACKSPACE, 0, 0, Integration::KeyEvent::DOWN, "", DEFAULT_DEVICE_NAME, Device::Class::NONE, Device::Subclass::NONE));
+ application.SendNotification();
+ application.Render();
+
+ // Remove all with delete
+ field.SetProperty(TextField::Property::TEXT, "<a href='https://www.tizen.org'>T</a>");
+ field.SetProperty(DevelTextField::Property::PRIMARY_CURSOR_POSITION, 0);
+ application.SendNotification();
+ application.Render();
+
+ application.ProcessEvent(GenerateKey("Delete", "", "Delete", Dali::DevelKey::DALI_KEY_DELETE, 0, 0, Integration::KeyEvent::DOWN, "Delete", DEFAULT_DEVICE_NAME, Device::Class::NONE, Device::Subclass::NONE));
+ application.SendNotification();
+ application.Render();
+
+ END_TEST;
+}
+
// Positive test for the textChanged signal.
int utcDaliTextFieldTextChangedP(void)
{
/*
- * Copyright (c) 2020 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2021 Samsung Electronics Co., Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
const std::string DEFAULT_FONT_DIR( "/resources/fonts" );
const unsigned int EMOJI_FONT_SIZE = 3840u; // 60 * 64
+static bool gAnchorClickedCallBackCalled;
+static bool gAnchorClickedCallBackNotCalled;
+
+struct CallbackFunctor
+{
+ CallbackFunctor(bool* callbackFlag)
+ : mCallbackFlag( callbackFlag )
+ {
+ }
+
+ void operator()()
+ {
+ *mCallbackFlag = true;
+ }
+ bool* mCallbackFlag;
+};
+
+static void TestAnchorClickedCallback(TextLabel control, const char* href, unsigned int hrefLength)
+{
+ tet_infoline(" TestAnchorClickedCallback");
+
+ gAnchorClickedCallBackNotCalled = false;
+
+ if (!strcmp(href, "https://www.tizen.org") && hrefLength == strlen(href))
+ {
+ gAnchorClickedCallBackCalled = true;
+ }
+}
+
bool DaliTestCheckMaps( const Property::Map& mapGet, const Property::Map& mapSet, const std::vector<std::string>& indexConversionTable = std::vector<std::string>() )
{
const Property::Map::SizeType size = mapGet.Count();
END_TEST;
}
+
+// Positive test for the anchorClicked signal.
+int UtcDaliToolkitTextlabelAnchorClicked(void)
+{
+ ToolkitTestApplication application;
+ tet_infoline(" UtcDaliToolkitTextlabelAnchorClicked");
+ TextLabel label = TextLabel::New();
+ DALI_TEST_CHECK(label);
+
+ application.GetScene().Add(label);
+
+ // connect to the anchor clicked signal.
+ ConnectionTracker* testTracker = new ConnectionTracker();
+ DevelTextLabel::AnchorClickedSignal(label).Connect(&TestAnchorClickedCallback);
+ bool anchorClickedSignal = false;
+ label.ConnectSignal(testTracker, "anchorClicked", CallbackFunctor(&anchorClickedSignal));
+
+ gAnchorClickedCallBackCalled = false;
+ label.SetProperty(TextLabel::Property::TEXT, "<a href='https://www.tizen.org'>TIZEN</a>");
+ label.SetProperty(TextLabel::Property::ENABLE_MARKUP, true);
+ label.SetProperty(Actor::Property::SIZE, Vector2(100.f, 50.f));
+ label.SetProperty(Actor::Property::PARENT_ORIGIN, ParentOrigin::TOP_LEFT);
+ label.SetProperty(Actor::Property::ANCHOR_POINT, AnchorPoint::TOP_LEFT);
+
+ application.SendNotification();
+ application.Render();
+
+ // Create a tap event to touch the text label.
+ TestGenerateTap(application, 5.0f, 25.0f);
+ application.SendNotification();
+ application.Render();
+
+ DALI_TEST_CHECK(gAnchorClickedCallBackCalled);
+ DALI_TEST_CHECK(anchorClickedSignal);
+
+
+ gAnchorClickedCallBackNotCalled = true;
+ // Tap the outside of anchor, callback should not be called.
+ TestGenerateTap(application, 150.f, 100.f);
+ application.SendNotification();
+ application.Render();
+
+ DALI_TEST_CHECK(gAnchorClickedCallBackNotCalled);
+
+ END_TEST;
+}
/*
- * Copyright (c) 2020 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2021 Samsung Electronics Co., Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
return GetImpl(textEditor).MaxLengthReachedSignal();
}
+AnchorClickedSignalType& AnchorClickedSignal(TextEditor textEditor)
+{
+ return GetImpl(textEditor).AnchorClickedSignal();
+}
+
void SelectWholeText(TextEditor textEditor)
{
GetImpl(textEditor).SelectWholeText();
*/
DALI_TOOLKIT_API MaxLengthReachedSignalType& MaxLengthReachedSignal(TextEditor textEditor);
+/**
+ * @brief Anchor clicked signal type.
+ *
+ * @note Signal
+ * - const char* : href of clicked anchor.
+ * - uint32_t : length of href.
+ */
+using AnchorClickedSignalType = Signal<void(TextEditor, const char*, uint32_t)>;
+
+/**
+ * @brief This signal is emitted when the anchor is clicked.
+ *
+ * A callback of the following type may be connected:
+ * @code
+ * void YourCallbackName(TextEditor textEditor, const char* href, uint32_t hrefLength);
+ * @endcode
+ * @param[in] textEditor The instance of TextEditor.
+ * @return The signal to connect to.
+ */
+DALI_TOOLKIT_API AnchorClickedSignalType& AnchorClickedSignal(TextEditor textEditor);
+
/**
* @brief Select the whole text of TextEditor.
*
/*
- * Copyright (c) 2020 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2021 Samsung Electronics Co., Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
return GetImpl(textField).GetInputMethodContext();
}
+AnchorClickedSignalType& AnchorClickedSignal(TextField textField)
+{
+ return GetImpl(textField).AnchorClickedSignal();
+}
+
void SelectWholeText(TextField textField)
{
GetImpl(textField).SelectWholeText();
*/
DALI_TOOLKIT_API InputMethodContext GetInputMethodContext(TextField textField);
+/**
+ * @brief Anchor clicked signal type.
+ *
+ * @note Signal
+ * - const char* : href of clicked anchor.
+ * - uint32_t : length of href.
+ */
+using AnchorClickedSignalType = Signal<void(TextField, const char*, uint32_t)>;
+
+/**
+ * @brief This signal is emitted when the anchor is clicked.
+ *
+ * A callback of the following type may be connected:
+ * @code
+ * void YourCallbackName(TextField textField, const char* href, uint32_t hrefLength);
+ * @endcode
+ * @param[in] textField The instance of TextField.
+ * @return The signal to connect to.
+ */
+DALI_TOOLKIT_API AnchorClickedSignalType& AnchorClickedSignal(TextField textField);
+
/**
* @brief Select the whole text of TextField.
*
--- /dev/null
+/*
+ * Copyright (c) 2021 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+// INTERNAL INCLUDES
+#include <dali-toolkit/devel-api/controls/text-controls/text-label-devel.h>
+#include <dali-toolkit/internal/controls/text-controls/text-label-impl.h>
+
+namespace Dali
+{
+namespace Toolkit
+{
+namespace DevelTextLabel
+{
+AnchorClickedSignalType& AnchorClickedSignal(TextLabel textLabel)
+{
+ return GetImpl(textLabel).AnchorClickedSignal();
+}
+
+} // namespace DevelTextLabel
+
+} // namespace Toolkit
+
+} // namespace Dali
#define DALI_TOOLKIT_TEXT_LABEL_DEVEL_H
/*
- * Copyright (c) 2020 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2021 Samsung Electronics Co., Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
} // namespace Property
+/**
+ * @brief Anchor clicked signal type.
+ *
+ * @note Signal
+ * - const char* : href of clicked anchor.
+ * - uint32_t : length of href.
+ */
+using AnchorClickedSignalType = Signal<void(TextLabel, const char*, uint32_t)>;
+
+/**
+ * @brief This signal is emitted when the anchor is clicked.
+ *
+ * A callback of the following type may be connected:
+ * @code
+ * void YourCallbackName(TextLabel textLabel, const char* href, uint32_t hrefLength);
+ * @endcode
+ * @param[in] textLabel The instance of TextLabel.
+ * @return The signal to connect to.
+ */
+DALI_TOOLKIT_API AnchorClickedSignalType& AnchorClickedSignal(TextLabel textLabel);
+
} // namespace DevelTextLabel
} // namespace Toolkit
${devel_api_src_dir}/controls/table-view/table-view.cpp
${devel_api_src_dir}/controls/text-controls/text-editor-devel.cpp
${devel_api_src_dir}/controls/text-controls/text-field-devel.cpp
+ ${devel_api_src_dir}/controls/text-controls/text-label-devel.cpp
${devel_api_src_dir}/controls/text-controls/text-selection-popup.cpp
${devel_api_src_dir}/controls/text-controls/text-selection-toolbar.cpp
${devel_api_src_dir}/controls/tool-bar/tool-bar.cpp
MarkupProcessData markupProcessData(colorRuns,
fontDescriptionRuns,
- textModel->mLogicalModel->mEmbeddedItems);
+ textModel->mLogicalModel->mEmbeddedItems,
+ textModel->mLogicalModel->mAnchors);
if(textParameters.markupEnabled)
{
DALI_SIGNAL_REGISTRATION(Toolkit, TextEditor, "textChanged", SIGNAL_TEXT_CHANGED )
DALI_SIGNAL_REGISTRATION(Toolkit, TextEditor, "inputStyleChanged", SIGNAL_INPUT_STYLE_CHANGED)
DALI_SIGNAL_REGISTRATION(Toolkit, TextEditor, "maxLengthReached", SIGNAL_MAX_LENGTH_REACHED )
+DALI_SIGNAL_REGISTRATION(Toolkit, TextEditor, "anchorClicked", SIGNAL_ANCHOR_CLICKED )
DALI_TYPE_REGISTRATION_END()
// clang-format on
return mMaxLengthReachedSignal;
}
+DevelTextEditor::AnchorClickedSignalType& TextEditor::AnchorClickedSignal()
+{
+ return mAnchorClickedSignal;
+}
+
Text::ControllerPtr TextEditor::getController()
{
return mController;
editorImpl.MaxLengthReachedSignal().Connect(tracker, functor);
}
}
+ else if(0 == strcmp(signalName.c_str(), SIGNAL_ANCHOR_CLICKED))
+ {
+ if(editor)
+ {
+ Internal::TextEditor& editorImpl(GetImpl(editor));
+ editorImpl.AnchorClickedSignal().Connect(tracker, functor);
+ }
+ }
else
{
// signalName does not match any signal
{
Actor self = Self();
- mController = Text::Controller::New(this, this, this);
+ mController = Text::Controller::New(this, this, this, this);
mDecorator = Text::Decorator::New(*mController,
*mController);
padding = Self().GetProperty<Extents>(Toolkit::Control::Property::PADDING);
const Vector2& localPoint = gesture.GetLocalPoint();
mController->TapEvent(gesture.GetNumberOfTaps(), localPoint.x - padding.start, localPoint.y - padding.top);
+ mController->AnchorEvent(localPoint.x - padding.start, localPoint.y - padding.top);
SetKeyInputFocus();
}
mInputStyleChangedSignal.Emit(handle, editorInputStyleMask);
}
+void TextEditor::AnchorClicked(const std::string& href)
+{
+ Dali::Toolkit::TextEditor handle(GetOwner());
+ mAnchorClickedSignal.Emit(handle, href.c_str(), href.length());
+}
+
void TextEditor::AddDecoration(Actor& actor, bool needsClipping)
{
if(actor)
#include <dali-toolkit/internal/controls/control/control-data-impl.h>
#include <dali-toolkit/internal/text/decorator/text-decorator.h>
#include <dali-toolkit/internal/text/rendering/text-renderer.h>
+#include <dali-toolkit/internal/text/text-anchor-control-interface.h>
#include <dali-toolkit/internal/text/text-control-interface.h>
#include <dali-toolkit/internal/text/text-controller.h>
#include <dali-toolkit/internal/text/text-editable-control-interface.h>
/**
* @brief A control which renders a long text string with styles.
*/
-class TextEditor : public Control, public Text::ControlInterface, public Text::EditableControlInterface, public Text::SelectableControlInterface
+class TextEditor : public Control, public Text::ControlInterface, public Text::EditableControlInterface, public Text::SelectableControlInterface, public Text::AnchorControlInterface
{
public:
/**
*/
DevelTextEditor::MaxLengthReachedSignalType& MaxLengthReachedSignal();
+ /**
+ * @copydoc Dali::Toollkit::TextEditor::AnchorClickedSignal()
+ */
+ DevelTextEditor::AnchorClickedSignalType& AnchorClickedSignal();
+
/**
* Connects a callback function with the object's signals.
* @param[in] object The object providing the signal.
/**
* @copydoc Text::EditableControlInterface::SetEditable()
*/
- void SetEditable(bool editable) override;
+ void SetEditable(bool editable) override;
+
+ // From AnchorControlInterface
+
+ /**
+ * @copydoc Text::AnchorControlInterface::AnchorClicked()
+ */
+ void AnchorClicked(const std::string& href) override;
+
Text::ControllerPtr getController();
private: // Implementation
Toolkit::TextEditor::InputStyleChangedSignalType mInputStyleChangedSignal;
Toolkit::TextEditor::ScrollStateChangedSignalType mScrollStateChangedSignal;
Toolkit::DevelTextEditor::MaxLengthReachedSignalType mMaxLengthReachedSignal;
+ Toolkit::DevelTextEditor::AnchorClickedSignalType mAnchorClickedSignal;
InputMethodContext mInputMethodContext;
Text::ControllerPtr mController;
DALI_SIGNAL_REGISTRATION(Toolkit, TextField, "textChanged", SIGNAL_TEXT_CHANGED )
DALI_SIGNAL_REGISTRATION(Toolkit, TextField, "maxLengthReached", SIGNAL_MAX_LENGTH_REACHED )
DALI_SIGNAL_REGISTRATION(Toolkit, TextField, "inputStyleChanged", SIGNAL_INPUT_STYLE_CHANGED)
+DALI_SIGNAL_REGISTRATION(Toolkit, TextField, "anchorClicked", SIGNAL_ANCHOR_CLICKED )
DALI_TYPE_REGISTRATION_END()
// clang-format on
{
field.InputStyleChangedSignal().Connect(tracker, functor);
}
+ else if(0 == strcmp(signalName.c_str(), SIGNAL_ANCHOR_CLICKED))
+ {
+ if(field)
+ {
+ Internal::TextField& fieldImpl(GetImpl(field));
+ fieldImpl.AnchorClickedSignal().Connect(tracker, functor);
+ }
+ }
else
{
// signalName does not match any signal
return mInputStyleChangedSignal;
}
+DevelTextField::AnchorClickedSignalType& TextField::AnchorClickedSignal()
+{
+ return mAnchorClickedSignal;
+}
+
void TextField::OnInitialize()
{
Actor self = Self();
- mController = Text::Controller::New(this, this, this);
+ mController = Text::Controller::New(this, this, this, this);
// When using the vector-based rendering, the size of the GLyphs are different
TextAbstraction::GlyphType glyphType = (DevelText::RENDERING_VECTOR_BASED == mRenderingBackend) ? TextAbstraction::VECTOR_GLYPH : TextAbstraction::BITMAP_GLYPH;
padding = Self().GetProperty<Extents>(Toolkit::Control::Property::PADDING);
const Vector2& localPoint = gesture.GetLocalPoint();
mController->TapEvent(gesture.GetNumberOfTaps(), localPoint.x - padding.start, localPoint.y - padding.top);
+ mController->AnchorEvent(localPoint.x - padding.start, localPoint.y - padding.top);
SetKeyInputFocus();
}
mInputStyleChangedSignal.Emit(handle, fieldInputStyleMask);
}
+void TextField::AnchorClicked(const std::string& href)
+{
+ Dali::Toolkit::TextField handle(GetOwner());
+ mAnchorClickedSignal.Emit(handle, href.c_str(), href.length());
+}
+
void TextField::AddDecoration(Actor& actor, bool needsClipping)
{
if(actor)
// INTERNAL INCLUDES
#include <dali-toolkit/devel-api/controls/control-devel.h>
+#include <dali-toolkit/devel-api/controls/text-controls/text-field-devel.h>
#include <dali-toolkit/internal/controls/control/control-data-impl.h>
#include <dali-toolkit/internal/text/decorator/text-decorator.h>
#include <dali-toolkit/internal/text/rendering/text-renderer.h>
+#include <dali-toolkit/internal/text/text-anchor-control-interface.h>
#include <dali-toolkit/internal/text/text-control-interface.h>
#include <dali-toolkit/internal/text/text-controller.h>
#include <dali-toolkit/internal/text/text-editable-control-interface.h>
/**
* @brief A control which renders a short text string.
*/
-class TextField : public Control, public Text::ControlInterface, public Text::EditableControlInterface, public Text::SelectableControlInterface
+class TextField : public Control, public Text::ControlInterface, public Text::EditableControlInterface, public Text::SelectableControlInterface, public Text::AnchorControlInterface
{
public:
/**
*/
Toolkit::TextField::InputStyleChangedSignalType& InputStyleChangedSignal();
+ /**
+ * @copydoc TextField::AnchorClickedSignal()
+ */
+ DevelTextField::AnchorClickedSignalType& AnchorClickedSignal();
+
Text::ControllerPtr getController();
private: // From Control
*/
void SetEditable(bool editable) override;
+ // From AnchorControlInterface
+
+ /**
+ * @copydoc Text::AnchorControlInterface::AnchorClicked()
+ */
+ void AnchorClicked(const std::string& href) override;
+
private: // Implementation
/**
* @copydoc Dali::Toolkit::Text::Controller::(InputMethodContext& inputMethodContext, const InputMethodContext::EventData& inputMethodContextEvent)
private: // Data
// Signals
- Toolkit::TextField::TextChangedSignalType mTextChangedSignal;
- Toolkit::TextField::MaxLengthReachedSignalType mMaxLengthReachedSignal;
- Toolkit::TextField::InputStyleChangedSignalType mInputStyleChangedSignal;
+ Toolkit::TextField::TextChangedSignalType mTextChangedSignal;
+ Toolkit::TextField::MaxLengthReachedSignalType mMaxLengthReachedSignal;
+ Toolkit::TextField::InputStyleChangedSignalType mInputStyleChangedSignal;
+ Toolkit::DevelTextField::AnchorClickedSignalType mAnchorClickedSignal;
InputMethodContext mInputMethodContext;
Text::ControllerPtr mController;
DALI_ANIMATABLE_PROPERTY_COMPONENT_REGISTRATION(Toolkit, TextLabel, "textColorGreen", TEXT_COLOR_GREEN, TEXT_COLOR, 1)
DALI_ANIMATABLE_PROPERTY_COMPONENT_REGISTRATION(Toolkit, TextLabel, "textColorBlue", TEXT_COLOR_BLUE, TEXT_COLOR, 2)
DALI_ANIMATABLE_PROPERTY_COMPONENT_REGISTRATION(Toolkit, TextLabel, "textColorAlpha", TEXT_COLOR_ALPHA, TEXT_COLOR, 3)
+
+DALI_SIGNAL_REGISTRATION(Toolkit, TextLabel, "anchorClicked", SIGNAL_ANCHOR_CLICKED)
+
DALI_TYPE_REGISTRATION_END()
// clang-format on
return value;
}
+bool TextLabel::DoConnectSignal(BaseObject* object, ConnectionTrackerInterface* tracker, const std::string& signalName, FunctorDelegate* functor)
+{
+ Dali::BaseHandle handle(object);
+
+ bool connected(true);
+ Toolkit::TextLabel label = Toolkit::TextLabel::DownCast(handle);
+
+ if(0 == strcmp(signalName.c_str(), SIGNAL_ANCHOR_CLICKED))
+ {
+ if(label)
+ {
+ Internal::TextLabel& labelImpl(GetImpl(label));
+ labelImpl.AnchorClickedSignal().Connect(tracker, functor);
+ }
+ }
+ else
+ {
+ // signalName does not match any signal
+ connected = false;
+ }
+
+ return connected;
+}
+
+DevelTextLabel::AnchorClickedSignalType& TextLabel::AnchorClickedSignal()
+{
+ return mAnchorClickedSignal;
+}
+
void TextLabel::OnInitialize()
{
Actor self = Self();
DALI_ASSERT_DEBUG(mController && "Invalid Text Controller")
mController->SetControlInterface(this);
+ mController->SetAnchorControlInterface(this);
// Use height-for-width negotiation by default
self.SetResizePolicy(ResizePolicy::FILL_TO_PARENT, Dimension::WIDTH);
Dali::LayoutDirection::Type layoutDirection = static_cast<Dali::LayoutDirection::Type>(stage.GetRootLayer().GetProperty(Dali::Actor::Property::LAYOUT_DIRECTION).Get<int>());
mController->SetLayoutDirection(layoutDirection);
+ // Forward input events to controller
+ EnableGestureDetection(static_cast<GestureType::Value>(GestureType::TAP));
+ GetTapGestureDetector().SetMaximumTapsRequired(1);
+
Layout::Engine& engine = mController->GetLayoutEngine();
engine.SetCursorWidth(0u); // Do not layout space for the cursor.
Control::OnStyleChange(styleManager, change);
}
+void TextLabel::OnTap(const TapGesture& gesture)
+{
+ DALI_LOG_INFO(gLogFilter, Debug::Verbose, "TextLabel::OnTap %p\n", mController.Get());
+
+ // Deliver the tap before the focus event to controller; this allows us to detect when focus is gained due to tap-gestures
+ Extents padding;
+ padding = Self().GetProperty<Extents>(Toolkit::Control::Property::PADDING);
+ const Vector2& localPoint = gesture.GetLocalPoint();
+ mController->AnchorEvent(localPoint.x - padding.start, localPoint.y - padding.top);
+}
+
+void TextLabel::AnchorClicked(const std::string& href)
+{
+ Dali::Toolkit::TextLabel handle(GetOwner());
+ mAnchorClickedSignal.Emit(handle, href.c_str(), href.length());
+}
+
Vector3 TextLabel::GetNaturalSize()
{
Extents padding;
// INTERNAL INCLUDES
#include <dali-toolkit/internal/controls/control/control-data-impl.h>
#include <dali-toolkit/internal/text/rendering/text-renderer.h>
+#include <dali-toolkit/internal/text/text-anchor-control-interface.h>
#include <dali-toolkit/internal/text/text-control-interface.h>
#include <dali-toolkit/internal/text/text-controller.h>
#include <dali-toolkit/internal/text/text-scroller-interface.h>
/**
* @brief A control which renders a short text string.
*/
-class TextLabel : public Control, public Text::ControlInterface, public Text::ScrollerInterface
+class TextLabel : public Control, public Text::ControlInterface, public Text::ScrollerInterface, public Text::AnchorControlInterface
{
public:
/**
*/
static Property::Value GetProperty(BaseObject* object, Property::Index index);
+ /**
+ * @copydoc Dali::Toollkit::TextLabel::AnchorClickedSignal()
+ */
+ DevelTextLabel::AnchorClickedSignalType& AnchorClickedSignal();
+
+ /**
+ * Connects a callback function with the object's signals.
+ * @param[in] object The object providing the signal.
+ * @param[in] tracker Used to disconnect the signal.
+ * @param[in] signalName The signal to connect to.
+ * @param[in] functor A newly allocated FunctorDelegate.
+ * @return True if the signal was connected.
+ * @post If a signal was connected, ownership of functor was passed to CallbackBase. Otherwise the caller is responsible for deleting the unused functor.
+ */
+ static bool DoConnectSignal(BaseObject* object, ConnectionTrackerInterface* tracker, const std::string& signalName, FunctorDelegate* functor);
+
Text::ControllerPtr getController();
private: // From Control
*/
void OnRelayout(const Vector2& size, RelayoutContainer& container) override;
+ /**
+ * @copydoc Control::OnTap()
+ */
+ void OnTap(const TapGesture& tap) override;
+
/**
* @copydoc Control::GetNaturalSize()
*/
*/
void ScrollingFinished() override;
+public: // From AnchorControlInterface
+ /**
+ * @copydoc Text::AnchorControlInterface::AnchorClicked()
+ */
+ void AnchorClicked(const std::string& href) override;
+
private: // Implementation
/**
* Construct a new TextLabel.
Toolkit::Visual::Base mVisual;
+ // Signals
+ Toolkit::DevelTextLabel::AnchorClickedSignalType mAnchorClickedSignal;
+
int mRenderingBackend;
bool mTextUpdateNeeded : 1;
${toolkit_src_dir}/text/markup-processor.cpp
${toolkit_src_dir}/text/markup-processor-color.cpp
${toolkit_src_dir}/text/markup-processor-embedded-item.cpp
+ ${toolkit_src_dir}/text/markup-processor-anchor.cpp
${toolkit_src_dir}/text/markup-processor-font.cpp
${toolkit_src_dir}/text/markup-processor-helper-functions.cpp
${toolkit_src_dir}/text/multi-language-support.cpp
--- /dev/null
+#ifndef DALI_TOOLKIT_TEXT_ANCHOR_H
+#define DALI_TOOLKIT_TEXT_ANCHOR_H
+
+/*
+ * Copyright (c) 2021 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+// INTERNAL INCLUDES
+#include <dali-toolkit/internal/text/text-definitions.h>
+
+namespace Dali
+{
+namespace Toolkit
+{
+namespace Text
+{
+/**
+ * @brief An anchor within the text.
+ */
+struct Anchor
+{
+ CharacterIndex startIndex; ///< The character's start index of the anchor within the string.
+ CharacterIndex endIndex; ///< The character's end index of the anchor within the string.
+ char* href; ///< The url path
+};
+
+} // namespace Text
+
+} // namespace Toolkit
+
+} // namespace Dali
+
+#endif // DALI_TOOLKIT_TEXT_ANCHOR_H
\ No newline at end of file
embeddedItem.Clear();
}
+void FreeAnchors(Vector<Anchor>& anchors)
+{
+ for(auto&& anchor : anchors)
+ {
+ delete[] anchor.href;
+ }
+
+ anchors.Clear();
+}
+
LogicalModelPtr LogicalModel::New()
{
return LogicalModelPtr(new LogicalModel());
FreeEmbeddedItems(mEmbeddedItems);
}
+void LogicalModel::ClearAnchors()
+{
+ FreeAnchors(mAnchors);
+}
+
LogicalModel::~LogicalModel()
{
ClearFontDescriptionRuns();
#include <dali/public-api/object/ref-object.h>
// INTERNAL INCLUDES
+#include <dali-toolkit/internal/text/anchor.h>
#include <dali-toolkit/internal/text/bidirectional-line-info-run.h>
#include <dali-toolkit/internal/text/bidirectional-paragraph-info-run.h>
#include <dali-toolkit/internal/text/color-run.h>
*/
void ClearEmbeddedImages();
+ /**
+ * @brief Clears the anchors.
+ */
+ void ClearAnchors();
+
protected:
/**
* @brief A reference counted object may only be deleted by calling Unreference().
Vector<CharacterDirection> mCharacterDirections; ///< For each character, whether is right to left. ( @e flase is left to right, @e true right to left ).
Vector<BidirectionalLineInfoRun> mBidirectionalLineInfo;
Vector<EmbeddedItem> mEmbeddedItems;
+ Vector<Anchor> mAnchors;
BidirectionalLineRunIndex mBidirectionalLineIndex; ///< The last fetched bidirectional line info.
};
--- /dev/null
+/*
+ * Copyright (c) 2021 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+// FILE HEADER
+#include <dali-toolkit/internal/text/markup-processor-anchor.h>
+
+// EXTERNAL INCLUDES
+#include <dali/public-api/common/dali-vector.h>
+#include <memory.h>
+
+// INTERNAL INCLUDES
+#include <dali-toolkit/internal/text/anchor.h>
+#include <dali-toolkit/internal/text/markup-processor-helper-functions.h>
+
+namespace Dali
+{
+namespace Toolkit
+{
+namespace Text
+{
+namespace
+{
+const std::string XHTML_HREF_ATTRIBUTE("href");
+} // namespace
+
+void ProcessAnchor(const Tag& tag, Anchor& anchor)
+{
+ anchor.href = nullptr;
+
+ for(auto&& attribute : tag.attributes)
+ {
+ if(TokenComparison(XHTML_HREF_ATTRIBUTE, attribute.nameBuffer, attribute.nameLength))
+ {
+ Length hrefLength = attribute.valueLength + 1;
+ anchor.href = new char[hrefLength];
+ memcpy(anchor.href, attribute.valueBuffer, hrefLength);
+ anchor.href[hrefLength - 1] = '\0';
+ // The memory is freed when the font run is removed from the logical model.
+ }
+ }
+}
+
+} // namespace Text
+
+} // namespace Toolkit
+
+} // namespace Dali
\ No newline at end of file
--- /dev/null
+#ifndef DALI_TOOLKIT_TEXT_MARKUP_PROCESSOR_ANCHOR_H
+#define DALI_TOOLKIT_TEXT_MARKUP_PROCESSOR_ANCHOR_H
+
+/*
+ * Copyright (c) 2021 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+namespace Dali
+{
+
+namespace Toolkit
+{
+
+namespace Text
+{
+
+struct Tag;
+struct Anchor;
+
+/**
+ * @brief Retrieves the @e anchor from the @p tag.
+ *
+ * @param[in] tag The anchor tag and its attributes.
+ * @param[in,out] anchor The anchor.
+ */
+void ProcessAnchor( const Tag& tag, Anchor& anchor );
+
+} // namespace Text
+
+} // namespace Toolkit
+
+} // namespace Dali
+
+#endif // DALI_TOOLKIT_TEXT_MARKUP_PROCESSOR_ANCHOR_H
\ No newline at end of file
// INTERNAL INCLUDES
#include <dali-toolkit/internal/text/character-set-conversion.h>
+#include <dali-toolkit/internal/text/markup-processor-anchor.h>
#include <dali-toolkit/internal/text/markup-processor-color.h>
#include <dali-toolkit/internal/text/markup-processor-embedded-item.h>
#include <dali-toolkit/internal/text/markup-processor-font.h>
const std::string XHTML_GLOW_TAG("glow");
const std::string XHTML_OUTLINE_TAG("outline");
const std::string XHTML_ITEM_TAG("item");
+const std::string XHTML_ANCHOR_TAG("a");
const char LESS_THAN = '<';
const char GREATER_THAN = '>';
}
}
+/**
+ * @brief Processes the anchor tag
+ *
+ * @param[in/out] markupProcessData The markup process data
+ * @param[in] tag The current tag
+ * @param[in/out] characterIndex The current character index
+ */
+void ProcessAnchorTag(
+ MarkupProcessData& markupProcessData,
+ const Tag tag,
+ CharacterIndex& characterIndex)
+{
+ if(!tag.isEndTag)
+ {
+ // Create an anchor instance.
+ Anchor anchor;
+ anchor.startIndex = characterIndex;
+ anchor.endIndex = 0u;
+ ProcessAnchor(tag, anchor);
+ markupProcessData.anchors.PushBack(anchor);
+ }
+ else
+ {
+ // Update end index.
+ unsigned int count = markupProcessData.anchors.Count();
+ if(count > 0)
+ {
+ markupProcessData.anchors[count - 1].endIndex = characterIndex;
+ }
+ }
+}
+
/**
* @brief Resizes the model's vectors
*
ProcessTagForRun<FontDescriptionRun>(
markupProcessData.fontRuns, styleStack, tag, characterIndex, fontRunIndex, fontTagReference, [](const Tag& tag, FontDescriptionRun& fontRun) { ProcessFontTag(tag, fontRun); });
} // <font></font>
+ else if(TokenComparison(XHTML_ANCHOR_TAG, tag.buffer, tag.length))
+ {
+ /* Anchor */
+ ProcessAnchorTag(markupProcessData, tag, characterIndex);
+ /* Color */
+ ProcessTagForRun<ColorRun>(
+ markupProcessData.colorRuns, styleStack, tag, characterIndex, colorRunIndex, colorTagReference, [](const Tag& tag, ColorRun& run) {
+ run.color = Color::BLUE;
+ ProcessColorTag(tag, run);
+ });
+ /* TODO - underline */
+ } // <a href=https://www.tizen.org>tizen</a>
else if(TokenComparison(XHTML_SHADOW_TAG, tag.buffer, tag.length))
{
// TODO: If !tag.isEndTag, then create a new shadow run.
#include <string>
// INTERNAL INCLUDES
+#include <dali-toolkit/internal/text/anchor.h>
#include <dali-toolkit/internal/text/color-run.h>
#include <dali-toolkit/internal/text/embedded-item.h>
#include <dali-toolkit/internal/text/font-description-run.h>
{
MarkupProcessData(Vector<ColorRun>& colorRuns,
Vector<FontDescriptionRun>& fontRuns,
- Vector<EmbeddedItem>& items)
+ Vector<EmbeddedItem>& items,
+ Vector<Anchor>& anchors)
: colorRuns(colorRuns),
fontRuns(fontRuns),
items(items),
+ anchors(anchors),
markupProcessedText()
{
}
Vector<ColorRun>& colorRuns; ///< The color runs.
Vector<FontDescriptionRun>& fontRuns; ///< The font description runs.
Vector<EmbeddedItem>& items; ///< The embedded items.
+ Vector<Anchor>& anchors; ///< The anchors.
std::string markupProcessedText; ///< The mark-up string.
};
--- /dev/null
+#ifndef DALI_TOOLKIT_TEXT_ANCHOR_CONTROL_INTERFACE_H
+#define DALI_TOOLKIT_TEXT_ANCHOR_CONTROL_INTERFACE_H
+
+/*
+ * Copyright (c) 2021 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+namespace Dali
+{
+namespace Toolkit
+{
+namespace Text
+{
+/**
+ * @brief An interface that the Text::Controller used for anchor functionality.
+ */
+class AnchorControlInterface
+{
+public:
+ /**
+ * @brief Virtual destructor.
+ */
+ virtual ~AnchorControlInterface() = default;
+
+ /**
+ * @brief Called to signal that anchor has been clicked.
+ */
+ virtual void AnchorClicked(const std::string& href) = 0;
+};
+
+} // namespace Text
+
+} // namespace Toolkit
+
+} // namespace Dali
+
+#endif // DALI_TOOLKIT_TEXT_ANCHOR_CONTROL_INTERFACE_H
#include <dali/integration-api/debug.h>
// INTERNAL INCLUDES
+#include <dali-toolkit/internal/text/cursor-helper-functions.h>
#include <dali-toolkit/internal/text/text-controller-impl.h>
#include <dali-toolkit/internal/text/text-editable-control-interface.h>
return true;
}
+void Controller::EventHandler::AnchorEvent(Controller& controller, float x, float y)
+{
+ if(!controller.mImpl->mMarkupProcessorEnabled ||
+ !controller.mImpl->mModel->mLogicalModel->mAnchors.Count() ||
+ !controller.mImpl->IsShowingRealText())
+ {
+ return;
+ }
+
+ CharacterIndex cursorPosition = 0u;
+
+ // Convert from control's coords to text's coords.
+ const float xPosition = x - controller.mImpl->mModel->mScrollPosition.x;
+ const float yPosition = y - controller.mImpl->mModel->mScrollPosition.y;
+
+ // Whether to touch point hits on a glyph.
+ bool matchedCharacter = false;
+ cursorPosition = Text::GetClosestCursorIndex(controller.mImpl->mModel->mVisualModel,
+ controller.mImpl->mModel->mLogicalModel,
+ controller.mImpl->mMetrics,
+ xPosition,
+ yPosition,
+ CharacterHitTest::TAP,
+ matchedCharacter);
+
+ for(const auto& anchor : controller.mImpl->mModel->mLogicalModel->mAnchors)
+ {
+ // Anchor clicked if the calculated cursor position is within the range of anchor.
+ if(cursorPosition >= anchor.startIndex && cursorPosition < anchor.endIndex)
+ {
+ if(controller.mImpl->mAnchorControlInterface && anchor.href)
+ {
+ std::string href(anchor.href);
+ controller.mImpl->mAnchorControlInterface->AnchorClicked(href);
+ break;
+ }
+ }
+ }
+}
+
void Controller::EventHandler::TapEvent(Controller& controller, unsigned int tapCount, float x, float y)
{
DALI_ASSERT_DEBUG(controller.mImpl->mEventData && "Unexpected TapEvent");
static void KeyboardFocusGainEvent(Controller& controller);
static void KeyboardFocusLostEvent(Controller& controller);
static bool KeyEvent(Controller& controller, const Dali::KeyEvent& keyEvent);
+ static void AnchorEvent(Controller& controller, float x, float y);
static void TapEvent(Controller& controller, unsigned int tapCount, float x, float y);
static void PanEvent(Controller& controller, GestureState state, const Vector2& displacement);
static void LongPressEvent(Controller& controller, GestureState state, float x, float y);
struct SelectionHandleController;
class SelectableControlInterface;
+class AnchorControlInterface;
struct Event
{
{
Impl(ControlInterface* controlInterface,
EditableControlInterface* editableControlInterface,
- SelectableControlInterface* selectableControlInterface)
+ SelectableControlInterface* selectableControlInterface,
+ AnchorControlInterface* anchorControlInterface)
: mControlInterface(controlInterface),
mEditableControlInterface(editableControlInterface),
mSelectableControlInterface(selectableControlInterface),
+ mAnchorControlInterface(anchorControlInterface),
mModel(),
mFontDefaults(NULL),
mUnderlineDefaults(NULL),
ControlInterface* mControlInterface; ///< Reference to the text controller.
EditableControlInterface* mEditableControlInterface; ///< Reference to the editable text controller.
SelectableControlInterface* mSelectableControlInterface; ///< Reference to the selectable text controller.
+ AnchorControlInterface* mAnchorControlInterface; ///< Reference to the anchor controller.
ModelPtr mModel; ///< Pointer to the text's model.
FontDefaults* mFontDefaults; ///< Avoid allocating this when the user does not specify a font.
UnderlineDefaults* mUnderlineDefaults; ///< Avoid allocating this when the user does not specify underline parameters.
MarkupProcessData markupProcessData(logicalModel->mColorRuns,
logicalModel->mFontDescriptionRuns,
- logicalModel->mEmbeddedItems);
+ logicalModel->mEmbeddedItems,
+ logicalModel->mAnchors);
Length textSize = 0u;
const uint8_t* utf8 = NULL;
textUpdateInfo.mNumberOfCharactersToAdd += maxSizeOfNewText;
}
+ if(impl.mMarkupProcessorEnabled)
+ {
+ InsertTextAnchor(controller, maxSizeOfNewText, cursorIndex);
+ }
+
// Update the cursor index.
cursorIndex += maxSizeOfNewText;
if(!impl.IsShowingPlaceholderText())
{
// Delete at current cursor position
- Vector<Character>& currentText = logicalModel->mText;
- CharacterIndex& oldCursorIndex = eventData->mPrimaryCursorPosition;
+ Vector<Character>& currentText = logicalModel->mText;
+ CharacterIndex& previousCursorIndex = eventData->mPrimaryCursorPosition;
CharacterIndex cursorIndex = 0;
currentText.Erase(first, last);
+ if(impl.mMarkupProcessorEnabled)
+ {
+ RemoveTextAnchor(controller, cursorOffset, numberOfCharacters, previousCursorIndex);
+ }
+
// Cursor position retreat
- oldCursorIndex = cursorIndex;
+ previousCursorIndex = cursorIndex;
eventData->mScrollAfterDelete = true;
{
textRemoved = true;
impl.ChangeState(EventData::EDITING);
+
+ if(impl.mMarkupProcessorEnabled)
+ {
+ int cursorOffset = -1;
+ int numberOfCharacters = removedString.length();
+ CharacterIndex& cursorIndex = impl.mEventData->mPrimaryCursorPosition;
+ CharacterIndex previousCursorIndex = cursorIndex + numberOfCharacters;
+
+ RemoveTextAnchor(controller, cursorOffset, numberOfCharacters, previousCursorIndex);
+ }
}
}
// Reset the embedded images buffer.
logicalModel->ClearEmbeddedImages();
+ // Reset the anchors buffer.
+ logicalModel->ClearAnchors();
+
// We have cleared everything including the placeholder-text
impl.PlaceholderCleared();
impl.mOperationsPending = ALL_OPERATIONS;
}
+void Controller::TextUpdater::InsertTextAnchor(Controller& controller, int numberOfCharacters, CharacterIndex previousCursorIndex)
+{
+ Controller::Impl& impl = *controller.mImpl;
+ ModelPtr& model = impl.mModel;
+ LogicalModelPtr& logicalModel = model->mLogicalModel;
+
+ for(auto& anchor : logicalModel->mAnchors)
+ {
+ if(anchor.endIndex < previousCursorIndex) // [anchor] CUR
+ {
+ continue;
+ }
+ if(anchor.startIndex < previousCursorIndex) // [anCURr]
+ {
+ anchor.endIndex += numberOfCharacters;
+ }
+ else // CUR [anchor]
+ {
+ anchor.startIndex += numberOfCharacters;
+ anchor.endIndex += numberOfCharacters;
+ }
+ DALI_LOG_INFO(gLogFilter, Debug::General, "Controller::InsertTextAnchor[%p] Anchor[%s] start[%d] end[%d]\n", &controller, anchor.href, anchor.startIndex, anchor.endIndex);
+ }
+}
+
+void Controller::TextUpdater::RemoveTextAnchor(Controller& controller, int cursorOffset, int numberOfCharacters, CharacterIndex previousCursorIndex)
+{
+ Controller::Impl& impl = *controller.mImpl;
+ ModelPtr& model = impl.mModel;
+ LogicalModelPtr& logicalModel = model->mLogicalModel;
+ Vector<Anchor>::Iterator it = logicalModel->mAnchors.Begin();
+
+ while(it != logicalModel->mAnchors.End())
+ {
+ Anchor& anchor = *it;
+
+ if(anchor.endIndex <= previousCursorIndex && cursorOffset == 0) // [anchor] CUR >>
+ {
+ // Nothing happens.
+ }
+ else if(anchor.endIndex <= previousCursorIndex && cursorOffset == -1) // [anchor] << CUR
+ {
+ int endIndex = anchor.endIndex;
+ int offset = previousCursorIndex - endIndex;
+ int index = endIndex - (numberOfCharacters - offset);
+
+ if(index < endIndex)
+ {
+ endIndex = index;
+ }
+
+ if((int)anchor.startIndex >= endIndex)
+ {
+ if(anchor.href)
+ {
+ delete[] anchor.href;
+ }
+ it = logicalModel->mAnchors.Erase(it);
+ continue;
+ }
+ else
+ {
+ anchor.endIndex = endIndex;
+ }
+ }
+ else if(anchor.startIndex >= previousCursorIndex && cursorOffset == -1) // << CUR [anchor]
+ {
+ anchor.startIndex -= numberOfCharacters;
+ anchor.endIndex -= numberOfCharacters;
+ }
+ else if(anchor.startIndex >= previousCursorIndex && cursorOffset == 0) // CUR >> [anchor]
+ {
+ int startIndex = anchor.startIndex;
+ int endIndex = anchor.endIndex;
+ int index = previousCursorIndex + numberOfCharacters - 1;
+
+ if(startIndex > index)
+ {
+ anchor.startIndex -= numberOfCharacters;
+ anchor.endIndex -= numberOfCharacters;
+ }
+ else if(endIndex > index + 1)
+ {
+ anchor.endIndex -= numberOfCharacters;
+ }
+ else
+ {
+ if(anchor.href)
+ {
+ delete[] anchor.href;
+ }
+ it = logicalModel->mAnchors.Erase(it);
+ continue;
+ }
+ }
+ else if(cursorOffset == -1) // [<< CUR]
+ {
+ int startIndex = anchor.startIndex;
+ int index = previousCursorIndex - numberOfCharacters;
+
+ if(startIndex >= index)
+ {
+ anchor.startIndex = index;
+ }
+ anchor.endIndex -= numberOfCharacters;
+ }
+ else if(cursorOffset == 0) // [CUR >>]
+ {
+ anchor.endIndex -= numberOfCharacters;
+ }
+ else
+ {
+ // When this condition is reached, someting is wrong.
+ DALI_LOG_ERROR("Controller::RemoveTextAnchor[%p] Invaild state cursorOffset[%d]\n", &controller, cursorOffset);
+ }
+
+ DALI_LOG_INFO(gLogFilter, Debug::General, "Controller::RemoveTextAnchor[%p] Anchor[%s] start[%d] end[%d]\n", &controller, anchor.href, anchor.startIndex, anchor.endIndex);
+
+ it++;
+ }
+}
+
} // namespace Text
} // namespace Toolkit
/// @copydoc Text::Contoller::ResetText
/// @param[in] controller The controller
static void ResetText(Controller& controller);
+
+ /// @copydoc Text::Contoller::InsertTextAnchor
+ /// @param[in] controller The controller
+ static void InsertTextAnchor(Controller& controller, int numberOfCharacters, CharacterIndex previousCursorIndex);
+
+ /// @copydoc Text::Contoller::RemoveTextAnchor
+ /// @param[in] controller The controller
+ static void RemoveTextAnchor(Controller& controller, int cursorOffset, int numberOfCharacters, CharacterIndex previousCursorIndex);
};
} // namespace Text
ControllerPtr Controller::New(ControlInterface* controlInterface,
EditableControlInterface* editableControlInterface,
- SelectableControlInterface* selectableControlInterface)
+ SelectableControlInterface* selectableControlInterface,
+ AnchorControlInterface* anchorControlInterface)
{
return ControllerPtr(new Controller(controlInterface,
editableControlInterface,
- selectableControlInterface));
+ selectableControlInterface,
+ anchorControlInterface));
}
// public : Configure the text controller.
return EventHandler::KeyEvent(*this, keyEvent);
}
+void Controller::AnchorEvent(float x, float y)
+{
+ EventHandler::AnchorEvent(*this, x, y);
+}
+
void Controller::TapEvent(unsigned int tapCount, float x, float y)
{
EventHandler::TapEvent(*this, tapCount, x, y);
return TextUpdater::RemoveSelectedText(*this);
}
+void Controller::InsertTextAnchor(int numberOfCharacters,
+ CharacterIndex previousCursorIndex)
+{
+ TextUpdater::InsertTextAnchor(*this, numberOfCharacters, previousCursorIndex);
+}
+
+void Controller::RemoveTextAnchor(int cursorOffset,
+ int numberOfCharacters,
+ CharacterIndex previousCursorIndex)
+{
+ TextUpdater::RemoveTextAnchor(*this, cursorOffset, numberOfCharacters, previousCursorIndex);
+}
+
// private : Relayout.
bool Controller::DoRelayout(const Size& size,
mImpl->mControlInterface = controlInterface;
}
+void Controller::SetAnchorControlInterface(AnchorControlInterface* anchorControlInterface)
+{
+ mImpl->mAnchorControlInterface = anchorControlInterface;
+}
+
bool Controller::ShouldClearFocusOnEscape() const
{
return mImpl->mShouldClearFocusOnEscape;
// private : Private contructors & copy operator.
Controller::Controller()
-: Controller(nullptr, nullptr, nullptr)
+: Controller(nullptr, nullptr, nullptr, nullptr)
{
}
Controller::Controller(ControlInterface* controlInterface)
-: Controller(controlInterface, nullptr, nullptr)
+: Controller(controlInterface, nullptr, nullptr, nullptr)
{
}
Controller::Controller(ControlInterface* controlInterface,
EditableControlInterface* editableControlInterface,
- SelectableControlInterface* selectableControlInterface)
-: mImpl(new Controller::Impl(controlInterface, editableControlInterface, selectableControlInterface))
+ SelectableControlInterface* selectableControlInterface,
+ AnchorControlInterface* anchorControlInterface)
+: mImpl(new Controller::Impl(controlInterface, editableControlInterface, selectableControlInterface, anchorControlInterface))
{
}
#include <dali-toolkit/internal/text/decorator/text-decorator.h>
#include <dali-toolkit/internal/text/hidden-text.h>
#include <dali-toolkit/internal/text/layouts/layout-engine.h>
+#include <dali-toolkit/internal/text/text-anchor-control-interface.h>
#include <dali-toolkit/internal/text/text-model-interface.h>
#include <dali-toolkit/internal/text/text-selectable-control-interface.h>
#include <dali-toolkit/public-api/text/text-enumerations.h>
* @param[in] controlInterface The control's interface.
* @param[in] editableControlInterface The editable control's interface.
* @param[in] selectableControlInterface The selectable control's interface.
+ * @param[in] anchorControlInterface The anchor control's interface.
*
* @return A pointer to a new Controller.
*/
static ControllerPtr New(ControlInterface* controlInterface,
EditableControlInterface* editableControlInterface,
- SelectableControlInterface* selectableControlInterface);
+ SelectableControlInterface* selectableControlInterface,
+ AnchorControlInterface* anchorControlInterface);
public: // Configure the text controller.
/**
*/
void SetControlInterface(ControlInterface* controlInterface);
+ /**
+ * @brief Set the anchor control's interface.
+ *
+ * @param[in] anchorControlInterface The control's interface.
+ */
+ void SetAnchorControlInterface(AnchorControlInterface* anchorControlInterface);
+
public: // Queries & retrieves.
/**
* @brief Return the layout engine.
*/
bool KeyEvent(const Dali::KeyEvent& event);
+ /**
+ * @brief Called by anchor when a tap gesture occurs.
+ * @param[in] x The x position relative to the top-left of the parent control.
+ * @param[in] y The y position relative to the top-left of the parent control.
+ */
+ void AnchorEvent(float x, float y);
+
/**
* @brief Called by editable UI controls when a tap gesture occurs.
* @param[in] tapCount The number of taps.
*/
bool RemoveSelectedText();
+ /**
+ * @brief Update anchor position from given number of inserted characters.
+ *
+ * @param[in] numberOfCharacters The number of inserted characters.
+ * @param[in] previousCursorIndex A cursor position before event occurs.
+ */
+ void InsertTextAnchor(int numberOfCharacters,
+ CharacterIndex previousCursorIndex);
+
+ /**
+ * @brief Update anchor position from given number of removed characters.
+ *
+ * @param[in] cursorOffset Start position from the current cursor position to start deleting characters.
+ * @param[in] numberOfCharacters The number of removed characters.
+ * @param[in] previousCursorIndex A cursor position before event occurs.
+ */
+ void RemoveTextAnchor(int cursorOffset,
+ int numberOfCharacters,
+ CharacterIndex previousCursorIndex);
+
private: // Relayout.
/**
* @brief Lays-out the text.
*/
Controller(ControlInterface* controlInterface,
EditableControlInterface* editableControlInterface,
- SelectableControlInterface* selectableControlInterface);
+ SelectableControlInterface* selectableControlInterface,
+ AnchorControlInterface* anchorControlInterface);
// Undefined
Controller(const Controller& handle);