The Battle for Wesnoth  1.19.5+dev
hotkey_handler.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2014 - 2024
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  , last_context_menu_x_(0)
55  , last_context_menu_y_(0)
56 {}
57 
59 
61  return &play_controller_.get_display();
62 }
63 
65  return play_controller_.gamestate();
66 }
67 
69  return play_controller_.gamestate();
70 }
71 
72 bool play_controller::hotkey_handler::browse() const { return play_controller_.is_browsing(); }
73 bool play_controller::hotkey_handler::linger() const { return play_controller_.is_linger_mode(); }
74 
77 }
78 
80  menu_handler_.show_statistics(gui()->viewing_team().side());
81 }
82 
85 }
86 
89 }
90 
92  play_controller_.save_game();
93 }
94 
96  play_controller_.save_replay();
97 }
98 
100  play_controller_.save_map();
101 }
102 
104  play_controller_.load_game();
105 }
106 
109 }
110 
112  const auto [x, y] = gui()->get_location(gui()->mouseover_hex());
113  SDL_MouseButtonEvent event;
114 
115  event.button = 1;
116  event.x = x + 30;
117  event.y = y + 30;
118  event.which = 0;
119  event.state = SDL_PRESSED;
120 
121  mouse_handler_.mouse_press(event, false);
122 }
123 
126 }
127 
129  auto touched_hex = gui()->mouseover_hex();
130  mouse_handler_.touch_action(touched_hex, false);
131 }
132 
134  mouse_handler_.move_action(browse());
135 }
136 
139 }
141  mouse_handler_.select_hex(gui()->mouseover_hex(), false);
142 }
143 
145  const auto [x, y] = gui()->get_location(gui()->mouseover_hex());
146  SDL_MouseButtonEvent event;
147 
148  event.button = 3;
149  event.x = x + 30;
150  event.y = y + 30;
151  event.which = 0;
152  event.state = SDL_PRESSED;
153 
154  mouse_handler_.mouse_press(event, true);
155 }
156 
157 
159  mouse_handler_.cycle_units(browse());
160 }
161 
164 }
165 
168 }
169 
172 }
173 
176 }
177 
179  play_controller_.undo();
180 }
181 
183  play_controller_.redo();
184 }
185 
187  menu_handler_.show_enemy_moves(ignore_units, play_controller_.current_side());
188 }
189 
191  menu_handler_.goto_leader(play_controller_.current_side());
192 }
193 
196 }
197 
200 }
201 
204 }
205 
208 }
209 
212 }
213 
215 {
216  prefs::get().set_turbo(!prefs::get().turbo());
217 
219  ao.discard_previous = true;
220 
221  if (prefs::get().turbo())
222  {
223  utils::string_map symbols;
225  gui()->announce(_("Accelerated speed enabled!") + "\n" + VGETTEXT("(press $hk to disable)", symbols), font::NORMAL_COLOR, ao);
226  }
227  else
228  {
229  gui()->announce(_("Accelerated speed disabled!"), font::NORMAL_COLOR, ao);
230  }
231 }
232 
234 {
235  play_controller_.set_scroll_up(on);
236 }
237 
239 {
240  play_controller_.set_scroll_down(on);
241 }
242 
244 {
245  play_controller_.set_scroll_left(on);
246 }
247 
249 {
250  play_controller_.set_scroll_right(on);
251 }
252 
254 {
255  DBG_G << "play_controller::do_execute_command: Found command:" << cmd.id;
256  if(balg::starts_with(cmd.id, quickload_prefix)) {
257  std::string savename = std::string(cmd.id.substr(quickload_prefix.size()));
258  // Load the game by throwing load_game_exception
259  load_autosave(savename, false);
260  }
261  if(balg::starts_with(cmd.id, quickreplay_prefix)) {
262  std::string savename = std::string(cmd.id.substr(quickreplay_prefix.size()));
263  // Load the game by throwing load_game_exception
264  load_autosave(savename, true);
265  }
266  // TODO c++20: Use string::starts_with
267  // wml commands that don't allow hotkey bindings use hotkey::HOTKEY_NULL. othes use HOTKEY_WML
268  if(balg::starts_with(cmd.id, wml_menu_hotkey_prefix)) {
269  std::string name = std::string(cmd.id.substr(wml_menu_hotkey_prefix.length()));
271 
273  name, hex, gamestate().gamedata_, gamestate(), play_controller_.get_units(), !press);
274  }
275  return command_executor::do_execute_command(cmd, press, release);
276 }
277 
279 {
280  switch(cmd.hotkey_command) {
281 
282  // Commands we can always do:
299  case hotkey::HOTKEY_MUTE:
307  case hotkey::HOTKEY_HELP:
323  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)
326  case hotkey::LUA_CONSOLE:
332  return true;
333 
335  std::size_t humans_notme_cnt = 0;
336  for(const auto& t : play_controller_.get_teams()) {
337  if(t.is_network_human()) {
338  ++humans_notme_cnt;
339  }
340  }
341 
342  return !(humans_notme_cnt < 1 || play_controller_.is_linger_mode() || play_controller_.is_observer());
343  }
344  // Commands that have some preconditions:
347 
350  return !linger() && play_controller_.enemies_visible();
351 
354  return !play_controller_.is_networked_mp(); // Can only load games if not in a network game
355 
360  return true;
361 
362  case hotkey::HOTKEY_REDO:
363  return play_controller_.can_redo();
364  case hotkey::HOTKEY_UNDO:
365  return play_controller_.can_undo();
366 
368  return menu_handler_.current_unit().valid();
369 
371  return mouse_handler_.get_last_hex().valid();
372 
374  return !events::commands_disabled &&
376  !(menu_handler_.current_unit()->unrenamable()) &&
377  menu_handler_.current_unit()->side() == gui()->viewing_team().side() &&
378  play_controller_.get_teams()[menu_handler_.current_unit()->side() - 1].is_local_human();
379 
380  default:
381  return false;
382  }
383 }
384 
385 template<typename T>
386 static void trim_items(std::vector<T>& newitems)
387 {
388  if(newitems.size() > 5) {
389  std::vector<T> subitems;
390  subitems.push_back(std::move(newitems[0]));
391  subitems.push_back(std::move(newitems[1]));
392  subitems.push_back(std::move(newitems[newitems.size() / 3]));
393  subitems.push_back(std::move(newitems[newitems.size() * 2 / 3]));
394  subitems.push_back(std::move(newitems.back()));
395  newitems = subitems;
396  }
397 }
398 
399 template<typename F>
400 static void foreach_autosave(int turn, saved_game& sg, F func) {
401 
403 
404  compression::format compression_format = prefs::get().save_compression_format();
405  savegame::autosave_savegame autosave(sg, compression_format);
406  savegame::scenariostart_savegame scenariostart_save(sg, compression_format);
407 
408  const std::string start_name = scenariostart_save.create_filename();
409 
410  for(; turn != 0; turn--) {
411  const std::string name = autosave.create_filename(turn);
412 
413  if(savegame::save_game_exists(name, comp_format)) {
414  func(turn, name + compression::format_extension(comp_format));
415  }
416  }
417 
418  if(savegame::save_game_exists(start_name, comp_format)) {
419  func(0, start_name + compression::format_extension(comp_format));
420  }
421 }
422 
423 void play_controller::hotkey_handler::expand_autosaves(std::vector<config>& items, int i)
424 {
425  auto pos = items.erase(items.begin() + i);
426  std::vector<config> newitems;
427 
428  foreach_autosave(play_controller_.turn(), saved_game_, [&](int turn, const std::string& filename) {
429  // TODO: should this use variable substitution instead?
430  std::string label = turn > 0 ? _("Back to Turn ") + std::to_string(turn) : _("Back to Start");
431  newitems.emplace_back("label", label, "id", quickload_prefix + filename);
432  });
433  // Make sure list doesn't get too long: keep top two, midpoint and bottom.
434  trim_items(newitems);
435 
436  items.insert(pos, newitems.begin(), newitems.end());
437 }
438 
439 void play_controller::hotkey_handler::expand_quickreplay(std::vector<config>& items, int i)
440 {
441  auto pos = items.erase(items.begin() + i);
442  std::vector<config> newitems;
443 
444  foreach_autosave(play_controller_.turn(), saved_game_, [&](int turn, const std::string& filename) {
445  // TODO: should this use variable substitution instead?
446  std::string label = turn > 0 ? _("Replay from Turn ") + std::to_string(turn) : _("Replay from Start");
447  newitems.emplace_back("label", label, "id", quickreplay_prefix + filename);
448  });
449  // Make sure list doesn't get too long: keep top two, midpoint and bottom.
450  trim_items(newitems);
451 
452  items.insert(pos, newitems.begin(), newitems.end());
453 }
454 
455 void play_controller::hotkey_handler::expand_wml_commands(std::vector<config>& items, int i)
456 {
457 
458  auto pos = items.erase(items.begin() + i);
459  std::vector<config> newitems;
460 
462  gamestate(), gamestate().gamedata_, play_controller_.get_units());
463 
464  // Replace this placeholder entry with available menu items.
465  items.insert(pos, newitems.begin(), newitems.end());
466 }
467 
468 void play_controller::hotkey_handler::show_menu(const std::vector<config>& items_arg, int xloc, int yloc, bool context_menu, display& disp)
469 {
470  if(context_menu) {
471  last_context_menu_x_ = xloc;
472  last_context_menu_y_ = yloc;
473  }
474 
475  std::vector<config> items;
476  for(const auto& item : items_arg) {
477 
478  std::string id = item["id"];
480 
481  if(id == "wml" || (can_execute_command(cmd) && (!context_menu || in_context_menu(cmd)))) {
482  items.emplace_back("id", id);
483  }
484  }
485 
486 
487  // Iterate in reverse to avoid also iterating over the new inserted items
488  for(int i = items.size() - 1; i >= 0; i--) {
489  if(items[i]["id"] == "AUTOSAVES") {
490  expand_autosaves(items, i);
491  } else if(items[i]["id"] == "QUICKREPLAY") {
492  expand_quickreplay(items, i);
493  } else if(items[i]["id"] == "wml") {
494  expand_wml_commands(items, i);
495  }
496  }
497 
498  if(items.empty()) {
499  return;
500  }
501 
502  command_executor::show_menu(items, xloc, yloc, context_menu, disp);
503 }
504 
506 {
507  switch(cmd.hotkey_command) {
508  // Only display these if the mouse is over a castle or keep tile
511  case hotkey::HOTKEY_RECALL: {
512  // last_hex_ is set by mouse_events::mouse_motion
513  const map_location & last_hex = mouse_handler_.get_last_hex();
514  const int viewing_side = gui()->viewing_team().side();
515 
516  // A quick check to save us having to create the future map and
517  // possibly loop through all units.
518  if ( !play_controller_.get_map().is_keep(last_hex) &&
519  !play_controller_.get_map().is_castle(last_hex) )
520  return false;
521 
522  wb::future_map future; /* lasts until method returns. */
523 
524  return gamestate().side_can_recruit_on(viewing_side, last_hex);
525  }
526  default:
527  return true;
528  }
529 }
530 
532 {
533  return command_executor::get_action_image(cmd);
534 }
535 
537 {
538  switch(cmd.hotkey_command) {
539 
541  return (prefs::get().minimap_draw_villages()) ? hotkey::ACTION_ON : hotkey::ACTION_OFF;
543  return (prefs::get().minimap_movement_coding()) ? hotkey::ACTION_ON : hotkey::ACTION_OFF;
545  return (prefs::get().minimap_terrain_coding()) ? hotkey::ACTION_ON : hotkey::ACTION_OFF;
547  return (prefs::get().minimap_draw_units()) ? hotkey::ACTION_ON : hotkey::ACTION_OFF;
549  return (prefs::get().minimap_draw_terrain()) ? hotkey::ACTION_ON : hotkey::ACTION_OFF;
551  return (gui()->get_zoom_factor() == 1.0) ? hotkey::ACTION_ON : hotkey::ACTION_OFF;
553  return gui()->viewing_team().auto_shroud_updates() ? hotkey::ACTION_OFF : hotkey::ACTION_ON;
554  default:
556  }
557 }
558 
560 {
562 }
double t
Definition: astarsearch.cpp:63
virtual bool in_context_menu(const hotkey::ui_command &cmd) const
Sort-of-Singleton that many classes, both GUI and non-GUI, use to access the game data.
Definition: display.hpp:97
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)
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:368
game_events::wmi_manager & get_wml_menu_items()
Definition: game_state.cpp:385
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)
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
void show_menu(const std::vector< config > &items_arg, int xloc, int yloc, bool context_menu, display &disp) 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 abortt 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:209
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:1023
static std::string _(const char *str)
Definition: gettext.hpp:93
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:188
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:55
std::map< std::string, t_string > string_map
std::string filename
Filename.
Holds options for calls to function 'announce' (announce).
Definition: display.hpp:616
bool discard_previous
An announcement according these options should replace the previous announce (typical of fast announc...
Definition: display.hpp:625
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 paramneter for can_execute_command/do_execute_command These functions are used to ex...
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. when the action is the result of a me...
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.