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