The Battle for Wesnoth  1.19.14+dev
faction_select.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2016 - 2025
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 "formatter.hpp"
20 #include "gettext.hpp"
21 #include "gui/widgets/button.hpp"
22 #include "gui/widgets/drawing.hpp"
23 #include "gui/widgets/label.hpp"
24 #include "gui/widgets/listbox.hpp"
27 #include "gui/widgets/window.hpp"
28 #include "help/help.hpp"
29 #include "preferences/preferences.hpp" // for encountered_units
30 #include "units/types.hpp"
31 
32 #ifdef __cpp_impl_three_way_comparison
33 #include <compare>
34 #endif
35 #include <functional>
36 
37 namespace gui2::dialogs
38 {
39 namespace
40 {
41 /** Wrapper type to allow custom sorting for faction names. */
42 struct faction_sorter
43 {
44  const config* cfg;
45 };
46 
47 #ifdef __cpp_impl_three_way_comparison
48 
49 /** Must be defined in the same namespace for ADL reasons. */
50 auto operator<=>(const faction_sorter& lhs, const faction_sorter& rhs)
51 {
52  // Since some eras have multiple random options we can't just
53  // assume there is only one random faction on top of the list.
54  bool lhs_rand = (*lhs.cfg)["random_faction"].to_bool();
55  bool rhs_rand = (*rhs.cfg)["random_faction"].to_bool();
56 
57  // Group random factions together.
58  if(lhs_rand <=> rhs_rand != 0) {
59  return std::strong_ordering::greater;
60  }
61 
62  std::string lhs_name = (*lhs.cfg)["name"];
63  std::string rhs_name = (*rhs.cfg)["name"];
64 
65  // TODO C++20: define three-way comparison for t_string?
66  auto cmp = translation::compare(lhs_name, rhs_name);
67  if(cmp < 0) return std::strong_ordering::less;
68  if(cmp > 0) return std::strong_ordering::greater;
69 
70  return std::strong_ordering::equal;
71 }
72 
73 #else // TODO: remove block below as soon as humanly possible
74 
75 bool operator<(const faction_sorter& lhs, const faction_sorter& rhs)
76 {
77  bool lhs_rand = (*lhs.cfg)["random_faction"].to_bool();
78  bool rhs_rand = (*rhs.cfg)["random_faction"].to_bool();
79 
80  if(lhs_rand && !rhs_rand) return true;
81  if(!lhs_rand && rhs_rand) return false;
82 
83  return translation::compare((*lhs.cfg)["name"].str(), (*rhs.cfg)["name"].str()) < 0;
84 }
85 
86 bool operator>(const faction_sorter& lhs, const faction_sorter& rhs)
87 {
88  bool lhs_rand = (*lhs.cfg)["random_faction"].to_bool();
89  bool rhs_rand = (*rhs.cfg)["random_faction"].to_bool();
90 
91  if(lhs_rand && !rhs_rand) return false;
92  if(!lhs_rand && rhs_rand) return true;
93 
94  return translation::compare((*lhs.cfg)["name"].str(), (*rhs.cfg)["name"].str()) > 0;
95 }
96 
97 #endif
98 
99 } // namespace
100 
101 REGISTER_DIALOG(faction_select)
102 
103 faction_select::faction_select(ng::flg_manager& flg_manager, const std::string& color, const int side)
104  : modal_dialog(window_id())
105  , flg_manager_(flg_manager)
106  , tc_color_(color)
107  , side_(side)
108  , last_faction_(flg_manager.current_faction_index())
109  , last_leader_(flg_manager.current_leader_index())
110  , last_gender_(flg_manager.current_gender_index())
111 {
112 }
113 
115 {
116  find_widget<label>("starting_pos").set_label(std::to_string(side_));
117 
118  //
119  // Set up gender radio buttons
120  //
121  toggle_button& gender_rand = find_widget<toggle_button>("gender_random");
122  toggle_button& gender_male = find_widget<toggle_button>("gender_male");
123  toggle_button& gender_female = find_widget<toggle_button>("gender_female");
124 
125  gender_toggle_.add_member(&gender_rand, "random");
128 
130 
132  std::bind(&faction_select::on_gender_select, this, std::placeholders::_2));
133 
134  //
135  // Set up leader menu button
136  //
137  connect_signal_notify_modified(find_widget<menu_button>("leader_menu"),
138  std::bind(&faction_select::on_leader_select, this));
139 
140  // Leader's profile button
141  find_widget<button>("type_profile").connect_click_handler(
142  std::bind(&faction_select::profile_button_callback, this));
143 
144  //
145  // Set up faction list
146  //
147  listbox& list = find_widget<listbox>("faction_list");
148 
149  keyboard_capture(&list);
150 
152  std::bind(&faction_select::on_faction_select, this));
153 
154  for(const config *s : flg_manager_.choosable_factions()) {
155  const config& side = *s;
156 
157  // flag_rgb here is unrelated to any handling in the unit class
158  const std::string flag_rgb = !side["flag_rgb"].empty() ? side["flag_rgb"].str() : "magenta";
159 
160  list.add_row(widget_data{
161  { "faction_image", {
162  { "label", (formatter() << side["image"] << "~RC(" << flag_rgb << ">" << tc_color_ << ")").str() }
163  }},
164  { "faction_name", {
165  { "label", side["name"].str() }
166  }},
167  });
168  }
169 
171  list.set_sorters([this](std::size_t i) { return faction_sorter{flg_manager_.choosable_factions()[i]}; });
173 
175 }
176 
178 {
179  const int selected_row = find_widget<listbox>("faction_list").get_selected_row();
180 
181  if(selected_row == -1) {
182  return;
183  }
184 
185  // Since set_current_faction overrides the current leader, save a copy of the previous leader index so the
186  // leader dropdown can be set to the appropriate initial selection.
187  const int previous_leader_selection = flg_manager_.current_leader_index();
188 
189  flg_manager_.set_current_faction(selected_row);
190 
191  std::vector<config> leaders;
192 
193  for(const std::string& leader : flg_manager_.choosable_leaders()) {
194  const unit_type* unit = unit_types.find(leader);
195 
196  if(unit) {
197  const std::string icon = formatter() << unit->image() << "~RC(" << unit->flag_rgb() << ">" << tc_color_ << ")";
198  leaders.emplace_back("label", unit->type_name(), "icon", icon);
199  } else if(leader == "random") {
200  leaders.emplace_back("label", _("Random"), "icon", ng::random_enemy_picture);
201  } else if(leader == "null") {
202  leaders.emplace_back("label", font::unicode_em_dash);
203  } else {
204  leaders.emplace_back("label", "?");
205  }
206  }
207 
208  menu_button& leader_dropdown = find_widget<menu_button>("leader_menu");
209 
210  leader_dropdown.set_values(leaders, std::min<int>(leaders.size() - 1, previous_leader_selection));
211  leader_dropdown.set_active(leaders.size() > 1 && !flg_manager_.is_saved_game());
212 
214 
215  // Print recruits
216  std::vector<t_string> recruit_names;
217  for(const auto& recruit : utils::split(flg_manager_.current_faction()["recruit"])) {
218  if(const unit_type* rt = unit_types.find(recruit)) {
219  recruit_names.push_back(rt->type_name());
220  }
221  }
222 
223  std::sort(recruit_names.begin(), recruit_names.end(), [](const std::string& s1, const std::string& s2) {
224  return translation::compare(s1, s2) < 0;
225  });
226 
227  find_widget<styled_widget>("recruits").set_label(utils::bullet_list(recruit_names, 0));
228 }
229 
231 {
232  flg_manager_.set_current_leader(find_widget<menu_button>("leader_menu").get_value());
233 
234  // TODO: should we decouple this from the flg manager and instead just check the unit type directly?
235  // If that's done so, we'd need to come up with a different check for Random availability.
236  gender_toggle_.set_members_enabled([this](const std::string& gender)->bool {
237  const std::vector<std::string>& genders = flg_manager_.choosable_genders();
238  return std::find(genders.begin(), genders.end(), gender) != genders.end();
239  });
240 
242 
243  // Disable the profile button if leader_type is dash or "Random"
244  button& profile_button = find_widget<button>("type_profile");
245  profile_button.set_active(unit_types.find(flg_manager_.current_leader()) != nullptr);
246 }
247 
249 {
251  prefs::get().encountered_units().insert(ut->id());
253  }
254 }
255 
256 void faction_select::on_gender_select(const std::string& val)
257 {
259 
261 }
262 
264 {
265  std::string leader_image = ng::random_enemy_picture;
266 
269  leader_image = formatter() << utg.image() << "~RC(" << utg.flag_rgb() << ">" << tc_color_ << ")";
270  }
271 
272  find_widget<drawing>("leader_image").set_label(leader_image);
273 }
274 
276 {
277  //
278  // If we're canceling, restore the previous selections. It might be worth looking
279  // into only making selections at all here in post_show, but that would require a
280  // refactor of the flg_manager class.
281  //
282  // Also, note it's important to set these in the order of faction -> leader -> gender
283  // or the saved indices will be invalid!
284  //
285  // -- vultraz, 2018-06-16
286  //
287  if(get_retval() != retval::OK) {
291  }
292 }
293 
294 } // namespace dialogs
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:158
bool empty() const
Definition: config.cpp:839
std::ostringstream wrapper.
Definition: formatter.hpp:40
Simple push button.
Definition: button.hpp:36
virtual void set_active(const bool active) override
See styled_widget::set_active.
Definition: button.cpp:64
virtual void post_show() override
Actions to be taken after the window has been shown.
virtual void pre_show() override
Actions to be taken before showing the window.
group< std::string > gender_toggle_
void on_gender_select(const std::string &val)
Abstract base class for all modal dialogs.
void on_modified(const Func &func)
Sets a common callback function for all members.
Definition: group.hpp:121
void add_member(selectable_item *w, const T &value)
Adds a widget/value pair to the group map.
Definition: group.hpp:41
void set_members_enabled(const Func &predicate)
Wrapper for enabling or disabling member widgets.
Definition: group.hpp:151
void set_member_states(const T &value)
Sets the toggle values for all widgets besides the one associated with the specified value to false.
Definition: group.hpp:110
The listbox class.
Definition: listbox.hpp:41
grid & add_row(const widget_item &item, const int index=-1)
When an item in the list is selected by the user we need to update the state.
Definition: listbox.cpp:92
void set_active_sorter(std::string_view id, sort_order::type order, bool select_first=false)
Sorts the listbox by a pre-set sorting option.
Definition: listbox.cpp:598
void set_sorters(Args &&... functors)
Registers sorting controls using magic index IDs.
Definition: listbox.hpp:306
bool select_row(const unsigned row, const bool select=true)
Selects a row.
Definition: listbox.cpp:267
void set_values(const std::vector<::config > &values, unsigned selected=0)
virtual void set_active(const bool active) override
See styled_widget::set_active.
Definition: menu_button.cpp:74
void keyboard_capture(widget *widget)
Definition: window.cpp:1201
int get_retval()
Definition: window.hpp:402
const std::vector< const config * > & choosable_factions() const
Definition: flg_manager.hpp:74
const std::string & current_gender() const
Definition: flg_manager.hpp:84
void set_current_faction(const unsigned index)
const era_metadata & era_info() const
Definition: flg_manager.hpp:96
const std::vector< std::string > & choosable_genders() const
Definition: flg_manager.hpp:78
bool is_saved_game() const
Definition: flg_manager.hpp:65
int current_leader_index() const
Definition: flg_manager.hpp:89
const std::vector< std::string > & choosable_leaders() const
Definition: flg_manager.hpp:76
void set_current_leader(const unsigned index)
int current_faction_index() const
void set_current_gender(const unsigned index)
const config & current_faction() const
Definition: flg_manager.hpp:80
const std::string & current_leader() const
Definition: flg_manager.hpp:82
std::set< std::string > & encountered_units()
static prefs & get()
static const std::string s_female
Standard string id (not translatable) for FEMALE.
Definition: race.hpp:29
static const std::string s_male
Standard string id (not translatable) for MALE.
Definition: race.hpp:30
const unit_type * find(const std::string &key, unit_type::BUILD_STATUS status=unit_type::FULL) const
Finds a unit_type by its id() and makes sure it is built to the specified level.
Definition: types.cpp:1224
A single unit type that the player may recruit.
Definition: types.hpp:43
const unit_type & get_gender_unit_type(const std::string &gender) const
Returns a gendered variant of this unit_type.
Definition: types.cpp:410
const std::string & image() const
Definition: types.hpp:179
const std::string & flag_rgb() const
Definition: types.cpp:677
This class represents a single unit of a specific type.
Definition: unit.hpp:132
const config * cfg
std::size_t i
Definition: function.cpp:1032
static bool operator<(const placing_info &a, const placing_info &b)
Definition: game_state.cpp:106
bool operator>(const version_info &l, const version_info &r)
Greater-than operator for version_info.
static std::string _(const char *str)
Definition: gettext.hpp:97
const t_string & type_name() const
Gets the translatable name of this unit's type.
Definition: unit.hpp:368
const std::string & flag_rgb() const
Get the source color palette to use when recoloring the unit's image.
Definition: unit.cpp:1182
This file contains the window object, this object is a top level container which has the event manage...
const std::string unicode_em_dash
Definition: constants.cpp:44
std::string flag_rgb
REGISTER_DIALOG(editor_edit_unit)
void connect_signal_notify_modified(dispatcher &dispatcher, const signal_notification &signal)
Connects a signal handler for getting a notification upon modification.
Definition: dispatcher.cpp:189
std::map< std::string, widget_item > widget_data
Definition: widget.hpp:36
@ OK
Dialog was closed with the OK button.
Definition: retval.hpp:35
void show_unit_description(const unit &u)
Definition: help.cpp:96
const std::string random_enemy_picture("units/random-dice.png")
int compare(const std::string &s1, const std::string &s2)
Case-sensitive lexicographical comparison.
Definition: gettext.cpp:502
std::string bullet_list(const T &v, std::size_t indent=4, const std::string &bullet=font::unicode_bullet)
Generates a new string containing a bullet list.
std::vector< std::string > split(const config_attribute_value &val)
auto * find(Container &container, const Value &value)
Convenience wrapper for using find on a container without needing to comare to end()
Definition: general.hpp:141
sort_order::type faction_sort_order
The order to display factions when a player selects their leader.
Definition: flg_manager.hpp:38
static map_location::direction s
unit_type_data unit_types
Definition: types.cpp:1463