The Battle for Wesnoth  1.19.7+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"
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"
41 #include "formula/string_utils.hpp"
42 #include "picture.hpp"
43 #include "language.hpp"
44 #include "utils/general.hpp"
45 
46 #include "config.hpp"
47 
48 #include <functional>
49 #include <set>
50 #include <sstream>
51 
52 namespace gui2::dialogs
53 {
54 
55 namespace {
56  struct filter_transform
57  {
58  explicit filter_transform(const std::vector<std::string>& filtertext) : filtertext_(filtertext) {}
59  bool operator()(const config& cfg) const
60  {
61  for(const auto& filter : filtertext_)
62  {
63  bool found = false;
64  for(const auto& [_, value] : cfg.attribute_range())
65  {
66  if(translation::ci_search(value.str(), filter))
67  {
68  found = true;
69  break;
70  }
71  }
72  for(const config& child : cfg.child_range("translation")) {
73  for(const auto& [_, value] : child.attribute_range()) {
74  if(translation::ci_search(value.str(), filter)) {
75  found = true;
76  break;
77  }
78  }
79  }
80  if(!found) {
81  return false;
82  }
83  }
84  return true;
85  }
86  const std::vector<std::string> filtertext_;
87  };
88 
89  std::string make_display_dependencies(
90  const std::string& addon_id,
91  const addons_list& addons_list,
92  const addons_tracking_list& addon_states)
93  {
94  const addon_info& addon = addons_list.at(addon_id);
95  std::string str;
96 
97  const std::set<std::string>& deps = addon.resolve_dependencies(addons_list);
98 
99  for(const auto& dep_id : deps) {
100  addon_info dep;
101  addon_tracking_info depstate;
102 
103  addons_list::const_iterator ali = addons_list.find(dep_id);
104  addons_tracking_list::const_iterator tli = addon_states.find(dep_id);
105 
106  if(ali == addons_list.end()) {
107  dep.id = dep_id; // Build dummy addon_info.
108  } else {
109  dep = ali->second;
110  }
111 
112  if(tli == addon_states.end()) {
113  depstate = get_addon_tracking_info(dep);
114  } else {
115  depstate = tli->second;
116  }
117 
118  if(!str.empty()) {
119  str += ", ";
120  }
121 
123  }
124 
125  return str;
126  }
127 
128  std::string langcode_to_string(const std::string& lcode)
129  {
130  for(const auto & ld : get_languages(true))
131  {
132  if(ld.localename == lcode || ld.localename.substr(0, 2) == lcode) {
133  return ld.language;
134  }
135  }
136 
137  return "";
138  }
139 }
140 
141 REGISTER_DIALOG(addon_manager)
142 
143 const std::vector<std::pair<ADDON_STATUS_FILTER, std::string>> addon_manager::status_filter_types_{
144  {FILTER_ALL, N_("addons_view^All Add-ons")},
145  {FILTER_INSTALLED, N_("addons_view^Installed")},
146  {FILTER_UPGRADABLE, N_("addons_view^Upgradable")},
147  {FILTER_PUBLISHABLE, N_("addons_view^Publishable")},
148  {FILTER_NOT_INSTALLED, N_("addons_view^Not Installed")},
149 };
150 
151 const std::vector<std::pair<ADDON_TYPE, std::string>> addon_manager::type_filter_types_{
152  {ADDON_SP_CAMPAIGN, N_("addons_of_type^Campaigns")},
153  {ADDON_SP_SCENARIO, N_("addons_of_type^Scenarios")},
154  {ADDON_SP_MP_CAMPAIGN, N_("addons_of_type^SP/MP campaigns")},
155  {ADDON_MP_CAMPAIGN, N_("addons_of_type^MP campaigns")},
156  {ADDON_MP_SCENARIO, N_("addons_of_type^MP scenarios")},
157  {ADDON_MP_MAPS, N_("addons_of_type^MP map-packs")},
158  {ADDON_MP_ERA, N_("addons_of_type^MP eras")},
159  {ADDON_MP_FACTION, N_("addons_of_type^MP factions")},
160  {ADDON_MOD, N_("addons_of_type^Modifications")},
161  {ADDON_CORE, N_("addons_of_type^Cores")},
162  {ADDON_THEME, N_("addons_of_type^Themes")},
163  {ADDON_MEDIA, N_("addons_of_type^Resources")},
164  // FIXME: (also in WML) should this and Unknown be a single option in the UI?
165  {ADDON_OTHER, N_("addons_of_type^Other")},
166  {ADDON_UNKNOWN, N_("addons_of_type^Unknown")},
167 };
168 
169 const std::vector<addon_manager::addon_order> addon_manager::all_orders_{
170  {N_("addons_order^Name ($order)"), "name", 0,
171  [](const addon_info& a, const addon_info& b) { return a.title < b.title; },
172  [](const addon_info& a, const addon_info& b) { return a.title > b.title; }},
173  {N_("addons_order^Author ($order)"), "author", 1,
174  [](const addon_info& a, const addon_info& b) { return a.author < b.author; },
175  [](const addon_info& a, const addon_info& b) { return a.author > b.author; }},
176  {N_("addons_order^Size ($order)"), "size", 2,
177  [](const addon_info& a, const addon_info& b) { return a.size < b.size; },
178  [](const addon_info& a, const addon_info& b) { return a.size > b.size; }},
179  {N_("addons_order^Downloads ($order)"), "downloads", 3,
180  [](const addon_info& a, const addon_info& b) { return a.downloads < b.downloads; },
181  [](const addon_info& a, const addon_info& b) { return a.downloads > b.downloads; }},
182  {N_("addons_order^Type ($order)"), "type", 4,
183  [](const addon_info& a, const addon_info& b) { return a.display_type() < b.display_type(); },
184  [](const addon_info& a, const addon_info& b) { return a.display_type() > b.display_type(); }},
185  {N_("addons_order^Last updated ($datelike_order)"), "last_updated", -1,
186  [](const addon_info& a, const addon_info& b) { return a.updated < b.updated; },
187  [](const addon_info& a, const addon_info& b) { return a.updated > b.updated; }},
188  {N_("addons_order^First uploaded ($datelike_order)"), "first_uploaded", -1,
189  [](const addon_info& a, const addon_info& b) { return a.created < b.created; },
190  [](const addon_info& a, const addon_info& b) { return a.created > b.created; }}
191 };
192 
193 namespace
194 {
195 struct addon_tag
196 {
197  /** Text to match against addon_info.tags() */
198  std::string id;
199  /** What to show in the filter's drop-down list */
200  std::string label;
201  /** Shown when hovering over an entry in the filter's drop-down list */
202  std::string tooltip;
203 };
204 
205 const std::vector<addon_tag> tag_filter_types_{
206  {"cooperative", N_("addon_tag^Cooperative"),
207  // TRANSLATORS: tooltip in the drop-down menu for filtering add-ons
208  N_("addon_tag^All human players are on the same team, versus the AI")},
209  {"cosmetic", N_("addon_tag^Cosmetic"),
210  // TRANSLATORS: tooltip in the drop-down menu for filtering add-ons
211  N_("addon_tag^These make the game look different, without changing gameplay")},
212  {"difficulty", N_("addon_tag^Difficulty"),
213  // TRANSLATORS: tooltip in the drop-down menu for filtering add-ons
214  N_("addon_tag^Can make campaigns easier or harder")},
215  {"rng", N_("addon_tag^RNG"),
216  // TRANSLATORS: tooltip in the drop-down menu for filtering add-ons
217  N_("addon_tag^Modify the randomness in the combat mechanics, or remove it entirely")},
218  {"survival", N_("addon_tag^Survival"),
219  // TRANSLATORS: tooltip in the drop-down menu for filtering add-ons
220  N_("addon_tag^Fight against waves of enemies")},
221  {"terraforming", N_("addon_tag^Terraforming"),
222  // TRANSLATORS: tooltip in the drop-down menu for filtering add-ons
223  N_("addon_tag^Players can change the terrain")},
224 };
225 };
226 
228  : modal_dialog(window_id())
229  , orders_()
230  , cfg_()
231  , client_(client)
232  , addons_()
233  , tracking_info_()
234  , need_wml_cache_refresh_(false)
235 {
236 }
237 
238 static std::string describe_status_verbose(const addon_tracking_info& state)
239 {
240  std::string s;
241 
242  utils::string_map i18n_symbols {{"local_version", state.installed_version.str()}};
243 
244  switch(state.state) {
245  case ADDON_NONE:
246  s = !state.can_publish
247  ? _("addon_state^Not installed")
248  : _("addon_state^Published, not installed");
249  break;
250  case ADDON_INSTALLED:
251  s = !state.can_publish
252  ? _("addon_state^Installed")
253  : _("addon_state^Published");
254  break;
255  case ADDON_NOT_TRACKED:
256  s = !state.can_publish
257  ? _("addon_state^Installed, not tracking local version")
258  // Published add-ons often don't have local status information,
259  // hence untracked. This should be considered normal.
260  : _("addon_state^Published, not tracking local version");
261  break;
263  const std::string vstr = !state.can_publish
264  ? _("addon_state^Installed ($local_version|), upgradable")
265  : _("addon_state^Published ($local_version| installed), upgradable");
266 
267  s = utils::interpolate_variables_into_string(vstr, &i18n_symbols);
268  } break;
270  const std::string vstr = !state.can_publish
271  ? _("addon_state^Installed ($local_version|), outdated on server")
272  : _("addon_state^Published ($local_version| installed), outdated on server");
273 
274  s = utils::interpolate_variables_into_string(vstr, &i18n_symbols);
275  } break;
277  s = !state.can_publish
278  ? _("addon_state^Installed, not ready to publish")
279  : _("addon_state^Ready to publish");
280  break;
282  s = !state.can_publish
283  ? _("addon_state^Installed, broken")
284  : _("addon_state^Published, broken");
285  break;
286  default:
287  s = _("addon_state^Unknown");
288  }
289 
291 }
292 
294 {
295  set_escape_disabled(true);
296 
297  stacked_widget& addr_info = find_widget<stacked_widget>("server_conn_info");
298  grid* addr_visible;
299 
300  if(client_.using_tls()) {
301  addr_info.select_layer(1);
302  addr_visible = addr_info.get_layer_grid(1);
303  } else {
304  addr_info.select_layer(0);
305  addr_visible = addr_info.get_layer_grid(0);
306  }
307 
308  if(addr_visible) {
309  auto addr_box = dynamic_cast<styled_widget*>(addr_visible->find("server_addr", false));
310  if(addr_box) {
311  if(!client_.server_id().empty()) {
312  auto full_id = formatter()
313  << client_.addr() << ' '
314  << font::unicode_em_dash << ' '
315  << client_.server_id();
316  if(game_config::debug && !client_.server_version().empty()) {
317  full_id << " (" << client_.server_version() << ')';
318  }
319  addr_box->set_label(full_id.str());
320  } else {
321  addr_box->set_label(client_.addr());
322  }
323  }
324  }
325 
326  addon_list& list = find_widget<addon_list>("addons");
327 
328  text_box& filter = find_widget<text_box>("filter");
330 
332  this, std::placeholders::_1));
334  this, std::placeholders::_1));
336  this, std::placeholders::_1));
337 
339  this, std::placeholders::_1));
341  this, std::placeholders::_1));
342 
343  list.set_modified_signal_handler([this]() { on_addon_select(); });
344 
346  load_addon_list();
347 
348  menu_button& status_filter = find_widget<menu_button>("install_status_filter");
349 
350  std::vector<config> status_filter_entries;
351  for(const auto& f : status_filter_types_) {
352  status_filter_entries.emplace_back("label", t_string(f.second, GETTEXT_DOMAIN));
353  }
354 
355  status_filter.set_values(status_filter_entries);
356 
357  connect_signal_notify_modified(status_filter,
358  std::bind(&addon_manager::apply_filters, this));
359 
360  // The tag filter
361  auto& tag_filter = find_widget<multimenu_button>("tag_filter");
362 
363  std::vector<config> tag_filter_entries;
364  for(const auto& f : tag_filter_types_) {
365  tag_filter_entries.emplace_back("label", t_string(f.label, GETTEXT_DOMAIN), "checkbox", false);
366  if(!f.tooltip.empty()) {
367  tag_filter_entries.back()["tooltip"] = t_string(f.tooltip, GETTEXT_DOMAIN);
368  }
369  }
370 
371  tag_filter.set_values(tag_filter_entries);
372 
373  connect_signal_notify_modified(tag_filter, std::bind(&addon_manager::apply_filters, this));
374 
375  // The type filter
376  multimenu_button& type_filter = find_widget<multimenu_button>("type_filter");
377 
378  std::vector<config> type_filter_entries;
379  for(const auto& f : type_filter_types_) {
380  type_filter_entries.emplace_back("label", t_string(f.second, GETTEXT_DOMAIN), "checkbox", false);
381  }
382 
383  type_filter.set_values(type_filter_entries);
384 
385  connect_signal_notify_modified(type_filter,
386  std::bind(&addon_manager::apply_filters, this));
387 
388  // Language filter
389  // Prepare shown languages, source all available languages from the addons themselves
390  std::set<std::string> languages_available;
391  for(const auto& a : addons_) {
392  for (const auto& b : a.second.locales) {
393  languages_available.insert(b);
394  }
395  }
396  std::set<std::string> language_strings_available;
397  for (const auto& i: languages_available) {
398  // Only show languages, which have a translation as per langcode_to_string() method
399  // Do not show tranlations with their langcode e.g. "sv_SV"
400  // Also put them into a set, so same lang strings are not producing doublettes
401  if (std::string lang_code_string = langcode_to_string(i); !lang_code_string.empty()) {
402  language_strings_available.insert(lang_code_string);
403  }
404  }
405  for (auto& i: language_strings_available) {
406  language_filter_types_.emplace_back(language_filter_types_.size(), std::move(i));
407  }
408  // The language filter
409  multimenu_button& language_filter = find_widget<multimenu_button>("language_filter");
410  std::vector<config> language_filter_entries;
411  for(const auto& f : language_filter_types_) {
412  language_filter_entries.emplace_back("label", f.second, "checkbox", false);
413  }
414 
415  language_filter.set_values(language_filter_entries);
416 
417  connect_signal_notify_modified(language_filter,
418  std::bind(&addon_manager::apply_filters, this));
419 
420  // Sorting order
421  menu_button& order_dropdown = find_widget<menu_button>("order_dropdown");
422 
423  std::vector<config> order_dropdown_entries;
424  for(const auto& f : all_orders_) {
425  utils::string_map symbols;
426 
427  symbols["order"] = _("ascending");
428  // TRANSLATORS: Sorting order of dates, oldest first
429  symbols["datelike_order"] = _("oldest to newest");
430  config entry{"label", VGETTEXT(f.label.c_str(), symbols)};
431  order_dropdown_entries.push_back(entry);
432  symbols["order"] = _("descending");
433  // TRANSLATORS: Sorting order of dates, newest first
434  symbols["datelike_order"] = _("newest to oldest");
435  entry["label"] = VGETTEXT(f.label.c_str(), symbols);
436  order_dropdown_entries.push_back(entry);
437  }
438 
439  order_dropdown.set_values(order_dropdown_entries);
440  {
441  const std::string saved_order_name = prefs::get().addon_manager_saved_order_name();
442  const sort_order::type saved_order_direction = prefs::get().addon_manager_saved_order_direction();
443 
444  if(!saved_order_name.empty()) {
445  auto order_it = std::find_if(all_orders_.begin(), all_orders_.end(),
446  [&saved_order_name](const addon_order& order) {return order.as_preference == saved_order_name;});
447  if(order_it != all_orders_.end()) {
448  int index = 2 * (std::distance(all_orders_.begin(), order_it));
450  if(saved_order_direction == sort_order::type::ascending) {
451  func = order_it->sort_func_asc;
452  } else {
453  func = order_it->sort_func_desc;
454  ++index;
455  }
456  find_widget<menu_button>("order_dropdown").set_value(index);
457  auto& addons = find_widget<addon_list>("addons");
458  addons.set_addon_order(func);
459  addons.select_first_addon();
460  }
461  }
462  }
463 
464  connect_signal_notify_modified(order_dropdown,
465  std::bind(&addon_manager::order_addons, this));
466 
467  label& url_label = find_widget<label>("url");
468 
469  url_label.set_use_markup(true);
470  url_label.set_link_aware(true);
471 
473  find_widget<button>("install"),
474  std::bind(&addon_manager::install_selected_addon, this));
475 
477  find_widget<button>("uninstall"),
478  std::bind(&addon_manager::uninstall_selected_addon, this));
479 
481  find_widget<button>("update"),
482  std::bind(&addon_manager::update_selected_addon, this));
483 
485  find_widget<button>("publish"),
486  std::bind(&addon_manager::publish_selected_addon, this));
487 
489  find_widget<button>("delete"),
490  std::bind(&addon_manager::delete_selected_addon, this));
491 
493  find_widget<button>("update_all"),
494  std::bind(&addon_manager::update_all_addons, this));
495 
497  find_widget<button>("show_help"),
498  std::bind(&addon_manager::show_help, this));
499 
500  if(stacked_widget* stk = find_widget<stacked_widget>("main_stack", false, false)) {
501  button& btn = find_widget<button>("details_toggle");
503  std::bind(&addon_manager::toggle_details, this, std::ref(btn), std::ref(*stk)));
504 
505  stk->select_layer(0);
506 
508  stk->get_layer_grid(1)->find_widget<menu_button>("version_filter"),
510  } else {
512  find_widget<menu_button>("version_filter"),
514  }
515 
516  on_addon_select();
517 
518  set_enter_disabled(true);
519 
520  keyboard_capture(&filter);
522 
523  list.set_callback_order_change(std::bind(&addon_manager::on_order_changed, this, std::placeholders::_1, std::placeholders::_2));
524 
525  // Use handle the special addon_list retval to allow installing addons on double click
526  set_exit_hook(window::exit_hook::on_all, std::bind(&addon_manager::exit_hook, this, std::placeholders::_1));
527 }
528 
530 {
531  if(stk.current_layer() == 0) {
532  btn.set_label(_("addons^Back to List"));
533  stk.select_layer(1);
534  } else {
535  btn.set_label(_("Add-on Details"));
536  stk.select_layer(0);
537  }
538 }
539 
541 {
542  bool success = client_.request_addons_list(cfg_, prefs::get().addon_icons());
543  if(!success) {
544  gui2::show_error_message(_("An error occurred while downloading the add-ons list from the server."));
545  close();
546  }
547 }
548 
550 {
553  }
554 
556 
557  std::vector<std::string> publishable_addons = available_addons();
558 
559  for(std::string id : publishable_addons) {
560  if(addons_.find(id) == addons_.end()) {
561  // Get a config from the addon's pbl file
562  // Note that the name= key is necessary or stuff breaks, since the filter code uses this key
563  // to match add-ons in the config list. It also fills in addon_info's id field. It's also
564  // neccessay to set local_only here so that flag can be properly set after addons_ is cleared
565  // and recreated by read_addons_list.
566  try {
567  config pbl_cfg = get_addon_pbl_info(id, false);
568  pbl_cfg["name"] = id;
569  pbl_cfg["local_only"] = true;
570 
571  // Add the add-on to the list.
572  addon_info addon(pbl_cfg);
573  addons_[id] = addon;
574 
575  // Add the addon to the config entry
576  cfg_.add_child("campaign", std::move(pbl_cfg));
577  } catch(invalid_pbl_exception&) {}
578  }
579  }
580 
581  if(addons_.empty()) {
582  show_transient_message(_("No Add-ons Available"), _("There are no add-ons available for download from this server."));
583  }
584 
585  addon_list& list = find_widget<addon_list>("addons");
586  list.set_addons(addons_);
587 
588  bool has_upgradable_addons = false;
589  for(const auto& a : addons_) {
590  tracking_info_[a.first] = get_addon_tracking_info(a.second);
591 
592  if(tracking_info_[a.first].state == ADDON_INSTALLED_UPGRADABLE) {
593  has_upgradable_addons = true;
594  }
595  }
596 
597  find_widget<button>("update_all").set_active(has_upgradable_addons);
598 
599  apply_filters();
600 }
601 
603 {
604  load_addon_list();
605 
606  // Reselect the add-on.
607  find_widget<addon_list>("addons").select_addon(id);
608  on_addon_select();
609 }
610 
611 boost::dynamic_bitset<> addon_manager::get_name_filter_visibility() const
612 {
613  const text_box& name_filter = find_widget<const text_box>("filter");
614  const std::string& text = name_filter.get_value();
615 
616  filter_transform filter(utils::split(text, ' '));
617  boost::dynamic_bitset<> res;
618 
619  const config::const_child_itors& addon_cfgs = cfg_.child_range("campaign");
620 
621  for(const auto& a : addons_)
622  {
623  const config& addon_cfg = *std::find_if(addon_cfgs.begin(), addon_cfgs.end(),
624  [&a](const config& cfg)
625  {
626  return cfg["name"] == a.first;
627  });
628 
629  res.push_back(filter(addon_cfg));
630  }
631 
632  return res;
633 }
634 
635 boost::dynamic_bitset<> addon_manager::get_status_filter_visibility() const
636 {
637  const menu_button& status_filter = find_widget<const menu_button>("install_status_filter");
638  const ADDON_STATUS_FILTER selection = status_filter_types_[status_filter.get_value()].first;
639 
640  boost::dynamic_bitset<> res;
641  for(const auto& a : addons_) {
642  const addon_tracking_info& info = tracking_info_.at(a.second.id);
643 
644  res.push_back(
645  (selection == FILTER_ALL) ||
646  (selection == FILTER_INSTALLED && is_installed_addon_status(info.state)) ||
647  (selection == FILTER_UPGRADABLE && info.state == ADDON_INSTALLED_UPGRADABLE) ||
648  (selection == FILTER_PUBLISHABLE && info.can_publish == true) ||
649  (selection == FILTER_NOT_INSTALLED && info.state == ADDON_NONE)
650  );
651  }
652 
653  return res;
654 }
655 
656 boost::dynamic_bitset<> addon_manager::get_tag_filter_visibility() const
657 {
658  const auto& tag_filter = find_widget<const multimenu_button>("tag_filter");
659  const auto toggle_states = tag_filter.get_toggle_states();
660  if(toggle_states.none()) {
661  // Nothing selected. It means that all add-ons are shown.
662  boost::dynamic_bitset<> res_flipped(addons_.size());
663  return ~res_flipped;
664  }
665 
666  std::vector<std::string> selected_tags;
667  for(std::size_t i = 0; i < tag_filter_types_.size(); ++i) {
668  if(toggle_states[i]) {
669  selected_tags.push_back(tag_filter_types_[i].id);
670  }
671  }
672 
673  boost::dynamic_bitset<> res;
674  for(const auto& a : addons_) {
675  bool matched_tag = false;
676  for(const auto& id : selected_tags) {
677  if(utils::contains(a.second.tags, id)) {
678  matched_tag = true;
679  break;
680  }
681  }
682  res.push_back(matched_tag);
683  }
684 
685  return res;
686 }
687 
688 boost::dynamic_bitset<> addon_manager::get_type_filter_visibility() const
689 {
690  const multimenu_button& type_filter = find_widget<const multimenu_button>("type_filter");
691 
692  boost::dynamic_bitset<> toggle_states = type_filter.get_toggle_states();
693  if(toggle_states.none()) {
694  // Nothing selected. It means that *all* add-ons are shown.
695  boost::dynamic_bitset<> res_flipped(addons_.size());
696  return ~res_flipped;
697  } else {
698  boost::dynamic_bitset<> res;
699 
700  for(const auto& a : addons_) {
701  int index = std::distance(type_filter_types_.begin(),
702  std::find_if(type_filter_types_.begin(), type_filter_types_.end(),
703  [&a](const std::pair<ADDON_TYPE, std::string>& entry) {
704  return entry.first == a.second.type;
705  })
706  );
707  res.push_back(toggle_states[index]);
708  }
709  return res;
710  }
711 }
712 
713 boost::dynamic_bitset<> addon_manager::get_lang_filter_visibility() const
714 {
715  const multimenu_button& lang_filter = find_widget<const multimenu_button>("language_filter");
716 
717  boost::dynamic_bitset<> toggle_states = lang_filter.get_toggle_states();
718 
719  if(toggle_states.none()) {
720  boost::dynamic_bitset<> res_flipped(addons_.size());
721  return ~res_flipped;
722  } else {
723  boost::dynamic_bitset<> res;
724  for(const auto& a : addons_) {
725  bool retval = false;
726  // langcode -> string conversion vector, to be able to detect either
727  // langcodes or langstring entries
728  std::vector<std::string> lang_string_vector;
729  for (long unsigned int i = 0; i < a.second.locales.size(); i++) {
730  lang_string_vector.push_back(langcode_to_string(a.second.locales[i]));
731  }
732  // Find all toggle states, where toggle = true and lang = lang
733  for (long unsigned int i = 0; i < toggle_states.size(); i++) {
734  if (toggle_states[i] == true) {
735  // does lang_code match?
736  bool contains_lang_code = utils::contains(a.second.locales, language_filter_types_[i].second);
737  // does land_string match?
738  bool contains_lang_string = utils::contains(lang_string_vector, language_filter_types_[i].second);
739  if ((contains_lang_code || contains_lang_string) == true)
740  retval = true;
741  }
742  }
743  res.push_back(retval);
744  }
745  return res;
746  }
747 }
748 
750 {
751  // In the small-screen layout, the text_box for the filter keeps keyboard focus even when the
752  // details panel is visible, which means this can be called when the list isn't visible. That
753  // causes problems both because find_widget can throw exceptions, but also because changing the
754  // filters can hide the currently-shown add-on, triggering a different one to be selected in a
755  // way that would seem random unless the user realised that they were typing into a filter box.
756  //
757  // Quick workaround is to not process the new filter if the list isn't visible.
758  auto list = find_widget<addon_list>("addons", false, false);
759  if(!list) {
760  return;
761  }
762 
763  boost::dynamic_bitset<> res =
769  list->set_addon_shown(res);
770 }
771 
773 {
774  const menu_button& order_menu = find_widget<const menu_button>("order_dropdown");
775  const addon_order& order_struct = all_orders_.at(order_menu.get_value() / 2);
776  sort_order::type order = order_menu.get_value() % 2 == 0 ? sort_order::type::ascending : sort_order::type::descending;
778  if(order == sort_order::type::ascending) {
779  func = order_struct.sort_func_asc;
780  } else {
781  func = order_struct.sort_func_desc;
782  }
783 
784  find_widget<addon_list>("addons").set_addon_order(func);
785  prefs::get().set_addon_manager_saved_order_name(order_struct.as_preference);
787 }
788 
789 void addon_manager::on_order_changed(unsigned int sort_column, sort_order::type order)
790 {
791  menu_button& order_menu = find_widget<menu_button>("order_dropdown");
792  auto order_it = std::find_if(all_orders_.begin(), all_orders_.end(),
793  [sort_column](const addon_order& order) {return order.column_index == static_cast<int>(sort_column);});
794  int index = 2 * (std::distance(all_orders_.begin(), order_it));
795  if(order == sort_order::type::descending) {
796  ++index;
797  }
798  order_menu.set_value(index);
799  prefs::get().set_addon_manager_saved_order_name(order_it->as_preference);
801 }
802 
803 template<void(addon_manager::*fptr)(const addon_info& addon)>
805 {
806  // Explicitly return to the main page if we're in low-res mode so the list is visible.
807  if(stacked_widget* stk = find_widget<stacked_widget>("main_stack", false, false)) {
808  stk->select_layer(0);
809  find_widget<button>("details_toggle").set_label(_("Add-on Details"));
810  }
811 
812  addon_list& addons = find_widget<addon_list>("addons");
813  const addon_info* addon = addons.get_selected_addon();
814 
815  if(addon == nullptr) {
816  return;
817  }
818 
819  try {
820  (this->*fptr)(*addon);
821  } catch(const addons_client::user_exit&) {
822  // User canceled the op.
823  }
824 }
825 
827 {
828  addon_info versioned_addon = addon;
829  if(stacked_widget* stk = find_widget<stacked_widget>("main_stack", false, false)) {
830  set_parent(stk->get_layer_grid(1));
831  }
832  if(addon.id == find_widget<addon_list>("addons").get_selected_addon()->id) {
833  versioned_addon.current_version = find_widget<menu_button>("version_filter").get_value_string();
834  }
835 
837 
838  // Take note if any wml_changes occurred
840 
843  }
844 }
845 
847 {
848  if(have_addon_pbl_info(addon.id) || have_addon_in_vcs_tree(addon.id)) {
850  _("The following add-on appears to have publishing or version control information stored locally, and will not be removed:")
851  + " " + addon.display_title_full());
852  return;
853  }
854 
855  bool success = remove_local_addon(addon.id);
856 
857  if(!success) {
858  gui2::show_error_message(_("The following add-on could not be deleted properly:") + " " + addon.display_title_full());
859  } else {
861 
863  }
864 }
865 
867 {
868  /* Currently, the install and update codepaths are the same, so this function simply
869  * calls the other. Since this might change in the future, I'm leaving this function
870  * here for now.
871  *
872  * - vultraz, 2017-03-12
873  */
874  install_addon(addon);
875 }
876 
878 {
879  for(const auto& a : addons_) {
880  if(tracking_info_[a.first].state == ADDON_INSTALLED_UPGRADABLE) {
882 
883  if(result.wml_changed) {
884  // Updating an add-on may have resulted in its dependencies being updated
885  // as well, so we need to reread version info blocks afterwards to make sure
886  // we don't try to re-download newly updated add-ons.
888  }
889 
890  // Take note if any wml_changes occurred
892  }
893  }
894 
896  load_addon_list();
897  }
898 }
899 
900 /** Performs all backend and UI actions for publishing the specified add-on. */
902 {
903  std::string server_msg;
904 
905  const std::string addon_id = addon.id;
906  // Since the user is planning to upload an addon, this is the right time to validate the .pbl.
907  config cfg = get_addon_pbl_info(addon_id, true);
908 
909  const version_info& version_to_publish = cfg["version"].str();
910 
911  if(version_to_publish <= tracking_info_[addon_id].remote_version) {
912  const int res = gui2::show_message(_("Warning"),
913  _("The remote version of this add-on is greater or equal to the version being uploaded. Do you really wish to continue?"),
915 
916  if(res != gui2::retval::OK) {
917  return;
918  }
919  }
920 
921  // if the passphrase isn't provided from the _server.pbl, try to pre-populate it from the preferences before prompting for it
922  if(cfg["passphrase"].empty()) {
923  cfg["passphrase"] = prefs::get().password(prefs::get().campaign_server(), cfg["author"]);
924  if(!gui2::dialogs::addon_auth::execute(cfg)) {
925  return;
926  } else {
927  prefs::get().set_password(prefs::get().campaign_server(), cfg["author"], cfg["passphrase"]);
928  }
929  } else if(cfg["forum_auth"].to_bool()) {
930  // if the uploader's forum password is present in the _server.pbl
931  gui2::show_error_message(_("The passphrase attribute cannot be present when forum_auth is used."));
932  return;
933  }
934 
935  if(!::image::exists(cfg["icon"].str())) {
936  gui2::show_error_message(_("Invalid icon path. Make sure the path points to a valid image."));
937  } else if(!client_.request_distribution_terms(server_msg)) {
939  _("The server responded with an error:") + "\n" + client_.get_last_server_error());
940  } else if(gui2::dialogs::addon_license_prompt::execute(server_msg)) {
941  if(!client_.upload_addon(addon_id, server_msg, cfg, tracking_info_[addon_id].state == ADDON_INSTALLED_LOCAL_ONLY)) {
942  const std::string& msg = _("The add-on was rejected by the server:") +
943  "\n\n" + client_.get_last_server_error();
944  const std::string& extra_data = client_.get_last_server_error_data();
945  if (!extra_data.empty()) {
946  // TODO: Allow user to copy the extra data portion to clipboard
947  // or something, maybe display it in a dialog similar to
948  // the WML load errors report in a monospace font and
949  // stuff (having a scroll container is especially
950  // important since a long list can cause the dialog to
951  // overflow).
952  gui2::show_error_message(msg + "\n\n" + extra_data, true);
953  } else {
955  }
956  } else {
957  gui2::show_transient_message(_("Response"), server_msg);
960  }
961  }
962 }
963 
964 /** Performs all backend and UI actions for taking down the specified add-on. */
966 {
967  const std::string addon_id = addon.id;
968  const std::string& text = VGETTEXT(
969  "Deleting '$addon|' will permanently erase its download and upload counts on the add-ons server. Do you really wish to continue?",
970  {{"addon", make_addon_title(addon_id)}} // FIXME: need the real title!
971  );
972 
973  const int res = gui2::show_message(_("Confirm"), text, gui2::dialogs::message::yes_no_buttons);
974 
975  if(res != gui2::retval::OK) {
976  return;
977  }
978 
979  std::string server_msg;
980  if(!client_.delete_remote_addon(addon_id, server_msg)) {
981  gui2::show_error_message(_("The server responded with an error:") + "\n" + client_.get_last_server_error());
982  } else {
983  // FIXME: translation needed!
984  gui2::show_transient_message(_("Response"), server_msg);
987  }
988 }
989 
990 /** Called when the player double-clicks an add-on. */
992 {
993  switch(tracking_info_[addon.id].state) {
994  case ADDON_NONE:
995  install_addon(addon);
996  break;
997  case ADDON_INSTALLED:
998  if(!tracking_info_[addon.id].can_publish) {
999  utils::string_map symbols{ { "addon", addon.display_title_full() } };
1000  int res = gui2::show_message(_("Uninstall add-on"),
1001  VGETTEXT("Do you want to uninstall '$addon|'?", symbols),
1003  if(res == gui2::retval::OK) {
1004  uninstall_addon(addon);
1005  }
1006  }
1007  break;
1009  update_addon(addon);
1010  break;
1013  publish_addon(addon);
1014  break;
1015  default:
1016  break;
1017  }
1018 }
1019 
1021 {
1022  help::show_help("installing_addons");
1023 }
1024 
1025 static std::string format_addon_time(const std::chrono::system_clock::time_point& time)
1026 {
1027  if(time == std::chrono::system_clock::time_point{}) {
1028  return font::unicode_em_dash;
1029  }
1030 
1031  const std::string format = prefs::get().use_twelve_hour_clock_format()
1032  // TRANSLATORS: Month + day of month + year + 12-hour time, eg 'November 02 2021, 1:59 PM'. Format for your locale.
1033  // Format reference: https://www.boost.org/doc/libs/1_85_0/doc/html/date_time/date_time_io.html#date_time.format_flags
1034  ? _("%B %d %Y, %I:%M %p")
1035  // TRANSLATORS: Month + day of month + year + 24-hour time, eg 'November 02 2021, 13:59'. Format for your locale.
1036  // Format reference: https://www.boost.org/doc/libs/1_85_0/doc/html/date_time/date_time_io.html#date_time.format_flags
1037  : _("%B %d %Y, %H:%M");
1038 
1039  auto as_time_t = std::chrono::system_clock::to_time_t(time);
1040  return translation::strftime(format, std::localtime(&as_time_t));
1041 }
1042 
1044 {
1045  widget* parent = this;
1046  const addon_info* info = nullptr;
1047  if(stacked_widget* stk = find_widget<stacked_widget>("main_stack", false, false)) {
1048  parent = stk->get_layer_grid(1);
1049  info = stk->get_layer_grid(0)->find_widget<addon_list>("addons").get_selected_addon();
1050  } else {
1051  info = find_widget<addon_list>("addons").get_selected_addon();
1052  }
1053 
1054  if(info == nullptr) {
1055  return;
1056  }
1057 
1058  parent->find_widget<drawing>("image").set_label(info->display_icon());
1059  parent->find_widget<styled_widget>("title").set_label(info->display_title_translated_or_original());
1060  parent->find_widget<styled_widget>("description").set_label(info->description_translated());
1061  menu_button& version_filter = parent->find_widget<menu_button>("version_filter");
1062  parent->find_widget<styled_widget>("author").set_label(info->author);
1063  parent->find_widget<styled_widget>("type").set_label(info->display_type());
1064 
1067  status.set_use_markup(true);
1068 
1070  parent->find_widget<styled_widget>("downloads").set_label(std::to_string(info->downloads));
1073 
1074  parent->find_widget<styled_widget>("dependencies").set_label(!info->depends.empty()
1075  ? make_display_dependencies(info->id, addons_, tracking_info_)
1076  : _("addon_dependencies^None"));
1077 
1078  std::string languages;
1079 
1080  for(const auto& lc : info->locales) {
1081  const std::string& langlabel = langcode_to_string(lc);
1082  if(!langlabel.empty()) {
1083  if(!languages.empty()) {
1084  languages += ", ";
1085  }
1086  languages += langlabel;
1087  }
1088  }
1089 
1090  parent->find_widget<styled_widget>("translations").set_label(!languages.empty() ? languages : _("translations^None"));
1091 
1092  const std::string& feedback_url = info->feedback_url;
1093  parent->find_widget<label>("url").set_label(!feedback_url.empty() ? feedback_url : _("url^None"));
1094  parent->find_widget<label>("id").set_label(info->id);
1095 
1096  bool installed = is_installed_addon_status(tracking_info_[info->id].state);
1097  bool updatable = tracking_info_[info->id].state == ADDON_INSTALLED_UPGRADABLE;
1098 
1100  // #TODO: Add tooltips with upload time and pack size
1101  std::vector<config> version_filter_entries;
1102 
1103  if(!tracking_info_[info->id].can_publish) {
1104  action_stack.select_layer(0);
1105 
1106  stacked_widget& install_update_stack = parent->find_widget<stacked_widget>("install_update_stack");
1107  install_update_stack.select_layer(updatable ? 1 : 0);
1108 
1109  if(!updatable) {
1110  parent->find_widget<button>("install").set_active(!installed);
1111  } else {
1112  parent->find_widget<button>("update").set_active(true);
1113  }
1114 
1115  parent->find_widget<button>("uninstall").set_active(installed);
1116 
1117  for(const auto& f : info->versions) {
1118  version_filter_entries.emplace_back("label", f.str());
1119  }
1120  } else {
1121  action_stack.select_layer(1);
1122 
1123  // Always enable the publish button, but disable the delete button if not yet published.
1124  parent->find_widget<button>("publish").set_active(true);
1125  parent->find_widget<button>("delete").set_active(!info->local_only);
1126 
1127  // Show only the version to be published
1128  version_filter_entries.emplace_back("label", info->current_version.str());
1129  }
1130 
1131  version_filter.set_values(version_filter_entries);
1132  version_filter.set_active(version_filter_entries.size() > 1);
1133 }
1134 
1136 {
1137  widget* parent_of_addons_list = parent();
1138  if(stacked_widget* stk = find_widget<stacked_widget>("main_stack", false, false)) {
1139  set_parent(stk->get_layer_grid(1));
1140  parent_of_addons_list = stk->get_layer_grid(0);
1141  }
1142 
1143  const addon_info* info = parent_of_addons_list->find_widget<addon_list>("addons").get_selected_addon();
1144 
1145  if(info == nullptr) {
1146  return;
1147  }
1148 
1149  if(!tracking_info_[info->id].can_publish && is_installed_addon_status(tracking_info_[info->id].state)) {
1150  bool updatable = tracking_info_[info->id].installed_version
1151  != find_widget<menu_button>("version_filter").get_value_string();
1152  stacked_widget& action_stack = find_widget<stacked_widget>("action_stack");
1153  action_stack.select_layer(0);
1154 
1155  stacked_widget& install_update_stack = find_widget<stacked_widget>("install_update_stack");
1156  install_update_stack.select_layer(1);
1157  find_widget<button>("update").set_active(updatable);
1158  }
1159 }
1160 
1162 {
1165  return false;
1166  }
1167 
1168  return true;
1169 }
1170 
1171 } // 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
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:575
bool delete_remote_addon(const std::string &id, std::string &response_message)
Requests the specified add-on to be removed from the server.
Definition: client.cpp:274
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:222
bool using_tls() const
Returns whether the current connection uses TLS.
Definition: client.hpp:217
bool request_distribution_terms(std::string &terms)
Request the add-ons server distribution terms message.
Definition: client.cpp:136
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:115
const std::string & server_version() const
Definition: client.hpp:227
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:159
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:172
const_attr_itors attribute_range() const
Definition: config.cpp:760
child_itors child_range(config_key_type key)
Definition: config.cpp:272
boost::iterator_range< const_child_iterator > const_child_itors
Definition: config.hpp:296
config & add_child(config_key_type key)
Definition: config.cpp:440
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:846
void on_order_changed(unsigned int sort_column, sort_order::type order)
Definition: manager.cpp:789
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:293
boost::dynamic_bitset get_lang_filter_visibility() const
Definition: manager.cpp:713
addons_client & client_
Definition: manager.hpp:80
boost::dynamic_bitset get_status_filter_visibility() const
Definition: manager.cpp:635
void execute_default_action(const addon_info &addon)
Called when the player double-clicks an add-on.
Definition: manager.cpp:991
void reload_list_and_reselect_item(const std::string &id)
Definition: manager.cpp:602
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:611
void toggle_details(button &btn, stacked_widget &stk)
Definition: manager.cpp:529
void delete_addon(const addon_info &addon)
Performs all backend and UI actions for taking down the specified add-on.
Definition: manager.cpp:965
addon_manager(addons_client &client)
Definition: manager.cpp:227
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:688
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:656
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:826
void update_addon(const addon_info &addon)
Definition: manager.cpp:866
void publish_addon(const addon_info &addon)
Performs all backend and UI actions for publishing the specified add-on.
Definition: manager.cpp:901
@ 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
void set_text_changed_callback(std::function< void(text_box_base *textbox, const std::string text)> cb)
Set the text_changed callback.
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:753
widget * parent()
Definition: widget.cpp:170
base class of top level items, the only item which needs to store the final canvases to draw on.
Definition: window.hpp:61
void set_enter_disabled(const bool enter_disabled)
Disable the enter key.
Definition: window.hpp:322
void keyboard_capture(widget *widget)
Definition: window.cpp:1207
void set_exit_hook(exit_hook mode, std::function< bool(window &)> func)
Sets the window's exit hook.
Definition: window.hpp:448
void close()
Requests to close the window.
Definition: window.hpp:219
status
The status of the window.
Definition: window.hpp:206
void set_escape_disabled(const bool escape_disabled)
Disable the escape key.
Definition: window.hpp:335
int get_retval()
Definition: window.hpp:401
@ on_all
Always run hook.
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:200
const std::vector< std::string > filtertext_
Definition: manager.cpp:86
#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:202
std::string id
Text to match against addon_info.tags()
Definition: manager.cpp:198
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:309
std::string make_addon_title(const std::string &id)
Replaces underscores to dress up file or dirnames as add-on titles.
Definition: info.cpp:318
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:292
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
const bool & debug
Definition: game_config.cpp:94
static std::string describe_status_verbose(const addon_tracking_info &state)
Definition: manager.cpp:238
REGISTER_DIALOG(editor_edit_unit)
static std::string format_addon_time(const std::chrono::system_clock::time_point &time)
Definition: manager.cpp:1025
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
logger & info()
Definition: log.cpp:319
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 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
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:247
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:279
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:126
install_outcome outcome
Overall outcome of the operation.
Definition: client.hpp:130
bool wml_changed
Specifies if WML on disk was altered and needs to be reloaded.
Definition: client.hpp:139
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:102
@ ADDON_THEME
GUI2 Themes.
Definition: validation.hpp:114
@ ADDON_SP_SCENARIO
Single-player scenario.
Definition: validation.hpp:105
@ ADDON_MP_SCENARIO
Multiplayer scenario.
Definition: validation.hpp:108
@ ADDON_MP_CAMPAIGN
Multiplayer campaign.
Definition: validation.hpp:107
@ ADDON_MP_FACTION
Multiplayer faction.
Definition: validation.hpp:111
@ ADDON_MEDIA
Miscellaneous content/media (unit packs, terrain packs, music packs, etc.).
Definition: validation.hpp:113
@ ADDON_MP_ERA
Multiplayer era.
Definition: validation.hpp:110
@ ADDON_CORE
Total Conversion Core.
Definition: validation.hpp:103
@ ADDON_SP_CAMPAIGN
Single-player campaign.
Definition: validation.hpp:104
@ ADDON_OTHER
an add-on that fits in no other category
Definition: validation.hpp:115
@ ADDON_SP_MP_CAMPAIGN
Hybrid campaign.
Definition: validation.hpp:106
@ ADDON_MP_MAPS
Multiplayer plain (no WML) map pack.
Definition: validation.hpp:109
@ ADDON_MOD
Modification of the game.
Definition: validation.hpp:112
#define f
#define b