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"
22 #include "game_state.hpp"
24 #include "hotkey/hotkey_item.hpp"
25 #include "log.hpp"
26 #include "map/map.hpp"
27 #include "play_controller.hpp"
29 #include "savegame.hpp"
30 #include "units/unit.hpp"
31 #include "whiteboard/manager.hpp"
32 
33 #include <boost/algorithm/string/predicate.hpp>
34 
35 namespace balg = boost::algorithm;
36 
37 #define ERR_G LOG_STREAM(err, lg::general())
38 #define WRN_G LOG_STREAM(warn, lg::general())
39 #define LOG_G LOG_STREAM(info, lg::general())
40 #define DBG_G LOG_STREAM(debug, lg::general())
41 
42 const std::string play_controller::hotkey_handler::wml_menu_hotkey_prefix = "wml_menu:";
43 
44 static const std::string quickload_prefix = "quickload:";
45 static const std::string quickreplay_prefix = "quickreplay:";
46 
48  : play_controller_(pc)
49  , menu_handler_(pc.get_menu_handler())
50  , mouse_handler_(pc.get_mouse_handler_base())
51  , saved_game_(sg)
52 {}
53 
55 
57  return &play_controller_.get_display();
58 }
59 
61  return play_controller_.gamestate();
62 }
63 
65  return play_controller_.gamestate();
66 }
67 
68 bool play_controller::hotkey_handler::browse() const { return play_controller_.is_browsing(); }
69 bool play_controller::hotkey_handler::linger() const { return play_controller_.is_linger_mode(); }
70 
73 }
74 
76  menu_handler_.show_statistics(gui()->viewing_team().side());
77 }
78 
81 }
82 
85 }
86 
88  play_controller_.save_game();
89 }
90 
92  play_controller_.save_replay();
93 }
94 
96  play_controller_.save_map();
97 }
98 
100  play_controller_.load_game();
101 }
102 
105 }
106 
109 }
110 
112  auto touched_hex = gui()->mouseover_hex();
113  mouse_handler_.touch_action(touched_hex, false);
114 }
115 
117  mouse_handler_.move_action(browse());
118 }
119 
122 }
124  mouse_handler_.select_hex(gui()->mouseover_hex(), false);
125 }
126 
128  mouse_handler_.cycle_units(browse());
129 }
130 
133 }
134 
137 }
138 
141 }
142 
145 }
146 
148  play_controller_.undo();
149 }
150 
152  play_controller_.redo();
153 }
154 
156  menu_handler_.show_enemy_moves(ignore_units, play_controller_.current_side());
157 }
158 
160  menu_handler_.goto_leader(play_controller_.current_side());
161 }
162 
165 }
166 
169 }
170 
173 }
174 
177 }
178 
181 }
182 
184 {
185  prefs::get().set_turbo(!prefs::get().turbo());
186 
188  ao.discard_previous = true;
189 
190  if(prefs::get().turbo()) {
191  utils::string_map symbols;
193  gui()->announce(_("Accelerated speed enabled!") + "\n" + VGETTEXT("(press $hk to disable)", symbols), font::NORMAL_COLOR, ao);
194  } else {
195  gui()->announce(_("Accelerated speed disabled!"), font::NORMAL_COLOR, ao);
196  }
197 }
198 
200 {
201  play_controller_.set_scroll_up(on);
202 }
203 
205 {
206  play_controller_.set_scroll_down(on);
207 }
208 
210 {
211  play_controller_.set_scroll_left(on);
212 }
213 
215 {
216  play_controller_.set_scroll_right(on);
217 }
218 
220 {
221  DBG_G << "play_controller::do_execute_command: Found command:" << cmd.id;
222 
223  // TODO c++20: Use string::starts_with
224  if(balg::starts_with(cmd.id, quickload_prefix)) {
225  std::string savename = cmd.id.substr(quickload_prefix.size());
226  // Load the game by throwing load_game_exception
227  load_autosave(savename, false);
228  }
229 
230  if(balg::starts_with(cmd.id, quickreplay_prefix)) {
231  std::string savename = cmd.id.substr(quickreplay_prefix.size());
232  // Load the game by throwing load_game_exception
233  load_autosave(savename, true);
234  }
235 
236  // wml commands that don't allow hotkey bindings use hotkey::HOTKEY_NULL. othes use HOTKEY_WML
237  if(balg::starts_with(cmd.id, wml_menu_hotkey_prefix)) {
238  std::string name = cmd.id.substr(wml_menu_hotkey_prefix.length());
240 
241  return gamestate()
243  .fire_item(name, hex, gamestate().gamedata_, gamestate(), play_controller_.get_units(), !press);
244  }
245 
246  return command_executor::do_execute_command(cmd, press, release);
247 }
248 
250 {
251  switch(cmd.hotkey_command) {
252 
253  // Commands we can always do:
270  case hotkey::HOTKEY_MUTE:
278  case hotkey::HOTKEY_HELP:
294  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)
297  case hotkey::LUA_CONSOLE:
303  return true;
304 
306  std::size_t humans_notme_cnt = 0;
307  for(const auto& t : play_controller_.get_teams()) {
308  if(t.is_network_human()) {
309  ++humans_notme_cnt;
310  }
311  }
312 
313  return !(humans_notme_cnt < 1 || play_controller_.is_linger_mode() || play_controller_.is_observer());
314  }
315 
316  // Commands that have some preconditions:
319 
322  return !linger() && play_controller_.enemies_visible();
323 
326  return !play_controller_.is_networked_mp(); // Can only load games if not in a network game
327 
332  return true;
333 
334  case hotkey::HOTKEY_REDO:
335  return play_controller_.can_redo();
336  case hotkey::HOTKEY_UNDO:
337  return play_controller_.can_undo();
338 
340  return menu_handler_.current_unit().valid();
341 
343  return mouse_handler_.get_last_hex().valid();
344 
346  return !events::commands_disabled &&
348  !(menu_handler_.current_unit()->unrenamable()) &&
349  menu_handler_.current_unit()->side() == gui()->viewing_team().side() &&
350  play_controller_.get_teams()[menu_handler_.current_unit()->side() - 1].is_local_human();
351 
352  default:
353  return false;
354  }
355 }
356 
357 namespace
358 {
359 template<typename T>
360 void append_items(std::vector<T>&& newitems, std::vector<T>& out)
361 {
362  auto input = std::move(newitems);
363 
364  // Make sure list doesn't get too long: keep top two, midpoint and bottom.
365  if(input.size() > 5) {
366  out.push_back(std::move(input[0]));
367  out.push_back(std::move(input[1]));
368  out.push_back(std::move(input[input.size() / 3]));
369  out.push_back(std::move(input[input.size() * 2 / 3]));
370  out.push_back(std::move(input.back()));
371  return;
372  }
373 
374  // Range is small enough; append the whole thing
375  std::move(input.begin(), input.end(), std::back_inserter(out));
376 }
377 
378 template<typename F>
379 void foreach_autosave(int turn, saved_game& sg, F func)
380 {
381  compression::format compression_format = prefs::get().save_compression_format();
382  auto autosave = savegame::autosave_savegame(sg, compression_format);
383  auto starting = savegame::scenariostart_savegame(sg, compression_format);
384 
385  for(; turn >= 0; --turn) {
386  const std::string name = turn > 0
387  ? autosave.create_filename(turn)
388  : starting.create_filename();
389 
390  if(savegame::save_game_exists(name, compression_format)) {
391  func(turn, name + compression::format_extension(compression_format));
392  }
393  }
394 }
395 
396 } // namespace
397 
398 void play_controller::hotkey_handler::expand_autosaves(std::vector<config>& items) const
399 {
400  std::vector<config> newitems;
401 
402  foreach_autosave(play_controller_.turn(), saved_game_, [&](int turn, const std::string& filename) {
403  std::string label = turn > 0
404  ? VGETTEXT("Back to Turn $number", {{"number", std::to_string(turn)}})
405  : _("Back to Start");
406 
407  newitems.emplace_back("label", label, "id", quickload_prefix + filename);
408  });
409 
410  append_items(std::move(newitems), items);
411 }
412 
413 void play_controller::hotkey_handler::expand_quickreplay(std::vector<config>& items) const
414 {
415  std::vector<config> newitems;
416 
417  foreach_autosave(play_controller_.turn(), saved_game_, [&](int turn, const std::string& filename) {
418  std::string label = turn > 0
419  ? VGETTEXT("Replay from Turn $number", {{"number", std::to_string(turn)}})
420  : _("Replay from Start");
421 
422  newitems.emplace_back("label", label, "id", quickreplay_prefix + filename);
423  });
424 
425  append_items(std::move(newitems), items);
426 }
427 
429 {
430  gamestate()
432  .get_items(mouse_handler_.get_last_hex(), items,gamestate(), gamestate().gamedata_, play_controller_.get_units());
433 }
434 
435 void play_controller::hotkey_handler::show_menu(const std::vector<config>& items_arg, const point& menu_loc, bool context_menu)
436 {
437  std::vector<config> items;
438  for(const auto& item : items_arg) {
439  std::string id = item["id"];
440  auto cmd = hotkey::ui_command(id);
441 
442  if(id == "AUTOSAVES") {
443  expand_autosaves(items);
444  } else if(id == "QUICKREPLAY") {
445  expand_quickreplay(items);
446  } else if(id == "wml") {
447  expand_wml_commands(items);
448  } else if(can_execute_command(cmd) && (!context_menu || in_context_menu(cmd))) {
449  items.emplace_back("id", id);
450  }
451  }
452 
453  if(items.empty()) {
454  return;
455  }
456 
457  command_executor::show_menu(items, menu_loc, context_menu);
458 }
459 
461 {
462  switch(cmd.hotkey_command) {
463  // Only display these if the mouse is over a castle or keep tile
466  case hotkey::HOTKEY_RECALL: {
467  // last_hex_ is set by mouse_events::mouse_motion
468  const map_location& last_hex = mouse_handler_.get_last_hex();
469 
470  // A quick check to save us having to create the future map and
471  // possibly loop through all units.
472  if(!play_controller_.get_map().is_keep(last_hex)
473  && !play_controller_.get_map().is_castle(last_hex))
474  {
475  return false;
476  }
477 
478  wb::future_map future; /* lasts until method returns. */
479 
480  return gamestate().side_can_recruit_on(gui()->viewing_team().side(), last_hex);
481  }
482  default:
483  return true;
484  }
485 }
486 
488 {
489  switch(cmd.hotkey_command) {
491  return hotkey::on_if(prefs::get().minimap_draw_villages());
493  return hotkey::on_if(prefs::get().minimap_movement_coding());
495  return hotkey::on_if(prefs::get().minimap_terrain_coding());
497  return hotkey::on_if(prefs::get().minimap_draw_units());
499  return hotkey::on_if(prefs::get().minimap_draw_terrain());
501  return hotkey::on_if(gui()->get_zoom_factor() == 1.0);
503  return hotkey::on_if(gui()->viewing_team().auto_shroud_updates() == false);
504  default:
506  }
507 }
508 
510 {
512 }
double t
Definition: astarsearch.cpp:63
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()
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:80
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
virtual hotkey::action_state get_action_state(const hotkey::ui_command &) const override
static const std::string wml_menu_hotkey_prefix
virtual void select_and_action() override
virtual void save_game() override
void expand_quickreplay(std::vector< config > &items) const
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
virtual void goto_leader() override
virtual void show_help() override
virtual void preferences() override
void expand_wml_commands(std::vector< config > &items)
Replaces "wml" in items with all active WML menu items for the current field.
virtual void scroll_up(bool on) override
virtual void scroll_left(bool on) override
virtual void status_table() override
void expand_autosaves(std::vector< config > &items) const
virtual void unit_list() override
virtual void undo() 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 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
virtual void speak() override
virtual void show_enemy_moves(bool ignore_units) override
hotkey_handler(play_controller &, saved_game &)
bool in_context_menu(const hotkey::ui_command &cmd) const override
Inherited from command_executor.
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
void show_menu(const std::vector< config > &items_arg, const point &menu_loc, bool context_menu) 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
Class for start-of-scenario saves.
Definition: savegame.hpp:307
#define VGETTEXT(msgid,...)
Handy wrappers around interpolate_variables_into_string and gettext.
static std::string _(const char *str)
Definition: gettext.hpp:97
std::string label
What to show in the filter's drop-down list.
Definition: manager.cpp:201
static const std::string quickload_prefix
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.
action_state on_if(bool condition)
Returns action_state::on if condition is true, else action_state::off.
const hotkey_command & get_hotkey_command(std::string_view command)
Returns the hotkey_command with the given id.
@ 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
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
Holds a 2D point.
Definition: point.hpp:25
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.