The Battle for Wesnoth  1.15.0-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"
29 #include "gui/widgets/window.hpp"
30 #include "lexical_cast.hpp"
31 #include "preferences/game.hpp"
32 
33 #include "utils/functional.hpp"
34 #include "utils/irdya_datetime.hpp"
35 
36 namespace gui2
37 {
38 namespace dialogs
39 {
40 /*WIKI
41  * @page = GUIWindowDefinitionWML
42  * @order = 2_campaign_selection
43  *
44  * == Campaign selection ==
45  *
46  * This shows the dialog which allows the user to choose which campaign to
47  * play.
48  *
49  * @begin{table}{dialog_widgets}
50  *
51  * campaign_tree & & tree_view & m &
52  * A tree_view that contains all available campaigns. $
53  *
54  * -icon & & image & o &
55  * The icon for the campaign. $
56  *
57  * -name & & styled_widget & o &
58  * The name of the campaign. $
59  *
60  * -victory & & image & o &
61  * The icon to show when the user finished the campaign. The engine
62  * determines whether or not the user has finished the campaign and
63  * sets the visible flag for the widget accordingly. $
64  *
65  * campaign_details & & multi_page & m &
66  * A multi page widget that shows more details for the selected
67  * campaign. $
68  *
69  * -image & & image & o &
70  * The image for the campaign. $
71  *
72  * -description & & styled_widget & o &
73  * The description of the campaign. $
74  *
75  * @end{table}
76  */
77 
78 REGISTER_DIALOG(campaign_selection)
79 
80 void campaign_selection::campaign_selected(window& window)
81 {
82  tree_view& tree = find_widget<tree_view>(&window, "campaign_tree", false);
83  if(tree.empty()) {
84  return;
85  }
86 
87  assert(tree.selected_item());
88 
89  if(!tree.selected_item()->id().empty()) {
90  auto iter = std::find(page_ids_.begin(), page_ids_.end(), tree.selected_item()->id());
91 
92  const int choice = std::distance(page_ids_.begin(), iter);
93  if(iter == page_ids_.end()) {
94  return;
95  }
96 
97  multi_page& pages = find_widget<multi_page>(&window, "campaign_details", false);
98  pages.select_page(choice);
99 
100  engine_.set_current_level(choice);
101  }
102 }
103 
105 {
106  using level_ptr = ng::create_engine::level_ptr;
107 
108  auto levels = engine_.get_levels_by_type_unfiltered(ng::level::TYPE::SP_CAMPAIGN);
109 
110  switch(order) {
111  case RANK: // Already sorted by rank
112  // This'll actually never happen, but who knows if that'll ever change...
113  if(!ascending) {
114  std::reverse(levels.begin(), levels.end());
115  }
116 
117  break;
118 
119  case DATE:
120  std::sort(levels.begin(), levels.end(), [ascending](const level_ptr& a, const level_ptr& b) {
121  auto cpn_a = std::dynamic_pointer_cast<ng::campaign>(a);
122  auto cpn_b = std::dynamic_pointer_cast<ng::campaign>(b);
123 
124  if(cpn_b == nullptr) {
125  return cpn_a != nullptr;
126  }
127 
128  if(cpn_a == nullptr) {
129  return false;
130  }
131 
132  return ascending
133  ? cpn_a->dates().first < cpn_b->dates().first
134  : cpn_a->dates().first > cpn_b->dates().first;
135  });
136 
137  break;
138 
139  case NAME:
140  std::sort(levels.begin(), levels.end(), [ascending](const level_ptr& a, const level_ptr& b) {
141  const int cmp = translation::icompare(a->name(), b->name());
142  return ascending ? cmp < 0 : cmp > 0;
143  });
144 
145  break;
146  }
147 
148  tree_view& tree = find_widget<tree_view>(&window, "campaign_tree", false);
149 
150  // Remember which campaign was selected...
151  std::string was_selected = tree.selected_item()->id();
152 
153  tree.clear();
154 
155  for(const auto& level : levels) {
156  add_campaign_to_tree(window, level->data());
157  }
158 
159  if(!was_selected.empty()) {
160  find_widget<tree_view_node>(&window, was_selected, false).select_node();
161  }
162 }
163 
165 {
166  static bool force = false;
167  if(force) {
168  return;
169  }
170 
171  if(current_sorting_ == order) {
173  currently_sorted_asc_ = false;
174  } else {
175  currently_sorted_asc_ = true;
177  }
178  } else if(current_sorting_ == RANK) {
179  currently_sorted_asc_ = true;
180  current_sorting_ = order;
181  } else {
182  currently_sorted_asc_ = true;
183  current_sorting_ = order;
184 
185  force = true;
186 
187  if(order == NAME) {
188  find_widget<toggle_button>(&window, "sort_time", false).set_value(0);
189  } else if(order == DATE) {
190  find_widget<toggle_button>(&window, "sort_name", false).set_value(0);
191  }
192 
193  force = false;
194  }
195 
197 }
198 
200 {
201  /***** Setup campaign tree. *****/
202  tree_view& tree = find_widget<tree_view>(&window, "campaign_tree", false);
203 
205  std::bind(&campaign_selection::campaign_selected, this, std::ref(window)));
206 
207  toggle_button& sort_name = find_widget<toggle_button>(&window, "sort_name", false);
208  toggle_button& sort_time = find_widget<toggle_button>(&window, "sort_time", false);
209 
211  std::bind(&campaign_selection::toggle_sorting_selection, this, std::ref(window), NAME));
212 
214  std::bind(&campaign_selection::toggle_sorting_selection, this, std::ref(window), DATE));
215 
216  window.keyboard_capture(&tree);
217 
218  /***** Setup campaign details. *****/
219  multi_page& pages = find_widget<multi_page>(&window, "campaign_details", false);
220 
221  for(const auto& level : engine_.get_levels_by_type_unfiltered(ng::level::TYPE::SP_CAMPAIGN)) {
222  const config& campaign = level->data();
223 
224  /*** Add tree item ***/
225  add_campaign_to_tree(window, campaign);
226 
227  /*** Add detail item ***/
228  widget_data data;
229  widget_item item;
230 
231  item["label"] = campaign["description"];
232  item["use_markup"] = "true";
233 
234  if(!campaign["description_alignment"].empty()) {
235  item["text_alignment"] = campaign["description_alignment"];
236  }
237 
238  data.emplace("description", item);
239 
240  item["label"] = campaign["image"];
241  data.emplace("image", item);
242 
243  pages.add_page(data);
244  page_ids_.push_back(campaign["id"]);
245  }
246 
247  //
248  // Set up Mods selection dropdown
249  //
250  multimenu_button& mods_menu = find_widget<multimenu_button>(&window, "mods_menu", false);
251 
253  std::vector<config> mod_menu_values;
254  std::vector<std::string> enabled = engine_.active_mods();
255 
257  const bool active = std::find(enabled.begin(), enabled.end(), mod->id) != enabled.end();
258 
259  mod_menu_values.emplace_back("label", mod->name, "checkbox", active);
260 
261  mod_states_.push_back(active);
262  }
263 
264  mods_menu.set_values(mod_menu_values);
265  mods_menu.select_options(mod_states_);
266 
267  connect_signal_notify_modified(mods_menu, std::bind(&campaign_selection::mod_toggled, this, std::ref(window)));
268  } else {
269  mods_menu.set_active(false);
270  mods_menu.set_label(_("active_modifications^None"));
271  }
272 
273  campaign_selected(window);
274 }
275 
277 {
278  tree_view& tree = find_widget<tree_view>(&window, "campaign_tree", false);
279  widget_data data;
280  widget_item item;
281 
282  item["label"] = campaign["icon"];
283  data.emplace("icon", item);
284 
285  item["label"] = campaign["name"];
286  data.emplace("name", item);
287 
288  // We completed the campaign! Calculate the appropriate victory laurel.
289  if(campaign["completed"].to_bool()) {
290  config::const_child_itors difficulties = campaign.child_range("difficulty");
291 
292  auto did_complete_at = [](const config& c) { return c["completed_at"].to_bool(); };
293 
294  // Check for non-completion on every difficulty save the first.
295  const bool only_first_completed = difficulties.size() > 1 &&
296  std::none_of(difficulties.begin() + 1, difficulties.end(), did_complete_at);
297 
298  /*
299  * Criteria:
300  *
301  * - Use the gold laurel (hardest) for campaigns with only one difficulty OR
302  * if out of two or more difficulties, the last one has been completed.
303  *
304  * - Use the bronze laurel (easiest) only if the first difficulty out of two
305  * or more has been completed.
306  *
307  * - Use the silver laurel otherwise.
308  */
309  if(!difficulties.empty() && did_complete_at(difficulties.back())) {
311  } else if(only_first_completed && did_complete_at(difficulties.front())) {
313  } else {
314  item["label"] = game_config::images::victory_laurel;
315  }
316 
317  data.emplace("victory", item);
318  }
319 
320  tree.add_node("campaign", data).set_id(campaign["id"]);
321 }
322 
324 {
325  tree_view& tree = find_widget<tree_view>(&window, "campaign_tree", false);
326 
327  if(tree.empty()) {
328  return;
329  }
330 
331  assert(tree.selected_item());
332  if(!tree.selected_item()->id().empty()) {
333  auto iter = std::find(page_ids_.begin(), page_ids_.end(), tree.selected_item()->id());
334  if(iter != page_ids_.end()) {
335  choice_ = std::distance(page_ids_.begin(), iter);
336  }
337  }
338 
339  deterministic_ = find_widget<toggle_button>(&window, "checkbox_deterministic", false).get_value_bool();
340 
342 }
343 
345 {
346  boost::dynamic_bitset<> new_mod_states =
347  find_widget<multimenu_button>(&window, "mods_menu", false).get_toggle_states();
348 
349  // Get a mask of any mods that were toggled, regardless of new state
350  mod_states_ = mod_states_ ^ new_mod_states;
351 
352  for(unsigned i = 0; i < mod_states_.size(); i++) {
353  if(mod_states_[i]) {
354  engine_.toggle_mod(i);
355  }
356  }
357 
358  // Save the full toggle states for next time
359  mod_states_ = new_mod_states;
360 }
361 
362 } // namespace dialogs
363 } // namespace gui2
void campaign_selected(window &window)
Called when another campaign is selected.
std::string victory_laurel
std::shared_ptr< level > level_ptr
tree_view_node & add_node(const std::string &id, const widget_data &data, const int index=-1)
Definition: tree_view.cpp:55
New lexcical_cast header.
#define a
const std::string & id() const
Definition: widget.cpp:106
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:366
tree_view_node * selected_item()
Definition: tree_view.hpp:90
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:114
Generic file dialog.
Definition: field-fwd.hpp:22
void toggle_sorting_selection(window &window, CAMPAIGN_ORDER order)
#define b
virtual void set_label(const t_string &label)
static UNUSEDNOWARN std::string _(const char *str)
Definition: gettext.hpp:89
bool toggle_mod(int index, bool force=false)
std::map< std::string, t_string > widget_item
Definition: widget.hpp:27
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:178
std::string victory_laurel_hardest
std::vector< level_ptr > get_levels_by_type_unfiltered(level::TYPE type) const
virtual void post_show(window &window) override
Inherited from modal_dialog.
void sort_campaigns(window &window, CAMPAIGN_ORDER order, bool ascending)
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:210
virtual void set_active(const bool active) override
See styled_widget::set_active.
std::size_t i
Definition: function.cpp:933
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:105
grid & add_page(const widget_item &item)
Adds single page to the grid.
Definition: multi_page.cpp:42
static int sort(lua_State *L)
Definition: ltablib.cpp:411
The multi page class.
Definition: multi_page.hpp:35
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:98
void set_modifications(const std::vector< std::string > &value, bool mp)
Definition: game.cpp:734
void set_id(const std::string &id)
Definition: widget.cpp:94
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
std::map< std::string, widget_item > widget_data
Definition: widget.hpp:30
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.
void add_campaign_to_tree(window &window, const config &campaign)