1 | /**************************************************************************/ |
2 | /* multi_node_edit.cpp */ |
3 | /**************************************************************************/ |
4 | /* This file is part of: */ |
5 | /* GODOT ENGINE */ |
6 | /* https://godotengine.org */ |
7 | /**************************************************************************/ |
8 | /* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ |
9 | /* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ |
10 | /* */ |
11 | /* Permission is hereby granted, free of charge, to any person obtaining */ |
12 | /* a copy of this software and associated documentation files (the */ |
13 | /* "Software"), to deal in the Software without restriction, including */ |
14 | /* without limitation the rights to use, copy, modify, merge, publish, */ |
15 | /* distribute, sublicense, and/or sell copies of the Software, and to */ |
16 | /* permit persons to whom the Software is furnished to do so, subject to */ |
17 | /* the following conditions: */ |
18 | /* */ |
19 | /* The above copyright notice and this permission notice shall be */ |
20 | /* included in all copies or substantial portions of the Software. */ |
21 | /* */ |
22 | /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ |
23 | /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ |
24 | /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ |
25 | /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ |
26 | /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ |
27 | /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ |
28 | /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ |
29 | /**************************************************************************/ |
30 | |
31 | #include "multi_node_edit.h" |
32 | |
33 | #include "core/math/math_fieldwise.h" |
34 | #include "editor/editor_node.h" |
35 | #include "editor/editor_undo_redo_manager.h" |
36 | |
37 | bool MultiNodeEdit::_set(const StringName &p_name, const Variant &p_value) { |
38 | return _set_impl(p_name, p_value, "" ); |
39 | } |
40 | |
41 | bool MultiNodeEdit::_set_impl(const StringName &p_name, const Variant &p_value, const String &p_field) { |
42 | Node *es = EditorNode::get_singleton()->get_edited_scene(); |
43 | if (!es) { |
44 | return false; |
45 | } |
46 | |
47 | String name = p_name; |
48 | |
49 | if (name == "scripts" ) { // Script set is intercepted at object level (check Variant Object::get()), so use a different name. |
50 | name = "script" ; |
51 | } else if (name.begins_with("Metadata/" )) { |
52 | name = name.replace_first("Metadata/" , "metadata/" ); |
53 | } |
54 | |
55 | Node *node_path_target = nullptr; |
56 | if (p_value.get_type() == Variant::NODE_PATH && p_value != NodePath()) { |
57 | node_path_target = es->get_node(p_value); |
58 | } |
59 | |
60 | EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton(); |
61 | |
62 | ur->create_action(vformat(TTR("Set %s on %d nodes" ), name, get_node_count()), UndoRedo::MERGE_ENDS); |
63 | for (const NodePath &E : nodes) { |
64 | Node *n = es->get_node_or_null(E); |
65 | if (!n) { |
66 | continue; |
67 | } |
68 | |
69 | if (p_value.get_type() == Variant::NODE_PATH) { |
70 | NodePath path; |
71 | if (node_path_target) { |
72 | path = n->get_path_to(node_path_target); |
73 | } |
74 | ur->add_do_property(n, name, path); |
75 | } else { |
76 | Variant new_value; |
77 | if (p_field.is_empty()) { |
78 | // whole value |
79 | new_value = p_value; |
80 | } else { |
81 | // only one field |
82 | new_value = fieldwise_assign(n->get(name), p_value, p_field); |
83 | } |
84 | ur->add_do_property(n, name, new_value); |
85 | } |
86 | |
87 | ur->add_undo_property(n, name, n->get(name)); |
88 | } |
89 | |
90 | ur->commit_action(); |
91 | return true; |
92 | } |
93 | |
94 | bool MultiNodeEdit::_get(const StringName &p_name, Variant &r_ret) const { |
95 | Node *es = EditorNode::get_singleton()->get_edited_scene(); |
96 | if (!es) { |
97 | return false; |
98 | } |
99 | |
100 | String name = p_name; |
101 | if (name == "scripts" ) { // Script set is intercepted at object level (check Variant Object::get()), so use a different name. |
102 | name = "script" ; |
103 | } else if (name.begins_with("Metadata/" )) { |
104 | name = name.replace_first("Metadata/" , "metadata/" ); |
105 | } |
106 | |
107 | for (const NodePath &E : nodes) { |
108 | const Node *n = es->get_node_or_null(E); |
109 | if (!n) { |
110 | continue; |
111 | } |
112 | |
113 | bool found; |
114 | r_ret = n->get(name, &found); |
115 | if (found) { |
116 | return true; |
117 | } |
118 | } |
119 | |
120 | return false; |
121 | } |
122 | |
123 | void MultiNodeEdit::_get_property_list(List<PropertyInfo> *p_list) const { |
124 | HashMap<String, PLData> usage; |
125 | |
126 | Node *es = EditorNode::get_singleton()->get_edited_scene(); |
127 | if (!es) { |
128 | return; |
129 | } |
130 | |
131 | int nc = 0; |
132 | |
133 | List<PLData *> data_list; |
134 | |
135 | for (const NodePath &E : nodes) { |
136 | Node *n = es->get_node_or_null(E); |
137 | if (!n) { |
138 | continue; |
139 | } |
140 | |
141 | List<PropertyInfo> plist; |
142 | n->get_property_list(&plist, true); |
143 | |
144 | for (PropertyInfo F : plist) { |
145 | if (F.name == "script" ) { |
146 | continue; // Added later manually, since this is intercepted before being set (check Variant Object::get()). |
147 | } else if (F.name.begins_with("metadata/" )) { |
148 | F.name = F.name.replace_first("metadata/" , "Metadata/" ); // Trick to not get actual metadata edited from MultiNodeEdit. |
149 | } |
150 | |
151 | if (!usage.has(F.name)) { |
152 | PLData pld; |
153 | pld.uses = 0; |
154 | pld.info = F; |
155 | pld.info.name = F.name; |
156 | usage[F.name] = pld; |
157 | data_list.push_back(usage.getptr(F.name)); |
158 | } |
159 | |
160 | // Make sure only properties with the same exact PropertyInfo data will appear. |
161 | if (usage[F.name].info == F) { |
162 | usage[F.name].uses++; |
163 | } |
164 | } |
165 | |
166 | nc++; |
167 | } |
168 | |
169 | for (const PLData *E : data_list) { |
170 | if (nc == E->uses) { |
171 | p_list->push_back(E->info); |
172 | } |
173 | } |
174 | |
175 | p_list->push_back(PropertyInfo(Variant::OBJECT, "scripts" , PROPERTY_HINT_RESOURCE_TYPE, "Script" )); |
176 | } |
177 | |
178 | String MultiNodeEdit::_get_editor_name() const { |
179 | return vformat(TTR("%s (%d Selected)" ), get_edited_class_name(), get_node_count()); |
180 | } |
181 | |
182 | bool MultiNodeEdit::_property_can_revert(const StringName &p_name) const { |
183 | Node *es = EditorNode::get_singleton()->get_edited_scene(); |
184 | if (!es) { |
185 | return false; |
186 | } |
187 | |
188 | if (ClassDB::has_property(get_edited_class_name(), p_name)) { |
189 | StringName class_name; |
190 | for (const NodePath &E : nodes) { |
191 | Node *node = es->get_node_or_null(E); |
192 | if (!node) { |
193 | continue; |
194 | } |
195 | |
196 | class_name = node->get_class_name(); |
197 | } |
198 | |
199 | Variant default_value = ClassDB::class_get_default_property_value(class_name, p_name); |
200 | for (const NodePath &E : nodes) { |
201 | Node *node = es->get_node_or_null(E); |
202 | if (!node) { |
203 | continue; |
204 | } |
205 | |
206 | if (node->get(p_name) != default_value) { |
207 | // A node that doesn't have the default value has been found, so show the revert button. |
208 | return true; |
209 | } |
210 | } |
211 | |
212 | return false; |
213 | } |
214 | |
215 | // Don't show the revert button if the edited class doesn't have the property. |
216 | return false; |
217 | } |
218 | |
219 | bool MultiNodeEdit::_property_get_revert(const StringName &p_name, Variant &r_property) const { |
220 | Node *es = EditorNode::get_singleton()->get_edited_scene(); |
221 | if (!es) { |
222 | return false; |
223 | } |
224 | |
225 | for (const NodePath &E : nodes) { |
226 | Node *node = es->get_node_or_null(E); |
227 | if (!node) { |
228 | continue; |
229 | } |
230 | |
231 | r_property = ClassDB::class_get_default_property_value(node->get_class_name(), p_name); |
232 | return true; |
233 | } |
234 | |
235 | return false; |
236 | } |
237 | |
238 | void MultiNodeEdit::add_node(const NodePath &p_node) { |
239 | nodes.push_back(p_node); |
240 | } |
241 | |
242 | int MultiNodeEdit::get_node_count() const { |
243 | return nodes.size(); |
244 | } |
245 | |
246 | NodePath MultiNodeEdit::get_node(int p_index) const { |
247 | ERR_FAIL_INDEX_V(p_index, nodes.size(), NodePath()); |
248 | return nodes[p_index]; |
249 | } |
250 | |
251 | StringName MultiNodeEdit::get_edited_class_name() const { |
252 | Node *es = EditorNode::get_singleton()->get_edited_scene(); |
253 | if (!es) { |
254 | return SNAME("Node" ); |
255 | } |
256 | |
257 | // Get the class name of the first node. |
258 | StringName class_name; |
259 | for (const NodePath &E : nodes) { |
260 | Node *node = es->get_node_or_null(E); |
261 | if (!node) { |
262 | continue; |
263 | } |
264 | |
265 | class_name = node->get_class_name(); |
266 | break; |
267 | } |
268 | |
269 | if (class_name == StringName()) { |
270 | return SNAME("Node" ); |
271 | } |
272 | |
273 | bool check_again = true; |
274 | while (check_again) { |
275 | check_again = false; |
276 | |
277 | if (class_name == SNAME("Node" ) || class_name == StringName()) { |
278 | // All nodes inherit from Node, so no need to continue checking. |
279 | return SNAME("Node" ); |
280 | } |
281 | |
282 | // Check that all nodes inherit from class_name. |
283 | for (const NodePath &E : nodes) { |
284 | Node *node = es->get_node_or_null(E); |
285 | if (!node) { |
286 | continue; |
287 | } |
288 | |
289 | const StringName node_class_name = node->get_class_name(); |
290 | if (class_name == node_class_name || ClassDB::is_parent_class(node_class_name, class_name)) { |
291 | // class_name is the same or a parent of the node's class. |
292 | continue; |
293 | } |
294 | |
295 | // class_name is not a parent of the node's class, so check again with the parent class. |
296 | class_name = ClassDB::get_parent_class(class_name); |
297 | check_again = true; |
298 | break; |
299 | } |
300 | } |
301 | |
302 | return class_name; |
303 | } |
304 | |
305 | void MultiNodeEdit::set_property_field(const StringName &p_property, const Variant &p_value, const String &p_field) { |
306 | _set_impl(p_property, p_value, p_field); |
307 | } |
308 | |
309 | void MultiNodeEdit::_bind_methods() { |
310 | ClassDB::bind_method("_hide_script_from_inspector" , &MultiNodeEdit::_hide_script_from_inspector); |
311 | ClassDB::bind_method("_hide_metadata_from_inspector" , &MultiNodeEdit::_hide_metadata_from_inspector); |
312 | ClassDB::bind_method("_get_editor_name" , &MultiNodeEdit::_get_editor_name); |
313 | } |
314 | |
315 | MultiNodeEdit::MultiNodeEdit() { |
316 | } |
317 | |