1 /*************************************************************************/
2 /*  popup_menu.cpp                                                       */
3 /*************************************************************************/
4 /*                       This file is part of:                           */
5 /*                           GODOT ENGINE                                */
6 /*                      https://godotengine.org                          */
7 /*************************************************************************/
8 /* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur.                 */
9 /* Copyright (c) 2014-2019 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 #include "popup_menu.h"
31 #include "os/input.h"
32 #include "os/keyboard.h"
33 #include "print_string.h"
34 #include "translation.h"
35 
_get_accel_text(int p_item) const36 String PopupMenu::_get_accel_text(int p_item) const {
37 
38 	ERR_FAIL_INDEX_V(p_item, items.size(), String());
39 
40 	if (items[p_item].shortcut.is_valid())
41 		return items[p_item].shortcut->get_as_text();
42 	else if (items[p_item].accel)
43 		return keycode_get_string(items[p_item].accel);
44 	return String();
45 
46 	/*
47 	String atxt;
48 	if (p_accel&KEY_MASK_SHIFT)
49 		atxt+="Shift+";
50 	if (p_accel&KEY_MASK_ALT)
51 		atxt+="Alt+";
52 	if (p_accel&KEY_MASK_CTRL)
53 		atxt+="Ctrl+";
54 	if (p_accel&KEY_MASK_META)
55 		atxt+="Meta+";
56 
57 	p_accel&=KEY_CODE_MASK;
58 
59 	atxt+=String::chr(p_accel).to_upper();
60 
61 	return atxt;
62 */
63 }
64 
get_minimum_size() const65 Size2 PopupMenu::get_minimum_size() const {
66 
67 	int vseparation = get_constant("vseparation");
68 	int hseparation = get_constant("hseparation");
69 
70 	Size2 minsize = get_stylebox("panel")->get_minimum_size();
71 	Ref<Font> font = get_font("font");
72 
73 	float max_w = 0;
74 	int font_h = font->get_height();
75 	int check_w = get_icon("checked")->get_width();
76 	int accel_max_w = 0;
77 
78 	for (int i = 0; i < items.size(); i++) {
79 
80 		Size2 size;
81 		if (!items[i].icon.is_null()) {
82 
83 			Size2 icon_size = items[i].icon->get_size();
84 			size.height = MAX(icon_size.height, font_h);
85 			size.width += icon_size.width;
86 			size.width += hseparation;
87 		} else {
88 
89 			size.height = font_h;
90 		}
91 
92 		if (items[i].checkable) {
93 
94 			size.width += check_w + hseparation;
95 		}
96 
97 		String text = items[i].shortcut.is_valid() ? String(tr(items[i].shortcut->get_name())) : items[i].text;
98 		size.width += font->get_string_size(text).width;
99 		if (i > 0)
100 			size.height += vseparation;
101 
102 		if (items[i].accel || (items[i].shortcut.is_valid() && items[i].shortcut->is_valid())) {
103 
104 			int accel_w = hseparation * 2;
105 			accel_w += font->get_string_size(_get_accel_text(i)).width;
106 			accel_max_w = MAX(accel_w, accel_max_w);
107 		}
108 
109 		if (items[i].submenu != "") {
110 
111 			size.width += get_icon("submenu")->get_width();
112 		}
113 
114 		minsize.height += size.height;
115 		max_w = MAX(max_w, size.width);
116 	}
117 
118 	minsize.width += max_w + accel_max_w;
119 
120 	return minsize;
121 }
122 
_get_mouse_over(const Point2 & p_over) const123 int PopupMenu::_get_mouse_over(const Point2 &p_over) const {
124 
125 	if (p_over.x < 0 || p_over.x >= get_size().width)
126 		return -1;
127 
128 	Ref<StyleBox> style = get_stylebox("panel");
129 
130 	Point2 ofs = style->get_offset();
131 
132 	if (ofs.y > p_over.y)
133 		return -1;
134 
135 	Ref<Font> font = get_font("font");
136 	int vseparation = get_constant("vseparation");
137 	//	int hseparation = get_constant("hseparation");
138 	float font_h = font->get_height();
139 
140 	for (int i = 0; i < items.size(); i++) {
141 
142 		if (i > 0)
143 			ofs.y += vseparation;
144 		float h;
145 
146 		if (!items[i].icon.is_null()) {
147 
148 			Size2 icon_size = items[i].icon->get_size();
149 			h = MAX(icon_size.height, font_h);
150 		} else {
151 
152 			h = font_h;
153 		}
154 
155 		ofs.y += h;
156 		if (p_over.y < ofs.y) {
157 			return i;
158 		}
159 	}
160 
161 	return -1;
162 }
163 
_activate_submenu(int over)164 void PopupMenu::_activate_submenu(int over) {
165 
166 	Node *n = get_node(items[over].submenu);
167 	ERR_EXPLAIN("item subnode does not exist: " + items[over].submenu);
168 	ERR_FAIL_COND(!n);
169 	Popup *pm = n->cast_to<Popup>();
170 	ERR_EXPLAIN("item subnode is not a Popup: " + items[over].submenu);
171 	ERR_FAIL_COND(!pm);
172 	if (pm->is_visible())
173 		return; //already visible!
174 
175 	Point2 p = get_global_pos();
176 	Rect2 pr(p, get_size());
177 	Ref<StyleBox> style = get_stylebox("panel");
178 
179 	Point2 pos = p + Point2(get_size().width, items[over]._ofs_cache - style->get_offset().y);
180 	Size2 size = pm->get_size();
181 	// fix pos
182 	if (pos.x + size.width > get_viewport_rect().size.width)
183 		pos.x = p.x - size.width;
184 
185 	pm->set_pos(pos);
186 	pm->popup();
187 
188 	PopupMenu *pum = pm->cast_to<PopupMenu>();
189 	if (pum) {
190 
191 		pr.pos -= pum->get_global_pos();
192 		pum->clear_autohide_areas();
193 		pum->add_autohide_area(Rect2(pr.pos.x, pr.pos.y, pr.size.x, items[over]._ofs_cache));
194 		if (over < items.size() - 1) {
195 			int from = items[over + 1]._ofs_cache;
196 			pum->add_autohide_area(Rect2(pr.pos.x, pr.pos.y + from, pr.size.x, pr.size.y - from));
197 		}
198 	}
199 }
200 
_submenu_timeout()201 void PopupMenu::_submenu_timeout() {
202 
203 	if (mouse_over == submenu_over) {
204 		_activate_submenu(mouse_over);
205 		submenu_over = -1;
206 	}
207 }
208 
_input_event(const InputEvent & p_event)209 void PopupMenu::_input_event(const InputEvent &p_event) {
210 
211 	switch (p_event.type) {
212 
213 		case InputEvent::KEY: {
214 
215 			if (!p_event.key.pressed)
216 				break;
217 
218 			switch (p_event.key.scancode) {
219 
220 				case KEY_DOWN: {
221 
222 					for (int i = mouse_over + 1; i < items.size(); i++) {
223 
224 						if (i < 0 || i >= items.size())
225 							continue;
226 
227 						if (!items[i].separator && !items[i].disabled) {
228 
229 							mouse_over = i;
230 							update();
231 							break;
232 						}
233 					}
234 				} break;
235 				case KEY_UP: {
236 
237 					for (int i = mouse_over - 1; i >= 0; i--) {
238 
239 						if (i < 0 || i >= items.size())
240 							continue;
241 
242 						if (!items[i].separator && !items[i].disabled) {
243 
244 							mouse_over = i;
245 							update();
246 							break;
247 						}
248 					}
249 				} break;
250 				case KEY_RETURN:
251 				case KEY_ENTER: {
252 
253 					if (mouse_over >= 0 && mouse_over < items.size() && !items[mouse_over].separator) {
254 
255 						activate_item(mouse_over);
256 					}
257 				} break;
258 			}
259 
260 		} break;
261 
262 		case InputEvent::MOUSE_BUTTON: {
263 
264 			const InputEventMouseButton &b = p_event.mouse_button;
265 			if (b.pressed)
266 				break;
267 
268 			switch (b.button_index) {
269 
270 				case BUTTON_WHEEL_DOWN: {
271 
272 					if (get_global_pos().y + get_size().y > get_viewport_rect().size.y) {
273 
274 						int vseparation = get_constant("vseparation");
275 						Ref<Font> font = get_font("font");
276 
277 						Point2 pos = get_pos();
278 						int s = (vseparation + font->get_height()) * 3;
279 						pos.y -= (s * b.factor);
280 						set_pos(pos);
281 
282 						//update hover
283 						InputEvent ie;
284 						ie.type = InputEvent::MOUSE_MOTION;
285 						ie.mouse_motion.x = b.x;
286 						ie.mouse_motion.y = b.y + s;
287 						_input_event(ie);
288 					}
289 				} break;
290 				case BUTTON_WHEEL_UP: {
291 
292 					if (get_global_pos().y < 0) {
293 
294 						int vseparation = get_constant("vseparation");
295 						Ref<Font> font = get_font("font");
296 
297 						Point2 pos = get_pos();
298 						int s = (vseparation + font->get_height()) * 3;
299 						pos.y += (s * b.factor);
300 						set_pos(pos);
301 
302 						//update hover
303 						InputEvent ie;
304 						ie.type = InputEvent::MOUSE_MOTION;
305 						ie.mouse_motion.x = b.x;
306 						ie.mouse_motion.y = b.y - s;
307 						_input_event(ie);
308 					}
309 				} break;
310 				case BUTTON_LEFT: {
311 
312 					int over = _get_mouse_over(Point2(b.x, b.y));
313 
314 					if (invalidated_click) {
315 						invalidated_click = false;
316 						break;
317 					}
318 					if (over < 0) {
319 						hide();
320 						break; //non-activable
321 					}
322 
323 					if (items[over].separator || items[over].disabled)
324 						break;
325 
326 					if (items[over].submenu != "") {
327 
328 						_activate_submenu(over);
329 						return;
330 					}
331 					activate_item(over);
332 
333 				} break;
334 			}
335 
336 			//update();
337 		} break;
338 		case InputEvent::MOUSE_MOTION: {
339 
340 			if (invalidated_click) {
341 				moved += Vector2(p_event.mouse_motion.relative_x, p_event.mouse_motion.relative_y);
342 				if (moved.length() > 4)
343 					invalidated_click = false;
344 			}
345 
346 			const InputEventMouseMotion &m = p_event.mouse_motion;
347 			for (List<Rect2>::Element *E = autohide_areas.front(); E; E = E->next()) {
348 
349 				if (!Rect2(Point2(), get_size()).has_point(Point2(m.x, m.y)) && E->get().has_point(Point2(m.x, m.y))) {
350 					call_deferred("hide");
351 					return;
352 				}
353 			}
354 
355 			int over = _get_mouse_over(Point2(m.x, m.y));
356 			int id = (over < 0 || items[over].separator || items[over].disabled) ? -1 : (items[over].ID >= 0 ? items[over].ID : over);
357 
358 			if (id < 0) {
359 				mouse_over = -1;
360 				update();
361 				break;
362 			}
363 
364 			if (items[over].submenu != "" && submenu_over != over) {
365 				submenu_over = over;
366 				submenu_timer->start();
367 			}
368 
369 			if (over != mouse_over) {
370 				mouse_over = over;
371 				update();
372 			}
373 		} break;
374 	}
375 }
376 
has_point(const Point2 & p_point) const377 bool PopupMenu::has_point(const Point2 &p_point) const {
378 
379 	if (parent_rect.has_point(p_point))
380 		return true;
381 	for (const List<Rect2>::Element *E = autohide_areas.front(); E; E = E->next()) {
382 
383 		if (E->get().has_point(p_point))
384 			return true;
385 	}
386 
387 	return Control::has_point(p_point);
388 }
389 
_notification(int p_what)390 void PopupMenu::_notification(int p_what) {
391 
392 	switch (p_what) {
393 
394 		case NOTIFICATION_DRAW: {
395 
396 			RID ci = get_canvas_item();
397 			Size2 size = get_size();
398 
399 			Ref<StyleBox> style = get_stylebox("panel");
400 			Ref<StyleBox> hover = get_stylebox("hover");
401 			Ref<Font> font = get_font("font");
402 			Ref<Texture> check = get_icon("checked");
403 			Ref<Texture> uncheck = get_icon("unchecked");
404 			Ref<Texture> submenu = get_icon("submenu");
405 			Ref<StyleBox> separator = get_stylebox("separator");
406 
407 			style->draw(ci, Rect2(Point2(), get_size()));
408 			Point2 ofs = style->get_offset();
409 			int vseparation = get_constant("vseparation");
410 			int hseparation = get_constant("hseparation");
411 			Color font_color = get_color("font_color");
412 			Color font_color_disabled = get_color("font_color_disabled");
413 			Color font_color_accel = get_color("font_color_accel");
414 			Color font_color_hover = get_color("font_color_hover");
415 			float font_h = font->get_height();
416 
417 			for (int i = 0; i < items.size(); i++) {
418 
419 				if (i > 0)
420 					ofs.y += vseparation;
421 				Point2 item_ofs = ofs;
422 				float h;
423 				Size2 icon_size;
424 
425 				if (!items[i].icon.is_null()) {
426 
427 					icon_size = items[i].icon->get_size();
428 					h = MAX(icon_size.height, font_h);
429 				} else {
430 
431 					h = font_h;
432 				}
433 
434 				if (i == mouse_over) {
435 
436 					hover->draw(ci, Rect2(ofs + Point2(-hseparation, -vseparation), Size2(get_size().width - style->get_minimum_size().width + hseparation * 2, h + vseparation * 2)));
437 				}
438 
439 				if (items[i].separator) {
440 
441 					int sep_h = separator->get_center_size().height + separator->get_minimum_size().height;
442 					separator->draw(ci, Rect2(ofs + Point2(0, Math::floor((h - sep_h) / 2.0)), Size2(get_size().width - style->get_minimum_size().width, sep_h)));
443 				}
444 
445 				if (items[i].checkable) {
446 
447 					if (items[i].checked)
448 						check->draw(ci, item_ofs + Point2(0, Math::floor((h - check->get_height()) / 2.0)));
449 					else
450 						uncheck->draw(ci, item_ofs + Point2(0, Math::floor((h - check->get_height()) / 2.0)));
451 
452 					item_ofs.x += check->get_width() + hseparation;
453 				}
454 
455 				if (!items[i].icon.is_null()) {
456 					items[i].icon->draw(ci, item_ofs + Point2(0, Math::floor((h - icon_size.height) / 2.0)));
457 					item_ofs.x += items[i].icon->get_width();
458 					item_ofs.x += hseparation;
459 				}
460 
461 				if (items[i].submenu != "") {
462 					submenu->draw(ci, Point2(size.width - style->get_margin(MARGIN_RIGHT) - submenu->get_width(), item_ofs.y + Math::floor(h - submenu->get_height()) / 2));
463 				}
464 
465 				item_ofs.y += font->get_ascent();
466 				String text = items[i].shortcut.is_valid() ? String(tr(items[i].shortcut->get_name())) : items[i].text;
467 				if (!items[i].separator) {
468 
469 					font->draw(ci, item_ofs + Point2(0, Math::floor((h - font_h) / 2.0)), text, items[i].disabled ? font_color_disabled : (i == mouse_over ? font_color_hover : font_color));
470 				}
471 
472 				if (items[i].accel || (items[i].shortcut.is_valid() && items[i].shortcut->is_valid())) {
473 					//accelerator
474 					String text = _get_accel_text(i);
475 					item_ofs.x = size.width - style->get_margin(MARGIN_RIGHT) - font->get_string_size(text).width;
476 					font->draw(ci, item_ofs + Point2(0, Math::floor((h - font_h) / 2.0)), text, i == mouse_over ? font_color_hover : font_color_accel);
477 				}
478 
479 				items[i]._ofs_cache = ofs.y;
480 
481 				ofs.y += h;
482 			}
483 
484 		} break;
485 		case NOTIFICATION_MOUSE_ENTER: {
486 
487 			grab_focus();
488 		} break;
489 		case NOTIFICATION_MOUSE_EXIT: {
490 
491 			if (mouse_over >= 0) {
492 				mouse_over = -1;
493 				update();
494 			}
495 		} break;
496 	}
497 }
498 
add_icon_item(const Ref<Texture> & p_icon,const String & p_label,int p_ID,uint32_t p_accel)499 void PopupMenu::add_icon_item(const Ref<Texture> &p_icon, const String &p_label, int p_ID, uint32_t p_accel) {
500 
501 	Item item;
502 	item.icon = p_icon;
503 	item.text = p_label;
504 	item.accel = p_accel;
505 	item.ID = p_ID;
506 	items.push_back(item);
507 	update();
508 }
add_item(const String & p_label,int p_ID,uint32_t p_accel)509 void PopupMenu::add_item(const String &p_label, int p_ID, uint32_t p_accel) {
510 
511 	Item item;
512 	item.text = XL_MESSAGE(p_label);
513 	item.accel = p_accel;
514 	item.ID = p_ID;
515 	items.push_back(item);
516 	update();
517 }
518 
add_submenu_item(const String & p_label,const String & p_submenu,int p_ID)519 void PopupMenu::add_submenu_item(const String &p_label, const String &p_submenu, int p_ID) {
520 
521 	Item item;
522 	item.text = XL_MESSAGE(p_label);
523 	item.ID = p_ID;
524 	item.submenu = p_submenu;
525 	items.push_back(item);
526 	update();
527 }
528 
add_icon_check_item(const Ref<Texture> & p_icon,const String & p_label,int p_ID,uint32_t p_accel)529 void PopupMenu::add_icon_check_item(const Ref<Texture> &p_icon, const String &p_label, int p_ID, uint32_t p_accel) {
530 
531 	Item item;
532 	item.icon = p_icon;
533 	item.text = XL_MESSAGE(p_label);
534 	item.accel = p_accel;
535 	item.ID = p_ID;
536 	item.checkable = true;
537 	items.push_back(item);
538 	update();
539 }
add_check_item(const String & p_label,int p_ID,uint32_t p_accel)540 void PopupMenu::add_check_item(const String &p_label, int p_ID, uint32_t p_accel) {
541 
542 	Item item;
543 	item.text = XL_MESSAGE(p_label);
544 	item.accel = p_accel;
545 	item.ID = p_ID;
546 	item.checkable = true;
547 	items.push_back(item);
548 	update();
549 }
550 
add_icon_shortcut(const Ref<Texture> & p_icon,const Ref<ShortCut> & p_shortcut,int p_ID)551 void PopupMenu::add_icon_shortcut(const Ref<Texture> &p_icon, const Ref<ShortCut> &p_shortcut, int p_ID) {
552 
553 	ERR_FAIL_COND(p_shortcut.is_null());
554 
555 	_ref_shortcut(p_shortcut);
556 
557 	Item item;
558 	item.ID = p_ID;
559 	item.icon = p_icon;
560 	item.shortcut = p_shortcut;
561 	items.push_back(item);
562 	update();
563 }
564 
add_shortcut(const Ref<ShortCut> & p_shortcut,int p_ID)565 void PopupMenu::add_shortcut(const Ref<ShortCut> &p_shortcut, int p_ID) {
566 
567 	ERR_FAIL_COND(p_shortcut.is_null());
568 
569 	_ref_shortcut(p_shortcut);
570 
571 	Item item;
572 	item.ID = p_ID;
573 	item.shortcut = p_shortcut;
574 	items.push_back(item);
575 	update();
576 }
add_icon_check_shortcut(const Ref<Texture> & p_icon,const Ref<ShortCut> & p_shortcut,int p_ID)577 void PopupMenu::add_icon_check_shortcut(const Ref<Texture> &p_icon, const Ref<ShortCut> &p_shortcut, int p_ID) {
578 
579 	ERR_FAIL_COND(p_shortcut.is_null());
580 
581 	_ref_shortcut(p_shortcut);
582 
583 	Item item;
584 	item.ID = p_ID;
585 	item.shortcut = p_shortcut;
586 	item.checkable = true;
587 	item.icon = p_icon;
588 	items.push_back(item);
589 	update();
590 }
591 
add_check_shortcut(const Ref<ShortCut> & p_shortcut,int p_ID)592 void PopupMenu::add_check_shortcut(const Ref<ShortCut> &p_shortcut, int p_ID) {
593 
594 	ERR_FAIL_COND(p_shortcut.is_null());
595 
596 	_ref_shortcut(p_shortcut);
597 
598 	Item item;
599 	item.ID = p_ID;
600 	item.shortcut = p_shortcut;
601 	item.checkable = true;
602 	items.push_back(item);
603 	update();
604 }
605 
set_item_text(int p_idx,const String & p_text)606 void PopupMenu::set_item_text(int p_idx, const String &p_text) {
607 
608 	ERR_FAIL_INDEX(p_idx, items.size());
609 	items[p_idx].text = XL_MESSAGE(p_text);
610 
611 	update();
612 }
set_item_icon(int p_idx,const Ref<Texture> & p_icon)613 void PopupMenu::set_item_icon(int p_idx, const Ref<Texture> &p_icon) {
614 
615 	ERR_FAIL_INDEX(p_idx, items.size());
616 	items[p_idx].icon = p_icon;
617 
618 	update();
619 }
set_item_checked(int p_idx,bool p_checked)620 void PopupMenu::set_item_checked(int p_idx, bool p_checked) {
621 
622 	ERR_FAIL_INDEX(p_idx, items.size());
623 
624 	items[p_idx].checked = p_checked;
625 
626 	update();
627 }
set_item_ID(int p_idx,int p_ID)628 void PopupMenu::set_item_ID(int p_idx, int p_ID) {
629 
630 	ERR_FAIL_INDEX(p_idx, items.size());
631 	items[p_idx].ID = p_ID;
632 
633 	update();
634 }
635 
set_item_accelerator(int p_idx,uint32_t p_accel)636 void PopupMenu::set_item_accelerator(int p_idx, uint32_t p_accel) {
637 
638 	ERR_FAIL_INDEX(p_idx, items.size());
639 	items[p_idx].accel = p_accel;
640 
641 	update();
642 }
643 
set_item_metadata(int p_idx,const Variant & p_meta)644 void PopupMenu::set_item_metadata(int p_idx, const Variant &p_meta) {
645 
646 	ERR_FAIL_INDEX(p_idx, items.size());
647 	items[p_idx].metadata = p_meta;
648 	update();
649 }
650 
set_item_disabled(int p_idx,bool p_disabled)651 void PopupMenu::set_item_disabled(int p_idx, bool p_disabled) {
652 
653 	ERR_FAIL_INDEX(p_idx, items.size());
654 	items[p_idx].disabled = p_disabled;
655 	update();
656 }
657 
set_item_submenu(int p_idx,const String & p_submenu)658 void PopupMenu::set_item_submenu(int p_idx, const String &p_submenu) {
659 
660 	ERR_FAIL_INDEX(p_idx, items.size());
661 	items[p_idx].submenu = p_submenu;
662 	update();
663 }
664 
toggle_item_checked(int p_idx)665 void PopupMenu::toggle_item_checked(int p_idx) {
666 
667 	ERR_FAIL_INDEX(p_idx, items.size());
668 	items[p_idx].checked = !items[p_idx].checked;
669 	update();
670 }
671 
get_item_text(int p_idx) const672 String PopupMenu::get_item_text(int p_idx) const {
673 
674 	ERR_FAIL_INDEX_V(p_idx, items.size(), "");
675 	return items[p_idx].text;
676 }
get_item_icon(int p_idx) const677 Ref<Texture> PopupMenu::get_item_icon(int p_idx) const {
678 
679 	ERR_FAIL_INDEX_V(p_idx, items.size(), Ref<Texture>());
680 	return items[p_idx].icon;
681 }
682 
get_item_accelerator(int p_idx) const683 uint32_t PopupMenu::get_item_accelerator(int p_idx) const {
684 
685 	ERR_FAIL_INDEX_V(p_idx, items.size(), 0);
686 	return items[p_idx].accel;
687 }
688 
get_item_metadata(int p_idx) const689 Variant PopupMenu::get_item_metadata(int p_idx) const {
690 
691 	ERR_FAIL_INDEX_V(p_idx, items.size(), Variant());
692 	return items[p_idx].metadata;
693 }
694 
is_item_disabled(int p_idx) const695 bool PopupMenu::is_item_disabled(int p_idx) const {
696 
697 	ERR_FAIL_INDEX_V(p_idx, items.size(), false);
698 	return items[p_idx].disabled;
699 }
700 
is_item_checked(int p_idx) const701 bool PopupMenu::is_item_checked(int p_idx) const {
702 
703 	ERR_FAIL_INDEX_V(p_idx, items.size(), false);
704 	return items[p_idx].checked;
705 }
706 
get_item_ID(int p_idx) const707 int PopupMenu::get_item_ID(int p_idx) const {
708 
709 	ERR_FAIL_INDEX_V(p_idx, items.size(), 0);
710 	return items[p_idx].ID;
711 }
712 
get_item_index(int p_ID) const713 int PopupMenu::get_item_index(int p_ID) const {
714 
715 	for (int i = 0; i < items.size(); i++) {
716 
717 		if (items[i].ID == p_ID)
718 			return i;
719 	}
720 
721 	return -1;
722 }
723 
get_item_submenu(int p_idx) const724 String PopupMenu::get_item_submenu(int p_idx) const {
725 
726 	ERR_FAIL_INDEX_V(p_idx, items.size(), "");
727 	return items[p_idx].submenu;
728 }
729 
get_item_tooltip(int p_idx) const730 String PopupMenu::get_item_tooltip(int p_idx) const {
731 
732 	ERR_FAIL_INDEX_V(p_idx, items.size(), "");
733 	return items[p_idx].tooltip;
734 }
735 
get_item_shortcut(int p_idx) const736 Ref<ShortCut> PopupMenu::get_item_shortcut(int p_idx) const {
737 
738 	ERR_FAIL_INDEX_V(p_idx, items.size(), Ref<ShortCut>());
739 	return items[p_idx].shortcut;
740 }
741 
set_item_as_separator(int p_idx,bool p_separator)742 void PopupMenu::set_item_as_separator(int p_idx, bool p_separator) {
743 
744 	ERR_FAIL_INDEX(p_idx, items.size());
745 	items[p_idx].separator = p_separator;
746 	update();
747 }
748 
is_item_separator(int p_idx) const749 bool PopupMenu::is_item_separator(int p_idx) const {
750 	ERR_FAIL_INDEX_V(p_idx, items.size(), false);
751 	return items[p_idx].separator;
752 }
753 
set_item_as_checkable(int p_idx,bool p_checkable)754 void PopupMenu::set_item_as_checkable(int p_idx, bool p_checkable) {
755 
756 	ERR_FAIL_INDEX(p_idx, items.size());
757 	items[p_idx].checkable = p_checkable;
758 	update();
759 }
760 
set_item_tooltip(int p_idx,const String & p_tooltip)761 void PopupMenu::set_item_tooltip(int p_idx, const String &p_tooltip) {
762 
763 	ERR_FAIL_INDEX(p_idx, items.size());
764 	items[p_idx].tooltip = p_tooltip;
765 	update();
766 }
767 
set_item_shortcut(int p_idx,const Ref<ShortCut> & p_shortcut)768 void PopupMenu::set_item_shortcut(int p_idx, const Ref<ShortCut> &p_shortcut) {
769 	ERR_FAIL_INDEX(p_idx, items.size());
770 	if (items[p_idx].shortcut.is_valid()) {
771 		_unref_shortcut(items[p_idx].shortcut);
772 	}
773 	items[p_idx].shortcut = p_shortcut;
774 
775 	if (items[p_idx].shortcut.is_valid()) {
776 		_ref_shortcut(items[p_idx].shortcut);
777 	}
778 
779 	update();
780 }
781 
is_item_checkable(int p_idx) const782 bool PopupMenu::is_item_checkable(int p_idx) const {
783 	ERR_FAIL_INDEX_V(p_idx, items.size(), false);
784 	return items[p_idx].checkable;
785 }
786 
get_item_count() const787 int PopupMenu::get_item_count() const {
788 
789 	return items.size();
790 }
791 
activate_item_by_event(const InputEvent & p_event)792 bool PopupMenu::activate_item_by_event(const InputEvent &p_event) {
793 
794 	uint32_t code = 0;
795 	if (p_event.type == InputEvent::KEY) {
796 		code = p_event.key.scancode;
797 		if (code == 0)
798 			code = p_event.key.unicode;
799 		if (p_event.key.mod.control)
800 			code |= KEY_MASK_CTRL;
801 		if (p_event.key.mod.alt)
802 			code |= KEY_MASK_ALT;
803 		if (p_event.key.mod.meta)
804 			code |= KEY_MASK_META;
805 		if (p_event.key.mod.shift)
806 			code |= KEY_MASK_SHIFT;
807 	}
808 
809 	int il = items.size();
810 	for (int i = 0; i < il; i++) {
811 		if (is_item_disabled(i))
812 			continue;
813 
814 		if (items[i].shortcut.is_valid() && items[i].shortcut->is_shortcut(p_event)) {
815 			activate_item(i);
816 			return true;
817 		}
818 
819 		if (code != 0 && items[i].accel == code) {
820 			activate_item(i);
821 			return true;
822 		}
823 
824 		if (items[i].submenu != "") {
825 			Node *n = get_node(items[i].submenu);
826 			if (!n)
827 				continue;
828 
829 			PopupMenu *pm = n->cast_to<PopupMenu>();
830 			if (!pm)
831 				continue;
832 
833 			if (pm->activate_item_by_event(p_event)) {
834 				return true;
835 			}
836 		}
837 	}
838 	return false;
839 }
840 
activate_item(int p_item)841 void PopupMenu::activate_item(int p_item) {
842 
843 	ERR_FAIL_INDEX(p_item, items.size());
844 	ERR_FAIL_COND(items[p_item].separator);
845 	int id = items[p_item].ID >= 0 ? items[p_item].ID : p_item;
846 	emit_signal("item_pressed", id);
847 
848 	//hide all parent PopupMenue's
849 	Node *next = get_parent();
850 	PopupMenu *pop = next->cast_to<PopupMenu>();
851 	while (pop) {
852 		// We close all parents that are chained together,
853 		// with hide_on_item_selection enabled
854 		if (hide_on_item_selection && pop->is_hide_on_item_selection()) {
855 			pop->hide();
856 			next = next->get_parent();
857 			pop = next->cast_to<PopupMenu>();
858 		} else {
859 			// Break out of loop when the next parent has
860 			// hide_on_item_selection disabled
861 			break;
862 		}
863 	}
864 	// Hides popup by default; unless otherwise specified
865 	// by using set_hide_on_item_selection
866 	if (hide_on_item_selection) {
867 		hide();
868 	}
869 }
870 
remove_item(int p_idx)871 void PopupMenu::remove_item(int p_idx) {
872 
873 	ERR_FAIL_INDEX(p_idx, items.size());
874 
875 	if (items[p_idx].shortcut.is_valid()) {
876 		_unref_shortcut(items[p_idx].shortcut);
877 	}
878 
879 	items.remove(p_idx);
880 	update();
881 }
882 
add_separator()883 void PopupMenu::add_separator() {
884 
885 	Item sep;
886 	sep.separator = true;
887 	sep.ID = -1;
888 	items.push_back(sep);
889 	update();
890 }
891 
clear()892 void PopupMenu::clear() {
893 
894 	for (int i = 0; i < items.size(); i++) {
895 		if (items[i].shortcut.is_valid()) {
896 			_unref_shortcut(items[i].shortcut);
897 		}
898 	}
899 	items.clear();
900 	mouse_over = -1;
901 	update();
902 }
903 
_get_items() const904 Array PopupMenu::_get_items() const {
905 
906 	Array items;
907 	for (int i = 0; i < get_item_count(); i++) {
908 
909 		items.push_back(get_item_text(i));
910 		items.push_back(get_item_icon(i));
911 		items.push_back(is_item_checkable(i));
912 		items.push_back(is_item_checked(i));
913 		items.push_back(is_item_disabled(i));
914 
915 		items.push_back(get_item_ID(i));
916 		items.push_back(get_item_accelerator(i));
917 		items.push_back(get_item_metadata(i));
918 		items.push_back(get_item_submenu(i));
919 		items.push_back(is_item_separator(i));
920 	}
921 
922 	return items;
923 }
924 
_ref_shortcut(Ref<ShortCut> p_sc)925 void PopupMenu::_ref_shortcut(Ref<ShortCut> p_sc) {
926 
927 	if (!shortcut_refcount.has(p_sc)) {
928 		shortcut_refcount[p_sc] = 1;
929 		p_sc->connect("changed", this, "update");
930 	} else {
931 		shortcut_refcount[p_sc] += 1;
932 	}
933 }
934 
_unref_shortcut(Ref<ShortCut> p_sc)935 void PopupMenu::_unref_shortcut(Ref<ShortCut> p_sc) {
936 
937 	ERR_FAIL_COND(!shortcut_refcount.has(p_sc));
938 	shortcut_refcount[p_sc]--;
939 	if (shortcut_refcount[p_sc] == 0) {
940 		p_sc->disconnect("changed", this, "update");
941 		shortcut_refcount.erase(p_sc);
942 	}
943 }
944 
_set_items(const Array & p_items)945 void PopupMenu::_set_items(const Array &p_items) {
946 
947 	ERR_FAIL_COND(p_items.size() % 10);
948 	clear();
949 
950 	for (int i = 0; i < p_items.size(); i += 10) {
951 
952 		String text = p_items[i + 0];
953 		Ref<Texture> icon = p_items[i + 1];
954 		bool checkable = p_items[i + 2];
955 		bool checked = p_items[i + 3];
956 		bool disabled = p_items[i + 4];
957 
958 		int id = p_items[i + 5];
959 		int accel = p_items[i + 6];
960 		Variant meta = p_items[i + 7];
961 		String subm = p_items[i + 8];
962 		bool sep = p_items[i + 9];
963 
964 		int idx = get_item_count();
965 		add_item(text, id);
966 		set_item_icon(idx, icon);
967 		set_item_as_checkable(idx, checkable);
968 		set_item_checked(idx, checked);
969 		set_item_disabled(idx, disabled);
970 		set_item_ID(idx, id);
971 		set_item_metadata(idx, meta);
972 		set_item_as_separator(idx, sep);
973 		set_item_accelerator(idx, accel);
974 		set_item_submenu(idx, subm);
975 	}
976 }
977 
978 // Hide on item selection determines whether or not the popup will close after item selection
set_hide_on_item_selection(bool p_enabled)979 void PopupMenu::set_hide_on_item_selection(bool p_enabled) {
980 
981 	hide_on_item_selection = p_enabled;
982 }
983 
is_hide_on_item_selection()984 bool PopupMenu::is_hide_on_item_selection() {
985 
986 	return hide_on_item_selection;
987 }
988 
get_tooltip(const Point2 & p_pos) const989 String PopupMenu::get_tooltip(const Point2 &p_pos) const {
990 
991 	int over = _get_mouse_over(p_pos);
992 	if (over < 0 || over >= items.size())
993 		return "";
994 	return items[over].tooltip;
995 }
996 
set_parent_rect(const Rect2 & p_rect)997 void PopupMenu::set_parent_rect(const Rect2 &p_rect) {
998 
999 	parent_rect = p_rect;
1000 }
1001 
get_translatable_strings(List<String> * p_strings) const1002 void PopupMenu::get_translatable_strings(List<String> *p_strings) const {
1003 
1004 	for (int i = 0; i < items.size(); i++) {
1005 
1006 		if (items[i].text != "")
1007 			p_strings->push_back(items[i].text);
1008 	}
1009 }
1010 
add_autohide_area(const Rect2 & p_area)1011 void PopupMenu::add_autohide_area(const Rect2 &p_area) {
1012 
1013 	autohide_areas.push_back(p_area);
1014 }
1015 
clear_autohide_areas()1016 void PopupMenu::clear_autohide_areas() {
1017 
1018 	autohide_areas.clear();
1019 }
1020 
_bind_methods()1021 void PopupMenu::_bind_methods() {
1022 
1023 	ObjectTypeDB::bind_method(_MD("_input_event"), &PopupMenu::_input_event);
1024 	ObjectTypeDB::bind_method(_MD("add_icon_item", "texture", "label", "id", "accel"), &PopupMenu::add_icon_item, DEFVAL(-1), DEFVAL(0));
1025 	ObjectTypeDB::bind_method(_MD("add_item", "label", "id", "accel"), &PopupMenu::add_item, DEFVAL(-1), DEFVAL(0));
1026 	ObjectTypeDB::bind_method(_MD("add_icon_check_item", "texture", "label", "id", "accel"), &PopupMenu::add_icon_check_item, DEFVAL(-1), DEFVAL(0));
1027 	ObjectTypeDB::bind_method(_MD("add_check_item", "label", "id", "accel"), &PopupMenu::add_check_item, DEFVAL(-1), DEFVAL(0));
1028 	ObjectTypeDB::bind_method(_MD("add_submenu_item", "label", "submenu", "id"), &PopupMenu::add_submenu_item, DEFVAL(-1));
1029 
1030 	ObjectTypeDB::bind_method(_MD("add_icon_shortcut", "texture", "shortcut:ShortCut", "id"), &PopupMenu::add_icon_shortcut, DEFVAL(-1));
1031 	ObjectTypeDB::bind_method(_MD("add_shortcut", "shortcut:ShortCut", "id"), &PopupMenu::add_shortcut, DEFVAL(-1));
1032 	ObjectTypeDB::bind_method(_MD("add_icon_check_shortcut", "texture", "shortcut:ShortCut", "id"), &PopupMenu::add_icon_check_shortcut, DEFVAL(-1));
1033 	ObjectTypeDB::bind_method(_MD("add_check_shortcut", "shortcut:ShortCut", "id"), &PopupMenu::add_check_shortcut, DEFVAL(-1));
1034 
1035 	ObjectTypeDB::bind_method(_MD("set_item_text", "idx", "text"), &PopupMenu::set_item_text);
1036 	ObjectTypeDB::bind_method(_MD("set_item_icon", "idx", "icon"), &PopupMenu::set_item_icon);
1037 	ObjectTypeDB::bind_method(_MD("set_item_checked", "idx", "checked"), &PopupMenu::set_item_checked);
1038 	ObjectTypeDB::bind_method(_MD("set_item_ID", "idx", "id"), &PopupMenu::set_item_ID);
1039 	ObjectTypeDB::bind_method(_MD("set_item_accelerator", "idx", "accel"), &PopupMenu::set_item_accelerator);
1040 	ObjectTypeDB::bind_method(_MD("set_item_metadata", "idx", "metadata"), &PopupMenu::set_item_metadata);
1041 	ObjectTypeDB::bind_method(_MD("set_item_disabled", "idx", "disabled"), &PopupMenu::set_item_disabled);
1042 	ObjectTypeDB::bind_method(_MD("set_item_submenu", "idx", "submenu"), &PopupMenu::set_item_submenu);
1043 	ObjectTypeDB::bind_method(_MD("set_item_as_separator", "idx", "enable"), &PopupMenu::set_item_as_separator);
1044 	ObjectTypeDB::bind_method(_MD("set_item_as_checkable", "idx", "enable"), &PopupMenu::set_item_as_checkable);
1045 	ObjectTypeDB::bind_method(_MD("set_item_tooltip", "idx", "tooltip"), &PopupMenu::set_item_tooltip);
1046 	ObjectTypeDB::bind_method(_MD("set_item_shortcut", "idx", "shortcut:ShortCut"), &PopupMenu::set_item_shortcut);
1047 
1048 	ObjectTypeDB::bind_method(_MD("toggle_item_checked", "idx"), &PopupMenu::toggle_item_checked);
1049 
1050 	ObjectTypeDB::bind_method(_MD("get_item_text", "idx"), &PopupMenu::get_item_text);
1051 	ObjectTypeDB::bind_method(_MD("get_item_icon", "idx"), &PopupMenu::get_item_icon);
1052 	ObjectTypeDB::bind_method(_MD("is_item_checked", "idx"), &PopupMenu::is_item_checked);
1053 	ObjectTypeDB::bind_method(_MD("get_item_ID", "idx"), &PopupMenu::get_item_ID);
1054 	ObjectTypeDB::bind_method(_MD("get_item_index", "id"), &PopupMenu::get_item_index);
1055 	ObjectTypeDB::bind_method(_MD("get_item_accelerator", "idx"), &PopupMenu::get_item_accelerator);
1056 	ObjectTypeDB::bind_method(_MD("get_item_metadata", "idx"), &PopupMenu::get_item_metadata);
1057 	ObjectTypeDB::bind_method(_MD("is_item_disabled", "idx"), &PopupMenu::is_item_disabled);
1058 	ObjectTypeDB::bind_method(_MD("get_item_submenu", "idx"), &PopupMenu::get_item_submenu);
1059 	ObjectTypeDB::bind_method(_MD("is_item_separator", "idx"), &PopupMenu::is_item_separator);
1060 	ObjectTypeDB::bind_method(_MD("is_item_checkable", "idx"), &PopupMenu::is_item_checkable);
1061 	ObjectTypeDB::bind_method(_MD("get_item_tooltip", "idx"), &PopupMenu::get_item_tooltip);
1062 	ObjectTypeDB::bind_method(_MD("get_item_shortcut:ShortCut", "idx"), &PopupMenu::get_item_shortcut);
1063 
1064 	ObjectTypeDB::bind_method(_MD("get_item_count"), &PopupMenu::get_item_count);
1065 
1066 	ObjectTypeDB::bind_method(_MD("remove_item", "idx"), &PopupMenu::remove_item);
1067 
1068 	ObjectTypeDB::bind_method(_MD("add_separator"), &PopupMenu::add_separator);
1069 	ObjectTypeDB::bind_method(_MD("clear"), &PopupMenu::clear);
1070 
1071 	ObjectTypeDB::bind_method(_MD("_set_items"), &PopupMenu::_set_items);
1072 	ObjectTypeDB::bind_method(_MD("_get_items"), &PopupMenu::_get_items);
1073 
1074 	ObjectTypeDB::bind_method(_MD("set_hide_on_item_selection", "enable"), &PopupMenu::set_hide_on_item_selection);
1075 	ObjectTypeDB::bind_method(_MD("is_hide_on_item_selection"), &PopupMenu::is_hide_on_item_selection);
1076 
1077 	ObjectTypeDB::bind_method(_MD("_submenu_timeout"), &PopupMenu::_submenu_timeout);
1078 
1079 	ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "items", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), _SCS("_set_items"), _SCS("_get_items"));
1080 	ADD_PROPERTYNO(PropertyInfo(Variant::BOOL, "hide_on_item_selection"), _SCS("set_hide_on_item_selection"), _SCS("is_hide_on_item_selection"));
1081 
1082 	ADD_SIGNAL(MethodInfo("item_pressed", PropertyInfo(Variant::INT, "ID")));
1083 }
1084 
set_invalidate_click_until_motion()1085 void PopupMenu::set_invalidate_click_until_motion() {
1086 	moved = Vector2();
1087 	invalidated_click = true;
1088 }
1089 
PopupMenu()1090 PopupMenu::PopupMenu() {
1091 
1092 	mouse_over = -1;
1093 
1094 	set_focus_mode(FOCUS_ALL);
1095 	set_as_toplevel(true);
1096 	set_hide_on_item_selection(true);
1097 
1098 	submenu_timer = memnew(Timer);
1099 	submenu_timer->set_wait_time(0.3);
1100 	submenu_timer->set_one_shot(true);
1101 	submenu_timer->connect("timeout", this, "_submenu_timeout");
1102 	add_child(submenu_timer);
1103 }
1104 
~PopupMenu()1105 PopupMenu::~PopupMenu() {
1106 }
1107