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"
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  if(!game_config::debug) {
523  find_widget<label>(&window, "addon_id", false).set_visible(widget::visibility::invisible);
524  }
525 
526  on_addon_select();
527 
529 
530  window.keyboard_capture(&filter);
532 
533  list.set_callback_order_change(std::bind(&addon_manager::on_order_changed, this, std::placeholders::_1, std::placeholders::_2));
534 
535  // Use handle the special addon_list retval to allow installing addons on double click
536  window.set_exit_hook(window::exit_hook::on_all, std::bind(&addon_manager::exit_hook, this, std::placeholders::_1));
537 }
538 
540 {
541  if(stk.current_layer() == 0) {
542  btn.set_label(_("addons^Back to List"));
543  stk.select_layer(1);
544  } else {
545  btn.set_label(_("Add-on Details"));
546  stk.select_layer(0);
547  }
548 }
549 
551 {
552  bool success = client_.request_addons_list(cfg_);
553  if(!success) {
554  gui2::show_error_message(_("An error occurred while downloading the add-ons list from the server."));
555  get_window()->close();
556  }
557 }
558 
560 {
563  }
564 
566 
567  std::vector<std::string> publishable_addons = available_addons();
568 
569  for(std::string id : publishable_addons) {
570  if(addons_.find(id) == addons_.end()) {
571  // Get a config from the addon's pbl file
572  // Note that the name= key is necessary or stuff breaks, since the filter code uses this key
573  // to match add-ons in the config list. It also fills in addon_info's id field. It's also
574  // neccessay to set local_only here so that flag can be properly set after addons_ is cleared
575  // and recreated by read_addons_list.
576  try {
577  config pbl_cfg = get_addon_pbl_info(id, false);
578  pbl_cfg["name"] = id;
579  pbl_cfg["local_only"] = true;
580 
581  // Add the add-on to the list.
582  addon_info addon(pbl_cfg);
583  addons_[id] = addon;
584 
585  // Add the addon to the config entry
586  cfg_.add_child("campaign", std::move(pbl_cfg));
587  } catch(invalid_pbl_exception&) {}
588  }
589  }
590 
591  if(addons_.empty()) {
592  show_transient_message(_("No Add-ons Available"), _("There are no add-ons available for download from this server."));
593  }
594 
595  addon_list& list = find_widget<addon_list>(get_window(), "addons", false);
596  list.set_addons(addons_);
597 
598  bool has_upgradable_addons = false;
599  for(const auto& a : addons_) {
600  tracking_info_[a.first] = get_addon_tracking_info(a.second);
601 
602  if(tracking_info_[a.first].state == ADDON_INSTALLED_UPGRADABLE) {
603  has_upgradable_addons = true;
604  }
605  }
606 
607  find_widget<button>(get_window(), "update_all", false).set_active(has_upgradable_addons);
608 
609  apply_filters();
610 }
611 
613 {
614  load_addon_list();
615 
616  // Reselect the add-on.
617  find_widget<addon_list>(get_window(), "addons", false).select_addon(id);
618  on_addon_select();
619 }
620 
621 boost::dynamic_bitset<> addon_manager::get_name_filter_visibility() const
622 {
623  const text_box& name_filter = find_widget<const text_box>(get_window(), "filter", false);
624  const std::string& text = name_filter.get_value();
625 
626  filter_transform filter(utils::split(text, ' '));
627  boost::dynamic_bitset<> res;
628 
629  const config::const_child_itors& addon_cfgs = cfg_.child_range("campaign");
630 
631  for(const auto& a : addons_)
632  {
633  const config& addon_cfg = *std::find_if(addon_cfgs.begin(), addon_cfgs.end(),
634  [&a](const config& cfg)
635  {
636  return cfg["name"] == a.first;
637  });
638 
639  res.push_back(filter(addon_cfg));
640  }
641 
642  return res;
643 }
644 
645 boost::dynamic_bitset<> addon_manager::get_status_filter_visibility() const
646 {
647  const menu_button& status_filter = find_widget<const menu_button>(get_window(), "install_status_filter", false);
648  const ADDON_STATUS_FILTER selection = status_filter_types_[status_filter.get_value()].first;
649 
650  boost::dynamic_bitset<> res;
651  for(const auto& a : addons_) {
652  const addon_tracking_info& info = tracking_info_.at(a.second.id);
653 
654  res.push_back(
655  (selection == FILTER_ALL) ||
656  (selection == FILTER_INSTALLED && is_installed_addon_status(info.state)) ||
657  (selection == FILTER_UPGRADABLE && info.state == ADDON_INSTALLED_UPGRADABLE) ||
658  (selection == FILTER_PUBLISHABLE && info.can_publish == true) ||
659  (selection == FILTER_NOT_INSTALLED && info.state == ADDON_NONE)
660  );
661  }
662 
663  return res;
664 }
665 
666 boost::dynamic_bitset<> addon_manager::get_tag_filter_visibility() const
667 {
668  const auto& tag_filter = find_widget<const multimenu_button>(get_window(), "tag_filter", false);
669  const auto toggle_states = tag_filter.get_toggle_states();
670  if(toggle_states.none()) {
671  // Nothing selected. It means that all add-ons are shown.
672  boost::dynamic_bitset<> res_flipped(addons_.size());
673  return ~res_flipped;
674  }
675 
676  std::vector<std::string> selected_tags;
677  for(std::size_t i = 0; i < tag_filter_types_.size(); ++i) {
678  if(toggle_states[i]) {
679  selected_tags.push_back(tag_filter_types_[i].id);
680  }
681  }
682 
683  boost::dynamic_bitset<> res;
684  for(const auto& a : addons_) {
685  bool matched_tag = false;
686  for(const auto& id : selected_tags) {
687  if(utils::contains(a.second.tags, id)) {
688  matched_tag = true;
689  break;
690  }
691  }
692  res.push_back(matched_tag);
693  }
694 
695  return res;
696 }
697 
698 boost::dynamic_bitset<> addon_manager::get_type_filter_visibility() const
699 {
700  const multimenu_button& type_filter = find_widget<const multimenu_button>(get_window(), "type_filter", false);
701 
702  boost::dynamic_bitset<> toggle_states = type_filter.get_toggle_states();
703  if(toggle_states.none()) {
704  // Nothing selected. It means that *all* add-ons are shown.
705  boost::dynamic_bitset<> res_flipped(addons_.size());
706  return ~res_flipped;
707  } else {
708  boost::dynamic_bitset<> res;
709 
710  for(const auto& a : addons_) {
711  int index = std::distance(type_filter_types_.begin(),
712  std::find_if(type_filter_types_.begin(), type_filter_types_.end(),
713  [&a](const std::pair<ADDON_TYPE, std::string>& entry) {
714  return entry.first == a.second.type;
715  })
716  );
717  res.push_back(toggle_states[index]);
718  }
719  return res;
720  }
721 }
722 
723 boost::dynamic_bitset<> addon_manager::get_lang_filter_visibility() const
724 {
725  const multimenu_button& lang_filter = find_widget<const multimenu_button>(get_window(), "language_filter", false);
726 
727  boost::dynamic_bitset<> toggle_states = lang_filter.get_toggle_states();
728 
729  if(toggle_states.none()) {
730  boost::dynamic_bitset<> res_flipped(addons_.size());
731  return ~res_flipped;
732  } else {
733  boost::dynamic_bitset<> res;
734  for(const auto& a : addons_) {
735  bool retval = false;
736  // langcode -> string conversion vector, to be able to detect either
737  // langcodes or langstring entries
738  std::vector<std::string> lang_string_vector;
739  for (long unsigned int i = 0; i < a.second.locales.size(); i++) {
740  lang_string_vector.push_back(langcode_to_string(a.second.locales[i]));
741  }
742  // Find all toggle states, where toggle = true and lang = lang
743  for (long unsigned int i = 0; i < toggle_states.size(); i++) {
744  if (toggle_states[i] == true) {
745  // does lang_code match?
746  bool contains_lang_code = utils::contains(a.second.locales, language_filter_types_[i].second);
747  // does land_string match?
748  bool contains_lang_string = utils::contains(lang_string_vector, language_filter_types_[i].second);
749  if ((contains_lang_code || contains_lang_string) == true)
750  retval = true;
751  }
752  }
753  res.push_back(retval);
754  }
755  return res;
756  }
757 }
758 
760 {
761  // In the small-screen layout, the text_box for the filter keeps keyboard focus even when the
762  // details panel is visible, which means this can be called when the list isn't visible. That
763  // causes problems both because find_widget can throw exceptions, but also because changing the
764  // filters can hide the currently-shown add-on, triggering a different one to be selected in a
765  // way that would seem random unless the user realised that they were typing into a filter box.
766  //
767  // Quick workaround is to not process the new filter if the list isn't visible.
768  auto list = find_widget<addon_list>(get_window(), "addons", false, false);
769  if(!list) {
770  return;
771  }
772 
773  boost::dynamic_bitset<> res =
779  list->set_addon_shown(res);
780 }
781 
783 {
784  const menu_button& order_menu = find_widget<const menu_button>(get_window(), "order_dropdown", false);
785  const addon_order& order_struct = all_orders_.at(order_menu.get_value() / 2);
786  sort_order::type order = order_menu.get_value() % 2 == 0 ? sort_order::type::ascending : sort_order::type::descending;
788  if(order == sort_order::type::ascending) {
789  func = order_struct.sort_func_asc;
790  } else {
791  func = order_struct.sort_func_desc;
792  }
793 
794  find_widget<addon_list>(get_window(), "addons", false).set_addon_order(func);
797 }
798 
799 void addon_manager::on_order_changed(unsigned int sort_column, sort_order::type order)
800 {
801  menu_button& order_menu = find_widget<menu_button>(get_window(), "order_dropdown", false);
802  auto order_it = std::find_if(all_orders_.begin(), all_orders_.end(),
803  [sort_column](const addon_order& order) {return order.column_index == static_cast<int>(sort_column);});
804  int index = 2 * (std::distance(all_orders_.begin(), order_it));
805  if(order == sort_order::type::descending) {
806  ++index;
807  }
808  order_menu.set_value(index);
809  prefs::get().set_addon_manager_saved_order_name(order_it->as_preference);
811 }
812 
813 template<void(addon_manager::*fptr)(const addon_info& addon)>
815 {
816  // Explicitly return to the main page if we're in low-res mode so the list is visible.
817  if(stacked_widget* stk = find_widget<stacked_widget>(get_window(), "main_stack", false, false)) {
818  stk->select_layer(0);
819  find_widget<button>(get_window(), "details_toggle", false).set_label(_("Add-on Details"));
820  }
821 
822  addon_list& addons = find_widget<addon_list>(get_window(), "addons", false);
823  const addon_info* addon = addons.get_selected_addon();
824 
825  if(addon == nullptr) {
826  return;
827  }
828 
829  try {
830  (this->*fptr)(*addon);
831  } catch(const addons_client::user_exit&) {
832  // User canceled the op.
833  }
834 }
835 
837 {
838  addon_info versioned_addon = addon;
839  widget* parent = get_window();
840  if(stacked_widget* stk = find_widget<stacked_widget>(get_window(), "main_stack", false, false)) {
841  parent = stk->get_layer_grid(1);
842  }
843  if(addon.id == find_widget<addon_list>(get_window(), "addons", false).get_selected_addon()->id) {
844  versioned_addon.current_version = find_widget<menu_button>(parent, "version_filter", false).get_value_string();
845  }
846 
848 
849  // Take note if any wml_changes occurred
851 
854  }
855 }
856 
858 {
859  if(have_addon_pbl_info(addon.id) || have_addon_in_vcs_tree(addon.id)) {
861  _("The following add-on appears to have publishing or version control information stored locally, and will not be removed:") + " " +
862  addon.display_title_full());
863  return;
864  }
865 
866  bool success = remove_local_addon(addon.id);
867 
868  if(!success) {
869  gui2::show_error_message(_("The following add-on could not be deleted properly:") + " " + addon.display_title_full());
870  } else {
872 
874  }
875 }
876 
878 {
879  /* Currently, the install and update codepaths are the same, so this function simply
880  * calls the other. Since this might change in the future, I'm leaving this function
881  * here for now.
882  *
883  * - vultraz, 2017-03-12
884  */
885  install_addon(addon);
886 }
887 
889 {
890  for(const auto& a : addons_) {
891  if(tracking_info_[a.first].state == ADDON_INSTALLED_UPGRADABLE) {
893 
894  if(result.wml_changed) {
895  // Updating an add-on may have resulted in its dependencies being updated
896  // as well, so we need to reread version info blocks afterwards to make sure
897  // we don't try to re-download newly updated add-ons.
899  }
900 
901  // Take note if any wml_changes occurred
903  }
904  }
905 
907  load_addon_list();
908  }
909 }
910 
911 /** Performs all backend and UI actions for publishing the specified add-on. */
913 {
914  std::string server_msg;
915 
916  const std::string addon_id = addon.id;
917  // Since the user is planning to upload an addon, this is the right time to validate the .pbl.
918  config cfg = get_addon_pbl_info(addon_id, true);
919 
920  const version_info& version_to_publish = cfg["version"].str();
921 
922  if(version_to_publish <= tracking_info_[addon_id].remote_version) {
923  const int res = gui2::show_message(_("Warning"),
924  _("The remote version of this add-on is greater or equal to the version being uploaded. Do you really wish to continue?"),
926 
927  if(res != gui2::retval::OK) {
928  return;
929  }
930  }
931 
932  // if the passphrase isn't provided from the _server.pbl, try to pre-populate it from the preferences before prompting for it
933  if(cfg["passphrase"].empty()) {
934  cfg["passphrase"] = prefs::get().password(prefs::get().campaign_server(), cfg["author"]);
935  if(!gui2::dialogs::addon_auth::execute(cfg)) {
936  return;
937  } else {
938  prefs::get().set_password(prefs::get().campaign_server(), cfg["author"], cfg["passphrase"]);
939  }
940  } else if(cfg["forum_auth"].to_bool()) {
941  // if the uploader's forum password is present in the _server.pbl
942  gui2::show_error_message(_("The passphrase attribute cannot be present when forum_auth is used."));
943  return;
944  }
945 
946  if(!::image::exists(cfg["icon"].str())) {
947  gui2::show_error_message(_("Invalid icon path. Make sure the path points to a valid image."));
948  } else if(!client_.request_distribution_terms(server_msg)) {
950  _("The server responded with an error:") + "\n" + client_.get_last_server_error());
951  } else if(gui2::dialogs::addon_license_prompt::execute(server_msg)) {
952  if(!client_.upload_addon(addon_id, server_msg, cfg, tracking_info_[addon_id].state == ADDON_INSTALLED_LOCAL_ONLY)) {
953  const std::string& msg = _("The add-on was rejected by the server:") +
954  "\n\n" + client_.get_last_server_error();
955  const std::string& extra_data = client_.get_last_server_error_data();
956  if (!extra_data.empty()) {
957  // TODO: Allow user to copy the extra data portion to clipboard
958  // or something, maybe display it in a dialog similar to
959  // the WML load errors report in a monospace font and
960  // stuff (having a scroll container is especially
961  // important since a long list can cause the dialog to
962  // overflow).
963  gui2::show_error_message(msg + "\n\n" + extra_data, true);
964  } else {
966  }
967  } else {
968  gui2::show_transient_message(_("Response"), server_msg);
971  }
972  }
973 }
974 
975 /** Performs all backend and UI actions for taking down the specified add-on. */
977 {
978  const std::string addon_id = addon.id;
979  const std::string& text = VGETTEXT(
980  "Deleting '$addon|' will permanently erase its download and upload counts on the add-ons server. Do you really wish to continue?",
981  {{"addon", make_addon_title(addon_id)}} // FIXME: need the real title!
982  );
983 
984  const int res = gui2::show_message(_("Confirm"), text, gui2::dialogs::message::yes_no_buttons);
985 
986  if(res != gui2::retval::OK) {
987  return;
988  }
989 
990  std::string server_msg;
991  if(!client_.delete_remote_addon(addon_id, server_msg)) {
992  gui2::show_error_message(_("The server responded with an error:") + "\n" + client_.get_last_server_error());
993  } else {
994  // FIXME: translation needed!
995  gui2::show_transient_message(_("Response"), server_msg);
998  }
999 }
1000 
1001 /** Called when the player double-clicks an add-on. */
1003 {
1004  switch(tracking_info_[addon.id].state) {
1005  case ADDON_NONE:
1006  install_addon(addon);
1007  break;
1008  case ADDON_INSTALLED:
1009  if(!tracking_info_[addon.id].can_publish) {
1010  utils::string_map symbols{ { "addon", addon.display_title_full() } };
1011  int res = gui2::show_message(_("Uninstall add-on"),
1012  VGETTEXT("Do you want to uninstall '$addon|'?", symbols),
1014  if(res == gui2::retval::OK) {
1015  uninstall_addon(addon);
1016  }
1017  }
1018  break;
1020  update_addon(addon);
1021  break;
1024  publish_addon(addon);
1025  break;
1026  default:
1027  break;
1028  }
1029 }
1030 
1032 {
1033  help::show_help("installing_addons");
1034 }
1035 
1036 static std::string format_addon_time(std::time_t time)
1037 {
1038  if(time) {
1039  std::ostringstream ss;
1040 
1041  const std::string format = prefs::get().use_twelve_hour_clock_format()
1042  // TRANSLATORS: Month + day of month + year + 12-hour time, eg 'November 02 2021, 1:59 PM'. Format for your locale.
1043  // Format reference: https://www.boost.org/doc/libs/1_85_0/doc/html/date_time/date_time_io.html#date_time.format_flags
1044  ? _("%B %d %Y, %I:%M %p")
1045  // TRANSLATORS: Month + day of month + year + 24-hour time, eg 'November 02 2021, 13:59'. Format for your locale.
1046  // Format reference: https://www.boost.org/doc/libs/1_85_0/doc/html/date_time/date_time_io.html#date_time.format_flags
1047  : _("%B %d %Y, %H:%M");
1048 
1049  ss << translation::strftime(format, std::localtime(&time));
1050 
1051  return ss.str();
1052  }
1053 
1054  return font::unicode_em_dash;
1055 }
1056 
1058 {
1059  widget* parent = get_window();
1060  widget* parent_of_addons_list = parent;
1061  if(stacked_widget* stk = find_widget<stacked_widget>(get_window(), "main_stack", false, false)) {
1062  parent = stk->get_layer_grid(1);
1063  parent_of_addons_list = stk->get_layer_grid(0);
1064  }
1065 
1066  const addon_info* info = find_widget<addon_list>(parent_of_addons_list, "addons", false).get_selected_addon();
1067 
1068  if(info == nullptr) {
1069  return;
1070  }
1071 
1072  find_widget<drawing>(parent, "image", false).set_label(info->display_icon());
1073 
1074  find_widget<styled_widget>(parent, "title", false).set_label(info->display_title_translated_or_original());
1075  find_widget<styled_widget>(parent, "description", false).set_label(info->description_translated());
1076  menu_button& version_filter = find_widget<menu_button>(parent, "version_filter", false);
1077  find_widget<styled_widget>(parent, "author", false).set_label(info->author);
1078  find_widget<styled_widget>(parent, "type", false).set_label(info->display_type());
1079 
1080  styled_widget& status = find_widget<styled_widget>(parent, "status", false);
1082  status.set_use_markup(true);
1083 
1084  find_widget<styled_widget>(parent, "size", false).set_label(size_display_string(info->size));
1085  find_widget<styled_widget>(parent, "downloads", false).set_label(std::to_string(info->downloads));
1086  find_widget<styled_widget>(parent, "created", false).set_label(format_addon_time(info->created));
1087  find_widget<styled_widget>(parent, "updated", false).set_label(format_addon_time(info->updated));
1088 
1089  find_widget<styled_widget>(parent, "dependencies", false).set_label(!info->depends.empty()
1090  ? make_display_dependencies(info->id, addons_, tracking_info_)
1091  : _("addon_dependencies^None"));
1092 
1093  std::string languages;
1094 
1095  for(const auto& lc : info->locales) {
1096  const std::string& langlabel = langcode_to_string(lc);
1097  if(!langlabel.empty()) {
1098  if(!languages.empty()) {
1099  languages += ", ";
1100  }
1101  languages += langlabel;
1102  }
1103  }
1104 
1105  find_widget<styled_widget>(parent, "translations", false).set_label(!languages.empty() ? languages : _("translations^None"));
1106 
1107  const std::string& feedback_url = info->feedback_url;
1108  find_widget<label>(parent, "url", false).set_label(!feedback_url.empty() ? feedback_url : _("url^None"));
1109 
1110  if(auto addon_id = find_widget<label>(parent, "addon_id", false, true)) {
1111  addon_id->set_label(info->id);
1112  }
1113 
1114  bool installed = is_installed_addon_status(tracking_info_[info->id].state);
1115  bool updatable = tracking_info_[info->id].state == ADDON_INSTALLED_UPGRADABLE;
1116 
1117  stacked_widget& action_stack = find_widget<stacked_widget>(parent, "action_stack", false);
1118  // #TODO: Add tooltips with upload time and pack size
1119  std::vector<config> version_filter_entries;
1120 
1121  if(!tracking_info_[info->id].can_publish) {
1122  action_stack.select_layer(0);
1123 
1124  stacked_widget& install_update_stack = find_widget<stacked_widget>(parent, "install_update_stack", false);
1125  install_update_stack.select_layer(updatable ? 1 : 0);
1126 
1127  if(!updatable) {
1128  find_widget<button>(parent, "install", false).set_active(!installed);
1129  } else {
1130  find_widget<button>(parent, "update", false).set_active(true);
1131  }
1132 
1133  find_widget<button>(parent, "uninstall", false).set_active(installed);
1134 
1135  for(const auto& f : info->versions) {
1136  version_filter_entries.emplace_back("label", f.str());
1137  }
1138  } else {
1139  action_stack.select_layer(1);
1140 
1141  // Always enable the publish button, but disable the delete button if not yet published.
1142  find_widget<button>(parent, "publish", false).set_active(true);
1143  find_widget<button>(parent, "delete", false).set_active(!info->local_only);
1144 
1145  // Show only the version to be published
1146  version_filter_entries.emplace_back("label", info->current_version.str());
1147  }
1148 
1149  version_filter.set_values(version_filter_entries);
1150  version_filter.set_active(version_filter_entries.size() > 1);
1151 }
1152 
1154 {
1155  widget* parent = get_window();
1156  widget* parent_of_addons_list = parent;
1157  if(stacked_widget* stk = find_widget<stacked_widget>(get_window(), "main_stack", false, false)) {
1158  parent = stk->get_layer_grid(1);
1159  parent_of_addons_list = stk->get_layer_grid(0);
1160  }
1161 
1162  const addon_info* info = find_widget<addon_list>(parent_of_addons_list, "addons", false).get_selected_addon();
1163 
1164  if(info == nullptr) {
1165  return;
1166  }
1167 
1168  if(!tracking_info_[info->id].can_publish && is_installed_addon_status(tracking_info_[info->id].state)) {
1169  bool updatable = tracking_info_[info->id].installed_version
1170  != find_widget<menu_button>(parent, "version_filter", false).get_value_string();
1171  stacked_widget& action_stack = find_widget<stacked_widget>(parent, "action_stack", false);
1172  action_stack.select_layer(0);
1173 
1174  stacked_widget& install_update_stack = find_widget<stacked_widget>(parent, "install_update_stack", false);
1175  install_update_stack.select_layer(1);
1176  find_widget<button>(parent, "update", false).set_active(updatable);
1177  }
1178 }
1179 
1181 {
1184  return false;
1185  }
1186 
1187  return true;
1188 }
1189 
1190 } // 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: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:857
void on_order_changed(unsigned int sort_column, sort_order::type order)
Definition: manager.cpp:799
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:723
addons_client & client_
Definition: manager.hpp:80
boost::dynamic_bitset get_status_filter_visibility() const
Definition: manager.cpp:645
void execute_default_action(const addon_info &addon)
Called when the player double-clicks an add-on.
Definition: manager.cpp:1002
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:621
void toggle_details(button &btn, stacked_widget &stk)
Definition: manager.cpp:539
void reload_list_and_reselect_item(const std::string id)
Definition: manager.cpp:612
void delete_addon(const addon_info &addon)
Performs all backend and UI actions for taking down the specified add-on.
Definition: manager.cpp:976
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:698
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:666
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:836
void update_addon(const addon_info &addon)
Definition: manager.cpp:877
void publish_addon(const addon_info &addon)
Performs all backend and UI actions for publishing the specified add-on.
Definition: manager.cpp:912
@ 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
@ invisible
The user set the widget invisible, that means:
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:91
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:1036
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:314
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