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 http://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;
363  break;
367  break;
371  break;
375  break;
379  break;
380  default:
381  return false;
382  }
383  return true;
384 }
385 
387  if(gui2::show_message(_("Surrender"), _("Do you really want to surrender the game?"), gui2::dialogs::message::yes_no_buttons) != gui2::retval::CANCEL) {
389  if(pmc && !pmc->is_linger_mode() && !pmc->is_observer()) {
390  pmc->surrender(display::get_singleton()->viewing_team());
391  }
392  }
393 }
394 
395 void command_executor::show_menu(const std::vector<config>& items_arg, int xloc, int yloc, bool /*context_menu*/, display& gui)
396 {
397  std::vector<config> items = items_arg;
398  if (items.empty()) return;
399 
400  get_menu_images(gui, items);
401 
402  int res = -1;
403  {
404  SDL_Rect pos {xloc, yloc, 1, 1};
405  gui2::dialogs::drop_down_menu mmenu(pos, items, -1, true, false); // TODO: last value should be variable
406  mmenu.show();
407  if(mmenu.get_retval() == gui2::retval::OK) {
408  res = mmenu.selected_item();
409  }
410  } // This will kill the dialog.
411  if (res < 0 || std::size_t(res) >= items.size()) return;
412 
413  const theme::menu* submenu = gui.get_theme().get_menu_item(items[res]["id"]);
414  if (submenu) {
415  int y,x;
416  SDL_GetMouseState(&x,&y);
417  this->show_menu(submenu->items(), x, y, submenu->is_context(), gui);
418  } else {
419  const hotkey::hotkey_command& cmd = hotkey::get_hotkey_command(items[res]["id"]);
420  do_execute_command(cmd, res);
421  }
422 }
423 
424 void command_executor::execute_action(const std::vector<std::string>& items_arg, int /*xloc*/, int /*yloc*/, bool /*context_menu*/, display&)
425 {
426  std::vector<std::string> items = items_arg;
427  if (items.empty()) {
428  return;
429  }
430 
431  std::vector<std::string>::iterator i = items.begin();
432  while(i != items.end()) {
433  const hotkey_command &command = hotkey::get_hotkey_command(*i);
434  if (can_execute_command(command)) {
435  do_execute_command(command);
436  }
437  ++i;
438  }
439 }
440 
441 std::string command_executor::get_menu_image(display& disp, const std::string& command, int index) const {
442 
443  // TODO: Find a way to do away with the fugly special markup
444  if(command[0] == '&') {
445  std::size_t n = command.find_first_of('=');
446  if(n != std::string::npos)
447  return command.substr(1, n - 1);
448  }
449 
450  const std::string base_image_name = "icons/action/" + command + "_25.png";
451  const std::string pressed_image_name = "icons/action/" + command + "_25-pressed.png";
452 
453  const hotkey::HOTKEY_COMMAND hk = hotkey::get_id(command);
454  const hotkey::ACTION_STATE state = get_action_state(hk, index);
455 
456  const theme::menu* menu = disp.get_theme().get_menu_item(command);
457  if (menu) {
458  return "icons/arrows/short_arrow_right_25.png~CROP(3,3,18,18)"; // TODO should not be hardcoded
459  }
460 
461  if (filesystem::file_exists(game_config::path + "/images/" + base_image_name)) {
462  switch (state) {
463  case ACTION_ON:
464  case ACTION_SELECTED:
465  return pressed_image_name + "~CROP(3,3,18,18)";
466  default:
467  return base_image_name + "~CROP(3,3,18,18)";
468  }
469  }
470 
471  switch (get_action_state(hk, index)) {
472  case ACTION_ON:
474  case ACTION_OFF:
476  case ACTION_SELECTED:
478  case ACTION_DESELECTED:
480  default: return get_action_image(hk, index);
481  }
482 }
483 
484 void command_executor::get_menu_images(display& disp, std::vector<config>& items)
485 {
486  for(std::size_t i = 0; i < items.size(); ++i) {
487  config& item = items[i];
488 
489  const std::string& item_id = item["id"];
490  const hotkey::HOTKEY_COMMAND hk = hotkey::get_id(item_id);
491 
492  //see if this menu item has an associated image
493  std::string img(get_menu_image(disp, item_id, i));
494  if (img.empty() == false) {
495  item["icon"] = img;
496  }
497 
498  const theme::menu* menu = disp.get_theme().get_menu_item(item_id);
499  if(menu) {
500  item["label"] = menu->title();
501  } else if(hk != hotkey::HOTKEY_NULL) {
502  std::string desc = hotkey::get_description(item_id);
503  if(hk == HOTKEY_ENDTURN) {
504  const theme::action *b = disp.get_theme().get_action_item("button-endturn");
505  if (b) {
506  desc = b->title();
507  }
508  }
509 
510  item["label"] = desc;
511  item["details"] = hotkey::get_names(item_id);
512  } else if(item["label"].empty()) {
513  // If no matching hotkey was found and a custom label wasn't already set, treat
514  // the id as a plaintext description. This is because either type of value can
515  // be written to the id field by the WMI manager. The plaintext description is
516  // used in the case the menu item specifies the relevant entry is *not* a hotkey.
517  item["label"] = item_id;
518  }
519  }
520 }
521 
522 void mbutton_event(const SDL_Event& event, command_executor* executor)
523 {
524  event_queue(event, executor);
525 }
526 
527 void jbutton_event(const SDL_Event& event, command_executor* executor)
528 {
529  event_queue(event, executor);
530 }
531 
532 void jhat_event(const SDL_Event& event, command_executor* executor)
533 {
534  event_queue(event, executor);
535 }
536 
537 void key_event(const SDL_Event& event, command_executor* executor)
538 {
539  if (!executor) return;
540  event_queue(event,executor);
541 }
542 
543 void keyup_event(const SDL_Event&, command_executor* executor)
544 {
545  if(!executor) return;
546  executor->handle_keyup();
547 }
548 
550 {
551  if(!executor) return;
552  executor->run_queued_commands();
553 }
554 
555 static void event_queue(const SDL_Event& event, command_executor* executor)
556 {
557  if (!executor) return;
558  executor->queue_command(event);
559 }
560 
561 void command_executor::queue_command(const SDL_Event& event, int index)
562 {
563  LOG_HK << "event 0x" << std::hex << event.type << std::dec << std::endl;
564  if(event.type == SDL_TEXTINPUT) {
565  LOG_HK << "SDL_TEXTINPUT \"" << event.text.text << "\"\n";
566  }
567 
568  const hotkey_ptr hk = get_hotkey(event);
569  if(!hk->active() || hk->is_disabled()) {
570  return;
571  }
572 
573  const hotkey_command& command = hotkey::get_hotkey_command(hk->get_command());
574  bool keypress = (event.type == SDL_KEYDOWN || event.type == SDL_TEXTINPUT) &&
576  bool press = keypress ||
577  (event.type == SDL_JOYBUTTONDOWN || event.type == SDL_MOUSEBUTTONDOWN);
578  bool release = event.type == SDL_KEYUP;
579  if(press) {
580  LOG_HK << "sending press event (keypress = " <<
581  std::boolalpha << keypress << std::noboolalpha << ")\n";
582  }
583  if(keypress) {
584  press_event_sent_ = true;
585  }
586 
587  command_queue_.emplace_back(command, index, press, release);
588 }
589 
591 {
592  if (!can_execute_command(*command.command, command.index)
593  || do_execute_command(*command.command, command.index, command.press, command.release)) {
594  return;
595  }
596 
597  if (!command.press) {
598  return; // none of the commands here respond to a key release
599  }
600 
601  switch (command.command->id) {
602  case HOTKEY_FULLSCREEN:
604  break;
605  case HOTKEY_SCREENSHOT:
606  make_screenshot(_("Screenshot"), false);
607  break;
608  case HOTKEY_ANIMATE_MAP:
610  break;
611  case HOTKEY_MOUSE_SCROLL:
613  break;
614  case HOTKEY_MUTE:
615  {
616  // look if both is not playing
617  static struct before_muted_s
618  {
619  bool playing_sound,playing_music;
620  before_muted_s() : playing_sound(false),playing_music(false){}
621  } before_muted;
623  {
624  // then remember settings and mute both
625  before_muted.playing_sound = preferences::sound_on();
626  before_muted.playing_music = preferences::music_on();
627  preferences::set_sound(false);
628  preferences::set_music(false);
629  }
630  else
631  {
632  // then set settings before mute
633  preferences::set_sound(before_muted.playing_sound);
634  preferences::set_music(before_muted.playing_music);
635  }
636  }
637  break;
638  default:
639  DBG_G << "command_executor: unknown command number " << command.command->id << ", ignoring.\n";
640  break;
641  }
642 }
643 
644 // Removes duplicate commands caused by both SDL_KEYDOWN and SDL_TEXTINPUT triggering hotkeys.
645 // See https://github.com/wesnoth/wesnoth/issues/1736
646 std::vector<command_executor::queued_command> command_executor::filter_command_queue()
647 {
648  std::vector<queued_command> filtered_commands;
649 
650  /** A command plus "key released" flag. Otherwise, we will filter out key releases that are preceded by a keypress. */
651  using command_with_keyrelease = std::pair<const hotkey_command*, bool>;
652  std::set<command_with_keyrelease> seen_commands;
653 
654  for(const queued_command& cmd : command_queue_) {
655  command_with_keyrelease command_key(cmd.command, cmd.release);
656  if(seen_commands.find(command_key) == seen_commands.end()) {
657  seen_commands.insert(command_key);
658  filtered_commands.push_back(cmd);
659  }
660  }
661 
662  command_queue_.clear();
663 
664  return filtered_commands;
665 }
666 
668 {
669  std::vector<queued_command> commands = filter_command_queue();
670  for(const queued_command& cmd : commands) {
672  }
673 }
674 
676 {
677  get_display().recalculate_minimap();
678 }
679 
681 {
682  if (get_display().in_game()) {
684  } else {
686  }
687 
688 }
689 
691 {
693 }
694 
696 {
697  if(!get_display().view_locked()) {
698  get_display().set_zoom(true);
699  }
700 }
701 
703 {
704  if(!get_display().view_locked()) {
705  get_display().set_zoom(false);
706  }
707 }
708 
710 {
711  if(!get_display().view_locked()) {
712  get_display().set_default_zoom();
713  }
714 }
715 
717 {
718  make_screenshot(_("Map-Screenshot"), true);
719 }
720 }
#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:150
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
virtual void recalculate_minimap()
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
bool is_linger_mode() const
surface screenshot(bool map_screenshot=false)
Capture a (map-)screenshot into a surface.
Definition: display.cpp:662
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:446
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
Dialog was closed with the OK button.
Definition: retval.hpp:34
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()