The Battle for Wesnoth 1.21.0-dev
 
Loading...
Searching...
No Matches
units_dialog.cpp
Go to the documentation of this file.
1/*
2 Copyright (C) 2024 - 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
20#include "formatter.hpp"
21#include "game_board.hpp"
22#include "gettext.hpp"
28#include "gui/widgets/label.hpp"
34#include "help/help.hpp"
35#include "log.hpp"
36#include "replay_helper.hpp"
37#include "play_controller.hpp"
38#include "resources.hpp"
40#include "synced_context.hpp"
41#include "team.hpp"
42#include "units/helper.hpp"
43#include "units/unit.hpp"
44#include "units/ptr.hpp"
45#include "units/types.hpp"
46#include "utils/ci_searcher.hpp"
48
49#include <functional>
50#include <string>
51
52static lg::log_domain log_display("display");
53#define LOG_DP LOG_STREAM(info, log_display)
54
55namespace gui2::dialogs
56{
57
58namespace
59{
60// Index 2 is by-level
61std::pair sort_default{ std::string{"unit_name"}, sort_order::type::ascending };
62utils::optional<decltype(sort_default)> sort_last;
63
64std::string star(bool starred)
65{
66 // Filled/unfilled five-pointed stars, used as favorite unit marker
67 return starred ? "\u2605" : "\u2606";
68}
69
70}
71
72REGISTER_DIALOG(units_dialog)
73
75 : modal_dialog(window_id())
76 , selected_index_(-1)
77 , num_rows_(0)
78 , ok_label_(_("OK"))
79 , cancel_label_(_("Cancel"))
80 , show_rename_(false)
81 , show_dismiss_(false)
82 , show_mark_favorite_(false)
83 , show_variations_(false)
84 , sort_order_(sort_default)
85 , gender_(unit_race::GENDER::MALE)
86 , variation_()
87 , filter_options_()
88 , gender_toggle_()
89{
90 for(widget* sort_toggle : find_widget<grid>("_header_grid")) {
91 // FIXME: only proper (non-spacer) header options are currently given an ID.
92 // We want the spacers to remain for sizing, but this is pretty fragile.
93 if(!sort_toggle->id().empty()) {
95 }
96 }
97}
98
99namespace {
100
101template<typename T>
102void dump_recall_list_to_console(const T& units)
103{
104 log_scope2(log_display, "dump_recall_list_to_console()")
105
106 LOG_DP << "size: " << units.size();
107
108 std::size_t idx = 0;
109 for(const auto& u_ptr : units) {
110 LOG_DP << "\tunit[" << (idx++) << "]: " << u_ptr->id() << " name = '" << u_ptr->name() << "'";
111 }
112}
113
114std::string get_title_suffix(int side_num)
115{
117 return "";
118 }
119
121
122 int controlled_recruiters = 0;
123 for(const auto& team : resources::gameboard->teams()) {
124 if(team.is_local_human() && !team.recruits().empty() && units.find_leader(team.side()) !=units.end()) {
125 ++controlled_recruiters;
126 }
127 }
128
129 std::stringstream msg;
130 if(controlled_recruiters >= 2) {
132 if(leader != resources::gameboard->units().end() && !leader->name().empty()) {
133 msg << " (" << leader->name() << ")";
134 }
135 }
136
137 return msg.str();
138}
139}
140
142{
143 text_box& filter = find_widget<text_box>("filter_box");
144 filter.on_modified([this](const auto& box) { filter_text_changed(box.text()); });
145
146 listbox& list = find_widget<listbox>("main_list");
147 connect_signal_notify_modified(list, std::bind(&units_dialog::list_item_clicked, this));
148
149 // Prevent close via OK (including keyboard Enter) if no units are available after filter.
150 set_exit_hook(window::exit_hook::ok_only, [&list] { return list.get_selected_row() != -1; });
151
152 connect_signal_mouse_left_click(
153 find_widget<button>("show_help"),
154 std::bind(&units_dialog::show_help, this));
155
156#ifdef __IPHONEOS__
157 // On iOS, opening these dialogs should not immediately summon the software
158 // keyboard just because the optional filter box exists.
159 keyboard_capture(&list);
160#else
161 keyboard_capture(&filter);
162#endif
164
165 show_list(list);
166
167 find_widget<label>("title").set_label(title_);
168 find_widget<button>("ok").set_label(ok_label_);
169 find_widget<button>("cancel").set_label(cancel_label_);
170 find_widget<button>("dismiss").set_visible(show_dismiss_);
171 find_widget<button>("rename").set_visible(show_rename_);
172 find_widget<button>("mark_favorite").set_visible(show_mark_favorite_);
173 find_widget<grid>("variation_gender_grid").set_visible(show_variations_);
174
175 // Gender and variation selectors
176 if(show_variations_) {
177 connect_signal_notify_modified(
178 find_widget<menu_button>("variation_box"), [this](auto&&...) { update_variation(); });
179
180 auto& group = get_toggle();
181 group.add_member(find_widget<toggle_button>("male_toggle", true, true), unit_race::MALE);
182 group.add_member(find_widget<toggle_button>("female_toggle", true, true), unit_race::FEMALE);
183
186 [this](widget&, const unit_race::GENDER& gender) { update_gender(gender); });
187 }
188
190}
191
193{
194 if (num_rows_ == 0) {
195 return;
196 }
197
198 for(std::size_t i = 0; i < num_rows_; i++) {
199 widget_data row_data;
200
201 // If custom filter text generator exists, use it to generate the filter text
202 if(filter_gen_) {
203 filter_options_.emplace_back(filter_gen_(i));
204 } else {
205 filter_options_.emplace_back();
206 }
207
208 for(const auto& [id, generator] : column_generators_) {
209 auto result = std::invoke(generator, i);
210
211 // Register labels to be filtered upon provided no filter generator exists
212 if(!filter_gen_ && id != "unit_image") {
213 filter_options_.back().push_back(result);
214 }
215
216 row_data.emplace(id, widget_item{{ "use_markup", "true" }, { "label", std::move(result) }});
217 }
218
219 grid& row_grid = list.add_row(row_data);
220
221 // Generate tooltip for ith row
222 if(tooltip_gen_) {
223 row_grid.find_widget<styled_widget>("row_panel").set_tooltip(tooltip_gen_(i));
224 }
225 }
226
227 const auto [sorter_id, order] = sort_last.value_or(sort_order_);
228 list.set_active_sorter(sorter_id, order, true);
229 if (is_selected()) { // In this case, is an entry preselected by caller?
231 fire(event::NOTIFY_MODIFIED, *this, nullptr);
232 }
233}
234
235void units_dialog::rename_unit(std::vector<unit_const_ptr>& unit_list)
236{
237 listbox& list = find_widget<listbox>("main_list");
238
240 if (selected_index_ == -1) {
241 return;
242 }
243
244 unit& selected_unit = const_cast<unit&>(*unit_list[selected_index_]);
245
246 std::string name = selected_unit.name();
247
248 if(gui2::dialogs::edit_text::execute(_("Rename Unit"), _("Name:"), name)) {
249 selected_unit.rename(name);
250
251 list.get_row_grid(selected_index_)->find_widget<label>("unit_name").set_label(name);
252
255
258 }
259}
260
261void units_dialog::dismiss_unit(std::vector<unit_const_ptr>& unit_list, const team& team)
262{
263 LOG_DP << "Recall list units:"; dump_recall_list_to_console(unit_list);
264
265 listbox& list = find_widget<listbox>("main_list");
267 if (selected_index_ == -1) {
268 return;
269 }
270
271 const unit& u = *unit_list[selected_index_].get();
272
273 if(!u.dismissable()) {
275 return;
276 }
277
278 // If the unit is of level > 1, or is close to advancing, we warn the player about it
279 std::stringstream message;
280 if(u.loyal()) {
281 message << _("This unit requires no upkeep.") << " " << (u.gender() == unit_race::MALE
282 ? _("Do you really want to dismiss him?")
283 : _("Do you really want to dismiss her?"));
284
285 } else if(u.level() > 1) {
286 message << _("This unit is an experienced one, having advanced levels.") << " " << (u.gender() == unit_race::MALE
287 ? _("Do you really want to dismiss him?")
288 : _("Do you really want to dismiss her?"));
289
290 } else if(u.experience() > u.max_experience()/2) {
291 message << _("This unit is close to advancing a level.") << " " << (u.gender() == unit_race::MALE
292 ? _("Do you really want to dismiss him?")
293 : _("Do you really want to dismiss her?"));
294 }
295
296 if(!message.str().empty()) {
297 const int res = gui2::show_message(_("Dismiss Unit"), message.str(), message::yes_no_buttons);
298 if(res != gui2::retval::OK) {
299 return;
300 }
301 }
302
303 unit_list.erase(unit_list.begin() + selected_index_);
304
305 // Remove the entry from the filter list
307
308 // Remove the entry from the dialog list
310
311 // This line can change selected_index_, so the erasing needs to be done before it
313
314 assert(filter_options_.size() == list.get_item_count());
315
316 LOG_DP << "Dismissing a unit, side = " << u.side() << ", id = '" << u.id() << "'";
317 LOG_DP << "That side's recall list:";
318 dump_recall_list_to_console(team.recall_list());
319
320 // Find the unit in the recall list.
321 unit_const_ptr dismissed_unit = team.recall_list().find_if_matches_id(u.id());
322 assert(dismissed_unit);
323
324 // Record the dismissal, then delete the unit.
325 synced_context::run_and_throw("disband", replay_helper::get_disband(dismissed_unit->id()));
326
327 // Close the dialog if all units are dismissed
328 if(list.get_item_count() == 0) {
330 }
331}
332
333void units_dialog::toggle_favorite(std::vector<unit_const_ptr>& unit_list)
334{
335 listbox& list = find_widget<listbox>("main_list");
336
338 if (selected_index_ == -1) {
339 return;
340 }
341
342 unit& selected_unit = const_cast<unit&>(*unit_list[selected_index_]);
343 selected_unit.set_favorite(!selected_unit.favorite());
344
345 list.get_row_grid(selected_index_)->find_widget<label>("unit_favorite")
346 .set_label(star(selected_unit.favorite()));
347
350}
351
353{
354 if (!topic_id_.empty()) {
356 }
357}
358
360{
361 selected_index_ = find_widget<listbox>("main_list").get_selected_row();
362
363 if (selected_index_ == -1) {
364 return;
365 }
366
367 fire(event::NOTIFY_MODIFIED, *this, nullptr);
368}
369
371{
372 listbox& list = find_widget<listbox>("main_list");
373 if(const auto [sorter, order] = list.get_active_sorter(); sorter) {
374 sort_last.emplace(sorter->id(), order);
375 } else {
376 sort_last.reset();
377 }
378
380}
381
382void units_dialog::filter_text_changed(const std::string& text)
383{
384 const std::size_t shown = find_widget<listbox>("main_list")
385 .filter_rows_by([this, match = translation::make_ci_matcher(text)](std::size_t row) {
386 return match(filter_options_[row]);
387 });
388
389 // Disable rename, dismiss, and OK buttons if no units are shown
390 find_widget<button>("rename").set_active(shown > 0);
391 find_widget<button>("dismiss").set_active(shown > 0);
392 find_widget<button>("ok").set_active(shown > 0);
393}
394
396{
397 gender_ = val;
398
399 selected_index_ = find_widget<listbox>("main_list").get_selected_row();
400 if(selected_index_ == -1) {
401 return;
402 }
403
404 fire(event::NOTIFY_MODIFIED, *this, nullptr);
405}
406
408{
409 variation_ = find_widget<menu_button>("variation_box").get_value_config()["variation_id"].str();
410
411 selected_index_ = find_widget<listbox>("main_list").get_selected_row();
412 if(selected_index_ == -1) {
413 return;
414 }
415
416 fire(event::NOTIFY_MODIFIED, *this, nullptr);
417}
418
419// } -------------------- BUILDERS -------------------- {
420std::unique_ptr<units_dialog> units_dialog::build_create_dialog(const std::vector<const unit_type*>& types_list)
421{
422 auto dlg = std::make_unique<units_dialog>();
423
424 const auto type_gen = [](const auto& type) {
425 std::string type_name = type->type_name();
426 if(type_name != type->id()) {
427 type_name += " (" + type->id() + ")";
428 }
429 return type_name;
430 };
431
432 const auto race_gen = [](const auto& type) {
433 return type->race()->plural_name();
434 };
435
436 const auto populate_variations = [](units_dialog* dlg, const unit_type& ut) {
437 // Populate variations box
438 menu_button& var_box = dlg->find_widget<menu_button>("variation_box");
439 std::vector<config> var_box_values;
440 var_box_values.emplace_back("label", _("unit_variation^Default Variation"), "variation_id", "");
441
442 const auto& uvars = ut.variation_types();
443
444 var_box.set_active(!uvars.empty());
445
446 unsigned n = 0, selection = 0;
447
448 for(const auto& [uv_id, uv] : uvars) {
449 ++n;
450
451 std::string uv_label;
452 if(!uv.variation_name().empty()) {
453 uv_label = uv.variation_name() + " (" + uv_id + ")";
454 } else if(!uv.type_name().empty() && uv.type_name() != ut.type_name()) {
455 uv_label = uv.type_name() + " (" + uv_id + ")";
456 } else {
457 uv_label = uv_id;
458 }
459
460 var_box_values.emplace_back("label", uv_label, "variation_id", uv_id);
461
462 if(uv_id == dlg->variation()) {
463 selection = n;
464 }
465 }
466
467 // If we didn't find the variation selection again then the new selected
468 // unit type doesn't have that variation id.
469 if(!selection) {
470 dlg->clear_variation();
471 }
472
473 var_box.set_values(var_box_values, selection);
474 };
475
476 dlg->set_title(_("Create Unit"))
477 .set_ok_label(_("Create"))
478 .set_help_topic("..units")
479 .set_row_num(types_list.size())
480 .set_show_variations(true);
481
482 // Listbox data
483 auto set_column = dlg->make_column_builder(types_list);
484
485 set_column("unit_name", type_gen, sort_type::generator);
486 set_column("unit_details", race_gen, sort_type::generator);
487
488 dlg->on_modified([populate_variations, &types_list](units_dialog* dlg, std::size_t index) -> const auto& {
489 const unit_type* ut = types_list[index];
490
491 if (dlg->is_selected() && (static_cast<int>(index) == dlg->get_selected_index())) {
492 dlg->get_toggle().set_member_states(dlg->gender());
493 } else {
494 dlg->get_toggle().set_members_enabled(
495 [ut](const unit_race::GENDER& gender) { return ut->has_gender_variation(gender); });
496 }
497
498 populate_variations(dlg, *ut);
499
500 const auto& g = dlg->gender();
501 if(ut->has_gender_variation(g)) {
502 ut = &ut->get_gender_unit_type(g);
503 }
504
505 const auto& var = dlg->variation();
506 if(!var.empty()) {
507 ut = &ut->get_variation(var);
508 }
509
510 return *ut;
511 });
512
513 return dlg;
514}
515
516std::unique_ptr<units_dialog> units_dialog::build_recruit_dialog(
517 const std::vector<const unit_type*>& recruit_list,
518 recruit_msgs_map& err_msgs_map,
519 const team& team
520)
521{
522 auto dlg = std::make_unique<units_dialog>();
523 auto set_column = dlg->make_column_builder(recruit_list);
524
525 set_column("unit_image", [&](const auto& recruit) {
526 std::string image_string = recruit->icon();
527 if (image_string.empty()) {
528 image_string = recruit->image();
529 }
530 image_string += "~RC(" + recruit->flag_rgb() + ">" + team.color() + ")";
531 image_string += "~SCALE_INTO(72,72)";
532 // Does the unit have error message? If so, grey out image.
533 if (!err_msgs_map[recruit].empty()) {
534 image_string += "~GS()";
535 }
536 return image_string;
537 }, sort_type::none);
538
539 set_column("unit_details", [&](const auto& recruit) {
540 // Does the unit have error message? If so, grey out text here.
541 bool recruitable = err_msgs_map[recruit].empty();
542 return unit_helper::maybe_inactive(recruit->type_name(), recruitable)
543 + '\n'
544 + unit_helper::format_cost_string(recruit->cost(), recruitable);
546
547 dlg->set_title(_("Recruit Unit") + get_title_suffix(team.side()))
548 .set_ok_label(_("Recruit"))
549 .set_help_topic("recruit_and_recall")
550 .set_row_num(recruit_list.size());
551
552 dlg->set_tooltip_generator([&](std::size_t index) {
553 // Show the error message in case of disabled units, if any.
554 return err_msgs_map[recruit_list[index]];
555 });
556
557 dlg->on_modified([&recruit_list](units_dialog*, std::size_t index) -> const auto& { return *recruit_list[index]; });
558
559 return dlg;
560}
561
562std::unique_ptr<units_dialog> units_dialog::build_unit_list_dialog(std::vector<unit_const_ptr>& unit_list)
563{
564 auto dlg = std::make_unique<units_dialog>();
565 dlg->set_title(_("Unit List"))
566 .set_ok_label(_("Scroll To"))
567 .set_help_topic("..units")
568 .set_row_num(unit_list.size())
569 .set_show_rename(true)
570 .set_show_favorite(true);
571
572 // Rename functionality
573 button& rename = dlg->find_widget<button>("rename");
574 connect_signal_mouse_left_click(rename, [&](auto&&...) {
575 dlg->rename_unit(unit_list);
576 });
577
578 // Mark favorite functionality
579 button& favorite = dlg->find_widget<button>("mark_favorite");
580 connect_signal_mouse_left_click(favorite, [&](auto&&...) {
581 dlg->toggle_favorite(unit_list);
582 });
583
584 auto set_column = dlg->make_column_builder(unit_list);
585
586 set_column("unit_favorite", [](const auto& unit) {
587 return star(unit->favorite());
589
590 set_column("unit_name",
591 [](const auto& unit) {
592 return !unit->name().empty() ? unit->name().str() : font::unicode_en_dash;
594
595 set_column("unit_details",
596 [](const auto& unit) {
597 return unit->type_name().str();
599
600 set_column("unit_level",
601 [](const auto& unit) {
603 },
604 [](const auto& unit) {
605 return std::tuple(unit->level(), -static_cast<int>(unit->experience_to_advance()));
606 });
607
608 set_column("unit_moves",
609 [](const auto& unit) {
611 },
612 [](const auto& unit) {
613 return unit->movement_left();
614 });
615
616 set_column("unit_hp",
617 [](const auto& unit) {
619 },
620 [](const auto& unit) {
621 return unit->hitpoints();
622 });
623
624 set_column("unit_xp",
625 [](const auto& unit) {
626 std::stringstream exp_str;
627 if(unit->can_advance()) {
628 exp_str << unit->experience() << "/" << unit->max_experience();
629 } else {
630 exp_str << font::unicode_en_dash;
631 }
632 return markup::span_color(unit->xp_color(), exp_str.str());
633 },
634 [](const auto& unit) {
635 // this allows 0/35, 0/100 etc to be sorted
636 // also sorts 23/35 before 0/35, after which 0/100 comes
637 return unit->experience() + unit->max_experience();
638 });
639
640 set_column("unit_status", [](const auto& unit) {
641 // Status
642 if(unit->incapacitated()) {
643 return "misc/petrified.png";
644 }
645
646 if(unit->poisoned()) {
647 return "misc/poisoned.png";
648 }
649
650 if(unit->slowed()) {
651 return "misc/slowed.png";
652 }
653
654 if(unit->invisible(unit->get_location(), false)) {
655 return "misc/invisible.png";
656 }
657
658 return "";
659 }, sort_type::none);
660
661 set_column("unit_traits", [](const auto& unit) {
662 return utils::join(unit->trait_names(), ", ");
664
665 dlg->set_filter_generator([&unit_list](std::size_t index) {
666 const auto& unit = unit_list[index];
667 std::vector<std::string> filter_keys;
668
669 filter_keys.emplace_back(unit->type_name());
670 filter_keys.emplace_back(!unit->name().empty() ? unit->name().str() : font::unicode_en_dash);
671 filter_keys.emplace_back(std::to_string(unit->level()));
672 filter_keys.emplace_back(unit_type::alignment_description(unit->alignment(), unit->gender()));
673
674 if(const auto* race = unit->race()) {
675 filter_keys.emplace_back(race->name(unit->gender()));
676 filter_keys.emplace_back(race->plural_name());
677 }
678
679 for(const std::string& trait : unit->trait_names()) {
680 filter_keys.emplace_back(trait);
681 }
682
683 return filter_keys;
684 });
685
686 dlg->on_modified([&unit_list, &rename](units_dialog*, std::size_t index) -> const auto& {
687 auto& unit = unit_list[index];
688 rename.set_active(!unit->unrenamable());
689 return *unit;
690 });
691
692 return dlg;
693}
694
695std::unique_ptr<units_dialog> units_dialog::build_recall_dialog(
696 std::vector<unit_const_ptr>& recall_list,
697 const team& team)
698{
700
701 // Lambda to check if a unit is recallable
702 const auto recallable = [wb_gold, &team](const unit& unit) {
703 // Note: Our callers apply [filter_recall], but leave it to us to apply cost-based filtering.
704 const int recall_cost = unit.recall_cost() > -1 ? unit.recall_cost() : team.recall_cost();
705 return recall_cost <= team.gold() - wb_gold;
706 };
707
708 auto dlg = std::make_unique<units_dialog>();
709 dlg->set_title(_("Recall Unit") + get_title_suffix(team.side()))
710 .set_ok_label(_("Recall"))
711 .set_help_topic("recruit_and_recall")
712 .set_row_num(recall_list.size())
713 .set_show_rename(true)
714 .set_show_dismiss(true)
715 .set_show_favorite(true);
716
717 // Rename functionality
718 button& rename = dlg->find_widget<button>("rename");
719 connect_signal_mouse_left_click(rename, [&](auto&&...) {
720 dlg->rename_unit(recall_list);
721 });
722
723 // Dismiss functionality
724 button& dismiss = dlg->find_widget<button>("dismiss");
725 connect_signal_mouse_left_click(dismiss, [&](auto&&...) {
726 dlg->dismiss_unit(recall_list, team);
727 });
728
729 // Mark favorite functionality
730 button& favorite = dlg->find_widget<button>("mark_favorite");
731 connect_signal_mouse_left_click(favorite, [&](auto&&...) {
732 dlg->toggle_favorite(recall_list);
733 });
734
735 auto set_column = dlg->make_column_builder(recall_list);
736
737 set_column("unit_favorite", [](const auto& unit) {
738 return star(unit->favorite());
740
741 set_column("unit_image", [recallable](const auto& unit) {
742 std::string mods = unit->image_mods();
743 if(unit->can_recruit()) { mods += "~BLIT(" + unit::leader_crown() + ")"; }
744 for(const std::string& overlay : unit->overlays()) {
745 mods += "~BLIT(" + overlay + ")";
746 }
747 if(!recallable(*unit)) { mods += "~GS()"; }
748 mods += "~SCALE_INTO(72,72)";
749 return unit->absolute_image() + mods;
750 }, sort_type::none);
751
752 set_column("unit_name",
753 [recallable](const auto& unit) {
754 const std::string& name = !unit->name().empty() ? unit->name().str() : font::unicode_en_dash;
755 return unit_helper::maybe_inactive(name, recallable(*unit));
756 },
757 [](const auto& unit) {
758 return unit->name().str();
759 });
760
761 set_column("unit_details",
762 [recallable, &team](const auto& unit) {
763 std::stringstream details;
764 details << unit_helper::maybe_inactive(unit->type_name().str(), recallable(*unit)) << '\n';
765 const int recall_cost = unit->recall_cost() == -1 ? team.recall_cost() : unit->recall_cost();
766 if (recallable(*unit)) {
767 details << unit_helper::format_cost_string(recall_cost, team.recall_cost());
768 } else {
769 details << unit_helper::format_cost_string(recall_cost, false);
770 }
771 return details.str();
772 },
773 [](const auto& unit) {
774 return unit->type_name().str();
775 });
776
777 set_column("unit_moves",
778 [recallable](const auto& unit) {
780 unit->movement_left(), unit->total_movement(), recallable(*unit));
781 },
782 [](const auto& unit) {
783 return unit->movement_left();
784 });
785
786 set_column("unit_level",
787 [recallable](const auto& unit) {
788 return unit_helper::format_level_string(unit->level(), recallable(*unit));
789 },
790 [](const auto& unit) {
791 return std::tuple(unit->level(), -static_cast<int>(unit->experience_to_advance()));
792 });
793
794 set_column("unit_hp",
795 [recallable](const auto& unit) {
796 const color_t& col = recallable(*unit) ? unit->hp_color() : font::GRAY_COLOR;
797 return markup::span_color(col, unit->hitpoints(), "/", unit->max_hitpoints());
798 },
799 [](const auto& unit) {
800 return unit->hitpoints();
801 });
802
803 set_column("unit_xp",
804 [recallable](const auto& unit) {
805 const color_t& col = recallable(*unit) ? unit->xp_color() : font::GRAY_COLOR;
806 if(unit->can_advance()) {
807 return markup::span_color(col, unit->experience(), "/", unit->max_experience());
808 } else {
810 }
811 },
812 [](const auto& unit) {
813 return std::tuple(static_cast<int>(unit->experience_to_advance()), unit->max_experience());
814 });
815
816 set_column("unit_traits",
817 [recallable](const auto& unit) {
818 std::string traits;
819 for(const std::string& trait : unit->trait_names()) {
820 traits += (traits.empty() ? "" : "\n") + trait;
821 }
822 return unit_helper::maybe_inactive((!traits.empty() ? traits : font::unicode_en_dash), recallable(*unit));
823 },
824 [](const auto& unit) {
825 return !unit->trait_names().empty() ? unit->trait_names().front().str() : "";
826 });
827
828
829 dlg->set_sort_by(std::pair("unit_level", sort_order::type::descending));
830
831 dlg->set_tooltip_generator([recallable, wb_gold, &recall_list](std::size_t index) {
832 if(recallable(*recall_list[index])) {
833 return std::string();
834 }
835
836 // Just set the tooltip on every single element in this row.
837 if(wb_gold > 0) {
838 return _("This unit cannot be recalled because you will not have enough gold at this point in your plan.");
839 } else {
840 return _("This unit cannot be recalled because you do not have enough gold.");
841 }
842 });
843
844 dlg->set_filter_generator([&recall_list](std::size_t index) {
845 const auto& unit = recall_list[index];
846 std::vector<std::string> filter_keys;
847
848 filter_keys.emplace_back(unit->type_name());
849 filter_keys.emplace_back(!unit->name().empty() ? unit->name().str() : font::unicode_en_dash);
850 filter_keys.emplace_back(std::to_string(unit->level()));
851 filter_keys.emplace_back(unit_type::alignment_description(unit->alignment(), unit->gender()));
852
853 if(const auto* race = unit->race()) {
854 filter_keys.emplace_back(race->name(unit->gender()));
855 filter_keys.emplace_back(race->plural_name());
856 }
857
858 for(const std::string& trait : unit->trait_names()) {
859 filter_keys.emplace_back(trait);
860 }
861
862 return filter_keys;
863 });
864
865 dlg->on_modified([&recall_list, &rename](units_dialog*, std::size_t index) -> const auto& {
866 const auto& unit = recall_list[index];
867 rename.set_active(!unit->unrenamable());
868 return *unit;
869 });
870
871 return dlg;
872}
873
874
875
876} // namespace dialogs
#define LOG_DP
static lg::log_domain log_display("display")
virtual const unit_map & units() const override
Simple push button.
Definition button.hpp:36
virtual void set_active(const bool active) override
See styled_widget::set_active.
Definition button.cpp:64
Main class to show messages to the user.
Definition message.hpp:36
@ yes_no_buttons
Shows a yes and no button.
Definition message.hpp:81
Abstract base class for all modal dialogs.
group< unit_race::GENDER > & get_toggle()
void show_list(listbox &list)
std::pair< std::string, sort_order::type > sort_order_
std::map< const unit_type *, t_string > recruit_msgs_map
std::vector< std::vector< std::string > > filter_options_
auto make_column_builder(const std::vector< Value > &list)
Creates a generator function which registers secondary generator and sorter functions for the list co...
std::function< std::string(std::size_t)> tooltip_gen_
std::function< std::vector< std::string >(std::size_t)> filter_gen_
units_dialog & set_ok_label(const std::string &ok_label)
virtual void post_show() override
Actions to be taken after the window has been shown.
void rename_unit(std::vector< unit_const_ptr > &unit_list)
std::string variation() const
Variation choice from the user.
units_dialog & set_show_variations(bool show=true)
units_dialog & set_help_topic(const std::string &topic_id)
units_dialog & set_title(const std::string &title)
void filter_text_changed(const std::string &text)
static std::unique_ptr< units_dialog > build_unit_list_dialog(std::vector< unit_const_ptr > &units_list)
units_dialog & set_row_num(const std::size_t row_num)
static std::unique_ptr< units_dialog > build_create_dialog(const std::vector< const unit_type * > &types_list)
void dismiss_unit(std::vector< unit_const_ptr > &unit_list, const team &team)
void toggle_favorite(std::vector< unit_const_ptr > &unit_list)
void on_modified(const Func &f)
Registers an function which will fired on NOTIFY_MODIFIED dialog events.
std::map< std::string_view, std::function< std::string(std::size_t)> > column_generators_
units_dialog & set_tooltip_generator(const Generator &generator)
Sets the generator function for the tooltips.
virtual void pre_show() override
Actions to be taken before showing the window.
void list_item_clicked()
Callbacks.
unit_race::GENDER gender() const
Gender choice from the user.
static std::unique_ptr< units_dialog > build_recruit_dialog(const std::vector< const unit_type * > &recruit_list, recruit_msgs_map &err_msgs_map, const team &team)
static std::unique_ptr< units_dialog > build_recall_dialog(std::vector< unit_const_ptr > &recall_list, const team &team)
void update_gender(const unit_race::GENDER val)
bool fire(const ui_event event, widget &target)
Fires an event which has no extra parameters.
Basic template class to generate new items.
Base container class.
Definition grid.hpp:32
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_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
const grid * get_row_grid(const unsigned row) const
Returns the grid of the wanted row.
Definition listbox.cpp:256
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:599
bool select_row(const unsigned row, const bool select=true)
Selects a row.
Definition listbox.cpp:267
std::pair< widget *, sort_order::type > get_active_sorter() const
Returns a widget pointer to the active sorter, along with its corresponding order.
Definition listbox.cpp:613
void remove_row(const unsigned row, unsigned count=1)
Removes a row in the listbox.
Definition listbox.cpp:108
int get_selected_row() const
Returns the first selected row.
Definition listbox.cpp:290
unsigned get_item_count() const
Returns the number of items in the listbox.
Definition listbox.cpp:153
void set_values(const std::vector<::config > &values, unsigned selected=0)
virtual void set_active(const bool active) override
See styled_widget::set_active.
void set_tooltip(const t_string &tooltip)
virtual void set_label(const t_string &text)
A widget that allows the user to input text in single line.
Definition text_box.hpp:125
Base class for all widgets.
Definition widget.hpp:55
T * find_widget(const std::string_view id, const bool must_be_active, const bool must_exist)
Gets a widget with the wanted id.
Definition widget.hpp:747
void set_visible(const visibility visible)
Definition widget.cpp:479
@ invisible
The user set the widget invisible, that means:
void set_retval(const int retval, const bool close_window=true)
Sets there return value of the window.
Definition window.hpp:387
void keyboard_capture(widget *widget)
Definition window.cpp:1197
void invalidate_layout()
Updates the size of the window.
Definition window.cpp:759
void add_to_keyboard_chain(widget *widget)
Adds the widget to the keyboard chain.
Definition window.cpp:1211
static const std::string & type()
Static type getter that does not rely on the widget being constructed.
void set_exit_hook(exit_hook mode, const Func &hook)
Sets the window's exit hook.
Definition window.hpp:435
int get_retval()
Definition window.hpp:394
unit_ptr find_if_matches_id(const std::string &unit_id)
Find a unit by id.
static config get_disband(const std::string &unit_id)
static bool run_and_throw(const std::string &commandname, const config &data, action_spectator &spectator=get_default_spectator())
bool empty() const
Definition tstring.hpp:200
const std::string & str() const
Definition tstring.hpp:204
This class stores all the data for a single 'side' (in game nomenclature).
Definition team.hpp:74
const std::set< std::string > & recruits() const
Definition team.hpp:250
int side() const
Definition team.hpp:182
int recall_cost() const
Definition team.hpp:198
bool is_local_human() const
Definition team.hpp:294
int gold() const
Definition team.hpp:183
recall_list_manager & recall_list()
Definition team.hpp:242
const std::string & color() const
Definition team.hpp:283
Container associating units to locations.
Definition map.hpp:98
unit_iterator end()
Definition map.hpp:428
unit_iterator find_leader(int side)
Definition map.cpp:320
@ FEMALE
Definition race.hpp:28
A single unit type that the player may recruit.
Definition types.hpp:43
static std::string alignment_description(unit_alignments::type align, unit_race::GENDER gender=unit_race::MALE)
Implementation detail of unit_type::alignment_description.
Definition types.cpp:779
This class represents a single unit of a specific type.
Definition unit.hpp:39
static std::string _(const char *str)
Definition gettext.hpp:100
void set_favorite(bool favorite)
Definition unit.hpp:1936
bool invisible(const map_location &loc, bool see_all=true) const
Definition unit.cpp:2634
bool favorite() const
Definition unit.hpp:1934
int max_hitpoints() const
The max number of hitpoints this unit can have.
Definition unit.hpp:427
unit_alignments::type alignment() const
The alignment of this unit.
Definition unit.hpp:397
bool incapacitated() const
Check if the unit has been petrified.
Definition unit.hpp:826
int level() const
The current level of this unit.
Definition unit.hpp:481
bool unrenamable() const
Whether this unit can be renamed.
Definition unit.hpp:342
int recall_cost() const
How much gold it costs to recall this unit, or -1 if the side's default recall cost is used.
Definition unit.hpp:562
void rename(const std::string &name)
Attempts to rename this unit's translatable display name, taking the 'unrenamable' flag into account.
Definition unit.hpp:330
int hitpoints() const
The current number of hitpoints this unit has.
Definition unit.hpp:421
const t_string & type_name() const
Gets the translatable name of this unit's type.
Definition unit.hpp:275
bool slowed() const
Check if the unit has been slowed.
Definition unit.hpp:835
const unit_race * race() const
Gets this unit's race.
Definition unit.hpp:415
int experience() const
The current number of experience points this unit has.
Definition unit.hpp:445
t_string block_dismiss_message() const
A message of why this unit cannot be dismissed.
Definition unit.hpp:366
bool can_recruit() const
Whether this unit can recruit other units - ie, are they a leader unit.
Definition unit.hpp:534
const std::string & id() const
Gets this unit's id.
Definition unit.hpp:286
int side() const
The side this unit belongs to.
Definition unit.hpp:249
bool dismissable() const
Whether this unit can be dismissed.
Definition unit.hpp:360
bool poisoned() const
Check if the unit has been poisoned.
Definition unit.hpp:817
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:463
int max_experience() const
The max number of experience points this unit can have.
Definition unit.hpp:451
unit_race::GENDER gender() const
The gender of this unit.
Definition unit.hpp:387
const t_string & name() const
Gets this unit's translatable display name.
Definition unit.hpp:309
bool can_advance() const
Checks whether this unit has any options to advance to.
Definition unit.hpp:178
const std::vector< std::string > & overlays() const
Get the unit's overlay images.
Definition unit.hpp:1600
color_t xp_color() const
Color for this unit's XP.
Definition unit.cpp:1266
color_t hp_color() const
Color for this unit's current hitpoints.
Definition unit.cpp:1222
std::string image_mods() const
Gets an IPF string containing all IPF image mods.
Definition unit.cpp:2792
std::string absolute_image() const
The name of the file to game_display (used in menus).
Definition unit.cpp:2611
const map_location & get_location() const
The current map location this unit is at.
Definition unit.hpp:1327
int movement_left() const
Gets how far a unit can move, considering the incapacitated flag.
Definition unit.hpp:1252
int total_movement() const
The maximum moves this unit has.
Definition unit.hpp:1236
const std::vector< t_string > & trait_names() const
Gets the names of the currently registered traits.
Definition unit.hpp:990
bool loyal() const
Gets whether this unit is loyal - ie, it costs no upkeep.
Definition unit.cpp:1761
This file contains the window object, this object is a top level container which has the event manage...
Standard logging facilities (interface).
#define log_scope2(domain, description)
Definition log.hpp:276
#define REGISTER_DIALOG(window_id)
Wrapper for REGISTER_DIALOG2.
const color_t GRAY_COLOR
const std::string unicode_en_dash
Definition constants.cpp:43
std::map< std::string, widget_item > widget_data
Definition widget.hpp:36
std::map< std::string, t_string > widget_item
Definition widget.hpp:33
void show_transient_message(const std::string &title, const std::string &message, const std::string &image, const bool message_use_markup, const bool title_use_markup)
Shows a transient message to the user.
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:148
@ OK
Dialog was closed with the OK button.
Definition retval.hpp:35
@ CANCEL
Dialog was closed with the CANCEL button.
Definition retval.hpp:38
void show_help(const std::string &show_topic)
Open the help browser.
Definition help.cpp:84
std::string span_color(const color_t &color, Args &&... data)
Applies Pango markup to the input specifying its display color.
Definition markup.hpp:110
game_board * gameboard
Definition resources.cpp:20
STL namespace.
T end(const std::pair< T, T > &p)
auto make_ci_matcher(std::string_view filter_text)
Returns a function which performs locale-aware case-insensitive search.
std::string format_level_string(const int level, bool recallable)
Definition helper.cpp:135
std::string format_movement_string(const int moves_left, const int moves_max, bool active)
Definition helper.cpp:154
int planned_gold_spent(int side_number)
Definition helper.cpp:167
std::string maybe_inactive(const std::string &str, bool active)
Definition helper.cpp:96
std::string format_cost_string(int unit_recall_cost, bool active)
Definition helper.cpp:101
std::string join(const Range &v, const std::string &s=",")
Generates a new string joining container items in a list.
std::shared_ptr< const unit > unit_const_ptr
Definition ptr.hpp:27
The basic class for representing 8-bit RGB or RGBA colour values.
Definition color.hpp:51
static map_location::direction n
static lg::log_domain log_display("display")
#define i
#define g