1 /*************************************************************************/
2 /*  item_list_editor_plugin.cpp                                          */
3 /*************************************************************************/
4 /*                       This file is part of:                           */
5 /*                           GODOT ENGINE                                */
6 /*                      https://godotengine.org                          */
7 /*************************************************************************/
8 /* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur.                 */
9 /* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md).   */
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 "item_list_editor_plugin.h"
32 
33 #include "core/io/resource_loader.h"
34 #include "editor/editor_scale.h"
35 
_set(const StringName & p_name,const Variant & p_value)36 bool ItemListPlugin::_set(const StringName &p_name, const Variant &p_value) {
37 
38 	String name = p_name;
39 	int idx = name.get_slice("/", 0).to_int();
40 	String what = name.get_slice("/", 1);
41 
42 	if (what == "text")
43 		set_item_text(idx, p_value);
44 	else if (what == "icon")
45 		set_item_icon(idx, p_value);
46 	else if (what == "checkable") {
47 		// This keeps compatibility to/from versions where this property was a boolean, before radio buttons
48 		switch ((int)p_value) {
49 			case 0:
50 			case 1:
51 				set_item_checkable(idx, p_value);
52 				break;
53 			case 2:
54 				set_item_radio_checkable(idx, true);
55 				break;
56 		}
57 	} else if (what == "checked")
58 		set_item_checked(idx, p_value);
59 	else if (what == "id")
60 		set_item_id(idx, p_value);
61 	else if (what == "enabled")
62 		set_item_enabled(idx, p_value);
63 	else if (what == "separator")
64 		set_item_separator(idx, p_value);
65 	else
66 		return false;
67 
68 	return true;
69 }
70 
_get(const StringName & p_name,Variant & r_ret) const71 bool ItemListPlugin::_get(const StringName &p_name, Variant &r_ret) const {
72 
73 	String name = p_name;
74 	int idx = name.get_slice("/", 0).to_int();
75 	String what = name.get_slice("/", 1);
76 
77 	if (what == "text")
78 		r_ret = get_item_text(idx);
79 	else if (what == "icon")
80 		r_ret = get_item_icon(idx);
81 	else if (what == "checkable") {
82 		// This keeps compatibility to/from versions where this property was a boolean, before radio buttons
83 		if (!is_item_checkable(idx)) {
84 			r_ret = 0;
85 		} else {
86 			r_ret = is_item_radio_checkable(idx) ? 2 : 1;
87 		}
88 	} else if (what == "checked")
89 		r_ret = is_item_checked(idx);
90 	else if (what == "id")
91 		r_ret = get_item_id(idx);
92 	else if (what == "enabled")
93 		r_ret = is_item_enabled(idx);
94 	else if (what == "separator")
95 		r_ret = is_item_separator(idx);
96 	else
97 		return false;
98 
99 	return true;
100 }
_get_property_list(List<PropertyInfo> * p_list) const101 void ItemListPlugin::_get_property_list(List<PropertyInfo> *p_list) const {
102 
103 	for (int i = 0; i < get_item_count(); i++) {
104 
105 		String base = itos(i) + "/";
106 
107 		p_list->push_back(PropertyInfo(Variant::STRING, base + "text"));
108 		p_list->push_back(PropertyInfo(Variant::OBJECT, base + "icon", PROPERTY_HINT_RESOURCE_TYPE, "Texture"));
109 
110 		int flags = get_flags();
111 
112 		if (flags & FLAG_CHECKABLE) {
113 			p_list->push_back(PropertyInfo(Variant::INT, base + "checkable", PROPERTY_HINT_ENUM, "No,As checkbox,As radio button"));
114 			p_list->push_back(PropertyInfo(Variant::BOOL, base + "checked"));
115 		}
116 
117 		if (flags & FLAG_ID)
118 			p_list->push_back(PropertyInfo(Variant::INT, base + "id", PROPERTY_HINT_RANGE, "-1,4096"));
119 
120 		if (flags & FLAG_ENABLE)
121 			p_list->push_back(PropertyInfo(Variant::BOOL, base + "enabled"));
122 
123 		if (flags & FLAG_SEPARATOR)
124 			p_list->push_back(PropertyInfo(Variant::BOOL, base + "separator"));
125 	}
126 }
127 
128 ///////////////////////////////////////////////////////////////
129 ///////////////////////// PLUGINS /////////////////////////////
130 ///////////////////////////////////////////////////////////////
131 
set_object(Object * p_object)132 void ItemListOptionButtonPlugin::set_object(Object *p_object) {
133 
134 	ob = Object::cast_to<OptionButton>(p_object);
135 }
136 
handles(Object * p_object) const137 bool ItemListOptionButtonPlugin::handles(Object *p_object) const {
138 
139 	return p_object->is_class("OptionButton");
140 }
141 
get_flags() const142 int ItemListOptionButtonPlugin::get_flags() const {
143 
144 	return FLAG_ICON | FLAG_ID | FLAG_ENABLE;
145 }
146 
add_item()147 void ItemListOptionButtonPlugin::add_item() {
148 
149 	ob->add_item(vformat(TTR("Item %d"), ob->get_item_count()));
150 	_change_notify();
151 }
152 
get_item_count() const153 int ItemListOptionButtonPlugin::get_item_count() const {
154 
155 	return ob->get_item_count();
156 }
157 
erase(int p_idx)158 void ItemListOptionButtonPlugin::erase(int p_idx) {
159 
160 	ob->remove_item(p_idx);
161 	_change_notify();
162 }
163 
ItemListOptionButtonPlugin()164 ItemListOptionButtonPlugin::ItemListOptionButtonPlugin() {
165 
166 	ob = NULL;
167 }
168 
169 ///////////////////////////////////////////////////////////////
170 
set_object(Object * p_object)171 void ItemListPopupMenuPlugin::set_object(Object *p_object) {
172 
173 	if (p_object->is_class("MenuButton"))
174 		pp = Object::cast_to<MenuButton>(p_object)->get_popup();
175 	else
176 		pp = Object::cast_to<PopupMenu>(p_object);
177 }
178 
handles(Object * p_object) const179 bool ItemListPopupMenuPlugin::handles(Object *p_object) const {
180 
181 	return p_object->is_class("PopupMenu") || p_object->is_class("MenuButton");
182 }
183 
get_flags() const184 int ItemListPopupMenuPlugin::get_flags() const {
185 
186 	return FLAG_ICON | FLAG_CHECKABLE | FLAG_ID | FLAG_ENABLE | FLAG_SEPARATOR;
187 }
188 
add_item()189 void ItemListPopupMenuPlugin::add_item() {
190 
191 	pp->add_item(vformat(TTR("Item %d"), pp->get_item_count()));
192 	_change_notify();
193 }
194 
get_item_count() const195 int ItemListPopupMenuPlugin::get_item_count() const {
196 
197 	return pp->get_item_count();
198 }
199 
erase(int p_idx)200 void ItemListPopupMenuPlugin::erase(int p_idx) {
201 
202 	pp->remove_item(p_idx);
203 	_change_notify();
204 }
205 
ItemListPopupMenuPlugin()206 ItemListPopupMenuPlugin::ItemListPopupMenuPlugin() {
207 
208 	pp = NULL;
209 }
210 
211 ///////////////////////////////////////////////////////////////
212 
set_object(Object * p_object)213 void ItemListItemListPlugin::set_object(Object *p_object) {
214 
215 	pp = Object::cast_to<ItemList>(p_object);
216 }
217 
handles(Object * p_object) const218 bool ItemListItemListPlugin::handles(Object *p_object) const {
219 
220 	return p_object->is_class("ItemList");
221 }
222 
get_flags() const223 int ItemListItemListPlugin::get_flags() const {
224 
225 	return FLAG_ICON | FLAG_ENABLE;
226 }
227 
add_item()228 void ItemListItemListPlugin::add_item() {
229 
230 	pp->add_item(vformat(TTR("Item %d"), pp->get_item_count()));
231 	_change_notify();
232 }
233 
get_item_count() const234 int ItemListItemListPlugin::get_item_count() const {
235 
236 	return pp->get_item_count();
237 }
238 
erase(int p_idx)239 void ItemListItemListPlugin::erase(int p_idx) {
240 
241 	pp->remove_item(p_idx);
242 	_change_notify();
243 }
244 
ItemListItemListPlugin()245 ItemListItemListPlugin::ItemListItemListPlugin() {
246 
247 	pp = NULL;
248 }
249 
250 ///////////////////////////////////////////////////////////////
251 ///////////////////////////////////////////////////////////////
252 ///////////////////////////////////////////////////////////////
253 
_node_removed(Node * p_node)254 void ItemListEditor::_node_removed(Node *p_node) {
255 
256 	if (p_node == item_list) {
257 		item_list = NULL;
258 		hide();
259 		dialog->hide();
260 	}
261 }
262 
_notification(int p_notification)263 void ItemListEditor::_notification(int p_notification) {
264 
265 	if (p_notification == NOTIFICATION_ENTER_TREE || p_notification == NOTIFICATION_THEME_CHANGED) {
266 
267 		add_button->set_icon(get_icon("Add", "EditorIcons"));
268 		del_button->set_icon(get_icon("Remove", "EditorIcons"));
269 	} else if (p_notification == NOTIFICATION_READY) {
270 
271 		get_tree()->connect("node_removed", this, "_node_removed");
272 	}
273 }
274 
_add_pressed()275 void ItemListEditor::_add_pressed() {
276 
277 	if (selected_idx == -1)
278 		return;
279 
280 	item_plugins[selected_idx]->add_item();
281 }
282 
_delete_pressed()283 void ItemListEditor::_delete_pressed() {
284 
285 	if (selected_idx == -1)
286 		return;
287 
288 	String current_selected = (String)property_editor->get_selected_path();
289 
290 	if (current_selected == "")
291 		return;
292 
293 	// FIXME: Currently relying on selecting a *property* to derive what item to delete
294 	// e.g. you select "1/enabled" to delete item 1.
295 	// This should be fixed so that you can delete by selecting the item section header,
296 	// or a delete button on that header.
297 
298 	int idx = current_selected.get_slice("/", 0).to_int();
299 
300 	item_plugins[selected_idx]->erase(idx);
301 }
302 
_edit_items()303 void ItemListEditor::_edit_items() {
304 
305 	dialog->popup_centered_clamped(Vector2(425, 1200) * EDSCALE, 0.8);
306 }
307 
edit(Node * p_item_list)308 void ItemListEditor::edit(Node *p_item_list) {
309 
310 	item_list = p_item_list;
311 
312 	if (!item_list) {
313 		selected_idx = -1;
314 		property_editor->edit(NULL);
315 		return;
316 	}
317 
318 	for (int i = 0; i < item_plugins.size(); i++) {
319 		if (item_plugins[i]->handles(p_item_list)) {
320 
321 			item_plugins[i]->set_object(p_item_list);
322 			property_editor->edit(item_plugins[i]);
323 
324 			toolbar_button->set_icon(EditorNode::get_singleton()->get_object_icon(item_list, ""));
325 
326 			selected_idx = i;
327 			return;
328 		}
329 	}
330 
331 	selected_idx = -1;
332 	property_editor->edit(NULL);
333 }
334 
handles(Object * p_object) const335 bool ItemListEditor::handles(Object *p_object) const {
336 
337 	for (int i = 0; i < item_plugins.size(); i++) {
338 		if (item_plugins[i]->handles(p_object)) {
339 			return true;
340 		}
341 	}
342 
343 	return false;
344 }
345 
_bind_methods()346 void ItemListEditor::_bind_methods() {
347 
348 	ClassDB::bind_method("_node_removed", &ItemListEditor::_node_removed);
349 	ClassDB::bind_method("_edit_items", &ItemListEditor::_edit_items);
350 	ClassDB::bind_method("_add_button", &ItemListEditor::_add_pressed);
351 	ClassDB::bind_method("_delete_button", &ItemListEditor::_delete_pressed);
352 }
353 
ItemListEditor()354 ItemListEditor::ItemListEditor() {
355 
356 	selected_idx = -1;
357 	item_list = NULL;
358 
359 	toolbar_button = memnew(ToolButton);
360 	toolbar_button->set_text(TTR("Items"));
361 	add_child(toolbar_button);
362 	toolbar_button->connect("pressed", this, "_edit_items");
363 
364 	dialog = memnew(AcceptDialog);
365 	dialog->set_title(TTR("Item List Editor"));
366 	add_child(dialog);
367 
368 	VBoxContainer *vbc = memnew(VBoxContainer);
369 	dialog->add_child(vbc);
370 	//dialog->set_child_rect(vbc);
371 
372 	HBoxContainer *hbc = memnew(HBoxContainer);
373 	hbc->set_h_size_flags(SIZE_EXPAND_FILL);
374 	vbc->add_child(hbc);
375 
376 	add_button = memnew(Button);
377 	add_button->set_text(TTR("Add"));
378 	hbc->add_child(add_button);
379 	add_button->connect("pressed", this, "_add_button");
380 
381 	hbc->add_spacer();
382 
383 	del_button = memnew(Button);
384 	del_button->set_text(TTR("Delete"));
385 	hbc->add_child(del_button);
386 	del_button->connect("pressed", this, "_delete_button");
387 
388 	property_editor = memnew(EditorInspector);
389 	vbc->add_child(property_editor);
390 	property_editor->set_v_size_flags(SIZE_EXPAND_FILL);
391 }
392 
~ItemListEditor()393 ItemListEditor::~ItemListEditor() {
394 
395 	for (int i = 0; i < item_plugins.size(); i++)
396 		memdelete(item_plugins[i]);
397 }
398 
edit(Object * p_object)399 void ItemListEditorPlugin::edit(Object *p_object) {
400 
401 	item_list_editor->edit(Object::cast_to<Node>(p_object));
402 }
403 
handles(Object * p_object) const404 bool ItemListEditorPlugin::handles(Object *p_object) const {
405 
406 	return item_list_editor->handles(p_object);
407 }
408 
make_visible(bool p_visible)409 void ItemListEditorPlugin::make_visible(bool p_visible) {
410 
411 	if (p_visible) {
412 		item_list_editor->show();
413 	} else {
414 
415 		item_list_editor->hide();
416 		item_list_editor->edit(NULL);
417 	}
418 }
419 
ItemListEditorPlugin(EditorNode * p_node)420 ItemListEditorPlugin::ItemListEditorPlugin(EditorNode *p_node) {
421 
422 	editor = p_node;
423 	item_list_editor = memnew(ItemListEditor);
424 	CanvasItemEditor::get_singleton()->add_control_to_menu_panel(item_list_editor);
425 
426 	item_list_editor->hide();
427 	item_list_editor->add_plugin(memnew(ItemListOptionButtonPlugin));
428 	item_list_editor->add_plugin(memnew(ItemListPopupMenuPlugin));
429 	item_list_editor->add_plugin(memnew(ItemListItemListPlugin));
430 }
431 
~ItemListEditorPlugin()432 ItemListEditorPlugin::~ItemListEditorPlugin() {
433 }
434