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