The Battle for Wesnoth  1.15.9+dev
campaign_selection.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2009 - 2018 by Mark de Wever <koraq@xs4all.nl>
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 
19 #include "font/text_formatting.hpp"
22 #include "gui/widgets/button.hpp"
23 #include "gui/widgets/image.hpp"
24 #include "gui/widgets/listbox.hpp"
29 #include "gui/widgets/settings.hpp"
30 #include "gui/widgets/text_box.hpp"
34 #include "gui/widgets/window.hpp"
35 #include "lexical_cast.hpp"
36 #include "preferences/game.hpp"
37 
38 #include <functional>
39 #include "utils/irdya_datetime.hpp"
40 
41 namespace gui2
42 {
43 namespace dialogs
44 {
45 
46 REGISTER_DIALOG(campaign_selection)
47 
48 void campaign_selection::campaign_selected()
49 {
50  tree_view& tree = find_widget<tree_view>(get_window(), "campaign_tree", false);
51  if(tree.empty()) {
52  return;
53  }
54 
55  assert(tree.selected_item());
56 
57  if(!tree.selected_item()->id().empty()) {
58  auto iter = std::find(page_ids_.begin(), page_ids_.end(), tree.selected_item()->id());
59 
60  const int choice = std::distance(page_ids_.begin(), iter);
61  if(iter == page_ids_.end()) {
62  return;
63  }
64 
65  multi_page& pages = find_widget<multi_page>(get_window(), "campaign_details", false);
66  pages.select_page(choice);
67 
68  engine_.set_current_level(choice);
69 
70  styled_widget& background = find_widget<styled_widget>(get_window(), "campaign_background", false);
71  background.set_label(engine_.current_level().data()["background"].str());
72 
73  // Rebuild difficulty menu
74  difficulties_.clear();
75 
76  auto& diff_menu = find_widget<menu_button>(get_window(), "difficulty_menu", false);
77 
78  const auto& diff_config = generate_difficulty_config(engine_.current_level().data());
79  diff_menu.set_active(diff_config.child_count("difficulty") > 1);
80 
81  if(!diff_config.empty()) {
82  std::vector<config> entry_list;
83  unsigned n = 0, selection = 0, max_n = diff_config.child_count("difficulty");
84 
85  for(const auto& cfg : diff_config.child_range("difficulty")) {
86  config entry;
87 
88  // FIXME: description may have markup that will display weird on the menu_button proper
89  entry["label"] = cfg["label"].str() + " (" + cfg["description"].str() + ")";
90  entry["image"] = cfg["image"].str("misc/blank-hex.png");
91 
92  if(preferences::is_campaign_completed(tree.selected_item()->id(), cfg["define"])) {
93  std::string laurel;
94 
95  if(n + 1 >= max_n) {
97  } else if(n == 0) {
99  } else {
101  }
102 
103  entry["image"] = laurel + "~BLIT(" + entry["image"] + ")";
104  }
105 
106  if(!cfg["description"].empty()) {
107  std::string desc;
108  if(cfg["auto_markup"].to_bool(true) == false) {
109  desc = cfg["description"].str();
110  } else {
111  //desc = "<small>";
112  if(!cfg["old_markup"].to_bool()) {
113  desc += font::span_color(font::GRAY_COLOR) + "(" + cfg["description"].str() + ")</span>";
114  } else {
115  desc += font::span_color(font::GRAY_COLOR) + cfg["description"].str() + "</span>";
116  }
117  //desc += "</small>";
118  }
119 
120  // Icons get displayed instead of the labels on the dropdown menu itself,
121  // so we want to prepend each label to its description here
122  desc = cfg["label"].str() + "\n" + desc;
123 
124  entry["details"] = std::move(desc);
125  }
126 
127  entry_list.emplace_back(std::move(entry));
128  difficulties_.emplace_back(cfg["define"].str());
129 
130  if(cfg["default"].to_bool(false)) {
131  selection = n;
132  }
133 
134  ++n;
135  }
136 
137  diff_menu.set_values(entry_list);
138  diff_menu.set_selected(selection);
139  }
140  }
141 }
142 
144 {
145  const std::size_t selection = find_widget<menu_button>(get_window(), "difficulty_menu", false).get_value();
146  current_difficulty_ = difficulties_.at(std::min(difficulties_.size() - 1, selection));
147 }
148 
150 {
151  using level_ptr = ng::create_engine::level_ptr;
152 
153  auto levels = engine_.get_levels_by_type_unfiltered(ng::level::TYPE::SP_CAMPAIGN);
154 
155  switch(order) {
156  case RANK: // Already sorted by rank
157  // This'll actually never happen, but who knows if that'll ever change...
158  if(!ascending) {
159  std::reverse(levels.begin(), levels.end());
160  }
161 
162  break;
163 
164  case DATE:
165  std::sort(levels.begin(), levels.end(), [ascending](const level_ptr& a, const level_ptr& b) {
166  auto cpn_a = std::dynamic_pointer_cast<ng::campaign>(a);
167  auto cpn_b = std::dynamic_pointer_cast<ng::campaign>(b);
168 
169  if(cpn_b == nullptr) {
170  return cpn_a != nullptr;
171  }
172 
173  if(cpn_a == nullptr) {
174  return false;
175  }
176 
177  return ascending
178  ? cpn_a->dates().first < cpn_b->dates().first
179  : cpn_a->dates().first > cpn_b->dates().first;
180  });
181 
182  break;
183 
184  case NAME:
185  std::sort(levels.begin(), levels.end(), [ascending](const level_ptr& a, const level_ptr& b) {
186  const int cmp = translation::icompare(a->name(), b->name());
187  return ascending ? cmp < 0 : cmp > 0;
188  });
189 
190  break;
191  }
192 
193  tree_view& tree = find_widget<tree_view>(get_window(), "campaign_tree", false);
194 
195  // Remember which campaign was selected...
196  std::string was_selected;
197  if(!tree.empty()) {
198  was_selected = tree.selected_item()->id();
199  tree.clear();
200  }
201 
202  boost::dynamic_bitset<> show_items;
203  show_items.resize(levels.size(), true);
204 
205  if(!last_search_words_.empty()) {
206  for(unsigned i = 0; i < levels.size(); ++i) {
207  bool found = false;
208  for(const auto& word : last_search_words_) {
209  found = translation::ci_search(levels[i]->name(), word) ||
210  translation::ci_search(levels[i]->data()["name"].t_str().base_str(), word) ||
211  translation::ci_search(levels[i]->description(), word) ||
212  translation::ci_search(levels[i]->data()["description"].t_str().base_str(), word) ||
213  translation::ci_search(levels[i]->data()["abbrev"], word) ||
214  translation::ci_search(levels[i]->data()["abbrev"].t_str().base_str(), word);
215 
216  if(!found) {
217  break;
218  }
219  }
220 
221  show_items[i] = found;
222  }
223  }
224 
225  bool exists_in_filtered_result = false;
226  for(unsigned i = 0; i < levels.size(); ++i) {
227  if(show_items[i]) {
228  add_campaign_to_tree(levels[i]->data());
229 
230  if (!exists_in_filtered_result) {
231  exists_in_filtered_result = levels[i]->id() == was_selected;
232  }
233  }
234  }
235 
236  if(!was_selected.empty() && exists_in_filtered_result) {
237  find_widget<tree_view_node>(get_window(), was_selected, false).select_node();
238  } else {
240  }
241 }
242 
244 {
245  static bool force = false;
246  if(force) {
247  return;
248  }
249 
250  if(current_sorting_ == order) {
252  currently_sorted_asc_ = false;
253  } else {
254  currently_sorted_asc_ = true;
256  }
257  } else if(current_sorting_ == RANK) {
258  currently_sorted_asc_ = true;
259  current_sorting_ = order;
260  } else {
261  currently_sorted_asc_ = true;
262  current_sorting_ = order;
263 
264  force = true;
265 
266  if(order == NAME) {
267  find_widget<toggle_button>(get_window(), "sort_time", false).set_value(0);
268  } else if(order == DATE) {
269  find_widget<toggle_button>(get_window(), "sort_name", false).set_value(0);
270  }
271 
272  force = false;
273  }
274 
276 }
277 
278 void campaign_selection::filter_text_changed(const std::string& text)
279 {
280  const std::vector<std::string> words = utils::split(text, ' ');
281 
282  if(words == last_search_words_) {
283  return;
284  }
285 
286  last_search_words_ = words;
288 }
289 
291 {
292  text_box* filter = find_widget<text_box>(&window, "filter_box", false, true);
294  std::bind(&campaign_selection::filter_text_changed, this, std::placeholders::_2));
295 
296  /***** Setup campaign tree. *****/
297  tree_view& tree = find_widget<tree_view>(&window, "campaign_tree", false);
298 
300  std::bind(&campaign_selection::campaign_selected, this));
301 
302  toggle_button& sort_name = find_widget<toggle_button>(&window, "sort_name", false);
303  toggle_button& sort_time = find_widget<toggle_button>(&window, "sort_time", false);
304 
307 
310 
311  window.keyboard_capture(filter);
312  window.add_to_keyboard_chain(&tree);
313 
314  /***** Setup campaign details. *****/
315  multi_page& pages = find_widget<multi_page>(&window, "campaign_details", false);
316 
317  for(const auto& level : engine_.get_levels_by_type_unfiltered(ng::level::TYPE::SP_CAMPAIGN)) {
318  const config& campaign = level->data();
319 
320  /*** Add tree item ***/
321  add_campaign_to_tree(campaign);
322 
323  /*** Add detail item ***/
324  std::map<std::string, string_map> data;
326 
327  item["label"] = campaign["description"];
328  item["use_markup"] = "true";
329 
330  if(!campaign["description_alignment"].empty()) {
331  item["text_alignment"] = campaign["description_alignment"];
332  }
333 
334  data.emplace("description", item);
335 
336  item["label"] = campaign["image"];
337  data.emplace("image", item);
338 
339  pages.add_page(data);
340  page_ids_.push_back(campaign["id"]);
341  }
342 
343  //
344  // Set up Mods selection dropdown
345  //
346  multimenu_button& mods_menu = find_widget<multimenu_button>(&window, "mods_menu", false);
347 
349  std::vector<config> mod_menu_values;
350  std::vector<std::string> enabled = engine_.active_mods();
351 
353  const bool active = std::find(enabled.begin(), enabled.end(), mod->id) != enabled.end();
354 
355  mod_menu_values.emplace_back("label", mod->name, "checkbox", active);
356 
357  mod_states_.push_back(active);
358  }
359 
360  mods_menu.set_values(mod_menu_values);
361  mods_menu.select_options(mod_states_);
362 
364  } else {
365  mods_menu.set_active(false);
366  mods_menu.set_label(_("active_modifications^None"));
367  }
368 
369  //
370  // Set up Difficulty dropdown
371  //
372  menu_button& diff_menu = find_widget<menu_button>(get_window(), "difficulty_menu", false);
373 
374  diff_menu.set_use_markup(true);
376 
378 }
379 
381 {
382  tree_view& tree = find_widget<tree_view>(get_window(), "campaign_tree", false);
383  std::map<std::string, string_map> data;
385 
386  item["label"] = campaign["icon"];
387  data.emplace("icon", item);
388 
389  item["label"] = campaign["name"];
390  data.emplace("name", item);
391 
392  // We completed the campaign! Calculate the appropriate victory laurel.
393  if(campaign["completed"].to_bool()) {
394  config::const_child_itors difficulties = campaign.child_range("difficulty");
395 
396  auto did_complete_at = [](const config& c) { return c["completed_at"].to_bool(); };
397 
398  // Check for non-completion on every difficulty save the first.
399  const bool only_first_completed = difficulties.size() > 1 &&
400  std::none_of(difficulties.begin() + 1, difficulties.end(), did_complete_at);
401 
402  /*
403  * Criteria:
404  *
405  * - Use the gold laurel (hardest) for campaigns with only one difficulty OR
406  * if out of two or more difficulties, the last one has been completed.
407  *
408  * - Use the bronze laurel (easiest) only if the first difficulty out of two
409  * or more has been completed.
410  *
411  * - Use the silver laurel otherwise.
412  */
413  if(!difficulties.empty() && did_complete_at(difficulties.back())) {
415  } else if(only_first_completed && did_complete_at(difficulties.front())) {
417  } else {
418  item["label"] = game_config::images::victory_laurel;
419  }
420 
421  data.emplace("victory", item);
422  }
423 
424  tree.add_node("campaign", data).set_id(campaign["id"]);
425 }
426 
428 {
429  tree_view& tree = find_widget<tree_view>(&window, "campaign_tree", false);
430 
431  if(tree.empty()) {
432  return;
433  }
434 
435  assert(tree.selected_item());
436  if(!tree.selected_item()->id().empty()) {
437  auto iter = std::find(page_ids_.begin(), page_ids_.end(), tree.selected_item()->id());
438  if(iter != page_ids_.end()) {
439  choice_ = std::distance(page_ids_.begin(), iter);
440  }
441  }
442 
443 
444  rng_mode_ = RNG_MODE(std::clamp<unsigned>(find_widget<menu_button>(&window, "rng_menu", false).get_value(), RNG_DEFAULT, RNG_BIASED));
445 
447 }
448 
450 {
451  boost::dynamic_bitset<> new_mod_states =
452  find_widget<multimenu_button>(get_window(), "mods_menu", false).get_toggle_states();
453 
454  // Get a mask of any mods that were toggled, regardless of new state
455  mod_states_ = mod_states_ ^ new_mod_states;
456 
457  for(unsigned i = 0; i < mod_states_.size(); i++) {
458  if(mod_states_[i]) {
459  engine_.toggle_mod(i);
460  }
461  }
462 
463  // Save the full toggle states for next time
464  mod_states_ = new_mod_states;
465 }
466 
467 } // namespace dialogs
468 } // namespace gui2
void set_text_changed_callback(std::function< void(text_box_base *textbox, const std::string text)> cb)
Set the text_changed callback.
std::string victory_laurel
void add_campaign_to_tree(const config &campaign) const
std::shared_ptr< level > level_ptr
A menu_button is a styled_widget to choose an element from a list of elements.
Definition: menu_button.hpp:64
This shows the dialog which allows the user to choose which campaign to play.
const color_t GRAY_COLOR
grid & add_page(const string_map &item)
Adds single page to the grid.
Definition: multi_page.cpp:43
New lexcical_cast header.
#define a
const std::string & id() const
Definition: widget.cpp:109
bool is_campaign_completed(const std::string &campaign_id)
Definition: game.cpp:301
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
tree_view_node * selected_item()
Definition: tree_view.hpp:115
window * get_window() const
Returns a pointer to the dialog&#39;s window.
std::string victory_laurel_easy
A multimenu_button is a styled_widget to choose an element from a list of elements.
std::pair< irdya_date, irdya_date > dates() const
static std::string _(const char *str)
Definition: gettext.hpp:92
void campaign_selected()
Called when another campaign is selected.
std::vector< std::pair< const std::string *, const stats * > > levels
Stats (and name) for each scenario.
Definition: statistics.hpp:135
Class for a single line text area.
Definition: text_box.hpp:140
Generic file dialog.
Definition: field-fwd.hpp:22
#define b
void filter_text_changed(const std::string &text)
virtual void set_label(const t_string &label)
std::string span_color(const color_t &color)
Returns a Pango formatting string using the provided color_t object.
void difficulty_selected()
Called when the difficulty selection changes.
bool toggle_mod(int index, bool force=false)
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:186
std::string victory_laurel_hardest
RNG_MODE rng_mode_
whether the player checked the "Deterministic" checkbox.
This file contains the settings handling of the widget library.
std::vector< level_ptr > get_levels_by_type_unfiltered(level::TYPE type) const
virtual void post_show(window &window) override
Inherited from modal_dialog.
virtual void set_use_markup(bool use_markup)
A tree view is a control that holds several items of the same or different types. ...
Definition: tree_view.hpp:59
std::vector< std::string > & active_mods()
Various uncategorised dialogs.
void sort_campaigns(CAMPAIGN_ORDER order, bool ascending)
std::vector< std::string > page_ids_
boost::iterator_range< const_child_iterator > const_child_itors
Definition: config.hpp:205
bool ci_search(const std::string &s1, const std::string &s2)
Definition: gettext.cpp:519
virtual void set_active(const bool active) override
See styled_widget::set_active.
std::size_t i
Definition: function.cpp:933
void toggle_sorting_selection(CAMPAIGN_ORDER order)
config generate_difficulty_config(const config &source)
Helper function to convert old difficulty markup.
virtual void pre_show(window &window) override
Inherited from modal_dialog.
void select_page(const unsigned page, const bool select=true)
Selects a page.
Definition: multi_page.cpp:106
std::vector< std::string > difficulties_
std::string name
Definition: sdl_ttf.cpp:70
std::map< std::string, t_string > string_map
Definition: widget.hpp:26
Base class for all visible items.
static int sort(lua_State *L)
Definition: ltablib.cpp:411
RNG_MODE
RNG mode selection values.
A multi page is a control that contains several &#39;pages&#39; of which only one is visible.
Definition: multi_page.hpp:48
std::vector< std::string > split(const config_attribute_value &val)
int icompare(const std::string &s1, const std::string &s2)
Case-insensitive lexicographical comparison.
Definition: gettext.cpp:476
bool empty() const
Definition: tree_view.cpp:110
void set_modifications(const std::vector< std::string > &value, bool mp)
Definition: game.cpp:728
void set_id(const std::string &id)
Definition: widget.cpp:97
void select_options(boost::dynamic_bitset<> states)
Set the options selected in the menu.
const std::vector< extras_metadata_ptr > & get_const_extras_by_type(const MP_EXTRA extra_type) const
void set_values(const std::vector<::config > &values)
Set the available menu options.
static void reverse(lua_State *L, StkId from, StkId to)
Definition: lapi.cpp:193
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:60
mock_char c
static map_location::DIRECTION n
base class of top level items, the only item which needs to store the final canvases to draw on...
Definition: window.hpp:64
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:56
std::pair< std::string, unsigned > item
Definition: help_impl.hpp:407
std::vector< std::string > last_search_words_