Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix TextEdit IME issues #87479

Merged
merged 1 commit into from
Feb 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion doc/classes/TextEdit.xml
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,12 @@
Adjust the viewport so the caret is visible.
</description>
</method>
<method name="apply_ime">
<return type="void" />
<description>
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.
</description>
</method>
<method name="backspace">
<return type="void" />
<param index="0" name="caret_index" type="int" default="-1" />
Expand All @@ -114,6 +120,12 @@
Starts a multipart edit. All edits will be treated as one action until [method end_complex_operation] is called.
</description>
</method>
<method name="cancel_ime">
<return type="void" />
<description>
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.
</description>
</method>
<method name="center_viewport_to_caret">
<return type="void" />
<param index="0" name="caret_index" type="int" default="0" />
Expand Down Expand Up @@ -611,7 +623,7 @@
<method name="has_ime_text" qualifiers="const">
<return type="bool" />
<description>
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).
</description>
</method>
<method name="has_redo" qualifiers="const">
Expand Down
3 changes: 3 additions & 0 deletions editor/plugins/script_text_editor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down Expand Up @@ -1960,6 +1961,8 @@ void ScriptTextEditor::_text_edit_gui_input(const Ref<InputEvent> &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;
Expand Down
3 changes: 3 additions & 0 deletions editor/plugins/text_editor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down Expand Up @@ -502,6 +503,8 @@ void TextEditor::_text_edit_gui_input(const Ref<InputEvent> &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;
Expand Down
4 changes: 4 additions & 0 deletions editor/plugins/text_shader_editor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -978,6 +980,8 @@ void TextShaderEditor::_text_edit_gui_input(const Ref<InputEvent> &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;
Expand Down
3 changes: 2 additions & 1 deletion scene/gui/code_edit.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -253,8 +253,9 @@ void CodeEdit::_notification(int p_what) {
void CodeEdit::gui_input(const Ref<InputEvent> &p_gui_input) {
Ref<InputEventMouseButton> 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;
}

Expand Down
136 changes: 81 additions & 55 deletions scene/gui/text_edit.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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;
Expand All @@ -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();
Expand All @@ -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;
Expand Down Expand Up @@ -1681,10 +1650,6 @@ void TextEdit::gui_input(const Ref<InputEvent> &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()) {
Expand Down Expand Up @@ -1718,6 +1683,8 @@ void TextEdit::gui_input(const Ref<InputEvent> &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;
Expand Down Expand Up @@ -1865,11 +1832,13 @@ void TextEdit::gui_input(const Ref<InputEvent> &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;
Expand Down Expand Up @@ -1909,6 +1878,11 @@ void TextEdit::gui_input(const Ref<InputEvent> &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();
Expand Down Expand Up @@ -1967,7 +1941,7 @@ void TextEdit::gui_input(const Ref<InputEvent> &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();
Expand Down Expand Up @@ -2012,6 +1986,7 @@ void TextEdit::gui_input(const Ref<InputEvent> &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);
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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()) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: _update_ime_text already checks has_ime_text

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I don't check for it, it would run the other branch in _update_ime_text() and invalidates the text cache unnecessarily.

_update_ime_text();
}

end_complex_operation();
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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);
Expand Down
6 changes: 6 additions & 0 deletions scene/gui/text_edit.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -696,6 +700,8 @@ class TextEdit : public Control {
/* Text */
// Text properties.
bool has_ime_text() const;
void cancel_ime();
void apply_ime();
kitbdev marked this conversation as resolved.
Show resolved Hide resolved

void set_editable(const bool p_editable);
bool is_editable() const;
Expand Down
Loading