Browse Source

Fix `LineEdit` and `TextEdit` context menus not customizable

Danil Alexeev 2 years ago
parent
commit
af8bf6f1d0
6 changed files with 373 additions and 215 deletions
  1. 67 22
      doc/classes/LineEdit.xml
  2. 69 24
      doc/classes/TextEdit.xml
  3. 116 83
      scene/gui/line_edit.cpp
  4. 4 2
      scene/gui/line_edit.h
  5. 113 83
      scene/gui/text_edit.cpp
  6. 4 1
      scene/gui/text_edit.h

+ 67 - 22
doc/classes/LineEdit.xml

@@ -61,6 +61,45 @@
 			<return type="PopupMenu" />
 			<return type="PopupMenu" />
 			<description>
 			<description>
 				Returns the [PopupMenu] of this [LineEdit]. By default, this menu is displayed when right-clicking on the [LineEdit].
 				Returns the [PopupMenu] of this [LineEdit]. By default, this menu is displayed when right-clicking on the [LineEdit].
+				You can add custom menu items or remove standard ones. Make sure your IDs don't conflict with the standard ones (see [enum MenuItems]). For example:
+				[codeblocks]
+				[gdscript]
+				func _ready():
+				    var menu = get_menu()
+				    # Remove all items after "Redo".
+				    menu.item_count = menu.get_item_index(MENU_REDO) + 1
+				    # Add custom items.
+				    menu.add_separator()
+				    menu.add_item("Insert Date", MENU_MAX + 1)
+				    # Connect callback.
+				    menu.id_pressed.connect(_on_item_pressed)
+
+				func _on_item_pressed(id):
+				    if id == MENU_MAX + 1:
+				        insert_text_at_caret(Time.get_date_string_from_system())
+				[/gdscript]
+				[csharp]
+				public override void _Ready()
+				{
+				    var menu = GetMenu();
+				    // Remove all items after "Redo".
+				    menu.ItemCount = menu.GetItemIndex(LineEdit.MenuItems.Redo) + 1;
+				    // Add custom items.
+				    menu.AddSeparator();
+				    menu.AddItem("Insert Date", LineEdit.MenuItems.Max + 1);
+				    // Add event handler.
+				    menu.IdPressed += OnItemPressed;
+				}
+
+				public void OnItemPressed(int id)
+				{
+				    if (id == LineEdit.MenuItems.Max + 1)
+				    {
+				        InsertTextAtCaret(Time.GetDateStringFromSystem());
+				    }
+				}
+				[/csharp]
+				[/codeblocks]
 				[b]Warning:[/b] This is a required internal node, removing and freeing it may cause a crash. If you wish to hide it or any of its children, use their [member Window.visible] property.
 				[b]Warning:[/b] This is a required internal node, removing and freeing it may cause a crash. If you wish to hide it or any of its children, use their [member Window.visible] property.
 			</description>
 			</description>
 		</method>
 		</method>
@@ -296,70 +335,76 @@
 		<constant name="MENU_REDO" value="6" enum="MenuItems">
 		<constant name="MENU_REDO" value="6" enum="MenuItems">
 			Reverse the last undo action.
 			Reverse the last undo action.
 		</constant>
 		</constant>
-		<constant name="MENU_DIR_INHERITED" value="7" enum="MenuItems">
+		<constant name="MENU_SUBMENU_TEXT_DIR" value="7" enum="MenuItems">
+			ID of "Text Writing Direction" submenu.
+		</constant>
+		<constant name="MENU_DIR_INHERITED" value="8" enum="MenuItems">
 			Sets text direction to inherited.
 			Sets text direction to inherited.
 		</constant>
 		</constant>
-		<constant name="MENU_DIR_AUTO" value="8" enum="MenuItems">
+		<constant name="MENU_DIR_AUTO" value="9" enum="MenuItems">
 			Sets text direction to automatic.
 			Sets text direction to automatic.
 		</constant>
 		</constant>
-		<constant name="MENU_DIR_LTR" value="9" enum="MenuItems">
+		<constant name="MENU_DIR_LTR" value="10" enum="MenuItems">
 			Sets text direction to left-to-right.
 			Sets text direction to left-to-right.
 		</constant>
 		</constant>
-		<constant name="MENU_DIR_RTL" value="10" enum="MenuItems">
+		<constant name="MENU_DIR_RTL" value="11" enum="MenuItems">
 			Sets text direction to right-to-left.
 			Sets text direction to right-to-left.
 		</constant>
 		</constant>
-		<constant name="MENU_DISPLAY_UCC" value="11" enum="MenuItems">
+		<constant name="MENU_DISPLAY_UCC" value="12" enum="MenuItems">
 			Toggles control character display.
 			Toggles control character display.
 		</constant>
 		</constant>
-		<constant name="MENU_INSERT_LRM" value="12" enum="MenuItems">
+		<constant name="MENU_SUBMENU_INSERT_UCC" value="13" enum="MenuItems">
+			ID of "Insert Control Character" submenu.
+		</constant>
+		<constant name="MENU_INSERT_LRM" value="14" enum="MenuItems">
 			Inserts left-to-right mark (LRM) character.
 			Inserts left-to-right mark (LRM) character.
 		</constant>
 		</constant>
-		<constant name="MENU_INSERT_RLM" value="13" enum="MenuItems">
+		<constant name="MENU_INSERT_RLM" value="15" enum="MenuItems">
 			Inserts right-to-left mark (RLM) character.
 			Inserts right-to-left mark (RLM) character.
 		</constant>
 		</constant>
-		<constant name="MENU_INSERT_LRE" value="14" enum="MenuItems">
+		<constant name="MENU_INSERT_LRE" value="16" enum="MenuItems">
 			Inserts start of left-to-right embedding (LRE) character.
 			Inserts start of left-to-right embedding (LRE) character.
 		</constant>
 		</constant>
-		<constant name="MENU_INSERT_RLE" value="15" enum="MenuItems">
+		<constant name="MENU_INSERT_RLE" value="17" enum="MenuItems">
 			Inserts start of right-to-left embedding (RLE) character.
 			Inserts start of right-to-left embedding (RLE) character.
 		</constant>
 		</constant>
-		<constant name="MENU_INSERT_LRO" value="16" enum="MenuItems">
+		<constant name="MENU_INSERT_LRO" value="18" enum="MenuItems">
 			Inserts start of left-to-right override (LRO) character.
 			Inserts start of left-to-right override (LRO) character.
 		</constant>
 		</constant>
-		<constant name="MENU_INSERT_RLO" value="17" enum="MenuItems">
+		<constant name="MENU_INSERT_RLO" value="19" enum="MenuItems">
 			Inserts start of right-to-left override (RLO) character.
 			Inserts start of right-to-left override (RLO) character.
 		</constant>
 		</constant>
-		<constant name="MENU_INSERT_PDF" value="18" enum="MenuItems">
+		<constant name="MENU_INSERT_PDF" value="20" enum="MenuItems">
 			Inserts pop direction formatting (PDF) character.
 			Inserts pop direction formatting (PDF) character.
 		</constant>
 		</constant>
-		<constant name="MENU_INSERT_ALM" value="19" enum="MenuItems">
+		<constant name="MENU_INSERT_ALM" value="21" enum="MenuItems">
 			Inserts Arabic letter mark (ALM) character.
 			Inserts Arabic letter mark (ALM) character.
 		</constant>
 		</constant>
-		<constant name="MENU_INSERT_LRI" value="20" enum="MenuItems">
+		<constant name="MENU_INSERT_LRI" value="22" enum="MenuItems">
 			Inserts left-to-right isolate (LRI) character.
 			Inserts left-to-right isolate (LRI) character.
 		</constant>
 		</constant>
