The Battle for Wesnoth 1.21.0-dev
 
Loading...
Searching...
No Matches
mp_options_helper.cpp
Go to the documentation of this file.
1/*
2 Copyright (C) 2008 - 2025
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#define GETTEXT_DOMAIN "wesnoth-lib"
16
18
28#include "wml_exception.hpp"
29
30
31namespace gui2::dialogs
32{
33
35 : create_engine_(create_engine)
36 , options_tree_(window.find_widget<tree_view>("custom_options"))
37 , no_options_notice_(window.find_widget<styled_widget>("no_options_notice"))
38 , node_data_map_()
39 , visible_options_()
40 , options_data_()
41{
42 for(const auto [_, cfg] : prefs::get().options().all_children_view()) {
43 for(const auto& saved_option : cfg.child_range("option")) {
44 options_data_[cfg["id"]][saved_option["id"].str()] = saved_option["value"];
45 }
46 }
47
49}
50
51void mp_options_helper::set_options(const config& new_options)
52{
53 visible_options_.clear();
54 node_data_map_.clear();
56
57 for(const auto [_, cfg] : new_options.all_children_view()) {
58 for(const auto& saved_option : cfg.child_range("option")) {
59 options_data_[cfg["id"]][saved_option["id"].str()] = saved_option["value"];
60 }
61 }
62
64}
65
76
78{
79 std::string type;
80
82 type = "campaign";
83 } else {
84 type = "multiplayer";
85 }
86
87 // For game options, we check for both types and remove them. This is to prevent options from a game
88 // of one type remaining visible when selecting a game of another type.
89 remove_nodes_for_type("campaign");
90 int pos = remove_nodes_for_type("multiplayer");
91
93
95}
96
98{
99 static const std::string type = "era";
100
101 int pos = remove_nodes_for_type(type);
102
104
106}
107
109{
110 static const std::string type = "modification";
111
112 int pos = remove_nodes_for_type(type);
113
114 for(const auto& mod : create_engine_.active_mods_data()) {
115 display_custom_options(type, pos, *mod->cfg);
116 }
117
119}
120
122{
123 // Remove all visible options of the specified source type
124 utils::erase_if(visible_options_, [&type](const option_source& source) {
125 return source.level_type == type;
126 });
127
128 // Get the node data for this specific source type
130
131 auto node_data_map_iter = node_data_map_.end();
132 std::tie(node_data_map_iter, std::ignore) = node_data_map_.emplace(type, type_node_data());
133
134 data = &node_data_map_iter->second;
135
136 node_vector& type_node_vector = data->nodes;
137
138 // The position to insert a new node of this type. If no nodes exist yet, the default value (-1) is
139 // accepted by tree_view_node as meaning at-end.
140 int& position = data->position;
141
142 // Remove each node in reverse, so that in the end we have the position of the first node removed
143 for(auto i = type_node_vector.rbegin(); i != type_node_vector.rend(); i++) {
144 position = options_tree_.remove_node(*i).second;
145 }
146
147 type_node_vector.clear();
148
149 return position;
150}
151
153{
154 // No custom options, display a message
156}
157
158template<typename T>
160{
161 options_data_[source.id][widget->id()] = widget->get_value();
162}
163
164template<>
166{
167 options_data_[source.id][widget->id()] = widget->get_value_bool();
168}
169
171{
172 options_data_[source.id][widget->id()] = cfg.child_range("item")[widget->get_value()]["value"].str();
173}
174
175void mp_options_helper::reset_options_data(const option_source& source, bool& handled, bool& halt)
176{
177 options_data_[source.id].clear();
178
179 if(source.level_type == "campaign" || source.level_type == "multiplayer") {
181 } else if(source.level_type == "era") {
183 } else if(source.level_type == "modification") {
185 }
186
187 handled = true;
188 halt = true;
189}
190
191template<typename T>
192std::pair<T*, config::attribute_value> mp_options_helper::add_node_and_get_widget(
193 tree_view_node& option_node, const std::string& id, data_map& data, const config& cfg)
194{
195 tree_view_node& node = option_node.add_child(id + "_node", data);
196
197 T* widget = dynamic_cast<T*>(node.find(id, true));
199
200 const std::string widget_id = cfg["id"];
201
202 auto& option_config = options_data_[visible_options_.back().id];
203 if(!option_config.has_attribute(widget_id) || option_config[widget_id].empty()) {
204 option_config[widget_id] = cfg["default"];
205 }
206
207 widget->set_id(widget_id);
208 widget->set_tooltip(cfg["description"].t_str());
209
210 return {widget, option_config[widget_id]};
211}
212
213void mp_options_helper::display_custom_options(const std::string& type, int node_position, const config& cfg)
214{
215 // Needed since some compilers don't like passing just {}
216 static const widget_data empty_map;
217
218 // This ensures that any game, era, or mod with no options doesn't get an entry in the visible_options_
219 // vector and prevents invalid options from different games, era, or mods being created when the options
220 // config is created.
221 if(!cfg.has_child("options")) {
222 return;
223 }
224
225 visible_options_.push_back({type, cfg["id"]});
226
227 // Get the node vector for this specific source type
228 node_vector& type_node_vector = node_data_map_[type].nodes;
229
230 for(const auto& options : cfg.child_range("options")) {
232 widget_item item;
233
234 item["label"] = cfg["name"].t_str();
235 data.emplace("tree_view_node_label", item);
236
237 tree_view_node& option_node = options_tree_.add_node("option_node", data, node_position);
238 type_node_vector.push_back(&option_node);
239
240 for(const auto [option_key, option_cfg] : options.all_children_view()) {
241 data.clear();
242 item.clear();
243
245
246 if(option_key == "checkbox") {
247 item["label"] = option_cfg["name"].t_str();
248 data.emplace("option_checkbox", item);
249
250 toggle_button* checkbox;
251 std::tie(checkbox, val) = add_node_and_get_widget<toggle_button>(option_node, "option_checkbox", data, option_cfg);
252
253 checkbox->set_value(val.to_bool());
254
255 connect_signal_notify_modified(*checkbox,
256 std::bind(&mp_options_helper::update_options_data_map<toggle_button>, this, checkbox, visible_options_.back()));
257
258 } else if(option_key == "spacer") {
259 option_node.add_child("options_spacer_node", empty_map);
260
261 } else if(option_key == "choice") {
262 if(!option_cfg.has_child("item")) {
263 continue;
264 }
265
266 item["label"] = option_cfg["name"].t_str();
267 data.emplace("menu_button_label", item);
268
269 std::vector<config> combo_items;
270 std::vector<std::string> combo_values;
271
272 for(auto i : option_cfg.child_range("item")) {
273 // Comboboxes expect this key to be 'label' not 'name'
274 i["label"] = i["name"];
275
276 combo_items.push_back(i);
277 combo_values.push_back(i["value"]);
278 }
279
280 menu_button* menu;
281 std::tie(menu, val) = add_node_and_get_widget<menu_button>(option_node, "option_menu_button", data, option_cfg);
282
283 // Needs to be called before set_selected
284 menu->set_values(combo_items);
285
286 auto iter = std::find(combo_values.begin(), combo_values.end(), val.str());
287
288 if(iter != combo_values.end()) {
289 menu->set_selected(std::distance(combo_values.begin(), iter));
290 }
291
292 connect_signal_notify_modified(*menu,
293 std::bind(&mp_options_helper::update_options_data_map_menu_button, this, menu, visible_options_.back(), option_cfg));
294
295 } else if(option_key == "slider") {
296 item["label"] = option_cfg["name"].t_str();
297 data.emplace("slider_label", item);
298
299 slider* slide;
300 std::tie(slide, val) = add_node_and_get_widget<slider>(option_node, "option_slider", data, option_cfg);
301
302 slide->set_value_range(option_cfg["min"].to_int(), option_cfg["max"].to_int());
303 slide->set_step_size(option_cfg["step"].to_int(1));
304 slide->set_value(val.to_int());
305
306 connect_signal_notify_modified(*slide,
307 std::bind(&mp_options_helper::update_options_data_map<slider>, this, slide, visible_options_.back()));
308
309 } else if(option_key == "entry") {
310 item["label"] = option_cfg["name"].t_str();
311 data.emplace("text_entry_label", item);
312
313 text_box* textbox;
314 std::tie(textbox, val) = add_node_and_get_widget<text_box>(option_node, "option_text_entry", data, option_cfg);
315
316 textbox->set_value(val.str());
317 textbox->on_modified([this](const auto& box) { update_options_data_map(&box, visible_options_.back()); });
318 }
319 }
320
321 // Add the Defaults button at the end
322 tree_view_node& node = option_node.add_child("options_default_button", empty_map);
323
324 connect_signal_mouse_left_click(node.find_widget<button>("reset_option_values"),
326 std::placeholders::_3, std::placeholders::_4));
327 }
328}
329
331{
332 config options;
333 for(const auto& source : visible_options_) {
334 config& mod = options.add_child(source.level_type);
335 mod["id"] = source.id;
336#if 0
337 // TODO: enable this as soon as we drop the old mp configure screen.
338 mod.add_child("options", options_data_[source.id]);
339#else
340 for(const auto& [key, value] : options_data_[source.id].attribute_range()) {
341 mod.add_child("option", config {"id", key, "value", value});
342 }
343#endif
344 }
345
346 return options;
347}
348
349} // namespace dialogs
Variant for storing WML attributes.
std::string str(const std::string &fallback="") const
bool to_bool(bool def=false) const
A config object defines a single node in a WML file, with access to child nodes.
Definition config.hpp:157
config & add_child(std::string_view key)
Definition config.cpp:436
auto all_children_view() const
In-order iteration over all children.
Definition config.hpp:795
child_itors child_range(std::string_view key)
Definition config.cpp:268
bool has_child(std::string_view key) const
Determine whether a config has a child or not.
Definition config.cpp:312
Simple push button.
Definition button.hpp:36
std::map< std::string, config > options_data_
void reset_options_data(const option_source &source, bool &handled, bool &halt)
void set_options(const config &new_options)
mp_options_helper(window &window, ng::create_engine &create_engine)
void update_options_data_map_menu_button(menu_button *widget, const option_source &source, const config &cfg)
void display_custom_options(const std::string &type, int node_position, const config &data)
std::map< std::string, type_node_data > node_data_map_
std::vector< tree_view_node * > node_vector
std::pair< T *, config::attribute_value > add_node_and_get_widget(tree_view_node &option_node, const std::string &id, data_map &data, const config &cfg)
std::vector< option_source > visible_options_
int remove_nodes_for_type(const std::string &type)
void update_options_data_map(T *widget, const option_source &source)
void set_selected(unsigned selected, bool fire_event=true)
void set_values(const std::vector<::config > &values, unsigned selected=0)
void set_step_size(int step_size)
Definition slider.cpp:277
virtual void set_value(int value) override
Inherited from integer_selector.
Definition slider.cpp:81
void set_value_range(int min_value, int max_value)
Definition slider.cpp:249
void on_modified(const Func &f)
Registers a NOTIFY_MODIFIED handler.
virtual void set_value(const std::string &text)
The set_value is virtual for the password_box class.
A widget that allows the user to input text in single line.
Definition text_box.hpp:125
virtual void set_value(unsigned selected, bool fire_event=false) override
Inherited from selectable_item.
void clear()
Removes all child items from the widget.
widget * find(const std::string_view id, const bool must_be_active) override
See widget::find.
tree_view_node & add_child(const std::string &id, const widget_data &data, const int index=-1)
Constructs a new child node.
bool empty() const
Definition tree_view.cpp:99
tree_view_node & add_node(const std::string &id, const widget_data &data, const int index=-1)
Definition tree_view.cpp:57
std::pair< std::shared_ptr< tree_view_node >, int > remove_node(tree_view_node *node)
Removes the given node as a child of its parent node.
Definition tree_view.cpp:63
Base class for all widgets.
Definition widget.hpp:55
T * find_widget(const std::string_view id, const bool must_be_active, const bool must_exist)
Gets a widget with the wanted id.
Definition widget.hpp:747
void set_visible(const visibility visible)
Definition widget.cpp:479
void set_id(const std::string &id)
Definition widget.cpp:98
const std::string & id() const
Definition widget.cpp:110
base class of top level items, the only item which needs to store the final canvases to draw on.
Definition window.hpp:59
const config & curent_era_cfg() const
std::vector< extras_metadata_ptr > active_mods_data()
bool is_campaign() const
Wrapper to simplify the is-type-campaign-or-sp-campaign check.
level & current_level() const
const config & data() const
const config & options()
static prefs & get()
const config * cfg
static std::string _(const char *str)
Definition gettext.hpp:100
This file contains the window object, this object is a top level container which has the event manage...
t_string missing_widget(const std::string &id)
Returns a default error message if a mandatory widget is omitted.
Definition helper.cpp:145
std::map< std::string, widget_item > widget_data
Definition widget.hpp:36
std::map< std::string, t_string > widget_item
Definition widget.hpp:33
void erase_if(Container &container, const Predicate &predicate)
Convenience wrapper for using std::remove_if on a container.
Definition general.hpp:108
std::string_view data
Definition picture.cpp:188
std::unique_ptr< MIX_Audio, decltype(&MIX_DestroyAudio)> value
Definition sound.cpp:139
Add a special kind of assert to validate whether the input from WML doesn't contain any problems that...
#define VALIDATE(cond, message)
The macro to use for the validation of WML.
#define i