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