-		<constant name="MENU_INSERT_RLI" value="21" enum="MenuItems">
+		<constant name="MENU_INSERT_RLI" value="23" enum="MenuItems">
 			Inserts right-to-left isolate (RLI) character.
 			Inserts right-to-left isolate (RLI) character.
 		</constant>
 		</constant>
-		<constant name="MENU_INSERT_FSI" value="22" enum="MenuItems">
+		<constant name="MENU_INSERT_FSI" value="24" enum="MenuItems">
 			Inserts first strong isolate (FSI) character.
 			Inserts first strong isolate (FSI) character.
 		</constant>
 		</constant>
-		<constant name="MENU_INSERT_PDI" value="23" enum="MenuItems">
+		<constant name="MENU_INSERT_PDI" value="25" enum="MenuItems">
 			Inserts pop direction isolate (PDI) character.
 			Inserts pop direction isolate (PDI) character.
 		</constant>
 		</constant>
-		<constant name="MENU_INSERT_ZWJ" value="24" enum="MenuItems">
+		<constant name="MENU_INSERT_ZWJ" value="26" enum="MenuItems">
 			Inserts zero width joiner (ZWJ) character.
 			Inserts zero width joiner (ZWJ) character.
 		</constant>
 		</constant>
-		<constant name="MENU_INSERT_ZWNJ" value="25" enum="MenuItems">
+		<constant name="MENU_INSERT_ZWNJ" value="27" enum="MenuItems">
 			Inserts zero width non-joiner (ZWNJ) character.
 			Inserts zero width non-joiner (ZWNJ) character.
 		</constant>
 		</constant>
-		<constant name="MENU_INSERT_WJ" value="26" enum="MenuItems">
+		<constant name="MENU_INSERT_WJ" value="28" enum="MenuItems">
 			Inserts word joiner (WJ) character.
 			Inserts word joiner (WJ) character.
 		</constant>
 		</constant>
-		<constant name="MENU_INSERT_SHY" value="27" enum="MenuItems">
+		<constant name="MENU_INSERT_SHY" value="29" enum="MenuItems">
 			Inserts soft hyphen (SHY) character.
 			Inserts soft hyphen (SHY) character.
 		</constant>
 		</constant>
-		<constant name="MENU_MAX" value="28" enum="MenuItems">
+		<constant name="MENU_MAX" value="30" enum="MenuItems">
 			Represents the size of the [enum MenuItems] enum.
 			Represents the size of the [enum MenuItems] enum.
 		</constant>
 		</constant>
 		<constant name="KEYBOARD_TYPE_DEFAULT" value="0" enum="VirtualKeyboardType">
 		<constant name="KEYBOARD_TYPE_DEFAULT" value="0" enum="VirtualKeyboardType">

+ 69 - 24
doc/classes/TextEdit.xml

@@ -390,6 +390,45 @@
 			<return type="PopupMenu" />
 			<return type="PopupMenu" />
 			<description>
 			<description>
 				Returns the [PopupMenu] of this [TextEdit]. By default, this menu is displayed when right-clicking on the [TextEdit].
 				Returns the [PopupMenu] of this [TextEdit]. By default, this menu is displayed when right-clicking on the [TextEdit].
+				You can add custom menu items or remove standard ones. Make sure your IDs don't conflict with the standard ones (see [enum MenuItems]). For example:
+				[codeblocks]
+				[gdscript]
+				func _ready():
+				    var menu = get_menu()
+				    # Remove all items after "Redo".
+				    menu.item_count = menu.get_item_index(MENU_REDO) + 1
+				    # Add custom items.
+				    menu.add_separator()
+				    menu.add_item("Insert Date", MENU_MAX + 1)
+				    # Connect callback.
+				    menu.id_pressed.connect(_on_item_pressed)
+
+				func _on_item_pressed(id):
+				    if id == MENU_MAX + 1:
+				        insert_text_at_caret(Time.get_date_string_from_system())
+				[/gdscript]
+				[csharp]
+				public override void _Ready()
+				{
+				    var menu = GetMenu();
+				    // Remove all items after "Redo".
+				    menu.ItemCount = menu.GetItemIndex(TextEdit.MenuItems.Redo) + 1;
+				    // Add custom items.
+				    menu.AddSeparator();
+				    menu.AddItem("Insert Date", TextEdit.MenuItems.Max + 1);
+				    // Add event handler.
+				    menu.IdPressed += OnItemPressed;
+				}
+
+				public void OnItemPressed(int id)
+				{
+				    if (id == TextEdit.MenuItems.Max + 1)
+				    {
+				        InsertTextAtCaret(Time.GetDateStringFromSystem());
+				    }
+				}
+				[/csharp]
+				[/codeblocks]
 				[b]Warning:[/b] This is a required internal node, removing and freeing it may cause a crash. If you wish to hide it or any of its children, use their [member Window.visible] property.
 				[b]Warning:[/b] This is a required internal node, removing and freeing it may cause a crash. If you wish to hide it or any of its children, use their [member Window.visible] property.
 			</description>
 			</description>
 		</method>
 		</method>
@@ -682,7 +721,7 @@
 			<return type="void" />
 			<return type="void" />
 			<param index="0" name="option" type="int" />
 			<param index="0" name="option" type="int" />
 			<description>
 			<description>
-				Triggers a right-click menu action by the specified index. See [enum MenuItems] for a list of available indexes.
+				Executes a given action as defined in the [enum MenuItems] enum.
 			</description>
 			</description>
 		</method>
 		</method>
 		<method name="merge_gutters">
 		<method name="merge_gutters">
@@ -764,7 +803,7 @@
 				[codeblocks]
 				[codeblocks]
 				[gdscript]
 				[gdscript]
 				var result = search("print", SEARCH_WHOLE_WORDS, 0, 0)
 				var result = search("print", SEARCH_WHOLE_WORDS, 0, 0)
-				if  result.x != -1:
+				if result.x != -1:
 				    # Result found.
 				    # Result found.
 				    var line_number = result.y
 				    var line_number = result.y
 				    var column_number = result.x
 				    var column_number = result.x
@@ -1224,70 +1263,76 @@
 		<constant name="MENU_REDO" value="6" enum="MenuItems">
 		<constant name="MENU_REDO" value="6" enum="MenuItems">
 			Redoes the previous action.
 			Redoes the previous action.
 		</constant>
 		</constant>
-		<constant name="MENU_DIR_INHERITED" value="7" enum="MenuItems">
+		<constant name="MENU_SUBMENU_TEXT_DIR" value="7" enum="MenuItems">
+			ID of "Text Writing Direction" submenu.
+		</constant>
+		<constant name="MENU_DIR_INHERITED" value="8" enum="MenuItems">
 			Sets text direction to inherited.
 			Sets text direction to inherited.
 		</constant>
 		</constant>
-		<constant name="MENU_DIR_AUTO" value="8" enum="MenuItems">
+		<constant name="MENU_DIR_AUTO" value="9" enum="MenuItems">
 			Sets text direction to automatic.
 			Sets text direction to automatic.
 		</constant>
 		</constant>
-		<constant name="MENU_DIR_LTR" value="9" enum="MenuItems">
+		<constant name="MENU_DIR_LTR" value="10" enum="MenuItems">
 			Sets text direction to left-to-right.
 			Sets text direction to left-to-right.
 		</constant>
 		</constant>
-		<constant name="MENU_DIR_RTL" value="10" enum="MenuItems">
+		<constant name="MENU_DIR_RTL" value="11" enum="MenuItems">
 			Sets text direction to right-to-left.
 			Sets text direction to right-to-left.
 		</constant>
 		</constant>
-		<constant name="MENU_DISPLAY_UCC" value="11" enum="MenuItems">
+		<constant name="MENU_DISPLAY_UCC" value="12" enum="MenuItems">
 			Toggles control character display.
 			Toggles control character display.
 		</constant>
 		</constant>
