The Battle for Wesnoth  1.17.21+dev
hotkey_handler.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2014 - 2023
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 "actions/create.hpp"
19 #include "font/standard_colors.hpp"
20 #include "formula/string_utils.hpp"
21 #include "game_display.hpp"
22 #include "game_errors.hpp"
25 #include "preferences/game.hpp"
26 #include "game_state.hpp"
28 #include "hotkey/hotkey_item.hpp"
29 #include "log.hpp"
30 #include "map/map.hpp"
31 #include "play_controller.hpp"
32 #include "preferences/display.hpp"
33 #include "savegame.hpp"
34 #include "saved_game.hpp"
35 #include "whiteboard/manager.hpp"
36 
37 #include <boost/algorithm/string/predicate.hpp>
38 
39 namespace balg = boost::algorithm;
40 
41 #include "units/unit.hpp"
42 
43 
44 #define ERR_G LOG_STREAM(err, lg::general())
45 #define WRN_G LOG_STREAM(warn, lg::general())
46 #define LOG_G LOG_STREAM(info, lg::general())
47 #define DBG_G LOG_STREAM(debug, lg::general())
48 
49 const std::string play_controller::hotkey_handler::wml_menu_hotkey_prefix = "wml_menu:";
50 
51 static const std::string quickload_prefix = "quickload:";
52 static const std::string quickreplay_prefix = "quickreplay:";
53 
55  : play_controller_(pc)
56  , menu_handler_(pc.get_menu_handler())
57  , mouse_handler_(pc.get_mouse_handler_base())
58  , saved_game_(sg)
59  , last_context_menu_x_(0)
60  , last_context_menu_y_(0)
61 {}
62 
64 
66  return &play_controller_.get_display();
67 }
68 
70  return play_controller_.gamestate();
71 }
72 
74  return play_controller_.gamestate();
75 }
76 
77 bool play_controller::hotkey_handler::browse() const { return play_controller_.is_browsing(); }
78 bool play_controller::hotkey_handler::linger() const { return play_controller_.is_linger_mode(); }
79 
80 const team & play_controller::hotkey_handler::viewing_team() const { return play_controller_.get_teams()[gui()->viewing_team()]; }
81 bool play_controller::hotkey_handler::viewing_team_is_playing() const { return gui()->viewing_team() == gui()->playing_team(); }
82 
85 }
86 
88  menu_handler_.show_statistics(gui()->viewing_team()+1);
89 }
90 
93 }
94 
97 }
98 
100  play_controller_.save_game();
101 }
102 
104  play_controller_.save_replay();
105 }
106 
108  play_controller_.save_map();
109 }
110 
112  play_controller_.load_game();
113 }
114 
117 }
118 
120  int x = gui()->get_location_x(gui()->mouseover_hex());
121  int y = gui()->get_location_y(gui()->mouseover_hex());
122 
123  SDL_MouseButtonEvent event;
124 
125  event.button = 1;
126  event.x = x + 30;
127  event.y = y + 30;
128  event.which = 0;
129  event.state = SDL_PRESSED;
130 
131  mouse_handler_.mouse_press(event, false);
132 }
133 
136 }
137 
139  auto touched_hex = gui()->mouseover_hex();
140  mouse_handler_.touch_action(touched_hex, false);
141 }
142 
144  mouse_handler_.move_action(browse());
145 }
146 
149 }
151  mouse_handler_.select_hex(gui()->mouseover_hex(), false);
152 }
153 
155  int x = gui()->get_location_x(gui()->mouseover_hex());
156  int y = gui()->get_location_y(gui()->mouseover_hex());
157 
158  SDL_MouseButtonEvent event;
159 
160  event.button = 3;
161  event.x = x + 30;
162  event.y = y + 30;
163  event.which = 0;
164  event.state = SDL_PRESSED;
165 
166  mouse_handler_.mouse_press(event, true);
167 }
168 
169 
171  mouse_handler_.cycle_units(browse());
172 }
173 
176 }
177 
180 }
181 
184 }
185 
188 }
189 
191  play_controller_.undo();
192 }
193 
195  play_controller_.redo();
196 }
197 
199  menu_handler_.show_enemy_moves(ignore_units, play_controller_.current_side());
200 }
201 
203  menu_handler_.goto_leader(play_controller_.current_side());
204 }
205 
208 }
209 
212 }
213 
216 }
217 
220 }
221 
224 }
225 
227 {
229 
231  ao.discard_previous = true;
232 
233  if (preferences::turbo())
234  {
235  utils::string_map symbols;
237  gui()->announce(_("Accelerated speed enabled!") + "\n" + VGETTEXT("(press $hk to disable)", symbols), font::NORMAL_COLOR, ao);
238  }
239  else
240  {
241  gui()->announce(_("Accelerated speed disabled!"), font::NORMAL_COLOR, ao);
242  }
243 }
244 
246 {
247  play_controller_.set_scroll_up(on);
248 }
249 
251 {
252  play_controller_.set_scroll_down(on);
253 }
254 
256 {
257  play_controller_.set_scroll_left(on);
258 }
259 
261 {
262  play_controller_.set_scroll_right(on);
263 }
264 
266 {
267  DBG_G << "play_controller::do_execute_command: Found command:" << cmd.id;
268  if(balg::starts_with(cmd.id, quickload_prefix)) {
269  std::string savename = std::string(cmd.id.substr(quickload_prefix.size()));
270  // Load the game by throwing load_game_exception
271  load_autosave(savename, false);
272  }
273  if(balg::starts_with(cmd.id, quickreplay_prefix)) {
274  std::string savename = std::string(cmd.id.substr(quickreplay_prefix.size()));
275  // Load the game by throwing load_game_exception
276  load_autosave(savename, true);
277  }
278  // TODO c++20: Use string::starts_with
279  // wml commands that don't allow hotkey bindings use hotkey::HOTKEY_NULL. othes use HOTKEY_WML
280  if(balg::starts_with(cmd.id, wml_menu_hotkey_prefix)) {
281  std::string name = std::string(cmd.id.substr(wml_menu_hotkey_prefix.length()));
283 
285  name, hex, gamestate().gamedata_, gamestate(), play_controller_.get_units(), !press);
286  }
287  return command_executor::do_execute_command(cmd, press, release);
288 }
289 
291 {
292  switch(cmd.hotkey_command) {
293 
294  // Commands we can always do:
311  case hotkey::HOTKEY_MUTE:
319  case hotkey::HOTKEY_HELP:
335  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)
338  case hotkey::LUA_CONSOLE:
344  return true;
345 
347  std::size_t humans_notme_cnt = 0;
348  for(const auto& t : play_controller_.get_teams()) {
349  if(t.is_network_human()) {
350  ++humans_notme_cnt;
351  }
352  }
353 
354  return !(humans_notme_cnt < 1 || play_controller_.is_linger_mode() || play_controller_.is_observer());
355  }
356  // Commands that have some preconditions:
359 
362  return !linger() && play_controller_.enemies_visible();
363 
365  return !play_controller_.is_networked_mp(); // Can only load games if not in a network game
366 
371  return true;
372 
373  case hotkey::HOTKEY_REDO:
374  return play_controller_.can_redo();
375  case hotkey::HOTKEY_UNDO:
376  return play_controller_.can_undo();
377 
379  return menu_handler_.current_unit().valid();
380 
382  return mouse_handler_.get_last_hex().valid();
383 
385  return !events::commands_disabled &&
387  !(menu_handler_.current_unit()->unrenamable()) &&
388  menu_handler_.current_unit()->side() == gui()->viewing_side() &&
389  play_controller_.get_teams()[menu_handler_.current_unit()->side() - 1].is_local_human();
390 
391  default:
392  return false;
393  }
394 }
395 
396 template<typename T>
397 static void trim_items(std::vector<T>& newitems)
398 {
399  if(newitems.size() > 5) {
400  std::vector<T> subitems;
401  subitems.push_back(std::move(newitems[0]));
402  subitems.push_back(std::move(newitems[1]));
403  subitems.push_back(std::move(newitems[newitems.size() / 3]));
404  subitems.push_back(std::move(newitems[newitems.size() * 2 / 3]));
405  subitems.push_back(std::move(newitems.back()));
406  newitems = subitems;
407  }
408 }
409 
410 template<typename F>
411 static void foreach_autosave(int turn, saved_game& sg, F func) {
412 
414 
416  savegame::autosave_savegame autosave(sg, compression_format);
417  savegame::scenariostart_savegame scenariostart_save(sg, compression_format);
418 
419  const std::string start_name = scenariostart_save.create_filename();
420 
421  for(; turn != 0; turn--) {
422  const std::string name = autosave.create_filename(turn);
423 
424  if(savegame::save_game_exists(name, comp_format)) {
425  func(turn, name + compression::format_extension(comp_format));
426  }
427  }
428 
429  if(savegame::save_game_exists(start_name, comp_format)) {
430  func(0, start_name + compression::format_extension(comp_format));
431  }
432 }
433 
435 {
436  auto pos = items.erase(items.begin() + i);
437  std::vector<config> newitems;
438 
439  foreach_autosave(play_controller_.turn(), saved_game_, [&](int turn, const std::string& filename) {
440  // TODO: should this use variable substitution instead?
441  std::string label = turn > 0 ? _("Back to Turn ") + std::to_string(turn) : _("Back to Start");
442  newitems.emplace_back("label", label, "id", quickload_prefix + filename);
443  });
444  // Make sure list doesn't get too long: keep top two, midpoint and bottom.
445  trim_items(newitems);
446 
447  items.insert(pos, newitems.begin(), newitems.end());
448 }
449 
451 {
452  auto pos = items.erase(items.begin() + i);
453  std::vector<config> newitems;
454 
455  foreach_autosave(play_controller_.turn(), saved_game_, [&](int turn, const std::string& filename) {
456  // TODO: should this use variable substitution instead?
457  std::string label = turn > 0 ? _("Replay from Turn ") + std::to_string(turn) : _("Replay from Start");
458  newitems.emplace_back("label", label, "id", quickreplay_prefix + filename);
459  });
460  // Make sure list doesn't get too long: keep top two, midpoint and bottom.
461  trim_items(newitems);
462 
463  items.insert(pos, newitems.begin(), newitems.end());
464 }
465 
467 {
468 
469  auto pos = items.erase(items.begin() + i);
470  std::vector<config> newitems;
471 
473  gamestate(), gamestate().gamedata_, play_controller_.get_units());
474 
475  // Replace this placeholder entry with available menu items.
476  items.insert(pos, newitems.begin(), newitems.end());
477 }
478 
479 void play_controller::hotkey_handler::show_menu(const std::vector<config>& items_arg, int xloc, int yloc, bool context_menu, display& disp)
480 {
481  if(context_menu) {
482  last_context_menu_x_ = xloc;
483  last_context_menu_y_ = yloc;
484  }
485 
486  std::vector<config> items;
487  for(const auto& item : items_arg) {
488 
489  std::string id = item["id"];
491 
492  if(id == "wml" || (can_execute_command(cmd) && (!context_menu || in_context_menu(cmd)))) {
493  items.emplace_back("id", id);
494  }
495  }
496 
497 
498  // Iterate in reverse to avoid also iterating over the new inserted items
499  for(int i = items.size() - 1; i >= 0; i--) {
500  if(items[i]["id"] == "AUTOSAVES") {
501  expand_autosaves(items, i);
502  } else if(items[i]["id"] == "QUICKREPLAY") {
503  expand_quickreplay(items, i);
504  } else if(items[i]["id"] == "wml") {
505  expand_wml_commands(items, i);
506  }
507  }
508 
509  if(items.empty()) {
510  return;
511  }
512 
513  command_executor::show_menu(items, xloc, yloc, context_menu, disp);
514 }
515 
517 {
518  switch(cmd.hotkey_command) {
519  // Only display these if the mouse is over a castle or keep tile
522  case hotkey::HOTKEY_RECALL: {
523  // last_hex_ is set by mouse_events::mouse_motion
524  const map_location & last_hex = mouse_handler_.get_last_hex();
525  const int viewing_side = gui()->viewing_side();
526 
527  // A quick check to save us having to create the future map and
528  // possibly loop through all units.
529  if ( !play_controller_.get_map().is_keep(last_hex) &&
530  !play_controller_.get_map().is_castle(last_hex) )
531  return false;
532 
533  wb::future_map future; /* lasts until method returns. */
534 
535  return gamestate().side_can_recruit_on(viewing_side, last_hex);
536  }
537  default:
538  return true;
539  }
540 }
541 
543 {
544  return command_executor::get_action_image(cmd);
545 }
546 
548 {
549  switch(cmd.hotkey_command) {
550 
562  return (gui()->get_zoom_factor() == 1.0) ? hotkey::ACTION_ON : hotkey::ACTION_OFF;
564  return viewing_team().auto_shroud_updates() ? hotkey::ACTION_OFF : hotkey::ACTION_ON;
565  default:
567  }
568 }
569 
570 void play_controller::hotkey_handler::load_autosave(const std::string& filename, bool)
571 {
573 }
double t
Definition: astarsearch.cpp:65
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:87
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 map_location & get_last_hex() const
void touch_action(const map_location hex, bool browse)
void cycle_units(const bool browse, const bool reverse=false)
void move_action(bool browse)
Overridden in derived class.
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
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:280
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:210
std::string create_filename() const
Build the filename according to the specific savegame's needs.
Definition: savegame.hpp:180
Class for start-of-scenario saves.
Definition: savegame.hpp:306
This class stores all the data for a single 'side' (in game nomenclature).
Definition: team.hpp:76
Various functions related to the creation of units (recruits, recalls, and placed units).
#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).
Declarations for a class that implements WML-defined (right-click) menu items.
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:414
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:185
const std::vector< std::string > items
void set_turbo(bool ison)
Definition: general.cpp:474
bool minimap_movement_coding()
Definition: general.cpp:835
bool minimap_draw_villages()
Definition: general.cpp:865
bool minimap_terrain_coding()
Definition: general.cpp:845
bool turbo()
Definition: general.cpp:465
bool minimap_draw_terrain()
Definition: general.cpp:875
compression::format save_compression_format()
Definition: game.cpp:843
bool minimap_draw_units()
Definition: general.cpp:855
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:61
std::map< std::string, t_string > string_map
Holds options for calls to function 'announce' (announce).
Definition: display.hpp:606
bool discard_previous
An announcement according these options should replace the previous announce (typical of fast announc...
Definition: display.hpp:615
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:274
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.