The Battle for Wesnoth  1.15.0-dev
menu_item.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2003 - 2018 by David White <dave@whitevine.net>
3  Part of the Battle for Wesnoth Project https://www.wesnoth.org/
4 
5  This program is free software; you can redistribute it and/or modify
6  it under the terms of the GNU General Public License as published by
7  the Free Software Foundation; either version 2 of the License, or
8  (at your option) any later version.
9  This program is distributed in the hope that it will be useful,
10  but WITHOUT ANY WARRANTY.
11 
12  See the COPYING file for more details.
13 */
14 
15 /**
16  * @file
17  * Definitions for a class that implements WML-defined (right-click) menu items.
18  */
19 
21 
23 #include "game_events/handlers.hpp"
24 #include "game_events/pump.hpp"
25 
26 #include "game_config.hpp"
28 #include "log.hpp"
29 #include "replay_helper.hpp"
30 #include "resources.hpp"
31 #include "synced_context.hpp"
32 #include "terrain/filter.hpp"
33 #include "deprecation.hpp"
34 
35 static lg::log_domain log_engine("engine");
36 #define ERR_NG LOG_STREAM(err, log_engine)
37 #define LOG_NG LOG_STREAM(info, log_engine)
38 
39 // This file is in the game_events namespace.
40 namespace game_events
41 {
42 namespace
43 { // Some helpers for construction.
44 
45 /**
46  * Build the event name associated with the given menu item id.
47  * This is a separate function so it can be easily shared by multiple
48  * constructors.
49  */
50 inline std::string make_item_name(const std::string& id)
51 {
52  return std::string("menu item") + (id.empty() ? "" : ' ' + id);
53 }
54 
55 /**
56  * Build the hotkey id associated with the given menu item id.
57  * This is a separate function so it can be easily shared by multiple
58  * constructors.
59  */
60 inline std::string make_item_hotkey(const std::string& id)
61 {
63 }
64 
65 } // anonymous namespace
66 
67 // Constructor for reading from a saved config.
68 wml_menu_item::wml_menu_item(const std::string& id, const config& cfg)
69  : item_id_(id)
70  , event_name_(make_item_name(id))
71  , hotkey_id_(make_item_hotkey(id))
72  , image_(cfg["image"].str())
73  , description_(cfg["description"].t_str())
74  , needs_select_(cfg["needs_select"].to_bool(false))
75  , show_if_(cfg.child_or_empty("show_if"), true)
76  , filter_location_(cfg.child_or_empty("filter_location"), true)
77  , command_(cfg.child_or_empty("command"))
78  , default_hotkey_(cfg.child_or_empty("default_hotkey"))
79  , use_hotkey_(cfg["use_hotkey"].to_bool(true))
80  , use_wml_menu_(cfg["use_hotkey"].str() != "only")
81  , is_synced_(cfg["synced"].to_bool(true))
82 {
83  if(cfg.has_attribute("needs_select")) {
84  deprecated_message("needs_select", DEP_LEVEL::INDEFINITE, {1, 15, 0});
85  }
86 }
87 
88 // Constructor for items defined in an event.
89 wml_menu_item::wml_menu_item(const std::string& id, const vconfig& definition)
90  : item_id_(id)
91  , event_name_(make_item_name(id))
92  , hotkey_id_(make_item_hotkey(id))
93  , image_()
94  , description_()
95  , needs_select_(false)
96  , show_if_(vconfig::empty_vconfig())
97  , filter_location_(vconfig::empty_vconfig())
98  , command_()
99  , default_hotkey_()
100  , use_hotkey_(true)
101  , use_wml_menu_(true)
102  , is_synced_(true)
103 {
104  // On the off-chance that update() doesn't do it, add the hotkey here.
105  // (Update can always modify it.)
107 
108  // Apply WML.
109  update(definition);
110 }
111 
112 // Constructor for items modified by an event.
113 wml_menu_item::wml_menu_item(const std::string& id, const vconfig& definition, const wml_menu_item& original)
114  : item_id_(id)
115  , event_name_(make_item_name(id))
116  , hotkey_id_(make_item_hotkey(id))
117  , image_(original.image_)
118  , description_(original.description_)
119  , needs_select_(original.needs_select_)
120  , show_if_(original.show_if_)
122  , command_(original.command_)
123  , default_hotkey_(original.default_hotkey_)
124  , use_hotkey_(original.use_hotkey_)
125  , use_wml_menu_(original.use_wml_menu_)
126  , is_synced_(original.is_synced_)
127 {
128  // Apply WML.
129  update(definition);
130 }
131 
132 const std::string& wml_menu_item::image() const
133 {
134  // Default the image?
135  return image_.empty() ? game_config::images::wml_menu : image_;
136 }
137 
138 
140 {
141  // Failing the [show_if] tag means no show.
143  return false;
144  }
145 
146  // Failing the [fiter_location] tag means no show.
147  if(!filter_location_.empty() && !terrain_filter(filter_location_, &filter_con)(hex)) {
148  return false;
149  }
150 
151  // Failing to have a required selection means no show.
152  if(needs_select_ && !data.last_selected.valid()) {
153  return false;
154  }
155 
156  // Passed all tests.
157  return true;
158 }
159 
160 void wml_menu_item::fire_event(const map_location& event_hex, const game_data& data) const
161 {
162  if(!this->is_synced()) {
163  // It is possible to for example show a help menu during a [delay] of a synced event.
165  assert(resources::game_events != nullptr);
167  return;
168  }
169 
170  const map_location& last_select = data.last_selected;
171 
172  // No new player-issued commands allowed while this is firing.
173  const events::command_disabler disable_commands;
174 
175  // instead of adding a second "select" event like it was done before, we just fire the select event again, and this
176  // time in a synced context.
177  // note that there couldn't be a user choice during the last "select" event because it didn't run in a synced
178  // context.
179  if(needs_select_ && last_select.valid()) {
181  "fire_event", replay_helper::get_event(event_name_, event_hex, &last_select));
182  } else {
184  "fire_event", replay_helper::get_event(event_name_, event_hex, nullptr));
185  }
186 }
187 
189 {
190  if(!command_.empty()) {
191  assert(resources::game_events);
193  }
194 
195  // Hotkey support
196  if(use_hotkey_) {
198  }
199 }
200 
202 {
203  // If this menu item has a [command], add a handler for it.
204  if(!command_.empty()) {
205  assert(resources::game_events);
207  }
208 
209  // Hotkey support
210  if(use_hotkey_) {
212  }
213 }
214 
216 {
217  cfg["id"] = item_id_;
218  cfg["image"] = image_;
219  cfg["description"] = description_;
220  cfg["needs_select"] = needs_select_;
221  cfg["synced"] = is_synced_;
222 
223  if(use_hotkey_ && use_wml_menu_) {
224  cfg["use_hotkey"] = true;
225  }
226 
227  if(use_hotkey_ && !use_wml_menu_) {
228  cfg["use_hotkey"] = "only";
229  }
230 
231  if(!use_hotkey_ && use_wml_menu_) {
232  cfg["use_hotkey"] = false;
233  }
234 
235  if(!use_hotkey_ && !use_wml_menu_) {
236  ERR_NG << "Bad data: wml_menu_item with both use_wml_menu and use_hotkey set to false is not supposed to be "
237  "possible.";
238  cfg["use_hotkey"] = false;
239  }
240 
241  if(!show_if_.empty()) {
242  cfg.add_child("show_if", show_if_.get_config());
243  }
244 
245  if(!filter_location_.empty()) {
246  cfg.add_child("filter_location", filter_location_.get_config());
247  }
248 
249  if(!command_.empty()) {
250  cfg.add_child("command", command_);
251  }
252 
253  if(!default_hotkey_.empty()) {
254  cfg.add_child("default_hotkey", default_hotkey_);
255  }
256 }
257 
259 {
260  const bool old_use_hotkey = use_hotkey_;
261  // Tracks whether or not the hotkey has been updated.
262  bool hotkey_updated = false;
263 
264  if(vcfg.has_attribute("image")) {
265  image_ = vcfg["image"].str();
266  }
267 
268  if(vcfg.has_attribute("description")) {
269  description_ = vcfg["description"].t_str();
270  hotkey_updated = true;
271  }
272 
273  if(vcfg.has_attribute("needs_select")) {
274  deprecated_message("needs_select", DEP_LEVEL::INDEFINITE, {1, 15, 0});
275  needs_select_ = vcfg["needs_select"].to_bool();
276  }
277 
278  if(vcfg.has_attribute("synced")) {
279  is_synced_ = vcfg["synced"].to_bool(true);
280  }
281 
282  if(const vconfig& child = vcfg.child("show_if")) {
283  show_if_ = child;
285  }
286 
287  if(const vconfig& child = vcfg.child("filter_location")) {
288  filter_location_ = child;
290  }
291 
292  if(const vconfig& child = vcfg.child("default_hotkey")) {
293  default_hotkey_ = child.get_parsed_config();
294  hotkey_updated = true;
295  }
296 
297  if(vcfg.has_attribute("use_hotkey")) {
298  const config::attribute_value& use_hotkey_av = vcfg["use_hotkey"];
299 
300  use_hotkey_ = use_hotkey_av.to_bool(true);
301  use_wml_menu_ = use_hotkey_av.str() != "only";
302  }
303 
304  if(const vconfig& cmd = vcfg.child("command")) {
305  const bool delayed = cmd["delayed_variable_substitution"].to_bool(true);
306  update_command(delayed ? cmd.get_config() : cmd.get_parsed_config());
307  }
308 
309  // Update the registered hotkey?
310 
311  if(use_hotkey_ && !old_use_hotkey) {
312  // The hotkey needs to be enabled.
314 
315  } else if(use_hotkey_ && hotkey_updated) {
316  // The hotkey needs to be updated.
318 
319  } else if(!use_hotkey_ && old_use_hotkey) {
320  // The hotkey needs to be disabled.
322  }
323 }
324 
325 void wml_menu_item::update_command(const config& new_command)
326 {
327  // If there is an old command, remove it from the event handlers.
328  if(!command_.empty()) {
329  assert(resources::game_events);
330 
332  if(ptr->is_menu_item()) {
333  LOG_NG << "Removing command for " << event_name_ << ".\n";
334  man.remove_event_handler(command_["id"].str());
335  }
336  });
337  }
338 
339  // Update our stored command.
340  if(new_command.empty()) {
341  command_.clear();
342  } else {
343  command_ = new_command;
344 
345  // Set some fields required by event processing.
346  config::attribute_value& event_id = command_["id"];
347  if(event_id.empty() && !item_id_.empty()) {
348  event_id = item_id_;
349  }
350 
351  command_["name"] = event_name_;
352  command_["first_time_only"] = false;
353 
354  // Register the event.
355  LOG_NG << "Setting command for " << event_name_ << " to:\n" << command_;
356  assert(resources::game_events);
357  resources::game_events->add_event_handler(command_, true);
358  }
359 }
360 
361 } // end namespace game_events
void remove_event_handler(const std::string &id)
Removes an event handler.
Definition: manager.cpp:74
void to_config(config &cfg) const
Writes *this to the provided config.
Definition: menu_item.cpp:215
bool needs_select_
Whether or not this event says it makes use of the last selected unit.
Definition: menu_item.hpp:157
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:83
vconfig child(const std::string &key) const
Returns a child of *this whose key is key.
Definition: variable.cpp:252
vconfig filter_location_
A location filter to be applied to the hex where the menu is invoked.
Definition: menu_item.hpp:173
Variant for storing WML attributes.
config command_
Actions to take when this item is chosen.
Definition: menu_item.hpp:176
bool has_attribute(config_key_type key) const
Definition: config.cpp:217
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:160
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:154
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:132
std::string image_
The image to display in the menu next to this item&#39;s description.
Definition: menu_item.hpp:151
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:139
void clear()
Definition: config.cpp:852
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:188
const vconfig & make_safe() const
instruct the vconfig to make a private copy of its underlying data.
Definition: variable.cpp:127
vconfig show_if_
A condition that must hold in order for this menu item to be visible.
Definition: menu_item.hpp:165
std::string deprecated_message(const std::string &elem_name, DEP_LEVEL level, const version_info &version, const std::string &detail)
Definition: deprecation.cpp:29
const t_string id
filter_context * filter_con
Definition: resources.cpp:23
bool valid() const
Definition: location.hpp:93
const std::string item_id_
The id of this menu item.
Definition: menu_item.hpp:142
game_events::manager * game_events
Definition: resources.cpp:24
static const std::string wml_menu_hotkey_prefix
Encapsulates the map of the game.
Definition: location.hpp:42
void init_handler() const
Initializes the implicit event handler for an inlined [command].
Definition: menu_item.cpp:201
Domain specific events.
Definition: action_wml.cpp:88
Define conditionals for the game&#39;s events mechanism, a.k.a.
void add_event_handler(const config &handler, bool is_menu_item=false)
Create an event handler.
Definition: manager.cpp:68
bool is_synced_
If true, this item will be sent to the clients.
Definition: menu_item.hpp:191
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:43
config & add_child(config_key_type key)
Definition: config.cpp:479
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:486
bool use_hotkey_
If true, allow using a hotkey to trigger this item.
Definition: menu_item.hpp:182
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:42
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:185
wml_menu_item(const std::string &id, const config &cfg)
Constructor for reading from a saved config.
Definition: menu_item.cpp:68
void update_command(const config &new_command)
Updates our command to new_command.
Definition: menu_item.cpp:325
void update(const vconfig &vcfg)
Updates *this based on vcfg.
Definition: menu_item.cpp:258
game_events::wml_event_pump & pump()
Definition: manager.cpp:226
std::shared_ptr< event_handler > handler_ptr
Definition: fwd.hpp:24
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:68
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:161
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:145
bool empty() const
Definition: config.cpp:873
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:148
config default_hotkey_
Config object containing the default hotkey.
Definition: menu_item.hpp:179