-		<constant name="MENU_INSERT_LRM" value="12" enum="MenuItems">
+		<constant name="MENU_SUBMENU_INSERT_UCC" value="13" enum="MenuItems">
+			ID of "Insert Control Character" submenu.
+		</constant>
+		<constant name="MENU_INSERT_LRM" value="14" enum="MenuItems">
 			Inserts left-to-right mark (LRM) character.
 			Inserts left-to-right mark (LRM) character.
 		</constant>
 		</constant>
-		<constant name="MENU_INSERT_RLM" value="13" enum="MenuItems">
+		<constant name="MENU_INSERT_RLM" value="15" enum="MenuItems">
 			Inserts right-to-left mark (RLM) character.
 			Inserts right-to-left mark (RLM) character.
 		</constant>
 		</constant>
-		<constant name="MENU_INSERT_LRE" value="14" enum="MenuItems">
+		<constant name="MENU_INSERT_LRE" value="16" enum="MenuItems">
 			Inserts start of left-to-right embedding (LRE) character.
 			Inserts start of left-to-right embedding (LRE) character.
 		</constant>
 		</constant>
-		<constant name="MENU_INSERT_RLE" value="15" enum="MenuItems">
+		<constant name="MENU_INSERT_RLE" value="17" enum="MenuItems">
 			Inserts start of right-to-left embedding (RLE) character.
 			Inserts start of right-to-left embedding (RLE) character.
 		</constant>
 		</constant>
-		<constant name="MENU_INSERT_LRO" value="16" enum="MenuItems">
+		<constant name="MENU_INSERT_LRO" value="18" enum="MenuItems">
 			Inserts start of left-to-right override (LRO) character.
 			Inserts start of left-to-right override (LRO) character.
 		</constant>
 		</constant>
-		<constant name="MENU_INSERT_RLO" value="17" enum="MenuItems">
+		<constant name="MENU_INSERT_RLO" value="19" enum="MenuItems">
 			Inserts start of right-to-left override (RLO) character.
 			Inserts start of right-to-left override (RLO) character.
 		</constant>
 		</constant>
-		<constant name="MENU_INSERT_PDF" value="18" enum="MenuItems">
+		<constant name="MENU_INSERT_PDF" value="20" enum="MenuItems">
 			Inserts pop direction formatting (PDF) character.
 			Inserts pop direction formatting (PDF) character.
 		</constant>
 		</constant>
-		<constant name="MENU_INSERT_ALM" value="19" enum="MenuItems">
+		<constant name="MENU_INSERT_ALM" value="21" enum="MenuItems">
 			Inserts Arabic letter mark (ALM) character.
 			Inserts Arabic letter mark (ALM) character.
 		</constant>
 		</constant>
-		<constant name="MENU_INSERT_LRI" value="20" enum="MenuItems">
+		<constant name="MENU_INSERT_LRI" value="22" enum="MenuItems">
 			Inserts left-to-right isolate (LRI) character.
 			Inserts left-to-right isolate (LRI) character.
 		</constant>
 		</constant>
-		<constant name="MENU_INSERT_RLI" value="21" enum="MenuItems">
+		<constant name="MENU_INSERT_RLI" value="23" enum="MenuItems">
 			Inserts right-to-left isolate (RLI) character.
 			Inserts right-to-left isolate (RLI) character.
 		</constant>
 		</constant>
-		<constant name="MENU_INSERT_FSI" value="22" enum="MenuItems">
+		<constant name="MENU_INSERT_FSI" value="24" enum="MenuItems">
 			Inserts first strong isolate (FSI) character.
 			Inserts first strong isolate (FSI) character.
 		</constant>
 		</constant>
-		<constant name="MENU_INSERT_PDI" value="23" enum="MenuItems">
+		<constant name="MENU_INSERT_PDI" value="25" enum="MenuItems">
 			Inserts pop direction isolate (PDI) character.
 			Inserts pop direction isolate (PDI) character.
 		</constant>
 		</constant>
-		<constant name="MENU_INSERT_ZWJ" value="24" enum="MenuItems">
+		<constant name="MENU_INSERT_ZWJ" value="26" enum="MenuItems">
 			Inserts zero width joiner (ZWJ) character.
 			Inserts zero width joiner (ZWJ) character.
 		</constant>
 		</constant>
-		<constant name="MENU_INSERT_ZWNJ" value="25" enum="MenuItems">
+		<constant name="MENU_INSERT_ZWNJ" value="27" enum="MenuItems">
 			Inserts zero width non-joiner (ZWNJ) character.
 			Inserts zero width non-joiner (ZWNJ) character.
 		</constant>
 		</constant>
-		<constant name="MENU_INSERT_WJ" value="26" enum="MenuItems">
+		<constant name="MENU_INSERT_WJ" value="28" enum="MenuItems">
 			Inserts word joiner (WJ) character.
 			Inserts word joiner (WJ) character.
 		</constant>
 		</constant>
-		<constant name="MENU_INSERT_SHY" value="27" enum="MenuItems">
+		<constant name="MENU_INSERT_SHY" value="29" enum="MenuItems">
 			Inserts soft hyphen (SHY) character.
 			Inserts soft hyphen (SHY) character.
 		</constant>
 		</constant>
-		<constant name="MENU_MAX" value="28" enum="MenuItems">
+		<constant name="MENU_MAX" value="30" enum="MenuItems">
 			Represents the size of the [enum MenuItems] enum.
 			Represents the size of the [enum MenuItems] enum.
 		</constant>
 		</constant>
 		<constant name="ACTION_NONE" value="0" enum="EditAction">
 		<constant name="ACTION_NONE" value="0" enum="EditAction">

+ 116 - 83
scene/gui/line_edit.cpp

