Repository: godotengine/godot
File: editor/inspector/editor_inspector.cpp (C++)

Code before:

				ur->commit_action();
			} else {
				const String action_name = vformat(TTR("Set category %s on node %s"), category_name, object->get("name"));
				ur->create_action(action_name);

				for (const KeyValue<Variant, Variant> &pair : clipboard) {
					const String property_name = pair.key;
					ur->add_do_property(object, property_name, pair.value);
					ur->add_undo_property(object, property_name, object->get(property_name));
				}

				ur->commit_action();
			}
		} break;

		case MENU_OPEN_DOCS: {
			ScriptEditor::get_singleton()->goto_help("class:" + doc_class_name);
			EditorNode::get_singleton()->get_editor_main_screen()->select(EditorMainScreen::EDITOR_SCRIPT);
		} break;

		case MENU_UNFAVORITE_ALL: {
			emit_signal(SNAME("unfavorite_all"));
		} break;
	}
}

void EditorInspectorCategory::_popup_context_menu(const Point2i &p_position) {
	if (!is_favorite && doc_class_name.is_empty()) {
		return;
	}

	if (menu == nullptr) {
		menu = memnew(PopupMenu);

		if (is_favorite) {
			menu->add_item(TTRC("Unfavorite All"), MENU_UNFAVORITE_ALL);
		} else {
			menu->add_icon_shortcut(theme_cache.icon_copy, ED_GET_SHORTCUT("property_editor/copy_category_values"), MENU_COPY_VALUE);
			menu->add_icon_shortcut(theme_cache.icon_paste, ED_GET_SHORTCUT("property_editor/paste_category_values"), MENU_PASTE_VALUE);
			menu->add_item(TTRC("Open Documentation"), MENU_OPEN_DOCS);
			menu->set_item_disabled(-1, !EditorHelp::get_doc_data()->class_list.has(doc_class_name));
		}

		menu->connect(SceneStringName(id_pressed), callable_mp(this, &EditorInspectorCategory::_handle_menu_option));
		add_child(menu);
	}

	if (menu_icon_dirty) {
		if (is_favorite) {
			menu->set_item_icon(menu->get_item_index(MENU_UNFAVORITE_ALL), theme_cache.icon_unfavorite);

Review comment (suggestion):
Putting a Dictionary into the clipboard means that you can paste it into a _property_ of type Dictionary

<img width="432" height="648" alt="Image" src="https://github.com/user-attachments/assets/04faec7f-250d-4266-8446-9af4989eabae" />

Though I guess it's not a significant problem 🤔

It could be fixed by checking whether the pasted property is a specific Dictionary and canceling paste (similar to #112382).

Code after:
		}

		r_properties.push_back(prop_info.name);
	}
}

void EditorInspectorCategory::_handle_menu_option(int p_option) {
	switch (p_option) {
		case MENU_COPY_VALUE: {
			const Object *object = EditorInterface::get_singleton()->get_inspector()->get_edited_object();
			String category_name = info.name;
			if (!EditorNode::get_editor_data().is_type_recognized(info.name) && ResourceLoader::exists(info.hint_string, "Script")) {
				Ref<Script> scr = ResourceLoader::load(info.hint_string, "Script");
				if (scr.is_valid()) {
					category_name = scr->get_doc_class_name();
				}
			}
			Dictionary clipboard;
			LocalVector<String> properties;
			_collect_properties(object, properties);

			clipboard["@pastebin_category_name"] = category_name;
			for (const String &property_name : properties) {
				clipboard[property_name] = object->get(property_name);
			}
			InspectorDock::get_inspector_singleton()->set_property_clipboard(clipboard);
		} break;

		case MENU_PASTE_VALUE: {
			Object *object = EditorInterface::get_singleton()->get_inspector()->get_edited_object();
			const Dictionary clipboard = InspectorDock::get_inspector_singleton()->get_property_clipboard();
			if (!clipboard.has("@pastebin_category_name")) {
				break;
			}
			const String pastebin_category_name = clipboard["@pastebin_category_name"];
			String category_name = info.name;

			if (!EditorNode::get_editor_data().is_type_recognized(info.name) && ResourceLoader::exists(info.hint_string, "Script")) {
				Ref<Script> scr = ResourceLoader::load(info.hint_string, "Script");
				if (scr.is_valid()) {
					category_name = scr->get_doc_class_name();
				}
			}

			EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton();
			if (const MultiNodeEdit *multi_node_edit = Object::cast_to<MultiNodeEdit>(object)) {
				const Node *es = EditorNode::get_singleton()->get_edited_scene();
				const String action_name = vformat(TTR("Set category %s on %d nodes"), category_name, multi_node_edit->get_node_count());
				ur->create_action(action_name);

				for (int i = 0; i < multi_node_edit->get_node_count(); i++) {