The Battle for Wesnoth 1.21.0-dev
 
Loading...
Searching...
No Matches
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
20#include "game_display.hpp"
22#include "game_state.hpp"
25#include "log.hpp"
26#include "map/map.hpp"
27#include "play_controller.hpp"
29#include "savegame.hpp"
30#include "units/unit.hpp"
32
33#include <boost/algorithm/string/predicate.hpp>
34
35namespace 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
43
44static const std::string quickload_prefix = "quickload:";
45static 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
68bool play_controller::hotkey_handler::browse() const { return play_controller_.is_browsing(); }
69bool play_controller::hotkey_handler::linger() const { return play_controller_.is_linger_mode(); }
70
74
78
82
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
106
110
112 auto touched_hex = gui()->mouseover_hex();
113 mouse_handler_.touch_action(touched_hex, false);
114}
115
119
124 mouse_handler_.select_hex(gui()->mouseover_hex(), false);
125}
126
130
134
138
142
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
166
170
174
178
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 reset_gamestate_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:
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)
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
335 return play_controller_.can_redo();
337 return play_controller_.can_undo();
338
341
344
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
357namespace
358{
359template<typename T>
360void 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
378template<typename F>
379void foreach_autosave(int turn, saved_game& sg, F func)
380{
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
398void 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
413void 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
435void 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
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{
511}
double t
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.
void cycle_units(const bool browse, const bool reverse=false)
const map_location & get_last_hex() const
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.
bool side_can_recruit_on(int side, map_location loc) const
Checks if any of the sides leaders can recruit at a location.
game_events::wmi_manager & get_wml_menu_items()
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 scroll_down(bool on) 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_
std::size_t turn() const
events::mouse_handler mouse_handler_
game_state & gamestate()
void set_turbo(bool ison)
static prefs & get()
compression::format save_compression_format()
Class for autosaves.
Definition savegame.hpp:257
Class for start-of-scenario saves.
Definition savegame.hpp:283
#define VGETTEXT(msgid,...)
Handy wrappers around interpolate_variables_into_string and gettext.
#define DBG_G
Definition gettext.cpp:36
static std::string _(const char *str)
Definition gettext.hpp:100
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
This file implements all the hotkey handling and menu details for play controller.
Standard logging facilities (interface).
std::string format_extension(format compression_format)
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_MOUSE_SCROLL
@ HOTKEY_TERRAIN_DESCRIPTION
@ HOTKEY_LABEL_SETTINGS
@ HOTKEY_HELP_ABOUT_SAVELOAD
@ HOTKEY_SHOW_ENEMY_MOVES
@ HOTKEY_ACHIEVEMENTS
@ HOTKEY_DESELECT_HEX
@ HOTKEY_UNIT_DESCRIPTION
@ HOTKEY_REPEAT_RECRUIT
@ HOTKEY_SCROLL_RIGHT
@ HOTKEY_SELECT_AND_ACTION
@ HOTKEY_MINIMAP_DRAW_TERRAIN
@ HOTKEY_MAP_SCREENSHOT
@ HOTKEY_BEST_ENEMY_MOVES
@ HOTKEY_QUIT_TO_DESKTOP
@ HOTKEY_LOAD_AUTOSAVES
@ HOTKEY_TOGGLE_ELLIPSES
@ HOTKEY_MINIMAP_CODING_TERRAIN
@ HOTKEY_MINIMAP_DRAW_UNITS
@ HOTKEY_DELAY_SHROUD
@ HOTKEY_STATUS_TABLE
@ HOTKEY_ZOOM_DEFAULT
@ HOTKEY_CYCLE_BACK_UNITS
@ HOTKEY_MINIMAP_CODING_UNIT
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:602
bool discard_previous
An announcement according these options should replace the previous announce (typical of fast announc...
Definition display.hpp:611
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:46
bool valid() const
Definition location.hpp:111
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.