@@ -247,7 +247,7 @@ void LineEdit::gui_input(const Ref<InputEvent> &p_event) {
 			return;
 			return;
 		}
 		}
 		if (b->is_pressed() && b->get_button_index() == MouseButton::RIGHT && context_menu_enabled) {
 		if (b->is_pressed() && b->get_button_index() == MouseButton::RIGHT && context_menu_enabled) {
-			_ensure_menu();
+			_update_context_menu();
 			menu->set_position(get_screen_position() + get_local_mouse_position());
 			menu->set_position(get_screen_position() + get_local_mouse_position());
 			menu->reset_size();
 			menu->reset_size();
 			menu->popup();
 			menu->popup();
@@ -452,7 +452,7 @@ void LineEdit::gui_input(const Ref<InputEvent> &p_event) {
 
 
 		if (context_menu_enabled) {
 		if (context_menu_enabled) {
 			if (k->is_action("ui_menu", true)) {
 			if (k->is_action("ui_menu", true)) {
-				_ensure_menu();
+				_update_context_menu();
 				Point2 pos = Point2(get_caret_pixel_pos().x, (get_size().y + theme_cache.font->get_height(theme_cache.font_size)) / 2);
 				Point2 pos = Point2(get_caret_pixel_pos().x, (get_size().y + theme_cache.font->get_height(theme_cache.font_size)) / 2);
 				menu->set_position(get_screen_position() + pos);
 				menu->set_position(get_screen_position() + pos);
 				menu->reset_size();
 				menu->reset_size();
@@ -2077,7 +2077,9 @@ bool LineEdit::is_menu_visible() const {
 }
 }
 
 
 PopupMenu *LineEdit::get_menu() const {
 PopupMenu *LineEdit::get_menu() const {
-	const_cast<LineEdit *>(this)->_ensure_menu();
+	if (!menu) {
+		const_cast<LineEdit *>(this)->_generate_context_menu();
+	}
 	return menu;
 	return menu;
 }
 }
 
 
@@ -2335,6 +2337,115 @@ Key LineEdit::_get_menu_action_accelerator(const String &p_action) {
 	}
 	}
 }
 }
 
 
+void LineEdit::_generate_context_menu() {
+	menu = memnew(PopupMenu);
+	add_child(menu, false, INTERNAL_MODE_FRONT);
+
+	menu_dir = memnew(PopupMenu);
+	menu_dir->set_name("DirMenu");
+	menu_dir->add_radio_check_item(RTR("Same as Layout Direction"), MENU_DIR_INHERITED);
+	menu_dir->add_radio_check_item(RTR("Auto-Detect Direction"), MENU_DIR_AUTO);
+	menu_dir->add_radio_check_item(RTR("Left-to-Right"), MENU_DIR_LTR);
+	menu_dir->add_radio_check_item(RTR("Right-to-Left"), MENU_DIR_RTL);
+	menu->add_child(menu_dir, false, INTERNAL_MODE_FRONT);
+
+	menu_ctl = memnew(PopupMenu);
+	menu_ctl->set_name("CTLMenu");
+	menu_ctl->add_item(RTR("Left-to-Right Mark (LRM)"), MENU_INSERT_LRM);
+	menu_ctl->add_item(RTR("Right-to-Left Mark (RLM)"), MENU_INSERT_RLM);
+	menu_ctl->add_item(RTR("Start of Left-to-Right Embedding (LRE)"), MENU_INSERT_LRE);
+	menu_ctl->add_item(RTR("Start of Right-to-Left Embedding (RLE)"), MENU_INSERT_RLE);
+	menu_ctl->add_item(RTR("Start of Left-to-Right Override (LRO)"), MENU_INSERT_LRO);
+	menu_ctl->add_item(RTR("Start of Right-to-Left Override (RLO)"), MENU_INSERT_RLO);
+	menu_ctl->add_item(RTR("Pop Direction Formatting (PDF)"), MENU_INSERT_PDF);
+	menu_ctl->add_separator();
+	menu_ctl->add_item(RTR("Arabic Letter Mark (ALM)"), MENU_INSERT_ALM);
+	menu_ctl->add_item(RTR("Left-to-Right Isolate (LRI)"), MENU_INSERT_LRI);
+	menu_ctl->add_item(RTR("Right-to-Left Isolate (RLI)"), MENU_INSERT_RLI);
+	menu_ctl->add_item(RTR("First Strong Isolate (FSI)"), MENU_INSERT_FSI);
+	menu_ctl->add_item(RTR("Pop Direction Isolate (PDI)"), MENU_INSERT_PDI);
+	menu_ctl->add_separator();
+	menu_ctl->add_item(RTR("Zero-Width Joiner (ZWJ)"), MENU_INSERT_ZWJ);
+	menu_ctl->add_item(RTR("Zero-Width Non-Joiner (ZWNJ)"), MENU_INSERT_ZWNJ);
+	menu_ctl->add_item(RTR("Word Joiner (WJ)"), MENU_INSERT_WJ);
+	menu_ctl->add_item(RTR("Soft Hyphen (SHY)"), MENU_INSERT_SHY);
+	menu->add_child(menu_ctl, false, INTERNAL_MODE_FRONT);
+
+	menu->add_item(RTR("Cut"), MENU_CUT);
+	menu->add_item(RTR("Copy"), MENU_COPY);
+	menu->add_item(RTR("Paste"), MENU_PASTE);
+	menu->add_separator();
+	menu->add_item(RTR("Select All"), MENU_SELECT_ALL);
+	menu->add_item(RTR("Clear"), MENU_CLEAR);
+	menu->add_separator();
+	menu->add_item(RTR("Undo"), MENU_UNDO);
+	menu->add_item(RTR("Redo"), MENU_REDO);
+	menu->add_separator();
+	menu->add_submenu_item(RTR("Text Writing Direction"), "DirMenu", MENU_SUBMENU_TEXT_DIR);
+	menu->add_separator();
+	menu->add_check_item(RTR("Display Control Characters"), MENU_DISPLAY_UCC);
+	menu->add_submenu_item(RTR("Insert Control Character"), "CTLMenu", MENU_SUBMENU_INSERT_UCC);
+
+	menu->connect("id_pressed", callable_mp(this, &LineEdit::menu_option));
+	menu_dir->connect("id_pressed", callable_mp(this, &LineEdit::menu_option));
+	menu_ctl->connect("id_pressed", callable_mp(this, &LineEdit::menu_option));
+
+	menu->connect(SNAME("focus_entered"), callable_mp(this, &LineEdit::_validate_caret_can_draw));
+	menu->connect(SNAME("focus_exited"), callable_mp(this, &LineEdit::_validate_caret_can_draw));
+}
+
+void LineEdit::_update_context_menu() {
+	if (!menu) {
+		_generate_context_menu();
+	}
+
+	int idx = -1;
+
+#define MENU_ITEM_ACTION_DISABLED(m_menu, m_id, m_action, m_disabled)                                                  \
+	idx = m_menu->get_item_index(m_id);                                                                                \
+	if (idx >= 0) {                                                                                                    \
+		m_menu->set_item_accelerator(idx, shortcut_keys_enabled ? _get_menu_action_accelerator(m_action) : Key::NONE); \
+		m_menu->set_item_disabled(idx, m_disabled);                                                                    \
+	}
+
+#define MENU_ITEM_ACTION(m_menu, m_id, m_action)                                                                       \
+	idx = m_menu->get_item_index(m_id);                                                                                \
+	if (idx >= 0) {                                                                                                    \
+		m_menu->set_item_accelerator(idx, shortcut_keys_enabled ? _get_menu_action_accelerator(m_action) : Key::NONE); \
+	}
+
+#define MENU_ITEM_DISABLED(m_menu, m_id, m_disabled) \
+	idx = m_menu->get_item_index(m_id);              \
+	if (idx >= 0) {                                  \
+		m_menu->set_item_disabled(idx, m_disabled);  \
+	}
+
+#define MENU_ITEM_CHECKED(m_menu, m_id, m_checked) \
+	idx = m_menu->get_item_index(m_id);            \
+	if (idx >= 0) {                                \
+		m_menu->set_item_checked(idx, m_checked);  \
+	}
+
+	MENU_ITEM_ACTION_DISABLED(menu, MENU_CUT, "ui_cut", !editable)
+	MENU_ITEM_ACTION(menu, MENU_COPY, "ui_copy")
+	MENU_ITEM_ACTION_DISABLED(menu, MENU_PASTE, "ui_paste", !editable)
+	MENU_ITEM_ACTION_DISABLED(menu, MENU_SELECT_ALL, "ui_text_select_all", !selecting_enabled)
+	MENU_ITEM_DISABLED(menu, MENU_CLEAR, !editable)
+	MENU_ITEM_ACTION_DISABLED(menu, MENU_UNDO, "ui_undo", !editable || !has_undo())
+	MENU_ITEM_ACTION_DISABLED(menu, MENU_REDO, "ui_redo", !editable || !has_redo())
+	MENU_ITEM_CHECKED(menu_dir, MENU_DIR_INHERITED, text_direction == TEXT_DIRECTION_INHERITED)
+	MENU_ITEM_CHECKED(menu_dir, MENU_DIR_AUTO, text_direction == TEXT_DIRECTION_AUTO)
+	MENU_ITEM_CHECKED(menu_dir, MENU_DIR_LTR, text_direction == TEXT_DIRECTION_LTR)
+	MENU_ITEM_CHECKED(menu_dir, MENU_DIR_RTL, text_direction == TEXT_DIRECTION_RTL)
+	MENU_ITEM_CHECKED(menu, MENU_DISPLAY_UCC, draw_control_chars)
+	MENU_ITEM_DISABLED(menu, MENU_SUBMENU_INSERT_UCC, !editable)
+
+#undef MENU_ITEM_ACTION_DISABLED
+#undef MENU_ITEM_ACTION
+#undef MENU_ITEM_DISABLED
+#undef MENU_ITEM_CHECKED
+}
+
 void LineEdit::_validate_property(PropertyInfo &p_property) const {
 void LineEdit::_validate_property(PropertyInfo &p_property) const {
 	if (!caret_blink_enabled && p_property.name == "caret_blink_interval") {
 	if (!caret_blink_enabled && p_property.name == "caret_blink_interval") {
 		p_property.usage = PROPERTY_USAGE_NO_EDITOR;
 		p_property.usage = PROPERTY_USAGE_NO_EDITOR;
@@ -2429,11 +2540,13 @@ void LineEdit::_bind_methods() {
 	BIND_ENUM_CONSTANT(MENU_SELECT_ALL);
 	BIND_ENUM_CONSTANT(MENU_SELECT_ALL);
 	BIND_ENUM_CONSTANT(MENU_UNDO);
 	BIND_ENUM_CONSTANT(MENU_UNDO);
 	BIND_ENUM_CONSTANT(MENU_REDO);
 	BIND_ENUM_CONSTANT(MENU_REDO);
+	BIND_ENUM_CONSTANT(MENU_SUBMENU_TEXT_DIR);
 	BIND_ENUM_CONSTANT(MENU_DIR_INHERITED);
 	BIND_ENUM_CONSTANT(MENU_DIR_INHERITED);
 	BIND_ENUM_CONSTANT(MENU_DIR_AUTO);
 	BIND_ENUM_CONSTANT(MENU_DIR_AUTO);
 	BIND_ENUM_CONSTANT(MENU_DIR_LTR);
 	BIND_ENUM_CONSTANT(MENU_DIR_LTR);
 	BIND_ENUM_CONSTANT(MENU_DIR_RTL);
 	BIND_ENUM_CONSTANT(MENU_DIR_RTL);
 	BIND_ENUM_CONSTANT(MENU_DISPLAY_UCC);
 	BIND_ENUM_CONSTANT(MENU_DISPLAY_UCC);
+	BIND_ENUM_CONSTANT(MENU_SUBMENU_INSERT_UCC);
 	BIND_ENUM_CONSTANT(MENU_INSERT_LRM);
 	BIND_ENUM_CONSTANT(MENU_INSERT_LRM);
 	BIND_ENUM_CONSTANT(MENU_INSERT_RLM);
 	BIND_ENUM_CONSTANT(MENU_INSERT_RLM);
 	BIND_ENUM_CONSTANT(MENU_INSERT_LRE);
 	BIND_ENUM_CONSTANT(MENU_INSERT_LRE);
@@ -2496,86 +2609,6 @@ void LineEdit::_bind_methods() {
 	ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "structured_text_bidi_override_options"), "set_structured_text_bidi_override_options", "get_structured_text_bidi_override_options");
 	ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "structured_text_bidi_override_options"), "set_structured_text_bidi_override_options", "get_structured_text_bidi_override_options");
 }
 }
 
 
-void LineEdit::_ensure_menu() {
-	if (!menu) {
-		menu = memnew(PopupMenu);
-		add_child(menu, false, INTERNAL_MODE_FRONT);
-
-		menu_dir = memnew(PopupMenu);
-		menu_dir->set_name("DirMenu");
-		menu_dir->add_radio_check_item(RTR("Same as Layout Direction"), MENU_DIR_INHERITED);
-		menu_dir->add_radio_check_item(RTR("Auto-Detect Direction"), MENU_DIR_AUTO);
-		menu_dir->add_radio_check_item(RTR("Left-to-Right"), MENU_DIR_LTR);
-		menu_dir->add_radio_check_item(RTR("Right-to-Left"), MENU_DIR_RTL);
-		menu->add_child(menu_dir, false, INTERNAL_MODE_FRONT);
-
-		menu_ctl = memnew(PopupMenu);
-		menu_ctl->set_name("CTLMenu");
-		menu_ctl->add_item(RTR("Left-to-Right Mark (LRM)"), MENU_INSERT_LRM);
-		menu_ctl->add_item(RTR("Right-to-Left Mark (RLM)"), MENU_INSERT_RLM);
-		menu_ctl->add_item(RTR("Start of Left-to-Right Embedding (LRE)"), MENU_INSERT_LRE);
-		menu_ctl->add_item(RTR("Start of Right-to-Left Embedding (RLE)"), MENU_INSERT_RLE);
-		menu_ctl->add_item(RTR("Start of Left-to-Right Override (LRO)"), MENU_INSERT_LRO);
-		menu_ctl->add_item(RTR("Start of Right-to-Left Override (RLO)"), MENU_INSERT_RLO);
-		menu_ctl->add_item(RTR("Pop Direction Formatting (PDF)"), MENU_INSERT_PDF);
-		menu_ctl->add_separator();
-		menu_ctl->add_item(RTR("Arabic Letter Mark (ALM)"), MENU_INSERT_ALM);
-		menu_ctl->add_item(RTR("Left-to-Right Isolate (LRI)"), MENU_INSERT_LRI);
-		menu_ctl->add_item(RTR("Right-to-Left Isolate (RLI)"), MENU_INSERT_RLI);
-		menu_ctl->add_item(RTR("First Strong Isolate (FSI)"), MENU_INSERT_FSI);
-		menu_ctl->add_item(RTR("Pop Direction Isolate (PDI)"), MENU_INSERT_PDI);
-		menu_ctl->add_separator();
-		menu_ctl->add_item(RTR("Zero-Width Joiner (ZWJ)"), MENU_INSERT_ZWJ);
-		menu_ctl->add_item(RTR("Zero-Width Non-Joiner (ZWNJ)"), MENU_INSERT_ZWNJ);
-		menu_ctl->add_item(RTR("Word Joiner (WJ)"), MENU_INSERT_WJ);
-		menu_ctl->add_item(RTR("Soft Hyphen (SHY)"), MENU_INSERT_SHY);
-		menu->add_child(menu_ctl, false, INTERNAL_MODE_FRONT);
-
-		menu->connect("id_pressed", callable_mp(this, &LineEdit::menu_option));
-		menu->connect(SNAME("focus_entered"), callable_mp(this, &LineEdit::_validate_caret_can_draw));
-		menu->connect(SNAME("focus_exited"), callable_mp(this, &LineEdit::_validate_caret_can_draw));
-		menu_dir->connect("id_pressed", callable_mp(this, &LineEdit::menu_option));
-		menu_ctl->connect("id_pressed", callable_mp(this, &LineEdit::menu_option));
-	}
-
-	// Reorganize context menu.
-	menu->clear();
-	if (editable) {
-		menu->add_item(RTR("Cut"), MENU_CUT, is_shortcut_keys_enabled() ? _get_menu_action_accelerator("ui_cut") : Key::NONE);
-	}
-	menu->add_item(RTR("Copy"), MENU_COPY, is_shortcut_keys_enabled() ? _get_menu_action_accelerator("ui_copy") : Key::NONE);
-	if (editable) {
-		menu->add_item(RTR("Paste"), MENU_PASTE, is_shortcut_keys_enabled() ? _get_menu_action_accelerator("ui_paste") : Key::NONE);
-	}
-	menu->add_separator();
-	if (is_selecting_enabled()) {
-		menu->add_item(RTR("Select All"), MENU_SELECT_ALL, is_shortcut_keys_enabled() ? _get_menu_action_accelerator("ui_text_select_all") : Key::NONE);
-	}
-	if (editable) {
-		menu->add_item(RTR("Clear"), MENU_CLEAR);
-		menu->add_separator();
-		menu->add_item(RTR("Undo"), MENU_UNDO, is_shortcut_keys_enabled() ? _get_menu_action_accelerator("ui_undo") : Key::NONE);
-		menu->add_item(RTR("Redo"), MENU_REDO, is_shortcut_keys_enabled() ? _get_menu_action_accelerator("ui_redo") : Key::NONE);
-	}
-	menu->add_separator();
-	menu->add_submenu_item(RTR("Text Writing Direction"), "DirMenu");
-	menu->add_separator();
-	menu->add_check_item(RTR("Display Control Characters"), MENU_DISPLAY_UCC);
-	menu->set_item_checked(menu->get_item_index(MENU_DISPLAY_UCC), draw_control_chars);
-	if (editable) {
-		menu->add_submenu_item(RTR("Insert Control Character"), "CTLMenu");
-	}
-	menu_dir->set_item_checked(menu_dir->get_item_index(MENU_DIR_INHERITED), text_direction == TEXT_DIRECTION_INHERITED);
-	menu_dir->set_item_checked(menu_dir->get_item_index(MENU_DIR_AUTO), text_direction == TEXT_DIRECTION_AUTO);
-	menu_dir->set_item_checked(menu_dir->get_item_index(MENU_DIR_LTR), text_direction == TEXT_DIRECTION_LTR);
-	menu_dir->set_item_checked(menu_dir->get_item_index(MENU_DIR_RTL), text_direction == TEXT_DIRECTION_RTL);
-
-	if (editable) {
-		menu->set_item_disabled(menu->get_item_index(MENU_UNDO), !has_undo());
-		menu->set_item_disabled(menu->get_item_index(MENU_REDO), !has_redo());
-	}
-}
-
 LineEdit::LineEdit(const String &p_placeholder) {
 LineEdit::LineEdit(const String &p_placeholder) {
 	text_rid = TS->create_shaped_text();
 	text_rid = TS->create_shaped_text();
 	_create_undo_state();
 	_create_undo_state();

+ 4 - 2
scene/gui/line_edit.h

@@ -46,11 +46,13 @@ public:
 		MENU_SELECT_ALL,
 		MENU_SELECT_ALL,
 		MENU_UNDO,
 		MENU_UNDO,
 		MENU_REDO,
 		MENU_REDO,
+		MENU_SUBMENU_TEXT_DIR,
 		MENU_DIR_INHERITED,
 		MENU_DIR_INHERITED,
 		MENU_DIR_AUTO,
 		MENU_DIR_AUTO,
 		MENU_DIR_LTR,
 		MENU_DIR_LTR,
 		MENU_DIR_RTL,
 		MENU_DIR_RTL,
 		MENU_DISPLAY_UCC,
 		MENU_DISPLAY_UCC,
+		MENU_SUBMENU_INSERT_UCC,
 		MENU_INSERT_LRM,
 		MENU_INSERT_LRM,
 		MENU_INSERT_RLM,
 		MENU_INSERT_RLM,
 		MENU_INSERT_LRE,
 		MENU_INSERT_LRE,
@@ -207,6 +209,8 @@ private:
 	void _create_undo_state();
 	void _create_undo_state();
 
 
 	Key _get_menu_action_accelerator(const String &p_action);
 	Key _get_menu_action_accelerator(const String &p_action);
+	void _generate_context_menu();
+	void _update_context_menu();
 
 
 	void _shape();
 	void _shape();
 	void _fit_to_width();
 	void _fit_to_width();
@@ -239,8 +243,6 @@ private:
 	void _backspace(bool p_word = false, bool p_all_to_left = false);
 	void _backspace(bool p_word = false, bool p_all_to_left = false);
 	void _delete(bool p_word = false, bool p_all_to_right = false);
 	void _delete(bool p_word = false, bool p_all_to_right = false);
 
 
-	void _ensure_menu();
-
 protected:
 protected:
 	bool _is_over_clear_button(const Point2 &p_pos) const;
 	bool _is_over_clear_button(const Point2 &p_pos) const;
 	virtual void _update_theme_item_cache() override;
 	virtual void _update_theme_item_cache() override;

+ 113 - 83
scene/gui/text_edit.cpp

@@ -1861,7 +1861,7 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) {
 				}
 				}
 
 
 				if (context_menu_enabled) {
 				if (context_menu_enabled) {
-					_generate_context_menu();
+					_update_context_menu();
 					menu->set_position(get_screen_position() + mpos);
 					menu->set_position(get_screen_position() + mpos);
 					menu->reset_size();
 					menu->reset_size();
 					menu->popup();
 					menu->popup();
@@ -2141,7 +2141,7 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) {
 		// MISC.
 		// MISC.
 		if (k->is_action("ui_menu", true)) {
 		if (k->is_action("ui_menu", true)) {
 			if (context_menu_enabled) {
 			if (context_menu_enabled) {
-				_generate_context_menu();
+				_update_context_menu();
 				adjust_viewport_to_caret();
 				adjust_viewport_to_caret();
 				menu->set_position(get_screen_position() + get_caret_draw_pos());
 				menu->set_position(get_screen_position() + get_caret_draw_pos());
 				menu->reset_size();
 				menu->reset_size();
@@ -3726,7 +3726,9 @@ void TextEdit::paste_primary_clipboard(int p_caret) {
 
 
 // Context menu.
 // Context menu.
 PopupMenu *TextEdit::get_menu() const {
 PopupMenu *TextEdit::get_menu() const {
-	const_cast<TextEdit *>(this)->_generate_context_menu();
+	if (!menu) {
+		const_cast<TextEdit *>(this)->_generate_context_menu();
+	}
 	return menu;
 	return menu;
 }
 }
 
 
@@ -6075,11 +6077,13 @@ void TextEdit::_bind_methods() {
 	BIND_ENUM_CONSTANT(MENU_SELECT_ALL);
 	BIND_ENUM_CONSTANT(MENU_SELECT_ALL);
 	BIND_ENUM_CONSTANT(MENU_UNDO);
 	BIND_ENUM_CONSTANT(MENU_UNDO);
 	BIND_ENUM_CONSTANT(MENU_REDO);
 	BIND_ENUM_CONSTANT(MENU_REDO);
+	BIND_ENUM_CONSTANT(MENU_SUBMENU_TEXT_DIR);
 	BIND_ENUM_CONSTANT(MENU_DIR_INHERITED);
 	BIND_ENUM_CONSTANT(MENU_DIR_INHERITED);
 	BIND_ENUM_CONSTANT(MENU_DIR_AUTO);
 	BIND_ENUM_CONSTANT(MENU_DIR_AUTO);
 	BIND_ENUM_CONSTANT(MENU_DIR_LTR);
 	BIND_ENUM_CONSTANT(MENU_DIR_LTR);
 	BIND_ENUM_CONSTANT(MENU_DIR_RTL);
 	BIND_ENUM_CONSTANT(MENU_DIR_RTL);
 	BIND_ENUM_CONSTANT(MENU_DISPLAY_UCC);
 	BIND_ENUM_CONSTANT(MENU_DISPLAY_UCC);
+	BIND_ENUM_CONSTANT(MENU_SUBMENU_INSERT_UCC);
 	BIND_ENUM_CONSTANT(MENU_INSERT_LRM);
 	BIND_ENUM_CONSTANT(MENU_INSERT_LRM);
 	BIND_ENUM_CONSTANT(MENU_INSERT_RLM);
 	BIND_ENUM_CONSTANT(MENU_INSERT_RLM);
 	BIND_ENUM_CONSTANT(MENU_INSERT_LRE);
 	BIND_ENUM_CONSTANT(MENU_INSERT_LRE);
@@ -6730,87 +6734,7 @@ void TextEdit::_paste_primary_clipboard_internal(int p_caret) {
 	grab_focus();
 	grab_focus();
 }
 }
 
 
-/* Text. */
 // Context menu.
 // Context menu.
-void TextEdit::_generate_context_menu() {
-	if (!menu) {
-		menu = memnew(PopupMenu);
-		add_child(menu, false, INTERNAL_MODE_FRONT);
-
-		menu_dir = memnew(PopupMenu);
-		menu_dir->set_name("DirMenu");
-		menu_dir->add_radio_check_item(RTR("Same as Layout Direction"), MENU_DIR_INHERITED);
-		menu_dir->add_radio_check_item(RTR("Auto-Detect Direction"), MENU_DIR_AUTO);
-		menu_dir->add_radio_check_item(RTR("Left-to-Right"), MENU_DIR_LTR);
-		menu_dir->add_radio_check_item(RTR("Right-to-Left"), MENU_DIR_RTL);
-		menu->add_child(menu_dir, false, INTERNAL_MODE_FRONT);
-
-		menu_ctl = memnew(PopupMenu);
-		menu_ctl->set_name("CTLMenu");
-		menu_ctl->add_item(RTR("Left-to-Right Mark (LRM)"), MENU_INSERT_LRM);
-		menu_ctl->add_item(RTR("Right-to-Left Mark (RLM)"), MENU_INSERT_RLM);
-		menu_ctl->add_item(RTR("Start of Left-to-Right Embedding (LRE)"), MENU_INSERT_LRE);
-		menu_ctl->add_item(RTR("Start of Right-to-Left Embedding (RLE)"), MENU_INSERT_RLE);
-		menu_ctl->add_item(RTR("Start of Left-to-Right Override (LRO)"), MENU_INSERT_LRO);
-		menu_ctl->add_item(RTR("Start of Right-to-Left Override (RLO)"), MENU_INSERT_RLO);
-		menu_ctl->add_item(RTR("Pop Direction Formatting (PDF)"), MENU_INSERT_PDF);
-		menu_ctl->add_separator();
-		menu_ctl->add_item(RTR("Arabic Letter Mark (ALM)"), MENU_INSERT_ALM);
-		menu_ctl->add_item(RTR("Left-to-Right Isolate (LRI)"), MENU_INSERT_LRI);
-		menu_ctl->add_item(RTR("Right-to-Left Isolate (RLI)"), MENU_INSERT_RLI);
-		menu_ctl->add_item(RTR("First Strong Isolate (FSI)"), MENU_INSERT_FSI);
-		menu_ctl->add_item(RTR("Pop Direction Isolate (PDI)"), MENU_INSERT_PDI);
-		menu_ctl->add_separator();
-		menu_ctl->add_item(RTR("Zero-Width Joiner (ZWJ)"), MENU_INSERT_ZWJ);
-		menu_ctl->add_item(RTR("Zero-Width Non-Joiner (ZWNJ)"), MENU_INSERT_ZWNJ);
-		menu_ctl->add_item(RTR("Word Joiner (WJ)"), MENU_INSERT_WJ);
-		menu_ctl->add_item(RTR("Soft Hyphen (SHY)"), MENU_INSERT_SHY);
-		menu->add_child(menu_ctl, false, INTERNAL_MODE_FRONT);
-
-		menu->connect("id_pressed", callable_mp(this, &TextEdit::menu_option));
-		menu_dir->connect("id_pressed", callable_mp(this, &TextEdit::menu_option));
-		menu_ctl->connect("id_pressed", callable_mp(this, &TextEdit::menu_option));
-	}
-
-	// Reorganize context menu.
-	menu->clear();
-	if (editable) {
-		menu->add_item(RTR("Cut"), MENU_CUT, is_shortcut_keys_enabled() ? _get_menu_action_accelerator("ui_cut") : Key::NONE);
-	}
-	menu->add_item(RTR("Copy"), MENU_COPY, is_shortcut_keys_enabled() ? _get_menu_action_accelerator("ui_copy") : Key::NONE);
-	if (editable) {
-		menu->add_item(RTR("Paste"), MENU_PASTE, is_shortcut_keys_enabled() ? _get_menu_action_accelerator("ui_paste") : Key::NONE);
-	}
-	if (selecting_enabled || editable) {
-		menu->add_separator();
-	}
-	if (selecting_enabled) {
-		menu->add_item(RTR("Select All"), MENU_SELECT_ALL, is_shortcut_keys_enabled() ? _get_menu_action_accelerator("ui_text_select_all") : Key::NONE);
-	}
-	if (editable) {
-		menu->add_item(RTR("Clear"), MENU_CLEAR);
-		menu->add_separator();
-		menu->add_item(RTR("Undo"), MENU_UNDO, is_shortcut_keys_enabled() ? _get_menu_action_accelerator("ui_undo") : Key::NONE);
-		menu->add_item(RTR("Redo"), MENU_REDO, is_shortcut_keys_enabled() ? _get_menu_action_accelerator("ui_redo") : Key::NONE);
-	}
-	menu->add_separator();
-	menu->add_submenu_item(RTR("Text Writing Direction"), "DirMenu");
-	menu->add_separator();
-	menu->add_check_item(RTR("Display Control Characters"), MENU_DISPLAY_UCC);
-	menu->set_item_checked(menu->get_item_index(MENU_DISPLAY_UCC), draw_control_chars);
-	if (editable) {
-		menu->add_submenu_item(RTR("Insert Control Character"), "CTLMenu");
-	}
-	menu_dir->set_item_checked(menu_dir->get_item_index(MENU_DIR_INHERITED), text_direction == TEXT_DIRECTION_INHERITED);
-	menu_dir->set_item_checked(menu_dir->get_item_index(MENU_DIR_AUTO), text_direction == TEXT_DIRECTION_AUTO);
-	menu_dir->set_item_checked(menu_dir->get_item_index(MENU_DIR_LTR), text_direction == TEXT_DIRECTION_LTR);
-	menu_dir->set_item_checked(menu_dir->get_item_index(MENU_DIR_RTL), text_direction == TEXT_DIRECTION_RTL);
-
-	if (editable) {
-		menu->set_item_disabled(menu->get_item_index(MENU_UNDO), !has_undo());
-		menu->set_item_disabled(menu->get_item_index(MENU_REDO), !has_redo());
-	}
-}
 
 
 Key TextEdit::_get_menu_action_accelerator(const String &p_action) {
 Key TextEdit::_get_menu_action_accelerator(const String &p_action) {
 	const List<Ref<InputEvent>> *events = InputMap::get_singleton()->action_get_events(p_action);
 	const List<Ref<InputEvent>> *events = InputMap::get_singleton()->action_get_events(p_action);
@@ -6837,6 +6761,112 @@ Key TextEdit::_get_menu_action_accelerator(const String &p_action) {
 	}
 	}
 }
 }
 
 
+void TextEdit::_generate_context_menu() {
+	menu = memnew(PopupMenu);
+	add_child(menu, false, INTERNAL_MODE_FRONT);
+
+	menu_dir = memnew(PopupMenu);
+	menu_dir->set_name("DirMenu");
+	menu_dir->add_radio_check_item(RTR("Same as Layout Direction"), MENU_DIR_INHERITED);
+	menu_dir->add_radio_check_item(RTR("Auto-Detect Direction"), MENU_DIR_AUTO);
+	menu_dir->add_radio_check_item(RTR("Left-to-Right"), MENU_DIR_LTR);
+	menu_dir->add_radio_check_item(RTR("Right-to-Left"), MENU_DIR_RTL);
+	menu->add_child(menu_dir, false, INTERNAL_MODE_FRONT);
+
+	menu_ctl = memnew(PopupMenu);
+	menu_ctl->set_name("CTLMenu");
+	menu_ctl->add_item(RTR("Left-to-Right Mark (LRM)"), MENU_INSERT_LRM);
+	menu_ctl->add_item(RTR("Right-to-Left Mark (RLM)"), MENU_INSERT_RLM);
+	menu_ctl->add_item(RTR("Start of Left-to-Right Embedding (LRE)"), MENU_INSERT_LRE);
+	menu_ctl->add_item(RTR("Start of Right-to-Left Embedding (RLE)"), MENU_INSERT_RLE);
+	menu_ctl->add_item(RTR("Start of Left-to-Right Override (LRO)"), MENU_INSERT_LRO);
+	menu_ctl->add_item(RTR("Start of Right-to-Left Override (RLO)"), MENU_INSERT_RLO);
+	menu_ctl->add_item(RTR("Pop Direction Formatting (PDF)"), MENU_INSERT_PDF);
+	menu_ctl->add_separator();
+	menu_ctl->add_item(RTR("Arabic Letter Mark (ALM)"), MENU_INSERT_ALM);
+	menu_ctl->add_item(RTR("Left-to-Right Isolate (LRI)"), MENU_INSERT_LRI);
+	menu_ctl->add_item(RTR("Right-to-Left Isolate (RLI)"), MENU_INSERT_RLI);
+	menu_ctl->add_item(RTR("First Strong Isolate (FSI)"), MENU_INSERT_FSI);
+	menu_ctl->add_item(RTR("Pop Direction Isolate (PDI)"), MENU_INSERT_PDI);
+	menu_ctl->add_separator();
+	menu_ctl->add_item(RTR("Zero-Width Joiner (ZWJ)"), MENU_INSERT_ZWJ);
+	menu_ctl->add_item(RTR("Zero-Width Non-Joiner (ZWNJ)"), MENU_INSERT_ZWNJ);
+	menu_ctl->add_item(RTR("Word Joiner (WJ)"), MENU_INSERT_WJ);
+	menu_ctl->add_item(RTR("Soft Hyphen (SHY)"), MENU_INSERT_SHY);
+	menu->add_child(menu_ctl, false, INTERNAL_MODE_FRONT);
+
+	menu->add_item(RTR("Cut"), MENU_CUT);
+	menu->add_item(RTR("Copy"), MENU_COPY);
+	menu->add_item(RTR("Paste"), MENU_PASTE);
+	menu->add_separator();
+	menu->add_item(RTR("Select All"), MENU_SELECT_ALL);
+	menu->add_item(RTR("Clear"), MENU_CLEAR);
+	menu->add_separator();
+	menu->add_item(RTR("Undo"), MENU_UNDO);
+	menu->add_item(RTR("Redo"), MENU_REDO);
+	menu->add_separator();
+	menu->add_submenu_item(RTR("Text Writing Direction"), "DirMenu", MENU_SUBMENU_TEXT_DIR);
+	menu->add_separator();
+	menu->add_check_item(RTR("Display Control Characters"), MENU_DISPLAY_UCC);
+	menu->add_submenu_item(RTR("Insert Control Character"), "CTLMenu", MENU_SUBMENU_INSERT_UCC);
+
+	menu->connect("id_pressed", callable_mp(this, &TextEdit::menu_option));
+	menu_dir->connect("id_pressed", callable_mp(this, &TextEdit::menu_option));
+	menu_ctl->connect("id_pressed", callable_mp(this, &TextEdit::menu_option));
+}
+
+void TextEdit::_update_context_menu() {
+	if (!menu) {
+		_generate_context_menu();
+	}
+
+	int idx = -1;
+
+#define MENU_ITEM_ACTION_DISABLED(m_menu, m_id, m_action, m_disabled)                                                  \
+	idx = m_menu->get_item_index(m_id);                                                                                \
+	if (idx >= 0) {                                                                                                    \
+		m_menu->set_item_accelerator(idx, shortcut_keys_enabled ? _get_menu_action_accelerator(m_action) : Key::NONE); \
+		m_menu->set_item_disabled(idx, m_disabled);                                                                    \
+	}
+
+#define MENU_ITEM_ACTION(m_menu, m_id, m_action)                                                                       \
+	idx = m_menu->get_item_index(m_id);                                                                                \
+	if (idx >= 0) {                                                                                                    \
+		m_menu->set_item_accelerator(idx, shortcut_keys_enabled ? _get_menu_action_accelerator(m_action) : Key::NONE); \
+	}
+
+#define MENU_ITEM_DISABLED(m_menu, m_id, m_disabled) \
+	idx = m_menu->get_item_index(m_id);              \
+	if (idx >= 0) {                                  \
+		m_menu->set_item_disabled(idx, m_disabled);  \
+	}
+
+#define MENU_ITEM_CHECKED(m_menu, m_id, m_checked) \
+	idx = m_menu->get_item_index(m_id);            \
+	if (idx >= 0) {                                \
+		m_menu->set_item_checked(idx, m_checked);  \
+	}
+
+	MENU_ITEM_ACTION_DISABLED(menu, MENU_CUT, "ui_cut", !editable)
+	MENU_ITEM_ACTION(menu, MENU_COPY, "ui_copy")
+	MENU_ITEM_ACTION_DISABLED(menu, MENU_PASTE, "ui_paste", !editable)
+	MENU_ITEM_ACTION_DISABLED(menu, MENU_SELECT_ALL, "ui_text_select_all", !selecting_enabled)
+	MENU_ITEM_DISABLED(menu, MENU_CLEAR, !editable)
+	MENU_ITEM_ACTION_DISABLED(menu, MENU_UNDO, "ui_undo", !editable || !has_undo())
+	MENU_ITEM_ACTION_DISABLED(menu, MENU_REDO, "ui_redo", !editable || !has_redo())
+	MENU_ITEM_CHECKED(menu_dir, MENU_DIR_INHERITED, text_direction == TEXT_DIRECTION_INHERITED)
+	MENU_ITEM_CHECKED(menu_dir, MENU_DIR_AUTO, text_direction == TEXT_DIRECTION_AUTO)
+	MENU_ITEM_CHECKED(menu_dir, MENU_DIR_LTR, text_direction == TEXT_DIRECTION_LTR)
+	MENU_ITEM_CHECKED(menu_dir, MENU_DIR_RTL, text_direction == TEXT_DIRECTION_RTL)
+	MENU_ITEM_CHECKED(menu, MENU_DISPLAY_UCC, draw_control_chars)
+	MENU_ITEM_DISABLED(menu, MENU_SUBMENU_INSERT_UCC, !editable)
+
+#undef MENU_ITEM_ACTION_DISABLED
+#undef MENU_ITEM_ACTION
+#undef MENU_ITEM_DISABLED
+#undef MENU_ITEM_CHECKED
+}
+
 /* Versioning */
 /* Versioning */
 void TextEdit::_push_current_op() {
 void TextEdit::_push_current_op() {
 	if (pending_action_end) {
 	if (pending_action_end) {

+ 4 - 1
scene/gui/text_edit.h

@@ -87,11 +87,13 @@ public:
 		MENU_SELECT_ALL,
 		MENU_SELECT_ALL,
 		MENU_UNDO,
 		MENU_UNDO,
 		MENU_REDO,
 		MENU_REDO,
+		MENU_SUBMENU_TEXT_DIR,
 		MENU_DIR_INHERITED,
 		MENU_DIR_INHERITED,
 		MENU_DIR_AUTO,
 		MENU_DIR_AUTO,
 		MENU_DIR_LTR,
 		MENU_DIR_LTR,
 		MENU_DIR_RTL,
 		MENU_DIR_RTL,
 		MENU_DISPLAY_UCC,
 		MENU_DISPLAY_UCC,
+		MENU_SUBMENU_INSERT_UCC,
 		MENU_INSERT_LRM,
 		MENU_INSERT_LRM,
 		MENU_INSERT_RLM,
 		MENU_INSERT_RLM,
 		MENU_INSERT_LRE,
 		MENU_INSERT_LRE,
@@ -303,8 +305,9 @@ private:
 	PopupMenu *menu_dir = nullptr;
 	PopupMenu *menu_dir = nullptr;
 	PopupMenu *menu_ctl = nullptr;
 	PopupMenu *menu_ctl = nullptr;
 
 
-	void _generate_context_menu();
 	Key _get_menu_action_accelerator(const String &p_action);
 	Key _get_menu_action_accelerator(const String &p_action);
+	void _generate_context_menu();
+	void _update_context_menu();
 
 
 	/* Versioning */
 	/* Versioning */
 	struct Caret;
 	struct Caret;