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 container for wml_menu_item.
18 */
19
20 #include "game_events/wmi_manager.hpp"
21 #include "game_events/menu_item.hpp"
22 #include "play_controller.hpp"
23 #include "resources.hpp"
24
25 #include "config.hpp"
26 #include "game_data.hpp"
27 #include "log.hpp"
28 #include "map/location.hpp"
29
30 static lg::log_domain log_engine("engine");
31 #define WRN_NG LOG_STREAM(warn, log_engine)
32 #define LOG_NG LOG_STREAM(info, log_engine)
33
34 // This file is in the game_events namespace.
35 namespace game_events
36 {
wmi_manager()37 wmi_manager::wmi_manager()
38 : wml_menu_items_()
39 {
40 }
41
42 /**
43 * Destructor.
44 * Default implementation, but defined here because this function needs to be
45 * able to see wml_menu_item's destructor.
46 */
~wmi_manager()47 wmi_manager::~wmi_manager()
48 {
49 }
50
51 /** Erases the item with id @a key. */
erase(const std::string & id)52 bool wmi_manager::erase(const std::string& id)
53 {
54 // Locate the item to erase.
55 const auto iter = wml_menu_items_.find(id);
56
57 if(iter == wml_menu_items_.end()) {
58 WRN_NG << "Trying to remove non-existent menu item '" << id << "'; ignoring." << std::endl;
59 // No such item.
60 return false;
61 }
62
63 // Clean up our bookkeeping.
64 iter->second->finish_handler();
65
66 // Remove the item from the map.
67 wml_menu_items_.erase(iter);
68
69 return true; // Erased one item.
70 }
71
72 /**
73 * Fires the menu item with the given @a id.
74 * @returns true if a matching item was found (even if it could not be fired).
75 * NOTE: The return value could be altered if it is decided that
76 * play_controller::execute_command() needs something different.
77 */
fire_item(const std::string & id,const map_location & hex,game_data & gamedata,filter_context & fc,unit_map & units,bool is_key_hold_repeat) const78 bool wmi_manager::fire_item(
79 const std::string& id, const map_location& hex, game_data& gamedata, filter_context& fc, unit_map& units, bool is_key_hold_repeat) const
80 {
81 // Does this item exist?
82 item_ptr wmi = get_item(id);
83 if(!wmi) {
84 return false;
85 } else if(is_key_hold_repeat && !wmi->hotkey_repeat()) {
86 return false;
87 }
88
89 // Prepare for can show().
90 config::attribute_value x1 = gamedata.get_variable("x1");
91 config::attribute_value y1 = gamedata.get_variable("y1");
92 gamedata.get_variable("x1") = hex.wml_x();
93 gamedata.get_variable("y1") = hex.wml_y();
94 scoped_xy_unit highlighted_unit("unit", hex, units);
95
96 // Can this item be shown?
97 if(wmi->can_show(hex, gamedata, fc)) {
98 wmi->fire_event(hex, gamedata);
99 }
100 gamedata.get_variable("x1") = x1;
101 gamedata.get_variable("y1") = y1;
102 return true;
103 }
104
105 /**
106 * Returns the menu items that can be shown for the given location.
107 *
108 * @param[out] items Pointers to applicable menu items will be pushed onto @a items.
109 * @param[out] descriptions Menu item text will be pushed onto @a descriptions (in the same order as @a items).
110 */
get_items(const map_location & hex,std::vector<std::shared_ptr<const wml_menu_item>> & items,std::vector<config> & descriptions,filter_context & fc,game_data & gamedata,unit_map & units) const111 void wmi_manager::get_items(const map_location& hex,
112 std::vector<std::shared_ptr<const wml_menu_item>>& items,
113 std::vector<config>& descriptions,
114 filter_context& fc,
115 game_data& gamedata,
116 unit_map& units) const
117 {
118 if(empty()) {
119 // Nothing to do (skip setting game variables).
120 return;
121 }
122
123 // Prepare for can show().
124
125
126 config::attribute_value x1 = gamedata.get_variable("x1");
127 config::attribute_value y1 = gamedata.get_variable("y1");
128 gamedata.get_variable("x1") = hex.wml_x();
129 gamedata.get_variable("y1") = hex.wml_y();
130 scoped_xy_unit highlighted_unit("unit", hex, units);
131
132 // Check each menu item.
133 for(const auto& item_pair : wml_menu_items_) {
134 item_ptr item = item_pair.second;
135
136 // Can this item be shown?
137 if(item->use_wml_menu() && (!item->is_synced() || resources::controller->can_use_synced_wml_menu())
138 && item->can_show(hex, gamedata, fc)) {
139 // Include this item.
140 items.push_back(item);
141 descriptions.emplace_back("id", item->menu_text());
142 }
143 }
144 gamedata.get_variable("x1") = x1;
145 gamedata.get_variable("y1") = y1;
146 }
147
get_item(const std::string & id) const148 wmi_manager::item_ptr wmi_manager::get_item(const std::string& id) const
149 {
150 auto iter = wml_menu_items_.find(id);
151 if(iter != wml_menu_items_.end()) {
152 return iter->second;
153 }
154
155 return nullptr;
156 }
157
158 /**
159 * Initializes the implicit event handlers for inlined [command]s.
160 */
init_handlers() const161 void wmi_manager::init_handlers() const
162 {
163 // Applying default hotkeys here currently does not work because
164 // the hotkeys are reset by play_controler::init_managers() ->
165 // display_manager::display_manager, which is called after this.
166 // The result is that default wml hotkeys will be ignored if wml
167 // hotkeys are set to default in the preferences menu. (They are
168 // still reapplied if set_menu_item is called again, for example
169 // by starting a new campaign.) Since it isn't that important
170 // I'll just leave it for now.
171
172 unsigned wmi_count = 0;
173
174 // Loop through each menu item.
175 for(const auto& item : wml_menu_items_) {
176 // If this menu item has a [command], add a handler for it.
177 item.second->init_handler();
178
179 // Count the menu items (for the diagnostic message).
180 ++wmi_count;
181 }
182
183 // Diagnostic:
184 if(wmi_count > 0) {
185 LOG_NG << wmi_count << " WML menu items found, loaded." << std::endl;
186 }
187 }
188
to_config(config & cfg) const189 void wmi_manager::to_config(config& cfg) const
190 {
191 // Loop through our items.
192 for(const auto& item : wml_menu_items_) {
193 // Add this item as a child of cfg.
194 item.second->to_config(cfg.add_child("menu_item"));
195 }
196 }
197
198 /**
199 * Updates or creates (as appropriate) the menu item with the given @a id.
200 */
set_item(const std::string & id,const vconfig & menu_item)201 void wmi_manager::set_item(const std::string& id, const vconfig& menu_item)
202 {
203 auto iter = wml_menu_items_.begin();
204 bool success;
205
206 // First, try to insert a brand new menu item.
207 std::tie(iter, success) = wml_menu_items_.emplace(id, std::make_shared<wml_menu_item>(id, menu_item));
208
209 // If an entry already exists, reset it.
210 if(!success) {
211 // Create a new menu item based on the old. This leaves the old item
212 // alone in case someone else is holding on to (and processing) it.
213 iter->second.reset(new wml_menu_item(id, menu_item, *iter->second));
214 }
215 }
216
217 /**
218 * Sets the current menu items to the "menu_item" children of @a cfg.
219 */
set_menu_items(const config & cfg)220 void wmi_manager::set_menu_items(const config& cfg)
221 {
222 wml_menu_items_.clear();
223 for(const config& item : cfg.child_range("menu_item")) {
224 if(!item.has_attribute("id")) {
225 continue;
226 }
227
228 const std::string& id = item["id"];
229 bool success;
230
231 std::tie(std::ignore, success) = wml_menu_items_.emplace(id, std::make_shared<wml_menu_item>(id, item));
232
233 if(!success) {
234 WRN_NG << "duplicate menu item (" << id << ") while loading from config" << std::endl;
235 }
236 }
237 }
238
239 } // end namespace game_events
240