From 632c0f0659030bea7e4854c815c88a1eeabcf843 Mon Sep 17 00:00:00 2001 From: Victor Cebollada Date: Tue, 28 Feb 2017 12:24:10 +0000 Subject: [PATCH] Update double tap and long press behaviour. It updates the double tap and long press event on a text area with no text. i.e. On a position clearly after the last character of the text. * Double Tap : The cursor is placed at the beginning or at the end of the text. * Long Press : The cursor is placed at the beginning or at the end of the text and shows the text's selection popup. Change-Id: Iaf9ea817a515781d20c87f60991e1996d5440a62 Signed-off-by: Victor Cebollada --- .../utc-Dali-Text-Controller.cpp | 39 ++ .../dali-toolkit-internal/utc-Dali-Text-Cursor.cpp | 455 +++++++++++++++++++-- .../src/dali-toolkit/utc-Dali-TextEditor.cpp | 199 ++++++++- .../src/dali-toolkit/utc-Dali-TextField.cpp | 11 + .../controls/text-controls/text-field-impl.cpp | 3 + .../internal/text/cursor-helper-functions.cpp | 128 ++++-- .../internal/text/cursor-helper-functions.h | 42 +- .../internal/text/text-controller-impl.cpp | 127 +++++- dali-toolkit/internal/text/text-controller-impl.h | 8 +- dali-toolkit/internal/text/text-controller.cpp | 96 ++++- dali-toolkit/internal/text/text-controller.h | 40 +- 11 files changed, 1027 insertions(+), 121 deletions(-) diff --git a/automated-tests/src/dali-toolkit-internal/utc-Dali-Text-Controller.cpp b/automated-tests/src/dali-toolkit-internal/utc-Dali-Text-Controller.cpp index 9b8217b..d39a88f 100644 --- a/automated-tests/src/dali-toolkit-internal/utc-Dali-Text-Controller.cpp +++ b/automated-tests/src/dali-toolkit-internal/utc-Dali-Text-Controller.cpp @@ -470,3 +470,42 @@ int UtcDaliTextControllerSetGetCheckProperty(void) tet_result(TET_PASS); END_TEST; } + +int UtcDaliTextControllerSetGetTapLongPressAction(void) +{ + tet_infoline(" UtcDaliTextControllerSetGetTapLongPressAction"); + ToolkitTestApplication application; + + // Creates a text controller. + ControllerPtr controller = Controller::New(); + + DALI_TEST_CHECK( controller ); + + // Test first with no decorator. + + DALI_TEST_EQUALS( Controller::NoTextTap::NO_ACTION, controller->GetNoTextDoubleTapAction(), TEST_LOCATION ); + controller->SetNoTextDoubleTapAction( Controller::NoTextTap::HIGHLIGHT ); + DALI_TEST_EQUALS( Controller::NoTextTap::NO_ACTION, controller->GetNoTextDoubleTapAction(), TEST_LOCATION ); + + DALI_TEST_EQUALS( Controller::NoTextTap::NO_ACTION, controller->GetNoTextLongPressAction(), TEST_LOCATION ); + controller->SetNoTextLongPressAction( Controller::NoTextTap::HIGHLIGHT ); + DALI_TEST_EQUALS( Controller::NoTextTap::NO_ACTION, controller->GetNoTextLongPressAction(), TEST_LOCATION ); + + // Add a decorator and re-test. + + // Creates a decorator. + Text::DecoratorPtr decorator = Text::Decorator::New( *controller, *controller ); + + // Enables the text input. + controller->EnableTextInput( decorator ); + + DALI_TEST_EQUALS( Controller::NoTextTap::NO_ACTION, controller->GetNoTextDoubleTapAction(), TEST_LOCATION ); + controller->SetNoTextDoubleTapAction( Controller::NoTextTap::HIGHLIGHT ); + DALI_TEST_EQUALS( Controller::NoTextTap::HIGHLIGHT, controller->GetNoTextDoubleTapAction(), TEST_LOCATION ); + + DALI_TEST_EQUALS( Controller::NoTextTap::SHOW_SELECTION_POPUP, controller->GetNoTextLongPressAction(), TEST_LOCATION ); // The default is SHOW_SELECTION_POPUP + controller->SetNoTextLongPressAction( Controller::NoTextTap::HIGHLIGHT ); + DALI_TEST_EQUALS( Controller::NoTextTap::HIGHLIGHT, controller->GetNoTextLongPressAction(), TEST_LOCATION ); + + END_TEST; +} diff --git a/automated-tests/src/dali-toolkit-internal/utc-Dali-Text-Cursor.cpp b/automated-tests/src/dali-toolkit-internal/utc-Dali-Text-Cursor.cpp index 1755b8b..1e2b72d 100644 --- a/automated-tests/src/dali-toolkit-internal/utc-Dali-Text-Cursor.cpp +++ b/automated-tests/src/dali-toolkit-internal/utc-Dali-Text-Cursor.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016 Samsung Electronics Co., Ltd. + * Copyright (c) 2017 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. @@ -32,12 +32,15 @@ using namespace Text; // Tests the following functions. // // LineIndex GetClosestLine( VisualModelPtr visualModel, -// float visualY ) +// float visualY, +// bool& isLineHit ) // CharacterIndex GetClosestCursorIndex( VisualModelPtr visualModel, // LogicalModelPtr logicalModel, // MetricsPtr metrics, // float visualX, -// float visualY ) +// float visualY, +// CharacterHitTest::Mode mode, +// bool& isCharacterHit ) ////////////////////////////////////////////////////////// @@ -51,16 +54,42 @@ struct GetClosestLineData unsigned int numberOfTests; ///< The number of tests. float* visualY; ///< The visual 'y' position for each test. LineIndex* lineIndex; ///< The expected line index for each test. + bool* isLineHit; ///< The expected line hit value for each test. }; struct GetClosestCursorIndexData { + std::string description; ///< Description of the test. + std::string text; ///< Input text. + unsigned int numberOfTests; ///< The number of tests. + float* visualX; ///< The visual 'x' position for each test. + float* visualY; ///< The visual 'y' position for each test. + CharacterHitTest::Mode* mode; ///< The type of hit test. + CharacterIndex* logicalIndex; ///< The expected logical cursor index for each test. + bool* isCharacterHit; ///< The expected character hit value for each test. +}; + +struct GetCursorPositionData +{ + std::string description; ///< Description of the test. + std::string text; ///< Input text. + unsigned int numberOfTests; ///< The number of tests. + CharacterIndex* logicalIndex; ///< The logical cursor index for each test. + float* visualX; ///< The expected visual 'x' position for each test. + float* visualY; ///< The expected visual 'y' position for each test. +}; + +struct FindSelectionIndicesData +{ std::string description; ///< Description of the test. std::string text; ///< Input text. unsigned int numberOfTests; ///< The number of tests. float* visualX; ///< The visual 'x' position for each test. float* visualY; ///< The visual 'y' position for each test. - CharacterIndex* logicalIndex; ///< The expected logical cursor index for each test. + bool* found; ///< Whether selection indices are found. + CharacterIndex* startIndex; ///< The expected start cursor index for each test. + CharacterIndex* endIndex; ///< The expected end cursor index for each test. + CharacterIndex* noTextHitIndex; ///< The expected character index when there is no hit. }; bool GetClosestLineTest( const GetClosestLineData& data ) @@ -85,16 +114,23 @@ bool GetClosestLineTest( const GetClosestLineData& data ) visualModel, metrics ); - for( unsigned int index = 0u; index < data.numberOfTests; ++index ) + for( unsigned int index = 0; index < data.numberOfTests; ++index ) { + bool isLineHit = false; const LineIndex lineIndex = GetClosestLine( visualModel, - data.visualY[index] ); + data.visualY[index], + isLineHit ); if( lineIndex != data.lineIndex[index] ) { std::cout << " test " << index << " failed. Different line index : " << lineIndex << ", expected : " << data.lineIndex[index] << std::endl; return false; } + if( isLineHit != data.isLineHit[index] ) + { + std::cout << " test " << index << " failed. Different line hit value : " << isLineHit << ", expected : " << data.isLineHit[index] << std::endl; + return false; + } } return true; @@ -122,24 +158,138 @@ bool GetClosestCursorIndexTest( const GetClosestCursorIndexData& data ) visualModel, metrics ); - for( unsigned int index = 0u; index < data.numberOfTests; ++index ) + for( unsigned int index = 0; index < data.numberOfTests; ++index ) { + bool isCharacterHit = false; const CharacterIndex logicalCursorIndex = GetClosestCursorIndex( visualModel, logicalModel, metrics, data.visualX[index], - data.visualY[index] ); + data.visualY[index], + data.mode[index], + isCharacterHit ); if( logicalCursorIndex != data.logicalIndex[index] ) { std::cout << " test " << index << " failed. Different logical cursor index : " << logicalCursorIndex << ", expected : " << data.logicalIndex[index] << std::endl; return false; } + if( isCharacterHit != data.isCharacterHit[index] ) + { + std::cout << " test " << index << " failed. Different character hit value : " << isCharacterHit << ", expected : " << data.isCharacterHit[index] << std::endl; + return false; + } + } + + return true; +} + +bool GetCursorPositionTest( const GetCursorPositionData& data ) +{ + std::cout << " testing : " << data.description << std::endl; + + // 1) Create the model. + LogicalModelPtr logicalModel; + VisualModelPtr visualModel; + MetricsPtr metrics; + Size textArea(400.f, 600.f); + Size layoutSize; + + Vector fontDescriptionRuns; + LayoutOptions options; + CreateTextModel( data.text, + textArea, + fontDescriptionRuns, + options, + layoutSize, + logicalModel, + visualModel, + metrics ); + + for( unsigned int index = 0; index < data.numberOfTests; ++index ) + { + CursorInfo cursorInfo; + GetCursorPosition( visualModel, + logicalModel, + metrics, + data.logicalIndex[index], + cursorInfo ); + + if( cursorInfo.primaryPosition.x != data.visualX[index] ) + { + std::cout << " test " << index << " failed. Different 'x' cursor position : " << cursorInfo.primaryPosition.x << ", expected : " << data.visualX[index] << std::endl; + return false; + } + if( cursorInfo.primaryPosition.y != data.visualY[index] ) + { + std::cout << " test " << index << " failed. Different 'y' cursor position : " << cursorInfo.primaryPosition.y << ", expected : " << data.visualY[index] << std::endl; + return false; + } } return true; } +bool FindSelectionIndicesTest( const FindSelectionIndicesData& data ) +{ + std::cout << " testing : " << data.description << std::endl; + + // 1) Create the model. + LogicalModelPtr logicalModel; + VisualModelPtr visualModel; + MetricsPtr metrics; + Size textArea(400.f, 600.f); + Size layoutSize; + + Vector fontDescriptionRuns; + LayoutOptions options; + CreateTextModel( data.text, + textArea, + fontDescriptionRuns, + options, + layoutSize, + logicalModel, + visualModel, + metrics ); + + for( unsigned int index = 0; index < data.numberOfTests; ++index ) + { + CharacterIndex startIndex = 0; + CharacterIndex endIndex = 0; + CharacterIndex noTextHitIndex = 0; + const bool found = FindSelectionIndices( visualModel, + logicalModel, + metrics, + data.visualX[index], + data.visualY[index], + startIndex, + endIndex, + noTextHitIndex ); + + if( found != data.found[index] ) + { + std::cout << " test " << index << " failed. Different found value : " << found << ", expected : " << data.found[index] << std::endl; + return false; + } + if( startIndex != data.startIndex[index] ) + { + std::cout << " test " << index << " failed. Different start index : " << startIndex << ", expected : " << data.startIndex[index] << std::endl; + return false; + } + if( endIndex != data.endIndex[index] ) + { + std::cout << " test " << index << " failed. Different end index : " << endIndex << ", expected : " << data.endIndex[index] << std::endl; + return false; + } + if( noTextHitIndex != data.noTextHitIndex[index] ) + { + std::cout << " test " << index << " failed. Different no text hit index : " << noTextHitIndex << ", expected : " << data.noTextHitIndex[index] << std::endl; + return false; + } + } + return true; +} + } // namespace ////////////////////////////////////////////////////////// @@ -154,13 +304,16 @@ int UtcDaliGetClosestLine(void) tet_infoline(" UtcDaliGetClosestLine"); float visualY01[] = { -4.f, 3.f, 1000.f }; - LineIndex lineIndices01[] = { 0u, 0u, 0u }; + LineIndex lineIndices01[] = { 0, 0, 0 }; + bool isLineHit01[] = { false, false, false }; float visualY02[] = { -4.f, 3.f, 1000.f }; - LineIndex lineIndices02[] = { 0u, 0u, 0u }; + LineIndex lineIndices02[] = { 0, 0, 0 }; + bool isLineHit02[] = { false, true, false }; float visualY03[] = { -4.f, 11.f, 30.f, 51.f, 68.f, 87.f, 109.f, 130.f }; - LineIndex lineIndices03[] = { 0u, 0u, 1u, 2u, 3u, 4u, 5u, 5u }; + LineIndex lineIndices03[] = { 0, 0, 1u, 2u, 3u, 4u, 5u, 5u }; + bool isLineHit03[] = { false, true, true, true, true, true, true, false }; struct GetClosestLineData data[] = { @@ -170,6 +323,7 @@ int UtcDaliGetClosestLine(void) 3u, visualY01, lineIndices01, + isLineHit01 }, { "Single line text.", @@ -177,6 +331,7 @@ int UtcDaliGetClosestLine(void) 3u, visualY02, lineIndices02, + isLineHit02 }, { "Multi-line text.", @@ -188,12 +343,13 @@ int UtcDaliGetClosestLine(void) "חלךmnoצמםpqrפרףstuדאוvwxהסתyzטזץ", 8u, visualY03, - lineIndices03 + lineIndices03, + isLineHit03 } }; const unsigned int numberOfTests = 3u; - for( unsigned int index = 0u; index < numberOfTests; ++index ) + for( unsigned int index = 0; index < numberOfTests; ++index ) { ToolkitTestApplication application; if( !GetClosestLineTest( data[index] ) ) @@ -209,17 +365,24 @@ int UtcDaliGetClosestLine(void) int UtcDaliGetClosestCursorIndex(void) { tet_infoline(" UtcDaliGetClosestCursorIndex"); + float visualX01[] = { -100.f }; float visualY01[] = { -100.f }; - CharacterIndex logicalIndex01[] = { 0u }; + CharacterHitTest::Mode mode01[] = { CharacterHitTest::TAP }; + CharacterIndex logicalIndex01[] = { 0 }; + bool isCharacterHit01[] = { false }; float visualX02[] = { -100.f, 1000.f, 60.f, 79.f, 83.f, 148.f, 99.f }; float visualY02[] = { -100.f, 1000.f, 12.f, 12.f, 12.f, 12.f, 12.f }; - CharacterIndex logicalIndex02[] = { 0u, 21u, 7u, 10u, 11u, 13u, 20u }; + CharacterHitTest::Mode mode02[] = { CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP }; + CharacterIndex logicalIndex02[] = { 0, 21u, 7u, 10u, 11u, 13u, 20u }; + bool isCharacterHit02[] = { false, false, true, true, true, true, true }; float visualX03[] = { 19.f, 104.f, -2.f, 127.f }; float visualY03[] = { 12.f, 12.f, 12.f, 12.f }; - CharacterIndex logicalIndex03[] = { 3u, 12u, 0u, 18u }; + CharacterHitTest::Mode mode03[] = { CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP }; + CharacterIndex logicalIndex03[] = { 3u, 12u, 0, 18u }; + bool isCharacterHit03[] = { true, true, false, false }; // 0 5 _ 6 11 12 // Hello world \n @@ -233,10 +396,16 @@ int UtcDaliGetClosestCursorIndex(void) float visualY04[] = { -100.f, 12.f, 12.f, 12.f, 12.f, 30.f, 30.f, 30.f, 30.f, 30.f, 50.f, 50.f, 50.f, 50.f, 50.f, 50.f, 50.f }; - CharacterIndex logicalIndex04[] = { 0u, 5u, 6u, 11u, 11u, + CharacterHitTest::Mode mode04[] = { CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, + CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, + CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP }; + CharacterIndex logicalIndex04[] = { 0, 5u, 6u, 11u, 11u, 12u, 16u, 17u, 21u, 21u, 22u, 25u, 31u, 32u, 34u, 40u, 40u, 41u }; + bool isCharacterHit04[] = { false, true, true, false, false, + false, true, true, true, false, + false, true, true, true, true, true, false }; // 0 10 20 30 40 46 // abcשנבdefג קכghiעיןjk lחלךmnoצמם pqrפרףstuד אוvwxה @@ -263,12 +432,24 @@ int UtcDaliGetClosestCursorIndex(void) 67.f, 67.f, 67.f, 67.f, 67.f, 67.f, 87.f, 87.f, 87.f, 87.f, 87.f, 87.f, 107.f, 107.f, 107.f, 107.f, 107.f }; - CharacterIndex logicalIndex05[] = { 0u, 10u, 20u, 30u, 40u, 45u, + CharacterHitTest::Mode mode05[] = { CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, + CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, + CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, + CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, + CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, + CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP }; + CharacterIndex logicalIndex05[] = { 0, 10u, 20u, 30u, 40u, 45u, 46u, 50u, 60u, 70u, 80u, 92u, 93u, 100u, 110u, 120u, 130u, 138u, 139u, 150u, 160u, 170u, 180u, 185u, 186u, 190u, 200u, 210u, 220u, 232u, 233u, 240u, 250u, 260u, 265u }; + bool isCharacterHit05[] = { false, true, true, true, true, false, + false, true, true, true, true, false, + false, true, true, true, true, false, + false, true, true, true, true, false, + false, true, true, true, true, false, + false, true, true, true, false }; // 0 10 20 30 40 46 // שנבabcגקכd efעיןghiחל ךjklצמםmno פרףpqrדאוs tuהסתv @@ -295,12 +476,36 @@ int UtcDaliGetClosestCursorIndex(void) 67.f, 67.f, 67.f, 67.f, 67.f, 67.f, 87.f, 87.f, 87.f, 87.f, 87.f, 87.f, 107.f, 107.f, 107.f, 107.f, 107.f }; - CharacterIndex logicalIndex06[] = { 0u, 10u, 20u, 30u, 40u, 45u, + CharacterHitTest::Mode mode06[] = { CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, + CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, + CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, + CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, + CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, + CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP }; + CharacterIndex logicalIndex06[] = { 0, 10u, 20u, 30u, 40u, 45u, 46u, 50u, 60u, 70u, 80u, 92u, 93u, 100u, 110u, 120u, 130u, 138u, 139u, 150u, 160u, 170u, 180u, 185u, 186u, 190u, 200u, 210u, 220u, 231u, 232u, 240u, 250u, 260u, 265u }; + bool isCharacterHit06[] = { false, true, true, true, true, false, + false, true, true, true, true, false, + false, true, true, true, true, false, + false, true, true, true, true, false, + false, true, true, true, true, false, + false, true, true, true, false }; + + float visualX07[] = { 395.f }; + float visualY07[] = { 12.f }; + CharacterHitTest::Mode mode07[] = { CharacterHitTest::TAP }; + CharacterIndex logicalIndex07[] = { 1u }; + bool isCharacterHit07[] = { true }; + + float visualX08[] = { 7.f }; + float visualY08[] = { 12.f }; + CharacterHitTest::Mode mode08[] = { CharacterHitTest::TAP }; + CharacterIndex logicalIndex08[] = { 1u }; + bool isCharacterHit08[] = { true }; struct GetClosestCursorIndexData data[] = { @@ -310,7 +515,9 @@ int UtcDaliGetClosestCursorIndex(void) 1u, visualX01, visualY01, - logicalIndex01 + mode01, + logicalIndex01, + isCharacterHit01 }, { "Single line text.", @@ -318,7 +525,9 @@ int UtcDaliGetClosestCursorIndex(void) 7u, visualX02, visualY02, - logicalIndex02 + mode02, + logicalIndex02, + isCharacterHit02 }, { "Single line with ligatures", @@ -326,7 +535,9 @@ int UtcDaliGetClosestCursorIndex(void) 4u, visualX03, visualY03, - logicalIndex03 + mode03, + logicalIndex03, + isCharacterHit03 }, { "Multiline. Single line paragraphs", @@ -336,7 +547,9 @@ int UtcDaliGetClosestCursorIndex(void) 17u, visualX04, visualY04, - logicalIndex04 + mode04, + logicalIndex04, + isCharacterHit04 }, { "Multiline. Single bidirectional paragraph, starts LTR, wrapped lines", @@ -349,7 +562,9 @@ int UtcDaliGetClosestCursorIndex(void) 35u, visualX05, visualY05, - logicalIndex05 + mode05, + logicalIndex05, + isCharacterHit05 }, { "Multiline. Single bidirectional paragraph, starts RTL, wrapped lines", @@ -362,12 +577,34 @@ int UtcDaliGetClosestCursorIndex(void) 35u, visualX06, visualY06, - logicalIndex06 + mode06, + logicalIndex06, + isCharacterHit06 + }, + { + "Testing complex characters. Arabic ligatures", + "الأَبْجَدِيَّة العَرَبِيَّة", + 1u, + visualX07, + visualY07, + mode07, + logicalIndex07, + isCharacterHit07 + }, + { + "Testing complex characters. Latin ligatures", + "fi ligature", + 1u, + visualX08, + visualY08, + mode08, + logicalIndex08, + isCharacterHit08 } }; - const unsigned int numberOfTests = 6u; + const unsigned int numberOfTests = 8u; - for( unsigned int index = 0u; index < numberOfTests; ++index ) + for( unsigned int index = 0; index < numberOfTests; ++index ) { ToolkitTestApplication application; if( !GetClosestCursorIndexTest( data[index] ) ) @@ -379,3 +616,167 @@ int UtcDaliGetClosestCursorIndex(void) tet_result(TET_PASS); END_TEST; } + +int UtcDaliGetCursorPosition(void) +{ + tet_infoline(" UtcDaliGetCursorPosition"); + + float visualX08[] = { 5.f }; + float visualY08[] = { 0.f }; + CharacterIndex logicalIndex08[] = { 1u }; + + struct GetCursorPositionData data[] = + { + { + "Testing complex characters. Latin ligatures", + "fi ligature", + 1u, + logicalIndex08, + visualX08, + visualY08, + } + }; + const unsigned int numberOfTests = 1u; + + for( unsigned int index = 0; index < numberOfTests; ++index ) + { + ToolkitTestApplication application; + if( !GetCursorPositionTest( data[index] ) ) + { + tet_result(TET_FAIL); + } + } + + tet_result(TET_PASS); + END_TEST; +} + +int UtcDaliFindSelectionIndices(void) +{ + tet_infoline(" UtcDaliFindSelectionIndices"); + + float visualX01[] = { -100.f }; + float visualY01[] = { -100.f }; + bool found01[] = { false }; + CharacterIndex startIndex01[] = { 0 }; + CharacterIndex endIndex01[] = { 0 }; + CharacterIndex noHitText01[] = { 0 }; + + float visualX02[] = { -100.f, 1000.f, 1000.f }; + float visualY02[] = { -100.f, 12.f, 1000.f }; + bool found02[] = { false, false, false }; + CharacterIndex startIndex02[] = { 0, 6u, 6u }; + CharacterIndex endIndex02[] = { 5u, 11u, 11u }; + CharacterIndex noHitText02[] = { 0, 11u, 11u }; + + float visualX03[] = { 70.f }; + float visualY03[] = { 12.f }; + bool found03[] = { true }; + CharacterIndex startIndex03[] = { 6u }; + CharacterIndex endIndex03[] = { 11u }; + CharacterIndex noHitText03[] = { 0u }; + + float visualX04[] = { 132.f }; + float visualY04[] = { 12.f }; + bool found04[] = { true }; + CharacterIndex startIndex04[] = { 12u }; + CharacterIndex endIndex04[] = { 16u }; + CharacterIndex noHitText04[] = { 0u }; + + float visualX05[] = { 1.f }; + float visualY05[] = { 12.f }; + bool found05[] = { true }; + CharacterIndex startIndex05[] = { 0 }; + CharacterIndex endIndex05[] = { 1u }; + CharacterIndex noHitText05[] = { 0 }; + + float visualX06[] = { 10.f }; + float visualY06[] = { 12.f }; + bool found06[] = { true }; + CharacterIndex startIndex06[] = { 0 }; + CharacterIndex endIndex06[] = { 1u }; + CharacterIndex noHitText06[] = { 0u }; + + struct FindSelectionIndicesData data[] = + { + { + "void text", + "", + 1u, + visualX01, + visualY01, + found01, + startIndex01, + endIndex01, + noHitText01 + }, + { + "touch out of text's boundaries", + "Hello world", + 3u, + visualX02, + visualY02, + found02, + startIndex02, + endIndex02, + noHitText02 + }, + { + "touch on the text", + "Hello world demo", + 1u, + visualX03, + visualY03, + found03, + startIndex03, + endIndex03, + noHitText03 + }, + { + "touch on the new paragraph character at the end of line", + "Hello world demo\n", + 1u, + visualX04, + visualY04, + found04, + startIndex04, + endIndex04, + noHitText04 + }, + { + "touch on a white space character. is the unique character of the line", + " ", + 1u, + visualX05, + visualY05, + found05, + startIndex05, + endIndex05, + noHitText05 + }, + { + "touch on a white space character. is between two words", + "h ello", + 1u, + visualX06, + visualY06, + found06, + startIndex06, + endIndex06, + noHitText06 + }, + }; + const unsigned int numberOfTests = 6u; + + for( unsigned int index = 0; index < numberOfTests; ++index ) + { + ToolkitTestApplication application; + if( !FindSelectionIndicesTest( data[index] ) ) + { + tet_result(TET_FAIL); + } + } + + tet_result(TET_PASS); + END_TEST; +} diff --git a/automated-tests/src/dali-toolkit/utc-Dali-TextEditor.cpp b/automated-tests/src/dali-toolkit/utc-Dali-TextEditor.cpp index aee8e65..18628a7 100644 --- a/automated-tests/src/dali-toolkit/utc-Dali-TextEditor.cpp +++ b/automated-tests/src/dali-toolkit/utc-Dali-TextEditor.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016 Samsung Electronics Co., Ltd. + * Copyright (c) 2017 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. @@ -22,7 +22,9 @@ #include #include #include +#include #include +#include #include #include #include @@ -98,6 +100,7 @@ const Dali::Vector4 LIGHT_BLUE( 0.75f, 0.96f, 1.f, 1.f ); // The text highlight const unsigned int CURSOR_BLINK_INTERVAL = 500u; // Cursor blink interval const float TO_MILLISECONDS = 1000.f; const float TO_SECONDS = 1.f / TO_MILLISECONDS; +const float RENDER_FRAME_INTERVAL = 16.66f; const float SCROLL_THRESHOLD = 10.f; const float SCROLL_SPEED = 300.f; @@ -109,6 +112,8 @@ const int KEY_A_CODE = 38; const int KEY_D_CODE = 40; const int KEY_WHITE_SPACE_CODE = 65; +const char* HANDLE_IMAGE_FILE_NAME = TEST_RESOURCE_DIR "/insertpoint-icon.png"; + static bool gTextChangedCallBackCalled; static bool gInputStyleChangedCallbackCalled; static Dali::Toolkit::TextEditor::InputStyle::Mask gInputStyleMask; @@ -158,6 +163,23 @@ Integration::TapGestureEvent GenerateTap( return tap; } +// Generate a PanGestureEvent to send to Core +Integration::PanGestureEvent GeneratePan( Gesture::State state, + const Vector2& previousPosition, + const Vector2& currentPosition, + unsigned long timeDelta, + unsigned int numberOfTouches = 1u ) +{ + Integration::PanGestureEvent pan(state); + + pan.previousPosition = previousPosition; + pan.currentPosition = currentPosition; + pan.timeDelta = timeDelta; + pan.numberOfTouches = numberOfTouches; + + return pan; +} + // Generate a KeyEvent to send to Core. Integration::KeyEvent GenerateKey( const std::string& keyName, const std::string& keyString, @@ -174,6 +196,69 @@ Integration::KeyEvent GenerateKey( const std::string& keyName, keyState ); } +/** + * Helper to generate PanGestureEvent + * + * @param[in] application Application instance + * @param[in] state The Gesture State + * @param[in] pos The current position of touch. + */ +static void SendPan(ToolkitTestApplication& application, Gesture::State state, const Vector2& pos) +{ + static Vector2 last; + + if( (state == Gesture::Started) || + (state == Gesture::Possible) ) + { + last.x = pos.x; + last.y = pos.y; + } + + application.ProcessEvent( GeneratePan( state, last, pos, 16 ) ); + + last.x = pos.x; + last.y = pos.y; +} + +/* + * Simulate time passed by. + * + * @note this will always process at least 1 frame (1/60 sec) + * + * @param application Test application instance + * @param duration Time to pass in milliseconds. + * @return The actual time passed in milliseconds + */ +static int Wait(ToolkitTestApplication& application, int duration = 0) +{ + int time = 0; + + for(int i = 0; i <= ( duration / RENDER_FRAME_INTERVAL); i++) + { + application.SendNotification(); + application.Render(RENDER_FRAME_INTERVAL); + time += RENDER_FRAME_INTERVAL; + } + + return time; +} + +Dali::Integration::Point GetPointDownInside( Vector2& pos ) +{ + Dali::Integration::Point point; + point.SetState( PointState::DOWN ); + point.SetScreenPosition( pos ); + return point; +} + +Dali::Integration::Point GetPointUpInside( Vector2& pos ) +{ + Dali::Integration::Point point; + point.SetState( PointState::UP ); + point.SetScreenPosition( pos ); + return point; +} + bool DaliTestCheckMaps( const Property::Map& fontStyleMapGet, const Property::Map& fontStyleMapSet ) { if( fontStyleMapGet.Count() == fontStyleMapSet.Count() ) @@ -1463,6 +1548,25 @@ int utcDaliTextEditorEvent03(void) Renderer highlight = stencil.GetChildAt( 1u ).GetRendererAt( 0u ); DALI_TEST_CHECK( highlight ); + // Double tap out of bounds + application.ProcessEvent( GenerateTap( Gesture::Possible, 2u, 1u, Vector2( 29.f, 25.0f ) ) ); + application.ProcessEvent( GenerateTap( Gesture::Started, 2u, 1u, Vector2( 29.f, 25.0f ) ) ); + + // Render and notify + application.SendNotification(); + application.Render(); + + // The stencil actor should have one actors: the renderer actor. + stencil = editor.GetChildAt( 0u ); + + // The stencil actor has a container with all the actors which contain the text renderers. + container = stencil.GetChildAt( 0u ); + for( unsigned int index = 0; index < container.GetChildCount(); ++index ) + { + Renderer renderer = container.GetChildAt( index ).GetRendererAt( 0u ); + DALI_TEST_CHECK( renderer ); + } + END_TEST; } @@ -1638,3 +1742,96 @@ int utcDaliTextEditorEvent05(void) END_TEST; } + +int utcDaliTextEditorHandles(void) +{ + ToolkitTestApplication application; + tet_infoline(" utcDaliTextEditorHandles"); + + TextEditor editor = TextEditor::New(); + DALI_TEST_CHECK( editor ); + + Stage::GetCurrent().Add( editor ); + + editor.SetProperty( TextEditor::Property::TEXT, "This is a long text for the size of the text-editor." ); + editor.SetProperty( TextEditor::Property::POINT_SIZE, 10.f ); + editor.SetProperty( TextEditor::Property::GRAB_HANDLE_IMAGE, HANDLE_IMAGE_FILE_NAME ); + editor.SetProperty( DevelTextEditor::Property::SMOOTH_SCROLL, true ); + + editor.SetSize( 30.f, 500.f ); + editor.SetParentOrigin( ParentOrigin::TOP_LEFT ); + editor.SetAnchorPoint( AnchorPoint::TOP_LEFT ); + + // Avoid a crash when core load gl resources. + application.GetGlAbstraction().SetCheckFramebufferStatusResult( GL_FRAMEBUFFER_COMPLETE ); + + // Render and notify + application.SendNotification(); + application.Render(); + + // Tap first to get the focus. + application.ProcessEvent( GenerateTap( Gesture::Possible, 1u, 1u, Vector2( 3.f, 25.0f ) ) ); + application.ProcessEvent( GenerateTap( Gesture::Started, 1u, 1u, Vector2( 3.f, 25.0f ) ) ); + + // Render and notify + application.SendNotification(); + application.Render(); + + // Tap to create the grab handle. + application.ProcessEvent( GenerateTap( Gesture::Possible, 1u, 1u, Vector2( 3.f, 25.0f ) ) ); + application.ProcessEvent( GenerateTap( Gesture::Started, 1u, 1u, Vector2( 3.f, 25.0f ) ) ); + + // Render and notify + application.SendNotification(); + application.Render(); + + // Get the active layer where the text's decoration is added. + Actor activeLayer = editor.GetChildAt( 1u ); + + // Get the handle's actor. + Actor handle = activeLayer.GetChildAt( 1u ); + handle.SetSize( 100.f, 100.f ); + + // Render and notify + application.SendNotification(); + application.Render(); + + // Touch the grab handle to set it as pressed. + Vector2 touchPos( 10.0f, 50.0f ); + Dali::Integration::TouchEvent event; + event = Dali::Integration::TouchEvent(); + event.AddPoint( GetPointDownInside( touchPos ) ); + application.ProcessEvent( event ); + + // Render and notify + application.SendNotification(); + application.Render(); + + // drag grab handle right + SendPan(application, Gesture::Possible, touchPos); + SendPan(application, Gesture::Started, touchPos); + touchPos.x += 5.0f; + Wait(application, 100); + + for(int i = 0;i<20;i++) + { + SendPan(application, Gesture::Continuing, touchPos); + touchPos.x += 5.0f; + Wait(application); + } + + SendPan(application, Gesture::Finished, touchPos); + Wait(application); + + // Release the grab handle. + event = Dali::Integration::TouchEvent(); + event.AddPoint( GetPointUpInside( touchPos ) ); + application.ProcessEvent( event ); + + // Render and notify + application.SendNotification(); + application.Render(); + + END_TEST; +} + diff --git a/automated-tests/src/dali-toolkit/utc-Dali-TextField.cpp b/automated-tests/src/dali-toolkit/utc-Dali-TextField.cpp index f551dad..e4926c6 100644 --- a/automated-tests/src/dali-toolkit/utc-Dali-TextField.cpp +++ b/automated-tests/src/dali-toolkit/utc-Dali-TextField.cpp @@ -1930,6 +1930,16 @@ int utcDaliTextFieldEvent08(void) Wait(application, 500); + // Long Press + application.ProcessEvent( GenerateLongPress( Gesture::Possible, 1u, Vector2( 1.f, 25.0f ) ) ); + application.ProcessEvent( GenerateLongPress( Gesture::Started, 1u, Vector2( 1.f, 25.0f ) ) ); + + // Render and notify + application.SendNotification(); + application.Render(); + + Wait(application, 500); + Stage stage = Stage::GetCurrent(); Layer layer = stage.GetRootLayer(); Actor actor = layer.FindChildByName("optionPaste"); @@ -1950,6 +1960,7 @@ int utcDaliTextFieldEvent08(void) application.ProcessEvent( event ); } DALI_TEST_EQUALS( field.GetProperty( TextEditor::Property::TEXT ), std::string("testTextFieldEvent"), TEST_LOCATION ); + END_TEST; } 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 3e2c606..240dfde 100644 --- a/dali-toolkit/internal/controls/text-controls/text-field-impl.cpp +++ b/dali-toolkit/internal/controls/text-controls/text-field-impl.cpp @@ -1162,6 +1162,9 @@ void TextField::OnInitialize() // Disable the smooth handle panning. mController->SetSmoothHandlePanEnabled( false ); + mController->SetNoTextDoubleTapAction( Controller::NoTextTap::NO_ACTION ); + mController->SetNoTextLongPressAction( Controller::NoTextTap::SHOW_SELECTION_POPUP ); + // Forward input events to controller EnableGestureDetection( static_cast( Gesture::Tap | Gesture::Pan | Gesture::LongPress ) ); GetTapGestureDetector().SetMaximumTapsRequired( 2 ); diff --git a/dali-toolkit/internal/text/cursor-helper-functions.cpp b/dali-toolkit/internal/text/cursor-helper-functions.cpp index 1fbca0d..b847996 100644 --- a/dali-toolkit/internal/text/cursor-helper-functions.cpp +++ b/dali-toolkit/internal/text/cursor-helper-functions.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016 Samsung Electronics Co., Ltd. + * Copyright (c) 2017 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. @@ -43,7 +43,7 @@ struct FindWordData : textBuffer( textBuffer ), totalNumberOfCharacters( totalNumberOfCharacters ), hitCharacter( hitCharacter ), - foundIndex( 0u ), + foundIndex( 0 ), isWhiteSpace( isWhiteSpace ), isNewParagraph( isNewParagraph ) {} @@ -136,10 +136,17 @@ namespace Text { LineIndex GetClosestLine( VisualModelPtr visualModel, - float visualY ) + float visualY, + bool& matchedLine ) { float totalHeight = 0.f; - LineIndex lineIndex = 0u; + LineIndex lineIndex = 0; + matchedLine = false; + + if( visualY < 0.f ) + { + return 0; + } const Vector& lines = visualModel->mLines; @@ -156,16 +163,17 @@ LineIndex GetClosestLine( VisualModelPtr visualModel, if( visualY < totalHeight ) { + matchedLine = true; return lineIndex; } } - if( lineIndex == 0u ) + if( lineIndex == 0 ) { return 0; } - return lineIndex-1; + return lineIndex - 1u; } float CalculateLineOffset( const Vector& lines, @@ -192,11 +200,16 @@ CharacterIndex GetClosestCursorIndex( VisualModelPtr visualModel, LogicalModelPtr logicalModel, MetricsPtr metrics, float visualX, - float visualY ) + float visualY, + CharacterHitTest::Mode mode, + bool& matchedCharacter ) { DALI_LOG_INFO( gLogFilter, Debug::Verbose, "GetClosestCursorIndex, closest visualX %f visualY %f\n", visualX, visualY ); - CharacterIndex logicalIndex = 0u; + // Whether there is a hit on a glyph. + matchedCharacter = false; + + CharacterIndex logicalIndex = 0; const Length totalNumberOfGlyphs = visualModel->mGlyphs.Count(); const Length totalNumberOfLines = visualModel->mLines.Count(); @@ -206,9 +219,19 @@ CharacterIndex GetClosestCursorIndex( VisualModelPtr visualModel, return logicalIndex; } + // Whether there is a hit on a line. + bool matchedLine = false; + // Find which line is closest. const LineIndex lineIndex = Text::GetClosestLine( visualModel, - visualY ); + visualY, + matchedLine ); + + if( !matchedLine && ( CharacterHitTest::TAP == mode ) ) + { + // Return the first or the last character if the touch point doesn't hit a line. + return ( visualY < 0.f ) ? 0 : logicalModel->mText.Count(); + } // Convert from text's coords to line's coords. const LineRun& line = *( visualModel->mLines.Begin() + lineIndex ); @@ -241,12 +264,12 @@ CharacterIndex GetClosestCursorIndex( VisualModelPtr visualModel, // The character's direction buffer. const CharacterDirection* const directionsBuffer = bidiLineFetched ? logicalModel->mCharacterDirections.Begin() : NULL; - // Whether there is a hit on a glyph. - bool matched = false; + // Whether the touch point if before the first glyph. + bool isBeforeFirstGlyph = false; // Traverses glyphs in visual order. To do that use the visual to logical conversion table. CharacterIndex visualIndex = startCharacter; - Length numberOfVisualCharacters = 0u; + Length numberOfVisualCharacters = 0; for( ; visualIndex < endCharacter; ++visualIndex ) { // The character in logical order. @@ -257,7 +280,7 @@ CharacterIndex GetClosestCursorIndex( VisualModelPtr visualModel, const Length numberOfGlyphs = *( glyphsPerCharacterBuffer + characterLogicalOrderIndex ); ++numberOfVisualCharacters; - if( 0u != numberOfGlyphs ) + if( 0 != numberOfGlyphs ) { // Get the first character/glyph of the group of glyphs. const CharacterIndex firstVisualCharacterIndex = 1u + visualIndex - numberOfVisualCharacters; @@ -275,6 +298,17 @@ CharacterIndex GetClosestCursorIndex( VisualModelPtr visualModel, // Get the position of the first glyph. const Vector2& position = *( positionsBuffer + firstLogicalGlyphIndex ); + if( startCharacter == visualIndex ) + { + const float glyphPosition = -glyphMetrics.xBearing + position.x; + + if( visualX < glyphPosition ) + { + isBeforeFirstGlyph = true; + break; + } + } + // Whether the glyph can be split, like Latin ligatures fi, ff or Arabic (ل + ا). Length numberOfCharacters = *( charactersPerGlyphBuffer + firstLogicalGlyphIndex ); if( direction != LTR ) @@ -284,7 +318,7 @@ CharacterIndex GetClosestCursorIndex( VisualModelPtr visualModel, // number of glyphs in the table is found first. // Jump the number of characters to the next glyph is needed. - if( 0u == numberOfCharacters ) + if( 0 == numberOfCharacters ) { // TODO: This is a workaround to fix an issue with complex characters in the arabic // script like i.e. رّ or الأَبْجَدِيَّة العَرَبِيَّة @@ -298,7 +332,7 @@ CharacterIndex GetClosestCursorIndex( VisualModelPtr visualModel, // Find the number of characters. for( GlyphIndex index = firstLogicalGlyphIndex + 1u; - ( 0u == numberOfCharacters ) && ( index < totalNumberOfGlyphs ) ; + ( 0 == numberOfCharacters ) && ( index < totalNumberOfGlyphs ); ++index ) { numberOfCharacters = *( charactersPerGlyphBuffer + index ); @@ -322,7 +356,7 @@ CharacterIndex GetClosestCursorIndex( VisualModelPtr visualModel, const Length numberOfBlocks = isInterglyphIndex ? numberOfCharacters : 1u; const float glyphAdvance = glyphMetrics.advance / static_cast( numberOfBlocks ); - CharacterIndex index = 0u; + CharacterIndex index = 0; for( ; index < numberOfBlocks; ++index ) { // Find the mid-point of the area containing the glyph @@ -330,31 +364,39 @@ CharacterIndex GetClosestCursorIndex( VisualModelPtr visualModel, if( visualX < glyphCenter ) { - matched = true; + matchedCharacter = true; break; } } - if( matched ) + if( matchedCharacter ) { // If the glyph is shaped from more than one character, it matches the character of the glyph. visualIndex = firstVisualCharacterIndex + index; break; } - numberOfVisualCharacters = 0u; + numberOfVisualCharacters = 0; } - } + } // for characters in visual order. // The number of characters of the whole text. const Length totalNumberOfCharacters = logicalModel->mText.Count(); // Return the logical position of the cursor in characters. - if( !matched ) + if( !matchedCharacter ) { - // If no character is matched, then the last character (in visual order) of the line is used. - visualIndex = endCharacter; + if( isBeforeFirstGlyph ) + { + // If no character is matched, then the first character (in visual order) of the line is used. + visualIndex = startCharacter; + } + else + { + // If no character is matched, then the last character (in visual order) of the line is used. + visualIndex = endCharacter; + } } // Get the paragraph direction. @@ -611,7 +653,7 @@ void GetCursorPosition( VisualModelPtr visualModel, isCurrentRightToLeft = *( directionsBuffer + index ); } - Length numberOfGlyphAdvance = ( isFirstPositionOfLine ? 0u : 1u ) + characterIndex - firstIndex; + Length numberOfGlyphAdvance = ( isFirstPositionOfLine ? 0 : 1u ) + characterIndex - firstIndex; if( isCurrentRightToLeft ) { numberOfGlyphAdvance = primaryNumberOfCharacters - numberOfGlyphAdvance; @@ -692,9 +734,9 @@ bool FindSelectionIndices( VisualModelPtr visualModel, float visualX, float visualY, CharacterIndex& startIndex, - CharacterIndex& endIndex ) + CharacterIndex& endIndex, + CharacterIndex& noTextHitIndex ) { - /* Hit character Select |-------------------------------------------------------|------------------------------------------| @@ -705,23 +747,33 @@ bool FindSelectionIndices( VisualModelPtr visualModel, | On a new paragraph character | The word or group of white spaces before | |-------------------------------------------------------|------------------------------------------| */ + const Length totalNumberOfCharacters = logicalModel->mText.Count(); + startIndex = 0; + endIndex = 0; + noTextHitIndex = 0; + if( 0 == totalNumberOfCharacters ) + { + // Nothing to do if the model is empty. + return false; + } + + bool matchedCharacter = false; CharacterIndex hitCharacter = Text::GetClosestCursorIndex( visualModel, logicalModel, metrics, visualX, - visualY ); - - const Length totalNumberOfCharacters = logicalModel->mText.Count(); - - DALI_ASSERT_DEBUG( ( hitCharacter <= totalNumberOfCharacters ) && "GetClosestCursorIndex returned out of bounds index" ); + visualY, + CharacterHitTest::TAP, + matchedCharacter ); - if( 0u == totalNumberOfCharacters ) + if( !matchedCharacter ) { - // Nothing to do if the model is empty. - return false; + noTextHitIndex = hitCharacter; } + DALI_ASSERT_DEBUG( ( hitCharacter <= totalNumberOfCharacters ) && "GetClosestCursorIndex returned out of bounds index" ); + if( hitCharacter >= totalNumberOfCharacters ) { // Closest hit character is the last character. @@ -757,7 +809,7 @@ bool FindSelectionIndices( VisualModelPtr visualModel, { // Find the first character before the hit one which is not a new paragraph character. - if( hitCharacter > 0u ) + if( hitCharacter > 0 ) { endIndex = hitCharacter - 1u; for( ; endIndex > 0; --endIndex ) @@ -790,13 +842,13 @@ bool FindSelectionIndices( VisualModelPtr visualModel, { // Select the word before or after the white space - if( 0u == hitCharacter ) + if( 0 == hitCharacter ) { data.isWhiteSpace = false; FindEndOfWord( data ); endIndex = data.foundIndex; } - else if( hitCharacter > 0u ) + else if( hitCharacter > 0 ) { // Find the start of the word. data.hitCharacter = hitCharacter - 1u; @@ -809,7 +861,7 @@ bool FindSelectionIndices( VisualModelPtr visualModel, } } - return true; + return matchedCharacter; } } // namespace Text diff --git a/dali-toolkit/internal/text/cursor-helper-functions.h b/dali-toolkit/internal/text/cursor-helper-functions.h index 5896b4d..f62165b 100644 --- a/dali-toolkit/internal/text/cursor-helper-functions.h +++ b/dali-toolkit/internal/text/cursor-helper-functions.h @@ -1,8 +1,8 @@ -#ifndef __DALI_TOOLKIT_TEXT_CURSOR_HELPER_FUNCTIONS_H__ -#define __DALI_TOOLKIT_TEXT_CURSOR_HELPER_FUNCTIONS_H__ +#ifndef DALI_TOOLKIT_TEXT_CURSOR_HELPER_FUNCTIONS_H +#define DALI_TOOLKIT_TEXT_CURSOR_HELPER_FUNCTIONS_H /* - * Copyright (c) 2016 Samsung Electronics Co., Ltd. + * Copyright (c) 2017 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. @@ -32,6 +32,18 @@ namespace Toolkit namespace Text { +struct CharacterHitTest +{ + /** + * @brief Enumeration of the types of hit test. + */ + enum Mode + { + TAP, ///< Retrieves the first or last character of the line if the touch point is outside of the boundaries of the text. + SCROLL ///< Retrieves the character above or below to the touch point if it's outside of the boundaries of the text. + }; +}; + struct CursorInfo { CursorInfo() @@ -63,11 +75,13 @@ struct CursorInfo * * @param[in] visualModel The visual model. * @param[in] visualY The touch point 'y' in text's coords. + * @param[out] matchedLine Whether the touch point actually hits a line. * * @return A line index. */ LineIndex GetClosestLine( VisualModelPtr visualModel, - float visualY ); + float visualY, + bool& matchedLine ); /** * @brief Calculates the vertical line's offset for a given line. @@ -85,11 +99,18 @@ float CalculateLineOffset( const Vector& lines, /** * @brief Retrieves the cursor's logical position for a given touch point x,y * + * There are two types of hit test: CharacterHitTest::TAP retrieves the first or + * last character of a line if the touch point is outside the boundaries of the + * text, CharacterHitTest::SCROLL retrieves the character above or below to the + * touch point if it's outside the boundaries of the text. + * * @param[in] visualModel The visual model. * @param[in] logicalModel The logical model. * @param[in] metrics A wrapper around FontClient used to get metrics. * @param[in] visualX The touch point 'x' in text's coords. * @param[in] visualY The touch point 'y' in text's coords. + * @param[in] mode The type of hit test. + * @param[out] matchedCharacter Whether the touch point actually hits a character. * * @return The logical cursor position (in characters). 0 is just before the first character, a value equal to the number of characters is just after the last character. */ @@ -97,8 +118,9 @@ CharacterIndex GetClosestCursorIndex( VisualModelPtr visualModel, LogicalModelPtr logicalModel, MetricsPtr metrics, float visualX, - float visualY ); - + float visualY, + CharacterHitTest::Mode mode, + bool& matchedCharacter ); /** * @brief Calculates the cursor's position for a given character index in the logical order. @@ -128,8 +150,9 @@ void GetCursorPosition( VisualModelPtr visualModel, * @param[in] visualY The touch point 'y' in text's coords. * @param[out] startIndex Index to the first character of the selected word. * @param[out] endIndex Index to the last character of the selected word. + * @param[out] noTextHitIndex Index to the nearest character when there is no hit. * - * @return @e true if the indices are found. + * @return @e true if the touch point hits a character. */ bool FindSelectionIndices( VisualModelPtr visualModel, LogicalModelPtr logicalModel, @@ -137,7 +160,8 @@ bool FindSelectionIndices( VisualModelPtr visualModel, float visualX, float visualY, CharacterIndex& startIndex, - CharacterIndex& endIndex ); + CharacterIndex& endIndex, + CharacterIndex& noTextHitIndex ); } // namespace Text @@ -145,4 +169,4 @@ bool FindSelectionIndices( VisualModelPtr visualModel, } // namespace Dali -#endif // __DALI_TOOLKIT_TEXT_CURSOR_HELPER_FUNCTIONS_H__ +#endif // DALI_TOOLKIT_TEXT_CURSOR_HELPER_FUNCTIONS_H diff --git a/dali-toolkit/internal/text/text-controller-impl.cpp b/dali-toolkit/internal/text/text-controller-impl.cpp index da7d929..05f35361 100644 --- a/dali-toolkit/internal/text/text-controller-impl.cpp +++ b/dali-toolkit/internal/text/text-controller-impl.cpp @@ -84,6 +84,8 @@ EventData::EventData( DecoratorPtr decorator ) mPreEditStartPosition( 0u ), mPreEditLength( 0u ), mCursorHookPositionX( 0.f ), + mDoubleTapAction( Controller::NoTextTap::NO_ACTION ), + mLongPressAction( Controller::NoTextTap::SHOW_SELECTION_POPUP ), mIsShowingPlaceholderText( false ), mPreEditFlag( false ), mDecoratorUpdated( false ), @@ -97,6 +99,7 @@ EventData::EventData( DecoratorPtr decorator ) mUpdateLeftSelectionPosition( false ), mUpdateRightSelectionPosition( false ), mIsLeftHandleSelected( false ), + mIsRightHandleSelected( false ), mUpdateHighlightBox( false ), mScrollAfterUpdatePosition( false ), mScrollAfterDelete( false ), @@ -227,10 +230,25 @@ bool Controller::Impl::ProcessInputEvents() if( mEventData->mScrollAfterUpdatePosition && ( mEventData->mIsLeftHandleSelected ? mEventData->mUpdateLeftSelectionPosition : mEventData->mUpdateRightSelectionPosition ) ) { - CursorInfo& info = mEventData->mIsLeftHandleSelected ? leftHandleInfo : rightHandleInfo; + if( mEventData->mIsLeftHandleSelected && mEventData->mIsRightHandleSelected ) + { + CursorInfo& infoLeft = leftHandleInfo; + + const Vector2 currentCursorPositionLeft( infoLeft.primaryPosition.x, infoLeft.lineOffset ); + ScrollToMakePositionVisible( currentCursorPositionLeft, infoLeft.lineHeight ); + + CursorInfo& infoRight = rightHandleInfo; - const Vector2 currentCursorPosition( info.primaryPosition.x, info.lineOffset ); - ScrollToMakePositionVisible( currentCursorPosition, info.lineHeight ); + const Vector2 currentCursorPositionRight( infoRight.primaryPosition.x, infoRight.lineOffset ); + ScrollToMakePositionVisible( currentCursorPositionRight, infoRight.lineHeight ); + } + else + { + CursorInfo& info = mEventData->mIsLeftHandleSelected ? leftHandleInfo : rightHandleInfo; + + const Vector2 currentCursorPosition( info.primaryPosition.x, info.lineOffset ); + ScrollToMakePositionVisible( currentCursorPosition, info.lineHeight ); + } } } @@ -261,6 +279,8 @@ bool Controller::Impl::ProcessInputEvents() mEventData->mUpdateLeftSelectionPosition = false; mEventData->mUpdateRightSelectionPosition = false; mEventData->mUpdateHighlightBox = false; + mEventData->mIsLeftHandleSelected = false; + mEventData->mIsRightHandleSelected = false; } mEventData->mScrollAfterUpdatePosition = false; @@ -1165,13 +1185,16 @@ void Controller::Impl::OnCursorKeyEvent( const Event& event ) const float hitPointY = cursorInfo.lineOffset - 0.5f * ( line.ascender - line.descender ); // Use the cursor hook position 'x' and the next hit 'y' position to calculate the new cursor index. + bool matchedCharacter = false; mEventData->mPrimaryCursorPosition = Text::GetClosestCursorIndex( mModel->mVisualModel, mModel->mLogicalModel, mMetrics, mEventData->mCursorHookPositionX, - hitPointY ); + hitPointY, + CharacterHitTest::TAP, + matchedCharacter ); } - else if( Dali::DALI_KEY_CURSOR_DOWN == keyCode ) + else if( Dali::DALI_KEY_CURSOR_DOWN == keyCode ) { // Get first the line index of the current cursor position index. CharacterIndex characterIndex = 0u; @@ -1197,11 +1220,14 @@ void Controller::Impl::OnCursorKeyEvent( const Event& event ) const float hitPointY = cursorInfo.lineOffset + cursorInfo.lineHeight + 0.5f * ( line.ascender - line.descender ); // Use the cursor hook position 'x' and the next hit 'y' position to calculate the new cursor index. + bool matchedCharacter = false; mEventData->mPrimaryCursorPosition = Text::GetClosestCursorIndex( mModel->mVisualModel, mModel->mLogicalModel, mMetrics, mEventData->mCursorHookPositionX, - hitPointY ); + hitPointY, + CharacterHitTest::TAP, + matchedCharacter ); } } @@ -1227,11 +1253,15 @@ void Controller::Impl::OnTapEvent( const Event& event ) // Keep the tap 'x' position. Used to move the cursor. mEventData->mCursorHookPositionX = xPosition; + // Whether to touch point hits on a glyph. + bool matchedCharacter = false; mEventData->mPrimaryCursorPosition = Text::GetClosestCursorIndex( mModel->mVisualModel, mModel->mLogicalModel, mMetrics, xPosition, - yPosition ); + yPosition, + CharacterHitTest::TAP, + matchedCharacter ); // When the cursor position is changing, delay cursor blinking mEventData->mDecorator->DelayCursorBlink(); @@ -1253,6 +1283,20 @@ void Controller::Impl::OnTapEvent( const Event& event ) mEventData->mImfManager.NotifyCursorPosition(); } } + else if( 2u == tapCount ) + { + if( mEventData->mSelectionEnabled ) + { + // Convert from control's coords to text's coords. + const float xPosition = event.p2.mFloat - mModel->mScrollPosition.x; + const float yPosition = event.p3.mFloat - mModel->mScrollPosition.y; + + // Calculates the logical position from the x,y coords. + RepositionSelectionHandles( xPosition, + yPosition, + mEventData->mDoubleTapAction ); + } + } } } @@ -1323,12 +1367,26 @@ void Controller::Impl::OnLongPressEvent( const Event& event ) { DALI_LOG_INFO( gLogFilter, Debug::General, "Controller::OnLongPressEvent\n" ); - if( EventData::EDITING == mEventData->mState ) + if( !IsShowingRealText() && ( EventData::EDITING == mEventData->mState ) ) { - ChangeState ( EventData::EDITING_WITH_POPUP ); + ChangeState( EventData::EDITING_WITH_POPUP ); mEventData->mDecoratorUpdated = true; mEventData->mUpdateInputStyle = true; } + else + { + if( mEventData->mSelectionEnabled ) + { + // Convert from control's coords to text's coords. + const float xPosition = event.p2.mFloat - mModel->mScrollPosition.x; + const float yPosition = event.p3.mFloat - mModel->mScrollPosition.y; + + // Calculates the logical position from the x,y coords. + RepositionSelectionHandles( xPosition, + yPosition, + mEventData->mLongPressAction ); + } + } } void Controller::Impl::OnHandleEvent( const Event& event ) @@ -1350,11 +1408,14 @@ void Controller::Impl::OnHandleEvent( const Event& event ) const float yPosition = event.p3.mFloat - mModel->mScrollPosition.y; // Need to calculate the handle's new position. + bool matchedCharacter = false; const CharacterIndex handleNewPosition = Text::GetClosestCursorIndex( mModel->mVisualModel, mModel->mLogicalModel, mMetrics, xPosition, - yPosition ); + yPosition, + CharacterHitTest::SCROLL, + matchedCharacter ); if( Event::GRAB_HANDLE_EVENT == event.type ) { @@ -1391,6 +1452,7 @@ void Controller::Impl::OnHandleEvent( const Event& event ) // Will define the order to scroll the text to match the handle position. mEventData->mIsLeftHandleSelected = true; + mEventData->mIsRightHandleSelected = false; } else if( Event::RIGHT_SELECTION_HANDLE_EVENT == event.type ) { @@ -1411,6 +1473,7 @@ void Controller::Impl::OnHandleEvent( const Event& event ) // Will define the order to scroll the text to match the handle position. mEventData->mIsLeftHandleSelected = false; + mEventData->mIsRightHandleSelected = true; } } // end ( HANDLE_PRESSED == state ) else if( ( HANDLE_RELEASED == state ) || @@ -1423,11 +1486,14 @@ void Controller::Impl::OnHandleEvent( const Event& event ) const float xPosition = event.p2.mFloat - mModel->mScrollPosition.x; const float yPosition = event.p3.mFloat - mModel->mScrollPosition.y; + bool matchedCharacter = false; handlePosition = Text::GetClosestCursorIndex( mModel->mVisualModel, mModel->mLogicalModel, mMetrics, xPosition, - yPosition ); + yPosition, + CharacterHitTest::SCROLL, + matchedCharacter ); } if( Event::GRAB_HANDLE_EVENT == event.type ) @@ -1539,11 +1605,14 @@ void Controller::Impl::OnHandleEvent( const Event& event ) // Get the new handle position. // The grab handle's position is in decorator's coords. Need to transforms to text's coords. + bool matchedCharacter = false; const CharacterIndex handlePosition = Text::GetClosestCursorIndex( mModel->mVisualModel, mModel->mLogicalModel, mMetrics, position.x - mModel->mScrollPosition.x, - position.y - mModel->mScrollPosition.y ); + position.y - mModel->mScrollPosition.y, + CharacterHitTest::SCROLL, + matchedCharacter ); if( mEventData->mPrimaryCursorPosition != handlePosition ) { @@ -1580,11 +1649,14 @@ void Controller::Impl::OnHandleEvent( const Event& event ) // Get the new handle position. // The selection handle's position is in decorator's coords. Need to transform to text's coords. + bool matchedCharacter = false; const CharacterIndex handlePosition = Text::GetClosestCursorIndex( mModel->mVisualModel, mModel->mLogicalModel, mMetrics, position.x - mModel->mScrollPosition.x, - position.y - mModel->mScrollPosition.y ); + position.y - mModel->mScrollPosition.y, + CharacterHitTest::SCROLL, + matchedCharacter ); if( leftSelectionHandleEvent ) { @@ -1637,7 +1709,8 @@ void Controller::Impl::OnSelectEvent( const Event& event ) // Calculates the logical position from the x,y coords. RepositionSelectionHandles( xPosition, - yPosition ); + yPosition, + Controller::NoTextTap::HIGHLIGHT ); } } @@ -2168,7 +2241,7 @@ void Controller::Impl::RepositionSelectionHandles() mEventData->mDecoratorUpdated = true; } -void Controller::Impl::RepositionSelectionHandles( float visualX, float visualY ) +void Controller::Impl::RepositionSelectionHandles( float visualX, float visualY, Controller::NoTextTap::Action action ) { if( NULL == mEventData ) { @@ -2194,16 +2267,18 @@ void Controller::Impl::RepositionSelectionHandles( float visualX, float visualY // Find which word was selected CharacterIndex selectionStart( 0 ); CharacterIndex selectionEnd( 0 ); - const bool indicesFound = FindSelectionIndices( mModel->mVisualModel, + CharacterIndex noTextHitIndex( 0 ); + const bool characterHit = FindSelectionIndices( mModel->mVisualModel, mModel->mLogicalModel, mMetrics, visualX, visualY, selectionStart, - selectionEnd ); + selectionEnd, + noTextHitIndex ); DALI_LOG_INFO( gLogFilter, Debug::Verbose, "%p selectionStart %d selectionEnd %d\n", this, selectionStart, selectionEnd ); - if( indicesFound ) + if( characterHit || ( Controller::NoTextTap::HIGHLIGHT == action ) ) { ChangeState( EventData::SELECTING ); @@ -2222,12 +2297,22 @@ void Controller::Impl::RepositionSelectionHandles( float visualX, float visualY mEventData->mScrollAfterUpdatePosition = ( mEventData->mLeftSelectionPosition != mEventData->mRightSelectionPosition ); } - else + else if( Controller::NoTextTap::SHOW_SELECTION_POPUP == action ) { // Nothing to select. i.e. a white space, out of bounds - ChangeState( EventData::EDITING ); + ChangeState( EventData::EDITING_WITH_POPUP ); - mEventData->mPrimaryCursorPosition = selectionEnd; + mEventData->mPrimaryCursorPosition = noTextHitIndex; + + mEventData->mUpdateCursorPosition = true; + mEventData->mUpdateGrabHandlePosition = true; + mEventData->mScrollAfterUpdatePosition = true; + mEventData->mUpdateInputStyle = true; + } + else if( Controller::NoTextTap::NO_ACTION == action ) + { + // Nothing to select. i.e. a white space, out of bounds + mEventData->mPrimaryCursorPosition = noTextHitIndex; mEventData->mUpdateCursorPosition = true; mEventData->mUpdateGrabHandlePosition = true; diff --git a/dali-toolkit/internal/text/text-controller-impl.h b/dali-toolkit/internal/text/text-controller-impl.h index 0eb8451..37e5e5f 100644 --- a/dali-toolkit/internal/text/text-controller-impl.h +++ b/dali-toolkit/internal/text/text-controller-impl.h @@ -2,7 +2,7 @@ #define DALI_TOOLKIT_TEXT_CONTROLLER_IMPL_H /* - * Copyright (c) 2016 Samsung Electronics Co., Ltd. + * Copyright (c) 2017 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. @@ -130,6 +130,9 @@ struct EventData float mCursorHookPositionX; ///< Used to move the cursor with the keys or when scrolling the text vertically with the handles. + Controller::NoTextTap::Action mDoubleTapAction; ///< Action to be done when there is a double tap on top of 'no text' + Controller::NoTextTap::Action mLongPressAction; ///< Action to be done when there is a long press on top of 'no text' + bool mIsShowingPlaceholderText : 1; ///< True if the place-holder text is being displayed. bool mPreEditFlag : 1; ///< True if the model contains text in pre-edit state. bool mDecoratorUpdated : 1; ///< True if the decorator was updated during event processing. @@ -143,6 +146,7 @@ struct EventData bool mUpdateLeftSelectionPosition : 1; ///< True if the visual position of the left selection handle must be recalculated. bool mUpdateRightSelectionPosition : 1; ///< True if the visual position of the right selection handle must be recalculated. bool mIsLeftHandleSelected : 1; ///< Whether is the left handle the one which is selected. + bool mIsRightHandleSelected : 1; ///< Whether is the right handle the one which is selected. bool mUpdateHighlightBox : 1; ///< True if the text selection high light box must be updated. bool mScrollAfterUpdatePosition : 1; ///< Whether to scroll after the cursor position is updated. bool mScrollAfterDelete : 1; ///< Whether to scroll after delete characters. @@ -594,7 +598,7 @@ struct Controller::Impl void RequestGetTextFromClipboard(); void RepositionSelectionHandles(); - void RepositionSelectionHandles( float visualX, float visualY ); + void RepositionSelectionHandles( float visualX, float visualY, Controller::NoTextTap::Action action ); void SetPopupButtons(); diff --git a/dali-toolkit/internal/text/text-controller.cpp b/dali-toolkit/internal/text/text-controller.cpp index 4eb5662..55f40e9 100644 --- a/dali-toolkit/internal/text/text-controller.cpp +++ b/dali-toolkit/internal/text/text-controller.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016 Samsung Electronics Co., Ltd. + * Copyright (c) 2017 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. @@ -1330,6 +1330,46 @@ bool Controller::IsInputModePassword() return false; } +void Controller::SetNoTextDoubleTapAction( NoTextTap::Action action ) +{ + if( NULL != mImpl->mEventData ) + { + mImpl->mEventData->mDoubleTapAction = action; + } +} + +Controller::NoTextTap::Action Controller::GetNoTextDoubleTapAction() const +{ + NoTextTap::Action action = NoTextTap::NO_ACTION; + + if( NULL != mImpl->mEventData ) + { + action = mImpl->mEventData->mDoubleTapAction; + } + + return action; +} + +void Controller::SetNoTextLongPressAction( NoTextTap::Action action ) +{ + if( NULL != mImpl->mEventData ) + { + mImpl->mEventData->mLongPressAction = action; + } +} + +Controller::NoTextTap::Action Controller::GetNoTextLongPressAction() const +{ + NoTextTap::Action action = NoTextTap::NO_ACTION; + + if( NULL != mImpl->mEventData ) + { + action = mImpl->mEventData->mLongPressAction; + } + + return action; +} + // public : Queries & retrieves. Layout::Engine& Controller::GetLayoutEngine() @@ -1873,9 +1913,12 @@ void Controller::TapEvent( unsigned int tapCount, float x, float y ) if( mImpl->mEventData->mSelectionEnabled && mImpl->IsShowingRealText() ) { - SelectEvent( x, y, false ); + relayoutNeeded = true; + mImpl->mEventData->mIsLeftHandleSelected = true; + mImpl->mEventData->mIsRightHandleSelected = true; } } + // Handles & cursors must be repositioned after Relayout() i.e. after the Model has been updated if( relayoutNeeded ) { @@ -1916,35 +1959,42 @@ void Controller::LongPressEvent( Gesture::State state, float x, float y ) if( ( state == Gesture::Started ) && ( NULL != mImpl->mEventData ) ) { - if( !mImpl->IsShowingRealText() ) + // The 1st long-press on inactive text-field is treated as tap + if( EventData::INACTIVE == mImpl->mEventData->mState ) + { + mImpl->ChangeState( EventData::EDITING ); + + Event event( Event::TAP_EVENT ); + event.p1.mUint = 1; + event.p2.mFloat = x; + event.p3.mFloat = y; + mImpl->mEventData->mEventQueue.push_back( event ); + + mImpl->RequestRelayout(); + } + else if( !mImpl->IsShowingRealText() ) { Event event( Event::LONG_PRESS_EVENT ); event.p1.mInt = state; + event.p2.mFloat = x; + event.p3.mFloat = y; mImpl->mEventData->mEventQueue.push_back( event ); mImpl->RequestRelayout(); } - else + else if( !mImpl->IsClipboardVisible() ) { - // The 1st long-press on inactive text-field is treated as tap - if( EventData::INACTIVE == mImpl->mEventData->mState ) - { - mImpl->ChangeState( EventData::EDITING ); + // Reset the imf manager to commit the pre-edit before selecting the text. + mImpl->ResetImfManager(); - Event event( Event::TAP_EVENT ); - event.p1.mUint = 1; - event.p2.mFloat = x; - event.p3.mFloat = y; - mImpl->mEventData->mEventQueue.push_back( event ); - - mImpl->RequestRelayout(); - } - else - { - // Reset the imf manger to commit the pre-edit before selecting the text. - mImpl->ResetImfManager(); + Event event( Event::LONG_PRESS_EVENT ); + event.p1.mInt = state; + event.p2.mFloat = x; + event.p3.mFloat = y; + mImpl->mEventData->mEventQueue.push_back( event ); + mImpl->RequestRelayout(); - SelectEvent( x, y, false ); - } + mImpl->mEventData->mIsLeftHandleSelected = true; + mImpl->mEventData->mIsRightHandleSelected = true; } } } @@ -2912,6 +2962,8 @@ void Controller::SelectEvent( float x, float y, bool selectAll ) } mImpl->mEventData->mCheckScrollAmount = true; + mImpl->mEventData->mIsLeftHandleSelected = true; + mImpl->mEventData->mIsRightHandleSelected = true; mImpl->RequestRelayout(); } } diff --git a/dali-toolkit/internal/text/text-controller.h b/dali-toolkit/internal/text/text-controller.h index 9612b1a..5005d8e 100644 --- a/dali-toolkit/internal/text/text-controller.h +++ b/dali-toolkit/internal/text/text-controller.h @@ -2,7 +2,7 @@ #define DALI_TOOLKIT_TEXT_CONTROLLER_H /* - * Copyright (c) 2016 Samsung Electronics Co., Ltd. + * Copyright (c) 2017 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. @@ -120,6 +120,16 @@ public: // Enumerated types. PLACEHOLDER_TYPE_INACTIVE, }; + struct NoTextTap + { + enum Action + { + NO_ACTION, ///< Does no action if there is a tap on top of an area with no text. + HIGHLIGHT, ///< Highlights the nearest text (at the beginning or end of the text) and shows the text's selection popup. + SHOW_SELECTION_POPUP ///< Shows the text's selection popup. + }; + }; + public: // Constructor. /** @@ -350,6 +360,34 @@ public: // Configure the text controller. */ bool IsInputModePassword(); + /** + * @brief Sets the action when there is a double tap event on top of a text area with no text. + * + * @param[in] action The action to do. + */ + void SetNoTextDoubleTapAction( NoTextTap::Action action ); + + /** + * @brief Retrieves the action when there is a double tap event on top of a text area with no text. + * + * @return The action to do. + */ + NoTextTap::Action GetNoTextDoubleTapAction() const; + + /** + * @briefSets the action when there is a long press event on top of a text area with no text. + * + * @param[in] action The action to do. + */ + void SetNoTextLongPressAction( NoTextTap::Action action ); + + /** + * @brief Retrieves the action when there is a long press event on top of a text area with no text. + * + * @return The action to do. + */ + NoTextTap::Action GetNoTextLongPressAction() const; + public: // Update. /** -- 2.7.4