diff --git a/doc/classes/TextEdit.xml b/doc/classes/TextEdit.xml index ea8185fff4af..6a1879baa4d4 100644 --- a/doc/classes/TextEdit.xml +++ b/doc/classes/TextEdit.xml @@ -101,6 +101,12 @@ Adjust the viewport so the caret is visible. + + + + Applies text from the [url=https://en.wikipedia.org/wiki/Input_method]Input Method Editor[/url] (IME) to each caret and closes the IME if it is open. + + @@ -114,6 +120,12 @@ Starts a multipart edit. All edits will be treated as one action until [method end_complex_operation] is called. + + + + Closes the [url=https://en.wikipedia.org/wiki/Input_method]Input Method Editor[/url] (IME) if it is open. Any text in the IME will be lost. + + @@ -611,7 +623,7 @@ - Returns if the user has IME text. + Returns [code]true[/code] if the user has text in the [url=https://en.wikipedia.org/wiki/Input_method]Input Method Editor[/url] (IME). diff --git a/editor/plugins/script_text_editor.cpp b/editor/plugins/script_text_editor.cpp index d8fb55ca5432..04228c441048 100644 --- a/editor/plugins/script_text_editor.cpp +++ b/editor/plugins/script_text_editor.cpp @@ -1270,6 +1270,7 @@ void ScriptTextEditor::_gutter_clicked(int p_line, int p_gutter) { void ScriptTextEditor::_edit_option(int p_op) { CodeEdit *tx = code_editor->get_text_editor(); + tx->apply_ime(); switch (p_op) { case EDIT_UNDO: { @@ -1960,6 +1961,8 @@ void ScriptTextEditor::_text_edit_gui_input(const Ref &ev) { } if (create_menu) { + tx->apply_ime(); + Point2i pos = tx->get_line_column_at_pos(local_pos); int row = pos.y; int col = pos.x; diff --git a/editor/plugins/text_editor.cpp b/editor/plugins/text_editor.cpp index a2364278b6f1..c9a0cbd2de90 100644 --- a/editor/plugins/text_editor.cpp +++ b/editor/plugins/text_editor.cpp @@ -348,6 +348,7 @@ void TextEditor::set_find_replace_bar(FindReplaceBar *p_bar) { void TextEditor::_edit_option(int p_op) { CodeEdit *tx = code_editor->get_text_editor(); + tx->apply_ime(); switch (p_op) { case EDIT_UNDO: { @@ -502,6 +503,8 @@ void TextEditor::_text_edit_gui_input(const Ref &ev) { if (mb->get_button_index() == MouseButton::RIGHT) { CodeEdit *tx = code_editor->get_text_editor(); + tx->apply_ime(); + Point2i pos = tx->get_line_column_at_pos(mb->get_global_position() - tx->get_global_position()); int row = pos.y; int col = pos.x; diff --git a/editor/plugins/text_shader_editor.cpp b/editor/plugins/text_shader_editor.cpp index 98d83b6e955a..e158709a7533 100644 --- a/editor/plugins/text_shader_editor.cpp +++ b/editor/plugins/text_shader_editor.cpp @@ -628,6 +628,8 @@ ShaderTextEditor::ShaderTextEditor() { /*** SCRIPT EDITOR ******/ void TextShaderEditor::_menu_option(int p_option) { + shader_editor->get_text_editor()->apply_ime(); + switch (p_option) { case EDIT_UNDO: { shader_editor->get_text_editor()->undo(); @@ -978,6 +980,8 @@ void TextShaderEditor::_text_edit_gui_input(const Ref &ev) { if (mb->get_button_index() == MouseButton::RIGHT && mb->is_pressed()) { CodeEdit *tx = shader_editor->get_text_editor(); + tx->apply_ime(); + Point2i pos = tx->get_line_column_at_pos(mb->get_global_position() - tx->get_global_position()); int row = pos.y; int col = pos.x; diff --git a/scene/gui/code_edit.cpp b/scene/gui/code_edit.cpp index e63e1ee42a50..f50ac4a0eef5 100644 --- a/scene/gui/code_edit.cpp +++ b/scene/gui/code_edit.cpp @@ -253,8 +253,9 @@ void CodeEdit::_notification(int p_what) { void CodeEdit::gui_input(const Ref &p_gui_input) { Ref mb = p_gui_input; if (mb.is_valid()) { - /* Ignore mouse clicks in IME input mode. */ + // Ignore mouse clicks in IME input mode, let TextEdit handle it. if (has_ime_text()) { + TextEdit::gui_input(p_gui_input); return; } diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp index 540c99913175..81584d9020b4 100644 --- a/scene/gui/text_edit.cpp +++ b/scene/gui/text_edit.cpp @@ -1489,14 +1489,7 @@ void TextEdit::_notification(int p_what) { } if (has_focus()) { - if (get_viewport()->get_window_id() != DisplayServer::INVALID_WINDOW_ID && DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_IME)) { - DisplayServer::get_singleton()->window_set_ime_active(true, get_viewport()->get_window_id()); - Point2 pos = get_global_position() + get_caret_draw_pos(); - if (get_window()->get_embedder()) { - pos += get_viewport()->get_popup_base_transform().get_origin(); - } - DisplayServer::get_singleton()->window_set_ime_position(pos, get_viewport()->get_window_id()); - } + _update_ime_window_position(); } } break; @@ -1507,14 +1500,7 @@ void TextEdit::_notification(int p_what) { draw_caret = true; } - if (get_viewport()->get_window_id() != DisplayServer::INVALID_WINDOW_ID && DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_IME)) { - DisplayServer::get_singleton()->window_set_ime_active(true, get_viewport()->get_window_id()); - Point2 pos = get_global_position() + get_caret_draw_pos(); - if (get_window()->get_embedder()) { - pos += get_viewport()->get_popup_base_transform().get_origin(); - } - DisplayServer::get_singleton()->window_set_ime_position(pos, get_viewport()->get_window_id()); - } + _update_ime_window_position(); if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_VIRTUAL_KEYBOARD) && virtual_keyboard_enabled) { int caret_start = -1; @@ -1541,17 +1527,7 @@ void TextEdit::_notification(int p_what) { caret_blink_timer->stop(); } - if (get_viewport()->get_window_id() != DisplayServer::INVALID_WINDOW_ID && DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_IME)) { - DisplayServer::get_singleton()->window_set_ime_position(Point2(), get_viewport()->get_window_id()); - DisplayServer::get_singleton()->window_set_ime_active(false, get_viewport()->get_window_id()); - } - if (!ime_text.is_empty()) { - ime_text = ""; - ime_selection = Point2(); - for (int i = 0; i < carets.size(); i++) { - text.invalidate_cache(get_caret_line(i), get_caret_column(i), true, ime_text); - } - } + apply_ime(); if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_VIRTUAL_KEYBOARD) && virtual_keyboard_enabled) { DisplayServer::get_singleton()->virtual_keyboard_hide(); @@ -1571,15 +1547,8 @@ void TextEdit::_notification(int p_what) { delete_selection(); } - for (int i = 0; i < carets.size(); i++) { - String t; - if (get_caret_column(i) >= 0) { - t = text[get_caret_line(i)].substr(0, get_caret_column(i)) + ime_text + text[get_caret_line(i)].substr(get_caret_column(i), text[get_caret_line(i)].length()); - } else { - t = ime_text; - } - text.invalidate_cache(get_caret_line(i), get_caret_column(i), true, t, structured_text_parser(st_parser, st_args, t)); - } + _update_ime_text(); + adjust_viewport_to_caret(0); queue_redraw(); } } break; @@ -1681,10 +1650,6 @@ void TextEdit::gui_input(const Ref &p_gui_input) { if (is_layout_rtl()) { mpos.x = get_size().x - mpos.x; } - if (ime_text.length() != 0) { - // Ignore mouse clicks in IME input mode. - return; - } if (mb->is_pressed()) { if (mb->get_button_index() == MouseButton::WHEEL_UP && !mb->is_command_or_control_pressed()) { @@ -1718,6 +1683,8 @@ void TextEdit::gui_input(const Ref &p_gui_input) { if (mb->get_button_index() == MouseButton::LEFT) { _reset_caret_blink_timer(); + apply_ime(); + Point2i pos = get_line_column_at_pos(mpos); int row = pos.y; int col = pos.x; @@ -1865,11 +1832,13 @@ void TextEdit::gui_input(const Ref &p_gui_input) { } if (is_middle_mouse_paste_enabled() && mb->get_button_index() == MouseButton::MIDDLE && DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_CLIPBOARD_PRIMARY)) { + apply_ime(); paste_primary_clipboard(); } if (mb->get_button_index() == MouseButton::RIGHT && (context_menu_enabled || is_move_caret_on_right_click_enabled())) { _reset_caret_blink_timer(); + apply_ime(); Point2i pos = get_line_column_at_pos(mpos); int row = pos.y; @@ -1909,6 +1878,11 @@ void TextEdit::gui_input(const Ref &p_gui_input) { } } } else { + if (has_ime_text()) { + // Ignore mouse up in IME input mode. + return; + } + if (mb->get_button_index() == MouseButton::LEFT) { if (selection_drag_attempt && is_mouse_over_selection()) { remove_secondary_carets(); @@ -1967,7 +1941,7 @@ void TextEdit::gui_input(const Ref &p_gui_input) { _update_minimap_drag(); } - if (!dragging_minimap) { + if (!dragging_minimap && !has_ime_text()) { switch (selecting_mode) { case SelectionMode::SELECTION_MODE_POINTER: { _update_selection_mode_pointer(); @@ -2012,6 +1986,7 @@ void TextEdit::gui_input(const Ref &p_gui_input) { } if (drag_action && can_drop_data(mpos, get_viewport()->gui_get_drag_data())) { + apply_ime(); drag_caret_force_displayed = true; Point2i pos = get_line_column_at_pos(get_local_mouse_pos()); set_caret_line(pos.y, false, true, 0, 0); @@ -3030,6 +3005,43 @@ void TextEdit::_update_caches() { } } +void TextEdit::_close_ime_window() { + if (get_viewport()->get_window_id() == DisplayServer::INVALID_WINDOW_ID || !DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_IME)) { + return; + } + DisplayServer::get_singleton()->window_set_ime_position(Point2(), get_viewport()->get_window_id()); + DisplayServer::get_singleton()->window_set_ime_active(false, get_viewport()->get_window_id()); +} + +void TextEdit::_update_ime_window_position() { + if (get_viewport()->get_window_id() == DisplayServer::INVALID_WINDOW_ID || !DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_IME)) { + return; + } + DisplayServer::get_singleton()->window_set_ime_active(true, get_viewport()->get_window_id()); + Point2 pos = get_global_position() + get_caret_draw_pos(); + if (get_window()->get_embedder()) { + pos += get_viewport()->get_popup_base_transform().get_origin(); + } + // The window will move to the updated position the next time the IME is updated, not immediately. + DisplayServer::get_singleton()->window_set_ime_position(pos, get_viewport()->get_window_id()); +} + +void TextEdit::_update_ime_text() { + if (has_ime_text()) { + // Update text to visually include IME text. + for (int i = 0; i < get_caret_count(); i++) { + String text_with_ime = text[get_caret_line(i)].substr(0, get_caret_column(i)) + ime_text + text[get_caret_line(i)].substr(get_caret_column(i), text[get_caret_line(i)].length()); + text.invalidate_cache(get_caret_line(i), get_caret_column(i), true, text_with_ime, structured_text_parser(st_parser, st_args, text_with_ime)); + } + } else { + // Reset text. + for (int i = 0; i < get_caret_count(); i++) { + text.invalidate_cache(get_caret_line(i), get_caret_column(i), true); + } + } + queue_redraw(); +} + /* General overrides. */ Size2 TextEdit::get_minimum_size() const { Size2 size = theme_cache.style_normal->get_minimum_size(); @@ -3189,6 +3201,26 @@ bool TextEdit::has_ime_text() const { return !ime_text.is_empty(); } +void TextEdit::cancel_ime() { + if (!has_ime_text()) { + return; + } + ime_text = String(); + ime_selection = Point2(); + _close_ime_window(); + _update_ime_text(); +} + +void TextEdit::apply_ime() { + if (!has_ime_text()) { + return; + } + // Force apply the current IME text. + String insert_ime_text = ime_text; + cancel_ime(); + insert_text_at_caret(insert_ime_text); +} + void TextEdit::set_editable(const bool p_editable) { if (editable == p_editable) { return; @@ -3568,16 +3600,8 @@ void TextEdit::insert_text_at_caret(const String &p_text, int p_caret) { adjust_carets_after_edit(i, new_line, new_column, from_line, from_col); } - if (!ime_text.is_empty()) { - for (int i = 0; i < carets.size(); i++) { - String t; - if (get_caret_column(i) >= 0) { - t = text[get_caret_line(i)].substr(0, get_caret_column(i)) + ime_text + text[get_caret_line(i)].substr(get_caret_column(i), text[get_caret_line(i)].length()); - } else { - t = ime_text; - } - text.invalidate_cache(get_caret_line(i), get_caret_column(i), true, t, structured_text_parser(st_parser, st_args, t)); - } + if (has_ime_text()) { + _update_ime_text(); } end_complex_operation(); @@ -5560,14 +5584,14 @@ void TextEdit::adjust_viewport_to_caret(int p_caret) { Vector2i caret_pos; // Get position of the start of caret. - if (ime_text.length() != 0 && ime_selection.x != 0) { + if (has_ime_text() && ime_selection.x != 0) { caret_pos.x = _get_column_x_offset_for_line(get_caret_column(p_caret) + ime_selection.x, get_caret_line(p_caret), get_caret_column(p_caret)); } else { caret_pos.x = _get_column_x_offset_for_line(get_caret_column(p_caret), get_caret_line(p_caret), get_caret_column(p_caret)); } // Get position of the end of caret. - if (ime_text.length() != 0) { + if (has_ime_text()) { if (ime_selection.y != 0) { caret_pos.y = _get_column_x_offset_for_line(get_caret_column(p_caret) + ime_selection.x + ime_selection.y, get_caret_line(p_caret), get_caret_column(p_caret)); } else { @@ -5612,14 +5636,14 @@ void TextEdit::center_viewport_to_caret(int p_caret) { Vector2i caret_pos; // Get position of the start of caret. - if (ime_text.length() != 0 && ime_selection.x != 0) { + if (has_ime_text() && ime_selection.x != 0) { caret_pos.x = _get_column_x_offset_for_line(get_caret_column(p_caret) + ime_selection.x, get_caret_line(p_caret), get_caret_column(p_caret)); } else { caret_pos.x = _get_column_x_offset_for_line(get_caret_column(p_caret), get_caret_line(p_caret), get_caret_column(p_caret)); } // Get position of the end of caret. - if (ime_text.length() != 0) { + if (has_ime_text()) { if (ime_selection.y != 0) { caret_pos.y = _get_column_x_offset_for_line(get_caret_column(p_caret) + ime_selection.x + ime_selection.y, get_caret_line(p_caret), get_caret_column(p_caret)); } else { @@ -6029,6 +6053,8 @@ void TextEdit::_bind_methods() { /* Text */ // Text properties ClassDB::bind_method(D_METHOD("has_ime_text"), &TextEdit::has_ime_text); + ClassDB::bind_method(D_METHOD("cancel_ime"), &TextEdit::cancel_ime); + ClassDB::bind_method(D_METHOD("apply_ime"), &TextEdit::apply_ime); ClassDB::bind_method(D_METHOD("set_editable", "enabled"), &TextEdit::set_editable); ClassDB::bind_method(D_METHOD("is_editable"), &TextEdit::is_editable); diff --git a/scene/gui/text_edit.h b/scene/gui/text_edit.h index 8a541b623b47..d49be860a9c7 100644 --- a/scene/gui/text_edit.h +++ b/scene/gui/text_edit.h @@ -293,6 +293,10 @@ class TextEdit : public Control { void _clear(); void _update_caches(); + void _close_ime_window(); + void _update_ime_window_position(); + void _update_ime_text(); + // User control. bool overtype_mode = false; bool context_menu_enabled = true; @@ -696,6 +700,8 @@ class TextEdit : public Control { /* Text */ // Text properties. bool has_ime_text() const; + void cancel_ime(); + void apply_ime(); void set_editable(const bool p_editable); bool is_editable() const;