The Battle for Wesnoth  1.19.13+dev
hotkey_handler.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2014 - 2025
3  by Chris Beck <render787@gmail.com>
4  Part of the Battle for Wesnoth Project https://www.wesnoth.org/
5 
6  This program is free software; you can redistribute it and/or modify
7  it under the terms of the GNU General Public License as published by
8  the Free Software Foundation; either version 2 of the License, or
9  (at your option) any later version.
10  This program is distributed in the hope that it will be useful,
11  but WITHOUT ANY WARRANTY.
12 
13  See the COPYING file for more details.
14 */
15 
17 
18 #include "font/standard_colors.hpp"
19 #include "formula/string_utils.hpp"
20 #include "game_display.hpp"
23 #include "game_state.hpp"
25 #include "hotkey/hotkey_item.hpp"
26 #include "log.hpp"
27 #include "map/map.hpp"
28 #include "play_controller.hpp"
29 #include "savegame.hpp"
30 #include "whiteboard/manager.hpp"
31 
32 #include <boost/algorithm/string/predicate.hpp>
33 
34 namespace balg = boost::algorithm;
35 
36 #include "units/unit.hpp"
37 
38 
39 #define ERR_G LOG_STREAM(err, lg::general())
40 #define WRN_G LOG_STREAM(warn, lg::general())
41 #define LOG_G LOG_STREAM(info, lg::general())
42 #define DBG_G LOG_STREAM(debug, lg::general())
43 
44 const std::string play_controller::hotkey_handler::wml_menu_hotkey_prefix = "wml_menu:";
45 
46 static const std::string quickload_prefix = "quickload:";
47 static const std::string quickreplay_prefix = "quickreplay:";
48 
50  : play_controller_(pc)
51  , menu_handler_(pc.get_menu_handler())
52  , mouse_handler_(pc.get_mouse_handler_base())
53  , saved_game_(sg)
54 {}
55 
57 
59  return &play_controller_.get_display();
60 }
61 
63  return play_controller_.gamestate();
64 }
65 
67  return play_controller_.gamestate();
68 }
69 
70 bool play_controller::hotkey_handler::browse() const { return play_controller_.is_browsing(); }
71 bool play_controller::hotkey_handler::linger() const { return play_controller_.is_linger_mode(); }
72 
75 }
76 
78  menu_handler_.show_statistics(gui()->viewing_team().side());
79 }
80 
83 }
84 
87 }
88 
90  play_controller_.save_game();
91 }
92 
94  play_controller_.save_replay();
95 }
96 
98  play_controller_.save_map();
99 }
100 
102  play_controller_.load_game();
103 }
104 
107 }
108 
110  const auto [x, y] = gui()->get_location(gui()->mouseover_hex());
111  SDL_MouseButtonEvent event;
112 
113  event.button = 1;
114  event.x = x + 30;
115  event.y = y + 30;
116  event.which = 0;
117  event.state = SDL_PRESSED;
118 
119  mouse_handler_.mouse_press(event, false);
120 }
121 
124 }
125 
127  auto touched_hex = gui()->mouseover_hex();
128  mouse_handler_.touch_action(touched_hex, false);
129 }
130 
132  mouse_handler_.move_action(browse());
133 }
134 
137 }
139  mouse_handler_.select_hex(gui()->mouseover_hex(), false);
140 }
141 
143  const auto [x, y] = gui()->get_location(gui()->mouseover_hex());
144  SDL_MouseButtonEvent event;
145 
146  event.button = 3;
147  event.x = x + 30;
148  event.y = y + 30;
149  event.which = 0;
150  event.state = SDL_PRESSED;
151 
152  mouse_handler_.mouse_press(event, true);
153 }
154 
155 
157  mouse_handler_.cycle_units(browse());
158 }
159 
162 }
163 
166 }
167 
170 }
171 
174 }
175 
177  play_controller_.undo();
178 }
179 
181  play_controller_.redo();
182 }
183 
185  menu_handler_.show_enemy_moves(ignore_units, play_controller_.current_side());
186 }
187 
189  menu_handler_.goto_leader(play_controller_.current_side());
190 }
191 
194 }
195 
198 }
199 
202 }
203 
206 }
207 
210 }
211 
213 {
214  prefs::get().set_turbo(!prefs::get().turbo());
215 
217  ao.discard_previous = true;
218 
219  if (prefs::get().turbo())
220  {
221  utils::string_map symbols;
223  gui()->announce(_("Accelerated speed enabled!") + "\n" + VGETTEXT("(press $hk to disable)", symbols), font::NORMAL_COLOR, ao);
224  }
225  else
226  {
227  gui()->announce(_("Accelerated speed disabled!"), font::NORMAL_COLOR, ao);
228  }
229 }
230 
232 {
233  play_controller_.set_scroll_up(on);
234 }
235 
237 {
238  play_controller_.set_scroll_down(on);
239 }
240 
242 {
243  play_controller_.set_scroll_left(on);
244 }
245 
247 {
248  play_controller_.set_scroll_right(on);
249 }
250 
252 {
253  DBG_G << "play_controller::do_execute_command: Found command:" << cmd.id;
254  if(balg::starts_with(cmd.id, quickload_prefix)) {
255  std::string savename = std::string(cmd.id.substr(quickload_prefix.size()));
256  // Load the game by throwing load_game_exception
257  load_autosave(savename, false);
258  }
259  if(balg::starts_with(cmd.id, quickreplay_prefix)) {
260  std::string savename = std::string(cmd.id.substr(quickreplay_prefix.size()));
261  // Load the game by throwing load_game_exception
262  load_autosave(savename, true);
263  }
264  // TODO c++20: Use string::starts_with
265  // wml commands that don't allow hotkey bindings use hotkey::HOTKEY_NULL. othes use HOTKEY_WML
266  if(balg::starts_with(cmd.id, wml_menu_hotkey_prefix)) {
267  std::string name = std::string(cmd.id.substr(wml_menu_hotkey_prefix.length()));
269 
271  name, hex, gamestate().gamedata_, gamestate(), play_controller_.get_units(), !press);
272  }
273  return command_executor::do_execute_command(cmd, press, release);
274 }
275 
277 {
278  switch(cmd.hotkey_command) {
279 
280  // Commands we can always do:
297  case hotkey::HOTKEY_MUTE:
305  case hotkey::HOTKEY_HELP:
321  case hotkey::HOTKEY_NULL: // HOTKEY_NULL is used for menu items that don't allow hotkey bindings (for example load autosave, wml menu items and menus)
324  case hotkey::LUA_CONSOLE:
330  return true;
331 
333  std::size_t humans_notme_cnt = 0;
334  for(const auto& t : play_controller_.get_teams()) {
335  if(t.is_network_human()) {
336  ++humans_notme_cnt;
337  }
338  }
339 
340  return !(humans_notme_cnt < 1 || play_controller_.is_linger_mode() || play_controller_.is_observer());
341  }
342  // Commands that have some preconditions:
345 
348  return !linger() && play_controller_.enemies_visible();
349 
352  return !play_controller_.is_networked_mp(); // Can only load games if not in a network game
353 
358  return true;
359 
360  case hotkey::HOTKEY_REDO:
361  return play_controller_.can_redo();
362  case hotkey::HOTKEY_UNDO:
363  return play_controller_.can_undo();
364 
366  return menu_handler_.current_unit().valid();
367 
369  return mouse_handler_.get_last_hex().valid();
370 
372  return !events::commands_disabled &&
374  !(menu_handler_.current_unit()->unrenamable()) &&
375  menu_handler_.current_unit()->side() == gui()->viewing_team().side() &&
376  play_controller_.get_teams()[menu_handler_.current_unit()->side() - 1].is_local_human();
377 
378  default:
379  return false;
380  }
381 }
382 
383 template<typename T>
384 static void trim_items(std::vector<T>& newitems)
385 {
386  if(newitems.size() > 5) {
387  std::vector<T> subitems;
388  subitems.push_back(std::move(newitems[0]));
389  subitems.push_back(std::move(newitems[1]));
390  subitems.push_back(std::move(newitems[newitems.size() / 3]));
391  subitems.push_back(std::move(newitems[newitems.size() * 2 / 3]));
392  subitems.push_back(std::move(newitems.back()));
393  newitems = subitems;
394  }
395 }
396 
397 template<typename F>
398 static void foreach_autosave(int turn, saved_game& sg, F func)
399 {
400  compression::format compression_format = prefs::get().save_compression_format();
401  savegame::autosave_savegame autosave(sg, compression_format);
402  savegame::scenariostart_savegame scenariostart_save(sg, compression_format);
403 
404  const std::string start_name = scenariostart_save.create_filename();
405 
406  for(; turn != 0; turn--) {
407  const std::string name = autosave.create_filename(turn);
408 
409  if(savegame::save_game_exists(name, compression_format)) {
410  func(turn, name + compression::format_extension(compression_format));
411  }
412  }
413 
414  if(savegame::save_game_exists(start_name, compression_format)) {
415  func(0, start_name + compression::format_extension(compression_format));
416  }
417 }
418 
419 void play_controller::hotkey_handler::expand_autosaves(std::vector<config>& items, int i)
420 {
421  auto pos = items.erase(items.begin() + i);
422  std::vector<config> newitems;
423 
424  foreach_autosave(play_controller_.turn(), saved_game_, [&](int turn, const std::string& filename) {
425  // TODO: should this use variable substitution instead?
426  std::string label = turn > 0 ? _("Back to Turn ") + std::to_string(turn) : _("Back to Start");
427  newitems.emplace_back("label", label, "id", quickload_prefix + filename);
428  });
429  // Make sure list doesn't get too long: keep top two, midpoint and bottom.
430  trim_items(newitems);
431 
432  items.insert(pos, newitems.begin(), newitems.end());
433 }
434 
435 void play_controller::hotkey_handler::expand_quickreplay(std::vector<config>& items, int i)
436 {
437  auto pos = items.erase(items.begin() + i);
438  std::vector<config> newitems;
439 
440  foreach_autosave(play_controller_.turn(), saved_game_, [&](int turn, const std::string& filename) {
441  // TODO: should this use variable substitution instead?
442  std::string label = turn > 0 ? _("Replay from Turn ") + std::to_string(turn) : _("Replay from Start");
443  newitems.emplace_back("label", label, "id", quickreplay_prefix + filename);
444  });
445  // Make sure list doesn't get too long: keep top two, midpoint and bottom.
446  trim_items(newitems);
447 
448  items.insert(pos, newitems.begin(), newitems.end());
449 }
450 
451 void play_controller::hotkey_handler::expand_wml_commands(std::vector<config>& items, int i)
452 {
453 
454  auto pos = items.erase(items.begin() + i);
455  std::vector<config> newitems;
456 
458  gamestate(), gamestate().gamedata_, play_controller_.get_units());
459 
460  // Replace this placeholder entry with available menu items.
461  items.insert(pos, newitems.begin(), newitems.end());
462 }
463 
464 void play_controller::hotkey_handler::show_menu(const std::vector<config>& items_arg, int xloc, int yloc, bool context_menu)
465 {
466  std::vector<config> items;
467  for(const auto& item : items_arg) {
468 
469  std::string id = item["id"];
471 
472  if(id == "wml" || (can_execute_command(cmd) && (!context_menu || in_context_menu(cmd)))) {
473  items.emplace_back("id", id);
474  }
475  }
476 
477  // Iterate in reverse to avoid also iterating over the new inserted items
478  for(int i = items.size() - 1; i >= 0; i--) {
479  if(items[i]["id"] == "AUTOSAVES") {
480  expand_autosaves(items, i);
481  } else if(items[i]["id"] == "QUICKREPLAY") {
482  expand_quickreplay(items, i);
483  } else if(items[i]["id"] == "wml") {
484  expand_wml_commands(items, i);
485  }
486  }
487 
488  if(items.empty()) {
489  return;
490  }
491 
492  command_executor::show_menu(items, xloc, yloc, context_menu);
493 }
494 
496 {
497  switch(cmd.hotkey_command) {
498  // Only display these if the mouse is over a castle or keep tile
501  case hotkey::HOTKEY_RECALL: {
502  // last_hex_ is set by mouse_events::mouse_motion
503  const map_location & last_hex = mouse_handler_.get_last_hex();
504  const int viewing_side = gui()->viewing_team().side();
505 
506  // A quick check to save us having to create the future map and
507  // possibly loop through all units.
508  if ( !play_controller_.get_map().is_keep(last_hex) &&
509  !play_controller_.get_map().is_castle(last_hex) )
510  return false;
511 
512  wb::future_map future; /* lasts until method returns. */
513 
514  return gamestate().side_can_recruit_on(viewing_side, last_hex);
515  }
516  default:
517  return true;
518  }
519 }
520 
522 {
523  return command_executor::get_action_image(cmd);
524 }
525 
527 {
528  switch(cmd.hotkey_command) {
529 
531  return (prefs::get().minimap_draw_villages()) ? hotkey::ACTION_ON : hotkey::ACTION_OFF;
533  return (prefs::get().minimap_movement_coding()) ? hotkey::ACTION_ON : hotkey::ACTION_OFF;
535  return (prefs::get().minimap_terrain_coding()) ? hotkey::ACTION_ON : hotkey::ACTION_OFF;
537  return (prefs::get().minimap_draw_units()) ? hotkey::ACTION_ON : hotkey::ACTION_OFF;
539  return (prefs::get().minimap_draw_terrain()) ? hotkey::ACTION_ON : hotkey::ACTION_OFF;
541  return (gui()->get_zoom_factor() == 1.0) ? hotkey::ACTION_ON : hotkey::ACTION_OFF;
543  return gui()->viewing_team().auto_shroud_updates() ? hotkey::ACTION_OFF : hotkey::ACTION_ON;
544  default:
546  }
547 }
548 
550 {
552 }
double t
Definition: astarsearch.cpp:63
virtual bool in_context_menu(const hotkey::ui_command &cmd) const
void show_enemy_moves(bool ignore_units, int side_num)
void goto_leader(int side_num)
void terrain_description(mouse_handler &mousehandler)
void show_statistics(int side_num)
unit_map::iterator current_unit()
virtual void mouse_press(const SDL_MouseButtonEvent &event, const bool browse)
void cycle_back_units(const bool browse)
void select_or_action(bool browse)
void select_hex(const map_location &hex, const bool browse, const bool highlight=true, const bool fire_event=true, const bool force_unhighlight=false)
void touch_action(const map_location hex, bool browse) override
void move_action(bool browse) override
Overridden in derived class.
const map_location & get_last_hex() const
void cycle_units(const bool browse, const bool reverse=false)
void get_items(const map_location &hex, std::vector< config > &items, filter_context &fc, game_data &gamedata, unit_map &units) const
Returns the menu items that can be shown for the given location.
bool fire_item(const std::string &id, const map_location &hex, game_data &gamedata, filter_context &fc, unit_map &units, bool is_key_hold_repeat=false) const
Fires the menu item with the given id.
Definition: wmi_manager.cpp:79
bool side_can_recruit_on(int side, map_location loc) const
Checks if any of the sides leaders can recruit at a location.
Definition: game_state.cpp:366
game_events::wmi_manager & get_wml_menu_items()
Definition: game_state.cpp:383
game_display * gui() const
static const std::string wml_menu_hotkey_prefix
virtual void select_and_action() override
virtual void save_game() override
virtual void right_mouse_click() override
virtual void cycle_units() override
virtual void search() override
virtual void select_hex() override
virtual void scroll_right(bool on) override
virtual void move_action() override
void expand_autosaves(std::vector< config > &items, int i)
virtual void goto_leader() override
virtual void show_help() override
virtual void preferences() override
virtual void scroll_up(bool on) override
virtual void scroll_left(bool on) override
virtual void status_table() override
virtual void unit_list() override
virtual void undo() override
void expand_quickreplay(std::vector< config > &items, int i)
void show_menu(const std::vector< config > &items_arg, int xloc, int yloc, bool context_menu) override
virtual void scroll_down(bool on) override
virtual void redo() override
virtual void load_autosave(const std::string &filename, bool start_replay=false)
virtual void objectives() override
virtual void show_statistics() override
virtual void save_map() override
virtual std::string get_action_image(const hotkey::ui_command &) const override
virtual hotkey::ACTION_STATE get_action_state(const hotkey::ui_command &) const override
virtual void deselect_hex() override
virtual bool do_execute_command(const hotkey::ui_command &command, bool press=true, bool release=false) override
virtual void load_game() override
bool in_context_menu(const hotkey::ui_command &cmd) const
Determines whether the command should be in the context menu or not.
void expand_wml_commands(std::vector< config > &items, int i)
Replaces "wml" in items with all active WML menu items for the current field.
virtual void speak() override
virtual void show_enemy_moves(bool ignore_units) override
hotkey_handler(play_controller &, saved_game &)
virtual void left_mouse_click() override
virtual void toggle_ellipses() override
virtual void terrain_description() override
virtual void toggle_accelerated_speed() override
virtual void cycle_back_units() override
virtual void show_chat_log() override
virtual void toggle_grid() override
virtual void unit_description() override
virtual bool can_execute_command(const hotkey::ui_command &command) const override
Check if a command can be executed.
virtual void save_replay() override
virtual void touch_hex() override
events::menu_handler menu_handler_
saved_game & saved_game_
game_state & gamestate()
std::size_t turn() const
events::mouse_handler mouse_handler_
void set_turbo(bool ison)
static prefs & get()
compression::format save_compression_format()
Class for autosaves.
Definition: savegame.hpp:281
Exception used to signal that the user has decided to abort a game, and to load another game instead.
Definition: savegame.hpp:85
static std::shared_ptr< save_index_class > default_saves_dir()
Returns an instance for managing saves in filesystem::get_saves_dir()
Definition: save_index.cpp:210
std::string create_filename() const
Build the filename according to the specific savegame's needs.
Definition: savegame.hpp:181
Class for start-of-scenario saves.
Definition: savegame.hpp:307
#define VGETTEXT(msgid,...)
Handy wrappers around interpolate_variables_into_string and gettext.
std::size_t i
Definition: function.cpp:1032
static std::string _(const char *str)
Definition: gettext.hpp:97
static void trim_items(std::vector< T > &newitems)
static const std::string quickload_prefix
static void foreach_autosave(int turn, saved_game &sg, F func)
static const std::string quickreplay_prefix
#define DBG_G
This file implements all the hotkey handling and menu details for play controller.
Standard logging facilities (interface).
std::string format_extension(format compression_format)
Definition: compression.hpp:24
const color_t NORMAL_COLOR
General purpose widgets.
std::string get_names(const std::string &id)
Returns a comma-separated string of hotkey names.
@ HOTKEY_MINIMAP_DRAW_VILLAGES
@ HOTKEY_FULLSCREEN
@ HOTKEY_OBJECTIVES
@ HOTKEY_ANIMATE_MAP
@ HOTKEY_SCREENSHOT
@ HOTKEY_SPEAK_ALLY
@ HOTKEY_ACCELERATED
@ HOTKEY_MOUSE_SCROLL
@ HOTKEY_TERRAIN_DESCRIPTION
@ HOTKEY_LABEL_SETTINGS
@ HOTKEY_HELP_ABOUT_SAVELOAD
@ HOTKEY_SCROLL_LEFT
@ HOTKEY_SAVE_GAME
@ HOTKEY_SPEAK_ALL
@ HOTKEY_SHOW_ENEMY_MOVES
@ HOTKEY_ACHIEVEMENTS
@ HOTKEY_DESELECT_HEX
@ HOTKEY_UNIT_DESCRIPTION
@ HOTKEY_REPEAT_RECRUIT
@ HOTKEY_SCROLL_RIGHT
@ HOTKEY_SAVE_REPLAY
@ HOTKEY_TOGGLE_GRID
@ HOTKEY_SURRENDER
@ HOTKEY_SELECT_AND_ACTION
@ HOTKEY_CLEAR_MSG
@ HOTKEY_MINIMAP_DRAW_TERRAIN
@ HOTKEY_CUSTOM_CMD
@ HOTKEY_MAP_SCREENSHOT
@ HOTKEY_BEST_ENEMY_MOVES
@ HOTKEY_QUIT_TO_DESKTOP
@ HOTKEY_LOAD_AUTOSAVES
@ HOTKEY_TOGGLE_ELLIPSES
@ HOTKEY_RENAME_UNIT
@ HOTKEY_MINIMAP_CODING_TERRAIN
@ HOTKEY_LOAD_GAME
@ HOTKEY_MINIMAP_DRAW_UNITS
@ HOTKEY_CYCLE_UNITS
@ HOTKEY_DELAY_SHROUD
@ HOTKEY_PREFERENCES
@ HOTKEY_STATUS_TABLE
@ HOTKEY_MOVE_ACTION
@ HOTKEY_TOUCH_HEX
@ HOTKEY_STATISTICS
@ HOTKEY_UNIT_LIST
@ HOTKEY_SCROLL_DOWN
@ HOTKEY_ZOOM_DEFAULT
@ HOTKEY_SCROLL_UP
@ HOTKEY_SELECT_HEX
@ HOTKEY_QUIT_GAME
@ HOTKEY_AI_FORMULA
@ HOTKEY_CYCLE_BACK_UNITS
@ HOTKEY_MINIMAP_CODING_UNIT
int show_menu(lua_State *L)
Displays a popup menu at the current mouse position Best used from a [set_menu_item],...
Definition: lua_gui2.cpp:193
bool save_game_exists(std::string name, compression::format compressed)
Returns true if there is already a savegame with this name, looking only in the default save director...
Definition: savegame.cpp:57
std::map< std::string, t_string > string_map
std::string filename
Filename.
Holds options for calls to function 'announce' (announce).
Definition: display.hpp:603
bool discard_previous
An announcement according these options should replace the previous announce (typical of fast announc...
Definition: display.hpp:612
static const hotkey_command & get_command_by_command(HOTKEY_COMMAND command)
the execute_command argument was changed from HOTKEY_COMMAND to hotkey_command, to be able to call it...
Used as the main parameter for can_execute_command/do_execute_command These functions are used to exe...
hotkey::HOTKEY_COMMAND hotkey_command
The hotkey::HOTKEY_COMMAND associated with this action, HOTKEY_NULL for actions that don't allow hotk...
std::string id
The string command, never empty, describes the action uniquely.
Encapsulates the map of the game.
Definition: location.hpp:45
bool valid() const
Definition: location.hpp:110
bool valid() const
Definition: map.hpp:273
Applies the planned unit map for the duration of the struct's life.
Definition: manager.hpp:253
Declarations for a container for wml_menu_item.