The Battle for Wesnoth  1.17.0-dev
menu_item.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2003 - 2021
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  , image_(cfg["image"].str())
74  , description_(cfg["description"].t_str())
75  , needs_select_(cfg["needs_select"].to_bool(false))
76  , show_if_(cfg.child_or_empty("show_if"), true)
77  , filter_location_(cfg.child_or_empty("filter_location"), true)
78  , command_(cfg.child_or_empty("command"))
79  , default_hotkey_(cfg.child_or_empty("default_hotkey"))
80  , use_hotkey_(cfg["use_hotkey"].to_bool(true))
81  , use_wml_menu_(cfg["use_hotkey"].str() != "only")
82  , is_synced_(cfg["synced"].to_bool(true))
83  , persistent_(cfg["persistent"].to_bool(true))
84 {
85  if(cfg.has_attribute("needs_select")) {
86  deprecated_message("needs_select", DEP_LEVEL::INDEFINITE, {1, 15, 0});
87  }
88 }
89 
90 // Constructor for items defined in an event.
91 wml_menu_item::wml_menu_item(const std::string& id, const vconfig& definition)
92  : item_id_(id)
93  , event_name_(make_item_name(id))
94  , hotkey_id_(make_item_hotkey(id))
95  , image_()
96  , description_()
97  , needs_select_(false)
98  , show_if_(vconfig::empty_vconfig())
99  , filter_location_(vconfig::empty_vconfig())
100  , command_()
101  , default_hotkey_()
102  , use_hotkey_(true)
103  , use_wml_menu_(true)
104  , is_synced_(true)
105  , persistent_(true)
106 {
107  // On the off-chance that update() doesn't do it, add the hotkey here.
108  // (Update can always modify it.)
110 
111  // Apply WML.
112  update(definition);
113 }
114 
115 // Constructor for items modified by an event.
116 wml_menu_item::wml_menu_item(const std::string& id, const vconfig& definition, const wml_menu_item& original)
117  : item_id_(id)
118  , event_name_(make_item_name(id))
119  , hotkey_id_(make_item_hotkey(id))
120  , image_(original.image_)
121  , description_(original.description_)
122  , needs_select_(original.needs_select_)
123  , show_if_(original.show_if_)
125  , command_(original.command_)
126  , default_hotkey_(original.default_hotkey_)
127  , use_hotkey_(original.use_hotkey_)
128  , use_wml_menu_(original.use_wml_menu_)
129  , is_synced_(original.is_synced_)
130  , persistent_(original.persistent_)
131 {
132  // Apply WML.
133  update(definition);
134 }
135 
136 const std::string& wml_menu_item::image() const
137 {
138  // Default the image?
139  return image_.empty() ? game_config::images::wml_menu : image_;
140 }
141 
142 
144 {
145  // Failing the [show_if] tag means no show.
147  return false;
148  }
149 
150  // Failing the [fiter_location] tag means no show.
151  if(!filter_location_.empty() && !terrain_filter(filter_location_, &filter_con, false)(hex)) {
152  return false;
153  }
154 
155  // Failing to have a required selection means no show.
156  if(needs_select_ && !data.last_selected.valid()) {
157  return false;
158  }
159 
160  // Passed all tests.
161  return true;
162 }
163 
164 void wml_menu_item::fire_event(const map_location& event_hex, const game_data& data) const
165 {
166  if(!this->is_synced()) {
167  // It is possible to for example show a help menu during a [delay] of a synced event.
169  assert(resources::game_events != nullptr);
171  return;
172  }
173 
174  const map_location& last_select = data.last_selected;
175 
176  // No new player-issued commands allowed while this is firing.
177  const events::command_disabler disable_commands;
178 
179  // instead of adding a second "select" event like it was done before, we just fire the select event again, and this
180  // time in a synced context.
181  // note that there couldn't be a user choice during the last "select" event because it didn't run in a synced
182  // context.
183  if(needs_select_ && last_select.valid()) {
185  "fire_event", replay_helper::get_event(event_name_, event_hex, &last_select));
186  } else {
188  "fire_event", replay_helper::get_event(event_name_, event_hex, nullptr));
189  }
190 }
191 
193 {
194  if(!command_.empty()) {
195  assert(resources::game_events);
197  }
198 
199  // Hotkey support
200  if(use_hotkey_) {
202  }
203 }
204 
206 {
207  // If this menu item has a [command], add a handler for it.
208  if(!command_.empty()) {
209  assert(resources::game_events);
211  }
212 
213  // Hotkey support
214  if(use_hotkey_) {
216  }
217 }
218 
220 {
221  cfg["id"] = item_id_;
222  cfg["image"] = image_;
223  cfg["description"] = description_;
224  cfg["synced"] = is_synced_;
225 
226  if(needs_select_) {
227  cfg["needs_select"] = true;
228  }
229 
230  if(use_hotkey_ && use_wml_menu_) {
231  cfg["use_hotkey"] = true;
232  }
233 
234  if(use_hotkey_ && !use_wml_menu_) {
235  cfg["use_hotkey"] = "only";
236  }
237 
238  if(!use_hotkey_ && use_wml_menu_) {
239  cfg["use_hotkey"] = false;
240  }
241 
242  if(!use_hotkey_ && !use_wml_menu_) {
243  ERR_NG << "Bad data: wml_menu_item with both use_wml_menu and use_hotkey set to false is not supposed to be "
244  "possible.";
245  cfg["use_hotkey"] = false;
246  }
247 
248  if(!show_if_.empty()) {
249  cfg.add_child("show_if", show_if_.get_config());
250  }
251 
252  if(!filter_location_.empty()) {
253  cfg.add_child("filter_location", filter_location_.get_config());
254  }
255 
256  if(!command_.empty()) {
257  cfg.add_child("command", command_);
258  }
259 
260  if(!default_hotkey_.empty()) {
261  cfg.add_child("default_hotkey", default_hotkey_);
262  }
263 }
264 
266 {
267  const bool old_use_hotkey = use_hotkey_;
268  // Tracks whether or not the hotkey has been updated.
269  bool hotkey_updated = false;
270 
271  if(vcfg.has_attribute("image")) {
272  image_ = vcfg["image"].str();
273  }
274 
275  if(vcfg.has_attribute("description")) {
276  description_ = vcfg["description"].t_str();
277  hotkey_updated = true;
278  }
279 
280  if(vcfg.has_attribute("needs_select")) {
281  deprecated_message("needs_select", DEP_LEVEL::INDEFINITE, {1, 15, 0});
282  needs_select_ = vcfg["needs_select"].to_bool();
283  }
284 
285  if(vcfg.has_attribute("synced")) {
286  is_synced_ = vcfg["synced"].to_bool(true);
287  }
288 
289  if(vcfg.has_attribute("persistent")) {
290  persistent_ = vcfg["persistent"].to_bool(true);
291  }
292 
293  if(const vconfig& child = vcfg.child("show_if")) {
294  show_if_ = child;
296  }
297 
298  if(const vconfig& child = vcfg.child("filter_location")) {
299  filter_location_ = child;
301  }
302 
303  if(const vconfig& child = vcfg.child("default_hotkey")) {
304  default_hotkey_ = child.get_parsed_config();
305  hotkey_updated = true;
306  }
307 
308  if(vcfg.has_attribute("use_hotkey")) {
309  const config::attribute_value& use_hotkey_av = vcfg["use_hotkey"];
310 
311  use_hotkey_ = use_hotkey_av.to_bool(true);
312  use_wml_menu_ = use_hotkey_av.str() != "only";
313  }
314 
315  if(const vconfig& cmd = vcfg.child("command")) {
316  const bool delayed = cmd["delayed_variable_substitution"].to_bool(true);
317  update_command(delayed ? cmd.get_config() : cmd.get_parsed_config());
318  }
319 
320  // Update the registered hotkey?
321 
322  if(use_hotkey_ && !old_use_hotkey) {
323  // The hotkey needs to be enabled.
325 
326  } else if(use_hotkey_ && hotkey_updated) {
327  // The hotkey needs to be updated.
329 
330  } else if(!use_hotkey_ && old_use_hotkey) {
331  // The hotkey needs to be disabled.
333  }
334 }
335 
336 void wml_menu_item::update_command(const config& new_command)
337 {
338  // If there is an old command, remove it from the event handlers.
339  if(!command_.empty()) {
340  assert(resources::game_events);
341 
343  if(ptr->is_menu_item()) {
344  LOG_NG << "Removing command for " << event_name_ << ".\n";
345  man.remove_event_handler(command_["id"].str());
346  }
347  });
348  }
349 
350  // Update our stored command.
351  if(new_command.empty()) {
352  command_.clear();
353  } else {
354  command_ = new_command;
355 
356  // Set some fields required by event processing.
357  config::attribute_value& event_id = command_["id"];
358  if(event_id.empty() && !item_id_.empty()) {
359  event_id = item_id_;
360  }
361 
362  command_["name"] = event_name_;
363  command_["first_time_only"] = false;
364 
365  // Register the event.
366  LOG_NG << "Setting command for " << event_name_ << " to:\n" << command_;
367  assert(resources::game_events);
368  resources::game_events->add_event_handler(command_, true);
369  }
370 }
371 
372 } // end namespace game_events
void remove_event_handler(const std::string &id)
Removes an event handler.
Definition: manager.cpp:75
void to_config(config &cfg) const
Writes *this to the provided config.
Definition: menu_item.cpp:219
bool needs_select_
Whether or not this event says it makes use of the last selected unit.
Definition: menu_item.hpp:160
bool empty() const
Tests for an attribute that either was never set or was set to "".
map_location last_selected
the last location where a select event fired.
Definition: game_data.hpp:92
vconfig child(const std::string &key) const
Returns a child of *this whose key is key.
Definition: variable.cpp:288
vconfig filter_location_
A location filter to be applied to the hex where the menu is invoked.
Definition: menu_item.hpp:176
Variant for storing WML attributes.
config command_
Actions to take when this item is chosen.
Definition: menu_item.hpp:179
bool has_attribute(config_key_type key) const
Definition: config.cpp:211
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:164
bool has_attribute(const std::string &key) const
< Synonym for operator[]
Definition: variable.hpp:99
t_string description_
The text to display in the menu for this item.
Definition: menu_item.hpp:157
void add_wml_hotkey(const std::string &id, const t_string &description, const config &default_hotkey)
adds a new wml hotkey to the list, but only if there is no hotkey with that id yet on the list...
const std::string & image() const
The image associated with this menu item.
Definition: menu_item.cpp:136
std::string image_
The image to display in the menu next to this item&#39;s description.
Definition: menu_item.hpp:154
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:143
void clear()
Definition: config.cpp:920
An object to leave the synced context during draw or unsynced wml items when we don’t know whether w...
This file implements all the hotkey handling and menu details for play controller.
bool empty() const
Definition: variable.hpp:100
void finish_handler() const
Removes the implicit event handler for an inlined [command].
Definition: menu_item.cpp:192
const vconfig & make_safe() const
instruct the vconfig to make a private copy of its underlying data.
Definition: variable.cpp:163
vconfig show_if_
A condition that must hold in order for this menu item to be visible.
Definition: menu_item.hpp:168
std::string deprecated_message(const std::string &elem_name, DEP_LEVEL level, const version_info &version, const std::string &detail)
Definition: deprecation.cpp:30
filter_context * filter_con
Definition: resources.cpp:24
bool valid() const
Definition: location.hpp:89
const std::string item_id_
The id of this menu item.
Definition: menu_item.hpp:145
game_events::manager * game_events
Definition: resources.cpp:25
static const std::string wml_menu_hotkey_prefix
Encapsulates the map of the game.
Definition: location.hpp:38
void init_handler() const
Initializes the implicit event handler for an inlined [command].
Definition: menu_item.cpp:205
Domain specific events.
Definition: action_wml.cpp:87
Define conditionals for the game&#39;s events mechanism, a.k.a.
std::string id
Text to match against addon_info.tags()
Definition: manager.cpp:215
void add_event_handler(const config &handler, bool is_menu_item=false)
Create an event handler.
Definition: manager.cpp:69
bool is_synced_
If true, this item will be sent to the clients.
Definition: menu_item.hpp:194
Define the game&#39;s event mechanism.
bool to_bool(bool def=false) const
The game event manager loads the scenario configuration object, and ensures that events are handled a...
Definition: manager.hpp:44
bool persistent_
If true, keep this menu item in later scenarios.
Definition: menu_item.hpp:197
config & add_child(config_key_type key)
Definition: config.cpp:514
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:481
bool use_hotkey_
If true, allow using a hotkey to trigger this item.
Definition: menu_item.hpp:185
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)
bool conditional_passed(const vconfig &cond)
Define the handlers for the game&#39;s events mechanism.
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:44
const config & get_config() const
Definition: variable.hpp:75
Standard logging facilities (interface).
bool use_wml_menu_
If true, allow using the menu to trigger this item.
Definition: menu_item.hpp:188
wml_menu_item(const std::string &id, const config &cfg)
Constructor for reading from a saved config.
Definition: menu_item.cpp:69
void update_command(const config &new_command)
Updates our command to new_command.
Definition: menu_item.cpp:336
A RAII object to temporary leave the synced context like in wesnoth.synchronize_choice.
void update(const vconfig &vcfg)
Updates *this based on vcfg.
Definition: menu_item.cpp:265
game_events::wml_event_pump & pump()
Definition: manager.cpp:230
std::shared_ptr< event_handler > handler_ptr
Definition: fwd.hpp:25
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:61
static config get_event(const std::string &name, const map_location &loc, const map_location *last_select_loc)
std::string wml_menu
void execute_on_events(const std::string &event_id, event_func_t func)
Definition: manager.cpp:162
bool remove_wml_hotkey(const std::string &id)
removes a wml hotkey with the given id, returns true if the deletion was successful ...
const std::string event_name_
The name of this item&#39;s event(s); based on the item&#39;s id.
Definition: menu_item.hpp:148
bool empty() const
Definition: config.cpp:941
std::string str(const std::string &fallback="") const
const std::string hotkey_id_
The id for this item&#39;s hotkey; based on the item&#39;s id.
Definition: menu_item.hpp:151
config default_hotkey_
Config object containing the default hotkey.
Definition: menu_item.hpp:182