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