The Battle for Wesnoth  1.19.5+dev
mp_options_helper.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2008 - 2024
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 
20 #include "gui/widgets/button.hpp"
22 #include "gui/widgets/slider.hpp"
24 #include "gui/widgets/text_box.hpp"
27 #include "gui/widgets/window.hpp"
28 #include "wml_exception.hpp"
29 
30 
31 namespace 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 
52 {
53  visible_options_.clear();
54  node_data_map_.clear();
56 
60 }
61 
63 {
64  std::string type;
65 
67  type = "campaign";
68  } else {
69  type = "multiplayer";
70  }
71 
72  // For game options, we check for both types and remove them. This is to prevent options from a game
73  // of one type remaining visible when selecting a game of another type.
74  remove_nodes_for_type("campaign");
75  int pos = remove_nodes_for_type("multiplayer");
76 
78 
80 }
81 
83 {
84  static const std::string type = "era";
85 
86  int pos = remove_nodes_for_type(type);
87 
89 
91 }
92 
94 {
95  static const std::string type = "modification";
96 
97  int pos = remove_nodes_for_type(type);
98 
99  for(const auto& mod : create_engine_.active_mods_data()) {
100  display_custom_options(type, pos, *mod->cfg);
101  }
102 
104 }
105 
107 {
108  // Remove all visible options of the specified source type
110  return source.level_type == type;
111  });
112 
113  // Get the node data for this specific source type
115 
116  auto node_data_map_iter = node_data_map_.end();
117  std::tie(node_data_map_iter, std::ignore) = node_data_map_.emplace(type, type_node_data());
118 
119  data = &node_data_map_iter->second;
120 
121  node_vector& type_node_vector = data->nodes;
122 
123  // The position to insert a new node of this type. If no nodes exist yet, the default value (-1) is
124  // accepted by tree_view_node as meaning at-end.
125  int& position = data->position;
126 
127  // Remove each node in reverse, so that in the end we have the position of the first node removed
128  for(auto i = type_node_vector.rbegin(); i != type_node_vector.rend(); i++) {
129  position = options_tree_.remove_node(*i).second;
130  }
131 
132  type_node_vector.clear();
133 
134  return position;
135 }
136 
138 {
139  // No custom options, display a message
141 }
142 
143 template<typename T>
145 {
146  options_data_[source.id][widget->id()] = widget->get_value();
147 }
148 
149 template<>
151 {
152  options_data_[source.id][widget->id()] = widget->get_value_bool();
153 }
154 
156 {
157  options_data_[source.id][widget->id()] = cfg.child_range("item")[widget->get_value()]["value"].str();
158 }
159 
160 void mp_options_helper::reset_options_data(const option_source& source, bool& handled, bool& halt)
161 {
162  options_data_[source.id].clear();
163 
164  if(source.level_type == "campaign" || source.level_type == "multiplayer") {
166  } else if(source.level_type == "era") {
168  } else if(source.level_type == "modification") {
170  }
171 
172  handled = true;
173  halt = true;
174 }
175 
176 template<typename T>
177 std::pair<T*, config::attribute_value> mp_options_helper::add_node_and_get_widget(
178  tree_view_node& option_node, const std::string& id, data_map& data, const config& cfg)
179 {
180  tree_view_node& node = option_node.add_child(id + "_node", data);
181 
182  T* widget = dynamic_cast<T*>(node.find(id, true));
184 
185  const std::string widget_id = cfg["id"];
186 
187  auto& option_config = options_data_[visible_options_.back().id];
188  if(!option_config.has_attribute(widget_id) || option_config[widget_id].empty()) {
189  option_config[widget_id] = cfg["default"];
190  }
191 
192  widget->set_id(widget_id);
193  widget->set_tooltip(cfg["description"]);
194 
195  return {widget, option_config[widget_id]};
196 }
197 
198 void mp_options_helper::display_custom_options(const std::string& type, int node_position, const config& cfg)
199 {
200  // Needed since some compilers don't like passing just {}
201  static const widget_data empty_map;
202 
203  // This ensures that any game, era, or mod with no options doesn't get an entry in the visible_options_
204  // vector and prevents invalid options from different games, era, or mods being created when the options
205  // config is created.
206  if(!cfg.has_child("options")) {
207  return;
208  }
209 
210  visible_options_.push_back({type, cfg["id"]});
211 
212  // Get the node vector for this specific source type
213  node_vector& type_node_vector = node_data_map_[type].nodes;
214 
215  for(const auto& options : cfg.child_range("options")) {
217  widget_item item;
218 
219  item["label"] = cfg["name"];
220  data.emplace("tree_view_node_label", item);
221 
222  tree_view_node& option_node = options_tree_.add_node("option_node", data, node_position);
223  type_node_vector.push_back(&option_node);
224 
225  for(const auto [option_key, option_cfg] : options.all_children_view()) {
226  data.clear();
227  item.clear();
228 
230 
231  if(option_key == "checkbox") {
232  item["label"] = option_cfg["name"];
233  data.emplace("option_checkbox", item);
234 
235  toggle_button* checkbox;
236  std::tie(checkbox, val) = add_node_and_get_widget<toggle_button>(option_node, "option_checkbox", data, option_cfg);
237 
238  checkbox->set_value(val.to_bool());
239 
241  std::bind(&mp_options_helper::update_options_data_map<toggle_button>, this, checkbox, visible_options_.back()));
242 
243  } else if(option_key == "spacer") {
244  option_node.add_child("options_spacer_node", empty_map);
245 
246  } else if(option_key == "choice") {
247  if(!option_cfg.has_child("item")) {
248  continue;
249  }
250 
251  item["label"] = option_cfg["name"];
252  data.emplace("menu_button_label", item);
253 
254  std::vector<config> combo_items;
255  std::vector<std::string> combo_values;
256 
257  for(auto i : option_cfg.child_range("item")) {
258  // Comboboxes expect this key to be 'label' not 'name'
259  i["label"] = i["name"];
260 
261  combo_items.push_back(i);
262  combo_values.push_back(i["value"]);
263  }
264 
265  menu_button* menu;
266  std::tie(menu, val) = add_node_and_get_widget<menu_button>(option_node, "option_menu_button", data, option_cfg);
267 
268  // Needs to be called before set_selected
269  menu->set_values(combo_items);
270 
271  auto iter = std::find(combo_values.begin(), combo_values.end(), val.str());
272 
273  if(iter != combo_values.end()) {
274  menu->set_selected(std::distance(combo_values.begin(), iter));
275  }
276 
278  std::bind(&mp_options_helper::update_options_data_map_menu_button, this, menu, visible_options_.back(), option_cfg));
279 
280  } else if(option_key == "slider") {
281  item["label"] = option_cfg["name"];
282  data.emplace("slider_label", item);
283 
284  slider* slide;
285  std::tie(slide, val) = add_node_and_get_widget<slider>(option_node, "option_slider", data, option_cfg);
286 
287  slide->set_value_range(option_cfg["min"].to_int(), option_cfg["max"].to_int());
288  slide->set_step_size(option_cfg["step"].to_int(1));
289  slide->set_value(val.to_int());
290 
292  std::bind(&mp_options_helper::update_options_data_map<slider>, this, slide, visible_options_.back()));
293 
294  } else if(option_key == "entry") {
295  item["label"] = option_cfg["name"];
296  data.emplace("text_entry_label", item);
297 
298  text_box* textbox;
299  std::tie(textbox, val) = add_node_and_get_widget<text_box>(option_node, "option_text_entry", data, option_cfg);
300 
301  textbox->set_value(val.str());
302  textbox->set_text_changed_callback(
303  std::bind(&mp_options_helper::update_options_data_map<text_box>, this, textbox, visible_options_.back()));
304  }
305  }
306 
307  // Add the Defaults button at the end
308  tree_view_node& node = option_node.add_child("options_default_button", empty_map);
309 
310  connect_signal_mouse_left_click(node.find_widget<button>("reset_option_values"),
312  std::placeholders::_3, std::placeholders::_4));
313  }
314 }
315 
317 {
318  config options;
319  for(const auto& source : visible_options_) {
320  config& mod = options.add_child(source.level_type);
321  mod["id"] = source.id;
322 #if 0
323  // TODO: enable this as soon as we drop the old mp configure screen.
324  mod.add_child("options", options_data_[source.id]);
325 #else
326  for(const auto& [key, value] : options_data_[source.id].attribute_range()) {
327  mod.add_child("option", config {"id", key, "value", value});
328  }
329 #endif
330  }
331 
332  return options;
333 }
334 
335 } // 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:172
bool has_child(config_key_type key) const
Determine whether a config has a child or not.
Definition: config.cpp:316
child_itors child_range(config_key_type key)
Definition: config.cpp:272
config & add_child(config_key_type key)
Definition: config.cpp:440
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)
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:276
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:250
virtual void set_value(const std::string &text)
The set_value is virtual for the password_box class.
void set_text_changed_callback(std::function< void(text_box_base *textbox, const std::string text)> cb)
Set the text_changed callback.
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.
widget * find(const std::string &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:56
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:62
Base class for all widgets.
Definition: widget.hpp:55
NOT_DANGLING T * find_widget(const std::string &id, const bool must_be_active, const bool must_exist)
Gets a widget with the wanted id.
Definition: widget.hpp:737
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
@ visible
The user sets the widget visible, that means:
@ invisible
The user set the widget invisible, that means:
base class of top level items, the only item which needs to store the final canvases to draw on.
Definition: window.hpp:61
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
static prefs & get()
std::size_t i
Definition: function.cpp:1023
static std::string _(const char *str)
Definition: gettext.hpp:93
This file contains the window object, this object is a top level container which has the event manage...
void connect_signal_notify_modified(dispatcher &dispatcher, const signal_notification &signal)
Connects a signal handler for getting a notification upon modification.
Definition: dispatcher.cpp:203
void connect_signal_mouse_left_click(dispatcher &dispatcher, const signal &signal)
Connects a signal handler for a left mouse button click.
Definition: dispatcher.cpp:177
t_string missing_widget(const std::string &id)
Returns a default error message if a mandatory widget is omitted.
Definition: helper.cpp:120
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:100
std::string_view data
Definition: picture.cpp:178
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.