1 /*
2    Copyright (C) 2003 - 2018 by David White <dave@whitevine.net>
3    Part of the Battle for Wesnoth Project https://www.wesnoth.org/
4 
5    This program is free software; you can redistribute it and/or modify
6    it under the terms of the GNU General Public License as published by
7    the Free Software Foundation; either version 2 of the License, or
8    (at your option) any later version.
9    This program is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY.
11 
12    See the COPYING file for more details.
13 */
14 
15 #include "hotkey/command_executor.hpp"
16 #include "hotkey/hotkey_item.hpp"
17 
18 #include "gui/dialogs/lua_interpreter.hpp"
19 #include "gui/dialogs/message.hpp"
20 #include "gui/dialogs/screenshot_notification.hpp"
21 #include "gui/dialogs/transient_message.hpp"
22 #include "gui/dialogs/drop_down_menu.hpp"
23 #include "gui/widgets/retval.hpp"
24 #include "filesystem.hpp"
25 #include "gettext.hpp"
26 #include "log.hpp"
27 #include "preferences/general.hpp"
28 #include "game_end_exceptions.hpp"
29 #include "display.hpp"
30 #include "quit_confirmation.hpp"
31 #include "sdl/surface.hpp"
32 #include "show_dialog.hpp"
33 #include "../resources.hpp"
34 #include "../playmp_controller.hpp"
35 
36 #include "utils/functional.hpp"
37 
38 #include <SDL2/SDL_image.h>
39 
40 #include <cassert>
41 #include <ios>
42 #include <set>
43 
44 static lg::log_domain log_config("config");
45 static lg::log_domain log_hotkey("hotkey");
46 #define ERR_G  LOG_STREAM(err,   lg::general())
47 #define WRN_G  LOG_STREAM(warn,   lg::general())
48 #define LOG_G  LOG_STREAM(info,  lg::general())
49 #define DBG_G  LOG_STREAM(debug, lg::general())
50 #define ERR_CF LOG_STREAM(err,   log_config)
51 #define LOG_HK LOG_STREAM(info, log_hotkey)
52 
53 namespace {
54 
make_screenshot(const std::string & name,bool map_screenshot)55 void make_screenshot(const std::string& name, bool map_screenshot)
56 {
57 	surface screenshot = display::get_singleton()->screenshot(map_screenshot);
58 	if(screenshot) {
59 		std::string filename = filesystem::get_screenshot_dir() + "/" + name + "_";
60 		filename = filesystem::get_next_filename(filename, ".png");
61 		gui2::dialogs::screenshot_notification::display(filename, screenshot);
62 	}
63 }
64 }
65 namespace hotkey {
66 
67 static void event_queue(const SDL_Event& event, command_executor* executor);
68 
do_execute_command(const hotkey_command & cmd,int,bool press,bool release)69 bool command_executor::do_execute_command(const hotkey_command&  cmd, int /*index*/, bool press, bool release)
70 {
71 	// hotkey release handling
72 	if (release) {
73 		switch(cmd.id) {
74 			// release a scroll key, un-apply scrolling in the given direction
75 			case HOTKEY_SCROLL_UP:
76 				scroll_up(false);
77 				break;
78 			case HOTKEY_SCROLL_DOWN:
79 				scroll_down(false);
80 				break;
81 			case HOTKEY_SCROLL_LEFT:
82 				scroll_left(false);
83 				break;
84 			case HOTKEY_SCROLL_RIGHT:
85 				scroll_right(false);
86 				break;
87 			default:
88 				return false; // nothing else handles a hotkey release
89 		}
90 
91 		return true;
92 	}
93 
94 	// handling of hotkeys which activate even on hold events
95 	switch(cmd.id) {
96 		case HOTKEY_REPEAT_RECRUIT:
97 			repeat_recruit();
98 			return true;
99 		case HOTKEY_SCROLL_UP:
100 			scroll_up(true);
101 			return true;
102 		case HOTKEY_SCROLL_DOWN:
103 			scroll_down(true);
104 			return true;
105 		case HOTKEY_SCROLL_LEFT:
106 			scroll_left(true);
107 			return true;
108 		case HOTKEY_SCROLL_RIGHT:
109 			scroll_right(true);
110 			return true;
111 		default:
112 			break;
113 	}
114 
115 	if(!press) {
116 		return false; // nothing else handles hotkey hold events
117 	}
118 
119 	// hotkey press handling
120 	switch(cmd.id) {
121 		case HOTKEY_CYCLE_UNITS:
122 			cycle_units();
123 			break;
124 		case HOTKEY_CYCLE_BACK_UNITS:
125 			cycle_back_units();
126 			break;
127 		case HOTKEY_ENDTURN:
128 			end_turn();
129 			break;
130 		case HOTKEY_UNIT_HOLD_POSITION:
131 			unit_hold_position();
132 			break;
133 		case HOTKEY_END_UNIT_TURN:
134 			end_unit_turn();
135 			break;
136 		case HOTKEY_LEADER:
137 			goto_leader();
138 			break;
139 		case HOTKEY_UNDO:
140 			undo();
141 			break;
142 		case HOTKEY_REDO:
143 			redo();
144 			break;
145 		case HOTKEY_TERRAIN_DESCRIPTION:
146 			terrain_description();
147 			break;
148 		case HOTKEY_UNIT_DESCRIPTION:
149 			unit_description();
150 			break;
151 		case HOTKEY_RENAME_UNIT:
152 			rename_unit();
153 			break;
154 		case HOTKEY_SAVE_GAME:
155 			save_game();
156 			break;
157 		case HOTKEY_SAVE_REPLAY:
158 			save_replay();
159 			break;
160 		case HOTKEY_SAVE_MAP:
161 			save_map();
162 			break;
163 		case HOTKEY_LOAD_GAME:
164 			load_game();
165 			break;
166 		case HOTKEY_TOGGLE_ELLIPSES:
167 			toggle_ellipses();
168 			break;
169 		case HOTKEY_TOGGLE_GRID:
170 			toggle_grid();
171 			break;
172 		case HOTKEY_STATUS_TABLE:
173 			status_table();
174 			break;
175 		case HOTKEY_RECALL:
176 			recall();
177 			break;
178 		case HOTKEY_LABEL_SETTINGS:
179 			label_settings();
180 			break;
181 		case HOTKEY_RECRUIT:
182 			recruit();
183 			break;
184 		case HOTKEY_SPEAK:
185 			speak();
186 			break;
187 		case HOTKEY_SPEAK_ALLY:
188 			whisper();
189 			break;
190 		case HOTKEY_SPEAK_ALL:
191 			shout();
192 			break;
193 		case HOTKEY_CREATE_UNIT:
194 			create_unit();
195 			break;
196 		case HOTKEY_CHANGE_SIDE:
197 			change_side();
198 			break;
199 		case HOTKEY_KILL_UNIT:
200 			kill_unit();
201 			break;
202 		case HOTKEY_PREFERENCES:
203 			preferences();
204 			break;
205 		case HOTKEY_OBJECTIVES:
206 			objectives();
207 			break;
208 		case HOTKEY_UNIT_LIST:
209 			unit_list();
210 			break;
211 		case HOTKEY_STATISTICS:
212 			show_statistics();
213 			break;
214 		case HOTKEY_STOP_NETWORK:
215 			stop_network();
216 			break;
217 		case HOTKEY_START_NETWORK:
218 			start_network();
219 			break;
220 		case HOTKEY_LABEL_TEAM_TERRAIN:
221 			label_terrain(true);
222 			break;
223 		case HOTKEY_LABEL_TERRAIN:
224 			label_terrain(false);
225 			break;
226 		case HOTKEY_CLEAR_LABELS:
227 			clear_labels();
228 			break;
229 		case HOTKEY_SHOW_ENEMY_MOVES:
230 			show_enemy_moves(false);
231 			break;
232 		case HOTKEY_BEST_ENEMY_MOVES:
233 			show_enemy_moves(true);
234 			break;
235 		case HOTKEY_DELAY_SHROUD:
236 			toggle_shroud_updates();
237 			break;
238 		case HOTKEY_UPDATE_SHROUD:
239 			update_shroud_now();
240 			break;
241 		case HOTKEY_CONTINUE_MOVE:
242 			continue_move();
243 			break;
244 		case HOTKEY_SEARCH:
245 			search();
246 			break;
247 		case HOTKEY_HELP:
248 			show_help();
249 			break;
250 		case HOTKEY_CHAT_LOG:
251 			show_chat_log();
252 			break;
253 		case HOTKEY_USER_CMD:
254 			user_command();
255 			break;
256 		case HOTKEY_CUSTOM_CMD:
257 			custom_command();
258 			break;
259 		case HOTKEY_AI_FORMULA:
260 			ai_formula();
261 			break;
262 		case HOTKEY_CLEAR_MSG:
263 			clear_messages();
264 			break;
265 		case HOTKEY_LANGUAGE:
266 			change_language();
267 			break;
268 		case HOTKEY_REPLAY_PLAY:
269 			play_replay();
270 			break;
271 		case HOTKEY_REPLAY_RESET:
272 			reset_replay();
273 			break;
274 		case HOTKEY_REPLAY_STOP:
275 			stop_replay();
276 			break;
277 		case HOTKEY_REPLAY_NEXT_TURN:
278 			replay_next_turn();
279 			break;
280 		case HOTKEY_REPLAY_NEXT_SIDE:
281 			replay_next_side();
282 			break;
283 		case HOTKEY_REPLAY_NEXT_MOVE:
284 			replay_next_move();
285 			break;
286 		case HOTKEY_REPLAY_SHOW_EVERYTHING:
287 			replay_show_everything();
288 			break;
289 		case HOTKEY_REPLAY_SHOW_EACH:
290 			replay_show_each();
291 			break;
292 		case HOTKEY_REPLAY_SHOW_TEAM1:
293 			replay_show_team1();
294 			break;
295 		case HOTKEY_REPLAY_SKIP_ANIMATION:
296 			replay_skip_animation();
297 			break;
298 		case HOTKEY_REPLAY_EXIT:
299 			replay_exit();
300 			break;
301 		case HOTKEY_WB_TOGGLE:
302 			whiteboard_toggle();
303 			break;
304 		case HOTKEY_WB_EXECUTE_ACTION:
305 			whiteboard_execute_action();
306 			break;
307 		case HOTKEY_WB_EXECUTE_ALL_ACTIONS:
308 			whiteboard_execute_all_actions();
309 			break;
310 		case HOTKEY_WB_DELETE_ACTION:
311 			whiteboard_delete_action();
312 			break;
313 		case HOTKEY_WB_BUMP_UP_ACTION:
314 			whiteboard_bump_up_action();
315 			break;
316 		case HOTKEY_WB_BUMP_DOWN_ACTION:
317 			whiteboard_bump_down_action();
318 			break;
319 		case HOTKEY_WB_SUPPOSE_DEAD:
320 			whiteboard_suppose_dead();
321 			break;
322 		case HOTKEY_SELECT_HEX:
323 			select_hex();
324 			break;
325 		case HOTKEY_DESELECT_HEX:
326 			deselect_hex();
327 			break;
328 		case HOTKEY_MOVE_ACTION:
329 			move_action();
330 			break;
331 		case HOTKEY_SELECT_AND_ACTION:
332 			select_and_action();
333 			break;
334 		case HOTKEY_TOUCH_HEX:
335 			touch_hex();
336 			break;
337 		case HOTKEY_ACCELERATED:
338 			toggle_accelerated_speed();
339 			break;
340 		case LUA_CONSOLE:
341 			lua_console();
342 			break;
343 		case HOTKEY_ZOOM_IN:
344 			zoom_in();
345 			break;
346 		case HOTKEY_ZOOM_OUT:
347 			zoom_out();
348 			break;
349 		case HOTKEY_ZOOM_DEFAULT:
350 			zoom_default();
351 			break;
352 		case HOTKEY_MAP_SCREENSHOT:
353 			map_screenshot();
354 			break;
355 		case HOTKEY_QUIT_TO_DESKTOP:
356 			quit_confirmation::quit_to_desktop();
357 			break;
358 		case HOTKEY_QUIT_GAME:
359 			quit_confirmation::quit_to_title();
360 			break;
361 		case HOTKEY_SURRENDER:
362 			surrender_game();
363 			break;
364 		case HOTKEY_MINIMAP_DRAW_TERRAIN:
365 			preferences::toggle_minimap_draw_terrain();
366 			recalculate_minimap();
367 			break;
368 		case HOTKEY_MINIMAP_CODING_TERRAIN:
369 			preferences::toggle_minimap_terrain_coding();
370 			recalculate_minimap();
371 			break;
372 		case HOTKEY_MINIMAP_CODING_UNIT:
373 			preferences::toggle_minimap_movement_coding();
374 			recalculate_minimap();
375 			break;
376 		case HOTKEY_MINIMAP_DRAW_UNITS:
377 			preferences::toggle_minimap_draw_units();
378 			recalculate_minimap();
379 			break;
380 		case HOTKEY_MINIMAP_DRAW_VILLAGES:
381 			preferences::toggle_minimap_draw_villages();
382 			recalculate_minimap();
383 			break;
384 		default:
385 			return false;
386 	}
387 	return true;
388 }
389 
surrender_game()390 void command_executor::surrender_game() {
391 	if(gui2::show_message(_("Surrender"), _("Do you really want to surrender the game?"), gui2::dialogs::message::yes_no_buttons) != gui2::retval::CANCEL) {
392 		playmp_controller* pmc = dynamic_cast<playmp_controller*>(resources::controller);
393 		if(pmc && !pmc->is_linger_mode() && !pmc->is_observer()) {
394 			pmc->surrender(display::get_singleton()->viewing_team());
395 		}
396 	}
397 }
398 
show_menu(const std::vector<config> & items_arg,int xloc,int yloc,bool,display & gui)399 void command_executor::show_menu(const std::vector<config>& items_arg, int xloc, int yloc, bool /*context_menu*/, display& gui)
400 {
401 	std::vector<config> items = items_arg;
402 	if (items.empty()) return;
403 
404 	get_menu_images(gui, items);
405 
406 	int res = -1;
407 	{
408 		SDL_Rect pos {xloc, yloc, 1, 1};
409 		gui2::dialogs::drop_down_menu mmenu(pos, items, -1, true, false); // TODO: last value should be variable
410 		if(mmenu.show()) {
411 			res = mmenu.selected_item();
412 		}
413 	} // This will kill the dialog.
414 	if (res < 0 || size_t(res) >= items.size()) return;
415 
416 	const theme::menu* submenu = gui.get_theme().get_menu_item(items[res]["id"]);
417 	if (submenu) {
418 		int y,x;
419 		SDL_GetMouseState(&x,&y);
420 		this->show_menu(submenu->items(), x, y, submenu->is_context(), gui);
421 	} else {
422 		const hotkey::hotkey_command& cmd = hotkey::get_hotkey_command(items[res]["id"]);
423 		do_execute_command(cmd, res);
424 		set_button_state();
425 	}
426 }
427 
execute_action(const std::vector<std::string> & items_arg,int,int,bool,display &)428 void command_executor::execute_action(const std::vector<std::string>& items_arg, int /*xloc*/, int /*yloc*/, bool /*context_menu*/, display&)
429 {
430 	std::vector<std::string> items = items_arg;
431 	if (items.empty()) {
432 		return;
433 	}
434 
435 	std::vector<std::string>::iterator i = items.begin();
436 	while(i != items.end()) {
437 		const hotkey_command &command = hotkey::get_hotkey_command(*i);
438 		if (can_execute_command(command)) {
439 			do_execute_command(command);
440 			set_button_state();
441 		}
442 		++i;
443 	}
444 }
445 
get_menu_image(display & disp,const std::string & command,int index) const446 std::string command_executor::get_menu_image(display& disp, const std::string& command, int index) const {
447 
448 	// TODO: Find a way to do away with the fugly special markup
449 	if(command[0] == '&') {
450 		size_t n = command.find_first_of('=');
451 		if(n != std::string::npos)
452 			return command.substr(1, n - 1);
453 	}
454 
455 	const std::string base_image_name = "icons/action/" + command + "_25.png";
456 	const std::string pressed_image_name = "icons/action/" + command + "_25-pressed.png";
457 
458 	const hotkey::HOTKEY_COMMAND hk = hotkey::get_id(command);
459 	const hotkey::ACTION_STATE state = get_action_state(hk, index);
460 
461 	const theme::menu* menu = disp.get_theme().get_menu_item(command);
462 	if (menu) {
463 		return "icons/arrows/short_arrow_right_25.png~CROP(3,3,18,18)"; // TODO should not be hardcoded
464 	}
465 
466 	if (filesystem::file_exists(game_config::path + "/images/" + base_image_name)) {
467 		switch (state) {
468 			case ACTION_ON:
469 			case ACTION_SELECTED:
470 				return pressed_image_name + "~CROP(3,3,18,18)";
471 			default:
472 				return base_image_name + "~CROP(3,3,18,18)";
473 		}
474 	}
475 
476 	switch (get_action_state(hk, index)) {
477 		case ACTION_ON:
478 			return game_config::images::checked_menu;
479 		case ACTION_OFF:
480 			return game_config::images::unchecked_menu;
481 		case ACTION_SELECTED:
482 			return game_config::images::selected_menu;
483 		case ACTION_DESELECTED:
484 			return game_config::images::deselected_menu;
485 		default: return get_action_image(hk, index);
486 	}
487 }
488 
get_menu_images(display & disp,std::vector<config> & items)489 void command_executor::get_menu_images(display& disp, std::vector<config>& items)
490 {
491 	for(size_t i = 0; i < items.size(); ++i) {
492 		config& item = items[i];
493 
494 		const std::string& item_id = item["id"];
495 		const hotkey::HOTKEY_COMMAND hk = hotkey::get_id(item_id);
496 
497 		//see if this menu item has an associated image
498 		std::string img(get_menu_image(disp, item_id, i));
499 		if (img.empty() == false) {
500 			item["icon"] = img;
501 		}
502 
503 		const theme::menu* menu = disp.get_theme().get_menu_item(item_id);
504 		if(menu) {
505 			item["label"] = menu->title();
506 		} else if(hk != hotkey::HOTKEY_NULL) {
507 			std::string desc = hotkey::get_description(item_id);
508 			if(hk == HOTKEY_ENDTURN) {
509 				const theme::action *b = disp.get_theme().get_action_item("button-endturn");
510 				if (b) {
511 					desc = b->title();
512 				}
513 			}
514 
515 			item["label"] = desc;
516 			item["details"] = hotkey::get_names(item_id);
517 		} else if(item["label"].empty()) {
518 			// If no matching hotkey was found and a custom label wasn't already set, treat
519 			// the id as a plaintext description. This is because either type of value can
520 			// be written to the id field by the WMI manager. The plaintext description is
521 			// used in the case the menu item specifies the relevant entry is *not* a hotkey.
522 			item["label"] = item_id;
523 		}
524 	}
525 }
526 
mbutton_event(const SDL_Event & event,command_executor * executor)527 void mbutton_event(const SDL_Event& event, command_executor* executor)
528 {
529 	event_queue(event, executor);
530 
531 	/* Run mouse events immediately.
532 
533 	This is necessary because the sidebar doesn't allow set_button_state() to be called after a
534 	button has received the mouse press event but before it has received the mouse release event.
535 	When https://github.com/wesnoth/wesnoth/pull/2872 delayed the processing of input events,
536 	set_button_state() ended up being called at such a time. However, if we run the event handlers
537 	now, the button (if any) hasn't received the press event yet and we can call set_button_state()
538 	safely.
539 
540 	See https://github.com/wesnoth/wesnoth/issues/2884 */
541 
542 	run_events(executor);
543 }
544 
jbutton_event(const SDL_Event & event,command_executor * executor)545 void jbutton_event(const SDL_Event& event, command_executor* executor)
546 {
547 	event_queue(event, executor);
548 }
549 
jhat_event(const SDL_Event & event,command_executor * executor)550 void jhat_event(const SDL_Event& event, command_executor* executor)
551 {
552 	event_queue(event, executor);
553 }
554 
key_event(const SDL_Event & event,command_executor * executor)555 void key_event(const SDL_Event& event, command_executor* executor)
556 {
557 	if (!executor) return;
558 	event_queue(event,executor);
559 }
560 
keyup_event(const SDL_Event &,command_executor * executor)561 void keyup_event(const SDL_Event&, command_executor* executor)
562 {
563 	if(!executor) return;
564 	executor->handle_keyup();
565 }
566 
run_events(command_executor * executor)567 void run_events(command_executor* executor)
568 {
569 	if(!executor) return;
570 	bool commands_ran = executor->run_queued_commands();
571 	if(commands_ran) {
572 		executor->set_button_state();
573 	}
574 }
575 
event_queue(const SDL_Event & event,command_executor * executor)576 static void event_queue(const SDL_Event& event, command_executor* executor)
577 {
578 	if (!executor) return;
579 	executor->queue_command(event);
580 }
581 
queue_command(const SDL_Event & event,int index)582 void command_executor::queue_command(const SDL_Event& event, int index)
583 {
584 	LOG_HK << "event 0x" << std::hex << event.type << std::dec << std::endl;
585 	if(event.type == SDL_TEXTINPUT) {
586 		LOG_HK << "SDL_TEXTINPUT \"" << event.text.text << "\"\n";
587 	}
588 
589 	const hotkey_ptr hk = get_hotkey(event);
590 	if(!hk->active() || hk->is_disabled()) {
591 		return;
592 	}
593 
594 	const hotkey_command& command = hotkey::get_hotkey_command(hk->get_command());
595 	bool keypress = (event.type == SDL_KEYDOWN || event.type == SDL_TEXTINPUT) &&
596 		!press_event_sent_;
597 	bool press = keypress ||
598 		(event.type == SDL_JOYBUTTONDOWN || event.type == SDL_MOUSEBUTTONDOWN || event.type == SDL_FINGERDOWN);
599 	bool release = event.type == SDL_KEYUP;
600 	if(press) {
601 		LOG_HK << "sending press event (keypress = " <<
602 			std::boolalpha << keypress << std::noboolalpha << ")\n";
603 	}
604 	if(keypress) {
605 		press_event_sent_ = true;
606 	}
607 
608 	command_queue_.emplace_back(command, index, press, release);
609 }
610 
execute_command_wrap(const command_executor::queued_command & command)611 void command_executor::execute_command_wrap(const command_executor::queued_command& command)
612 {
613 	if (!can_execute_command(*command.command, command.index)
614 			|| do_execute_command(*command.command, command.index, command.press, command.release)) {
615 		return;
616 	}
617 
618 	if (!command.press) {
619 		return; // none of the commands here respond to a key release
620     }
621 
622 	switch (command.command->id) {
623 		case HOTKEY_FULLSCREEN:
624 			CVideo::get_singleton().toggle_fullscreen();
625 			break;
626 		case HOTKEY_SCREENSHOT:
627 			make_screenshot(_("Screenshot"), false);
628 			break;
629 		case HOTKEY_ANIMATE_MAP:
630 			preferences::set_animate_map(!preferences::animate_map());
631 			break;
632 		case HOTKEY_MOUSE_SCROLL:
633 			preferences::enable_mouse_scroll(!preferences::mouse_scroll_enabled());
634 			break;
635 		case HOTKEY_MUTE:
636 			{
637 				// look if both is not playing
638 				static struct before_muted_s
639 				{
640 					bool playing_sound,playing_music;
641 					before_muted_s() : playing_sound(false),playing_music(false){}
642 				} before_muted;
643 				if (preferences::music_on() || preferences::sound_on())
644 				{
645 					// then remember settings and mute both
646 					before_muted.playing_sound = preferences::sound_on();
647 					before_muted.playing_music = preferences::music_on();
648 					preferences::set_sound(false);
649 					preferences::set_music(false);
650 				}
651 				else
652 				{
653 					// then set settings before mute
654 					preferences::set_sound(before_muted.playing_sound);
655 					preferences::set_music(before_muted.playing_music);
656 				}
657 			}
658 			break;
659 		default:
660 			DBG_G << "command_executor: unknown command number " << command.command->id << ", ignoring.\n";
661 			break;
662 	}
663 }
664 
665 // Removes duplicate commands caused by both SDL_KEYDOWN and SDL_TEXTINPUT triggering hotkeys.
666 // See https://github.com/wesnoth/wesnoth/issues/1736
filter_command_queue()667 std::vector<command_executor::queued_command> command_executor::filter_command_queue()
668 {
669 	std::vector<queued_command> filtered_commands;
670 
671 	/** A command plus "key released" flag. Otherwise, we will filter out key releases that are preceded by a keypress. */
672 	using command_with_keyrelease = std::pair<const hotkey_command*, bool>;
673 	std::set<command_with_keyrelease> seen_commands;
674 
675 	for(const queued_command& cmd : command_queue_) {
676 		command_with_keyrelease command_key(cmd.command, cmd.release);
677 		if(seen_commands.find(command_key) == seen_commands.end()) {
678 			seen_commands.insert(command_key);
679 			filtered_commands.push_back(cmd);
680 		}
681 	}
682 
683 	command_queue_.clear();
684 
685 	return filtered_commands;
686 }
687 
run_queued_commands()688 bool command_executor::run_queued_commands()
689 {
690 	std::vector<queued_command> commands = filter_command_queue();
691 	for(const queued_command& cmd : commands) {
692 		execute_command_wrap(cmd);
693 	}
694 
695 	return !commands.empty();
696 }
697 
set_button_state()698 void command_executor_default::set_button_state()
699 {
700 	display& disp = get_display();
701 	for (const theme::menu& menu : disp.get_theme().menus()) {
702 
703 		std::shared_ptr<gui::button> button = disp.find_menu_button(menu.get_id());
704 		if (!button) continue;
705 		bool enabled = false;
706 		for (const auto& command : menu.items()) {
707 
708 			const hotkey::hotkey_command& command_obj = hotkey::get_hotkey_command(command["id"]);
709 			bool can_execute = can_execute_command(command_obj);
710 			if (can_execute) {
711 				enabled = true;
712 				break;
713 			}
714 		}
715 		button->enable(enabled);
716 	}
717 
718 	for (const theme::action& action : disp.get_theme().actions()) {
719 
720 		std::shared_ptr<gui::button> button = disp.find_action_button(action.get_id());
721 		if (!button) continue;
722 		bool enabled = false;
723 		int i = 0;
724 		for (const std::string& command : action.items()) {
725 
726 			const hotkey::hotkey_command& command_obj = hotkey::get_hotkey_command(command);
727 			std::string tooltip = action.tooltip(i);
728 			if (filesystem::file_exists(game_config::path + "/images/icons/action/" + command + "_25.png" ))
729 				button->set_overlay("icons/action/" + command);
730 			if (!tooltip.empty())
731 				button->set_tooltip_string(tooltip);
732 
733 			bool can_execute = can_execute_command(command_obj);
734 			i++;
735 			if (!can_execute) continue;
736 			enabled = true;
737 
738 			ACTION_STATE state = get_action_state(command_obj.id, -1);
739 			switch (state) {
740 			case ACTION_SELECTED:
741 			case ACTION_ON:
742 				button->set_check(true);
743 				break;
744 			case ACTION_OFF:
745 			case ACTION_DESELECTED:
746 				button->set_check(false);
747 				break;
748 			case ACTION_STATELESS:
749 				break;
750 			default:
751 				break;
752 			}
753 
754 			break;
755 		}
756 		button->enable(enabled);
757 	}
758 }
759 
recalculate_minimap()760 void command_executor_default::recalculate_minimap()
761 {
762 	get_display().recalculate_minimap();
763 }
764 
lua_console()765 void command_executor_default::lua_console()
766 {
767 	if (get_display().in_game()) {
768 		gui2::dialogs::lua_interpreter::display(gui2::dialogs::lua_interpreter::GAME);
769 	} else {
770 		command_executor::lua_console();
771 	}
772 
773 }
774 
lua_console()775 void command_executor::lua_console()
776 {
777 	gui2::dialogs::lua_interpreter::display(gui2::dialogs::lua_interpreter::APP);
778 }
779 
zoom_in()780 void command_executor_default::zoom_in()
781 {
782 	if(!get_display().view_locked()) {
783 		get_display().set_zoom(true);
784 	}
785 }
786 
zoom_out()787 void command_executor_default::zoom_out()
788 {
789 	if(!get_display().view_locked()) {
790 		get_display().set_zoom(false);
791 	}
792 }
793 
zoom_default()794 void command_executor_default::zoom_default()
795 {
796 	if(!get_display().view_locked()) {
797 		get_display().set_default_zoom();
798 	}
799 }
800 
map_screenshot()801 void command_executor_default::map_screenshot()
802 {
803 	make_screenshot(_("Map-Screenshot"), true);
804 }
805 }
806