Path: blob/master/editor/debugger/editor_debugger_inspector.cpp
20831 views
/**************************************************************************/1/* editor_debugger_inspector.cpp */2/**************************************************************************/3/* This file is part of: */4/* GODOT ENGINE */5/* https://godotengine.org */6/**************************************************************************/7/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */8/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */9/* */10/* Permission is hereby granted, free of charge, to any person obtaining */11/* a copy of this software and associated documentation files (the */12/* "Software"), to deal in the Software without restriction, including */13/* without limitation the rights to use, copy, modify, merge, publish, */14/* distribute, sublicense, and/or sell copies of the Software, and to */15/* permit persons to whom the Software is furnished to do so, subject to */16/* the following conditions: */17/* */18/* The above copyright notice and this permission notice shall be */19/* included in all copies or substantial portions of the Software. */20/* */21/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */22/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */23/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */24/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */25/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */26/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */27/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */28/**************************************************************************/2930#include "editor_debugger_inspector.h"3132#include "core/debugger/debugger_marshalls.h"33#include "core/io/marshalls.h"34#include "core/io/resource_loader.h"35#include "editor/docks/inspector_dock.h"36#include "editor/editor_node.h"37#include "editor/editor_undo_redo_manager.h"38#include "scene/debugger/scene_debugger_object.h"3940bool EditorDebuggerRemoteObjects::_set(const StringName &p_name, const Variant &p_value) {41return _set_impl(p_name, p_value, "");42}4344bool EditorDebuggerRemoteObjects::_set_impl(const StringName &p_name, const Variant &p_value, const String &p_field) {45String name = p_name;46if (!prop_values.has(name) || String(name).begins_with("Constants/")) {47return false;48}4950// Change it back to the real name when fetching.51if (name == "Script") {52name = "script";53} else if (name.begins_with("Metadata/")) {54name = name.replace_first("Metadata/", "metadata/");55}5657Dictionary &values = prop_values[p_name];58Dictionary old_values = values.duplicate();59for (const uint64_t key : values.keys()) {60values.set(key, p_value);61}6263EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton();64const int size = remote_object_ids.size();65ur->create_action(size == 1 ? vformat(TTR("Set %s"), name) : vformat(TTR("Set %s on %d objects"), name, size), UndoRedo::MERGE_ENDS);6667ur->add_do_method(this, SNAME("emit_signal"), SNAME("values_edited"), name, values, p_field);68ur->add_undo_method(this, SNAME("emit_signal"), SNAME("values_edited"), name, old_values, p_field);69ur->commit_action();7071return true;72}7374bool EditorDebuggerRemoteObjects::_get(const StringName &p_name, Variant &r_ret) const {75String name = p_name;76if (!prop_values.has(name)) {77return false;78}7980// Change it back to the real name when fetching.81if (name == "Script") {82name = "script";83} else if (name.begins_with("Metadata/")) {84name = name.replace_first("Metadata/", "metadata/");85}8687r_ret = prop_values[p_name][remote_object_ids[0]];88return true;89}9091void EditorDebuggerRemoteObjects::_get_property_list(List<PropertyInfo> *p_list) const {92p_list->clear(); // Sorry, don't want any categories.93for (const PropertyInfo &prop : prop_list) {94p_list->push_back(prop);95}96}9798void EditorDebuggerRemoteObjects::set_property_field(const StringName &p_property, const Variant &p_value, const String &p_field) {99// Ignore the field with arrays and dictionaries, as they are passed whole when edited.100Variant::Type type = p_value.get_type();101if (type == Variant::ARRAY || type == Variant::DICTIONARY) {102_set_impl(p_property, p_value, "");103} else {104_set_impl(p_property, p_value, p_field);105}106}107108String EditorDebuggerRemoteObjects::get_title() {109if (!remote_object_ids.is_empty() && ObjectID(remote_object_ids[0].operator uint64_t()).is_valid()) {110const int size = remote_object_ids.size();111return size == 1 ? vformat(TTR("Remote %s: %d"), type_name, remote_object_ids[0]) : vformat(TTR("Remote %s (%d Selected)"), type_name, size);112}113114return "<null>";115}116117Variant EditorDebuggerRemoteObjects::get_variant(const StringName &p_name) {118Variant var;119_get(p_name, var);120return var;121}122123void EditorDebuggerRemoteObjects::_bind_methods() {124ClassDB::bind_method(D_METHOD("get_title"), &EditorDebuggerRemoteObjects::get_title);125ClassDB::bind_method("_hide_script_from_inspector", &EditorDebuggerRemoteObjects::_hide_script_from_inspector);126ClassDB::bind_method("_hide_metadata_from_inspector", &EditorDebuggerRemoteObjects::_hide_metadata_from_inspector);127128ADD_SIGNAL(MethodInfo("values_edited", PropertyInfo(Variant::STRING, "property"), PropertyInfo(Variant::DICTIONARY, "values", PROPERTY_HINT_DICTIONARY_TYPE, "uint64_t;Variant"), PropertyInfo(Variant::STRING, "field")));129}130131/// EditorDebuggerInspector132133EditorDebuggerInspector::EditorDebuggerInspector() {134variables = memnew(EditorDebuggerRemoteObjects);135}136137EditorDebuggerInspector::~EditorDebuggerInspector() {138clear_cache();139memdelete(variables);140}141142void EditorDebuggerInspector::_bind_methods() {143ADD_SIGNAL(MethodInfo("object_selected", PropertyInfo(Variant::INT, "id")));144ADD_SIGNAL(MethodInfo("objects_edited", PropertyInfo(Variant::ARRAY, "ids"), PropertyInfo(Variant::STRING, "property"), PropertyInfo("value"), PropertyInfo(Variant::STRING, "field")));145ADD_SIGNAL(MethodInfo("object_property_updated", PropertyInfo(Variant::INT, "id"), PropertyInfo(Variant::STRING, "property")));146}147148void EditorDebuggerInspector::_notification(int p_what) {149switch (p_what) {150case NOTIFICATION_POSTINITIALIZE: {151connect("object_id_selected", callable_mp(this, &EditorDebuggerInspector::_object_selected));152} break;153154case NOTIFICATION_ENTER_TREE: {155variables->remote_object_ids.append(0);156edit(variables);157} break;158}159}160161void EditorDebuggerInspector::_objects_edited(const String &p_prop, const TypedDictionary<uint64_t, Variant> &p_values, const String &p_field) {162emit_signal(SNAME("objects_edited"), p_prop, p_values, p_field);163}164165void EditorDebuggerInspector::_object_selected(ObjectID p_object) {166emit_signal(SNAME("object_selected"), p_object);167}168169EditorDebuggerRemoteObjects *EditorDebuggerInspector::set_objects(const Array &p_arr) {170ERR_FAIL_COND_V(p_arr.is_empty(), nullptr);171172TypedArray<uint64_t> ids;173LocalVector<SceneDebuggerObject> objects;174for (const Array arr : p_arr) {175SceneDebuggerObject obj;176obj.deserialize(arr);177if (obj.id.is_valid()) {178ids.push_back((uint64_t)obj.id);179objects.push_back(obj);180}181}182ERR_FAIL_COND_V(ids.is_empty(), nullptr);183184// Sorting is necessary, as selected nodes in the remote tree are ordered by index.185ids.sort();186187EditorDebuggerRemoteObjects *remote_objects = nullptr;188for (EditorDebuggerRemoteObjects *robjs : remote_objects_list) {189if (robjs->remote_object_ids == ids) {190remote_objects = robjs;191break;192}193}194195if (!remote_objects) {196remote_objects = memnew(EditorDebuggerRemoteObjects);197remote_objects->remote_object_ids = ids;198remote_objects->remote_object_ids.make_read_only();199remote_objects->connect("values_edited", callable_mp(this, &EditorDebuggerInspector::_objects_edited));200remote_objects_list.push_back(remote_objects);201}202203StringName class_name = objects[0].class_name;204if (class_name != SNAME("Object")) {205// Search for the common class between all selected objects.206bool check_type_again = true;207while (check_type_again) {208check_type_again = false;209210if (class_name == SNAME("Object") || class_name == StringName()) {211// All objects inherit from Object, so no need to continue checking.212class_name = SNAME("Object");213break;214}215216// Check that all objects inherit from type_name.217for (const SceneDebuggerObject &obj : objects) {218if (obj.class_name == class_name || ClassDB::is_parent_class(obj.class_name, class_name)) {219continue; // class_name is the same or a parent of the object's class.220}221222// class_name is not a parent of the node's class, so check again with the parent class.223class_name = ClassDB::get_parent_class(class_name);224check_type_again = true;225break;226}227}228}229remote_objects->type_name = class_name;230231// Search for properties that are present in all selected objects.232struct UsageData {233int qty = 0;234SceneDebuggerObject::SceneDebuggerProperty prop;235TypedDictionary<uint64_t, Variant> values;236};237HashMap<String, UsageData> usage;238int nc = 0;239for (const SceneDebuggerObject &obj : objects) {240for (const SceneDebuggerObject::SceneDebuggerProperty &prop : obj.properties) {241PropertyInfo pinfo = prop.first;242// Rename those variables, so they don't conflict with the ones from the resource itself.243if (pinfo.name == "script") {244pinfo.name = "Script";245} else if (pinfo.name.begins_with("metadata/")) {246pinfo.name = pinfo.name.replace_first("metadata/", "Metadata/");247}248249if (!usage.has(pinfo.name)) {250UsageData usage_dt;251usage_dt.prop = prop;252usage_dt.prop.first.name = pinfo.name;253usage_dt.values[obj.id] = prop.second;254usage[pinfo.name] = usage_dt;255}256257// Make sure only properties with the same exact PropertyInfo data will appear.258if (usage[pinfo.name].prop.first == pinfo) {259usage[pinfo.name].qty++;260usage[pinfo.name].values[obj.id] = prop.second;261}262}263264nc++;265}266for (HashMap<String, UsageData>::Iterator E = usage.begin(); E;) {267HashMap<String, UsageData>::Iterator next = E;268++next;269270UsageData usage_dt = E->value;271if (nc != usage_dt.qty) {272// Doesn't appear on all of them, remove it.273usage.erase(E->key);274}275276E = next;277}278279int old_prop_size = remote_objects->prop_list.size();280281remote_objects->prop_list.clear();282int new_props_added = 0;283HashSet<String> changed;284for (KeyValue<String, UsageData> &KV : usage) {285const PropertyInfo &pinfo = KV.value.prop.first;286Variant var = KV.value.values[remote_objects->remote_object_ids[0]];287288if (pinfo.type == Variant::OBJECT && var.is_string()) {289String path = var;290if (path.contains("::")) {291// Built-in resource.292String base_path = path.get_slice("::", 0);293Ref<Resource> dependency = ResourceLoader::load(base_path);294if (dependency.is_valid()) {295remote_dependencies.insert(dependency);296}297}298var = ResourceLoader::load(path);299KV.value.values[remote_objects->remote_object_ids[0]] = var;300}301302// Always add the property, since props may have been added or removed.303remote_objects->prop_list.push_back(pinfo);304305if (!remote_objects->prop_values.has(pinfo.name)) {306new_props_added++;307} else if (bool(Variant::evaluate(Variant::OP_NOT_EQUAL, remote_objects->prop_values[pinfo.name], var))) {308changed.insert(pinfo.name);309}310311remote_objects->prop_values[pinfo.name] = KV.value.values;312}313314if (old_prop_size == remote_objects->prop_list.size() && new_props_added == 0) {315// Only some may have changed, if so, then update those, if they exist.316for (const String &E : changed) {317emit_signal(SNAME("object_property_updated"), remote_objects->get_instance_id(), E);318}319} else {320// Full update, because props were added or removed.321remote_objects->update();322}323324return remote_objects;325}326327void EditorDebuggerInspector::clear_remote_inspector() {328if (remote_objects_list.is_empty()) {329return;330}331332const Object *obj = InspectorDock::get_inspector_singleton()->get_edited_object();333// Check if the inspector holds remote items, and take it out if so.334if (Object::cast_to<EditorDebuggerRemoteObjects>(obj)) {335EditorNode::get_singleton()->push_item(nullptr);336}337}338339void EditorDebuggerInspector::clear_cache() {340clear_remote_inspector();341342for (EditorDebuggerRemoteObjects *robjs : remote_objects_list) {343memdelete(robjs);344}345remote_objects_list.clear();346347remote_dependencies.clear();348}349350void EditorDebuggerInspector::invalidate_selection_from_cache(const TypedArray<uint64_t> &p_ids) {351for (EditorDebuggerRemoteObjects *robjs : remote_objects_list) {352if (robjs->remote_object_ids == p_ids) {353const Object *obj = InspectorDock::get_inspector_singleton()->get_edited_object();354if (obj == robjs) {355EditorNode::get_singleton()->push_item(nullptr);356}357358remote_objects_list.erase(robjs);359memdelete(robjs);360break;361}362}363}364365void EditorDebuggerInspector::add_stack_variable(const Array &p_array, int p_offset) {366DebuggerMarshalls::ScriptStackVariable var;367var.deserialize(p_array);368String n = var.name;369Variant v = var.value;370371PropertyHint h = PROPERTY_HINT_NONE;372String hs;373374if (var.var_type == Variant::OBJECT && v) {375v = Object::cast_to<EncodedObjectAsID>(v)->get_object_id();376h = PROPERTY_HINT_OBJECT_ID;377hs = "Object";378}379String type;380switch (var.type) {381case 0:382type = "Locals/";383break;384case 1:385type = "Members/";386break;387case 2:388type = "Globals/";389break;390case 3:391type = "Evaluated/";392break;393default:394type = "Unknown/";395}396397PropertyInfo pinfo;398// Encode special characters to avoid issues with expressions in Evaluator.399// Dots are skipped by uri_encode(), but uri_decode() process them correctly when replaced with "%2E".400pinfo.name = type + n.uri_encode().replace(".", "%2E");401pinfo.type = v.get_type();402pinfo.hint = h;403pinfo.hint_string = hs;404405if ((p_offset == -1) || variables->prop_list.is_empty()) {406variables->prop_list.push_back(pinfo);407} else {408List<PropertyInfo>::Element *current = variables->prop_list.front();409for (int i = 0; i < p_offset; i++) {410current = current->next();411}412variables->prop_list.insert_before(current, pinfo);413}414variables->prop_values[pinfo.name][0] = v;415variables->update();416edit(variables);417}418419void EditorDebuggerInspector::clear_stack_variables() {420variables->clear();421variables->update();422}423424String EditorDebuggerInspector::get_stack_variable(const String &p_var) {425for (KeyValue<StringName, TypedDictionary<uint64_t, Variant>> &E : variables->prop_values) {426String v = E.key.operator String();427if (v.get_slicec('/', 1) == p_var) {428return variables->get_variant(v);429}430}431return String();432}433434435