The Battle for Wesnoth  1.17.21+dev
menu_item.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2003 - 2023
3  by David White <dave@whitevine.net>
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 
16 /**
17  * @file
18  * Definitions for a class that implements WML-defined (right-click) menu items.
19  */
20 
22 
24 #include "game_events/handlers.hpp"
25 #include "game_events/pump.hpp"
26 
27 #include "game_config.hpp"
29 #include "log.hpp"
30 #include "replay_helper.hpp"
31 #include "resources.hpp"
32 #include "synced_context.hpp"
33 #include "terrain/filter.hpp"
34 #include "deprecation.hpp"
35 
36 static lg::log_domain log_engine("engine");
37 #define ERR_NG LOG_STREAM(err, log_engine)
38 #define LOG_NG LOG_STREAM(info, log_engine)
39 
40 // This file is in the game_events namespace.
41 namespace game_events
42 {
43 namespace
44 { // Some helpers for construction.
45 
46 /**
47  * Build the event name associated with the given menu item id.
48  * This is a separate function so it can be easily shared by multiple
49  * constructors.
50  */
51 inline std::string make_item_name(const std::string& id)
52 {
53  return std::string("menu item") + (id.empty() ? "" : ' ' + id);
54 }
55 
56 /**
57  * Build the hotkey id associated with the given menu item id.
58  * This is a separate function so it can be easily shared by multiple
59  * constructors.
60  */
61 inline std::string make_item_hotkey(const std::string& id)
62 {
64 }
65 
66 } // anonymous namespace
67 
68 // Constructor for reading from a saved config.
69 wml_menu_item::wml_menu_item(const std::string& id, const config& cfg)
70  : item_id_(id)
71  , event_name_(make_item_name(id))
72  , hotkey_id_(make_item_hotkey(id))
73  , hotkey_record_()
74  , image_(cfg["image"].str())
75  , description_(cfg["description"].t_str())
76  , needs_select_(cfg["needs_select"].to_bool(false))
77  , show_if_(cfg.child_or_empty("show_if"), true)
78  , filter_location_(cfg.child_or_empty("filter_location"), true)
79  , command_(cfg.child_or_empty("command"))
80  , default_hotkey_(cfg.child_or_empty("default_hotkey"))
81  , use_hotkey_(cfg["use_hotkey"].to_bool(true))
82  , use_wml_menu_(cfg["use_hotkey"].str() != "only")
83  , is_synced_(cfg["synced"].to_bool(true))
84  , persistent_(cfg["persistent"].to_bool(true))
85 {
86  if(cfg.has_attribute("needs_select")) {
87  deprecated_message("needs_select", DEP_LEVEL::INDEFINITE, {1, 15, 0});
88  }
89 }
90 
91 // Constructor for items defined in an event.
92 wml_menu_item::wml_menu_item(const std::string& id, const vconfig& definition)
93  : item_id_(id)
94  , event_name_(make_item_name(id))
95  , hotkey_id_(make_item_hotkey(id))
96  , hotkey_record_()
97  , image_()
98  , description_()
99  , needs_select_(false)
100  , show_if_(vconfig::empty_vconfig())
101  , filter_location_(vconfig::empty_vconfig())
102  , command_()
103  , default_hotkey_()
104  , use_hotkey_(true)
105  , use_wml_menu_(true)
106  , is_synced_(true)
107  , persistent_(true)
108 {
109  // On the off-chance that update() doesn't do it, add the hotkey here.
110  // (Update can always modify it.)
112 
113  // Apply WML.
114  update(definition);
115 }
116 
117 // Constructor for items modified by an event.
118 wml_menu_item::wml_menu_item(const std::string& id, const vconfig& definition, wml_menu_item& original)
119  : item_id_(id)
120  , event_name_(make_item_name(id))
121  , hotkey_id_(make_item_hotkey(id))
122  , hotkey_record_(std::move(original.hotkey_record_)) // Make sure we have full lifetime control of the old record
123  , image_(original.image_)
124  , description_(original.description_)
125  , needs_select_(original.needs_select_)
126  , show_if_(original.show_if_)
127  , filter_location_(original.filter_location_)
128  , command_(original.command_)
129  , default_hotkey_(original.default_hotkey_)
130  , use_hotkey_(original.use_hotkey_)
131  , use_wml_menu_(original.use_wml_menu_)
132  , is_synced_(original.is_synced_)
133  , persistent_(original.persistent_)
134 {
135  // Apply WML.
136  update(definition);
137 }
138 
139 const std::string& wml_menu_item::image() const
140 {
141  // Default the image?
142  return image_.empty() ? game_config::images::wml_menu : image_;
143 }
144 
145 
147 {
148  // Failing the [show_if] tag means no show.
150  return false;
151  }
152 
153  // Failing the [fiter_location] tag means no show.
154  if(!filter_location_.empty() && !terrain_filter(filter_location_, &filter_con, false)(hex)) {
155  return false;
156  }
157 
158  // Failing to have a required selection means no show.
159  if(needs_select_ && !data.last_selected.valid()) {
160  return false;
161  }
162 
163  // Passed all tests.
164  return true;
165 }
166 
167 void wml_menu_item::fire_event(const map_location& event_hex, const game_data& data) const
168 {
169  if(!this->is_synced()) {
170  // It is possible to for example show a help menu during a [delay] of a synced event.
172  assert(resources::game_events != nullptr);
174  return;
175  }
176 
177  const map_location& last_select = data.last_selected;
178 
179  // No new player-issued commands allowed while this is firing.
180  const events::command_disabler disable_commands;
181 
182  // instead of adding a second "select" event like it was done before, we just fire the select event again, and this
183  // time in a synced context.
184  // note that there couldn't be a user choice during the last "select" event because it didn't run in a synced
185  // context.
186  if(needs_select_ && last_select.valid()) {
188  "fire_event", replay_helper::get_event(event_name_, event_hex, &last_select));
189  } else {
191  "fire_event", replay_helper::get_event(event_name_, event_hex, nullptr));
192  }
193 }
194 
196 {
197  if(!command_.empty()) {
198  assert(resources::game_events);
200  }
201 
202  // Hotkey support
203  if(use_hotkey_) {
204  hotkey_record_.reset();
205  }
206 }
207 
209 {
210  // If this menu item has a [command], add a handler for it.
211  if(!command_.empty()) {
212  assert(resources::game_events);
214  }
215 
216  // Hotkey support
217  if(use_hotkey_) {
219  }
220 }
221 
223 {
224  cfg["id"] = item_id_;
225  cfg["image"] = image_;
226  cfg["description"] = description_;
227  cfg["synced"] = is_synced_;
228 
229  if(needs_select_) {
230  cfg["needs_select"] = true;
231  }
232 
233  if(use_hotkey_ && use_wml_menu_) {
234  cfg["use_hotkey"] = true;
235  }
236 
237  if(use_hotkey_ && !use_wml_menu_) {
238  cfg["use_hotkey"] = "only";
239  }
240 
241  if(!use_hotkey_ && use_wml_menu_) {
242  cfg["use_hotkey"] = false;
243  }
244 
245  if(!use_hotkey_ && !use_wml_menu_) {
246  ERR_NG << "Bad data: wml_menu_item with both use_wml_menu and "
247  "use_hotkey set to false is not supposed to be possible.";
248  cfg["use_hotkey"] = false;
249  }
250 
251  if(!show_if_.empty()) {
252  cfg.add_child("show_if", show_if_.get_config());
253  }
254 
255  if(!filter_location_.empty()) {
256  cfg.add_child("filter_location", filter_location_.get_config());
257  }
258 
259  if(!command_.empty()) {
260  cfg.add_child("command", command_);
261  }
262 
263  if(!default_hotkey_.empty()) {
264  cfg.add_child("default_hotkey", default_hotkey_);
265  }
266 }
267 
269 {
270  const bool old_use_hotkey = use_hotkey_;
271  // Tracks whether or not the hotkey has been updated.
272  bool hotkey_updated = false;
273 
274  if(vcfg.has_attribute("image")) {
275  image_ = vcfg["image"].str();
276  }
277 
278  if(vcfg.has_attribute("description")) {
279  description_ = vcfg["description"].t_str();
280  hotkey_updated = true;
281  }
282 
283  if(vcfg.has_attribute("needs_select")) {
284  deprecated_message("needs_select", DEP_LEVEL::INDEFINITE, {1, 15, 0});
285  needs_select_ = vcfg["needs_select"].to_bool();
286  }
287 
288  if(vcfg.has_attribute("synced")) {
289  is_synced_ = vcfg["synced"].to_bool(true);
290  }
291 
292  if(vcfg.has_attribute("persistent")) {
293  persistent_ = vcfg["persistent"].to_bool(true);
294  }
295 
296  if(const vconfig& child = vcfg.child("show_if")) {
297  show_if_ = child;
299  }
300 
301  if(const vconfig& child = vcfg.child("filter_location")) {
302  filter_location_ = child;
304  }
305 
306  if(const vconfig& child = vcfg.child("default_hotkey")) {
307  default_hotkey_ = child.get_parsed_config();
308  hotkey_updated = true;
309  }
310 
311  if(vcfg.has_attribute("use_hotkey")) {
312  const config::attribute_value& use_hotkey_av = vcfg["use_hotkey"];
313 
314  use_hotkey_ = use_hotkey_av.to_bool(true);
315  use_wml_menu_ = use_hotkey_av.str() != "only";
316  }
317 
318  if(const vconfig& cmd = vcfg.child("command")) {
319  const bool delayed = cmd["delayed_variable_substitution"].to_bool(true);
320  update_command(delayed ? cmd.get_config() : cmd.get_parsed_config());
321  }
322 
323  // Update the registered hotkey?
324 
325  if(use_hotkey_ && !old_use_hotkey) {
326  // The hotkey needs to be enabled.
328 
329  } else if(use_hotkey_ && hotkey_updated) {
330  // The hotkey needs to be updated.
332 
333  } else if(!use_hotkey_ && old_use_hotkey) {
334  // The hotkey needs to be disabled.
335  hotkey_record_.reset();
336  }
337 }
338 
339 void wml_menu_item::update_command(const config& new_command)
340 {
341  // If there is an old command, remove it from the event handlers.
342  assert(resources::game_events);
343 
345  if(ptr->is_menu_item()) {
346  LOG_NG << "Removing command for " << event_name_ << ".";
347  man.remove_event_handler(command_["id"].str());
348  }
349  });
350 
351  // Update our stored command.
352  if(new_command.empty()) {
353  command_.clear();
354  } else {
355  command_ = new_command;
356 
357  // Set some fields required by event processing.
358  config::attribute_value& event_id = command_["id"];
359  if(event_id.empty() && !item_id_.empty()) {
360  event_id = item_id_;
361  }
362 
363  command_["name"] = event_name_;
364  command_["first_time_only"] = false;
365  command_["priority"] = 0.;
366 
367  // Register the event.
368  LOG_NG << "Setting command for " << event_name_ << " to:\n" << command_;
369  assert(resources::game_events);
370  assert(resources::lua_kernel);
372  }
373 }
374 
375 } // end namespace game_events
Variant for storing WML attributes.
std::string str(const std::string &fallback="") const
bool to_bool(bool def=false) const
bool empty() const
Tests for an attribute that either was never set or was set to "".
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:161
bool has_attribute(config_key_type key) const
Definition: config.cpp:159
bool empty() const
Definition: config.cpp:856
void clear()
Definition: config.cpp:835
config & add_child(config_key_type key)
Definition: config.cpp:445
The game event manager loads the scenario configuration object, and ensures that events are handled a...
Definition: manager.hpp:47
void execute_on_events(const std::string &event_id, event_func_t func)
Definition: manager.cpp:207
game_events::wml_event_pump & pump()
Definition: manager.cpp:259
void remove_event_handler(const std::string &id)
Removes an event handler.
Definition: manager.cpp:114
void add_event_handler_from_wml(const config &handler, game_lua_kernel &lk, bool is_menu_item=false)
Create an event handler from an [event] tag.
Definition: manager.cpp:68
pump_result_t fire(const std::string &event, const entity_location &loc1=entity_location::null_entity, const entity_location &loc2=entity_location::null_entity, const config &data=config())
Function to fire an event.
Definition: pump.cpp:402
const std::string event_name_
The name of this item's event(s); based on the item's id.
Definition: menu_item.hpp:159
bool needs_select_
Whether or not this event says it makes use of the last selected unit.
Definition: menu_item.hpp:174
config command_
Actions to take when this item is chosen.
Definition: menu_item.hpp:193
void fire_event(const map_location &event_hex, const game_data &data) const
Causes the event associated with this item to fire.
Definition: menu_item.cpp:167
void update(const vconfig &vcfg)
Updates *this based on vcfg.
Definition: menu_item.cpp:268
const std::string hotkey_id_
The id for this item's hotkey; based on the item's id.
Definition: menu_item.hpp:162
bool use_hotkey_
If true, allow using a hotkey to trigger this item.
Definition: menu_item.hpp:199
wml_menu_item(const std::string &id, const config &cfg)
Constructor for reading from a saved config.
Definition: menu_item.cpp:69
const std::string item_id_
The id of this menu item.
Definition: menu_item.hpp:156
std::string image_
The image to display in the menu next to this item's description.
Definition: menu_item.hpp:168
void update_command(const config &new_command)
Updates our command to new_command.
Definition: menu_item.cpp:339
vconfig show_if_
A condition that must hold in order for this menu item to be visible.
Definition: menu_item.hpp:182
bool can_show(const map_location &hex, const game_data &data, filter_context &context) const
Returns whether or not *this is applicable given the context.
Definition: menu_item.cpp:146
bool use_wml_menu_
If true, allow using the menu to trigger this item.
Definition: menu_item.hpp:202
const std::string & image() const
The image associated with this menu item.
Definition: menu_item.cpp:139
bool persistent_
If true, keep this menu item in later scenarios.
Definition: menu_item.hpp:211
void init_handler(game_lua_kernel &lk)
Initializes the implicit event handler for an inlined [command].
Definition: menu_item.cpp:208
config default_hotkey_
Config object containing the default hotkey.
Definition: menu_item.hpp:196
vconfig filter_location_
A location filter to be applied to the hex where the menu is invoked.
Definition: menu_item.hpp:190
void finish_handler()
Removes the implicit event handler for an inlined [command].
Definition: menu_item.cpp:195
bool is_synced_
If true, this item will be sent to the clients.
Definition: menu_item.hpp:208
std::optional< hotkey::wml_hotkey_record > hotkey_record_
Controls the lifetime of the associate hotkey's hotkey_command.
Definition: menu_item.hpp:165
void to_config(config &cfg) const
Writes *this to the provided config.
Definition: menu_item.cpp:222
t_string description_
The text to display in the menu for this item.
Definition: menu_item.hpp:171
A RAII object to temporary leave the synced context like in wesnoth.synchronize_choice.
static const std::string wml_menu_hotkey_prefix
static config get_event(const std::string &name, const map_location &loc, const map_location *last_select_loc)
An object to leave the synced context during draw or unsynced wml items when we don’t know whether we...
static bool run_and_throw(const std::string &commandname, const config &data, bool use_undo=true, bool show=true, synced_command::error_handler_function error_handler=default_error_function)
static bool run_in_synced_context_if_not_already(const std::string &commandname, const config &data, bool use_undo=true, bool show=true, synced_command::error_handler_function error_handler=default_error_function)
Checks whether we are currently running in a synced context, and if not we enters it.
A variable-expanding proxy for the config class.
Definition: variable.hpp:45
bool has_attribute(const std::string &key) const
< Synonym for operator[]
Definition: variable.hpp:99
vconfig child(const std::string &key) const
Returns a child of *this whose key is key.
Definition: variable.cpp:288
const config & get_config() const
Definition: variable.hpp:75
const vconfig & make_safe() const
instruct the vconfig to make a private copy of its underlying data.
Definition: variable.cpp:163
bool empty() const
Definition: variable.hpp:100
Define conditionals for the game's events mechanism, a.k.a.
std::string deprecated_message(const std::string &elem_name, DEP_LEVEL level, const version_info &version, const std::string &detail)
Definition: deprecation.cpp:30
std::string id
Text to match against addon_info.tags()
Definition: manager.cpp:215
Define the handlers for the game's events mechanism.
This file implements all the hotkey handling and menu details for play controller.
Standard logging facilities (interface).
static lg::log_domain log_engine("engine")
#define ERR_NG
Definition: menu_item.cpp:37
#define LOG_NG
Definition: menu_item.cpp:38
Declarations for a class that implements WML-defined (right-click) menu items.
std::string wml_menu
Domain specific events.
bool conditional_passed(const vconfig &cond)
std::shared_ptr< event_handler > handler_ptr
Definition: fwd.hpp:25
game_events::manager * game_events
Definition: resources.cpp:25
game_lua_kernel * lua_kernel
Definition: resources.cpp:26
filter_context * filter_con
Definition: resources.cpp:24
std::string_view data
Definition: picture.cpp:199
Define the game's event mechanism.
Encapsulates the map of the game.
Definition: location.hpp:38
bool valid() const
Definition: location.hpp:89