The Battle for Wesnoth  1.19.0-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"
22 #include "preferences/game.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 
75 const team & play_controller::hotkey_handler::viewing_team() const { return play_controller_.get_teams()[gui()->viewing_team()]; }
76 bool play_controller::hotkey_handler::viewing_team_is_playing() const { return gui()->viewing_team() == gui()->playing_team(); }
77 
80 }
81 
83  menu_handler_.show_statistics(gui()->viewing_team()+1);
84 }
85 
88 }
89 
92 }
93 
95  play_controller_.save_game();
96 }
97 
99  play_controller_.save_replay();
100 }
101 
103  play_controller_.save_map();
104 }
105 
107  play_controller_.load_game();
108 }
109 
112 }
113 
115  int x = gui()->get_location_x(gui()->mouseover_hex());
116  int y = gui()->get_location_y(gui()->mouseover_hex());
117 
118  SDL_MouseButtonEvent event;
119 
120  event.button = 1;
121  event.x = x + 30;
122  event.y = y + 30;
123  event.which = 0;
124  event.state = SDL_PRESSED;
125 
126  mouse_handler_.mouse_press(event, false);
127 }
128 
131 }
132 
134  auto touched_hex = gui()->mouseover_hex();
135  mouse_handler_.touch_action(touched_hex, false);
136 }
137 
139  mouse_handler_.move_action(browse());
140 }
141 
144 }
146  mouse_handler_.select_hex(gui()->mouseover_hex(), false);
147 }
148 
150  int x = gui()->get_location_x(gui()->mouseover_hex());
151  int y = gui()->get_location_y(gui()->mouseover_hex());
152 
153  SDL_MouseButtonEvent event;
154 
155  event.button = 3;
156  event.x = x + 30;
157  event.y = y + 30;
158  event.which = 0;
159  event.state = SDL_PRESSED;
160 
161  mouse_handler_.mouse_press(event, true);
162 }
163 
164 
166  mouse_handler_.cycle_units(browse());
167 }
168 
171 }
172 
175 }
176 
179 }
180 
183 }
184 
186  play_controller_.undo();
187 }
188 
190  play_controller_.redo();
191 }
192 
194  menu_handler_.show_enemy_moves(ignore_units, play_controller_.current_side());
195 }
196 
198  menu_handler_.goto_leader(play_controller_.current_side());
199 }
200 
203 }
204 
207 }
208 
211 }
212 
215 }
216 
219 }
220 
222 {
224 
226  ao.discard_previous = true;
227 
228  if (preferences::turbo())
229  {
230  utils::string_map symbols;
232  gui()->announce(_("Accelerated speed enabled!") + "\n" + VGETTEXT("(press $hk to disable)", symbols), font::NORMAL_COLOR, ao);
233  }
234  else
235  {
236  gui()->announce(_("Accelerated speed disabled!"), font::NORMAL_COLOR, ao);
237  }
238 }
239 
241 {
242  play_controller_.set_scroll_up(on);
243 }
244 
246 {
247  play_controller_.set_scroll_down(on);
248 }
249 
251 {
252  play_controller_.set_scroll_left(on);
253 }
254 
256 {
257  play_controller_.set_scroll_right(on);
258 }
259 
261 {
262  DBG_G << "play_controller::do_execute_command: Found command:" << cmd.id;
263  if(balg::starts_with(cmd.id, quickload_prefix)) {
264  std::string savename = std::string(cmd.id.substr(quickload_prefix.size()));
265  // Load the game by throwing load_game_exception
266  load_autosave(savename, false);
267  }
268  if(balg::starts_with(cmd.id, quickreplay_prefix)) {
269  std::string savename = std::string(cmd.id.substr(quickreplay_prefix.size()));
270  // Load the game by throwing load_game_exception
271  load_autosave(savename, true);
272  }
273  // TODO c++20: Use string::starts_with
274  // wml commands that don't allow hotkey bindings use hotkey::HOTKEY_NULL. othes use HOTKEY_WML
275  if(balg::starts_with(cmd.id, wml_menu_hotkey_prefix)) {
276  std::string name = std::string(cmd.id.substr(wml_menu_hotkey_prefix.length()));
278 
280  name, hex, gamestate().gamedata_, gamestate(), play_controller_.get_units(), !press);
281  }
282  return command_executor::do_execute_command(cmd, press, release);
283 }
284 
286 {
287  switch(cmd.hotkey_command) {
288 
289  // Commands we can always do:
306  case hotkey::HOTKEY_MUTE:
314  case hotkey::HOTKEY_HELP:
330  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)
333  case hotkey::LUA_CONSOLE:
339  return true;
340 
342  std::size_t humans_notme_cnt = 0;
343  for(const auto& t : play_controller_.get_teams()) {
344  if(t.is_network_human()) {
345  ++humans_notme_cnt;
346  }
347  }
348 
349  return !(humans_notme_cnt < 1 || play_controller_.is_linger_mode() || play_controller_.is_observer());
350  }
351  // Commands that have some preconditions:
354 
357  return !linger() && play_controller_.enemies_visible();
358 
360  return !play_controller_.is_networked_mp(); // Can only load games if not in a network game
361 
366  return true;
367 
368  case hotkey::HOTKEY_REDO:
369  return play_controller_.can_redo();
370  case hotkey::HOTKEY_UNDO:
371  return play_controller_.can_undo();
372 
374  return menu_handler_.current_unit().valid();
375 
377  return mouse_handler_.get_last_hex().valid();
378 
380  return !events::commands_disabled &&
382  !(menu_handler_.current_unit()->unrenamable()) &&
383  menu_handler_.current_unit()->side() == gui()->viewing_side() &&
384  play_controller_.get_teams()[menu_handler_.current_unit()->side() - 1].is_local_human();
385 
386  default:
387  return false;
388  }
389 }
390 
391 template<typename T>
392 static void trim_items(std::vector<T>& newitems)
393 {
394  if(newitems.size() > 5) {
395  std::vector<T> subitems;
396  subitems.push_back(std::move(newitems[0]));
397  subitems.push_back(std::move(newitems[1]));
398  subitems.push_back(std::move(newitems[newitems.size() / 3]));
399  subitems.push_back(std::move(newitems[newitems.size() * 2 / 3]));
400  subitems.push_back(std::move(newitems.back()));
401  newitems = subitems;
402  }
403 }
404 
405 template<typename F>
406 static void foreach_autosave(int turn, saved_game& sg, F func) {
407 
409 
411  savegame::autosave_savegame autosave(sg, compression_format);
412  savegame::scenariostart_savegame scenariostart_save(sg, compression_format);
413 
414  const std::string start_name = scenariostart_save.create_filename();
415 
416  for(; turn != 0; turn--) {
417  const std::string name = autosave.create_filename(turn);
418 
419  if(savegame::save_game_exists(name, comp_format)) {
420  func(turn, name + compression::format_extension(comp_format));
421  }
422  }
423 
424  if(savegame::save_game_exists(start_name, comp_format)) {
425  func(0, start_name + compression::format_extension(comp_format));
426  }
427 }
428 
430 {
431  auto pos = items.erase(items.begin() + i);
432  std::vector<config> newitems;
433 
434  foreach_autosave(play_controller_.turn(), saved_game_, [&](int turn, const std::string& filename) {
435  // TODO: should this use variable substitution instead?
436  std::string label = turn > 0 ? _("Back to Turn ") + std::to_string(turn) : _("Back to Start");
437  newitems.emplace_back("label", label, "id", quickload_prefix + filename);
438  });
439  // Make sure list doesn't get too long: keep top two, midpoint and bottom.
440  trim_items(newitems);
441 
442  items.insert(pos, newitems.begin(), newitems.end());
443 }
444 
446 {
447  auto pos = items.erase(items.begin() + i);
448  std::vector<config> newitems;
449 
450  foreach_autosave(play_controller_.turn(), saved_game_, [&](int turn, const std::string& filename) {
451  // TODO: should this use variable substitution instead?
452  std::string label = turn > 0 ? _("Replay from Turn ") + std::to_string(turn) : _("Replay from Start");
453  newitems.emplace_back("label", label, "id", quickreplay_prefix + filename);
454  });
455  // Make sure list doesn't get too long: keep top two, midpoint and bottom.
456  trim_items(newitems);
457 
458  items.insert(pos, newitems.begin(), newitems.end());
459 }
460 
462 {
463 
464  auto pos = items.erase(items.begin() + i);
465  std::vector<config> newitems;
466 
468  gamestate(), gamestate().gamedata_, play_controller_.get_units());
469 
470  // Replace this placeholder entry with available menu items.
471  items.insert(pos, newitems.begin(), newitems.end());
472 }
473 
474 void play_controller::hotkey_handler::show_menu(const std::vector<config>& items_arg, int xloc, int yloc, bool context_menu, display& disp)
475 {
476  if(context_menu) {
477  last_context_menu_x_ = xloc;
478  last_context_menu_y_ = yloc;
479  }
480 
481  std::vector<config> items;
482  for(const auto& item : items_arg) {
483 
484  std::string id = item["id"];
486 
487  if(id == "wml" || (can_execute_command(cmd) && (!context_menu || in_context_menu(cmd)))) {
488  items.emplace_back("id", id);
489  }
490  }
491 
492 
493  // Iterate in reverse to avoid also iterating over the new inserted items
494  for(int i = items.size() - 1; i >= 0; i--) {
495  if(items[i]["id"] == "AUTOSAVES") {
496  expand_autosaves(items, i);
497  } else if(items[i]["id"] == "QUICKREPLAY") {
498  expand_quickreplay(items, i);
499  } else if(items[i]["id"] == "wml") {
500  expand_wml_commands(items, i);
501  }
502  }
503 
504  if(items.empty()) {
505  return;
506  }
507 
508  command_executor::show_menu(items, xloc, yloc, context_menu, disp);
509 }
510 
512 {
513  switch(cmd.hotkey_command) {
514  // Only display these if the mouse is over a castle or keep tile
517  case hotkey::HOTKEY_RECALL: {
518  // last_hex_ is set by mouse_events::mouse_motion
519  const map_location & last_hex = mouse_handler_.get_last_hex();
520  const int viewing_side = gui()->viewing_side();
521 
522  // A quick check to save us having to create the future map and
523  // possibly loop through all units.
524  if ( !play_controller_.get_map().is_keep(last_hex) &&
525  !play_controller_.get_map().is_castle(last_hex) )
526  return false;
527 
528  wb::future_map future; /* lasts until method returns. */
529 
530  return gamestate().side_can_recruit_on(viewing_side, last_hex);
531  }
532  default:
533  return true;
534  }
535 }
536 
538 {
539  return command_executor::get_action_image(cmd);
540 }
541 
543 {
544  switch(cmd.hotkey_command) {
545 
557  return (gui()->get_zoom_factor() == 1.0) ? hotkey::ACTION_ON : hotkey::ACTION_OFF;
559  return viewing_team().auto_shroud_updates() ? hotkey::ACTION_OFF : hotkey::ACTION_ON;
560  default:
562  }
563 }
564 
565 void play_controller::hotkey_handler::load_autosave(const std::string& filename, bool)
566 {
568 }
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:81
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
A button is a control that can be pushed to start an action or close a dialog.
Definition: button.hpp:52
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
const team & viewing_team() const
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_
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:208
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
This class stores all the data for a single 'side' (in game nomenclature).
Definition: team.hpp:74
#define VGETTEXT(msgid,...)
Handy wrappers around interpolate_variables_into_string and gettext.
std::size_t i
Definition: function.cpp:968
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::pair< std::string, unsigned > item
Definition: help_impl.hpp:412
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_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:177
const std::vector< std::string > items
void set_turbo(bool ison)
Definition: general.cpp:474
bool minimap_movement_coding()
Definition: general.cpp:827
bool minimap_draw_villages()
Definition: general.cpp:857
bool minimap_terrain_coding()
Definition: general.cpp:837
bool turbo()
Definition: general.cpp:465
bool minimap_draw_terrain()
Definition: general.cpp:867
compression::format save_compression_format()
Definition: game.cpp:840
bool minimap_draw_units()
Definition: general.cpp:847
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
Holds options for calls to function 'announce' (announce).
Definition: display.hpp:605
bool discard_previous
An announcement according these options should replace the previous announce (typical of fast announc...
Definition: display.hpp:614
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:38
bool valid() const
Definition: location.hpp:89
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.