The Battle for Wesnoth  1.15.0+dev
mp_options_helper.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2008 - 2018 by the Battle for Wesnoth Project https://www.wesnoth.org/
3 
4  This program is free software; you can redistribute it and/or modify
5  it under the terms of the GNU General Public License as published by
6  the Free Software Foundation; either version 2 of the License, or
7  (at your option) any later version.
8  This program is distributed in the hope that it will be useful,
9  but WITHOUT ANY WARRANTY.
10 
11  See the COPYING file for more details.
12 */
13 
14 #define GETTEXT_DOMAIN "wesnoth-lib"
15 
17 
18 #include "preferences/game.hpp"
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 "deprecation.hpp"
29 
30 namespace gui2
31 {
32 namespace dialogs
33 {
34 
36  : create_engine_(create_engine)
37  , options_tree_(find_widget<tree_view>(&window, "custom_options", false))
38  , no_options_notice_(find_widget<styled_widget>(&window, "no_options_notice", false))
39  , node_data_map_()
40  , visible_options_()
41  , options_data_()
42 {
43  for(const auto& c : preferences::options().all_children_range()) {
44  for(const auto& saved_option : c.cfg.child_range("option")) {
45  options_data_[c.cfg["id"]][saved_option["id"]] = saved_option["value"];
46  }
47  }
48 
50 }
51 
53 {
54  visible_options_.clear();
55  node_data_map_.clear();
57 
61 }
62 
64 {
65  std::string type;
66 
68  type = "campaign";
69  } else {
70  type = "multiplayer";
71  }
72 
73  // For game options, we check for both types and remove them. This is to prevent options from a game
74  // of one type remaining visible when selecting a game of another type.
75  remove_nodes_for_type("campaign");
76  int pos = remove_nodes_for_type("multiplayer");
77 
79 
81 }
82 
84 {
85  static const std::string type = "era";
86 
87  int pos = remove_nodes_for_type(type);
88 
90 
92 }
93 
95 {
96  static const std::string type = "modification";
97 
98  int pos = remove_nodes_for_type(type);
99 
100  for(const auto& mod : create_engine_.active_mods_data()) {
101  display_custom_options(type, pos, *mod->cfg);
102  }
103 
105 }
106 
108 {
109  // Remove all visible options of the specified source type
110  auto vo_iter = std::remove_if(visible_options_.begin(), visible_options_.end(), [&type](const option_source& source) {
111  return source.level_type == type;
112  });
113 
114  visible_options_.erase(vo_iter, visible_options_.end());
115 
116  // Get the node data for this specific source type
117  type_node_data* data;
118 
119  auto node_data_map_iter = node_data_map_.end();
120  std::tie(node_data_map_iter, std::ignore) = node_data_map_.emplace(type, type_node_data());
121 
122  data = &node_data_map_iter->second;
123 
124  node_vector& type_node_vector = data->nodes;
125 
126  // The position to insert a new node of this type. If no nodes exist yet, the default value (-1) is
127  // accepted by tree_view_node as meaning at-end.
128  int& position = data->position;
129 
130  // Remove each node in reverse, so that in the end we have the position of the first node removed
131  for(auto i = type_node_vector.rbegin(); i != type_node_vector.rend(); i++) {
132  position = options_tree_.remove_node(*i).second;
133  }
134 
135  type_node_vector.clear();
136 
137  return position;
138 }
139 
141 {
142  // No custom options, display a message
144 }
145 
146 template<typename T>
148 {
149  options_data_[source.id][widget->id()] = widget->get_value();
150 }
151 
152 template<>
154 {
155  options_data_[source.id][widget->id()] = widget->get_value_bool();
156 }
157 
159 {
160  options_data_[source.id][widget->id()] = cfg.child_range("item")[widget->get_value()]["value"].str();
161 }
162 
163 void mp_options_helper::reset_options_data(const option_source& source, bool& handled, bool& halt)
164 {
165  options_data_[source.id].clear();
166 
167  if(source.level_type == "campaign" || source.level_type == "multiplayer") {
169  } else if(source.level_type == "era") {
171  } else if(source.level_type == "modification") {
173  }
174 
175  handled = true;
176  halt = true;
177 }
178 
179 template<typename T>
180 std::pair<T*, config::attribute_value> mp_options_helper::add_node_and_get_widget(
181  tree_view_node& option_node, const std::string& id, data_map& data, const config& cfg)
182 {
183  tree_view_node& node = option_node.add_child(id + "_node", data);
184 
185  T* widget = dynamic_cast<T*>(node.find(id, true));
186  VALIDATE(widget, missing_widget(id));
187 
188  const std::string widget_id = cfg["id"];
189 
190  auto& option_config = options_data_[visible_options_.back().id];
191  if(!option_config.has_attribute(widget_id) || option_config[widget_id].empty()) {
192  option_config[widget_id] = cfg["default"];
193  }
194 
195  widget->set_id(widget_id);
196  widget->set_tooltip(cfg["description"]);
197 
198  return {widget, option_config[widget_id]};
199 }
200 
201 void mp_options_helper::display_custom_options(const std::string& type, int node_position, const config& cfg)
202 {
203  // Needed since some compilers don't like passing just {}
204  static const std::map<std::string, string_map> empty_map;
205 
206  // This ensures that any game, era, or mod with no options doesn't get an entry in the visible_options_
207  // vector and prevents invalid options from different games, era, or mods being created when the options
208  // config is created.
209  if(!cfg.has_child("options")) {
210  return;
211  }
212 
213  visible_options_.push_back({type, cfg["id"]});
214 
215  // Get the node vector for this specific source type
216  node_vector& type_node_vector = node_data_map_[type].nodes;
217 
218  for(const auto& options : cfg.child_range("options")) {
219  std::map<std::string, string_map> data;
221 
222  item["label"] = cfg["name"];
223  data.emplace("tree_view_node_label", item);
224 
225  tree_view_node& option_node = options_tree_.add_node("option_node", data, node_position);
226  type_node_vector.push_back(&option_node);
227 
228  for(const config::any_child& opt : options.all_children_range()) {
229  data.clear();
230  item.clear();
231 
232  const config& option_cfg = opt.cfg;
233 
234  const auto add_name = [&](const std::string& id) {
235  item["label"] = option_cfg["name"];
236  data.emplace(id, item);
237  };
238 
240 
241  if(opt.key == "checkbox") {
242  add_name("option_checkbox");
243 
244  toggle_button* checkbox;
245  std::tie(checkbox, val) = add_node_and_get_widget<toggle_button>(option_node, "option_checkbox", data, option_cfg);
246 
247  checkbox->set_value(val.to_bool());
248 
250  std::bind(&mp_options_helper::update_options_data_map<toggle_button>, this, checkbox, visible_options_.back()));
251 
252  } else if(opt.key == "spacer") {
253  option_node.add_child("options_spacer_node", empty_map);
254 
255  } else if(opt.key == "choice" || opt.key == "combo") {
256  if(opt.key == "combo") {
257  deprecated_message("combo", DEP_LEVEL::FOR_REMOVAL, {1, 15, 0}, "Use [choice] instead.");
258  }
259 
260  if(!option_cfg.has_child("item")) {
261  continue;
262  }
263 
264  add_name("menu_button_label");
265 
266  std::vector<config> combo_items;
267  std::vector<std::string> combo_values;
268 
269  for(auto i : option_cfg.child_range("item")) {
270  // Comboboxes expect this key to be 'label' not 'name'
271  i["label"] = i["name"];
272 
273  combo_items.push_back(i);
274  combo_values.push_back(i["value"]);
275  }
276 
277  menu_button* menu;
278  std::tie(menu, val) = add_node_and_get_widget<menu_button>(option_node, "option_menu_button", data, option_cfg);
279 
280  // Needs to be called before set_selected
281  menu->set_values(combo_items);
282 
283  auto iter = std::find(combo_values.begin(), combo_values.end(), val.str());
284 
285  if(iter != combo_values.end()) {
286  menu->set_selected(std::distance(combo_values.begin(), iter));
287  }
288 
290  std::bind(&mp_options_helper::update_options_data_map_menu_button, this, menu, visible_options_.back(), option_cfg));
291 
292  } else if(opt.key == "slider") {
293  add_name("slider_label");
294 
295  slider* slide;
296  std::tie(slide, val) = add_node_and_get_widget<slider>(option_node, "option_slider", data, option_cfg);
297 
298  slide->set_value_range(option_cfg["min"].to_int(), option_cfg["max"].to_int());
299  slide->set_step_size(option_cfg["step"].to_int(1));
300  slide->set_value(val.to_int());
301 
303  std::bind(&mp_options_helper::update_options_data_map<slider>, this, slide, visible_options_.back()));
304 
305  } else if(opt.key == "entry") {
306  add_name("text_entry_label");
307 
308  text_box* textbox;
309  std::tie(textbox, val) = add_node_and_get_widget<text_box>(option_node, "option_text_entry", data, option_cfg);
310 
311  textbox->set_value(val.str());
312  textbox->set_text_changed_callback(
313  std::bind(&mp_options_helper::update_options_data_map<text_box>, this, textbox, visible_options_.back()));
314  }
315  }
316 
317  // Add the Defaults button at the end
318  tree_view_node& node = option_node.add_child("options_default_button", empty_map);
319 
320  connect_signal_mouse_left_click(find_widget<button>(&node, "reset_option_values", false),
322  std::placeholders::_3, std::placeholders::_4));
323  }
324 }
325 
327 {
328  config options;
329  for(const auto& source : visible_options_) {
330  config& mod = options.add_child(source.level_type);
331  mod["id"] = source.id;
332 #if 0
333  // TODO: enable this as soon as we drop the old mp configure screen.
334  mod.add_child("options", options_data_[source.id]);
335 #else
336  for(const auto& option : options_data_[source.id].attribute_range()) {
337  mod.add_child("option", config {"id", option.first, "value", option.second});
338  }
339 #endif
340  }
341 
342  return options;
343 }
344 
345 } // namespace dialogs
346 } // namespace gui2 // namespace gui2
std::map< std::string, config > options_data_
void set_text_changed_callback(std::function< void(text_box_base *textbox, const std::string text)> cb)
Set the text_changed callback.
void set_selected(unsigned selected, bool fire_event=true)
void update_options_data_map(T *widget, const option_source &source)
virtual void set_value(unsigned selected, bool fire_event=false) override
Inherited from selectable_item.
const_all_children_itors all_children_range() const
In-order iteration over all children.
Definition: config.cpp:921
Simple push button.
Definition: menu_button.hpp:41
Variant for storing WML attributes.
bool has_child(config_key_type key) const
Determine whether a config has a child or not.
Definition: config.cpp:412
const std::string & id() const
Definition: widget.cpp:107
This file contains the window object, this object is a top level container which has the event manage...
child_itors child_range(config_key_type key)
Definition: config.cpp:362
Base class for all widgets.
Definition: widget.hpp:47
void display_custom_options(const std::string &type, int node_position, const config &data)
std::vector< option_source > visible_options_
widget * find(const std::string &id, const bool must_be_active) override
See widget::find.
bool is_campaign() const
Wrapper to simplify the is-type-campaign-or-sp-campaign check.
std::pair< tree_view_node::ptr_t, int > remove_node(tree_view_node *node)
Removes the given node as a child of its parent node.
Definition: tree_view.cpp:69
Class for a single line text area.
Definition: text_box.hpp:121
T * find_widget(utils::const_clone_ptr< widget, T > widget, const std::string &id, const bool must_be_active, const bool must_exist)
Gets a widget with the wanted id.
Definition: find_widget.hpp:68
Generic file dialog.
Definition: field-fwd.hpp:22
void update_options_data_map_menu_button(menu_button *widget, const option_source &source, const config &cfg)
const config & data() const
std::map< std::string, string_map > data_map
std::vector< extras_metadata_ptr > active_mods_data()
const config & options()
Definition: game.cpp:582
virtual unsigned get_value() const override
Inherited from selectable_item.
Definition: menu_button.hpp:64
const config & curent_era_cfg() const
void set_value_range(int min_value, int max_value)
Definition: slider.cpp:254
void connect_signal_notify_modified(dispatcher &dispatcher, const signal_notification_function &signal)
Connects a signal handler for getting a notification upon modification.
Definition: dispatcher.cpp:248
std::string deprecated_message(const std::string &elem_name, DEP_LEVEL level, const version_info &version, const std::string &detail)
Definition: deprecation.cpp:29
#define VALIDATE(cond, message)
The macro to use for the validation of WML.
void set_visible(const visibility visible)
Definition: widget.cpp:473
void connect_signal_mouse_left_click(dispatcher &dispatcher, const signal_function &signal)
Connects a signal handler for a left mouse button click.
Definition: dispatcher.cpp:233
void set_values(const std::vector<::config > &values, unsigned selected=0)
void reset_options_data(const option_source &source, bool &handled, bool &halt)
Various uncategorised dialogs.
std::size_t i
Definition: function.cpp:933
level & current_level() const
The user set the widget invisible, that means:
std::map< std::string, type_node_data > node_data_map_
std::map< std::string, t_string > string_map
Definition: widget.hpp:24
bool to_bool(bool def=false) const
Base class for all visible items.
config & add_child(config_key_type key)
Definition: config.cpp:476
virtual void set_value(const std::string &text)
The set_value is virtual for the password_box class.
The user sets the widget visible, that means:
bool find(E event, F functor)
Tests whether an event handler is available.
bool empty() const
Definition: tree_view.cpp:119
A slider.
Definition: slider.hpp:33
virtual void set_value(int value) override
Inherited from integer_selector.
Definition: slider.cpp:86
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:92
mock_char c
tree_view_node & add_child(const std::string &id, const std::map< std::string, string_map > &data, const int index=-1)
Constructs a new child node.
std::vector< tree_view_node * > node_vector
void set_step_size(int step_size)
Definition: slider.cpp:285
int remove_nodes_for_type(const std::string &type)
base class of top level items, the only item which needs to store the final canvases to draw on ...
Definition: window.hpp:63
mp_options_helper(window &window, ng::create_engine &create_engine)
t_string missing_widget(const std::string &id)
Returns a default error message if a mandatory widget is omitted.
Definition: helper.cpp:94
Class for a toggle button.
tree_view_node & add_node(const std::string &id, const std::map< std::string, string_map > &data, const int index=-1)
Definition: tree_view.cpp:61
std::pair< std::string, unsigned > item
Definition: help_impl.hpp:371
std::string str(const std::string &fallback="") const
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)