The Battle for Wesnoth  1.19.22+dev
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"
29 #include "gui/dialogs/message.hpp"
31 #include "gui/widgets/button.hpp"
32 #include "gui/widgets/label.hpp"
36 #include "gui/widgets/drawing.hpp"
37 #include "gui/widgets/text_box.hpp"
38 #include "gui/widgets/window.hpp"
40 #include "serialization/chrono.hpp"
42 #include "formula/string_utils.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 
53 namespace gui2::dialogs
54 {
55 
56 namespace {
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,
92  const addons_list& addons_list,
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 
142 REGISTER_DIALOG(addon_manager)
143 
144 const 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 
152 const 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 
170 const 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 
194 namespace
195 {
196 struct 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 
206 const 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 
239 static 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;
256  case ADDON_NOT_TRACKED:
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 {
296  set_escape_disabled(true);
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 
347  load_addon_list();
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 = {
386  {ADDON_SP_CAMPAIGN, 0},
387  {ADDON_SP_SCENARIO, 0},
389  {ADDON_MP_CAMPAIGN, 0},
390  {ADDON_MP_SCENARIO, 0},
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 
499  find_widget<button>("install"),
500  std::bind(&addon_manager::install_selected_addon, this));
501 
503  find_widget<button>("uninstall"),
504  std::bind(&addon_manager::uninstall_selected_addon, this));
505 
507  find_widget<button>("update"),
508  std::bind(&addon_manager::update_selected_addon, this));
509 
511  find_widget<button>("publish"),
512  std::bind(&addon_manager::publish_selected_addon, this));
513 
515  find_widget<button>("delete"),
516  std::bind(&addon_manager::delete_selected_addon, this));
517 
520  find_widget<button>("info"),
521  std::bind(&addon_manager::info, this));
522  } else {
523  find_widget<button>("info").set_visible(false);
524  }
525 
527  find_widget<button>("update_all"),
528  std::bind(&addon_manager::update_all_addons, this));
529 
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");
537  std::bind(&addon_manager::toggle_details, this, std::ref(btn), std::ref(*stk)));
538 
539  stk->select_layer(0);
540 
542  stk->get_layer_grid(1)->find_widget<menu_button>("version_filter"),
544  } else {
546  find_widget<menu_button>("version_filter"),
548  }
549 
550  on_addon_select();
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
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
566  set_exit_hook(window::exit_hook::always, [this] { return exit_hook(); });
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 
639  apply_filters();
640 }
641 
643 {
644  load_addon_list();
645 
646  // Reselect the add-on.
647  find_widget<addon_list>("addons").select_addon(id);
648  on_addon_select();
649 }
650 
651 boost::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 
672 boost::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 
693 boost::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 
725 boost::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 
747 boost::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 
823 void 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 
836 template<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 
936  load_addon_list();
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 {
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 
1091 static 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 
1105  return chrono::format_local_timestamp(time, format);
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 
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();
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
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 > available_addons()
Returns a list of local add-ons that can be published.
Definition: manager.cpp:181
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
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 & 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...
const std::string & server_id() const
Definition: client.hpp:231
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
bool request_distribution_terms(std::string &terms)
Request the add-ons server distribution terms message.
Definition: client.cpp:274
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
const std::string & addr() const
Returns the current hostname:port used for this connection.
Definition: client.hpp:74
bool request_addons_list(config &cfg, bool icons)
Request the add-ons list from the server.
Definition: client.cpp:253
const std::string & server_version() const
Definition: client.hpp:236
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
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.
Definition: addon_list.hpp:77
std::function< bool(const addon_info &, const addon_info &)> addon_sort_func
Definition: addon_list.hpp:40
void set_publish_function(addon_op_func_t function)
Sets the function to upload an addon to the addons server.
Definition: addon_list.hpp:89
void add_list_to_keyboard_chain()
Adds the internal listbox to the keyboard event chain.
Definition: addon_list.cpp:369
void set_install_function(addon_op_func_t function)
Sets the function to call when the player clicks the install button.
Definition: addon_list.hpp:71
void set_delete_function(addon_op_func_t function)
Sets the function to install an addon from the addons server.
Definition: addon_list.hpp:95
const addon_info * get_selected_addon() const
Returns the selected add-on.
Definition: addon_list.cpp:314
void set_update_function(addon_op_func_t function)
Sets the function to call when the player clicks the update button.
Definition: addon_list.hpp:83
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.
Definition: addon_list.hpp:130
static const int DEFAULT_ACTION_RETVAL
Special retval for the toggle panels in the addons list.
Definition: addon_list.hpp:45
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...
Definition: addon_list.cpp:64
void set_addons(const addons_list &addons)
Sets the add-ons to show.
Definition: addon_list.cpp:157
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.
Definition: menu_button.hpp:58
std::string get_value_string() const
Returns the value of the selected row.
Definition: menu_button.hpp:64
void set_values(const std::vector<::config > &values, unsigned selected=0)
virtual unsigned get_value() const override
Inherited from selectable_item.
Definition: menu_button.hpp:55
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
const std::string & id() const
Definition: widget.cpp:110
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
void set_addon_manager_saved_order_direction(sort_order::type value)
static prefs & get()
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.
std::size_t i
Definition: function.cpp:1031
#define N_(String)
Definition: gettext.hpp:105
static std::string _(const char *str)
Definition: gettext.hpp:97
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
#define GETTEXT_DOMAIN
Definition: manager.cpp:16
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:27
std::vector< language_def > get_languages(bool all)
Return a list of available translations.
Definition: language.cpp:137
auto format_local_timestamp(const std::chrono::system_clock::time_point &time, std::string_view format="%F %T")
Definition: chrono.hpp:62
std::deque< std::unique_ptr< editor_action > > action_stack
Action stack typedef.
const std::string unicode_em_dash
Definition: constants.cpp:44
bool addon_server_info
Definition: game_config.cpp:79
const bool & debug
Definition: game_config.cpp:95
static std::string describe_status_verbose(const addon_tracking_info &state)
Definition: manager.cpp:239
REGISTER_DIALOG(editor_edit_unit)
static std::string format_addon_time(const std::chrono::system_clock::time_point &time)
Definition: manager.cpp:1091
void connect_signal_notify_modified(dispatcher &dispatcher, const signal_notification &signal)
Connects a signal handler for getting a notification upon modification.
Definition: dispatcher.cpp:189
void connect_signal_mouse_left_click(dispatcher &dispatcher, const signal &signal)
Connects a signal handler for a left mouse button click.
Definition: dispatcher.cpp:163
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:83
bool exists(const image::locator &i_locator)
Returns true if the given image actually exists, without loading it.
Definition: picture.cpp:851
bool ci_search(const std::string &s1, const std::string &s2)
Case-insensitive search.
Definition: gettext.cpp:559
std::size_t size(std::string_view str)
Length in characters of a UTF-8 string.
Definition: unicode.cpp:81
std::size_t index(std::string_view str, const std::size_t index)
Codepoint index corresponding to the nth character in a UTF-8 string.
Definition: unicode.cpp:70
auto find(Container &container, const Value &value, const Projection &projection={})
Definition: general.hpp:196
constexpr auto filter
Definition: ranges.hpp:42
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:87
std::map< std::string, t_string > string_map
std::vector< std::string > split(const config_attribute_value &val)
static void msg(const char *act, debug_info &i, const char *to="", const char *result="")
Definition: debugger.cpp:109
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.
Definition: validation.hpp:103
@ ADDON_THEME
GUI2 Themes.
Definition: validation.hpp:115
@ ADDON_SP_SCENARIO
Single-player scenario.
Definition: validation.hpp:106
@ ADDON_MP_SCENARIO
Multiplayer scenario.
Definition: validation.hpp:109
@ ADDON_MP_CAMPAIGN
Multiplayer campaign.
Definition: validation.hpp:108
@ ADDON_MP_FACTION
Multiplayer faction.
Definition: validation.hpp:112
@ ADDON_MEDIA
Miscellaneous content/media (unit packs, terrain packs, music packs, etc.).
Definition: validation.hpp:114
@ ADDON_MP_ERA
Multiplayer era.
Definition: validation.hpp:111
@ ADDON_CORE
Total Conversion Core.
Definition: validation.hpp:104
@ ADDON_SP_CAMPAIGN
Single-player campaign.
Definition: validation.hpp:105
@ ADDON_OTHER
an add-on that fits in no other category
Definition: validation.hpp:116
@ ADDON_SP_MP_CAMPAIGN
Hybrid campaign.
Definition: validation.hpp:107
@ ADDON_MP_MAPS
Multiplayer plain (no WML) map pack.
Definition: validation.hpp:110
@ ADDON_MOD
Modification of the game.
Definition: validation.hpp:113
#define f
#define b