From 504e0656bb7d530204ce7deb20075b6569846d50 Mon Sep 17 00:00:00 2001 From: Haoyu Qiu Date: Tue, 16 Apr 2024 17:34:01 +0800 Subject: [PATCH] Add TextEdit option to prevent copying without a selection --- doc/classes/EditorSettings.xml | 3 +++ doc/classes/TextEdit.xml | 3 +++ editor/code_editor.cpp | 3 +++ editor/editor_settings.cpp | 3 +++ scene/gui/code_edit.cpp | 3 +++ scene/gui/text_edit.cpp | 20 ++++++++++++++ scene/gui/text_edit.h | 4 +++ tests/scene/test_code_edit.h | 25 ++++++++++++++++++ tests/scene/test_text_edit.h | 48 ++++++++++++++++++++++++++++++++++ 9 files changed, 112 insertions(+) diff --git a/doc/classes/EditorSettings.xml b/doc/classes/EditorSettings.xml index 389d65e05a0..44197820406 100644 --- a/doc/classes/EditorSettings.xml +++ b/doc/classes/EditorSettings.xml @@ -1199,6 +1199,9 @@ If [code]true[/code], trims trailing whitespace when saving a script. Trailing whitespace refers to tab and space characters placed at the end of lines. Since these serve no practical purpose, they can and should be removed to make version control diffs less noisy. + + If [code]true[/code], copying or cutting without a selection is performed on all lines with a caret. Otherwise, copy and cut require a selection. + If [code]true[/code], automatically indents code when pressing the [kbd]Enter[/kbd] key based on blocks above the new line. diff --git a/doc/classes/TextEdit.xml b/doc/classes/TextEdit.xml index 6505e48fb9a..05184336d16 100644 --- a/doc/classes/TextEdit.xml +++ b/doc/classes/TextEdit.xml @@ -1299,6 +1299,9 @@ If [code]false[/code], existing text cannot be modified and new text cannot be added. + + If [code]true[/code], copying or cutting without a selection is performed on all lines with a caret. Otherwise, copy and cut require a selection. + If [code]true[/code], all occurrences of the selected text will be highlighted. diff --git a/editor/code_editor.cpp b/editor/code_editor.cpp index dd8aa523c46..7d895ce519a 100644 --- a/editor/code_editor.cpp +++ b/editor/code_editor.cpp @@ -1068,6 +1068,9 @@ void CodeTextEditor::update_editor_settings() { text_editor->set_draw_spaces(EDITOR_GET("text_editor/appearance/whitespace/draw_spaces")); text_editor->add_theme_constant_override("line_spacing", EDITOR_GET("text_editor/appearance/whitespace/line_spacing")); + // Behavior: General + text_editor->set_empty_selection_clipboard_enabled(EDITOR_GET("text_editor/behavior/general/empty_selection_clipboard")); + // Behavior: Navigation text_editor->set_scroll_past_end_of_file_enabled(EDITOR_GET("text_editor/behavior/navigation/scroll_past_end_of_file")); text_editor->set_smooth_scroll_enabled(EDITOR_GET("text_editor/behavior/navigation/smooth_scrolling")); diff --git a/editor/editor_settings.cpp b/editor/editor_settings.cpp index 96a7700c34a..c7d8ee32f2c 100644 --- a/editor/editor_settings.cpp +++ b/editor/editor_settings.cpp @@ -657,6 +657,9 @@ void EditorSettings::_load_defaults(Ref p_extra_config) { EDITOR_SETTING(Variant::INT, PROPERTY_HINT_RANGE, "text_editor/appearance/whitespace/line_spacing", 4, "0,50,1") // Behavior + // Behavior: General + _initial_set("text_editor/behavior/general/empty_selection_clipboard", true); + // Behavior: Navigation _initial_set("text_editor/behavior/navigation/move_caret_on_right_click", true); _initial_set("text_editor/behavior/navigation/scroll_past_end_of_file", false); diff --git a/scene/gui/code_edit.cpp b/scene/gui/code_edit.cpp index c3287035ffc..635228670d0 100644 --- a/scene/gui/code_edit.cpp +++ b/scene/gui/code_edit.cpp @@ -831,6 +831,9 @@ void CodeEdit::_cut_internal(int p_caret) { delete_selection(p_caret); return; } + if (!is_empty_selection_clipboard_enabled()) { + return; + } if (p_caret == -1) { delete_lines(); } else { diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp index d7799588ead..16e32be26ec 100644 --- a/scene/gui/text_edit.cpp +++ b/scene/gui/text_edit.cpp @@ -3366,6 +3366,14 @@ bool TextEdit::is_middle_mouse_paste_enabled() const { return middle_mouse_paste_enabled; } +void TextEdit::set_empty_selection_clipboard_enabled(bool p_enabled) { + empty_selection_clipboard_enabled = p_enabled; +} + +bool TextEdit::is_empty_selection_clipboard_enabled() const { + return empty_selection_clipboard_enabled; +} + // Text manipulation void TextEdit::clear() { setting_text = true; @@ -6568,6 +6576,9 @@ void TextEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("set_middle_mouse_paste_enabled", "enabled"), &TextEdit::set_middle_mouse_paste_enabled); ClassDB::bind_method(D_METHOD("is_middle_mouse_paste_enabled"), &TextEdit::is_middle_mouse_paste_enabled); + ClassDB::bind_method(D_METHOD("set_empty_selection_clipboard_enabled", "enabled"), &TextEdit::set_empty_selection_clipboard_enabled); + ClassDB::bind_method(D_METHOD("is_empty_selection_clipboard_enabled"), &TextEdit::is_empty_selection_clipboard_enabled); + // Text manipulation ClassDB::bind_method(D_METHOD("clear"), &TextEdit::clear); @@ -6961,6 +6972,7 @@ void TextEdit::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "drag_and_drop_selection_enabled"), "set_drag_and_drop_selection_enabled", "is_drag_and_drop_selection_enabled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "virtual_keyboard_enabled"), "set_virtual_keyboard_enabled", "is_virtual_keyboard_enabled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "middle_mouse_paste_enabled"), "set_middle_mouse_paste_enabled", "is_middle_mouse_paste_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "empty_selection_clipboard_enabled"), "set_empty_selection_clipboard_enabled", "is_empty_selection_clipboard_enabled"); ADD_PROPERTY(PropertyInfo(Variant::INT, "wrap_mode", PROPERTY_HINT_ENUM, "None,Boundary"), "set_line_wrapping_mode", "get_line_wrapping_mode"); ADD_PROPERTY(PropertyInfo(Variant::INT, "autowrap_mode", PROPERTY_HINT_ENUM, "Arbitrary:1,Word:2,Word (Smart):3"), "set_autowrap_mode", "get_autowrap_mode"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "indent_wrapped_lines"), "set_indent_wrapped_lines", "is_indent_wrapped_lines"); @@ -7215,6 +7227,10 @@ void TextEdit::_cut_internal(int p_caret) { return; } + if (!empty_selection_clipboard_enabled) { + return; + } + // Remove full lines. begin_complex_operation(); begin_multicaret_edit(); @@ -7245,6 +7261,10 @@ void TextEdit::_copy_internal(int p_caret) { return; } + if (!empty_selection_clipboard_enabled) { + return; + } + // Copy full lines. StringBuilder clipboard; Vector line_ranges; diff --git a/scene/gui/text_edit.h b/scene/gui/text_edit.h index c5f838020bd..94b105d4861 100644 --- a/scene/gui/text_edit.h +++ b/scene/gui/text_edit.h @@ -319,6 +319,7 @@ private: bool shortcut_keys_enabled = true; bool virtual_keyboard_enabled = true; bool middle_mouse_paste_enabled = true; + bool empty_selection_clipboard_enabled = true; // Overridable actions. String cut_copy_line = ""; @@ -770,6 +771,9 @@ public: void set_middle_mouse_paste_enabled(bool p_enabled); bool is_middle_mouse_paste_enabled() const; + void set_empty_selection_clipboard_enabled(bool p_enabled); + bool is_empty_selection_clipboard_enabled() const; + // Text manipulation void clear(); diff --git a/tests/scene/test_code_edit.h b/tests/scene/test_code_edit.h index 9ec1b812dfc..ef630ad4f79 100644 --- a/tests/scene/test_code_edit.h +++ b/tests/scene/test_code_edit.h @@ -4779,6 +4779,31 @@ TEST_CASE("[SceneTree][CodeEdit] text manipulation") { CHECK(code_edit->get_caret_column(3) == 0); } + SUBCASE("[SceneTree][CodeEdit] cut when empty selection clipboard disabled") { + DisplayServerMock *DS = (DisplayServerMock *)(DisplayServer::get_singleton()); + code_edit->set_empty_selection_clipboard_enabled(false); + DS->clipboard_set(""); + + code_edit->set_text("this is\nsome\n"); + code_edit->set_caret_line(0); + code_edit->set_caret_column(6); + MessageQueue::get_singleton()->flush(); + SIGNAL_DISCARD("text_set"); + SIGNAL_DISCARD("text_changed"); + SIGNAL_DISCARD("lines_edited_from"); + SIGNAL_DISCARD("caret_changed"); + + code_edit->cut(); + MessageQueue::get_singleton()->flush(); + CHECK(DS->clipboard_get() == ""); + CHECK(code_edit->get_text() == "this is\nsome\n"); + CHECK(code_edit->get_caret_line() == 0); + CHECK(code_edit->get_caret_column() == 6); + SIGNAL_CHECK_FALSE("caret_changed"); + SIGNAL_CHECK_FALSE("text_changed"); + SIGNAL_CHECK_FALSE("lines_edited_from"); + } + SUBCASE("[SceneTree][CodeEdit] new line") { // Add a new line. code_edit->set_text("test new line"); diff --git a/tests/scene/test_text_edit.h b/tests/scene/test_text_edit.h index 46a5046b21c..c41eebdf3a4 100644 --- a/tests/scene/test_text_edit.h +++ b/tests/scene/test_text_edit.h @@ -3585,6 +3585,54 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { SIGNAL_CHECK_FALSE("lines_edited_from"); } + SUBCASE("[TextEdit] cut when empty selection clipboard disabled") { + text_edit->set_empty_selection_clipboard_enabled(false); + DS->clipboard_set(""); + + text_edit->set_text("this is\nsome\n"); + text_edit->set_caret_line(0); + text_edit->set_caret_column(6); + MessageQueue::get_singleton()->flush(); + SIGNAL_DISCARD("text_set"); + SIGNAL_DISCARD("text_changed"); + SIGNAL_DISCARD("lines_edited_from"); + SIGNAL_DISCARD("caret_changed"); + + text_edit->cut(); + MessageQueue::get_singleton()->flush(); + CHECK(DS->clipboard_get() == ""); + CHECK(text_edit->get_text() == "this is\nsome\n"); + CHECK(text_edit->get_caret_line() == 0); + CHECK(text_edit->get_caret_column() == 6); + SIGNAL_CHECK_FALSE("caret_changed"); + SIGNAL_CHECK_FALSE("text_changed"); + SIGNAL_CHECK_FALSE("lines_edited_from"); + } + + SUBCASE("[TextEdit] copy when empty selection clipboard disabled") { + text_edit->set_empty_selection_clipboard_enabled(false); + DS->clipboard_set(""); + + text_edit->set_text("this is\nsome\n"); + text_edit->set_caret_line(0); + text_edit->set_caret_column(6); + MessageQueue::get_singleton()->flush(); + SIGNAL_DISCARD("text_set"); + SIGNAL_DISCARD("text_changed"); + SIGNAL_DISCARD("lines_edited_from"); + SIGNAL_DISCARD("caret_changed"); + + text_edit->copy(); + MessageQueue::get_singleton()->flush(); + CHECK(DS->clipboard_get() == ""); + CHECK(text_edit->get_text() == "this is\nsome\n"); + CHECK(text_edit->get_caret_line() == 0); + CHECK(text_edit->get_caret_column() == 6); + SIGNAL_CHECK_FALSE("caret_changed"); + SIGNAL_CHECK_FALSE("text_changed"); + SIGNAL_CHECK_FALSE("lines_edited_from"); + } + SIGNAL_UNWATCH(text_edit, "text_set"); SIGNAL_UNWATCH(text_edit, "text_changed"); SIGNAL_UNWATCH(text_edit, "lines_edited_from");