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