The Battle for Wesnoth  1.15.0+dev
unit_recall.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2016 - 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 "font/text_formatting.hpp"
20 #include "gui/core/log.hpp"
22 #include "gui/dialogs/message.hpp"
23 #include "gui/widgets/listbox.hpp"
24 #include "gui/widgets/settings.hpp"
25 #include "gui/widgets/button.hpp"
26 #include "gui/widgets/image.hpp"
27 #include "gui/widgets/label.hpp"
28 #include "gui/widgets/text_box.hpp"
31 #include "gui/widgets/window.hpp"
32 #include "help/help.hpp"
33 #include "game_board.hpp"
34 #include "gettext.hpp"
35 #include "replay_helper.hpp"
36 #include "resources.hpp"
37 #include "synced_context.hpp"
38 #include "team.hpp"
39 #include "units/types.hpp"
40 #include "units/unit.hpp"
41 #include "units/ptr.hpp"
42 #include "utils/functional.hpp"
43 
44 #include <boost/dynamic_bitset.hpp>
45 
46 static lg::log_domain log_display("display");
47 #define LOG_DP LOG_STREAM(info, log_display)
48 
49 namespace gui2
50 {
51 namespace dialogs
52 {
53 
54 // Index 2 is by-level
57 
59 
61  : recall_list_(recall_list)
62  , team_(team)
63  , selected_index_()
64  , filter_options_()
65  , last_words_()
66 {
67 }
68 
69 template<typename T>
70 static void dump_recall_list_to_console(const T& units)
71 {
72  log_scope2(log_display, "dump_recall_list_to_console()")
73 
74  LOG_DP << "size: " << units.size() << "\n";
75 
76  std::size_t idx = 0;
77  for(const unit_const_ptr& u_ptr : units) {
78  LOG_DP << "\tunit[" << (idx++) << "]: " << u_ptr->id() << " name = '" << u_ptr->name() << "'\n";
79  }
80 }
81 
82 static std::string format_level_string(const int level)
83 {
84  std::string lvl = std::to_string(level);
85 
86  if(level < 1) {
87  return "<span color='#969696'>" + lvl + "</span>";
88  } else if(level == 1) {
89  return lvl;
90  } else if(level == 2) {
91  return "<b>" + lvl + "</b>";
92  } else {
93  return"<b><span color='#ffffff'>" + lvl + "</span></b>";
94  }
95 }
96 
97 static std::string format_cost_string(int unit_recall_cost, const int team_recall_cost)
98 {
99  std::stringstream str;
100 
101  if(unit_recall_cost < 0) {
102  unit_recall_cost = team_recall_cost;
103  }
104 
105  if(unit_recall_cost > team_recall_cost) {
106  str << "<span color='#ff0000'>" << unit_recall_cost << "</span>";
107  } else if(unit_recall_cost == team_recall_cost) {
108  str << unit_recall_cost;
109  } else if(unit_recall_cost < team_recall_cost) {
110  str << "<span color='#00ff00'>" << unit_recall_cost << "</span>";
111  }
112 
113  return str.str();
114 }
115 
116 static std::string get_title_suffix(int side_num)
117 {
118  if(!resources::gameboard) {
119  return "";
120  }
121 
122  unit_map& units = resources::gameboard->units();
123 
124  int controlled_recruiters = 0;
125  for(const auto& team : resources::gameboard->teams()) {
126  if(team.is_local_human() && !team.recruits().empty() && units.find_leader(team.side()) !=units.end()) {
127  ++controlled_recruiters;
128  }
129  }
130 
131  std::stringstream msg;
132  if(controlled_recruiters >= 2) {
134  if(leader != resources::gameboard->units().end() && !leader->name().empty()) {
135  msg << " (" << leader->name(); msg << ")";
136  }
137  }
138 
139  return msg.str();
140 }
141 
143 {
144  label& title = find_widget<label>(&window, "title", true);
145  title.set_label(title.get_label() + get_title_suffix(team_.side()));
146 
147  text_box* filter
148  = find_widget<text_box>(&window, "filter_box", false, true);
149 
151  std::bind(&unit_recall::filter_text_changed, this, _1, _2));
152 
153  listbox& list = find_widget<listbox>(&window, "recall_list", false);
154 
155  connect_signal_notify_modified(list, std::bind(&unit_recall::list_item_clicked, this, std::ref(window)));
156 
157  list.clear();
158 
159  window.keyboard_capture(filter);
160  window.add_to_keyboard_chain(&list);
161 
163  find_widget<button>(&window, "rename", false),
164  std::bind(&unit_recall::rename_unit, this, std::ref(window)));
165 
167  find_widget<button>(&window, "dismiss", false),
168  std::bind(&unit_recall::dismiss_unit, this, std::ref(window)));
169 
171  find_widget<button>(&window, "show_help", false),
172  std::bind(&unit_recall::show_help, this));
173 
174  for(const unit_const_ptr& unit : recall_list_) {
175  std::map<std::string, string_map> row_data;
176  string_map column;
177 
178  std::string mods = unit->image_mods();
179 
180  if(unit->can_recruit()) {
181  mods += "~BLIT(" + unit::leader_crown() + ")";
182  }
183 
184  for(const std::string& overlay : unit->overlays()) {
185  mods += "~BLIT(" + overlay + ")";
186  }
187 
188  column["use_markup"] = "true";
189 
190  column["label"] = unit->absolute_image() + mods;
191  row_data.emplace("unit_image", column);
192 
193  column["label"] = unit->type_name();
194  row_data.emplace("unit_type", column);
195 
196  column["label"] = format_cost_string(unit->recall_cost(), team_.recall_cost());
197  row_data.emplace("unit_recall_cost", column);
198 
199  const std::string& name = !unit->name().empty() ? unit->name().str() : font::unicode_en_dash;
200  column["label"] = name;
201  row_data.emplace("unit_name", column);
202 
203  column["label"] = format_level_string(unit->level());
204  row_data.emplace("unit_level", column);
205 
206  std::stringstream exp_str;
207  exp_str << font::span_color(unit->xp_color());
208  if(unit->can_advance()) {
209  exp_str << unit->experience() << "/" << unit->max_experience();
210  } else {
211  exp_str << font::unicode_en_dash;
212  }
213  exp_str << "</span>";
214 
215  column["label"] = exp_str.str();
216  row_data.emplace("unit_experience", column);
217 
218  // Since the table widgets use heavy formatting, we save a bare copy
219  // of certain options to filter on.
220  std::string filter_text = unit->type_name() + " " + name + " " + std::to_string(unit->level());
221 
222  std::string traits;
223  for(const std::string& trait : unit->trait_names()) {
224  traits += (traits.empty() ? "" : "\n") + trait;
225  filter_text += " " + trait;
226  }
227 
228  column["label"] = !traits.empty() ? traits : font::unicode_en_dash;
229  row_data.emplace("unit_traits", column);
230 
231  list.add_row(row_data);
232  filter_options_.push_back(filter_text);
233  }
234 
235  list.register_translatable_sorting_option(0, [this](const int i) { return recall_list_[i]->type_name().str(); });
236  list.register_translatable_sorting_option(1, [this](const int i) { return recall_list_[i]->name().str(); });
237  list.register_sorting_option(2, [this](const int i) {
238  const unit& u = *recall_list_[i];
239  return std::make_tuple(u.level(), -static_cast<int>(u.experience_to_advance()));
240  });
241  list.register_sorting_option(3, [this](const int i) { return recall_list_[i]->experience(); });
242  list.register_translatable_sorting_option(4, [this](const int i) {
243  return !recall_list_[i]->trait_names().empty() ? recall_list_[i]->trait_names().front().str() : "";
244  });
245 
246  list.set_active_sorting_option(sort_last.first >= 0 ? sort_last : sort_default, true);
247 
248  list_item_clicked(window);
249 }
250 
252 {
253  listbox& list = find_widget<listbox>(&window, "recall_list", false);
254 
255  const int index = list.get_selected_row();
256  unit& selected_unit = const_cast<unit&>(*recall_list_[index].get());
257 
258  std::string name = selected_unit.name();
259  const std::string dialog_title(_("Rename Unit"));
260  const std::string dialog_label(_("Name:"));
261 
262  if(gui2::dialogs::edit_text::execute(dialog_title, dialog_label, name)) {
263  selected_unit.rename(name);
264 
265  find_widget<label>(list.get_row_grid(index), "unit_name", false).set_label(name);
266 
267  filter_options_.erase(filter_options_.begin() + index);
268  std::ostringstream filter_text;
269  filter_text << selected_unit.type_name() << " " << name << " " << std::to_string(selected_unit.level());
270  for(const std::string& trait : selected_unit.trait_names()) {
271  filter_text << " " << trait;
272  }
273  filter_options_.insert(filter_options_.begin() + index, filter_text.str());
274 
275  list_item_clicked(window);
276  window.invalidate_layout();
277  }
278 }
279 
281 {
282  LOG_DP << "Recall list units:\n"; dump_recall_list_to_console(recall_list_);
283 
284  listbox& list = find_widget<listbox>(&window, "recall_list", false);
285  const int index = list.get_selected_row();
286 
287  const unit& u = *recall_list_[index].get();
288 
289  // If the unit is of level > 1, or is close to advancing, we warn the player about it
290  std::stringstream message;
291  if(u.loyal()) {
292  message << _("This unit is loyal and requires no upkeep.") << " " << (u.gender() == unit_race::MALE
293  ? _("Do you really want to dismiss him?")
294  : _("Do you really want to dismiss her?"));
295 
296  } else if(u.level() > 1) {
297  message << _("This unit is an experienced one, having advanced levels.") << " " << (u.gender() == unit_race::MALE
298  ? _("Do you really want to dismiss him?")
299  : _("Do you really want to dismiss her?"));
300 
301  } else if(u.experience() > u.max_experience()/2) {
302  message << _("This unit is close to advancing a level.") << " " << (u.gender() == unit_race::MALE
303  ? _("Do you really want to dismiss him?")
304  : _("Do you really want to dismiss her?"));
305  }
306 
307  if(!message.str().empty()) {
308  const int res = gui2::show_message(_("Dismiss Unit"), message.str(), message::yes_no_buttons);
309 
310  if(res != gui2::retval::OK) {
311  return;
312  }
313  }
314 
315  recall_list_.erase(recall_list_.begin() + index);
316 
317  // Remove the entry from the dialog list
318  list.remove_row(index);
319  list_item_clicked(window);
320 
321  // Remove the entry from the filter list
322  filter_options_.erase(filter_options_.begin() + index);
323  assert(filter_options_.size() == list.get_item_count());
324 
325  LOG_DP << "Dismissing a unit, side = " << u.side() << ", id = '" << u.id() << "'\n";
326  LOG_DP << "That side's recall list:\n";
328 
329  // Find the unit in the recall list.
330  unit_ptr dismissed_unit = team_.recall_list().find_if_matches_id(u.id());
331  assert(dismissed_unit);
332 
333  // Record the dismissal, then delete the unit.
334  synced_context::run_and_throw("disband", replay_helper::get_disband(dismissed_unit->id()));
335 
336  // Close the dialog if all units are dismissed
337  if(list.get_item_count() == 0) {
338  window.set_retval(retval::CANCEL);
339  }
340 }
341 
343 {
344  help::show_help("recruit_and_recall");
345 }
346 
348 {
349  const int selected_row
350  = find_widget<listbox>(&window, "recall_list", false).get_selected_row();
351 
352  if(selected_row == -1) {
353  return;
354  }
355 
356  const unit& selected_unit = *recall_list_[selected_row].get();
357 
358  find_widget<unit_preview_pane>(&window, "unit_details", false)
359  .set_displayed_unit(selected_unit);
360 
361  find_widget<button>(&window, "rename", false).set_active(!selected_unit.unrenamable());
362 }
363 
365 {
366  listbox& list = find_widget<listbox>(&window, "recall_list", false);
368 
369  if(get_retval() == retval::OK) {
371  }
372 }
373 
374 void unit_recall::filter_text_changed(text_box_base* textbox, const std::string& text)
375 {
376  window& window = *textbox->get_window();
377 
378  listbox& list = find_widget<listbox>(&window, "recall_list", false);
379 
380  const std::vector<std::string> words = utils::split(text, ' ');
381 
382  if(words == last_words_)
383  return;
384  last_words_ = words;
385 
386  boost::dynamic_bitset<> show_items;
387  show_items.resize(list.get_item_count(), true);
388 
389  if(!text.empty()) {
390  for(unsigned int i = 0; i < list.get_item_count(); i++) {
391  bool found = false;
392 
393  for(const auto & word : words) {
394  found = translation::ci_search(filter_options_[i], word);
395 
396  if(!found) {
397  // one word doesn't match, we don't reach words.end()
398  break;
399  }
400  }
401 
402  show_items[i] = found;
403  }
404  }
405 
406  list.set_row_shown(show_items);
407 }
408 
409 } // namespace dialogs
410 } // namespace gui2
const order_pair get_active_sorting_option()
Definition: listbox.cpp:659
Define the common log macros for the gui toolkit.
boost::intrusive_ptr< const unit > unit_const_ptr
Definition: ptr.hpp:30
void list_item_clicked(window &window)
Callbacks.
Dialog was closed with the CANCEL button.
Definition: retval.hpp:37
void set_active_sorting_option(const order_pair &sort_by, const bool select_first=false)
Sorts the listbox by a pre-set sorting option.
Definition: listbox.cpp:640
void set_text_changed_callback(std::function< void(text_box_base *textbox, const std::string text)> cb)
Set the text_changed callback.
void show_message(const std::string &title, const std::string &msg, const std::string &button_caption, const bool auto_close, const bool message_use_markup, const bool title_use_markup)
Shows a message to the user.
Definition: message.cpp:152
unit_iterator end()
Definition: map.hpp:415
void show_help(const std::string &show_topic, int xloc, int yloc)
Open the help browser, show topic with id show_topic.
Definition: help.cpp:116
Abstract base class for text items.
virtual const unit_map & units() const override
Definition: game_board.hpp:114
This class represents a single unit of a specific type.
Definition: unit.hpp:99
unit_iterator find_leader(int side)
Definition: map.cpp:329
Main class to show messages to the user.
Definition: message.hpp:34
unit_race::GENDER gender() const
The gender of this unit.
Definition: unit.hpp:419
void rename(const std::string &name)
Attempts to rename this unit&#39;s translatable display name, taking the &#39;unrenamable&#39; flag into account...
Definition: unit.hpp:387
virtual void post_show(window &window) override
Inherited from modal_dialog.
std::vector< unit_const_ptr > recalls_ptr_vector
Definition: unit_recall.hpp:37
This file contains the window object, this object is a top level container which has the event manage...
static listbox::order_pair sort_default
Definition: unit_recall.cpp:56
void register_translatable_sorting_option(const int col, translatable_sorter_func_t f)
Registers a special sorting function specifically for translatable values.
Definition: listbox.cpp:632
std::string absolute_image() const
The name of the file to game_display (used in menus).
Definition: unit.cpp:2349
static void msg(const char *act, debug_info &i, const char *to="", const char *result="")
Definition: debugger.cpp:109
Label showing a text.
Definition: label.hpp:32
int get_selected_row() const
Returns the first selected row.
Definition: listbox.cpp:272
-file sdl_utils.hpp
std::vector< std::string > split(const std::string &val, const char c, const int flags)
Splits a (comma-)separated string into a vector of pieces.
std::pair< int, SORT_ORDER > order_pair
Definition: listbox.hpp:288
static lg::log_domain log_display("display")
void filter_text_changed(text_box_base *textbox, const std::string &text)
Class for a single line text area.
Definition: text_box.hpp:121
Generic file dialog.
Definition: field-fwd.hpp:22
static listbox::order_pair sort_last
Definition: unit_recall.cpp:55
unsigned int experience_to_advance() const
The number of experience points this unit needs to level up, or 0 if current XP > max XP...
Definition: unit.hpp:495
virtual void set_label(const t_string &label)
The listbox class.
Definition: listbox.hpp:40
static const char * name(const std::vector< SDL_Joystick *> &joysticks, const std::size_t index)
Definition: joystick.cpp:48
static std::string format_level_string(const int level)
Definition: unit_list.cpp:54
std::string span_color(const color_t &color)
Returns a Pango formatting string using the provided color_t object.
This class stores all the data for a single &#39;side&#39; (in game nomenclature).
Definition: team.hpp:44
static UNUSEDNOWARN std::string _(const char *str)
Definition: gettext.hpp:91
static const std::string & leader_crown()
The path to the leader crown overlay.
Definition: unit.cpp:1051
const std::string & id() const
Gets this unit&#39;s id.
Definition: unit.hpp:343
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
unit_ptr find_if_matches_id(const std::string &unit_id)
Find a unit by id.
std::vector< std::string > filter_options_
Definition: unit_recall.hpp:54
This file contains the settings handling of the widget library.
void clear()
Removes all the rows in the listbox, clearing it.
Definition: listbox.cpp:125
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
recalls_ptr_vector & recall_list_
Definition: unit_recall.hpp:48
color_t xp_color() const
Color for this unit&#39;s XP.
Definition: unit.cpp:1109
game_board * gameboard
Definition: resources.cpp:20
const t_string & name() const
Gets this unit&#39;s translatable display name.
Definition: unit.hpp:366
int max_experience() const
The max number of experience points this unit can have.
Definition: unit.hpp:483
unsigned get_item_count() const
Returns the number of items in the listbox.
Definition: listbox.cpp:131
Shows a yes and no button.
Definition: message.hpp:79
int level() const
The current level of this unit.
Definition: unit.hpp:513
#define log_scope2(domain, description)
Definition: log.hpp:187
const std::vector< t_string > & trait_names() const
Gets the names of the currently registered traits.
Definition: unit.hpp:1028
const std::vector< std::string > & overlays() const
Get the unit&#39;s overlay images.
Definition: unit.hpp:1500
#define LOG_DP
Definition: unit_recall.cpp:47
const t_string & type_name() const
Gets the translatable name of this unit&#39;s type.
Definition: unit.hpp:332
static std::string format_cost_string(int unit_recall_cost, const int team_recall_cost)
Definition: unit_recall.cpp:97
Various uncategorised dialogs.
int recall_cost() const
Definition: team.hpp:193
bool loyal() const
Gets whether this unit is loyal - ie, it costs no upkeep.
Definition: unit.cpp:1602
virtual void pre_show(window &window) override
Inherited from modal_dialog.
bool ci_search(const std::string &s1, const std::string &s2)
Definition: gettext.cpp:518
std::size_t i
Definition: function.cpp:933
const std::string unicode_en_dash
Definition: constants.cpp:39
void rename_unit(window &window)
window * get_window()
Get the parent window.
Definition: widget.cpp:114
bool can_recruit() const
Whether this unit can recruit other units - ie, are they a leader unit.
Definition: unit.hpp:570
std::map< std::string, t_string > string_map
Definition: widget.hpp:24
grid & add_row(const string_map &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:66
static std::string get_title_suffix(int side_num)
std::size_t index(const std::string &str, const std::size_t index)
Codepoint index corresponding to the nth character in a UTF-8 string.
Definition: unicode.cpp:71
static void dump_recall_list_to_console(const T &units)
Definition: unit_recall.cpp:70
std::vector< std::string > last_words_
Definition: unit_recall.hpp:55
static config get_disband(const std::string &unit_id)
const grid * get_row_grid(const unsigned row) const
Returns the grid of the wanted row.
Definition: listbox.cpp:237
static bool run_and_throw(const std::string &commandname, const config &data, bool use_undo=true, bool show=true, synced_command::error_handler_function error_handler=default_error_function)
const t_string & get_label() const
bool empty() const
Definition: tstring.hpp:182
bool is_local_human() const
Definition: team.hpp:266
boost::intrusive_ptr< unit > unit_ptr
Definition: ptr.hpp:29
int experience() const
The current number of experience points this unit has.
Definition: unit.hpp:477
void dismiss_unit(window &window)
bool can_advance() const
Checks whether this unit has any options to advance to.
Definition: unit.hpp:231
recall_list_manager & recall_list()
Definition: team.hpp:215
void remove_row(const unsigned row, unsigned count=1)
Removes a row in the listbox.
Definition: listbox.cpp:86
Container associating units to locations.
Definition: map.hpp:99
int side() const
The side this unit belongs to.
Definition: unit.hpp:303
Dialog was closed with the OK button.
Definition: retval.hpp:34
int side() const
Definition: team.hpp:188
void register_sorting_option(const int col, const Func &f)
Definition: listbox.hpp:269
const std::string & str() const
Definition: tstring.hpp:186
base class of top level items, the only item which needs to store the final canvases to draw on ...
Definition: window.hpp:63
bool unrenamable() const
Whether this unit can be renamed.
Definition: unit.hpp:399
int recall_cost() const
How much gold it costs to recall this unit.
Definition: unit.hpp:597
void set_row_shown(const unsigned row, const bool shown)
Makes a row visible or invisible.
Definition: listbox.cpp:143
std::string image_mods() const
Gets an IPF string containing all IPF image mods.
Definition: unit.cpp:2533
const std::set< std::string > & recruits() const
Definition: team.hpp:223