Browse Source

Editor: Inspector and Signal docks improvements

Danil Alexeev 2 years ago
parent
commit
c33ca26e17

+ 1 - 1
core/object/object.cpp

@@ -485,7 +485,7 @@ void Object::get_property_list(List<PropertyInfo> *p_list, bool p_reversed) cons
 	if (_extension) {
 	if (_extension) {
 		const ObjectGDExtension *current_extension = _extension;
 		const ObjectGDExtension *current_extension = _extension;
 		while (current_extension) {
 		while (current_extension) {
-			p_list->push_back(PropertyInfo(Variant::NIL, current_extension->class_name, PROPERTY_HINT_NONE, String(), PROPERTY_USAGE_CATEGORY));
+			p_list->push_back(PropertyInfo(Variant::NIL, current_extension->class_name, PROPERTY_HINT_NONE, current_extension->class_name, PROPERTY_USAGE_CATEGORY));
 
 
 			ClassDB::get_property_list(current_extension->class_name, p_list, true, this);
 			ClassDB::get_property_list(current_extension->class_name, p_list, true, this);
 
 

+ 1 - 1
core/object/object.h

@@ -489,7 +489,7 @@ protected:
 		if (!p_reversed) {                                                                                                                       \
 		if (!p_reversed) {                                                                                                                       \
 			m_inherits::_get_property_listv(p_list, p_reversed);                                                                                 \
 			m_inherits::_get_property_listv(p_list, p_reversed);                                                                                 \
 		}                                                                                                                                        \
 		}                                                                                                                                        \
-		p_list->push_back(PropertyInfo(Variant::NIL, get_class_static(), PROPERTY_HINT_NONE, String(), PROPERTY_USAGE_CATEGORY));                \
+		p_list->push_back(PropertyInfo(Variant::NIL, get_class_static(), PROPERTY_HINT_NONE, get_class_static(), PROPERTY_USAGE_CATEGORY));      \
 		if (!_is_gpl_reversed()) {                                                                                                               \
 		if (!_is_gpl_reversed()) {                                                                                                               \
 			::ClassDB::get_property_list(#m_class, p_list, true, this);                                                                          \
 			::ClassDB::get_property_list(#m_class, p_list, true, this);                                                                          \
 		}                                                                                                                                        \
 		}                                                                                                                                        \

+ 145 - 95
editor/connections_dialog.cpp

@@ -811,26 +811,30 @@ ConnectDialog::~ConnectDialog() {
 
 
 // Originally copied and adapted from EditorProperty, try to keep style in sync.
 // Originally copied and adapted from EditorProperty, try to keep style in sync.
 Control *ConnectionsDockTree::make_custom_tooltip(const String &p_text) const {
 Control *ConnectionsDockTree::make_custom_tooltip(const String &p_text) const {
-	EditorHelpBit *help_bit = memnew(EditorHelpBit);
-	help_bit->get_rich_text()->set_custom_minimum_size(Size2(360 * EDSCALE, 1));
-
-	// p_text is expected to be something like this:
-	// "gui_input::(event: InputEvent)::<Signal description>"
-	// with the latter being possibly empty.
-	PackedStringArray slices = p_text.split("::", false);
-	if (slices.size() < 2) {
-		// Shouldn't happen here, but just in case pass the text along.
-		help_bit->set_text(p_text);
-		return help_bit;
-	}
-
-	String text = TTR("Signal:") + " [u][b]" + slices[0] + "[/b][/u]";
-	text += slices[1].strip_edges() + "\n";
-	if (slices.size() > 2) {
-		text += slices[2].strip_edges();
-	} else {
+	// `p_text` is expected to be something like this:
+	// - `class|Control||Control brief description.`;
+	// - `signal|gui_input|(event: InputEvent)|gui_input description.`;
+	// - `../../.. :: _on_gui_input()`.
+	// Note that the description can be empty or contain `|`.
+	PackedStringArray slices = p_text.split("|", true, 3);
+	if (slices.size() < 4) {
+		return nullptr; // Use default tooltip instead.
+	}
+
+	String item_type = (slices[0] == "class") ? TTR("Class:") : TTR("Signal:");
+	String item_name = slices[1].strip_edges();
+	String item_params = slices[2].strip_edges();
+	String item_descr = slices[3].strip_edges();
+
+	String text = item_type + " [u][b]" + item_name + "[/b][/u]" + item_params + "\n";
+	if (item_descr.is_empty()) {
 		text += "[i]" + TTR("No description.") + "[/i]";
 		text += "[i]" + TTR("No description.") + "[/i]";
+	} else {
+		text += item_descr;
 	}
 	}
+
+	EditorHelpBit *help_bit = memnew(EditorHelpBit);
+	help_bit->get_rich_text()->set_custom_minimum_size(Size2(360 * EDSCALE, 1));
 	help_bit->set_text(text);
 	help_bit->set_text(text);
 
 
 	return help_bit;
 	return help_bit;
@@ -960,8 +964,7 @@ void ConnectionsDock::_disconnect(const ConnectDialog::ConnectionData &p_cd) {
  */
  */
 void ConnectionsDock::_disconnect_all() {
 void ConnectionsDock::_disconnect_all() {
 	TreeItem *item = tree->get_selected();
 	TreeItem *item = tree->get_selected();
-
-	if (!_is_item_signal(*item)) {
+	if (!item || _get_item_type(*item) != TREE_ITEM_TYPE_SIGNAL) {
 		return;
 		return;
 	}
 	}
 
 
@@ -990,38 +993,44 @@ void ConnectionsDock::_disconnect_all() {
 
 
 void ConnectionsDock::_tree_item_selected() {
 void ConnectionsDock::_tree_item_selected() {
 	TreeItem *item = tree->get_selected();
 	TreeItem *item = tree->get_selected();
-	if (!item) { // Unlikely. Disable button just in case.
-		connect_button->set_text(TTR("Connect..."));
-		connect_button->set_icon(get_theme_icon(SNAME("Instance"), SNAME("EditorIcons")));
-		connect_button->set_disabled(true);
-	} else if (_is_item_signal(*item)) {
+	if (item && _get_item_type(*item) == TREE_ITEM_TYPE_SIGNAL) {
 		connect_button->set_text(TTR("Connect..."));
 		connect_button->set_text(TTR("Connect..."));
 		connect_button->set_icon(get_theme_icon(SNAME("Instance"), SNAME("EditorIcons")));
 		connect_button->set_icon(get_theme_icon(SNAME("Instance"), SNAME("EditorIcons")));
 		connect_button->set_disabled(false);
 		connect_button->set_disabled(false);
-	} else {
+	} else if (item && _get_item_type(*item) == TREE_ITEM_TYPE_CONNECTION) {
 		connect_button->set_text(TTR("Disconnect"));
 		connect_button->set_text(TTR("Disconnect"));
 		connect_button->set_icon(get_theme_icon(SNAME("Unlinked"), SNAME("EditorIcons")));
 		connect_button->set_icon(get_theme_icon(SNAME("Unlinked"), SNAME("EditorIcons")));
 		connect_button->set_disabled(false);
 		connect_button->set_disabled(false);
+	} else {
+		connect_button->set_text(TTR("Connect..."));
+		connect_button->set_icon(get_theme_icon(SNAME("Instance"), SNAME("EditorIcons")));
+		connect_button->set_disabled(true);
 	}
 	}
 }
 }
 
 
 void ConnectionsDock::_tree_item_activated() { // "Activation" on double-click.
 void ConnectionsDock::_tree_item_activated() { // "Activation" on double-click.
-
 	TreeItem *item = tree->get_selected();
 	TreeItem *item = tree->get_selected();
-
 	if (!item) {
 	if (!item) {
 		return;
 		return;
 	}
 	}
 
 
-	if (_is_item_signal(*item)) {
+	if (_get_item_type(*item) == TREE_ITEM_TYPE_SIGNAL) {
 		_open_connection_dialog(*item);
 		_open_connection_dialog(*item);
-	} else {
-		_go_to_script(*item);
+	} else if (_get_item_type(*item) == TREE_ITEM_TYPE_CONNECTION) {
+		_go_to_method(*item);
 	}
 	}
 }
 }
 
 
-bool ConnectionsDock::_is_item_signal(TreeItem &p_item) {
-	return (p_item.get_parent() == tree->get_root() || p_item.get_parent()->get_parent() == tree->get_root());
+ConnectionsDock::TreeItemType ConnectionsDock::_get_item_type(const TreeItem &p_item) const {
+	if (&p_item == tree->get_root()) {
+		return TREE_ITEM_TYPE_ROOT;
+	} else if (p_item.get_parent() == tree->get_root()) {
+		return TREE_ITEM_TYPE_CLASS;
+	} else if (p_item.get_parent()->get_parent() == tree->get_root()) {
+		return TREE_ITEM_TYPE_SIGNAL;
+	} else {
+		return TREE_ITEM_TYPE_CONNECTION;
+	}
 }
 }
 
 
 bool ConnectionsDock::_is_connection_inherited(Connection &p_connection) {
 bool ConnectionsDock::_is_connection_inherited(Connection &p_connection) {
@@ -1077,8 +1086,8 @@ void ConnectionsDock::_open_edit_connection_dialog(TreeItem &p_item) {
 /*
 /*
  * Open slot method location in script editor.
  * Open slot method location in script editor.
  */
  */
-void ConnectionsDock::_go_to_script(TreeItem &p_item) {
-	if (_is_item_signal(p_item)) {
+void ConnectionsDock::_go_to_method(TreeItem &p_item) {
+	if (_get_item_type(p_item) != TREE_ITEM_TYPE_CONNECTION) {
 		return;
 		return;
 	}
 	}
 
 
@@ -1101,27 +1110,39 @@ void ConnectionsDock::_go_to_script(TreeItem &p_item) {
 	}
 	}
 }
 }
 
 
+void ConnectionsDock::_handle_class_menu_option(int p_option) {
+	switch (p_option) {
+		case CLASS_MENU_OPEN_DOCS:
+			ScriptEditor::get_singleton()->goto_help("class:" + class_menu_doc_class_name);
+			EditorNode::get_singleton()->set_visible_editor(EditorNode::EDITOR_SCRIPT);
+			break;
+	}
+}
+
+void ConnectionsDock::_class_menu_about_to_popup() {
+	class_menu->set_item_disabled(class_menu->get_item_index(CLASS_MENU_OPEN_DOCS), class_menu_doc_class_name.is_empty());
+}
+
 void ConnectionsDock::_handle_signal_menu_option(int p_option) {
 void ConnectionsDock::_handle_signal_menu_option(int p_option) {
 	TreeItem *item = tree->get_selected();
 	TreeItem *item = tree->get_selected();
-
-	if (!item) {
+	if (!item || _get_item_type(*item) != TREE_ITEM_TYPE_SIGNAL) {
 		return;
 		return;
 	}
 	}
 
 
 	Dictionary meta = item->get_metadata(0);
 	Dictionary meta = item->get_metadata(0);
 
 
 	switch (p_option) {
 	switch (p_option) {
-		case CONNECT: {
+		case SIGNAL_MENU_CONNECT: {
 			_open_connection_dialog(*item);
 			_open_connection_dialog(*item);
 		} break;
 		} break;
-		case DISCONNECT_ALL: {
+		case SIGNAL_MENU_DISCONNECT_ALL: {
 			disconnect_all_dialog->set_text(vformat(TTR("Are you sure you want to remove all connections from the \"%s\" signal?"), meta["name"]));
 			disconnect_all_dialog->set_text(vformat(TTR("Are you sure you want to remove all connections from the \"%s\" signal?"), meta["name"]));
 			disconnect_all_dialog->popup_centered();
 			disconnect_all_dialog->popup_centered();
 		} break;
 		} break;
-		case COPY_NAME: {
+		case SIGNAL_MENU_COPY_NAME: {
 			DisplayServer::get_singleton()->clipboard_set(meta["name"]);
 			DisplayServer::get_singleton()->clipboard_set(meta["name"]);
 		} break;
 		} break;
-		case OPEN_DOCUMENTATION: {
+		case SIGNAL_MENU_OPEN_DOCS: {
 			ScriptEditor::get_singleton()->goto_help("class_signal:" + String(meta["class"]) + ":" + String(meta["name"]));
 			ScriptEditor::get_singleton()->goto_help("class_signal:" + String(meta["class"]) + ":" + String(meta["name"]));
 			EditorNode::get_singleton()->set_visible_editor(EditorNode::EDITOR_SCRIPT);
 			EditorNode::get_singleton()->set_visible_editor(EditorNode::EDITOR_SCRIPT);
 		} break;
 		} break;
@@ -1130,8 +1151,7 @@ void ConnectionsDock::_handle_signal_menu_option(int p_option) {
 
 
 void ConnectionsDock::_signal_menu_about_to_popup() {
 void ConnectionsDock::_signal_menu_about_to_popup() {
 	TreeItem *item = tree->get_selected();
 	TreeItem *item = tree->get_selected();
-
-	if (!item) {
+	if (!item || _get_item_type(*item) != TREE_ITEM_TYPE_SIGNAL) {
 		return;
 		return;
 	}
 	}
 
 
@@ -1144,25 +1164,24 @@ void ConnectionsDock::_signal_menu_about_to_popup() {
 		}
 		}
 	}
 	}
 
 
-	signal_menu->set_item_disabled(signal_menu->get_item_index(DISCONNECT_ALL), disable_disconnect_all);
-	signal_menu->set_item_disabled(signal_menu->get_item_index(OPEN_DOCUMENTATION), String(meta["class"]).is_empty());
+	signal_menu->set_item_disabled(signal_menu->get_item_index(SIGNAL_MENU_DISCONNECT_ALL), disable_disconnect_all);
+	signal_menu->set_item_disabled(signal_menu->get_item_index(SIGNAL_MENU_OPEN_DOCS), String(meta["class"]).is_empty());
 }
 }
 
 
 void ConnectionsDock::_handle_slot_menu_option(int p_option) {
 void ConnectionsDock::_handle_slot_menu_option(int p_option) {
 	TreeItem *item = tree->get_selected();
 	TreeItem *item = tree->get_selected();
-
-	if (!item) {
+	if (!item || _get_item_type(*item) != TREE_ITEM_TYPE_CONNECTION) {
 		return;
 		return;
 	}
 	}
 
 
 	switch (p_option) {
 	switch (p_option) {
-		case EDIT: {
+		case SLOT_MENU_EDIT: {
 			_open_edit_connection_dialog(*item);
 			_open_edit_connection_dialog(*item);
 		} break;
 		} break;
-		case GO_TO_SCRIPT: {
-			_go_to_script(*item);
+		case SLOT_MENU_GO_TO_METHOD: {
+			_go_to_method(*item);
 		} break;
 		} break;
-		case DISCONNECT: {
+		case SLOT_MENU_DISCONNECT: {
 			Connection connection = item->get_metadata(0);
 			Connection connection = item->get_metadata(0);
 			_disconnect(connection);
 			_disconnect(connection);
 			update_tree();
 			update_tree();
@@ -1171,33 +1190,50 @@ void ConnectionsDock::_handle_slot_menu_option(int p_option) {
 }
 }
 
 
 void ConnectionsDock::_slot_menu_about_to_popup() {
 void ConnectionsDock::_slot_menu_about_to_popup() {
-	bool connection_is_inherited = tree->get_selected()->has_meta("_inherited_connection");
+	TreeItem *item = tree->get_selected();
+	if (!item || _get_item_type(*item) != TREE_ITEM_TYPE_CONNECTION) {
+		return;
+	}
+
+	bool connection_is_inherited = item->has_meta("_inherited_connection");
 
 
-	slot_menu->set_item_disabled(slot_menu->get_item_index(EDIT), connection_is_inherited);
-	slot_menu->set_item_disabled(slot_menu->get_item_index(DISCONNECT), connection_is_inherited);
+	slot_menu->set_item_disabled(slot_menu->get_item_index(SLOT_MENU_EDIT), connection_is_inherited);
+	slot_menu->set_item_disabled(slot_menu->get_item_index(SLOT_MENU_DISCONNECT), connection_is_inherited);
 }
 }
 
 
-void ConnectionsDock::_rmb_pressed(Vector2 p_position, MouseButton p_button) {
-	if (p_button != MouseButton::RIGHT) {
+void ConnectionsDock::_rmb_pressed(const Ref<InputEvent> &p_event) {
+	const Ref<InputEventMouseButton> &mb_event = p_event;
+	if (mb_event.is_null() || !mb_event->is_pressed() || mb_event->get_button_index() != MouseButton::RIGHT) {
 		return;
 		return;
 	}
 	}
 
 
-	TreeItem *item = tree->get_selected();
-
+	TreeItem *item = tree->get_item_at_position(mb_event->get_position());
 	if (!item) {
 	if (!item) {
 		return;
 		return;
 	}
 	}
 
 
-	Vector2 screen_position = tree->get_screen_position() + p_position;
+	Vector2 screen_position = tree->get_screen_position() + mb_event->get_position();
 
 
-	if (_is_item_signal(*item)) {
-		signal_menu->set_position(screen_position);
-		signal_menu->reset_size();
-		signal_menu->popup();
-	} else {
-		slot_menu->set_position(screen_position);
-		slot_menu->reset_size();
-		slot_menu->popup();
+	switch (_get_item_type(*item)) {
+		case TREE_ITEM_TYPE_ROOT:
+			break;
+		case TREE_ITEM_TYPE_CLASS:
+			class_menu_doc_class_name = item->get_metadata(0);
+			class_menu->set_position(screen_position);
+			class_menu->reset_size();
+			class_menu->popup();
+			accept_event(); // Don't collapse item.
+			break;
+		case TREE_ITEM_TYPE_SIGNAL:
+			signal_menu->set_position(screen_position);
+			signal_menu->reset_size();
+			signal_menu->popup();
+			break;
+		case TREE_ITEM_TYPE_CONNECTION:
+			slot_menu->set_position(screen_position);
+			slot_menu->reset_size();
+			slot_menu->popup();
+			break;
 	}
 	}
 }
 }
 
 
@@ -1212,9 +1248,9 @@ void ConnectionsDock::_connect_pressed() {
 		return;
 		return;
 	}
 	}
 
 
-	if (_is_item_signal(*item)) {
+	if (_get_item_type(*item) == TREE_ITEM_TYPE_SIGNAL) {
 		_open_connection_dialog(*item);
 		_open_connection_dialog(*item);
-	} else {
+	} else if (_get_item_type(*item) == TREE_ITEM_TYPE_CONNECTION) {
 		Connection connection = item->get_metadata(0);
 		Connection connection = item->get_metadata(0);
 		_disconnect(connection);
 		_disconnect(connection);
 		update_tree();
 		update_tree();
@@ -1227,14 +1263,16 @@ void ConnectionsDock::_notification(int p_what) {
 		case NOTIFICATION_THEME_CHANGED: {
 		case NOTIFICATION_THEME_CHANGED: {
 			search_box->set_right_icon(get_theme_icon(SNAME("Search"), SNAME("EditorIcons")));
 			search_box->set_right_icon(get_theme_icon(SNAME("Search"), SNAME("EditorIcons")));
 
 
-			signal_menu->set_item_icon(signal_menu->get_item_index(CONNECT), get_theme_icon(SNAME("Instance"), SNAME("EditorIcons")));
-			signal_menu->set_item_icon(signal_menu->get_item_index(DISCONNECT_ALL), get_theme_icon(SNAME("Unlinked"), SNAME("EditorIcons")));
-			signal_menu->set_item_icon(signal_menu->get_item_index(COPY_NAME), get_theme_icon(SNAME("ActionCopy"), SNAME("EditorIcons")));
-			signal_menu->set_item_icon(signal_menu->get_item_index(OPEN_DOCUMENTATION), get_theme_icon(SNAME("Help"), SNAME("EditorIcons")));
+			class_menu->set_item_icon(class_menu->get_item_index(CLASS_MENU_OPEN_DOCS), get_theme_icon(SNAME("Help"), SNAME("EditorIcons")));
+
+			signal_menu->set_item_icon(signal_menu->get_item_index(SIGNAL_MENU_CONNECT), get_theme_icon(SNAME("Instance"), SNAME("EditorIcons")));
+			signal_menu->set_item_icon(signal_menu->get_item_index(SIGNAL_MENU_DISCONNECT_ALL), get_theme_icon(SNAME("Unlinked"), SNAME("EditorIcons")));
+			signal_menu->set_item_icon(signal_menu->get_item_index(SIGNAL_MENU_COPY_NAME), get_theme_icon(SNAME("ActionCopy"), SNAME("EditorIcons")));
+			signal_menu->set_item_icon(signal_menu->get_item_index(SIGNAL_MENU_OPEN_DOCS), get_theme_icon(SNAME("Help"), SNAME("EditorIcons")));
 
 
-			slot_menu->set_item_icon(slot_menu->get_item_index(EDIT), get_theme_icon(SNAME("Edit"), SNAME("EditorIcons")));
-			slot_menu->set_item_icon(slot_menu->get_item_index(GO_TO_SCRIPT), get_theme_icon(SNAME("ArrowRight"), SNAME("EditorIcons")));
-			slot_menu->set_item_icon(slot_menu->get_item_index(DISCONNECT), get_theme_icon(SNAME("Unlinked"), SNAME("EditorIcons")));
+			slot_menu->set_item_icon(slot_menu->get_item_index(SLOT_MENU_EDIT), get_theme_icon(SNAME("Edit"), SNAME("EditorIcons")));
+			slot_menu->set_item_icon(slot_menu->get_item_index(SLOT_MENU_GO_TO_METHOD), get_theme_icon(SNAME("ArrowRight"), SNAME("EditorIcons")));
+			slot_menu->set_item_icon(slot_menu->get_item_index(SLOT_MENU_DISCONNECT), get_theme_icon(SNAME("Unlinked"), SNAME("EditorIcons")));
 		} break;
 		} break;
 
 
 		case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: {
 		case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: {
@@ -1272,6 +1310,7 @@ void ConnectionsDock::update_tree() {
 	while (native_base != StringName()) {
 	while (native_base != StringName()) {
 		String class_name;
 		String class_name;
 		String doc_class_name;
 		String doc_class_name;
+		String class_brief;
 		Ref<Texture2D> class_icon;
 		Ref<Texture2D> class_icon;
 		List<MethodInfo> class_signals;
 		List<MethodInfo> class_signals;
 
 
@@ -1293,6 +1332,7 @@ void ConnectionsDock::update_tree() {
 				}
 				}
 				HashMap<String, DocData::ClassDoc>::ConstIterator F = doc_data->class_list.find(doc_class_name);
 				HashMap<String, DocData::ClassDoc>::ConstIterator F = doc_data->class_list.find(doc_class_name);
 				if (F) {
 				if (F) {
+					class_brief = F->value.brief_description;
 					for (int i = 0; i < F->value.signals.size(); i++) {
 					for (int i = 0; i < F->value.signals.size(); i++) {
 						descr_cache[doc_class_name][F->value.signals[i].name] = F->value.signals[i].description;
 						descr_cache[doc_class_name][F->value.signals[i].name] = F->value.signals[i].description;
 					}
 					}
@@ -1329,16 +1369,17 @@ void ConnectionsDock::update_tree() {
 			class_name = native_base;
 			class_name = native_base;
 			doc_class_name = class_name;
 			doc_class_name = class_name;
 
 
-			// For a native class, the cache is filled once.
-			if (!descr_cache.has(doc_class_name)) {
-				HashMap<String, DocData::ClassDoc>::ConstIterator F = doc_data->class_list.find(doc_class_name);
-				if (F) {
+			HashMap<String, DocData::ClassDoc>::ConstIterator F = doc_data->class_list.find(doc_class_name);
+			if (F) {
+				class_brief = DTR(F->value.brief_description);
+				// For a native class, the cache is filled once.
+				if (!descr_cache.has(doc_class_name)) {
 					for (int i = 0; i < F->value.signals.size(); i++) {
 					for (int i = 0; i < F->value.signals.size(); i++) {
 						descr_cache[doc_class_name][F->value.signals[i].name] = DTR(F->value.signals[i].description);
 						descr_cache[doc_class_name][F->value.signals[i].name] = DTR(F->value.signals[i].description);
 					}
 					}
-				} else {
-					doc_class_name = String();
 				}
 				}
+			} else {
+				doc_class_name = String();
 			}
 			}
 
 
 			if (has_theme_icon(native_base, SNAME("EditorIcons"))) {
 			if (has_theme_icon(native_base, SNAME("EditorIcons"))) {
@@ -1362,14 +1403,17 @@ void ConnectionsDock::update_tree() {
 
 
 			section_item = tree->create_item(root);
 			section_item = tree->create_item(root);
 			section_item->set_text(0, class_name);
 			section_item->set_text(0, class_name);
+			// `|` separators used in `make_custom_tooltip()` for formatting.
+			section_item->set_tooltip_text(0, "class|" + class_name + "||" + class_brief);
 			section_item->set_icon(0, class_icon);
 			section_item->set_icon(0, class_icon);
 			section_item->set_selectable(0, false);
 			section_item->set_selectable(0, false);
 			section_item->set_editable(0, false);
 			section_item->set_editable(0, false);
 			section_item->set_custom_bg_color(0, get_theme_color(SNAME("prop_subsection"), SNAME("Editor")));
 			section_item->set_custom_bg_color(0, get_theme_color(SNAME("prop_subsection"), SNAME("Editor")));
+			section_item->set_metadata(0, doc_class_name);
 		}
 		}
 
 
 		for (MethodInfo &mi : class_signals) {
 		for (MethodInfo &mi : class_signals) {
-			const StringName signal_name = mi.name;
+			const StringName &signal_name = mi.name;
 			if (!search_box->get_text().is_subsequence_ofn(signal_name)) {
 			if (!search_box->get_text().is_subsequence_ofn(signal_name)) {
 				continue;
 				continue;
 			}
 			}
@@ -1404,8 +1448,8 @@ void ConnectionsDock::update_tree() {
 					}
 					}
 				}
 				}
 
 
-				// "::" separators used in make_custom_tooltip for formatting.
-				signal_item->set_tooltip_text(0, String(signal_name) + "::" + signame.trim_prefix(mi.name) + "::" + descr);
+				// `|` separators used in `make_custom_tooltip()` for formatting.
+				signal_item->set_tooltip_text(0, "signal|" + String(signal_name) + "|" + signame.trim_prefix(mi.name) + "|" + descr);
 			}
 			}
 
 
 			// List existing connections.
 			// List existing connections.
@@ -1500,28 +1544,34 @@ ConnectionsDock::ConnectionsDock() {
 	disconnect_all_dialog->connect("confirmed", callable_mp(this, &ConnectionsDock::_disconnect_all));
 	disconnect_all_dialog->connect("confirmed", callable_mp(this, &ConnectionsDock::_disconnect_all));
 	disconnect_all_dialog->set_text(TTR("Are you sure you want to remove all connections from this signal?"));
 	disconnect_all_dialog->set_text(TTR("Are you sure you want to remove all connections from this signal?"));
 
 
+	class_menu = memnew(PopupMenu);
+	class_menu->connect("id_pressed", callable_mp(this, &ConnectionsDock::_handle_class_menu_option));
+	class_menu->connect("about_to_popup", callable_mp(this, &ConnectionsDock::_class_menu_about_to_popup));
+	class_menu->add_item(TTR("Open Documentation"), CLASS_MENU_OPEN_DOCS);
+	add_child(class_menu);
+
 	signal_menu = memnew(PopupMenu);
 	signal_menu = memnew(PopupMenu);
-	add_child(signal_menu);
 	signal_menu->connect("id_pressed", callable_mp(this, &ConnectionsDock::_handle_signal_menu_option));
 	signal_menu->connect("id_pressed", callable_mp(this, &ConnectionsDock::_handle_signal_menu_option));
 	signal_menu->connect("about_to_popup", callable_mp(this, &ConnectionsDock::_signal_menu_about_to_popup));
 	signal_menu->connect("about_to_popup", callable_mp(this, &ConnectionsDock::_signal_menu_about_to_popup));
-	signal_menu->add_item(TTR("Connect..."), CONNECT);
-	signal_menu->add_item(TTR("Disconnect All"), DISCONNECT_ALL);
-	signal_menu->add_item(TTR("Copy Name"), COPY_NAME);
+	signal_menu->add_item(TTR("Connect..."), SIGNAL_MENU_CONNECT);
+	signal_menu->add_item(TTR("Disconnect All"), SIGNAL_MENU_DISCONNECT_ALL);
+	signal_menu->add_item(TTR("Copy Name"), SIGNAL_MENU_COPY_NAME);
 	signal_menu->add_separator();
 	signal_menu->add_separator();
-	signal_menu->add_item(TTR("Open Documentation"), OPEN_DOCUMENTATION);
+	signal_menu->add_item(TTR("Open Documentation"), SIGNAL_MENU_OPEN_DOCS);
+	add_child(signal_menu);
 
 
 	slot_menu = memnew(PopupMenu);
 	slot_menu = memnew(PopupMenu);
-	add_child(slot_menu);
 	slot_menu->connect("id_pressed", callable_mp(this, &ConnectionsDock::_handle_slot_menu_option));
 	slot_menu->connect("id_pressed", callable_mp(this, &ConnectionsDock::_handle_slot_menu_option));
 	slot_menu->connect("about_to_popup", callable_mp(this, &ConnectionsDock::_slot_menu_about_to_popup));
 	slot_menu->connect("about_to_popup", callable_mp(this, &ConnectionsDock::_slot_menu_about_to_popup));
-	slot_menu->add_item(TTR("Edit..."), EDIT);
-	slot_menu->add_item(TTR("Go to Method"), GO_TO_SCRIPT);
-	slot_menu->add_item(TTR("Disconnect"), DISCONNECT);
+	slot_menu->add_item(TTR("Edit..."), SLOT_MENU_EDIT);
+	slot_menu->add_item(TTR("Go to Method"), SLOT_MENU_GO_TO_METHOD);
+	slot_menu->add_item(TTR("Disconnect"), SLOT_MENU_DISCONNECT);
+	add_child(slot_menu);
 
 
 	connect_dialog->connect("connected", callable_mp(this, &ConnectionsDock::_make_or_edit_connection));
 	connect_dialog->connect("connected", callable_mp(this, &ConnectionsDock::_make_or_edit_connection));
 	tree->connect("item_selected", callable_mp(this, &ConnectionsDock::_tree_item_selected));
 	tree->connect("item_selected", callable_mp(this, &ConnectionsDock::_tree_item_selected));
 	tree->connect("item_activated", callable_mp(this, &ConnectionsDock::_tree_item_activated));
 	tree->connect("item_activated", callable_mp(this, &ConnectionsDock::_tree_item_activated));
-	tree->connect("item_mouse_selected", callable_mp(this, &ConnectionsDock::_rmb_pressed));
+	tree->connect("gui_input", callable_mp(this, &ConnectionsDock::_rmb_pressed));
 
 
 	add_theme_constant_override("separation", 3 * EDSCALE);
 	add_theme_constant_override("separation", 3 * EDSCALE);
 }
 }

+ 25 - 12
editor/connections_dialog.h

@@ -196,18 +196,27 @@ class ConnectionsDockTree : public Tree {
 class ConnectionsDock : public VBoxContainer {
 class ConnectionsDock : public VBoxContainer {
 	GDCLASS(ConnectionsDock, VBoxContainer);
 	GDCLASS(ConnectionsDock, VBoxContainer);
 
 
-	// Right-click popup menu options.
-	enum SignalMenuOption {
-		CONNECT,
-		DISCONNECT_ALL,
-		COPY_NAME,
-		OPEN_DOCUMENTATION,
+	enum TreeItemType {
+		TREE_ITEM_TYPE_ROOT,
+		TREE_ITEM_TYPE_CLASS,
+		TREE_ITEM_TYPE_SIGNAL,
+		TREE_ITEM_TYPE_CONNECTION,
 	};
 	};
 
 
+	// Right-click context menu options.
+	enum ClassMenuOption {
+		CLASS_MENU_OPEN_DOCS,
+	};
+	enum SignalMenuOption {
+		SIGNAL_MENU_CONNECT,
+		SIGNAL_MENU_DISCONNECT_ALL,
+		SIGNAL_MENU_COPY_NAME,
+		SIGNAL_MENU_OPEN_DOCS,
+	};
 	enum SlotMenuOption {
 	enum SlotMenuOption {
-		EDIT,
-		GO_TO_SCRIPT,
-		DISCONNECT,
+		SLOT_MENU_EDIT,
+		SLOT_MENU_GO_TO_METHOD,
+		SLOT_MENU_DISCONNECT,
 	};
 	};
 
 
 	Node *selected_node = nullptr;
 	Node *selected_node = nullptr;
@@ -216,6 +225,8 @@ class ConnectionsDock : public VBoxContainer {
 	ConfirmationDialog *disconnect_all_dialog = nullptr;
 	ConfirmationDialog *disconnect_all_dialog = nullptr;
 	ConnectDialog *connect_dialog = nullptr;
 	ConnectDialog *connect_dialog = nullptr;
 	Button *connect_button = nullptr;
 	Button *connect_button = nullptr;
+	PopupMenu *class_menu = nullptr;
+	String class_menu_doc_class_name;
 	PopupMenu *signal_menu = nullptr;
 	PopupMenu *signal_menu = nullptr;
 	PopupMenu *slot_menu = nullptr;
 	PopupMenu *slot_menu = nullptr;
 	LineEdit *search_box = nullptr;
 	LineEdit *search_box = nullptr;
@@ -231,18 +242,20 @@ class ConnectionsDock : public VBoxContainer {
 
 
 	void _tree_item_selected();
 	void _tree_item_selected();
 	void _tree_item_activated();
 	void _tree_item_activated();
-	bool _is_item_signal(TreeItem &p_item);
+	TreeItemType _get_item_type(const TreeItem &p_item) const;
 	bool _is_connection_inherited(Connection &p_connection);
 	bool _is_connection_inherited(Connection &p_connection);
 
 
 	void _open_connection_dialog(TreeItem &p_item);
 	void _open_connection_dialog(TreeItem &p_item);
 	void _open_edit_connection_dialog(TreeItem &p_item);
 	void _open_edit_connection_dialog(TreeItem &p_item);
-	void _go_to_script(TreeItem &p_item);
+	void _go_to_method(TreeItem &p_item);
 
 
+	void _handle_class_menu_option(int p_option);
+	void _class_menu_about_to_popup();
 	void _handle_signal_menu_option(int p_option);
 	void _handle_signal_menu_option(int p_option);
 	void _signal_menu_about_to_popup();
 	void _signal_menu_about_to_popup();
 	void _handle_slot_menu_option(int p_option);
 	void _handle_slot_menu_option(int p_option);
 	void _slot_menu_about_to_popup();
 	void _slot_menu_about_to_popup();
-	void _rmb_pressed(Vector2 p_position, MouseButton p_button);
+	void _rmb_pressed(const Ref<InputEvent> &p_event);
 	void _close();
 	void _close();
 
 
 protected:
 protected:

+ 86 - 34
editor/editor_inspector.cpp

@@ -904,36 +904,34 @@ void EditorProperty::_update_pin_flags() {
 	}
 	}
 }
 }
 
 
-static Control *make_help_bit(const String &p_text, const String &p_warning, const Color &p_warn_color, bool p_property) {
-	EditorHelpBit *help_bit = memnew(EditorHelpBit);
-	help_bit->get_rich_text()->set_custom_minimum_size(Size2(360 * EDSCALE, 1));
-
-	PackedStringArray slices = p_text.split("::", false);
-	if (slices.is_empty()) {
-		// Shouldn't happen here, but just in case pass the text along.
-		help_bit->set_text(p_text);
-		return help_bit;
+static Control *make_help_bit(const String &p_item_type, const String &p_text, const String &p_warning, const Color &p_warn_color) {
+	// `p_text` is expected to be something like this:
+	// `item_name|Item description.`.
+	// Note that the description can be empty or contain `|`.
+	PackedStringArray slices = p_text.split("|", true, 1);
+	if (slices.size() < 2) {
+		return nullptr; // Use default tooltip instead.
 	}
 	}
 
 
-	String property_name = slices[0].strip_edges();
+	String item_name = slices[0].strip_edges();
+	String item_descr = slices[1].strip_edges();
+
 	String text;
 	String text;
-	if (p_property) {
-		text = TTR("Property:") + " ";
+	if (!p_item_type.is_empty()) {
+		text = p_item_type + " ";
 	}
 	}
-	text += "[u][b]" + property_name + "[/b][/u]";
-
-	if (slices.size() > 1) {
-		String property_doc = slices[1].strip_edges();
-		if (property_name != property_doc) {
-			text += "\n" + property_doc;
-		}
+	text += "[u][b]" + item_name + "[/b][/u]\n";
+	if (item_descr.is_empty()) {
+		text += "[i]" + TTR("No description.") + "[/i]";
 	} else {
 	} else {
-		text += "\n[i]" + TTR("No description.") + "[/i]";
+		text += item_descr;
 	}
 	}
-
 	if (!p_warning.is_empty()) {
 	if (!p_warning.is_empty()) {
 		text += "\n[b][color=" + p_warn_color.to_html(false) + "]" + p_warning + "[/color][/b]";
 		text += "\n[b][color=" + p_warn_color.to_html(false) + "]" + p_warning + "[/color][/b]";
 	}
 	}
+
+	EditorHelpBit *help_bit = memnew(EditorHelpBit);
+	help_bit->get_rich_text()->set_custom_minimum_size(Size2(360 * EDSCALE, 1));
 	help_bit->set_text(text);
 	help_bit->set_text(text);
 
 
 	return help_bit;
 	return help_bit;
@@ -946,7 +944,7 @@ Control *EditorProperty::make_custom_tooltip(const String &p_text) const {
 		warn = object->call("_get_property_warning", property);
 		warn = object->call("_get_property_warning", property);
 		warn_color = get_theme_color(SNAME("warning_color"));
 		warn_color = get_theme_color(SNAME("warning_color"));
 	}
 	}
-	return make_help_bit(p_text, warn, warn_color, true);
+	return make_help_bit(TTR("Property:"), p_text, warn, warn_color);
 }
 }
 
 
 void EditorProperty::menu_option(int p_option) {
 void EditorProperty::menu_option(int p_option) {
@@ -1142,6 +1140,10 @@ void EditorInspectorPlugin::_bind_methods() {
 
 
 void EditorInspectorCategory::_notification(int p_what) {
 void EditorInspectorCategory::_notification(int p_what) {
 	switch (p_what) {
 	switch (p_what) {
+		case NOTIFICATION_ENTER_TREE:
+		case NOTIFICATION_THEME_CHANGED: {
+			menu->set_item_icon(menu->get_item_index(MENU_OPEN_DOCS), get_theme_icon(SNAME("Help"), SNAME("EditorIcons")));
+		} break;
 		case NOTIFICATION_DRAW: {
 		case NOTIFICATION_DRAW: {
 			Ref<StyleBox> sb = get_theme_stylebox(SNAME("bg"));
 			Ref<StyleBox> sb = get_theme_stylebox(SNAME("bg"));
 
 
@@ -1175,7 +1177,7 @@ void EditorInspectorCategory::_notification(int p_what) {
 }
 }
 
 
 Control *EditorInspectorCategory::make_custom_tooltip(const String &p_text) const {
 Control *EditorInspectorCategory::make_custom_tooltip(const String &p_text) const {
-	return make_help_bit(p_text, String(), Color(), false);
+	return make_help_bit(TTR("Class:"), p_text, String(), Color());
 }
 }
 
 
 Size2 EditorInspectorCategory::get_minimum_size() const {
 Size2 EditorInspectorCategory::get_minimum_size() const {
@@ -1192,7 +1194,37 @@ Size2 EditorInspectorCategory::get_minimum_size() const {
 	return ms;
 	return ms;
 }
 }
 
 
+void EditorInspectorCategory::_handle_menu_option(int p_option) {
+	switch (p_option) {
+		case MENU_OPEN_DOCS:
+			ScriptEditor::get_singleton()->goto_help("class:" + doc_class_name);
+			EditorNode::get_singleton()->set_visible_editor(EditorNode::EDITOR_SCRIPT);
+			break;
+	}
+}
+
+void EditorInspectorCategory::gui_input(const Ref<InputEvent> &p_event) {
+	if (doc_class_name.is_empty()) {
+		return;
+	}
+
+	const Ref<InputEventMouseButton> &mb_event = p_event;
+	if (mb_event.is_null() || !mb_event->is_pressed() || mb_event->get_button_index() != MouseButton::RIGHT) {
+		return;
+	}
+
+	menu->set_item_disabled(menu->get_item_index(MENU_OPEN_DOCS), !EditorHelp::get_doc_data()->class_list.has(doc_class_name));
+
+	menu->set_position(get_screen_position() + mb_event->get_position());
+	menu->reset_size();
+	menu->popup();
+}
+
 EditorInspectorCategory::EditorInspectorCategory() {
 EditorInspectorCategory::EditorInspectorCategory() {
+	menu = memnew(PopupMenu);
+	menu->connect("id_pressed", callable_mp(this, &EditorInspectorCategory::_handle_menu_option));
+	menu->add_item(TTR("Open Documentation"), MENU_OPEN_DOCS);
+	add_child(menu);
 }
 }
 
 
 ////////////////////////////////////////////////
 ////////////////////////////////////////////////
@@ -2801,6 +2833,14 @@ void EditorInspector::update_tree() {
 			main_vbox->add_child(category);
 			main_vbox->add_child(category);
 			category_vbox = nullptr; //reset
 			category_vbox = nullptr; //reset
 
 
+			// `hint_script` should contain a native class name or a script path.
+			// Otherwise the category was probably added via `@export_category` or `_get_property_list()`.
+			if (p.hint_string.is_empty()) {
+				category->label = p.name;
+				category->set_tooltip_text(p.name);
+				continue; // Do not add an icon, do not change the current class (`doc_name`).
+			}
+
 			String type = p.name;
 			String type = p.name;
 			String label = p.name;
 			String label = p.name;
 			doc_name = p.name;
 			doc_name = p.name;
@@ -2837,6 +2877,7 @@ void EditorInspector::update_tree() {
 
 
 			// Set the category label.
 			// Set the category label.
 			category->label = label;
 			category->label = label;
+			category->doc_class_name = doc_name;
 
 
 			if (use_doc_hints) {
 			if (use_doc_hints) {
 				String descr = "";
 				String descr = "";
@@ -2845,16 +2886,18 @@ void EditorInspector::update_tree() {
 					DocTools *dd = EditorHelp::get_doc_data();
 					DocTools *dd = EditorHelp::get_doc_data();
 					HashMap<String, DocData::ClassDoc>::Iterator E = dd->class_list.find(doc_name);
 					HashMap<String, DocData::ClassDoc>::Iterator E = dd->class_list.find(doc_name);
 					if (E) {
 					if (E) {
-						descr = DTR(E->value.brief_description);
+						descr = E->value.brief_description;
 					}
 					}
 					if (ClassDB::class_exists(doc_name)) {
 					if (ClassDB::class_exists(doc_name)) {
+						descr = DTR(descr); // Do not translate the class description of scripts.
 						class_descr_cache[doc_name] = descr; // Do not cache the class description of scripts.
 						class_descr_cache[doc_name] = descr; // Do not cache the class description of scripts.
 					}
 					}
 				} else {
 				} else {
 					descr = class_descr_cache[doc_name];
 					descr = class_descr_cache[doc_name];
 				}
 				}
 
 
-				category->set_tooltip_text(p.name + "::" + descr);
+				// `|` separator used in `make_help_bit()` for formatting.
+				category->set_tooltip_text(p.name + "|" + descr);
 			}
 			}
 
 
 			// Add editors at the start of a category.
 			// Add editors at the start of a category.
@@ -3196,13 +3239,18 @@ void EditorInspector::update_tree() {
 			}
 			}
 
 
 			if (!found) {
 			if (!found) {
+				bool is_native_class = ClassDB::class_exists(classname);
+
 				// Build the property description String and add it to the cache.
 				// Build the property description String and add it to the cache.
 				DocTools *dd = EditorHelp::get_doc_data();
 				DocTools *dd = EditorHelp::get_doc_data();
 				HashMap<String, DocData::ClassDoc>::ConstIterator F = dd->class_list.find(classname);
 				HashMap<String, DocData::ClassDoc>::ConstIterator F = dd->class_list.find(classname);
 				while (F && doc_info.description.is_empty()) {
 				while (F && doc_info.description.is_empty()) {
 					for (int i = 0; i < F->value.properties.size(); i++) {
 					for (int i = 0; i < F->value.properties.size(); i++) {
 						if (F->value.properties[i].name == propname.operator String()) {
 						if (F->value.properties[i].name == propname.operator String()) {
-							doc_info.description = DTR(F->value.properties[i].description);
+							doc_info.description = F->value.properties[i].description;
+							if (is_native_class) {
+								doc_info.description = DTR(doc_info.description); // Do not translate the property description of scripts.
+							}
 
 
 							const Vector<String> class_enum = F->value.properties[i].enumeration.split(".");
 							const Vector<String> class_enum = F->value.properties[i].enumeration.split(".");
 							const String class_name = class_enum[0];
 							const String class_name = class_enum[0];
@@ -3215,7 +3263,11 @@ void EditorInspector::update_tree() {
 										if (val.enumeration == enum_name && !val.name.ends_with("_MAX")) {
 										if (val.enumeration == enum_name && !val.name.ends_with("_MAX")) {
 											const String enum_value = EditorPropertyNameProcessor::get_singleton()->process_name(val.name, EditorPropertyNameProcessor::STYLE_CAPITALIZED);
 											const String enum_value = EditorPropertyNameProcessor::get_singleton()->process_name(val.name, EditorPropertyNameProcessor::STYLE_CAPITALIZED);
 											// Prettify the enum value display, so that "<ENUM NAME>_<VALUE>" becomes "Value".
 											// Prettify the enum value display, so that "<ENUM NAME>_<VALUE>" becomes "Value".
-											String desc = DTR(val.description).trim_prefix("\n");
+											String desc = val.description;
+											if (is_native_class) {
+												desc = DTR(desc); // Do not translate the enum value description of scripts.
+											}
+											desc = desc.trim_prefix("\n");
 											doc_info.description += vformat(
 											doc_info.description += vformat(
 													"\n[b]%s:[/b] %s",
 													"\n[b]%s:[/b] %s",
 													enum_value.trim_prefix(EditorPropertyNameProcessor::get_singleton()->process_name(enum_name, EditorPropertyNameProcessor::STYLE_CAPITALIZED) + " "),
 													enum_value.trim_prefix(EditorPropertyNameProcessor::get_singleton()->process_name(enum_name, EditorPropertyNameProcessor::STYLE_CAPITALIZED) + " "),
@@ -3234,7 +3286,10 @@ void EditorInspector::update_tree() {
 					if (slices.size() == 2 && slices[0].begins_with("theme_override_")) {
 					if (slices.size() == 2 && slices[0].begins_with("theme_override_")) {
 						for (int i = 0; i < F->value.theme_properties.size(); i++) {
 						for (int i = 0; i < F->value.theme_properties.size(); i++) {
 							if (F->value.theme_properties[i].name == slices[1]) {
 							if (F->value.theme_properties[i].name == slices[1]) {
-								doc_info.description = DTR(F->value.theme_properties[i].description);
+								doc_info.description = F->value.theme_properties[i].description;
+								if (is_native_class) {
+									doc_info.description = DTR(doc_info.description); // Do not translate the theme item description of scripts.
+								}
 								doc_info.path = "class_theme_item:" + F->value.name + ":" + F->value.theme_properties[i].name;
 								doc_info.path = "class_theme_item:" + F->value.name + ":" + F->value.theme_properties[i].name;
 								break;
 								break;
 							}
 							}
@@ -3248,7 +3303,7 @@ void EditorInspector::update_tree() {
 					}
 					}
 				}
 				}
 
 
-				if (ClassDB::class_exists(classname)) {
+				if (is_native_class) {
 					doc_info_cache[classname][propname] = doc_info; // Do not cache the doc information of scripts.
 					doc_info_cache[classname][propname] = doc_info; // Do not cache the doc information of scripts.
 				}
 				}
 			}
 			}
@@ -3340,11 +3395,8 @@ void EditorInspector::update_tree() {
 				ep->connect("multiple_properties_changed", callable_mp(this, &EditorInspector::_multiple_properties_changed));
 				ep->connect("multiple_properties_changed", callable_mp(this, &EditorInspector::_multiple_properties_changed));
 				ep->connect("resource_selected", callable_mp(this, &EditorInspector::_resource_selected), CONNECT_DEFERRED);
 				ep->connect("resource_selected", callable_mp(this, &EditorInspector::_resource_selected), CONNECT_DEFERRED);
 				ep->connect("object_id_selected", callable_mp(this, &EditorInspector::_object_id_selected), CONNECT_DEFERRED);
 				ep->connect("object_id_selected", callable_mp(this, &EditorInspector::_object_id_selected), CONNECT_DEFERRED);
-				if (!doc_info.description.is_empty()) {
-					ep->set_tooltip_text(property_prefix + p.name + "::" + doc_info.description);
-				} else {
-					ep->set_tooltip_text(property_prefix + p.name);
-				}
+				// `|` separator used in `make_help_bit()` for formatting.
+				ep->set_tooltip_text(property_prefix + p.name + "|" + doc_info.description);
 				ep->set_doc_path(doc_info.path);
 				ep->set_doc_path(doc_info.path);
 				ep->update_property();
 				ep->update_property();
 				ep->_update_pin_flags();
 				ep->_update_pin_flags();

+ 11 - 0
editor/editor_inspector.h

@@ -253,11 +253,22 @@ class EditorInspectorCategory : public Control {
 	GDCLASS(EditorInspectorCategory, Control);
 	GDCLASS(EditorInspectorCategory, Control);
 
 
 	friend class EditorInspector;
 	friend class EditorInspector;
+
+	// Right-click context menu options.
+	enum ClassMenuOption {
+		MENU_OPEN_DOCS,
+	};
+
 	Ref<Texture2D> icon;
 	Ref<Texture2D> icon;
 	String label;
 	String label;
+	String doc_class_name;
+	PopupMenu *menu = nullptr;
+
+	void _handle_menu_option(int p_option);
 
 
 protected:
 protected:
 	void _notification(int p_what);
 	void _notification(int p_what);
+	virtual void gui_input(const Ref<InputEvent> &p_event) override;
 
 
 public:
 public:
 	virtual Size2 get_minimum_size() const override;
 	virtual Size2 get_minimum_size() const override;