From: Richard Huang Date: Wed, 29 Nov 2017 14:15:20 +0000 (+0000) Subject: [4.0] Support Shift+Left/Right keys for text selection and Ctrl+C/V/X for text copy... X-Git-Tag: accepted/tizen/4.0/unified/20171205.221941~3 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=refs%2Fchanges%2F45%2F162445%2F3;p=platform%2Fcore%2Fuifw%2Fdali-toolkit.git [4.0] Support Shift+Left/Right keys for text selection and Ctrl+C/V/X for text copy/paste/cut Change-Id: I2135bf267424dc9e207fc5995280951033d02fc0 (cherry picked from commit 718e975ec30138ecd521ed2e4afdcf84172f0b08) --- diff --git a/automated-tests/src/dali-toolkit/utc-Dali-TextEditor.cpp b/automated-tests/src/dali-toolkit/utc-Dali-TextEditor.cpp index 6d4c3e3..487ae20 100644 --- a/automated-tests/src/dali-toolkit/utc-Dali-TextEditor.cpp +++ b/automated-tests/src/dali-toolkit/utc-Dali-TextEditor.cpp @@ -118,8 +118,14 @@ const std::string DEFAULT_FONT_DIR( "/resources/fonts" ); const int KEY_A_CODE = 38; const int KEY_D_CODE = 40; +const int KEY_C_CODE = 54; +const int KEY_V_CODE = 55; +const int KEY_X_CODE = 53; const int KEY_WHITE_SPACE_CODE = 65; +const int KEY_SHIFT_MODIFIER = 257; +const int KEY_CONTROL_MODIFIER = 258; + const char* HANDLE_IMAGE_FILE_NAME = TEST_RESOURCE_DIR "/insertpoint-icon.png"; const std::string DEFAULT_DEVICE_NAME("hwKeyboard"); @@ -2035,6 +2041,113 @@ int utcDaliTextEditorEvent06(void) application.SendNotification(); application.Render(); + application.ProcessEvent( GenerateKey( "", "", Dali::DevelKey::DALI_KEY_CONTROL_LEFT, 0, 0, Integration::KeyEvent::Down, DEFAULT_DEVICE_NAME, Device::Class::NONE, Device::Subclass::NONE ) ); + application.SendNotification(); + application.Render(); + + application.ProcessEvent( GenerateKey( "", "", Dali::DevelKey::DALI_KEY_CONTROL_RIGHT, 0, 0, Integration::KeyEvent::Down, DEFAULT_DEVICE_NAME, Device::Class::NONE, Device::Subclass::NONE ) ); + application.SendNotification(); + application.Render(); + + END_TEST; +} + +int utcDaliTextEditorEvent07(void) +{ + ToolkitTestApplication application; + tet_infoline(" utcDaliTextEditorEvent07"); + + // Checks if the highlight actor is created. + + TextEditor editor = TextEditor::New(); + DALI_TEST_CHECK( editor ); + + Stage::GetCurrent().Add( editor ); + + editor.SetProperty( TextEditor::Property::TEXT, "Hello\nworld\nHello world" ); + editor.SetProperty( TextEditor::Property::POINT_SIZE, 10.f ); + editor.SetSize( 100.f, 50.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 on the text editor + 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(); + + // Move to second line of the text. + application.ProcessEvent( GenerateKey( "", "", DALI_KEY_CURSOR_DOWN, 0, 0, Integration::KeyEvent::Down, DEFAULT_DEVICE_NAME, Device::Class::NONE, Device::Subclass::NONE ) ); + + // Render and notify + application.SendNotification(); + application.Render(); + + // Select some text in the right of the current cursor position + application.ProcessEvent( GenerateKey( "", "", DALI_KEY_SHIFT_LEFT, 0, 0, Integration::KeyEvent::Down, DEFAULT_DEVICE_NAME, Device::Class::NONE, Device::Subclass::NONE ) ); + application.ProcessEvent( GenerateKey( "", "", DALI_KEY_CURSOR_RIGHT, KEY_SHIFT_MODIFIER, 0, Integration::KeyEvent::Down, DEFAULT_DEVICE_NAME, Device::Class::NONE, Device::Subclass::NONE ) ); + application.ProcessEvent( GenerateKey( "", "", DALI_KEY_CURSOR_RIGHT, KEY_SHIFT_MODIFIER, 0, Integration::KeyEvent::Down, DEFAULT_DEVICE_NAME, Device::Class::NONE, Device::Subclass::NONE ) ); + application.ProcessEvent( GenerateKey( "", "", DALI_KEY_CURSOR_RIGHT, KEY_SHIFT_MODIFIER, 0, Integration::KeyEvent::Down, DEFAULT_DEVICE_NAME, Device::Class::NONE, Device::Subclass::NONE ) ); + + // Render and notify + application.SendNotification(); + application.Render(); + + // Cut the selected text + application.ProcessEvent( GenerateKey( "", "", Dali::DevelKey::DALI_KEY_CONTROL_LEFT, 0, 0, Integration::KeyEvent::Down, DEFAULT_DEVICE_NAME, Device::Class::NONE, Device::Subclass::NONE ) ); + application.ProcessEvent( GenerateKey( "x", "x", KEY_X_CODE, KEY_CONTROL_MODIFIER, 0, Integration::KeyEvent::Down, DEFAULT_DEVICE_NAME, Device::Class::NONE, Device::Subclass::NONE ) ); + + // Render and notify + application.SendNotification(); + application.Render(); + + DALI_TEST_EQUALS( "Hello\nld\nHello world", editor.GetProperty( TextEditor::Property::TEXT ), TEST_LOCATION ); + + // Select some text in the left of the current cursor position + application.ProcessEvent( GenerateKey( "", "", DALI_KEY_SHIFT_LEFT, 0, 0, Integration::KeyEvent::Down, DEFAULT_DEVICE_NAME, Device::Class::NONE, Device::Subclass::NONE ) ); + application.ProcessEvent( GenerateKey( "", "", DALI_KEY_CURSOR_LEFT, KEY_SHIFT_MODIFIER, 0, Integration::KeyEvent::Down, DEFAULT_DEVICE_NAME, Device::Class::NONE, Device::Subclass::NONE ) ); + application.ProcessEvent( GenerateKey( "", "", DALI_KEY_CURSOR_LEFT, KEY_SHIFT_MODIFIER, 0, Integration::KeyEvent::Down, DEFAULT_DEVICE_NAME, Device::Class::NONE, Device::Subclass::NONE ) ); + application.ProcessEvent( GenerateKey( "", "", DALI_KEY_CURSOR_LEFT, KEY_SHIFT_MODIFIER, 0, Integration::KeyEvent::Down, DEFAULT_DEVICE_NAME, Device::Class::NONE, Device::Subclass::NONE ) ); + + // Render and notify + application.SendNotification(); + application.Render(); + + // Copy the selected text + application.ProcessEvent( GenerateKey( "", "", Dali::DevelKey::DALI_KEY_CONTROL_LEFT, 0, 0, Integration::KeyEvent::Down, DEFAULT_DEVICE_NAME, Device::Class::NONE, Device::Subclass::NONE ) ); + application.ProcessEvent( GenerateKey( "c", "c", KEY_C_CODE, KEY_CONTROL_MODIFIER, 0, Integration::KeyEvent::Down, DEFAULT_DEVICE_NAME, Device::Class::NONE, Device::Subclass::NONE ) ); + + // Render and notify + application.SendNotification(); + application.Render(); + + // Move the cursor to the third line + application.ProcessEvent( GenerateKey( "", "", DALI_KEY_CURSOR_DOWN, 0, 0, Integration::KeyEvent::Down, DEFAULT_DEVICE_NAME, Device::Class::NONE, Device::Subclass::NONE ) ); + application.ProcessEvent( GenerateKey( "", "", DALI_KEY_CURSOR_DOWN, 0, 0, Integration::KeyEvent::Down, DEFAULT_DEVICE_NAME, Device::Class::NONE, Device::Subclass::NONE ) ); + + // Render and notify + application.SendNotification(); + application.Render(); + + // Paste the selected text at the current cursor position + application.ProcessEvent( GenerateKey( "", "", Dali::DevelKey::DALI_KEY_CONTROL_LEFT, 0, 0, Integration::KeyEvent::Down, DEFAULT_DEVICE_NAME, Device::Class::NONE, Device::Subclass::NONE ) ); + application.ProcessEvent( GenerateKey( "v", "v", KEY_V_CODE, KEY_CONTROL_MODIFIER, 0, Integration::KeyEvent::Down, DEFAULT_DEVICE_NAME, Device::Class::NONE, Device::Subclass::NONE ) ); + + // Render and notify + application.SendNotification(); + application.Render(); + + DALI_TEST_EQUALS( "Hello\nld\nHello lo\nworld", editor.GetProperty( TextEditor::Property::TEXT ), TEST_LOCATION ); + END_TEST; } diff --git a/dali-toolkit/internal/text/text-controller-impl.cpp b/dali-toolkit/internal/text/text-controller-impl.cpp index a451a2a..d0c3d53 100644 --- a/dali-toolkit/internal/text/text-controller-impl.cpp +++ b/dali-toolkit/internal/text/text-controller-impl.cpp @@ -1162,6 +1162,9 @@ void Controller::Impl::OnCursorKeyEvent( const Event& event ) } int keyCode = event.p1.mInt; + bool isShiftModifier = event.p2.mBool; + + CharacterIndex previousPrimaryCursorPosition = mEventData->mPrimaryCursorPosition; if( Dali::DALI_KEY_CURSOR_LEFT == keyCode ) { @@ -1177,8 +1180,10 @@ void Controller::Impl::OnCursorKeyEvent( const Event& event ) mEventData->mPrimaryCursorPosition = CalculateNewCursorIndex( mEventData->mPrimaryCursorPosition ); } } - else if( Dali::DALI_KEY_CURSOR_UP == keyCode ) + else if( Dali::DALI_KEY_CURSOR_UP == keyCode && !isShiftModifier ) { + // Ignore Shift-Up for text selection for now. + // Get first the line index of the current cursor position index. CharacterIndex characterIndex = 0u; @@ -1211,8 +1216,10 @@ void Controller::Impl::OnCursorKeyEvent( const Event& event ) CharacterHitTest::TAP, matchedCharacter ); } - else if( Dali::DALI_KEY_CURSOR_DOWN == keyCode ) + else if( Dali::DALI_KEY_CURSOR_DOWN == keyCode && !isShiftModifier ) { + // Ignore Shift-Down for text selection for now. + // Get first the line index of the current cursor position index. CharacterIndex characterIndex = 0u; @@ -1248,7 +1255,64 @@ void Controller::Impl::OnCursorKeyEvent( const Event& event ) } } - mEventData->mUpdateCursorPosition = true; + if ( !isShiftModifier && mEventData->mState != EventData::SELECTING ) + { + // Update selection position after moving the cursor + mEventData->mLeftSelectionPosition = mEventData->mPrimaryCursorPosition; + mEventData->mRightSelectionPosition = mEventData->mPrimaryCursorPosition; + } + + if ( isShiftModifier && IsShowingRealText() ) + { + // Handle text selection + bool selecting = false; + + if ( Dali::DALI_KEY_CURSOR_LEFT == keyCode || Dali::DALI_KEY_CURSOR_RIGHT == keyCode ) + { + // Shift-Left/Right to select the text + int cursorPositionDelta = mEventData->mPrimaryCursorPosition - previousPrimaryCursorPosition; + if ( cursorPositionDelta > 0 || mEventData->mRightSelectionPosition > 0u ) // Check the boundary + { + mEventData->mRightSelectionPosition += cursorPositionDelta; + } + selecting = true; + } + else if ( mEventData->mLeftSelectionPosition != mEventData->mRightSelectionPosition ) + { + // Show no grab handles and text highlight if Shift-Up/Down pressed but no selected text + selecting = true; + } + + if ( selecting ) + { + // Notify the cursor position to the imf manager. + if( mEventData->mImfManager ) + { + mEventData->mImfManager.SetCursorPosition( mEventData->mPrimaryCursorPosition ); + mEventData->mImfManager.NotifyCursorPosition(); + } + + ChangeState( EventData::SELECTING ); + + mEventData->mUpdateLeftSelectionPosition = true; + mEventData->mUpdateRightSelectionPosition = true; + mEventData->mUpdateGrabHandlePosition = true; + mEventData->mUpdateHighlightBox = true; + + // Hide the text selection popup if select the text using keyboard instead of moving grab handles + if( mEventData->mGrabHandlePopupEnabled ) + { + mEventData->mDecorator->SetPopupActive( false ); + } + } + } + else + { + // Handle normal cursor move + ChangeState( EventData::EDITING ); + mEventData->mUpdateCursorPosition = true; + } + mEventData->mUpdateInputStyle = true; mEventData->mScrollAfterUpdatePosition = true; } @@ -1288,6 +1352,10 @@ void Controller::Impl::OnTapEvent( const Event& event ) mEventData->mPrimaryCursorPosition = 0u; } + // Update selection position after tapping + mEventData->mLeftSelectionPosition = mEventData->mPrimaryCursorPosition; + mEventData->mRightSelectionPosition = mEventData->mPrimaryCursorPosition; + mEventData->mUpdateCursorPosition = true; mEventData->mUpdateGrabHandlePosition = true; mEventData->mScrollAfterUpdatePosition = true; diff --git a/dali-toolkit/internal/text/text-controller-impl.h b/dali-toolkit/internal/text/text-controller-impl.h index 44d1a7d..a5de151 100644 --- a/dali-toolkit/internal/text/text-controller-impl.h +++ b/dali-toolkit/internal/text/text-controller-impl.h @@ -64,6 +64,7 @@ struct Event int mInt; unsigned int mUint; float mFloat; + bool mBool; }; Event( Type eventType ) diff --git a/dali-toolkit/internal/text/text-controller.cpp b/dali-toolkit/internal/text/text-controller.cpp index babf62c..811474e 100755 --- a/dali-toolkit/internal/text/text-controller.cpp +++ b/dali-toolkit/internal/text/text-controller.cpp @@ -49,6 +49,10 @@ const float MAX_FLOAT = std::numeric_limits::max(); const std::string EMPTY_STRING(""); +const std::string KEY_C_NAME = "c"; +const std::string KEY_V_NAME = "v"; +const std::string KEY_X_NAME = "x"; + const char * const PLACEHOLDER_TEXT = "text"; const char * const PLACEHOLDER_TEXT_FOCUSED = "textFocused"; const char * const PLACEHOLDER_COLOR = "color"; @@ -2340,6 +2344,7 @@ bool Controller::KeyEvent( const Dali::KeyEvent& keyEvent ) { int keyCode = keyEvent.keyCode; const std::string& keyString = keyEvent.keyPressed; + const std::string keyName = keyEvent.keyPressedName; const bool isNullKey = ( 0 == keyCode ) && ( keyString.empty() ); @@ -2385,11 +2390,43 @@ bool Controller::KeyEvent( const Dali::KeyEvent& keyEvent ) mImpl->mEventData->mCheckScrollAmount = true; Event event( Event::CURSOR_KEY_EVENT ); event.p1.mInt = keyCode; + event.p2.mBool = keyEvent.IsShiftModifier(); mImpl->mEventData->mEventQueue.push_back( event ); // Will request for relayout. relayoutNeeded = true; } + else if ( Dali::DevelKey::DALI_KEY_CONTROL_LEFT == keyCode || Dali::DevelKey::DALI_KEY_CONTROL_RIGHT == keyCode ) + { + // Left or Right Control key event is received before Ctrl-C/V/X key event is received + // If not handle it here, any selected text will be deleted + + // Do nothing + return false; + } + else if ( keyEvent.IsCtrlModifier() ) + { + bool consumed = false; + if (keyName == KEY_C_NAME) + { + // Ctrl-C to copy the selected text + TextPopupButtonTouched( Toolkit::TextSelectionPopup::COPY ); + consumed = true; + } + else if (keyName == KEY_V_NAME) + { + // Ctrl-V to paste the copied text + TextPopupButtonTouched( Toolkit::TextSelectionPopup::PASTE ); + consumed = true; + } + else if (keyName == KEY_X_NAME) + { + // Ctrl-X to cut the selected text + TextPopupButtonTouched( Toolkit::TextSelectionPopup::CUT ); + consumed = true; + } + return consumed; + } else if( ( Dali::DALI_KEY_BACKSPACE == keyCode ) || ( Dali::DevelKey::DALI_KEY_DELETE == keyCode ) ) { @@ -3561,6 +3598,10 @@ void Controller::ProcessModifyEvents() { // When the text is being modified, delay cursor blinking mImpl->mEventData->mDecorator->DelayCursorBlink(); + + // Update selection position after modifying the text + mImpl->mEventData->mLeftSelectionPosition = mImpl->mEventData->mPrimaryCursorPosition; + mImpl->mEventData->mRightSelectionPosition = mImpl->mEventData->mPrimaryCursorPosition; } // Discard temporary text