The Battle for Wesnoth  1.15.7+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 
20 #include "gui/widgets/button.hpp"
21 #include "gui/widgets/image.hpp"
22 #include "gui/widgets/listbox.hpp"
26 #include "gui/widgets/settings.hpp"
27 #include "gui/widgets/text_box.hpp"
31 #include "gui/widgets/window.hpp"
32 #include "lexical_cast.hpp"
33 #include "preferences/game.hpp"
34 
35 #include "utils/functional.hpp"
36 #include "utils/irdya_datetime.hpp"
37 
38 namespace gui2
39 {
40 namespace dialogs
41 {
42 /*WIKI
43  * @page = GUIWindowDefinitionWML
44  * @order = 2_campaign_selection
45  *
46  * == Campaign selection ==
47  *
48  * This shows the dialog which allows the user to choose which campaign to
49  * play.
50  *
51  * @begin{table}{dialog_widgets}
52  *
53  * campaign_tree & & tree_view & m &
54  * A tree_view that contains all available campaigns. $
55  *
56  * -icon & & image & o &
57  * The icon for the campaign. $
58  *
59  * -name & & styled_widget & o &
60  * The name of the campaign. $
61  *
62  * -victory & & image & o &
63  * The icon to show when the user finished the campaign. The engine
64  * determines whether or not the user has finished the campaign and
65  * sets the visible flag for the widget accordingly. $
66  *
67  * campaign_details & & multi_page & m &
68  * A multi page widget that shows more details for the selected
69  * campaign. $
70  *
71  * -image & & image & o &
72  * The image for the campaign. $
73  *
74  * -description & & styled_widget & o &
75  * The description of the campaign. $
76  *
77  * @end{table}
78  */
79 
80 REGISTER_DIALOG(campaign_selection)
81 
82 void campaign_selection::campaign_selected() const
83 {
84  tree_view& tree = find_widget<tree_view>(get_window(), "campaign_tree", false);
85  if(tree.empty()) {
86  return;
87  }
88 
89  assert(tree.selected_item());
90 
91  if(!tree.selected_item()->id().empty()) {
92  auto iter = std::find(page_ids_.begin(), page_ids_.end(), tree.selected_item()->id());
93 
94  const int choice = std::distance(page_ids_.begin(), iter);
95  if(iter == page_ids_.end()) {
96  return;
97  }
98 
99  multi_page& pages = find_widget<multi_page>(get_window(), "campaign_details", false);
100  pages.select_page(choice);
101 
102  engine_.set_current_level(choice);
103  }
104 }
105 
107 {
108  using level_ptr = ng::create_engine::level_ptr;
109 
110  auto levels = engine_.get_levels_by_type_unfiltered(ng::level::TYPE::SP_CAMPAIGN);
111 
112  switch(order) {
113  case RANK: // Already sorted by rank
114  // This'll actually never happen, but who knows if that'll ever change...
115  if(!ascending) {
116  std::reverse(levels.begin(), levels.end());
117  }
118 
119  break;
120 
121  case DATE:
122  std::sort(levels.begin(), levels.end(), [ascending](const level_ptr& a, const level_ptr& b) {
123  auto cpn_a = std::dynamic_pointer_cast<ng::campaign>(a);
124  auto cpn_b = std::dynamic_pointer_cast<ng::campaign>(b);
125 
126  if(cpn_b == nullptr) {
127  return cpn_a != nullptr;
128  }
129 
130  if(cpn_a == nullptr) {
131  return false;
132  }
133 
134  return ascending
135  ? cpn_a->dates().first < cpn_b->dates().first
136  : cpn_a->dates().first > cpn_b->dates().first;
137  });
138 
139  break;
140 
141  case NAME:
142  std::sort(levels.begin(), levels.end(), [ascending](const level_ptr& a, const level_ptr& b) {
143  const int cmp = translation::icompare(a->name(), b->name());
144  return ascending ? cmp < 0 : cmp > 0;
145  });
146 
147  break;
148  }
149 
150  tree_view& tree = find_widget<tree_view>(get_window(), "campaign_tree", false);
151 
152  // Remember which campaign was selected...
153  std::string was_selected;
154  if(!tree.empty()) {
155  was_selected = tree.selected_item()->id();
156  tree.clear();
157  }
158 
159  boost::dynamic_bitset<> show_items;
160  show_items.resize(levels.size(), true);
161 
162  if(!last_search_words_.empty()) {
163  for(unsigned i = 0; i < levels.size(); ++i) {
164  bool found = false;
165  for(const auto& word : last_search_words_) {
166  found = translation::ci_search(levels[i]->name(), word) ||
167  translation::ci_search(levels[i]->data()["name"].t_str().base_str(), word) ||
168  translation::ci_search(levels[i]->description(), word) ||
169  translation::ci_search(levels[i]->data()["description"].t_str().base_str(), word) ||
170  translation::ci_search(levels[i]->data()["abbrev"], word) ||
171  translation::ci_search(levels[i]->data()["abbrev"].t_str().base_str(), word);
172 
173  if(!found) {
174  break;
175  }
176  }
177 
178  show_items[i] = found;
179  }
180  }
181 
182  bool exists_in_filtered_result = false;
183  for(unsigned i = 0; i < levels.size(); ++i) {
184  if(show_items[i]) {
186 
187  if (!exists_in_filtered_result) {
188  exists_in_filtered_result = levels[i]->id() == was_selected;
189  }
190  }
191  }
192 
193  if(!was_selected.empty() && exists_in_filtered_result) {
194  find_widget<tree_view_node>(get_window(), was_selected, false).select_node();
195  } else {
197  }
198 }
199 
201 {
202  static bool force = false;
203  if(force) {
204  return;
205  }
206 
207  if(current_sorting_ == order) {
209  currently_sorted_asc_ = false;
210  } else {
211  currently_sorted_asc_ = true;
213  }
214  } else if(current_sorting_ == RANK) {
215  currently_sorted_asc_ = true;
216  current_sorting_ = order;
217  } else {
218  currently_sorted_asc_ = true;
219  current_sorting_ = order;
220 
221  force = true;
222 
223  if(order == NAME) {
224  find_widget<toggle_button>(get_window(), "sort_time", false).set_value(0);
225  } else if(order == DATE) {
226  find_widget<toggle_button>(get_window(), "sort_name", false).set_value(0);
227  }
228 
229  force = false;
230  }
231 
233 }
234 
236 {
237  const std::vector<std::string> words = utils::split(text, ' ');
238 
239  if(words == last_search_words_) {
240  return;
241  }
242 
243  last_search_words_ = words;
245 }
246 
248 {
249  text_box* filter = find_widget<text_box>(&window, "filter_box", false, true);
251  std::bind(&campaign_selection::filter_text_changed, this, _2));
252 
253  /***** Setup campaign tree. *****/
254  tree_view& tree = find_widget<tree_view>(&window, "campaign_tree", false);
255 
257  std::bind(&campaign_selection::campaign_selected, this));
258 
259  toggle_button& sort_name = find_widget<toggle_button>(&window, "sort_name", false);
260  toggle_button& sort_time = find_widget<toggle_button>(&window, "sort_time", false);
261 
264 
267 
268  window.keyboard_capture(filter);
269  window.add_to_keyboard_chain(&tree);
270 
271  /***** Setup campaign details. *****/
272  multi_page& pages = find_widget<multi_page>(&window, "campaign_details", false);
273 
274  for(const auto& level : engine_.get_levels_by_type_unfiltered(ng::level::TYPE::SP_CAMPAIGN)) {
275  const config& campaign = level->data();
276 
277  /*** Add tree item ***/
278  add_campaign_to_tree(campaign);
279 
280  /*** Add detail item ***/
281  std::map<std::string, string_map> data;
283 
284  item["label"] = campaign["description"];
285  item["use_markup"] = "true";
286 
287  if(!campaign["description_alignment"].empty()) {
288  item["text_alignment"] = campaign["description_alignment"];
289  }
290 
291  data.emplace("description", item);
292 
293  item["label"] = campaign["image"];
294  data.emplace("image", item);
295 
296  pages.add_page(data);
297  page_ids_.push_back(campaign["id"]);
298  }
299 
300  //
301  // Set up Mods selection dropdown
302  //
303  multimenu_button& mods_menu = find_widget<multimenu_button>(&window, "mods_menu", false);
304 
306  std::vector<config> mod_menu_values;
307  std::vector<std::string> enabled = engine_.active_mods();
308 
310  const bool active = std::find(enabled.begin(), enabled.end(), mod->id) != enabled.end();
311 
312  mod_menu_values.emplace_back("label", mod->name, "checkbox", active);
313 
314  mod_states_.push_back(active);
315  }
316 
317  mods_menu.set_values(mod_menu_values);
318  mods_menu.select_options(mod_states_);
319 
321  } else {
322  mods_menu.set_active(false);
323  mods_menu.set_label(_("active_modifications^None"));
324  }
325 
327 }
328 
330 {
331  tree_view& tree = find_widget<tree_view>(get_window(), "campaign_tree", false);
332  std::map<std::string, string_map> data;
334 
335  item["label"] = campaign["icon"];
336  data.emplace("icon", item);
337 
338  item["label"] = campaign["name"];
339  data.emplace("name", item);
340 
341  // We completed the campaign! Calculate the appropriate victory laurel.
342  if(campaign["completed"].to_bool()) {
343  config::const_child_itors difficulties = campaign.child_range("difficulty");
344 
345  auto did_complete_at = [](const config& c) { return c["completed_at"].to_bool(); };
346 
347  // Check for non-completion on every difficulty save the first.
348  const bool only_first_completed = difficulties.size() > 1 &&
349  std::none_of(difficulties.begin() + 1, difficulties.end(), did_complete_at);
350 
351  /*
352  * Criteria:
353  *
354  * - Use the gold laurel (hardest) for campaigns with only one difficulty OR
355  * if out of two or more difficulties, the last one has been completed.
356  *
357  * - Use the bronze laurel (easiest) only if the first difficulty out of two
358  * or more has been completed.
359  *
360  * - Use the silver laurel otherwise.
361  */
362  if(!difficulties.empty() && did_complete_at(difficulties.back())) {
364  } else if(only_first_completed && did_complete_at(difficulties.front())) {
366  } else {
367  item["label"] = game_config::images::victory_laurel;
368  }
369 
370  data.emplace("victory", item);
371  }
372 
373  tree.add_node("campaign", data).set_id(campaign["id"]);
374 }
375 
377 {
378  tree_view& tree = find_widget<tree_view>(&window, "campaign_tree", false);
379 
380  if(tree.empty()) {
381  return;
382  }
383 
384  assert(tree.selected_item());
385  if(!tree.selected_item()->id().empty()) {
386  auto iter = std::find(page_ids_.begin(), page_ids_.end(), tree.selected_item()->id());
387  if(iter != page_ids_.end()) {
388  choice_ = std::distance(page_ids_.begin(), iter);
389  }
390  }
391 
392  deterministic_ = find_widget<toggle_button>(&window, "checkbox_deterministic", false).get_value_bool();
393 
395 }
396 
398 {
399  boost::dynamic_bitset<> new_mod_states =
400  find_widget<multimenu_button>(get_window(), "mods_menu", false).get_toggle_states();
401 
402  // Get a mask of any mods that were toggled, regardless of new state
403  mod_states_ = mod_states_ ^ new_mod_states;
404 
405  for(unsigned i = 0; i < mod_states_.size(); i++) {
406  if(mod_states_[i]) {
407  engine_.toggle_mod(i);
408  }
409  }
410 
411  // Save the full toggle states for next time
412  mod_states_ = new_mod_states;
413 }
414 
415 } // namespace dialogs
416 } // namespace gui2
void campaign_selected() const
Called when another campaign is selected.
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
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
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
void sort_campaigns(CAMPAIGN_ORDER order, bool ascending) const
tree_view_node * selected_item()
Definition: tree_view.hpp:95
window * get_window() const
Returns a pointer to the dialog&#39;s window.
std::string victory_laurel_easy
Simple push button.
std::pair< irdya_date, irdya_date > dates() const
std::vector< std::pair< const std::string *, const stats * > > levels
Stats (and name) for each scenario. The pointers are never nullptr.
Definition: statistics.hpp:137
Class for a single line text area.
Definition: text_box.hpp:121
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)
static UNUSEDNOWARN std::string _(const char *str)
Definition: gettext.hpp:100
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
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.
std::vector< std::string > & active_mods()
Various uncategorised dialogs.
std::vector< std::string > page_ids_
boost::iterator_range< const_child_iterator > const_child_itors
Definition: config.hpp:213
bool ci_search(const std::string &s1, const std::string &s2)
Definition: gettext.cpp:518
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)
virtual void pre_show(window &window) override
Inherited from modal_dialog.
void select_page(const unsigned page, const bool select=true)
Selectes a page.
Definition: multi_page.cpp:106
std::string name
Definition: sdl_ttf.cpp:70
std::map< std::string, t_string > string_map
Definition: widget.hpp:26
static int sort(lua_State *L)
Definition: ltablib.cpp:411
The multi page class.
Definition: multi_page.hpp:35
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:475
bool empty() const
Definition: tree_view.cpp:110
void set_modifications(const std::vector< std::string > &value, bool mp)
Definition: game.cpp:750
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
bool deterministic_
whether the player checked the "Deterministic" checkbox.
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:68
mock_char c
base class of top level items, the only item which needs to store the final canvases to draw on ...
Definition: window.hpp:62
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:384
std::vector< std::string > last_search_words_