The Battle for Wesnoth 1.21.0-dev
 
Loading...
Searching...
No Matches
manager.cpp
Go to the documentation of this file.
1/*
2 Copyright (C) 2008 - 2025
3 by Mark de Wever <koraq@xs4all.nl>
4 Part of the Battle for Wesnoth Project https://www.wesnoth.org/
5
6 This program is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY.
12
13 See the COPYING file for more details.
14*/
15
16#define GETTEXT_DOMAIN "wesnoth-lib"
17
19
20#include "addon/info.hpp"
21#include "addon/manager.hpp"
22#include "addon/state.hpp"
23
24#include "help/help.hpp"
25#include "gettext.hpp"
32#include "gui/widgets/label.hpp"
43#include "picture.hpp"
44#include "language.hpp"
45#include "utils/general.hpp"
46
47#include "config.hpp"
48
49#include <functional>
50#include <set>
51#include <sstream>
52
53namespace gui2::dialogs
54{
55
56namespace {
57 struct filter_transform
58 {
59 explicit filter_transform(const std::vector<std::string>& filtertext) : filtertext_(filtertext) {}
60 bool operator()(const config& cfg) const
61 {
62 for(const auto& filter : filtertext_)
63 {
64 bool found = false;
65 for(const auto& [_, value] : cfg.attribute_range())
66 {
67 if(translation::ci_search(value.str(), filter))
68 {
69 found = true;
70 break;
71 }
72 }
73 for(const config& child : cfg.child_range("translation")) {
74 for(const auto& [_, value] : child.attribute_range()) {
75 if(translation::ci_search(value.str(), filter)) {
76 found = true;
77 break;
78 }
79 }
80 }
81 if(!found) {
82 return false;
83 }
84 }
85 return true;
86 }
87 const std::vector<std::string> filtertext_;
88 };
89
90 std::string make_display_dependencies(
91 const std::string& addon_id,
93 const addons_tracking_list& addon_states)
94 {
95 const addon_info& addon = addons_list.at(addon_id);
96 std::string str;
97
98 const std::set<std::string>& deps = addon.resolve_dependencies(addons_list);
99
100 for(const auto& dep_id : deps) {
101 addon_info dep;
102 addon_tracking_info depstate;
103
104 addons_list::const_iterator ali = addons_list.find(dep_id);
105 addons_tracking_list::const_iterator tli = addon_states.find(dep_id);
106
107 if(ali == addons_list.end()) {
108 dep.id = dep_id; // Build dummy addon_info.
109 } else {
110 dep = ali->second;
111 }
112
113 if(tli == addon_states.end()) {
114 depstate = get_addon_tracking_info(dep);
115 } else {
116 depstate = tli->second;
117 }
118
119 if(!str.empty()) {
120 str += ", ";
121 }
122
124 }
125
126 return str;
127 }
128
129 std::string langcode_to_string(const std::string& lcode)
130 {
131 for(const auto & ld : get_languages(true))
132 {
133 if(ld.localename == lcode || ld.localename.substr(0, 2) == lcode) {
134 return ld.language;
135 }
136 }
137
138 return "";
139 }
140}
141
142REGISTER_DIALOG(addon_manager)
143
144const std::vector<std::pair<ADDON_STATUS_FILTER, std::string>> addon_manager::status_filter_types_{
145 {FILTER_ALL, N_("addons_view^All Add-ons")},
146 {FILTER_INSTALLED, N_("addons_view^Installed")},
147 {FILTER_UPGRADABLE, N_("addons_view^Upgradable")},
148 {FILTER_PUBLISHABLE, N_("addons_view^Publishable")},
149 {FILTER_NOT_INSTALLED, N_("addons_view^Not Installed")},
150};
151
152const std::vector<std::pair<ADDON_TYPE, std::string>> addon_manager::type_filter_types_{
153 {ADDON_SP_CAMPAIGN, N_("addons_of_type^Campaigns")},
154 {ADDON_SP_SCENARIO, N_("addons_of_type^Scenarios")},
155 {ADDON_SP_MP_CAMPAIGN, N_("addons_of_type^SP/MP campaigns")},
156 {ADDON_MP_CAMPAIGN, N_("addons_of_type^MP campaigns")},
157 {ADDON_MP_SCENARIO, N_("addons_of_type^MP scenarios")},
158 {ADDON_MP_MAPS, N_("addons_of_type^MP map-packs")},
159 {ADDON_MP_ERA, N_("addons_of_type^MP eras")},
160 {ADDON_MP_FACTION, N_("addons_of_type^MP factions")},
161 {ADDON_MOD, N_("addons_of_type^Modifications")},
162 {ADDON_CORE, N_("addons_of_type^Cores")},
163 {ADDON_THEME, N_("addons_of_type^Themes")},
164 {ADDON_MEDIA, N_("addons_of_type^Resources")},
165 // FIXME: (also in WML) should this and Unknown be a single option in the UI?
166 {ADDON_OTHER, N_("addons_of_type^Other")},
167 {ADDON_UNKNOWN, N_("addons_of_type^Unknown")},
168};
169
170const std::vector<addon_manager::addon_order> addon_manager::all_orders_{
171 {N_("addons_order^Name ($order)"), "name", 0,
172 [](const addon_info& a, const addon_info& b) { return a.title < b.title; },
173 [](const addon_info& a, const addon_info& b) { return a.title > b.title; }},
174 {N_("addons_order^Author ($order)"), "author", 1,
175 [](const addon_info& a, const addon_info& b) { return a.author < b.author; },
176 [](const addon_info& a, const addon_info& b) { return a.author > b.author; }},
177 {N_("addons_order^Size ($order)"), "size", 2,
178 [](const addon_info& a, const addon_info& b) { return a.size < b.size; },
179 [](const addon_info& a, const addon_info& b) { return a.size > b.size; }},
180 {N_("addons_order^Downloads ($order)"), "downloads", 3,
181 [](const addon_info& a, const addon_info& b) { return a.downloads < b.downloads; },
182 [](const addon_info& a, const addon_info& b) { return a.downloads > b.downloads; }},
183 {N_("addons_order^Type ($order)"), "type", 4,
184 [](const addon_info& a, const addon_info& b) { return a.display_type() < b.display_type(); },
185 [](const addon_info& a, const addon_info& b) { return a.display_type() > b.display_type(); }},
186 {N_("addons_order^Last updated ($datelike_order)"), "last_updated", -1,
187 [](const addon_info& a, const addon_info& b) { return a.updated < b.updated; },
188 [](const addon_info& a, const addon_info& b) { return a.updated > b.updated; }},
189 {N_("addons_order^First uploaded ($datelike_order)"), "first_uploaded", -1,
190 [](const addon_info& a, const addon_info& b) { return a.created < b.created; },
191 [](const addon_info& a, const addon_info& b) { return a.created > b.created; }}
192};
193
194namespace
195{
196struct addon_tag
197{
198 /** Text to match against addon_info.tags() */
199 std::string id;
200 /** What to show in the filter's drop-down list */
201 std::string label;
202 /** Shown when hovering over an entry in the filter's drop-down list */
203 std::string tooltip;
204};
205
206const std::vector<addon_tag> tag_filter_types_{
207 {"cooperative", N_("addon_tag^Cooperative"),
208 // TRANSLATORS: tooltip in the drop-down menu for filtering add-ons
209 N_("addon_tag^All human players are on the same team, versus the AI")},
210 {"cosmetic", N_("addon_tag^Cosmetic"),
211 // TRANSLATORS: tooltip in the drop-down menu for filtering add-ons
212 N_("addon_tag^These make the game look different, without changing gameplay")},
213 {"difficulty", N_("addon_tag^Difficulty"),
214 // TRANSLATORS: tooltip in the drop-down menu for filtering add-ons
215 N_("addon_tag^Can make campaigns easier or harder")},
216 {"rng", N_("addon_tag^RNG"),
217 // TRANSLATORS: tooltip in the drop-down menu for filtering add-ons
218 N_("addon_tag^Modify the randomness in the combat mechanics, or remove it entirely")},
219 {"survival", N_("addon_tag^Survival"),
220 // TRANSLATORS: tooltip in the drop-down menu for filtering add-ons
221 N_("addon_tag^Fight against waves of enemies")},
222 {"terraforming", N_("addon_tag^Terraforming"),
223 // TRANSLATORS: tooltip in the drop-down menu for filtering add-ons
224 N_("addon_tag^Players can change the terrain")},
225};
226};
227
229 : modal_dialog(window_id())
230 , orders_()
231 , cfg_()
232 , client_(client)
233 , addons_()
234 , tracking_info_()
235 , need_wml_cache_refresh_(false)
236{
237}
238
239static std::string describe_status_verbose(const addon_tracking_info& state)
240{
241 std::string s;
242
243 utils::string_map i18n_symbols {{"local_version", state.installed_version.str()}};
244
245 switch(state.state) {
246 case ADDON_NONE:
247 s = !state.can_publish
248 ? _("addon_state^Not installed")
249 : _("addon_state^Published, not installed");
250 break;
251 case ADDON_INSTALLED:
252 s = !state.can_publish
253 ? _("addon_state^Installed")
254 : _("addon_state^Published");
255 break;
257 s = !state.can_publish
258 ? _("addon_state^Installed, not tracking local version")
259 // Published add-ons often don't have local status information,
260 // hence untracked. This should be considered normal.
261 : _("addon_state^Published, not tracking local version");
262 break;
264 const std::string vstr = !state.can_publish
265 ? _("addon_state^Installed ($local_version|), upgradable")
266 : _("addon_state^Published ($local_version| installed), upgradable");
267
268 s = utils::interpolate_variables_into_string(vstr, &i18n_symbols);
269 } break;
271 const std::string vstr = !state.can_publish
272 ? _("addon_state^Installed ($local_version|), outdated on server")
273 : _("addon_state^Published ($local_version| installed), outdated on server");
274
275 s = utils::interpolate_variables_into_string(vstr, &i18n_symbols);
276 } break;
278 s = !state.can_publish
279 ? _("addon_state^Installed, not ready to publish")
280 : _("addon_state^Ready to publish");
281 break;
283 s = !state.can_publish
284 ? _("addon_state^Installed, broken")
285 : _("addon_state^Published, broken");
286 break;
287 default:
288 s = _("addon_state^Unknown");
289 }
290
292}
293
295{
297
298 stacked_widget& addr_info = find_widget<stacked_widget>("server_conn_info");
299 grid* addr_visible;
300
301 if(client_.using_tls()) {
302 addr_info.select_layer(1);
303 addr_visible = addr_info.get_layer_grid(1);
304 } else {
305 addr_info.select_layer(0);
306 addr_visible = addr_info.get_layer_grid(0);
307 }
308
309 if(addr_visible) {
310 auto addr_box = dynamic_cast<styled_widget*>(addr_visible->find("server_addr", false));
311 if(addr_box) {
312 if(!client_.server_id().empty()) {
313 auto full_id = formatter()
314 << client_.addr() << ' '
315 << font::unicode_em_dash << ' '
316 << client_.server_id();
317 if(game_config::debug && !client_.server_version().empty()) {
318 full_id << " (" << client_.server_version() << ')';
319 }
320 addr_box->set_label(full_id.str());
321 } else {
322 addr_box->set_label(client_.addr());
323 }
324 }
325 }
326
327 addon_list& list = find_widget<addon_list>("addons");
328
329 text_box& filter = find_widget<text_box>("filter");
330 filter.on_modified([this](const auto&) { apply_filters(); });
331
333 this, std::placeholders::_1));
335 this, std::placeholders::_1));
337 this, std::placeholders::_1));
338
340 this, std::placeholders::_1));
342 this, std::placeholders::_1));
343
344 connect_signal_notify_modified(list, [this](auto&&...) { on_addon_select(); });
345
348
349 menu_button& status_filter = find_widget<menu_button>("install_status_filter");
350
351 std::vector<config> status_filter_entries;
352 for(const auto& f : status_filter_types_) {
353 if(f.first == FILTER_ALL) {
354 status_filter_entries.emplace_back("label", t_string(f.second, GETTEXT_DOMAIN)+" ("+std::to_string(addons_.size())+")");
355 } else if(f.first == FILTER_INSTALLED) {
356 status_filter_entries.emplace_back("label", t_string(f.second, GETTEXT_DOMAIN)+" ("+std::to_string(installed_addons().size())+")");
357 } else {
358 status_filter_entries.emplace_back("label", t_string(f.second, GETTEXT_DOMAIN));
359 }
360 }
361
362 status_filter.set_values(status_filter_entries);
363
364 connect_signal_notify_modified(status_filter,
365 std::bind(&addon_manager::apply_filters, this));
366
367 // The tag filter
368 auto& tag_filter = find_widget<multimenu_button>("tag_filter");
369
370 std::vector<config> tag_filter_entries;
371 for(const auto& f : tag_filter_types_) {
372 tag_filter_entries.emplace_back("label", t_string(f.label, GETTEXT_DOMAIN), "checkbox", false);
373 if(!f.tooltip.empty()) {
374 tag_filter_entries.back()["tooltip"] = t_string(f.tooltip, GETTEXT_DOMAIN);
375 }
376 }
377
378 tag_filter.set_values(tag_filter_entries);
379
380 connect_signal_notify_modified(tag_filter, std::bind(&addon_manager::apply_filters, this));
381
382 // The type filter
383 multimenu_button& type_filter = find_widget<multimenu_button>("type_filter");
384
385 std::map<ADDON_TYPE, int> type_counts = {
391 {ADDON_MP_MAPS, 0},
392 {ADDON_MP_ERA, 0},
393 {ADDON_MP_FACTION, 0},
394 {ADDON_MOD, 0},
395 {ADDON_CORE, 0},
396 {ADDON_THEME, 0},
397 {ADDON_MEDIA, 0},
398 {ADDON_OTHER, 0},
399 {ADDON_UNKNOWN, 0}
400 };
401 for(const auto& addon : addons_) {
402 type_counts[addon.second.type]++;
403 }
404
405 std::vector<config> type_filter_entries;
406 for(const auto& f : type_filter_types_) {
407 type_filter_entries.emplace_back("label", t_string(f.second, GETTEXT_DOMAIN)+" ("+std::to_string(type_counts[f.first])+")", "checkbox", false);
408 }
409
410 type_filter.set_values(type_filter_entries);
411
412 connect_signal_notify_modified(type_filter,
413 std::bind(&addon_manager::apply_filters, this));
414
415 // Language filter
416 // Prepare shown languages, source all available languages from the addons themselves
417 std::set<std::string> languages_available;
418 for(const auto& a : addons_) {
419 for (const auto& b : a.second.locales) {
420 languages_available.insert(b);
421 }
422 }
423 std::set<std::string> language_strings_available;
424 for (const auto& i: languages_available) {
425 // Only show languages, which have a translation as per langcode_to_string() method
426 // Do not show tranlations with their langcode e.g. "sv_SV"
427 // Also put them into a set, so same lang strings are not producing doublettes
428 if (std::string lang_code_string = langcode_to_string(i); !lang_code_string.empty()) {
429 language_strings_available.insert(lang_code_string);
430 }
431 }
432 for (auto& i: language_strings_available) {
433 language_filter_types_.emplace_back(language_filter_types_.size(), i);
434 }
435 // The language filter
436 multimenu_button& language_filter = find_widget<multimenu_button>("language_filter");
437 std::vector<config> language_filter_entries;
438 for(const auto& f : language_filter_types_) {
439 language_filter_entries.emplace_back("label", f.second, "checkbox", false);
440 }
441
442 language_filter.set_values(language_filter_entries);
443
444 connect_signal_notify_modified(language_filter,
445 std::bind(&addon_manager::apply_filters, this));
446
447 // Sorting order
448 menu_button& order_dropdown = find_widget<menu_button>("order_dropdown");
449
450 std::vector<config> order_dropdown_entries;
451 for(const auto& f : all_orders_) {
452 utils::string_map symbols;
453
454 symbols["order"] = _("ascending");
455 // TRANSLATORS: Sorting order of dates, oldest first
456 symbols["datelike_order"] = _("oldest to newest");
457 config entry{"label", VGETTEXT(f.label.c_str(), symbols)};
458 order_dropdown_entries.push_back(entry);
459 symbols["order"] = _("descending");
460 // TRANSLATORS: Sorting order of dates, newest first
461 symbols["datelike_order"] = _("newest to oldest");
462 entry["label"] = VGETTEXT(f.label.c_str(), symbols);
463 order_dropdown_entries.push_back(entry);
464 }
465
466 order_dropdown.set_values(order_dropdown_entries);
467 {
468 const std::string saved_order_name = prefs::get().addon_manager_saved_order_name();
469 const sort_order::type saved_order_direction = prefs::get().addon_manager_saved_order_direction();
470
471 if(!saved_order_name.empty()) {
472 auto order_it = utils::ranges::find(all_orders_, saved_order_name, &addon_order::as_preference);
473 if(order_it != all_orders_.end()) {
474 int index = 2 * (std::distance(all_orders_.begin(), order_it));
476 if(saved_order_direction == sort_order::type::ascending) {
477 func = order_it->sort_func_asc;
478 } else {
479 func = order_it->sort_func_desc;
480 ++index;
481 }
482 find_widget<menu_button>("order_dropdown").set_value(index);
483 auto& addons = find_widget<addon_list>("addons");
484 addons.set_addon_order(func);
485 addons.select_first_addon();
486 }
487 }
488 }
489
490 connect_signal_notify_modified(order_dropdown,
491 std::bind(&addon_manager::order_addons, this));
492
493 label& url_label = find_widget<label>("url");
494
495 url_label.set_use_markup(true);
496 url_label.set_link_aware(true);
497
498 connect_signal_mouse_left_click(
499 find_widget<button>("install"),
500 std::bind(&addon_manager::install_selected_addon, this));
501
502 connect_signal_mouse_left_click(
503 find_widget<button>("uninstall"),
505
506 connect_signal_mouse_left_click(
507 find_widget<button>("update"),
508 std::bind(&addon_manager::update_selected_addon, this));
509
510 connect_signal_mouse_left_click(
511 find_widget<button>("publish"),
512 std::bind(&addon_manager::publish_selected_addon, this));
513
514 connect_signal_mouse_left_click(
515 find_widget<button>("delete"),
516 std::bind(&addon_manager::delete_selected_addon, this));
517
519 connect_signal_mouse_left_click(
520 find_widget<button>("info"),
521 std::bind(&addon_manager::info, this));
522 } else {
523 find_widget<button>("info").set_visible(false);
524 }
525
526 connect_signal_mouse_left_click(
527 find_widget<button>("update_all"),
528 std::bind(&addon_manager::update_all_addons, this));
529
530 connect_signal_mouse_left_click(
531 find_widget<button>("show_help"),
532 std::bind(&addon_manager::show_help, this));
533
534 if(stacked_widget* stk = find_widget<stacked_widget>("main_stack", false, false)) {
535 button& btn = find_widget<button>("details_toggle");
536 connect_signal_mouse_left_click(btn,
537 std::bind(&addon_manager::toggle_details, this, std::ref(btn), std::ref(*stk)));
538
539 stk->select_layer(0);
540
541 connect_signal_notify_modified(
542 stk->get_layer_grid(1)->find_widget<menu_button>("version_filter"),
544 } else {
545 connect_signal_notify_modified(
546 find_widget<menu_button>("version_filter"),
548 }
549
551
552 set_enter_disabled(true);
553
554#ifdef __IPHONEOS__
555 // On iOS, opening the add-ons manager should not immediately summon the
556 // software keyboard just because the optional filter field exists.
557 keyboard_capture(&list);
558#else
559 keyboard_capture(&filter);
560#endif
562
563 list.set_callback_order_change(std::bind(&addon_manager::on_order_changed, this, std::placeholders::_1, std::placeholders::_2));
564
565 // Use handle the special addon_list retval to allow installing addons on double click
567}
568
570{
571 if(stk.current_layer() == 0) {
572 btn.set_label(_("addons^Back to List"));
573 stk.select_layer(1);
574 } else {
575 btn.set_label(_("Add-on Details"));
576 stk.select_layer(0);
577 }
578}
579
581{
582 bool success = client_.request_addons_list(cfg_, prefs::get().addon_icons());
583 if(!success) {
584 gui2::show_error_message(_("An error occurred while downloading the add-ons list from the server."));
585 close();
586 }
587}
588
590{
593 }
594
596
597 std::vector<std::string> publishable_addons = available_addons();
598
599 for(std::string id : publishable_addons) {
600 if(addons_.find(id) == addons_.end()) {
601 // Get a config from the addon's pbl file
602 // Note that the name= key is necessary or stuff breaks, since the filter code uses this key
603 // to match add-ons in the config list. It also fills in addon_info's id field. It's also
604 // neccessay to set local_only here so that flag can be properly set after addons_ is cleared
605 // and recreated by read_addons_list.
606 try {
607 config pbl_cfg = get_addon_pbl_info(id, false);
608 pbl_cfg["name"] = id;
609 pbl_cfg["local_only"] = true;
610
611 // Add the add-on to the list.
612 addon_info addon(pbl_cfg);
613 addons_[id] = addon;
614
615 // Add the addon to the config entry
616 cfg_.add_child("campaign", std::move(pbl_cfg));
617 } catch(invalid_pbl_exception&) {}
618 }
619 }
620
621 if(addons_.empty()) {
622 show_transient_message(_("No Add-ons Available"), _("There are no add-ons available for download from this server."));
623 }
624
625 addon_list& list = find_widget<addon_list>("addons");
626 list.set_addons(addons_);
627
628 bool has_upgradable_addons = false;
629 for(const auto& a : addons_) {
630 tracking_info_[a.first] = get_addon_tracking_info(a.second);
631
632 if(tracking_info_[a.first].state == ADDON_INSTALLED_UPGRADABLE) {
633 has_upgradable_addons = true;
634 }
635 }
636
637 find_widget<button>("update_all").set_active(has_upgradable_addons);
638
640}
641
643{
645
646 // Reselect the add-on.
647 find_widget<addon_list>("addons").select_addon(id);
649}
650
651boost::dynamic_bitset<> addon_manager::get_name_filter_visibility() const
652{
653 const text_box& name_filter = find_widget<const text_box>("filter");
654 const std::string& text = name_filter.get_value();
655
656 filter_transform filter(utils::split(text, ' '));
657 boost::dynamic_bitset<> res;
658
659 const config::const_child_itors& addon_cfgs = cfg_.child_range("campaign");
660
661 for(const auto& a : addons_)
662 {
663 const config& addon_cfg = *utils::ranges::find(addon_cfgs, a.first,
664 [](const config& cfg) { return cfg["name"]; });
665
666 res.push_back(filter(addon_cfg));
667 }
668
669 return res;
670}
671
672boost::dynamic_bitset<> addon_manager::get_status_filter_visibility() const
673{
674 const menu_button& status_filter = find_widget<const menu_button>("install_status_filter");
675 const ADDON_STATUS_FILTER selection = status_filter_types_[status_filter.get_value()].first;
676
677 boost::dynamic_bitset<> res;
678 for(const auto& a : addons_) {
679 const addon_tracking_info& info = tracking_info_.at(a.second.id);
680
681 res.push_back(
682 (selection == FILTER_ALL) ||
683 (selection == FILTER_INSTALLED && is_installed_addon_status(info.state)) ||
684 (selection == FILTER_UPGRADABLE && info.state == ADDON_INSTALLED_UPGRADABLE) ||
685 (selection == FILTER_PUBLISHABLE && info.can_publish == true) ||
686 (selection == FILTER_NOT_INSTALLED && info.state == ADDON_NONE)
687 );
688 }
689
690 return res;
691}
692
693boost::dynamic_bitset<> addon_manager::get_tag_filter_visibility() const
694{
695 const auto& tag_filter = find_widget<const multimenu_button>("tag_filter");
696 const auto toggle_states = tag_filter.get_toggle_states();
697 if(toggle_states.none()) {
698 // Nothing selected. It means that all add-ons are shown.
699 boost::dynamic_bitset<> res_flipped(addons_.size());
700 return ~res_flipped;
701 }
702
703 std::vector<std::string> selected_tags;
704 for(std::size_t i = 0; i < tag_filter_types_.size(); ++i) {
705 if(toggle_states[i]) {
706 selected_tags.push_back(tag_filter_types_[i].id);
707 }
708 }
709
710 boost::dynamic_bitset<> res;
711 for(const auto& a : addons_) {
712 bool matched_tag = false;
713 for(const auto& id : selected_tags) {
714 if(utils::contains(a.second.tags, id)) {
715 matched_tag = true;
716 break;
717 }
718 }
719 res.push_back(matched_tag);
720 }
721
722 return res;
723}
724
725boost::dynamic_bitset<> addon_manager::get_type_filter_visibility() const
726{
727 const multimenu_button& type_filter = find_widget<const multimenu_button>("type_filter");
728
729 boost::dynamic_bitset<> toggle_states = type_filter.get_toggle_states();
730 if(toggle_states.none()) {
731 // Nothing selected. It means that *all* add-ons are shown.
732 boost::dynamic_bitset<> res_flipped(addons_.size());
733 return ~res_flipped;
734 } else {
735 boost::dynamic_bitset<> res;
736
737 for(const auto& a : addons_) {
738 int index = std::distance(type_filter_types_.begin(),
740 [](const std::pair<ADDON_TYPE, std::string>& entry) { return entry.first; }));
741 res.push_back(toggle_states[index]);
742 }
743 return res;
744 }
745}
746
747boost::dynamic_bitset<> addon_manager::get_lang_filter_visibility() const
748{
749 const multimenu_button& lang_filter = find_widget<const multimenu_button>("language_filter");
750
751 boost::dynamic_bitset<> toggle_states = lang_filter.get_toggle_states();
752
753 if(toggle_states.none()) {
754 boost::dynamic_bitset<> res_flipped(addons_.size());
755 return ~res_flipped;
756 } else {
757 boost::dynamic_bitset<> res;
758 for(const auto& a : addons_) {
759 bool retval = false;
760 // langcode -> string conversion vector, to be able to detect either
761 // langcodes or langstring entries
762 std::vector<std::string> lang_string_vector;
763 for (long unsigned int i = 0; i < a.second.locales.size(); i++) {
764 lang_string_vector.push_back(langcode_to_string(a.second.locales[i]));
765 }
766 // Find all toggle states, where toggle = true and lang = lang
767 for (long unsigned int i = 0; i < toggle_states.size(); i++) {
768 if (toggle_states[i] == true) {
769 // does lang_code match?
770 bool contains_lang_code = utils::contains(a.second.locales, language_filter_types_[i].second);
771 // does land_string match?
772 bool contains_lang_string = utils::contains(lang_string_vector, language_filter_types_[i].second);
773 if ((contains_lang_code || contains_lang_string) == true)
774 retval = true;
775 }
776 }
777 res.push_back(retval);
778 }
779 return res;
780 }
781}
782
784{
785 // In the small-screen layout, the text_box for the filter keeps keyboard focus even when the
786 // details panel is visible, which means this can be called when the list isn't visible. That
787 // causes problems both because find_widget can throw exceptions, but also because changing the
788 // filters can hide the currently-shown add-on, triggering a different one to be selected in a
789 // way that would seem random unless the user realised that they were typing into a filter box.
790 //
791 // Quick workaround is to not process the new filter if the list isn't visible.
792 auto list = find_widget<addon_list>("addons", false, false);
793 if(!list) {
794 return;
795 }
796
797 boost::dynamic_bitset<> res =
803 list->set_addon_shown(res);
804}
805
807{
808 const menu_button& order_menu = find_widget<const menu_button>("order_dropdown");
809 const addon_order& order_struct = all_orders_.at(order_menu.get_value() / 2);
810 sort_order::type order = order_menu.get_value() % 2 == 0 ? sort_order::type::ascending : sort_order::type::descending;
812 if(order == sort_order::type::ascending) {
813 func = order_struct.sort_func_asc;
814 } else {
815 func = order_struct.sort_func_desc;
816 }
817
818 find_widget<addon_list>("addons").set_addon_order(func);
819 prefs::get().set_addon_manager_saved_order_name(order_struct.as_preference);
821}
822
823void addon_manager::on_order_changed(unsigned int sort_column, sort_order::type order)
824{
825 menu_button& order_menu = find_widget<menu_button>("order_dropdown");
826 auto order_it = utils::ranges::find(all_orders_, static_cast<int>(sort_column), &addon_order::column_index);
827 int index = 2 * (std::distance(all_orders_.begin(), order_it));
828 if(order == sort_order::type::descending) {
829 ++index;
830 }
831 order_menu.set_value(index);
832 prefs::get().set_addon_manager_saved_order_name(order_it->as_preference);
834}
835
836template<void(addon_manager::*fptr)(const addon_info& addon)>
838{
839 // Explicitly return to the main page if we're in low-res mode so the list is visible.
840 if(stacked_widget* stk = find_widget<stacked_widget>("main_stack", false, false)) {
841 stk->select_layer(0);
842 find_widget<button>("details_toggle").set_label(_("Add-on Details"));
843 }
844
845 addon_list& addons = find_widget<addon_list>("addons");
846 // The pointer returned by get_selected_addon comes from the vector<addon_info*> owned by the addon_list.
847 // This is a pointer into the addons_list map owned by the addon_manager. Any modification to the map
848 // kicked off by this method call will cause that pointer to dangle. Copying the addon_info here prevents
849 // that.
850 const addon_info* p_addon = addons.get_selected_addon();
851 if(p_addon == nullptr) {
852 return;
853 }
854 addon_info selected_addon = *p_addon;
855
856 try {
857 (this->*fptr)(selected_addon);
858 } catch(const addons_client::user_exit&) {
859 // User canceled the op.
860 }
861}
862
864{
865 addon_info versioned_addon = addon;
866 if(addon.id == find_widget<addon_list>("addons").get_selected_addon()->id) {
867 widget* parent = this;
868 if(stacked_widget* stk = find_widget<stacked_widget>("main_stack", false, false)) {
869 parent = stk->get_layer_grid(1);
870 }
871 // At this point the version list should always be found, so error if it's not there
872 menu_button& list = parent->find_widget<menu_button>("version_filter");
873 versioned_addon.current_version = list.get_value_string();
874 }
875
877
878 // Take note if any wml_changes occurred
880
883 }
884}
885
887{
888 if(have_addon_pbl_info(addon.id) || have_addon_in_vcs_tree(addon.id)) {
890 _("The following add-on appears to have publishing or version control information stored locally, and will not be removed:")
891 + " " + addon.display_title_full());
892 return;
893 }
894
895 bool success = remove_local_addon(addon.id);
896
897 if(!success) {
898 gui2::show_error_message(_("The following add-on could not be deleted properly:") + " " + addon.display_title_full());
899 } else {
901
903 }
904}
905
907{
908 /* Currently, the install and update codepaths are the same, so this function simply
909 * calls the other. Since this might change in the future, I'm leaving this function
910 * here for now.
911 *
912 * - vultraz, 2017-03-12
913 */
914 install_addon(addon);
915}
916
918{
919 for(const auto& a : addons_) {
920 if(tracking_info_[a.first].state == ADDON_INSTALLED_UPGRADABLE) {
922
923 if(result.wml_changed) {
924 // Updating an add-on may have resulted in its dependencies being updated
925 // as well, so we need to reread version info blocks afterwards to make sure
926 // we don't try to re-download newly updated add-ons.
928 }
929
930 // Take note if any wml_changes occurred
932 }
933 }
934
937 }
938}
939
941{
942 // TODO: make this a separate method to avoid code duplication
943
944 // Explicitly return to the main page if we're in low-res mode so the list is visible.
945 if(stacked_widget* stk = find_widget<stacked_widget>("main_stack", false, false)) {
946 stk->select_layer(0);
947 find_widget<button>("details_toggle").set_label(_("Add-on Details"));
948 }
949
950 addon_list& addons = find_widget<addon_list>("addons");
951 const addon_info* addon = addons.get_selected_addon();
952
953 bool needs_refresh = false;
954 if(addon == nullptr) {
955 gui2::dialogs::addon_server_info::execute(client_, "", needs_refresh);
956 } else {
957 gui2::dialogs::addon_server_info::execute(client_, addon->id, needs_refresh);
958 }
959
960 if(needs_refresh) {
963 }
964}
965
966/** Performs all backend and UI actions for publishing the specified add-on. */
968{
969 std::string server_msg;
970
971 const std::string addon_id = addon.id;
972 // Since the user is planning to upload an addon, this is the right time to validate the .pbl.
973 config cfg = get_addon_pbl_info(addon_id, true);
974
975 const version_info& version_to_publish = cfg["version"].str();
976
977 if(version_to_publish <= tracking_info_[addon_id].remote_version) {
978 const int res = gui2::show_message(_("Warning"),
979 _("The remote version of this add-on is greater or equal to the version being uploaded. Do you really wish to continue?"),
981
982 if(res != gui2::retval::OK) {
983 return;
984 }
985 }
986
987 // if the passphrase isn't provided from the _server.pbl, try to pre-populate it from the preferences before prompting for it
988 if(cfg["passphrase"].empty()) {
989 cfg["passphrase"] = prefs::get().password(prefs::get().campaign_server(), cfg["author"]);
990 if(!gui2::dialogs::addon_auth::execute(cfg)) {
991 return;
992 } else {
993 prefs::get().set_password(prefs::get().campaign_server(), cfg["author"], cfg["passphrase"]);
994 }
995 } else if(cfg["forum_auth"].to_bool()) {
996 // if the uploader's forum password is present in the _server.pbl
997 gui2::show_error_message(_("The passphrase attribute cannot be present when forum_auth is used."));
998 return;
999 }
1000
1001 if(!cfg["icon"].empty() && !::image::exists(cfg["icon"].str())) {
1002 gui2::show_error_message(_("Invalid icon path. Make sure the path points to a valid image."));
1003 } else if(!client_.request_distribution_terms(server_msg)) {
1005 _("The server responded with an error:") + "\n" + client_.get_last_server_error(), true);
1006 } else if(gui2::dialogs::addon_license_prompt::execute(server_msg)) {
1007 if(!client_.upload_addon(addon_id, server_msg, cfg, tracking_info_[addon_id].state == ADDON_INSTALLED_LOCAL_ONLY)) {
1008 const std::string& msg = _("The add-on was rejected by the server:") +
1009 "\n\n" + client_.get_last_server_error();
1010 const std::string& extra_data = client_.get_last_server_error_data();
1011 if (!extra_data.empty()) {
1012 // TODO: Allow user to copy the extra data portion to clipboard
1013 // or something, maybe display it in a dialog similar to
1014 // the WML load errors report in a monospace font and
1015 // stuff (having a scroll container is especially
1016 // important since a long list can cause the dialog to
1017 // overflow).
1018 gui2::show_error_message(msg + "\n\n" + extra_data, true);
1019 } else {
1020 gui2::show_error_message(msg, true);
1021 }
1022 } else {
1023 gui2::show_transient_message(_("Response"), server_msg);
1026 }
1027 }
1028}
1029
1030/** Performs all backend and UI actions for taking down the specified add-on. */
1032{
1033 const std::string addon_id = addon.id;
1034 const std::string& text = VGETTEXT(
1035 "Deleting '$addon|' will permanently erase its download and upload counts on the add-ons server. Do you really wish to continue?",
1036 {{"addon", make_addon_title(addon_id)}} // FIXME: need the real title!
1037 );
1038
1039 const int res = gui2::show_message(_("Confirm"), text, gui2::dialogs::message::yes_no_buttons);
1040
1041 if(res != gui2::retval::OK) {
1042 return;
1043 }
1044
1045 std::string server_msg;
1046 if(!client_.delete_remote_addon(addon_id, server_msg)) {
1047 gui2::show_error_message(_("The server responded with an error:") + "\n" + client_.get_last_server_error(), true);
1048 } else {
1049 // FIXME: translation needed!
1050 gui2::show_transient_message(_("Response"), server_msg);
1053 }
1054}
1055
1056/** Called when the player double-clicks an add-on. */
1058{
1059 switch(tracking_info_[addon.id].state) {
1060 case ADDON_NONE:
1061 install_addon(addon);
1062 break;
1063 case ADDON_INSTALLED:
1064 if(!tracking_info_[addon.id].can_publish) {
1065 utils::string_map symbols{ { "addon", addon.display_title_full() } };
1066 int res = gui2::show_message(_("Uninstall add-on"),
1067 VGETTEXT("Do you want to uninstall '$addon|'?", symbols),
1069 if(res == gui2::retval::OK) {
1070 uninstall_addon(addon);
1071 }
1072 }
1073 break;
1075 update_addon(addon);
1076 break;
1079 publish_addon(addon);
1080 break;
1081 default:
1082 break;
1083 }
1084}
1085
1087{
1088 help::show_help("installing_addons");
1089}
1090
1091static std::string format_addon_time(const std::chrono::system_clock::time_point& time)
1092{
1093 if(time == std::chrono::system_clock::time_point{}) {
1094 return font::unicode_em_dash;
1095 }
1096
1097 const std::string format = prefs::get().use_twelve_hour_clock_format()
1098 // TRANSLATORS: Month + day of month + year + 12-hour time, eg 'November 02 2021, 1:59 PM'. Format for your locale.
1099 // Format reference: https://www.boost.org/doc/libs/1_85_0/doc/html/date_time/date_time_io.html#date_time.format_flags
1100 ? _("%B %d %Y, %I:%M %p")
1101 // TRANSLATORS: Month + day of month + year + 24-hour time, eg 'November 02 2021, 13:59'. Format for your locale.
1102 // Format reference: https://www.boost.org/doc/libs/1_85_0/doc/html/date_time/date_time_io.html#date_time.format_flags
1103 : _("%B %d %Y, %H:%M");
1104
1106}
1107
1109{
1110 widget* parent = this;
1111 const addon_info* info = nullptr;
1112 if(stacked_widget* stk = find_widget<stacked_widget>("main_stack", false, false)) {
1113 parent = stk->get_layer_grid(1);
1114 info = stk->get_layer_grid(0)->find_widget<addon_list>("addons").get_selected_addon();
1115 } else {
1116 info = find_widget<addon_list>("addons").get_selected_addon();
1117 }
1118
1119 if(info == nullptr) {
1120 return;
1121 }
1122
1123 parent->find_widget<drawing>("image").set_label(info->display_icon());
1124 parent->find_widget<styled_widget>("title").set_label(info->display_title_translated_or_original());
1125 parent->find_widget<styled_widget>("description").set_label(info->description_translated());
1126 menu_button& version_filter = parent->find_widget<menu_button>("version_filter");
1127 parent->find_widget<styled_widget>("author").set_label(info->author);
1128 parent->find_widget<styled_widget>("type").set_label(info->display_type());
1129
1132 status.set_use_markup(true);
1133
1135 parent->find_widget<styled_widget>("downloads").set_label(std::to_string(info->downloads));
1138
1139 parent->find_widget<styled_widget>("dependencies").set_label(!info->depends.empty()
1140 ? make_display_dependencies(info->id, addons_, tracking_info_)
1141 : _("addon_dependencies^None"));
1142
1143 std::string languages;
1144
1145 for(const auto& lc : info->locales) {
1146 const std::string& langlabel = langcode_to_string(lc);
1147 if(!langlabel.empty()) {
1148 if(!languages.empty()) {
1149 languages += ", ";
1150 }
1151 languages += langlabel;
1152 }
1153 }
1154
1155 parent->find_widget<styled_widget>("translations").set_label(!languages.empty() ? languages : _("translations^None"));
1156
1157 const std::string& feedback_url = info->feedback_url;
1158 parent->find_widget<label>("url").set_label(!feedback_url.empty() ? feedback_url : _("url^None"));
1159 parent->find_widget<label>("id").set_label(info->id);
1160
1161 bool installed = is_installed_addon_status(tracking_info_[info->id].state);
1162 bool updatable = tracking_info_[info->id].state == ADDON_INSTALLED_UPGRADABLE;
1163
1164 stacked_widget& action_stack = parent->find_widget<stacked_widget>("action_stack");
1165 // #TODO: Add tooltips with upload time and pack size
1166 std::vector<config> version_filter_entries;
1167
1168 if(!tracking_info_[info->id].can_publish) {
1169 action_stack.select_layer(0);
1170
1171 stacked_widget& install_update_stack = parent->find_widget<stacked_widget>("install_update_stack");
1172 install_update_stack.select_layer(updatable ? 1 : 0);
1173
1174 if(!updatable) {
1175 parent->find_widget<button>("install").set_active(!installed);
1176 } else {
1177 parent->find_widget<button>("update").set_active(true);
1178 }
1179
1180 parent->find_widget<button>("uninstall").set_active(installed);
1181
1182 for(const auto& f : info->versions) {
1183 version_filter_entries.emplace_back("label", f.str());
1184 }
1185 } else {
1186 action_stack.select_layer(1);
1187
1188 // Always enable the publish button, but disable the delete button if not yet published.
1189 parent->find_widget<button>("publish").set_active(true);
1190 parent->find_widget<button>("delete").set_active(!info->local_only);
1191
1192 // Show only the version to be published
1193 version_filter_entries.emplace_back("label", info->current_version.str());
1194 }
1195
1196 version_filter.set_values(version_filter_entries);
1197 version_filter.set_active(version_filter_entries.size() > 1);
1198}
1199
1201{
1202 widget* parent = this;
1203 const addon_info* info = nullptr;
1204 if(stacked_widget* stk = find_widget<stacked_widget>("main_stack", false, false)) {
1205 parent = stk->get_layer_grid(1);
1206 info = stk->get_layer_grid(0)->find_widget<addon_list>("addons").get_selected_addon();
1207 } else {
1208 info = find_widget<addon_list>("addons").get_selected_addon();
1209 }
1210
1211 if(info == nullptr) {
1212 return;
1213 }
1214
1215 if(!tracking_info_[info->id].can_publish && is_installed_addon_status(tracking_info_[info->id].state)) {
1216 bool updatable = tracking_info_[info->id].installed_version
1217 != parent->find_widget<menu_button>("version_filter").get_value_string();
1218 stacked_widget& action_stack = parent->find_widget<stacked_widget>("action_stack");
1219 action_stack.select_layer(0);
1220
1221 stacked_widget& install_update_stack = parent->find_widget<stacked_widget>("install_update_stack");
1222 install_update_stack.select_layer(1);
1223 parent->find_widget<button>("update").set_active(updatable);
1224 }
1225}
1226
1228{
1231 return false;
1232 }
1233
1234 return true;
1235}
1236
1237} // namespace dialogs
bool remove_local_addon(const std::string &addon)
Removes the specified add-on, deleting its full directory structure.
Definition manager.cpp:138
config get_addon_pbl_info(const std::string &addon_name, bool do_validate)
Gets the publish information for an add-on.
Definition manager.cpp:71
bool have_addon_in_vcs_tree(const std::string &addon_name)
Returns whether the specified add-on appears to be managed by a VCS or not.
Definition manager.cpp:57
std::vector< std::string > available_addons()
Returns a list of local add-ons that can be published.
Definition manager.cpp:181
bool have_addon_pbl_info(const std::string &addon_name)
Returns whether a .pbl file is present for the specified add-on or not.
Definition manager.cpp:66
std::vector< std::string > installed_addons()
Retrieves the names of all installed add-ons.
Definition manager.cpp:186
void refresh_addon_version_info_cache()
Refreshes the per-session cache of add-on's version information structs.
Definition manager.cpp:381
#define GETTEXT_DOMAIN
Add-ons (campaignd) client class.
Definition client.hpp:41
install_result install_addon_with_checks(const addons_list &addons, const addon_info &addon)
Performs an add-on download and install cycle.
Definition client.cpp:725
const std::string & server_version() const
Definition client.hpp:236
const std::string & get_last_server_error_data() const
Returns the last error message extra data sent by the server, or an empty string.
Definition client.hpp:80
@ abort
User aborted the operation because of an issue with dependencies or chose not to overwrite the add-on...
bool delete_remote_addon(const std::string &id, std::string &response_message, const std::set< std::string > &admin_set={})
Requests the specified add-on to be removed from the server.
Definition client.cpp:417
bool using_tls() const
Returns whether the current connection uses TLS.
Definition client.hpp:226
const std::string & server_id() const
Definition client.hpp:231
const std::string & addr() const
Returns the current hostname:port used for this connection.
Definition client.hpp:74
bool request_distribution_terms(std::string &terms)
Request the add-ons server distribution terms message.
Definition client.cpp:274
bool request_addons_list(config &cfg, bool icons)
Request the add-ons list from the server.
Definition client.cpp:253
bool upload_addon(const std::string &id, std::string &response_message, config &cfg, bool local_only)
Uploads an add-on to the server.
Definition client.cpp:297
const std::string & get_last_server_error() const
Returns the last error message sent by the server, or an empty string.
Definition client.hpp:77
A config object defines a single node in a WML file, with access to child nodes.
Definition config.hpp:157
config & add_child(std::string_view key)
Definition config.cpp:436
const_attr_itors attribute_range() const
Definition config.cpp:740
child_itors child_range(std::string_view key)
Definition config.cpp:268
boost::iterator_range< const_child_iterator > const_child_itors
Definition config.hpp:281
std::ostringstream wrapper.
Definition formatter.hpp:40
void set_uninstall_function(addon_op_func_t function)
Sets the function to call when the player clicks the uninstall button.
std::function< bool(const addon_info &, const addon_info &)> addon_sort_func
void set_publish_function(addon_op_func_t function)
Sets the function to upload an addon to the addons server.
void add_list_to_keyboard_chain()
Adds the internal listbox to the keyboard event chain.
void set_install_function(addon_op_func_t function)
Sets the function to call when the player clicks the install button.
void set_delete_function(addon_op_func_t function)
Sets the function to install an addon from the addons server.
const addon_info * get_selected_addon() const
Returns the selected add-on.
void set_update_function(addon_op_func_t function)
Sets the function to call when the player clicks the update button.
void set_callback_order_change(std::function< void(unsigned, sort_order::type)> callback)
Sets up a callback that will be called when the player changes the sorting order.
static const int DEFAULT_ACTION_RETVAL
Special retval for the toggle panels in the addons list.
static std::string colorize_addon_state_string(const std::string &str, ADDON_STATUS state, bool verbose=false)
Changes the color of an add-on state string (installed, outdated, etc.) according to the state itself...
void set_addons(const addons_list &addons)
Sets the add-ons to show.
Simple push button.
Definition button.hpp:36
virtual void set_active(const bool active) override
See styled_widget::set_active.
void uninstall_addon(const addon_info &addon)
Definition manager.cpp:886
void on_order_changed(unsigned int sort_column, sort_order::type order)
Definition manager.cpp:823
static const std::vector< std::pair< ADDON_STATUS_FILTER, std::string > > status_filter_types_
Definition manager.hpp:86
virtual void pre_show() override
Actions to be taken before showing the window.
Definition manager.cpp:294
boost::dynamic_bitset get_lang_filter_visibility() const
Definition manager.cpp:747
addons_client & client_
Definition manager.hpp:80
boost::dynamic_bitset get_status_filter_visibility() const
Definition manager.cpp:672
void execute_default_action(const addon_info &addon)
Called when the player double-clicks an add-on.
Definition manager.cpp:1057
void reload_list_and_reselect_item(const std::string &id)
Definition manager.cpp:642
std::vector< std::pair< int, std::string > > language_filter_types_
Definition manager.hpp:88
void execute_default_action_on_selected_addon()
Definition manager.hpp:127
boost::dynamic_bitset get_name_filter_visibility() const
Definition manager.cpp:651
void toggle_details(button &btn, stacked_widget &stk)
Definition manager.cpp:569
void delete_addon(const addon_info &addon)
Performs all backend and UI actions for taking down the specified add-on.
Definition manager.cpp:1031
addon_manager(addons_client &client)
Definition manager.cpp:228
config cfg_
Config which contains the list with the campaigns.
Definition manager.hpp:78
boost::dynamic_bitset get_type_filter_visibility() const
Definition manager.cpp:725
addons_tracking_list tracking_info_
Definition manager.hpp:84
static const std::vector< addon_order > all_orders_
Definition manager.hpp:89
boost::dynamic_bitset get_tag_filter_visibility() const
Definition manager.cpp:693
static const std::vector< std::pair< ADDON_TYPE, std::string > > type_filter_types_
Definition manager.hpp:87
void install_addon(const addon_info &addon)
Definition manager.cpp:863
void update_addon(const addon_info &addon)
Definition manager.cpp:906
void publish_addon(const addon_info &addon)
Performs all backend and UI actions for publishing the specified add-on.
Definition manager.cpp:967
@ yes_no_buttons
Shows a yes and no button.
Definition message.hpp:81
@ ok_cancel_buttons
Shows an ok and cancel button.
Definition message.hpp:77
Abstract base class for all modal dialogs.
Base container class.
Definition grid.hpp:32
widget * find(const std::string_view id, const bool must_be_active) override
See widget::find.
Definition grid.cpp:645
void set_link_aware(bool l)
Definition label.cpp:89
virtual void set_value(unsigned value, bool fire_event=false) override
Inherited from selectable_item.
std::string get_value_string() const
Returns the value of the selected row.
void set_values(const std::vector<::config > &values, unsigned selected=0)
virtual unsigned get_value() const override
Inherited from selectable_item.
void set_values(const std::vector<::config > &values)
Set the available menu options.
boost::dynamic_bitset get_toggle_states() const
Get the current state of the menu options.
int current_layer() const
Gets the current visible layer number.
grid * get_layer_grid(unsigned int i)
Gets the grid for a specified layer.
void select_layer(const int layer)
Selects and displays a particular layer.
virtual void set_label(const t_string &text)
virtual void set_use_markup(bool use_markup)
std::string get_value() const
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
widget * parent()
Definition widget.cpp:170
void set_enter_disabled(const bool enter_disabled)
Disable the enter key.
Definition window.hpp:313
void keyboard_capture(widget *widget)
Definition window.cpp:1197
void close()
Requests to close the window.
Definition window.hpp:216
status
The status of the window.
Definition window.hpp:203
void set_escape_disabled(const bool escape_disabled)
Disable the escape key.
Definition window.hpp:326
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
static prefs & get()
void set_addon_manager_saved_order_direction(sort_order::type value)
void set_password(const std::string &server, const std::string &login, const std::string &key)
sort_order::type addon_manager_saved_order_direction()
bool use_twelve_hour_clock_format()
std::string password(const std::string &server, const std::string &login)
Represents version numbers.
std::string str() const
Serializes the version number into string form.
Definitions for the interface to Wesnoth Markup Language (WML).
const config * cfg
#define VGETTEXT(msgid,...)
Handy wrappers around interpolate_variables_into_string and gettext.
#define N_(String)
Definition gettext.hpp:108
static std::string _(const char *str)
Definition gettext.hpp:100
std::string label
What to show in the filter's drop-down list.
Definition manager.cpp:201
const std::vector< std::string > filtertext_
Definition manager.cpp:87
std::string tooltip
Shown when hovering over an entry in the filter's drop-down list.
Definition manager.cpp:203
std::string id
Text to match against addon_info.tags()
Definition manager.cpp:199
This file contains the window object, this object is a top level container which has the event manage...
std::string size_display_string(double size)
Get a human-readable representation of the specified byte count.
Definition info.cpp:294
std::string make_addon_title(const std::string &id)
Replaces underscores to dress up file or dirnames as add-on titles.
Definition info.cpp:299
void read_addons_list(const config &cfg, addons_list &dest)
Parse the specified add-ons list WML into an actual addons_list object.
Definition info.cpp:277
std::map< std::string, addon_info > addons_list
Definition info.hpp:28
std::vector< language_def > get_languages(bool all)
Return a list of available translations.
Definition language.cpp:159
#define REGISTER_DIALOG(window_id)
Wrapper for REGISTER_DIALOG2.
auto get_local_timestamp(const std::chrono::system_clock::time_point &time)
Definition chrono.hpp:63
const std::string unicode_em_dash
Definition constants.cpp:44
bool addon_server_info
const bool & debug
static std::string describe_status_verbose(const addon_tracking_info &state)
Definition manager.cpp:239
static std::string format_addon_time(const std::chrono::system_clock::time_point &time)
Definition manager.cpp:1091
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_error_message(const std::string &msg, bool message_use_markup)
Shows an error message to the user.
Definition message.cpp:201
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
retval
Default window/dialog return values.
Definition retval.hpp:30
@ OK
Dialog was closed with the OK button.
Definition retval.hpp:35
void show_help(const std::string &show_topic)
Open the help browser.
Definition help.cpp:84
bool exists(const image::locator &i_locator)
Returns true if the given image actually exists, without loading it.
Definition picture.cpp:855
std::string translate_timestamp(time_t time, std::string_view format)
Translates the passed timestamp and returns it in the requested format.
Definition gettext.cpp:577
bool ci_search(const std::string &s1, const std::string &s2)
Case-insensitive search.
Definition gettext.cpp:559
auto find(Container &container, const Value &value, const Projection &projection={})
Definition general.hpp:213
std::string interpolate_variables_into_string(const std::string &str, const string_map *const symbols)
Function which will interpolate variables, starting with '$' in the string 'str' with the equivalent ...
bool contains(const Container &container, const Value &value)
Returns true iff value is found in container.
Definition general.hpp:88
std::map< std::string, t_string > string_map
std::vector< std::string > split(const config_attribute_value &val)
std::unique_ptr< MIX_Audio, decltype(&MIX_DestroyAudio)> value
Definition sound.cpp:139
addon_tracking_info get_addon_tracking_info(const addon_info &addon)
Get information about an add-on comparing its local state with the add-ons server entry.
Definition state.cpp:25
bool is_installed_addon_status(ADDON_STATUS s)
Definition state.hpp:40
@ ADDON_NOT_TRACKED
No tracking information available.
Definition state.hpp:37
@ ADDON_INSTALLED_OUTDATED
Version in the server is older than local installation.
Definition state.hpp:30
@ ADDON_NONE
Add-on is not installed.
Definition state.hpp:24
@ ADDON_INSTALLED_UPGRADABLE
Version in the server is newer than local installation.
Definition state.hpp:28
@ ADDON_INSTALLED
Version in the server matches local installation.
Definition state.hpp:26
@ ADDON_INSTALLED_LOCAL_ONLY
No version in the server.
Definition state.hpp:32
@ ADDON_INSTALLED_BROKEN
Dependencies not satisfied.
Definition state.hpp:35
ADDON_STATUS_FILTER
Add-on installation status filters for the user interface.
Definition state.hpp:84
@ FILTER_INSTALLED
Definition state.hpp:86
@ FILTER_PUBLISHABLE
Definition state.hpp:88
@ FILTER_NOT_INSTALLED
Definition state.hpp:89
@ FILTER_ALL
Definition state.hpp:85
@ FILTER_UPGRADABLE
Definition state.hpp:87
std::map< std::string, addon_tracking_info > addons_tracking_list
Definition state.hpp:64
std::vector< std::string > tags
Definition info.hpp:92
version_info current_version
Definition info.hpp:81
std::string display_type() const
Get an add-on type identifier for display in the user's language.
Definition info.cpp:232
int downloads
Definition info.hpp:87
std::chrono::system_clock::time_point updated
Definition info.hpp:102
std::string title
Definition info.hpp:76
std::string display_title_translated_or_original() const
Definition info.cpp:198
std::chrono::system_clock::time_point created
Definition info.hpp:103
std::string author
Definition info.hpp:84
std::string display_title_full() const
Definition info.cpp:210
std::set< std::string > resolve_dependencies(const addons_list &addons) const
Resolve an add-on's dependency tree in a recursive fashion.
Definition info.cpp:264
int size
Definition info.hpp:86
std::vector< std::string > locales
Definition info.hpp:93
std::string id
Definition info.hpp:75
ADDON_TYPE type
Definition info.hpp:90
Stores additional status information about add-ons.
Definition state.hpp:47
ADDON_STATUS state
Definition state.hpp:57
version_info installed_version
Definition state.hpp:60
Contains the outcome of an add-on install operation.
Definition client.hpp:134
install_outcome outcome
Overall outcome of the operation.
Definition client.hpp:138
bool wml_changed
Specifies if WML on disk was altered and needs to be reloaded.
Definition client.hpp:147
addon_list::addon_sort_func sort_func_desc
Definition manager.hpp:52
addon_list::addon_sort_func sort_func_asc
Definition manager.hpp:51
std::string as_preference
The value used in the preferences file.
Definition manager.hpp:49
Exception thrown when the WML parser fails to read a .pbl file.
Definition manager.hpp:45
static map_location::direction s
@ ADDON_UNKNOWN
a.k.a.
@ ADDON_THEME
GUI2 Themes.
@ ADDON_SP_SCENARIO
Single-player scenario.
@ ADDON_MP_SCENARIO
Multiplayer scenario.
@ ADDON_MP_CAMPAIGN
Multiplayer campaign.
@ ADDON_MP_FACTION
Multiplayer faction.
@ ADDON_MEDIA
Miscellaneous content/media (unit packs, terrain packs, music packs, etc.).
@ ADDON_MP_ERA
Multiplayer era.
@ ADDON_CORE
Total Conversion Core.
@ ADDON_SP_CAMPAIGN
Single-player campaign.
@ ADDON_OTHER
an add-on that fits in no other category
@ ADDON_SP_MP_CAMPAIGN
Hybrid campaign.
@ ADDON_MP_MAPS
Multiplayer plain (no WML) map pack.
@ ADDON_MOD
Modification of the game.
#define i
#define f
#define b