The Battle for Wesnoth  1.15.0-dev
command_executor.cpp
Go to the documentation of this file.
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 
16 #include "hotkey/hotkey_item.hpp"
17 
19 #include "gui/dialogs/message.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 "../resources.hpp"
33 #include "../playmp_controller.hpp"
34 
35 #include "utils/functional.hpp"
36 
37 #include <SDL_image.h>
38 
39 #include <cassert>
40 #include <ios>
41 #include <set>
42 
43 static lg::log_domain log_config("config");
44 static lg::log_domain log_hotkey("hotkey");
45 #define ERR_G LOG_STREAM(err, lg::general())
46 #define WRN_G LOG_STREAM(warn, lg::general())
47 #define LOG_G LOG_STREAM(info, lg::general())
48 #define DBG_G LOG_STREAM(debug, lg::general())
49 #define ERR_CF LOG_STREAM(err, log_config)
50 #define LOG_HK LOG_STREAM(info, log_hotkey)
51 
52 namespace {
53 
54 void make_screenshot(const std::string& name, bool map_screenshot)
55 {
56  surface screenshot = display::get_singleton()->screenshot(map_screenshot);
57  if(!screenshot.null()) {
58  std::string filename = filesystem::get_screenshot_dir() + "/" + name + "_";
59  filename = filesystem::get_next_filename(filename, ".png");
60  gui2::dialogs::screenshot_notification::display(filename, screenshot);
61  }
62 }
63 }
64 namespace hotkey {
65 
66 static void event_queue(const SDL_Event& event, command_executor* executor);
67 
68 bool command_executor::do_execute_command(const hotkey_command& cmd, int /*index*/, bool press, bool release)
69 {
70  // hotkey release handling
71  if (release) {
72  switch(cmd.id) {
73  // release a scroll key, un-apply scrolling in the given direction
74  case HOTKEY_SCROLL_UP:
75  scroll_up(false);
76  break;
77  case HOTKEY_SCROLL_DOWN:
78  scroll_down(false);
79  break;
80  case HOTKEY_SCROLL_LEFT:
81  scroll_left(false);
82  break;
84  scroll_right(false);
85  break;
86  default:
87  return false; // nothing else handles a hotkey release
88  }
89 
90  return true;
91  }
92 
93  // handling of hotkeys which activate even on hold events
94  switch(cmd.id) {
97  return true;
98  case HOTKEY_SCROLL_UP:
99  scroll_up(true);
100  return true;
101  case HOTKEY_SCROLL_DOWN:
102  scroll_down(true);
103  return true;
104  case HOTKEY_SCROLL_LEFT:
105  scroll_left(true);
106  return true;
107  case HOTKEY_SCROLL_RIGHT:
108  scroll_right(true);
109  return true;
110  default:
111  break;
112  }
113 
114  if(!press) {
115  return false; // nothing else handles hotkey hold events
116  }
117 
118  // hotkey press handling
119  switch(cmd.id) {
120  case HOTKEY_CYCLE_UNITS:
121  cycle_units();
122  break;
125  break;
126  case HOTKEY_ENDTURN:
127  end_turn();
128  break;
131  break;
133  end_unit_turn();
134  break;
135  case HOTKEY_LEADER:
136  goto_leader();
137  break;
138  case HOTKEY_UNDO:
139  undo();
140  break;
141  case HOTKEY_REDO:
142  redo();
143  break;
146  break;
149  break;
150  case HOTKEY_RENAME_UNIT:
151  rename_unit();
152  break;
153  case HOTKEY_SAVE_GAME:
154  save_game();
155  break;
156  case HOTKEY_SAVE_REPLAY:
157  save_replay();
158  break;
159  case HOTKEY_SAVE_MAP:
160  save_map();
161  break;
162  case HOTKEY_LOAD_GAME:
163  load_game();
164  break;
166  toggle_ellipses();
167  break;
168  case HOTKEY_TOGGLE_GRID:
169  toggle_grid();
170  break;
171  case HOTKEY_STATUS_TABLE:
172  status_table();
173  break;
174  case HOTKEY_RECALL:
175  recall();
176  break;
178  label_settings();
179  break;
180  case HOTKEY_RECRUIT:
181  recruit();
182  break;
183  case HOTKEY_SPEAK:
184  speak();
185  break;
186  case HOTKEY_SPEAK_ALLY:
187  whisper();
188  break;
189  case HOTKEY_SPEAK_ALL:
190  shout();
191  break;
192  case HOTKEY_CREATE_UNIT:
193  create_unit();
194  break;
195  case HOTKEY_CHANGE_SIDE:
196  change_side();
197  break;
198  case HOTKEY_KILL_UNIT:
199  kill_unit();
200  break;
201  case HOTKEY_PREFERENCES:
202  preferences();
203  break;
204  case HOTKEY_OBJECTIVES:
205  objectives();
206  break;
207  case HOTKEY_UNIT_LIST:
208  unit_list();
209  break;
210  case HOTKEY_STATISTICS:
211  show_statistics();
212  break;
213  case HOTKEY_STOP_NETWORK:
214  stop_network();
215  break;
217  start_network();
218  break;
220  label_terrain(true);
221  break;
223  label_terrain(false);
224  break;
225  case HOTKEY_CLEAR_LABELS:
226  clear_labels();
227  break;
229  show_enemy_moves(false);
230  break;
232  show_enemy_moves(true);
233  break;
234  case HOTKEY_DELAY_SHROUD:
236  break;
239  break;
241  continue_move();
242  break;
243  case HOTKEY_SEARCH:
244  search();
245  break;
246  case HOTKEY_HELP:
247  show_help();
248  break;
249  case HOTKEY_CHAT_LOG:
250  show_chat_log();
251  break;
252  case HOTKEY_USER_CMD:
253  user_command();
254  break;
255  case HOTKEY_CUSTOM_CMD:
256  custom_command();
257  break;
258  case HOTKEY_AI_FORMULA:
259  ai_formula();
260  break;
261  case HOTKEY_CLEAR_MSG:
262  clear_messages();
263  break;
264  case HOTKEY_LANGUAGE:
265  change_language();
266  break;
267  case HOTKEY_REPLAY_PLAY:
268  play_replay();
269  break;
270  case HOTKEY_REPLAY_RESET:
271  reset_replay();
272  break;
273  case HOTKEY_REPLAY_STOP:
274  stop_replay();
275  break;
278  break;
281  break;
284  break;
287  break;
290  break;
293  break;
296  break;
297  case HOTKEY_REPLAY_EXIT:
298  replay_exit();
299  break;
300  case HOTKEY_WB_TOGGLE:
302  break;
305  break;
308  break;
311  break;
314  break;
317  break;
320  break;
321  case HOTKEY_SELECT_HEX:
322  select_hex();
323  break;
324  case HOTKEY_DESELECT_HEX:
325  deselect_hex();
326  break;
327  case HOTKEY_MOVE_ACTION:
328  move_action();
329  break;
332  break;
333  case HOTKEY_ACCELERATED:
335  break;
336  case LUA_CONSOLE:
337  lua_console();
338  break;
339  case HOTKEY_ZOOM_IN:
340  zoom_in();
341  break;
342  case HOTKEY_ZOOM_OUT:
343  zoom_out();
344  break;
345  case HOTKEY_ZOOM_DEFAULT:
346  zoom_default();
347  break;
349  map_screenshot();
350  break;
353  break;
354  case HOTKEY_QUIT_GAME:
356  break;
357  case HOTKEY_SURRENDER:
358  surrender_game();
359  break;
362  break;
365  break;
368  break;
371  break;
374  break;
375  default:
376  return false;
377  }
378  return true;
379 }
380 
382  if(gui2::show_message(_("Surrender"), _("Do you really want to surrender the game?"), gui2::dialogs::message::yes_no_buttons) != gui2::retval::CANCEL) {
384  if(pmc && !pmc->is_linger_mode() && !pmc->is_observer()) {
385  pmc->surrender(display::get_singleton()->viewing_team());
386  }
387  }
388 }
389 
390 void command_executor::show_menu(const std::vector<config>& items_arg, int xloc, int yloc, bool /*context_menu*/, display& gui)
391 {
392  std::vector<config> items = items_arg;
393  if (items.empty()) return;
394 
395  get_menu_images(gui, items);
396 
397  int res = -1;
398  {
399  SDL_Rect pos {xloc, yloc, 1, 1};
400  gui2::dialogs::drop_down_menu mmenu(pos, items, -1, true, false); // TODO: last value should be variable
401  if(mmenu.show()) {
402  res = mmenu.selected_item();
403  }
404  } // This will kill the dialog.
405  if (res < 0 || std::size_t(res) >= items.size()) return;
406 
407  const theme::menu* submenu = gui.get_theme().get_menu_item(items[res]["id"]);
408  if (submenu) {
409  int y,x;
410  SDL_GetMouseState(&x,&y);
411  this->show_menu(submenu->items(), x, y, submenu->is_context(), gui);
412  } else {
413  const hotkey::hotkey_command& cmd = hotkey::get_hotkey_command(items[res]["id"]);
414  do_execute_command(cmd, res);
415  }
416 }
417 
418 void command_executor::execute_action(const std::vector<std::string>& items_arg, int /*xloc*/, int /*yloc*/, bool /*context_menu*/, display&)
419 {
420  std::vector<std::string> items = items_arg;
421  if (items.empty()) {
422  return;
423  }
424 
425  std::vector<std::string>::iterator i = items.begin();
426  while(i != items.end()) {
427  const hotkey_command &command = hotkey::get_hotkey_command(*i);
428  if (can_execute_command(command)) {
429  do_execute_command(command);
430  }
431  ++i;
432  }
433 }
434 
435 std::string command_executor::get_menu_image(display& disp, const std::string& command, int index) const {
436 
437  // TODO: Find a way to do away with the fugly special markup
438  if(command[0] == '&') {
439  std::size_t n = command.find_first_of('=');
440  if(n != std::string::npos)
441  return command.substr(1, n - 1);
442  }
443 
444  const std::string base_image_name = "icons/action/" + command + "_25.png";
445  const std::string pressed_image_name = "icons/action/" + command + "_25-pressed.png";
446 
447  const hotkey::HOTKEY_COMMAND hk = hotkey::get_id(command);
448  const hotkey::ACTION_STATE state = get_action_state(hk, index);
449 
450  const theme::menu* menu = disp.get_theme().get_menu_item(command);
451  if (menu) {
452  return "icons/arrows/short_arrow_right_25.png~CROP(3,3,18,18)"; // TODO should not be hardcoded
453  }
454 
455  if (filesystem::file_exists(game_config::path + "/images/" + base_image_name)) {
456  switch (state) {
457  case ACTION_ON:
458  case ACTION_SELECTED:
459  return pressed_image_name + "~CROP(3,3,18,18)";
460  default:
461  return base_image_name + "~CROP(3,3,18,18)";
462  }
463  }
464 
465  switch (get_action_state(hk, index)) {
466  case ACTION_ON:
468  case ACTION_OFF:
470  case ACTION_SELECTED:
472  case ACTION_DESELECTED:
474  default: return get_action_image(hk, index);
475  }
476 }
477 
478 void command_executor::get_menu_images(display& disp, std::vector<config>& items)
479 {
480  for(std::size_t i = 0; i < items.size(); ++i) {
481  config& item = items[i];
482 
483  const std::string& item_id = item["id"];
484  const hotkey::HOTKEY_COMMAND hk = hotkey::get_id(item_id);
485 
486  //see if this menu item has an associated image
487  std::string img(get_menu_image(disp, item_id, i));
488  if (img.empty() == false) {
489  item["icon"] = img;
490  }
491 
492  const theme::menu* menu = disp.get_theme().get_menu_item(item_id);
493  if(menu) {
494  item["label"] = menu->title();
495  } else if(hk != hotkey::HOTKEY_NULL) {
496  std::string desc = hotkey::get_description(item_id);
497  if(hk == HOTKEY_ENDTURN) {
498  const theme::action *b = disp.get_theme().get_action_item("button-endturn");
499  if (b) {
500  desc = b->title();
501  }
502  }
503 
504  item["label"] = desc;
505  item["details"] = hotkey::get_names(item_id);
506  } else if(item["label"].empty()) {
507  // If no matching hotkey was found and a custom label wasn't already set, treat
508  // the id as a plaintext description. This is because either type of value can
509  // be written to the id field by the WMI manager. The plaintext description is
510  // used in the case the menu item specifies the relevant entry is *not* a hotkey.
511  item["label"] = item_id;
512  }
513  }
514 }
515 
516 void mbutton_event(const SDL_Event& event, command_executor* executor)
517 {
518  event_queue(event, executor);
519 }
520 
521 void jbutton_event(const SDL_Event& event, command_executor* executor)
522 {
523  event_queue(event, executor);
524 }
525 
526 void jhat_event(const SDL_Event& event, command_executor* executor)
527 {
528  event_queue(event, executor);
529 }
530 
531 void key_event(const SDL_Event& event, command_executor* executor)
532 {
533  if (!executor) return;
534  event_queue(event,executor);
535 }
536 
537 void keyup_event(const SDL_Event&, command_executor* executor)
538 {
539  if(!executor) return;
540  executor->handle_keyup();
541 }
542 
544 {
545  if(!executor) return;
546  executor->run_queued_commands();
547 }
548 
549 static void event_queue(const SDL_Event& event, command_executor* executor)
550 {
551  if (!executor) return;
552  executor->queue_command(event);
553 }
554 
555 void command_executor::queue_command(const SDL_Event& event, int index)
556 {
557  LOG_HK << "event 0x" << std::hex << event.type << std::dec << std::endl;
558  if(event.type == SDL_TEXTINPUT) {
559  LOG_HK << "SDL_TEXTINPUT \"" << event.text.text << "\"\n";
560  }
561 
562  const hotkey_ptr hk = get_hotkey(event);
563  if(!hk->active() || hk->is_disabled()) {
564  return;
565  }
566 
567  const hotkey_command& command = hotkey::get_hotkey_command(hk->get_command());
568  bool keypress = (event.type == SDL_KEYDOWN || event.type == SDL_TEXTINPUT) &&
570  bool press = keypress ||
571  (event.type == SDL_JOYBUTTONDOWN || event.type == SDL_MOUSEBUTTONDOWN);
572  bool release = event.type == SDL_KEYUP;
573  if(press) {
574  LOG_HK << "sending press event (keypress = " <<
575  std::boolalpha << keypress << std::noboolalpha << ")\n";
576  }
577  if(keypress) {
578  press_event_sent_ = true;
579  }
580 
581  command_queue_.emplace_back(command, index, press, release);
582 }
583 
585 {
586  if (!can_execute_command(*command.command, command.index)
587  || do_execute_command(*command.command, command.index, command.press, command.release)) {
588  return;
589  }
590 
591  if (!command.press) {
592  return; // none of the commands here respond to a key release
593  }
594 
595  switch (command.command->id) {
596  case HOTKEY_FULLSCREEN:
598  break;
599  case HOTKEY_SCREENSHOT:
600  make_screenshot(_("Screenshot"), false);
601  break;
602  case HOTKEY_ANIMATE_MAP:
604  break;
605  case HOTKEY_MOUSE_SCROLL:
607  break;
608  case HOTKEY_MUTE:
609  {
610  // look if both is not playing
611  static struct before_muted_s
612  {
613  bool playing_sound,playing_music;
614  before_muted_s() : playing_sound(false),playing_music(false){}
615  } before_muted;
617  {
618  // then remember settings and mute both
619  before_muted.playing_sound = preferences::sound_on();
620  before_muted.playing_music = preferences::music_on();
621  preferences::set_sound(false);
622  preferences::set_music(false);
623  }
624  else
625  {
626  // then set settings before mute
627  preferences::set_sound(before_muted.playing_sound);
628  preferences::set_music(before_muted.playing_music);
629  }
630  }
631  break;
632  default:
633  DBG_G << "command_executor: unknown command number " << command.command->id << ", ignoring.\n";
634  break;
635  }
636 }
637 
638 // Removes duplicate commands caused by both SDL_KEYDOWN and SDL_TEXTINPUT triggering hotkeys.
639 // See https://github.com/wesnoth/wesnoth/issues/1736
640 std::vector<command_executor::queued_command> command_executor::filter_command_queue()
641 {
642  std::vector<queued_command> filtered_commands;
643 
644  /** A command plus "key released" flag. Otherwise, we will filter out key releases that are preceded by a keypress. */
645  using command_with_keyrelease = std::pair<const hotkey_command*, bool>;
646  std::set<command_with_keyrelease> seen_commands;
647 
648  for(const queued_command& cmd : command_queue_) {
649  command_with_keyrelease command_key(cmd.command, cmd.release);
650  if(seen_commands.find(command_key) == seen_commands.end()) {
651  seen_commands.insert(command_key);
652  filtered_commands.push_back(cmd);
653  }
654  }
655 
656  command_queue_.clear();
657 
658  return filtered_commands;
659 }
660 
662 {
663  std::vector<queued_command> commands = filter_command_queue();
664  for(const queued_command& cmd : commands) {
666  }
667 }
668 
670 {
671 }
672 
674 {
675  if (get_display().in_game()) {
677  } else {
679  }
680 
681 }
682 
684 {
686 }
687 
689 {
690  if(!get_display().view_locked()) {
691  get_display().set_zoom(true);
692  }
693 }
694 
696 {
697  if(!get_display().view_locked()) {
698  get_display().set_zoom(false);
699  }
700 }
701 
703 {
704  if(!get_display().view_locked()) {
705  get_display().set_default_zoom();
706  }
707 }
708 
710 {
711  make_screenshot(_("Map-Screenshot"), true);
712 }
713 }
#define LOG_HK
play_controller * controller
Definition: resources.cpp:21
Dialog was closed with the CANCEL button.
Definition: retval.hpp:37
bool set_sound(bool ison)
Definition: general.cpp:665
virtual void toggle_shroud_updates()
void show_message(const std::string &title, const std::string &msg, const std::string &button_caption, const bool auto_close, const bool message_use_markup, const bool title_use_markup)
Shows a message to the user.
Definition: message.cpp:149
static display * get_singleton()
Returns the display object if a display object exists.
Definition: display.hpp:99
HOTKEY_COMMAND id
the names are strange: the "hotkey::HOTKEY_COMMAND" is named id, and the string to identify the objec...
std::string get_next_filename(const std::string &name, const std::string &extension)
Get the next free filename using "name + number (3 digits) + extension" maximum 1000 files then start...
Definition: filesystem.cpp:527
theme & get_theme()
Definition: display.hpp:439
void surrender(int side_number)
static void quit_to_title()
const action * get_action_item(const std::string &key) const
Definition: theme.cpp:913
static bool file_exists(const fs::path &fpath)
Definition: filesystem.cpp:300
const std::string & title() const
Definition: theme.hpp:222
std::string selected_menu
General purpose widgets.
void enable_mouse_scroll(bool value)
Definition: general.cpp:747
Stores all information related to functions that can be bound to hotkeys.
virtual void terrain_description()
std::string get_screenshot_dir()
static CVideo & get_singleton()
Definition: video.hpp:48
void mbutton_event(const SDL_Event &event, command_executor *executor)
std::string unchecked_menu
const std::vector< std::string > items
Contains the exception interfaces used to signal completion of a scenario, campaign or turn...
bool show(const unsigned auto_close_time=0)
Shows the window.
virtual void whiteboard_execute_action()
virtual void scroll_up(bool)
bool sound_on()
Definition: general.cpp:660
Keyboard shortcuts for game actions.
bool null() const
Definition: surface.hpp:79
virtual void whiteboard_delete_action()
virtual bool can_execute_command(const hotkey_command &command, int index=-1) const =0
static void display(lua_kernel_base *lk)
Display a new console, using given video and lua kernel.
virtual void whiteboard_bump_up_action()
virtual void label_terrain(bool)
#define b
void run_events(command_executor *executor)
static lg::log_domain log_config("config")
std::string get_names(const std::string &id)
Returns a comma-separated string of hotkey names.
static void event_queue(const SDL_Event &event, command_executor *executor)
static UNUSEDNOWARN std::string _(const char *str)
Definition: gettext.hpp:89
std::string deselected_menu
void get_menu_images(display &, std::vector< config > &items)
virtual void replay_show_team1()
void jbutton_event(const SDL_Event &event, command_executor *executor)
void toggle_minimap_draw_units()
Definition: general.cpp:792
const t_string name
bool is_linger_mode() const
surface screenshot(bool map_screenshot=false)
Capture a (map-)screenshot into a surface.
Definition: display.cpp:660
const hotkey_ptr get_hotkey(const SDL_Event &event)
Iterate through the list of hotkeys and return a hotkey that matches the SDL_Event and the current ke...
virtual void unit_hold_position()
virtual void scroll_right(bool)
Used by the menu_button widget.
virtual void show_menu(const std::vector< config > &items_arg, int xloc, int yloc, bool context_menu, display &gui)
void keyup_event(const SDL_Event &, command_executor *executor)
virtual void show_enemy_moves(bool)
std::string path
Definition: game_config.cpp:39
Shows a yes and no button.
Definition: message.hpp:79
void toggle_minimap_draw_terrain()
Definition: general.cpp:812
map_display and display: classes which take care of displaying the map and game-data on the screen...
bool animate_map()
Definition: general.cpp:757
void set_animate_map(bool value)
Definition: general.cpp:817
void jhat_event(const SDL_Event &event, command_executor *executor)
bool is_observer() const
void toggle_minimap_draw_villages()
Definition: general.cpp:802
void queue_command(const SDL_Event &event, int index=-1)
std::string get_menu_image(display &disp, const std::string &command, int index=-1) const
void execute_command_wrap(const queued_command &command)
static lg::log_domain log_hotkey("hotkey")
static void quit_to_desktop()
std::size_t i
Definition: function.cpp:933
const std::vector< config > & items() const
Definition: theme.hpp:230
virtual void scroll_left(bool)
std::vector< queued_command > command_queue_
virtual void update_shroud_now()
void toggle_minimap_terrain_coding()
Definition: general.cpp:782
void execute_action(const std::vector< std::string > &items_arg, int xloc, int yloc, bool context_menu, display &gui)
void toggle_fullscreen()
Definition: video.cpp:449
virtual void replay_show_everything()
bool music_on()
Definition: general.cpp:683
virtual void scroll_down(bool)
virtual std::string get_action_image(hotkey::HOTKEY_COMMAND, int) const
void toggle_minimap_movement_coding()
Definition: general.cpp:772
virtual bool do_execute_command(const hotkey_command &command, int index=-1, bool press=true, bool release=false)
Declarations for File-IO.
bool is_context() const
Definition: theme.hpp:220
const menu * get_menu_item(const std::string &key) const
Definition: theme.cpp:904
std::shared_ptr< hotkey_base > hotkey_ptr
Definition: hotkey_item.hpp:30
std::size_t index(const std::string &str, const std::size_t index)
Codepoint index corresponding to the nth character in a UTF-8 string.
Definition: unicode.cpp:71
virtual void whiteboard_suppose_dead()
void key_event(const SDL_Event &event, command_executor *executor)
const std::string & get_description(const std::string &command)
virtual void whiteboard_bump_down_action()
bool mouse_scroll_enabled()
Definition: general.cpp:742
const std::string & title() const
Definition: theme.hpp:167
virtual void whiteboard_execute_all_actions()
virtual ACTION_STATE get_action_state(hotkey::HOTKEY_COMMAND, int) const
bool set_music(bool ison)
Definition: general.cpp:688
Standard logging facilities (interface).
std::vector< queued_command > filter_command_queue()
#define DBG_G
std::string checked_menu
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:68
const hotkey_command & get_hotkey_command(const std::string &command)
returns the hotkey_command with the given name
static map_location::DIRECTION n
std::string::const_iterator iterator
Definition: tokenizer.hpp:24
HOTKEY_COMMAND get_id(const std::string &command)
returns get_hotkey_command(command).id
virtual void replay_skip_animation()
virtual void whiteboard_toggle()
virtual void toggle_accelerated_speed()