The Battle for Wesnoth  1.19.8+dev
manager.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2008 - 2024
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 
25 #include "help/help.hpp"
26 #include "gettext.hpp"
30 #include "gui/dialogs/message.hpp"
32 #include "gui/widgets/button.hpp"
33 #include "gui/widgets/label.hpp"
37 #include "gui/widgets/drawing.hpp"
38 #include "gui/widgets/text_box.hpp"
39 #include "gui/widgets/window.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  list.set_modified_signal_handler([this]() { 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 = std::find_if(all_orders_.begin(), all_orders_.end(),
473  [&saved_order_name](const addon_order& order) {return order.as_preference == saved_order_name;});
474  if(order_it != all_orders_.end()) {
475  int index = 2 * (std::distance(all_orders_.begin(), order_it));
477  if(saved_order_direction == sort_order::type::ascending) {
478  func = order_it->sort_func_asc;
479  } else {
480  func = order_it->sort_func_desc;
481  ++index;
482  }
483  find_widget<menu_button>("order_dropdown").set_value(index);
484  auto& addons = find_widget<addon_list>("addons");
485  addons.set_addon_order(func);
486  addons.select_first_addon();
487  }
488  }
489  }
490 
491  connect_signal_notify_modified(order_dropdown,
492  std::bind(&addon_manager::order_addons, this));
493 
494  label& url_label = find_widget<label>("url");
495 
496  url_label.set_use_markup(true);
497  url_label.set_link_aware(true);
498 
500  find_widget<button>("install"),
501  std::bind(&addon_manager::install_selected_addon, this));
502 
504  find_widget<button>("uninstall"),
505  std::bind(&addon_manager::uninstall_selected_addon, this));
506 
508  find_widget<button>("update"),
509  std::bind(&addon_manager::update_selected_addon, this));
510 
512  find_widget<button>("publish"),
513  std::bind(&addon_manager::publish_selected_addon, this));
514 
516  find_widget<button>("delete"),
517  std::bind(&addon_manager::delete_selected_addon, this));
518 
521  find_widget<button>("info"),
522  std::bind(&addon_manager::info, this));
523  } else {
524  find_widget<button>("info").set_visible(false);
525  }
526 
528  find_widget<button>("update_all"),
529  std::bind(&addon_manager::update_all_addons, this));
530 
532  find_widget<button>("show_help"),
533  std::bind(&addon_manager::show_help, this));
534 
535  if(stacked_widget* stk = find_widget<stacked_widget>("main_stack", false, false)) {
536  button& btn = find_widget<button>("details_toggle");
538  std::bind(&addon_manager::toggle_details, this, std::ref(btn), std::ref(*stk)));
539 
540  stk->select_layer(0);
541 
543  stk->get_layer_grid(1)->find_widget<menu_button>("version_filter"),
545  } else {
547  find_widget<menu_button>("version_filter"),
549  }
550 
551  on_addon_select();
552 
553  set_enter_disabled(true);
554 
557 
558  list.set_callback_order_change(std::bind(&addon_manager::on_order_changed, this, std::placeholders::_1, std::placeholders::_2));
559 
560  // Use handle the special addon_list retval to allow installing addons on double click
561  set_exit_hook(window::exit_hook::always, [this] { return exit_hook(); });
562 }
563 
565 {
566  if(stk.current_layer() == 0) {
567  btn.set_label(_("addons^Back to List"));
568  stk.select_layer(1);
569  } else {
570  btn.set_label(_("Add-on Details"));
571  stk.select_layer(0);
572  }
573 }
574 
576 {
577  bool success = client_.request_addons_list(cfg_, prefs::get().addon_icons());
578  if(!success) {
579  gui2::show_error_message(_("An error occurred while downloading the add-ons list from the server."));
580  close();
581  }
582 }
583 
585 {
588  }
589 
591 
592  std::vector<std::string> publishable_addons = available_addons();
593 
594  for(std::string id : publishable_addons) {
595  if(addons_.find(id) == addons_.end()) {
596  // Get a config from the addon's pbl file
597  // Note that the name= key is necessary or stuff breaks, since the filter code uses this key
598  // to match add-ons in the config list. It also fills in addon_info's id field. It's also
599  // neccessay to set local_only here so that flag can be properly set after addons_ is cleared
600  // and recreated by read_addons_list.
601  try {
602  config pbl_cfg = get_addon_pbl_info(id, false);
603  pbl_cfg["name"] = id;
604  pbl_cfg["local_only"] = true;
605 
606  // Add the add-on to the list.
607  addon_info addon(pbl_cfg);
608  addons_[id] = addon;
609 
610  // Add the addon to the config entry
611  cfg_.add_child("campaign", std::move(pbl_cfg));
612  } catch(invalid_pbl_exception&) {}
613  }
614  }
615 
616  if(addons_.empty()) {
617  show_transient_message(_("No Add-ons Available"), _("There are no add-ons available for download from this server."));
618  }
619 
620  addon_list& list = find_widget<addon_list>("addons");
621  list.set_addons(addons_);
622 
623  bool has_upgradable_addons = false;
624  for(const auto& a : addons_) {
625  tracking_info_[a.first] = get_addon_tracking_info(a.second);
626 
627  if(tracking_info_[a.first].state == ADDON_INSTALLED_UPGRADABLE) {
628  has_upgradable_addons = true;
629  }
630  }
631 
632  find_widget<button>("update_all").set_active(has_upgradable_addons);
633 
634  apply_filters();
635 }
636 
638 {
639  load_addon_list();
640 
641  // Reselect the add-on.
642  find_widget<addon_list>("addons").select_addon(id);
643  on_addon_select();
644 }
645 
646 boost::dynamic_bitset<> addon_manager::get_name_filter_visibility() const
647 {
648  const text_box& name_filter = find_widget<const text_box>("filter");
649  const std::string& text = name_filter.get_value();
650 
651  filter_transform filter(utils::split(text, ' '));
652  boost::dynamic_bitset<> res;
653 
654  const config::const_child_itors& addon_cfgs = cfg_.child_range("campaign");
655 
656  for(const auto& a : addons_)
657  {
658  const config& addon_cfg = *std::find_if(addon_cfgs.begin(), addon_cfgs.end(),
659  [&a](const config& cfg)
660  {
661  return cfg["name"] == a.first;
662  });
663 
664  res.push_back(filter(addon_cfg));
665  }
666 
667  return res;
668 }
669 
670 boost::dynamic_bitset<> addon_manager::get_status_filter_visibility() const
671 {
672  const menu_button& status_filter = find_widget<const menu_button>("install_status_filter");
673  const ADDON_STATUS_FILTER selection = status_filter_types_[status_filter.get_value()].first;
674 
675  boost::dynamic_bitset<> res;
676  for(const auto& a : addons_) {
677  const addon_tracking_info& info = tracking_info_.at(a.second.id);
678 
679  res.push_back(
680  (selection == FILTER_ALL) ||
681  (selection == FILTER_INSTALLED && is_installed_addon_status(info.state)) ||
682  (selection == FILTER_UPGRADABLE && info.state == ADDON_INSTALLED_UPGRADABLE) ||
683  (selection == FILTER_PUBLISHABLE && info.can_publish == true) ||
684  (selection == FILTER_NOT_INSTALLED && info.state == ADDON_NONE)
685  );
686  }
687 
688  return res;
689 }
690 
691 boost::dynamic_bitset<> addon_manager::get_tag_filter_visibility() const
692 {
693  const auto& tag_filter = find_widget<const multimenu_button>("tag_filter");
694  const auto toggle_states = tag_filter.get_toggle_states();
695  if(toggle_states.none()) {
696  // Nothing selected. It means that all add-ons are shown.
697  boost::dynamic_bitset<> res_flipped(addons_.size());
698  return ~res_flipped;
699  }
700 
701  std::vector<std::string> selected_tags;
702  for(std::size_t i = 0; i < tag_filter_types_.size(); ++i) {
703  if(toggle_states[i]) {
704  selected_tags.push_back(tag_filter_types_[i].id);
705  }
706  }
707 
708  boost::dynamic_bitset<> res;
709  for(const auto& a : addons_) {
710  bool matched_tag = false;
711  for(const auto& id : selected_tags) {
712  if(utils::contains(a.second.tags, id)) {
713  matched_tag = true;
714  break;
715  }
716  }
717  res.push_back(matched_tag);
718  }
719 
720  return res;
721 }
722 
723 boost::dynamic_bitset<> addon_manager::get_type_filter_visibility() const
724 {
725  const multimenu_button& type_filter = find_widget<const multimenu_button>("type_filter");
726 
727  boost::dynamic_bitset<> toggle_states = type_filter.get_toggle_states();
728  if(toggle_states.none()) {
729  // Nothing selected. It means that *all* add-ons are shown.
730  boost::dynamic_bitset<> res_flipped(addons_.size());
731  return ~res_flipped;
732  } else {
733  boost::dynamic_bitset<> res;
734 
735  for(const auto& a : addons_) {
736  int index = std::distance(type_filter_types_.begin(),
737  std::find_if(type_filter_types_.begin(), type_filter_types_.end(),
738  [&a](const std::pair<ADDON_TYPE, std::string>& entry) {
739  return entry.first == a.second.type;
740  })
741  );
742  res.push_back(toggle_states[index]);
743  }
744  return res;
745  }
746 }
747 
748 boost::dynamic_bitset<> addon_manager::get_lang_filter_visibility() const
749 {
750  const multimenu_button& lang_filter = find_widget<const multimenu_button>("language_filter");
751 
752  boost::dynamic_bitset<> toggle_states = lang_filter.get_toggle_states();
753 
754  if(toggle_states.none()) {
755  boost::dynamic_bitset<> res_flipped(addons_.size());
756  return ~res_flipped;
757  } else {
758  boost::dynamic_bitset<> res;
759  for(const auto& a : addons_) {
760  bool retval = false;
761  // langcode -> string conversion vector, to be able to detect either
762  // langcodes or langstring entries
763  std::vector<std::string> lang_string_vector;
764  for (long unsigned int i = 0; i < a.second.locales.size(); i++) {
765  lang_string_vector.push_back(langcode_to_string(a.second.locales[i]));
766  }
767  // Find all toggle states, where toggle = true and lang = lang
768  for (long unsigned int i = 0; i < toggle_states.size(); i++) {
769  if (toggle_states[i] == true) {
770  // does lang_code match?
771  bool contains_lang_code = utils::contains(a.second.locales, language_filter_types_[i].second);
772  // does land_string match?
773  bool contains_lang_string = utils::contains(lang_string_vector, language_filter_types_[i].second);
774  if ((contains_lang_code || contains_lang_string) == true)
775  retval = true;
776  }
777  }
778  res.push_back(retval);
779  }
780  return res;
781  }
782 }
783 
785 {
786  // In the small-screen layout, the text_box for the filter keeps keyboard focus even when the
787  // details panel is visible, which means this can be called when the list isn't visible. That
788  // causes problems both because find_widget can throw exceptions, but also because changing the
789  // filters can hide the currently-shown add-on, triggering a different one to be selected in a
790  // way that would seem random unless the user realised that they were typing into a filter box.
791  //
792  // Quick workaround is to not process the new filter if the list isn't visible.
793  auto list = find_widget<addon_list>("addons", false, false);
794  if(!list) {
795  return;
796  }
797 
798  boost::dynamic_bitset<> res =
804  list->set_addon_shown(res);
805 }
806 
808 {
809  const menu_button& order_menu = find_widget<const menu_button>("order_dropdown");
810  const addon_order& order_struct = all_orders_.at(order_menu.get_value() / 2);
811  sort_order::type order = order_menu.get_value() % 2 == 0 ? sort_order::type::ascending : sort_order::type::descending;
813  if(order == sort_order::type::ascending) {
814  func = order_struct.sort_func_asc;
815  } else {
816  func = order_struct.sort_func_desc;
817  }
818 
819  find_widget<addon_list>("addons").set_addon_order(func);
820  prefs::get().set_addon_manager_saved_order_name(order_struct.as_preference);
822 }
823 
824 void addon_manager::on_order_changed(unsigned int sort_column, sort_order::type order)
825 {
826  menu_button& order_menu = find_widget<menu_button>("order_dropdown");
827  auto order_it = std::find_if(all_orders_.begin(), all_orders_.end(),
828  [sort_column](const addon_order& order) {return order.column_index == static_cast<int>(sort_column);});
829  int index = 2 * (std::distance(all_orders_.begin(), order_it));
830  if(order == sort_order::type::descending) {
831  ++index;
832  }
833  order_menu.set_value(index);
834  prefs::get().set_addon_manager_saved_order_name(order_it->as_preference);
836 }
837 
838 template<void(addon_manager::*fptr)(const addon_info& addon)>
840 {
841  // Explicitly return to the main page if we're in low-res mode so the list is visible.
842  if(stacked_widget* stk = find_widget<stacked_widget>("main_stack", false, false)) {
843  stk->select_layer(0);
844  find_widget<button>("details_toggle").set_label(_("Add-on Details"));
845  }
846 
847  addon_list& addons = find_widget<addon_list>("addons");
848  const addon_info* addon = addons.get_selected_addon();
849 
850  if(addon == nullptr) {
851  return;
852  }
853 
854  try {
855  (this->*fptr)(*addon);
856  } catch(const addons_client::user_exit&) {
857  // User canceled the op.
858  }
859 }
860 
862 {
863  addon_info versioned_addon = addon;
864  if(stacked_widget* stk = find_widget<stacked_widget>("main_stack", false, false)) {
865  set_parent(stk->get_layer_grid(1));
866  }
867  if(addon.id == find_widget<addon_list>("addons").get_selected_addon()->id) {
868  versioned_addon.current_version = find_widget<menu_button>("version_filter").get_value_string();
869  }
870 
872 
873  // Take note if any wml_changes occurred
875 
878  }
879 }
880 
882 {
883  if(have_addon_pbl_info(addon.id) || have_addon_in_vcs_tree(addon.id)) {
885  _("The following add-on appears to have publishing or version control information stored locally, and will not be removed:")
886  + " " + addon.display_title_full());
887  return;
888  }
889 
890  bool success = remove_local_addon(addon.id);
891 
892  if(!success) {
893  gui2::show_error_message(_("The following add-on could not be deleted properly:") + " " + addon.display_title_full());
894  } else {
896 
898  }
899 }
900 
902 {
903  /* Currently, the install and update codepaths are the same, so this function simply
904  * calls the other. Since this might change in the future, I'm leaving this function
905  * here for now.
906  *
907  * - vultraz, 2017-03-12
908  */
909  install_addon(addon);
910 }
911 
913 {
914  for(const auto& a : addons_) {
915  if(tracking_info_[a.first].state == ADDON_INSTALLED_UPGRADABLE) {
917 
918  if(result.wml_changed) {
919  // Updating an add-on may have resulted in its dependencies being updated
920  // as well, so we need to reread version info blocks afterwards to make sure
921  // we don't try to re-download newly updated add-ons.
923  }
924 
925  // Take note if any wml_changes occurred
927  }
928  }
929 
931  load_addon_list();
932  }
933 }
934 
936 {
937  // TODO: make this a separate method to avoid code duplication
938 
939  // Explicitly return to the main page if we're in low-res mode so the list is visible.
940  if(stacked_widget* stk = find_widget<stacked_widget>("main_stack", false, false)) {
941  stk->select_layer(0);
942  find_widget<button>("details_toggle").set_label(_("Add-on Details"));
943  }
944 
945  addon_list& addons = find_widget<addon_list>("addons");
946  const addon_info* addon = addons.get_selected_addon();
947 
948  bool needs_refresh = false;
949  if(addon == nullptr) {
950  gui2::dialogs::addon_server_info::execute(client_, "", needs_refresh);
951  } else {
952  gui2::dialogs::addon_server_info::execute(client_, addon->id, needs_refresh);
953  }
954 
955  if(needs_refresh) {
958  }
959 }
960 
961 /** Performs all backend and UI actions for publishing the specified add-on. */
963 {
964  std::string server_msg;
965 
966  const std::string addon_id = addon.id;
967  // Since the user is planning to upload an addon, this is the right time to validate the .pbl.
968  config cfg = get_addon_pbl_info(addon_id, true);
969 
970  const version_info& version_to_publish = cfg["version"].str();
971 
972  if(version_to_publish <= tracking_info_[addon_id].remote_version) {
973  const int res = gui2::show_message(_("Warning"),
974  _("The remote version of this add-on is greater or equal to the version being uploaded. Do you really wish to continue?"),
976 
977  if(res != gui2::retval::OK) {
978  return;
979  }
980  }
981 
982  // if the passphrase isn't provided from the _server.pbl, try to pre-populate it from the preferences before prompting for it
983  if(cfg["passphrase"].empty()) {
984  cfg["passphrase"] = prefs::get().password(prefs::get().campaign_server(), cfg["author"]);
985  if(!gui2::dialogs::addon_auth::execute(cfg)) {
986  return;
987  } else {
988  prefs::get().set_password(prefs::get().campaign_server(), cfg["author"], cfg["passphrase"]);
989  }
990  } else if(cfg["forum_auth"].to_bool()) {
991  // if the uploader's forum password is present in the _server.pbl
992  gui2::show_error_message(_("The passphrase attribute cannot be present when forum_auth is used."));
993  return;
994  }
995 
996  if(!::image::exists(cfg["icon"].str())) {
997  gui2::show_error_message(_("Invalid icon path. Make sure the path points to a valid image."));
998  } else if(!client_.request_distribution_terms(server_msg)) {
1000  _("The server responded with an error:") + "\n" + client_.get_last_server_error());
1001  } else if(gui2::dialogs::addon_license_prompt::execute(server_msg)) {
1002  if(!client_.upload_addon(addon_id, server_msg, cfg, tracking_info_[addon_id].state == ADDON_INSTALLED_LOCAL_ONLY)) {
1003  const std::string& msg = _("The add-on was rejected by the server:") +
1004  "\n\n" + client_.get_last_server_error();
1005  const std::string& extra_data = client_.get_last_server_error_data();
1006  if (!extra_data.empty()) {
1007  // TODO: Allow user to copy the extra data portion to clipboard
1008  // or something, maybe display it in a dialog similar to
1009  // the WML load errors report in a monospace font and
1010  // stuff (having a scroll container is especially
1011  // important since a long list can cause the dialog to
1012  // overflow).
1013  gui2::show_error_message(msg + "\n\n" + extra_data, true);
1014  } else {
1016  }
1017  } else {
1018  gui2::show_transient_message(_("Response"), server_msg);
1021  }
1022  }
1023 }
1024 
1025 /** Performs all backend and UI actions for taking down the specified add-on. */
1027 {
1028  const std::string addon_id = addon.id;
1029  const std::string& text = VGETTEXT(
1030  "Deleting '$addon|' will permanently erase its download and upload counts on the add-ons server. Do you really wish to continue?",
1031  {{"addon", make_addon_title(addon_id)}} // FIXME: need the real title!
1032  );
1033 
1034  const int res = gui2::show_message(_("Confirm"), text, gui2::dialogs::message::yes_no_buttons);
1035 
1036  if(res != gui2::retval::OK) {
1037  return;
1038  }
1039 
1040  std::string server_msg;
1041  if(!client_.delete_remote_addon(addon_id, server_msg)) {
1042  gui2::show_error_message(_("The server responded with an error:") + "\n" + client_.get_last_server_error());
1043  } else {
1044  // FIXME: translation needed!
1045  gui2::show_transient_message(_("Response"), server_msg);
1048  }
1049 }
1050 
1051 /** Called when the player double-clicks an add-on. */
1053 {
1054  switch(tracking_info_[addon.id].state) {
1055  case ADDON_NONE:
1056  install_addon(addon);
1057  break;
1058  case ADDON_INSTALLED:
1059  if(!tracking_info_[addon.id].can_publish) {
1060  utils::string_map symbols{ { "addon", addon.display_title_full() } };
1061  int res = gui2::show_message(_("Uninstall add-on"),
1062  VGETTEXT("Do you want to uninstall '$addon|'?", symbols),
1064  if(res == gui2::retval::OK) {
1065  uninstall_addon(addon);
1066  }
1067  }
1068  break;
1070  update_addon(addon);
1071  break;
1074  publish_addon(addon);
1075  break;
1076  default:
1077  break;
1078  }
1079 }
1080 
1082 {
1083  help::show_help("installing_addons");
1084 }
1085 
1086 static std::string format_addon_time(const std::chrono::system_clock::time_point& time)
1087 {
1088  if(time == std::chrono::system_clock::time_point{}) {
1089  return font::unicode_em_dash;
1090  }
1091 
1092  const std::string format = prefs::get().use_twelve_hour_clock_format()
1093  // TRANSLATORS: Month + day of month + year + 12-hour time, eg 'November 02 2021, 1:59 PM'. Format for your locale.
1094  // Format reference: https://www.boost.org/doc/libs/1_85_0/doc/html/date_time/date_time_io.html#date_time.format_flags
1095  ? _("%B %d %Y, %I:%M %p")
1096  // TRANSLATORS: Month + day of month + year + 24-hour time, eg 'November 02 2021, 13:59'. Format for your locale.
1097  // Format reference: https://www.boost.org/doc/libs/1_85_0/doc/html/date_time/date_time_io.html#date_time.format_flags
1098  : _("%B %d %Y, %H:%M");
1099 
1100  auto as_time_t = std::chrono::system_clock::to_time_t(time);
1101  return translation::strftime(format, std::localtime(&as_time_t));
1102 }
1103 
1105 {
1106  widget* parent = this;
1107  const addon_info* info = nullptr;
1108  if(stacked_widget* stk = find_widget<stacked_widget>("main_stack", false, false)) {
1109  parent = stk->get_layer_grid(1);
1110  info = stk->get_layer_grid(0)->find_widget<addon_list>("addons").get_selected_addon();
1111  } else {
1112  info = find_widget<addon_list>("addons").get_selected_addon();
1113  }
1114 
1115  if(info == nullptr) {
1116  return;
1117  }
1118 
1119  parent->find_widget<drawing>("image").set_label(info->display_icon());
1120  parent->find_widget<styled_widget>("title").set_label(info->display_title_translated_or_original());
1121  parent->find_widget<styled_widget>("description").set_label(info->description_translated());
1122  menu_button& version_filter = parent->find_widget<menu_button>("version_filter");
1123  parent->find_widget<styled_widget>("author").set_label(info->author);
1124  parent->find_widget<styled_widget>("type").set_label(info->display_type());
1125 
1128  status.set_use_markup(true);
1129 
1131  parent->find_widget<styled_widget>("downloads").set_label(std::to_string(info->downloads));
1134 
1135  parent->find_widget<styled_widget>("dependencies").set_label(!info->depends.empty()
1136  ? make_display_dependencies(info->id, addons_, tracking_info_)
1137  : _("addon_dependencies^None"));
1138 
1139  std::string languages;
1140 
1141  for(const auto& lc : info->locales) {
1142  const std::string& langlabel = langcode_to_string(lc);
1143  if(!langlabel.empty()) {
1144  if(!languages.empty()) {
1145  languages += ", ";
1146  }
1147  languages += langlabel;
1148  }
1149  }
1150 
1151  parent->find_widget<styled_widget>("translations").set_label(!languages.empty() ? languages : _("translations^None"));
1152 
1153  const std::string& feedback_url = info->feedback_url;
1154  parent->find_widget<label>("url").set_label(!feedback_url.empty() ? feedback_url : _("url^None"));
1155  parent->find_widget<label>("id").set_label(info->id);
1156 
1157  bool installed = is_installed_addon_status(tracking_info_[info->id].state);
1158  bool updatable = tracking_info_[info->id].state == ADDON_INSTALLED_UPGRADABLE;
1159 
1161  // #TODO: Add tooltips with upload time and pack size
1162  std::vector<config> version_filter_entries;
1163 
1164  if(!tracking_info_[info->id].can_publish) {
1165  action_stack.select_layer(0);
1166 
1167  stacked_widget& install_update_stack = parent->find_widget<stacked_widget>("install_update_stack");
1168  install_update_stack.select_layer(updatable ? 1 : 0);
1169 
1170  if(!updatable) {
1171  parent->find_widget<button>("install").set_active(!installed);
1172  } else {
1173  parent->find_widget<button>("update").set_active(true);
1174  }
1175 
1176  parent->find_widget<button>("uninstall").set_active(installed);
1177 
1178  for(const auto& f : info->versions) {
1179  version_filter_entries.emplace_back("label", f.str());
1180  }
1181  } else {
1182  action_stack.select_layer(1);
1183 
1184  // Always enable the publish button, but disable the delete button if not yet published.
1185  parent->find_widget<button>("publish").set_active(true);
1186  parent->find_widget<button>("delete").set_active(!info->local_only);
1187 
1188  // Show only the version to be published
1189  version_filter_entries.emplace_back("label", info->current_version.str());
1190  }
1191 
1192  version_filter.set_values(version_filter_entries);
1193  version_filter.set_active(version_filter_entries.size() > 1);
1194 }
1195 
1197 {
1198  widget* parent_of_addons_list = parent();
1199  if(stacked_widget* stk = find_widget<stacked_widget>("main_stack", false, false)) {
1200  set_parent(stk->get_layer_grid(1));
1201  parent_of_addons_list = stk->get_layer_grid(0);
1202  }
1203 
1204  const addon_info* info = parent_of_addons_list->find_widget<addon_list>("addons").get_selected_addon();
1205 
1206  if(info == nullptr) {
1207  return;
1208  }
1209 
1210  if(!tracking_info_[info->id].can_publish && is_installed_addon_status(tracking_info_[info->id].state)) {
1211  bool updatable = tracking_info_[info->id].installed_version
1212  != find_widget<menu_button>("version_filter").get_value_string();
1213  stacked_widget& action_stack = find_widget<stacked_widget>("action_stack");
1214  action_stack.select_layer(0);
1215 
1216  stacked_widget& install_update_stack = find_widget<stacked_widget>("install_update_stack");
1217  install_update_stack.select_layer(1);
1218  find_widget<button>("update").set_active(updatable);
1219  }
1220 }
1221 
1223 {
1226  return false;
1227  }
1228 
1229  return true;
1230 }
1231 
1232 } // namespace dialogs
bool remove_local_addon(const std::string &addon)
Removes the specified add-on, deleting its full directory structure.
Definition: manager.cpp:144
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:187
std::vector< std::string > installed_addons()
Retrieves the names of all installed add-ons.
Definition: manager.cpp:192
void refresh_addon_version_info_cache()
Refreshes the per-session cache of add-on's version information structs.
Definition: manager.cpp:387
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:724
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:158
const_attr_itors attribute_range() const
Definition: config.cpp:756
child_itors child_range(config_key_type key)
Definition: config.cpp:268
boost::iterator_range< const_child_iterator > const_child_itors
Definition: config.hpp:282
config & add_child(config_key_type key)
Definition: config.cpp:436
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:83
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:95
void add_list_to_keyboard_chain()
Adds the internal listbox to the keyboard event chain.
Definition: addon_list.cpp:367
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:77
void set_delete_function(addon_op_func_t function)
Sets the function to install an addon from the addons server.
Definition: addon_list.hpp:101
const addon_info * get_selected_addon() const
Returns the selected add-on.
Definition: addon_list.cpp:310
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:89
void set_modified_signal_handler(const std::function< void()> &callback)
Sets up a callback that will be called when the player selects an add-on.
Definition: addon_list.hpp:53
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:136
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:63
void set_addons(const addons_list &addons)
Sets the add-ons to show.
Definition: addon_list.cpp:156
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:881
void on_order_changed(unsigned int sort_column, sort_order::type order)
Definition: manager.cpp:824
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:748
addons_client & client_
Definition: manager.hpp:80
boost::dynamic_bitset get_status_filter_visibility() const
Definition: manager.cpp:670
void execute_default_action(const addon_info &addon)
Called when the player double-clicks an add-on.
Definition: manager.cpp:1052
void reload_list_and_reselect_item(const std::string &id)
Definition: manager.cpp:637
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:646
void toggle_details(button &btn, stacked_widget &stk)
Definition: manager.cpp:564
void delete_addon(const addon_info &addon)
Performs all backend and UI actions for taking down the specified add-on.
Definition: manager.cpp:1026
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:723
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:691
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:861
void update_addon(const addon_info &addon)
Definition: manager.cpp:901
void publish_addon(const addon_info &addon)
Performs all backend and UI actions for publishing the specified add-on.
Definition: manager.cpp:962
@ 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:88
virtual void set_value(unsigned value, bool fire_event=false) override
Inherited from selectable_item.
Definition: menu_button.hpp:58
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
void set_parent(widget *parent)
Definition: widget.cpp:165
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:321
void keyboard_capture(widget *widget)
Definition: window.cpp:1193
void close()
Requests to close the window.
Definition: window.hpp:218
status
The status of the window.
Definition: window.hpp:205
void set_escape_disabled(const bool escape_disabled)
Disable the escape key.
Definition: window.hpp:334
void set_exit_hook(exit_hook mode, const Func &hook)
Sets the window's exit hook.
Definition: window.hpp:448
int get_retval()
Definition: window.hpp:402
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).
#define VGETTEXT(msgid,...)
Handy wrappers around interpolate_variables_into_string and gettext.
std::size_t i
Definition: function.cpp:1029
#define N_(String)
Definition: gettext.hpp:101
static std::string _(const char *str)
Definition: gettext.hpp:93
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:314
std::string make_addon_title(const std::string &id)
Replaces underscores to dress up file or dirnames as add-on titles.
Definition: info.cpp:323
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:297
std::map< std::string, addon_info > addons_list
Definition: info.hpp:27
language_list get_languages(bool all)
Return a list of available translations.
Definition: language.cpp:126
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:1086
void connect_signal_notify_modified(dispatcher &dispatcher, const signal_notification &signal)
Connects a signal handler for getting a notification upon modification.
Definition: dispatcher.cpp:203
void connect_signal_mouse_left_click(dispatcher &dispatcher, const signal &signal)
Connects a signal handler for a left mouse button click.
Definition: dispatcher.cpp:177
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, show topic with id show_topic.
Definition: help.cpp:140
bool exists(const image::locator &i_locator)
Returns true if the given image actually exists, without loading it.
Definition: picture.cpp:818
std::string strftime(const std::string &format, const std::tm *time)
Definition: gettext.cpp:555
bool ci_search(const std::string &s1, const std::string &s2)
Definition: gettext.cpp:565
std::size_t size(std::string_view str)
Length in characters of a UTF-8 string.
Definition: unicode.cpp:85
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
constexpr auto filter
Definition: ranges.hpp:38
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:86
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:252
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:207
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:224
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:284
int size
Definition: info.hpp:86
std::vector< std::string > locales
Definition: info.hpp:93
std::string id
Definition: info.hpp:75
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