The Battle for Wesnoth  1.13.11+dev
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Modules Pages
manager.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2008 - 2018 by Mark de Wever <koraq@xs4all.nl>
3  Part of the Battle for Wesnoth Project http://www.wesnoth.org/
4 
5  This program is free software; you can redistribute it and/or modify
6  it under the terms of the GNU General Public License as published by
7  the Free Software Foundation; either version 2 of the License, or
8  (at your option) any later version.
9  This program is distributed in the hope that it will be useful,
10  but WITHOUT ANY WARRANTY.
11 
12  See the COPYING file for more details.
13 */
14 
15 #define GETTEXT_DOMAIN "wesnoth-lib"
16 
18 
19 #include "addon/info.hpp"
20 #include "addon/manager.hpp"
21 #include "addon/state.hpp"
22 
23 #include "desktop/clipboard.hpp"
24 #include "desktop/open.hpp"
25 
26 #include "help/help.hpp"
27 #include "gettext.hpp"
28 #include "gui/auxiliary/filter.hpp"
30 #include "gui/dialogs/helper.hpp"
31 #include "gui/dialogs/message.hpp"
33 #include "gui/widgets/button.hpp"
34 #include "gui/widgets/label.hpp"
38 #include "gui/widgets/drawing.hpp"
39 #include "gui/widgets/image.hpp"
40 #ifdef GUI2_EXPERIMENTAL_LISTBOX
41 #include "gui/widgets/list.hpp"
42 #else
43 #include "gui/widgets/listbox.hpp"
44 #endif
45 #include "gui/widgets/pane.hpp"
46 #include "gui/widgets/settings.hpp"
48 #include "gui/widgets/text_box.hpp"
49 #include "gui/widgets/window.hpp"
51 #include "formula/string_utils.hpp"
52 #include "image.hpp"
53 #include "language.hpp"
54 #include "preferences/general.hpp"
55 #include "utils/general.hpp"
56 
57 #include "config.hpp"
58 
59 #include "utils/functional.hpp"
60 
61 #include <iomanip>
62 #include <sstream>
63 #include <stdexcept>
64 #include "utils/io.hpp"
65 
66 namespace gui2
67 {
68 namespace dialogs
69 {
70 
71 /*WIKI
72  * @page = GUIWindowDefinitionWML
73  * @order = 2_addon_list
74  *
75  * == Addon list ==
76  *
77  * This shows the dialog with the addons to install. This dialog is under
78  * construction and only used with --new-widgets.
79  *
80  * @begin{table}{dialog_widgets}
81  *
82  * addons & & listbox & m &
83  * A listbox that will contain the info about all addons on the server. $
84  *
85  * -name & & styled_widget & o &
86  * The name of the addon. $
87  *
88  * -version & & styled_widget & o &
89  * The version number of the addon. $
90  *
91  * -author & & styled_widget & o &
92  * The author of the addon. $
93  *
94  * -downloads & & styled_widget & o &
95  * The number of times the addon has been downloaded. $
96  *
97  * -size & & styled_widget & o &
98  * The size of the addon. $
99  *
100  * @end{table}
101  */
102 
103 namespace {
104  struct filter_transform
105  {
106  explicit filter_transform(const std::vector<std::string>& filtertext) : filtertext_(filtertext) {}
107  bool operator()(const config& cfg) const
108  {
109  for(const auto& filter : filtertext_)
110  {
111  bool found = false;
112  for(const auto& attribute : cfg.attribute_range())
113  {
114  std::string val = attribute.second.str();
115  if(std::search(val.begin(),
116  val.end(),
117  filter.begin(),
118  filter.end(),
120  != val.end())
121  {
122  found = true;
123  break;
124  }
125  }
126  if(!found) {
127  return false;
128  }
129  }
130  return true;
131  }
132  const std::vector<std::string> filtertext_;
133  };
134 
135  std::string make_display_dependencies(
136  const std::string& addon_id,
137  const addons_list& addons_list,
138  const addons_tracking_list& addon_states)
139  {
140  const addon_info& addon = addons_list.at(addon_id);
141  std::string str;
142 
143  const std::set<std::string>& deps = addon.resolve_dependencies(addons_list);
144 
145  for(const auto& dep_id : deps) {
146  addon_info dep;
147  addon_tracking_info depstate;
148 
149  addons_list::const_iterator ali = addons_list.find(dep_id);
150  addons_tracking_list::const_iterator tli = addon_states.find(dep_id);
151 
152  if(ali == addons_list.end()) {
153  dep.id = dep_id; // Build dummy addon_info.
154  } else {
155  dep = ali->second;
156  }
157 
158  if(tli == addon_states.end()) {
159  depstate = get_addon_tracking_info(dep);
160  } else {
161  depstate = tli->second;
162  }
163 
164  if(!str.empty()) {
165  str += ", ";
166  }
167 
169  }
170 
171  return str;
172  }
173 
174  std::string langcode_to_string(const std::string& lcode)
175  {
176  for(const auto & ld : get_languages())
177  {
178  if(ld.localename == lcode || ld.localename.substr(0, 2) == lcode) {
179  return ld.language;
180  }
181  }
182 
183  return "";
184  }
185 }
186 
187 REGISTER_DIALOG(addon_manager)
188 
189 const std::vector<std::pair<ADDON_STATUS_FILTER, std::string>> addon_manager::status_filter_types_{
190  {FILTER_ALL, N_("addons_view^All Add-ons")},
191  {FILTER_INSTALLED, N_("addons_view^Installed")},
192  {FILTER_UPGRADABLE, N_("addons_view^Upgradable")},
193  {FILTER_PUBLISHABLE, N_("addons_view^Publishable")},
194  {FILTER_NOT_INSTALLED, N_("addons_view^Not Installed")},
195 };
196 
197 const std::vector<std::pair<ADDON_TYPE, std::string>> addon_manager::type_filter_types_{
198  {ADDON_SP_CAMPAIGN, N_("addons_of_type^Campaigns")},
199  {ADDON_SP_SCENARIO, N_("addons_of_type^Scenarios")},
200  {ADDON_SP_MP_CAMPAIGN, N_("addons_of_type^SP/MP campaigns")},
201  {ADDON_MP_CAMPAIGN, N_("addons_of_type^MP campaigns")},
202  {ADDON_MP_SCENARIO, N_("addons_of_type^MP scenarios")},
203  {ADDON_MP_MAPS, N_("addons_of_type^MP map-packs")},
204  {ADDON_MP_ERA, N_("addons_of_type^MP eras")},
205  {ADDON_MP_FACTION, N_("addons_of_type^MP factions")},
206  {ADDON_MOD, N_("addons_of_type^Modifications")},
207  {ADDON_CORE, N_("addons_of_type^Cores")},
208  {ADDON_MEDIA, N_("addons_of_type^Resources")},
209  // FIXME: (also in WML) should this and Unknown be a single option in the UI?
210  {ADDON_OTHER, N_("addons_of_type^Other")},
211  {ADDON_UNKNOWN, N_("addons_of_type^Unknown")},
212 };
213 
214 const std::vector<addon_manager::addon_order> addon_manager::all_orders_{
215  {N_("addons_order^Name ($order)"), 0,
216  [](const addon_info& a, const addon_info& b) { return a.title < b.title; },
217  [](const addon_info& a, const addon_info& b) { return a.title > b.title; }},
218  {N_("addons_order^Author ($order)"), 1,
219  [](const addon_info& a, const addon_info& b) { return a.author < b.author; },
220  [](const addon_info& a, const addon_info& b) { return a.author > b.author; }},
221  {N_("addons_order^Size ($order)"), 2,
222  [](const addon_info& a, const addon_info& b) { return a.size < b.size; },
223  [](const addon_info& a, const addon_info& b) { return a.size > b.size; }},
224  {N_("addons_order^Downloads ($order)"), 3,
225  [](const addon_info& a, const addon_info& b) { return a.downloads < b.downloads; },
226  [](const addon_info& a, const addon_info& b) { return a.downloads > b.downloads; }},
227  {N_("addons_order^Type ($order)"), 4,
228  [](const addon_info& a, const addon_info& b) { return a.display_type() < b.display_type(); },
229  [](const addon_info& a, const addon_info& b) { return a.display_type() > b.display_type(); }},
230  {N_("addons_order^Last updated ($order)"), -1,
231  [](const addon_info& a, const addon_info& b) { return a.updated < b.updated; },
232  [](const addon_info& a, const addon_info& b) { return a.updated > b.updated; }},
233  {N_("addons_order^First uploaded ($order)"), -1,
234  [](const addon_info& a, const addon_info& b) { return a.created < b.created; },
235  [](const addon_info& a, const addon_info& b) { return a.created > b.created; }}
236 };
237 
239  : orders_()
240  , cfg_()
241  , client_(client)
242  , addons_()
243  , tracking_info_()
244  , need_wml_cache_refresh_(false)
245 {
246 }
247 
249 {
250  apply_filters(*textbox->get_window());
251 }
252 
254 {
255  std::string s;
256 
257  utils::string_map i18n_symbols {{"local_version", state.installed_version.str()}};
258 
259  switch(state.state) {
260  case ADDON_NONE:
261  s = !state.can_publish
262  ? _("addon_state^Not installed")
263  : _("addon_state^Published, not installed");
264  break;
265  case ADDON_INSTALLED:
266  s = !state.can_publish
267  ? _("addon_state^Installed")
268  : _("addon_state^Published");
269  break;
270  case ADDON_NOT_TRACKED:
271  s = !state.can_publish
272  ? _("addon_state^Installed, not tracking local version")
273  // Published add-ons often don't have local status information,
274  // hence untracked. This should be considered normal.
275  : _("addon_state^Published, not tracking local version");
276  break;
278  const std::string vstr = !state.can_publish
279  ? _("addon_state^Installed ($local_version|), upgradable")
280  : _("addon_state^Published ($local_version| installed), upgradable");
281 
282  s = utils::interpolate_variables_into_string(vstr, &i18n_symbols);
283  } break;
285  const std::string vstr = !state.can_publish
286  ? _("addon_state^Installed ($local_version|), outdated on server")
287  : _("addon_state^Published ($local_version| installed), outdated on server");
288 
289  s = utils::interpolate_variables_into_string(vstr, &i18n_symbols);
290  } break;
292  s = !state.can_publish
293  ? _("addon_state^Installed, not ready to publish")
294  : _("addon_state^Ready to publish");
295  break;
297  s = !state.can_publish
298  ? _("addon_state^Installed, broken")
299  : _("addon_state^Published, broken");
300  break;
301  default:
302  s = _("addon_state^Unknown");
303  }
304 
306 }
307 
309 {
310  window.set_escape_disabled(true);
311 
312  addon_list& list = find_widget<addon_list>(&window, "addons", false);
313 
314  text_box& filter = find_widget<text_box>(&window, "filter", false);
316 
318  this, std::placeholders::_1, std::ref(window)));
320  this, std::placeholders::_1, std::ref(window)));
322  this, std::placeholders::_1, std::ref(window)));
323 
325  this, std::placeholders::_1, std::ref(window)));
327  this, std::placeholders::_1, std::ref(window)));
328 
329  list.set_modified_signal_handler([this, &window]() { on_addon_select(window); });
330 
331  fetch_addons_list(window);
332  load_addon_list(window);
333 
334  menu_button& status_filter = find_widget<menu_button>(&window, "install_status_filter", false);
335 
336  std::vector<config> status_filter_entries;
337  for(const auto& f : status_filter_types_) {
338  status_filter_entries.emplace_back(config {"label", t_string(f.second, GETTEXT_DOMAIN)});
339  }
340 
341  status_filter.set_values(status_filter_entries);
342  status_filter.connect_click_handler(std::bind(&addon_manager::apply_filters, this, std::ref(window)));
343 
344  multimenu_button& type_filter = find_widget<multimenu_button>(&window, "type_filter", false);
345 
346  std::vector<config> type_filter_entries;
347  for(const auto& f : type_filter_types_) {
348  type_filter_entries.emplace_back(config {"label", t_string(f.second, GETTEXT_DOMAIN), "checkbox", false});
349  }
350 
351  type_filter.set_values(type_filter_entries);
352 
353  connect_signal_notify_modified(type_filter, std::bind(&addon_manager::apply_filters, this, std::ref(window)));
354 
355  menu_button& order_dropdown = find_widget<menu_button>(&window, "order_dropdown", false);
356 
357  std::vector<config> order_dropdown_entries;
358  for(const auto& f : all_orders_) {
359  utils::string_map symbols;
360 
361  // TRANSLATORS: ascending
362  symbols["order"] = _("asc");
363  config entry{"label", VGETTEXT(f.label.c_str(), symbols)};
364  order_dropdown_entries.push_back(entry);
365  // TRANSLATORS: descending
366  symbols["order"] = _("desc");
367  entry["label"] = VGETTEXT(f.label.c_str(), symbols);
368  order_dropdown_entries.push_back(entry);
369  }
370 
371  order_dropdown.set_values(order_dropdown_entries);
372  order_dropdown.connect_click_handler(std::bind(&addon_manager::order_addons, this, std::ref(window)));
373 
374  button& url_go_button = find_widget<button>(&window, "url_go", false);
375  button& url_copy_button = find_widget<button>(&window, "url_copy", false);
376  text_box& url_textbox = find_widget<text_box>(&window, "url", false);
377 
378  url_textbox.set_active(false);
379 
381  url_copy_button.set_active(false);
382  url_copy_button.set_tooltip(_("Clipboard support not found, contact your packager"));
383  }
384 
386  // No point in displaying the button on platforms that can't do
387  // open_object().
389  }
390 
392  find_widget<button>(&window, "install", false),
393  std::bind(&addon_manager::install_selected_addon, this, std::ref(window)));
394 
396  find_widget<button>(&window, "uninstall", false),
397  std::bind(&addon_manager::uninstall_selected_addon, this, std::ref(window)));
398 
400  find_widget<button>(&window, "update", false),
401  std::bind(&addon_manager::update_selected_addon, this, std::ref(window)));
402 
404  find_widget<button>(&window, "publish", false),
405  std::bind(&addon_manager::publish_selected_addon, this, std::ref(window)));
406 
408  find_widget<button>(&window, "delete", false),
409  std::bind(&addon_manager::delete_selected_addon, this, std::ref(window)));
410 
412  find_widget<button>(&window, "update_all", false),
413  std::bind(&addon_manager::update_all_addons, this, std::ref(window)));
414 
416  url_go_button,
417  std::bind(&addon_manager::browse_url_callback, this, std::ref(url_textbox)));
418 
420  url_copy_button,
421  std::bind(&addon_manager::copy_url_callback, this, std::ref(url_textbox)));
422 
424  find_widget<button>(&window, "show_help", false),
425  std::bind(&addon_manager::show_help, this));
426 
427  if(stacked_widget* stk = find_widget<stacked_widget>(&window, "main_stack", false, false)) {
428  button& btn = find_widget<button>(&window, "details_toggle", false);
429  connect_signal_mouse_left_click(btn, std::bind(&addon_manager::toggle_details, this, std::ref(btn), std::ref(*stk)));
430  stk->select_layer(0);
431  }
432 
433  on_addon_select(window);
434 
435  window.set_enter_disabled(true);
436 
437  window.keyboard_capture(&filter);
439 
440  list.set_callback_order_change(std::bind(&addon_manager::on_order_changed, this, std::ref(window),
441  std::placeholders::_1, std::placeholders::_2));
442 
443  // Use handle the special addon_list retval to allow installing addons on double click
444  window.set_exit_hook(std::bind(&addon_manager::exit_hook, this, std::ref(window)));
445 }
446 
448 {
449  if(stk.current_layer() == 0) {
450  btn.set_label(_("addons^Back to List"));
451  stk.select_layer(1);
452  } else {
453  btn.set_label(_("Addon Details"));
454  stk.select_layer(0);
455  }
456 }
457 
459 {
461  if(!cfg_) {
462  gui2::show_error_message(_("An error occurred while downloading the add-ons list from the server."));
463  window.close();
464  }
465 }
466 
467 void addon_manager::load_addon_list(window& window)
468 {
471  }
472 
474 
475  std::vector<std::string> publishable_addons = available_addons();
476 
477  for(std::string id : publishable_addons) {
478  if(addons_.find(id) == addons_.end()) {
479  // Get a config from the addon's pbl file
480  // Note that the name= key is necessary or stuff breaks, since the filter code uses this key
481  // to match add-ons in the config list. It also fills in addon_info's id field. It's also
482  // neccessay to set local_only here so that flag can be properly set after addons_ is cleared
483  // and recreated by read_addons_list.
484  config pbl_cfg = get_addon_pbl_info(id);
485  pbl_cfg["name"] = id;
486  pbl_cfg["local_only"] = true;
487 
488  // Add the add-on to the list.
489  addon_info addon(pbl_cfg);
490  addons_[id] = addon;
491 
492  // Add the addon to the config entry
493  cfg_.add_child("campaign", std::move(pbl_cfg));
494  }
495  }
496 
497  if(addons_.empty()) {
498  show_transient_message(_("No Add-ons Available"), _("There are no add-ons available for download from this server."));
499  }
500 
501  addon_list& list = find_widget<addon_list>(&window, "addons", false);
502  list.set_addons(addons_);
503 
504  bool has_upgradable_addons = false;
505  for(const auto& a : addons_) {
506  tracking_info_[a.first] = get_addon_tracking_info(a.second);
507 
508  if(tracking_info_[a.first].state == ADDON_INSTALLED_UPGRADABLE) {
509  has_upgradable_addons = true;
510  }
511  }
512 
513  find_widget<button>(&window, "update_all", false).set_active(has_upgradable_addons);
514 
515  apply_filters(window);
516 }
517 
519 {
520  load_addon_list(window);
521 
522  // Reselect the add-on.
523  find_widget<addon_list>(&window, "addons", false).select_addon(id);
524  on_addon_select(window);
525 }
526 
527 boost::dynamic_bitset<> addon_manager::get_name_filter_visibility(const window& window) const
528 {
529  const text_box& name_filter = find_widget<const text_box>(&window, "filter", false);
530  const std::string& text = name_filter.get_value();
531 
532  filter_transform filter(utils::split(text, ' '));
533  boost::dynamic_bitset<> res;
534 
535  const config::const_child_itors& addon_cfgs = cfg_.child_range("campaign");
536 
537  for(const auto& a : addons_)
538  {
539  const config& addon_cfg = *std::find_if(addon_cfgs.begin(), addon_cfgs.end(),
540  [&a](const config& cfg)
541  {
542  return cfg["name"] == a.first;
543  });
544 
545  res.push_back(filter(addon_cfg));
546  }
547 
548  return res;
549 }
550 
551 boost::dynamic_bitset<> addon_manager::get_status_filter_visibility(const window& window) const
552 {
553  const menu_button& status_filter = find_widget<const menu_button>(&window, "install_status_filter", false);
554  const ADDON_STATUS_FILTER selection = status_filter_types_[status_filter.get_value()].first;
555 
556  boost::dynamic_bitset<> res;
557  for(const auto& a : addons_) {
558  const addon_tracking_info& info = tracking_info_.at(a.second.id);
559 
560  res.push_back(
561  (selection == FILTER_ALL) ||
562  (selection == FILTER_INSTALLED && is_installed_addon_status(info.state)) ||
563  (selection == FILTER_UPGRADABLE && info.state == ADDON_INSTALLED_UPGRADABLE) ||
564  (selection == FILTER_PUBLISHABLE && info.can_publish == true) ||
565  (selection == FILTER_NOT_INSTALLED && info.state == ADDON_NONE)
566  );
567  }
568 
569  return res;
570 }
571 
572 boost::dynamic_bitset<> addon_manager::get_type_filter_visibility(const window& window) const
573 {
574  const multimenu_button& type_filter = find_widget<const multimenu_button>(&window, "type_filter", false);
575 
576  boost::dynamic_bitset<> toggle_states = type_filter.get_toggle_states();
577  if(toggle_states.none()) {
578  // Nothing selected. It means that *all* add-ons are shown.
579  boost::dynamic_bitset<> res_flipped(addons_.size());
580  return ~res_flipped;
581  } else {
582  boost::dynamic_bitset<> res;
583 
584  for(const auto& a : addons_) {
585  int index = std::distance(type_filter_types_.begin(),
586  std::find_if(type_filter_types_.begin(), type_filter_types_.end(),
587  [&a](const std::pair<ADDON_TYPE, std::string>& entry) {
588  return entry.first == a.second.type;
589  })
590  );
591  res.push_back(toggle_states[index]);
592  }
593 
594  return res;
595  }
596 }
597 
598 void addon_manager::apply_filters(window& window)
599 {
600  boost::dynamic_bitset<> res =
603  & get_name_filter_visibility(window);
604  find_widget<addon_list>(&window, "addons", false).set_addon_shown(res);
605 }
606 
607 void addon_manager::order_addons(window& window)
608 {
609  const menu_button& order_menu = find_widget<const menu_button>(&window, "order_dropdown", false);
610  const addon_order& order_struct = all_orders_.at(order_menu.get_value() / 2);
613  if(order == listbox::SORT_ASCENDING) {
614  func = order_struct.sort_func_asc;
615  } else {
616  func = order_struct.sort_func_desc;
617  }
618 
619  find_widget<addon_list>(&window, "addons", false).set_addon_order(func);
620 }
621 
622 void addon_manager::on_order_changed(window& window, unsigned int sort_column, listbox::SORT_ORDER order)
623 {
624  menu_button& order_menu = find_widget<menu_button>(&window, "order_dropdown", false);
625  auto order_it = std::find_if(all_orders_.begin(), all_orders_.end(),
626  [sort_column](const addon_order& order) {return order.column_index == static_cast<int>(sort_column);});
627  int index = 2 * (std::distance(all_orders_.begin(), order_it));
628  if(order == listbox::SORT_DESCENDING) {
629  ++index;
630  }
631  order_menu.set_value(index);
632 }
633 
634 template<void(addon_manager::*fptr)(const addon_info& addon, window& window)>
636 {
637  // Explicitly return to the main page if we're in low-res mode so the list is visible.
638  if(stacked_widget* stk = find_widget<stacked_widget>(&window, "main_stack", false, false)) {
639  stk->select_layer(0);
640  }
641 
642  addon_list& addons = find_widget<addon_list>(&window, "addons", false);
643  const addon_info* addon = addons.get_selected_addon();
644 
645  if(addon == nullptr) {
646  return;
647  }
648 
649  try {
650  (this->*fptr)(*addon, window);
651  } catch(const addons_client::user_exit&) {
652  // User canceled the op.
653  }
654 }
655 
656 void addon_manager::install_addon(const addon_info& addon, window& window)
657 {
659 
660  // Take note if any wml_changes occurred
662 
664  reload_list_and_reselect_item(addon.id, window);
665  }
666 }
667 
668 void addon_manager::uninstall_addon(const addon_info& addon, window& window)
669 {
670  if(have_addon_pbl_info(addon.id) || have_addon_in_vcs_tree(addon.id)) {
672  _("The following add-on appears to have publishing or version control information stored locally, and will not be removed:") + " " +
673  addon.display_title());
674  return;
675  }
676 
677  bool success = remove_local_addon(addon.id);
678 
679  if(!success) {
680  gui2::show_error_message(_("The following add-on could not be deleted properly:") + " " + addon.display_title());
681  } else {
683 
684  reload_list_and_reselect_item(addon.id, window);
685  }
686 }
687 
688 void addon_manager::update_addon(const addon_info& addon, window& window)
689 {
690  /* Currently, the install and update codepaths are the same, so this function simply
691  * calls the other. Since this might change in the future, I'm leaving this function
692  * here for now.
693  *
694  * - vultraz, 2017-03-12
695  */
696  install_addon(addon, window);
697 }
698 
700 {
701  for(const auto& a : addons_) {
702  if(tracking_info_[a.first].state == ADDON_INSTALLED_UPGRADABLE) {
704 
705  // Take note if any wml_changes occurred
707  }
708  }
709 
711  load_addon_list(window);
712  }
713 }
714 
715 /** Performs all backend and UI actions for publishing the specified add-on. */
716 void addon_manager::publish_addon(const addon_info& addon, window& window)
717 {
718  std::string server_msg;
719 
720  const std::string addon_id = addon.id;
721  config cfg = get_addon_pbl_info(addon_id);
722 
723  const version_info& version_to_publish = cfg["version"].str();
724 
725  if(version_to_publish <= tracking_info_[addon_id].remote_version) {
726  const int res = gui2::show_message(_("Warning"),
727  _("The remote version of this add-on is greater or equal to the version being uploaded. Do you really wish to continue?"),
729 
730  if(res != gui2::window::OK) {
731  return;
732  }
733  }
734 
735  if(!::image::exists(cfg["icon"].str())) {
736  gui2::show_error_message(_("Invalid icon path. Make sure the path points to a valid image."));
737  } else if(!client_.request_distribution_terms(server_msg)) {
739  _("The server responded with an error:") + "\n" + client_.get_last_server_error());
740  } else if(gui2::show_message(_("Terms"), server_msg, gui2::dialogs::message::ok_cancel_buttons, true) == gui2::window::OK) {
741  if(!client_.upload_addon(addon_id, server_msg, cfg)) {
742  const std::string& msg = _("The server responded with an error:") +
743  "\n\n" + client_.get_last_server_error();
744  const std::string& extra_data = client_.get_last_server_error_data();
745  if (!extra_data.empty()) {
746  // TODO: Allow user to copy the extra data portion to clipboard
747  // or something, maybe display it in a dialog similar to
748  // the WML load errors report in a monospace font and
749  // stuff (having a scroll container is especially
750  // important since a long list can cause the dialog to
751  // overflow).
752  gui2::show_error_message(msg + "\n\n" + extra_data, true);
753  } else {
754  gui2::show_error_message(msg, true);
755  }
756  } else {
757  gui2::show_transient_message(_("Response"), server_msg);
758  fetch_addons_list(window);
759  reload_list_and_reselect_item(addon_id, window);
760  }
761  }
762 }
763 
764 /** Performs all backend and UI actions for taking down the specified add-on. */
765 void addon_manager::delete_addon(const addon_info& addon, window& window)
766 {
767  const std::string addon_id = addon.id;
768  const std::string& text = vgettext(
769  "Deleting '$addon|' will permanently erase its download and upload counts on the add-ons server. Do you really wish to continue?",
770  {{"addon", make_addon_title(addon_id)}} // FIXME: need the real title!
771  );
772 
773  const int res = gui2::show_message(_("Confirm"), text, gui2::dialogs::message::yes_no_buttons);
774 
775  if(res != gui2::window::OK) {
776  return;
777  }
778 
779  std::string server_msg;
780  if(!client_.delete_remote_addon(addon_id, server_msg)) {
781  gui2::show_error_message(_("The server responded with an error:") + "\n" + client_.get_last_server_error());
782  } else {
783  // FIXME: translation needed!
784  gui2::show_transient_message(_("Response"), server_msg);
785  fetch_addons_list(window);
786  reload_list_and_reselect_item(addon_id, window);
787  }
788 }
789 
790 /** Called when the player double-clicks an add-on. */
791 void addon_manager::execute_default_action(const addon_info& addon, window& window)
792 {
793  switch(tracking_info_[addon.id].state) {
794  case ADDON_NONE:
795  install_addon(addon, window);
796  break;
797  case ADDON_INSTALLED:
798  if(!tracking_info_[addon.id].can_publish) {
799  utils::string_map symbols{ { "addon", addon.display_title() } };
800  int res = gui2::show_message(_("Uninstall add-on"),
801  vgettext("Do you want to uninstall '$addon|'?", symbols),
803  if(res == gui2::window::OK) {
804  uninstall_addon(addon, window);
805  }
806  }
807  break;
809  update_addon(addon, window);
810  break;
813  publish_addon(addon, window);
814  break;
815  default:
816  break;
817  }
818 }
819 
821 {
822  help::show_help("installing_addons");
823 }
824 
826 {
827  /* TODO: ask for confirmation */
828  desktop::open_object(url_box.get_value());
829 }
830 
832 {
834 }
835 
836 static std::string format_addon_time(time_t time)
837 {
838  if(time) {
839  std::ostringstream ss;
840 
842  ? "%Y-%m-%d %I:%M %p"
843  : "%Y-%m-%d %H:%M";
844 
845  ss << utils::put_time(std::localtime(&time), format);
846 
847  return ss.str();
848  }
849 
850  return font::unicode_em_dash;
851 }
852 
853 void addon_manager::on_addon_select(window& window)
854 {
855  const addon_info* info = find_widget<addon_list>(&window, "addons", false).get_selected_addon();
856 
857  if(info == nullptr) {
858  return;
859  }
860 
861  widget* parent = &window;
862  if(stacked_widget* stk = find_widget<stacked_widget>(&window, "main_stack", false, false)) {
863  parent = stk->get_layer_grid(1);
864  }
865 
866  find_widget<drawing>(parent, "image", false).set_label(info->display_icon());
867 
868  find_widget<styled_widget>(parent, "title", false).set_label(info->display_title());
869  find_widget<styled_widget>(parent, "description", false).set_label(info->description);
870  find_widget<styled_widget>(parent, "version", false).set_label(info->version.str());
871  find_widget<styled_widget>(parent, "author", false).set_label(info->author);
872  find_widget<styled_widget>(parent, "type", false).set_label(info->display_type());
873 
874  styled_widget& status = find_widget<styled_widget>(parent, "status", false);
876  status.set_use_markup(true);
877 
878  find_widget<styled_widget>(parent, "size", false).set_label(size_display_string(info->size));
879  find_widget<styled_widget>(parent, "downloads", false).set_label(std::to_string(info->downloads));
880  find_widget<styled_widget>(parent, "created", false).set_label(format_addon_time(info->created));
881  find_widget<styled_widget>(parent, "updated", false).set_label(format_addon_time(info->updated));
882 
883  find_widget<styled_widget>(parent, "dependencies", false).set_label(!info->depends.empty()
884  ? make_display_dependencies(info->id, addons_, tracking_info_)
885  : _("None"));
886 
887  std::string languages;
888 
889  for(const auto& lc : info->locales) {
890  const std::string& langlabel = langcode_to_string(lc);
891  if(!langlabel.empty()) {
892  if(!languages.empty()) {
893  languages += ", ";
894  }
895  languages += langlabel;
896  }
897  }
898 
899  find_widget<styled_widget>(parent, "translations", false).set_label(!languages.empty() ? languages : _("None"));
900 
901  const std::string& feedback_url = info->feedback_url;
902 
903  if(!feedback_url.empty()) {
904  find_widget<stacked_widget>(parent, "feedback_stack", false).select_layer(1);
905  find_widget<text_box>(parent, "url", false).set_value(feedback_url);
906  } else {
907  find_widget<stacked_widget>(parent, "feedback_stack", false).select_layer(0);
908  }
909 
910  bool installed = is_installed_addon_status(tracking_info_[info->id].state);
911  bool updatable = tracking_info_[info->id].state == ADDON_INSTALLED_UPGRADABLE;
912 
913  stacked_widget& action_stack = find_widget<stacked_widget>(parent, "action_stack", false);
914 
915  if(!tracking_info_[info->id].can_publish) {
916  action_stack.select_layer(0);
917 
918  stacked_widget& install_update_stack = find_widget<stacked_widget>(parent, "install_update_stack", false);
919  install_update_stack.select_layer(updatable ? 1 : 0);
920 
921  if(!updatable) {
922  find_widget<button>(parent, "install", false).set_active(!installed);
923  } else {
924  find_widget<button>(parent, "update", false).set_active(true);
925  }
926 
927  find_widget<button>(parent, "uninstall", false).set_active(installed);
928  } else {
929  action_stack.select_layer(1);
930 
931  // Always enable the publish button, but disable the delete button if not yet published.
932  find_widget<button>(parent, "publish", false).set_active(true);
933  find_widget<button>(parent, "delete", false).set_active(!info->local_only);
934  }
935 }
936 
937 bool addon_manager::exit_hook(window& window)
938 {
941  return false;
942  }
943 
944  return true;
945 }
946 
947 } // namespace dialogs
948 } // namespace gui2
int size
Definition: info.hpp:41
void read_addons_list(const config &cfg, addons_list &dest)
Definition: info.cpp:196
void close()
Requests to close the window.
Definition: window.hpp:211
boost::dynamic_bitset get_toggle_states() const
Get the current state of the menu options.
void set_text_changed_callback(std::function< void(text_box_base *textbox, const std::string text)> cb)
Set the text_changed callback.
ADDON_STATUS state
Definition: state.hpp:56
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:152
Dialog is closed with ok button.
Definition: window.hpp:101
Modification of the game.
Definition: validation.hpp:52
Single-player scenario.
Definition: validation.hpp:44
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:116
std::vector< char_t > string
size_t index(const utf8::string &str, const size_t index)
Codepoint index corresponding to the nth character in a UTF-8 string.
Definition: unicode.cpp:71
config cfg_
Config which contains the list with the campaigns.
Definition: manager.hpp:81
Abstract base class for text items.
const addon_info * get_selected_addon() const
Returns the selected add-on.
Definition: addon_list.cpp:304
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 ...
std::map< std::string, t_string > string_map
bool exit_hook(window &window)
Definition: manager.cpp:937
void load_addon_list(window &window)
Definition: manager.cpp:467
void connect_click_handler(const event::signal_function &signal)
Inherited from clickable_item.
Definition: menu_button.hpp:58
std::function< bool(const addon_info &, const addon_info &)> addon_sort_func
Definition: addon_list.hpp:41
an add-on that fits in no other category
Definition: validation.hpp:55
Simple push button.
Definition: menu_button.hpp:41
boost::dynamic_bitset get_name_filter_visibility(const window &window) const
Definition: manager.cpp:527
Total Conversion Core.
Definition: validation.hpp:42
bool available()
Whether wesnoth was compiled with support for a clipboard.
Definition: clipboard.cpp:56
addons_tracking_list tracking_info_
Definition: manager.hpp:87
void execute_default_action_on_selected_addon(window &window)
Definition: manager.hpp:129
logger & info()
Definition: log.cpp:91
void add_list_to_keyboard_chain()
Adds the internal listbox to the keyboard event chain.
Definition: addon_list.cpp:361
#define a
Shows an ok and cancel button.
Definition: message.hpp:75
This file contains the window object, this object is a top level container which has the event manage...
child_itors child_range(config_key_type key)
Definition: config.cpp:362
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, const bool restore_background)
Shows a transient message to the user.
Base class for all widgets.
Definition: widget.hpp:48
std::string description
Definition: info.hpp:33
Multiplayer faction.
Definition: validation.hpp:50
virtual void set_value(unsigned value, bool fire_event=false) override
Inherited from selectable_item.
Definition: menu_button.hpp:84
void set_escape_disabled(const bool escape_disabled)
Disable the escape key.
Definition: window.hpp:328
STL namespace.
void update_all_addons(window &window)
Definition: manager.cpp:699
static void msg(const char *act, debug_info &i, const char *to="", const char *result="")
Definition: debugger.cpp:109
static const std::vector< std::pair< ADDON_STATUS_FILTER, std::string > > status_filter_types_
Definition: manager.hpp:89
std::string get_value() const
void install_selected_addon(window &window)
Definition: manager.hpp:99
bool chars_equal_insensitive(char a, char b)
Definition: general.hpp:21
Simple push button.
std::string feedback_url
Definition: info.hpp:54
bool have_addon_in_vcs_tree(const std::string &addon_name)
Returns true if the specified add-ons appear to be managed by a 'supported' VCS.
Definition: manager.cpp:66
std::deque< editor_action_ptr > action_stack
Action stack typedef.
Definitions for the interface to Wesnoth Markup Language (WML).
Define the common filters for the gui2::pane class.
time_t created
Definition: info.hpp:57
std::vector< std::string > split(const std::string &val, const char c, const int flags)
Splits a (comma-)separated string into a vector of pieces.
version_info installed_version
Definition: state.hpp:59
No tracking information available.
Definition: state.hpp:36
Add-on is not installed.
Definition: state.hpp:23
Class for a single line text area.
Definition: text_box.hpp:121
Generic file dialog.
Definition: field-fwd.hpp:22
std::vector< std::string > available_addons()
Returns a list of local add-ons that can be published.
Definition: manager.cpp:132
void execute_action_on_selected_addon(window &window)
Definition: manager.cpp:635
#define b
const std::vector< std::string > filtertext_
Definition: manager.cpp:132
virtual void set_label(const t_string &label)
bool exists(const image::locator &i_locator)
returns true if the given image actually exists, without loading it.
Definition: image.cpp:1129
Desktop environment interaction functions.
virtual unsigned get_value() const override
Inherited from selectable_item.
Definition: menu_button.hpp:81
const std::string & get_last_server_error() const
Returns the last error message sent by the server, or an empty string.
Definition: client.hpp:66
void set_delete_function(addon_op_func_t function)
Sets the function to install an addon from the addons server.
Definition: addon_list.hpp:100
static UNUSEDNOWARN std::string _(const char *str)
Definition: gettext.hpp:89
void on_addon_select(window &window)
Definition: manager.cpp:853
Version in the server is older than local installation.
Definition: state.hpp:29
void connect_signal_notify_modified(dispatcher &dispatcher, const signal_notification_function &signal)
Connects a signal handler for getting a notification upon modification.
Definition: dispatcher.cpp:233
void reload_list_and_reselect_item(const std::string id, window &window)
Definition: manager.cpp:518
void uninstall_addon(const addon_info &addon, window &window)
Definition: manager.cpp:668
bool is_installed_addon_status(ADDON_STATUS s)
Definition: state.hpp:39
This file contains the settings handling of the widget library.
void set_values(const std::vector<::config > &values, int selected=0)
ADDON_STATUS_FILTER
Add-on installation status filters for the user interface.
Definition: state.hpp:83
int current_layer() const
Gets the current visible layer number.
std::string display_title() const
Get a title or automatic title for display.
Definition: info.cpp:125
void set_visible(const visibility visible)
Definition: widget.cpp:479
void connect_signal_mouse_left_click(dispatcher &dispatcher, const signal_function &signal)
Connects a signal handler for a left mouse button click.
Definition: dispatcher.cpp:218
void set_callback_order_change(std::function< void(unsigned, listbox::SORT_ORDER)> callback)
Sets up a callback that will be called when the player changes the sorting order. ...
Definition: addon_list.hpp:135
std::string id
Definition: info.hpp:31
Version in the server is newer than local installation.
Definition: state.hpp:27
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:69
boost::dynamic_bitset get_status_filter_visibility(const window &window) const
Definition: manager.cpp:551
void toggle_details(button &btn, stacked_widget &stk)
Definition: manager.cpp:447
Shows a yes and no button.
Definition: message.hpp:79
void refresh_addon_version_info_cache()
Refreshes the per-session cache of add-on's version information structs.
Definition: manager.cpp:341
void set_addons(const addons_list &addons)
Sets the add-ons to show.
Definition: addon_list.cpp:147
bool remove_local_addon(const std::string &addon)
Definition: manager.cpp:118
ADDON_TYPE type
Definition: info.hpp:45
Dependencies not satisfied.
Definition: state.hpp:34
void copy_url_callback(text_box &url_box)
Definition: manager.cpp:831
Add-ons (campaignd) client class.
Definition: client.hpp:29
Various uncategorised dialogs.
int get_retval()
Definition: window.hpp:399
config get_addon_pbl_info(const std::string &addon_name)
Gets the publish information for an add-on.
Definition: manager.cpp:80
void delete_addon(const addon_info &addon, window &window)
Performs all backend and UI actions for taking down the specified add-on.
Definition: manager.cpp:765
std::string str() const
Serializes the version number into string form.
Definition: version.cpp:93
Miscellaneous content/media (unit packs, terrain packs, music packs, etc.).
Definition: validation.hpp:54
boost::iterator_range< const_child_iterator > const_child_itors
Definition: config.hpp:239
std::string display_icon() const
Get an icon path fixed for display (e.g.
Definition: info.cpp:134
boost::dynamic_bitset get_type_filter_visibility(const window &window) const
Definition: manager.cpp:572
static std::string describe_status_verbose(const addon_tracking_info &state)
Definition: manager.cpp:253
bool local_only
Definition: info.hpp:66
bool open_object(const std::string &path_or_url)
Opens the specified object with the default application configured for its type.
Definition: open.cpp:53
std::vector< std::string > locales
Definition: info.hpp:47
Multiplayer scenario.
Definition: validation.hpp:47
void on_order_changed(window &window, unsigned int sort_column, listbox::SORT_ORDER order)
Definition: manager.cpp:622
Multiplayer era.
Definition: validation.hpp:49
bool have_addon_pbl_info(const std::string &addon_name)
Returns true if there's a local .pbl file stored for the specified add-on.
Definition: manager.cpp:75
void install_addon(const addon_info &addon, window &window)
Definition: manager.cpp:656
void browse_url_callback(text_box &url_box)
Definition: manager.cpp:825
std::string display_type() const
Get an add-on type identifier for display in the user's language.
Definition: info.cpp:151
addons_client & client_
Definition: manager.hpp:83
The user set the widget invisible, that means:
const_attr_itors attribute_range() const
Definition: config.cpp:755
window * get_window()
Get the parent window.
Definition: widget.cpp:114
static map_location::DIRECTION s
void uninstall_selected_addon(window &window)
Definition: manager.hpp:105
void execute_default_action(const addon_info &addon, window &window)
Called when the player double-clicks an add-on.
Definition: manager.cpp:791
std::string size_display_string(double size)
Get a human-readable representation of the specified byte count.
Definition: info.cpp:216
addon_manager(addons_client &client)
Definition: manager.cpp:238
void set_publish_function(addon_op_func_t function)
Sets the function to upload an addon to the addons server.
Definition: addon_list.hpp:94
void fetch_addons_list(window &window)
Definition: manager.cpp:458
language_list get_languages()
Definition: language.cpp:114
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:88
void delete_selected_addon(window &window)
Definition: manager.hpp:123
Multiplayer plain (no WML) map pack.
Definition: validation.hpp:48
void apply_filters(window &window)
Definition: manager.cpp:598
#define N_(String)
Definition: gettext.hpp:97
No version in the server.
Definition: state.hpp:31
Base class for all visible items.
#define VGETTEXT(msgid,...)
install_outcome outcome
Definition: client.hpp:36
void select_layer(const int layer)
Selects and displays a particular layer.
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
Represents version numbers.
Definition: version.hpp:43
config & add_child(config_key_type key)
Definition: config.cpp:475
std::string make_addon_title(const std::string &id)
Replaces underscores to dress up file or dirnames as add-on titles.
Definition: info.cpp:225
std::string vgettext(const char *msgid, const utils::string_map &symbols)
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:82
bool open_object_is_supported()
Returns whether open_object() is supported/implemented for the current platform.
Definition: open.cpp:44
void copy_to_clipboard(const std::string &text, const bool)
Copies text to the clipboard.
Definition: clipboard.cpp:35
static const std::vector< std::pair< ADDON_TYPE, std::string > > type_filter_types_
Definition: manager.hpp:90
std::vector< std::string > depends
Definition: info.hpp:51
#define f
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:24
Hybrid campaign.
Definition: validation.hpp:45
static std::string format_addon_time(time_t time)
Definition: manager.cpp:836
void order_addons(window &window)
Definition: manager.cpp:607
std::map< std::string, addon_tracking_info > addons_tracking_list
Definition: state.hpp:63
const std::string unicode_em_dash
Definition: constants.cpp:39
std::string title
Definition: info.hpp:32
bool upload_addon(const std::string &id, std::string &response_message, config &cfg)
Requests the specified add-on to be uploaded.
Definition: client.cpp:112
std::string put_time(struct tm *time, const char *fmt)
Definition: io.hpp:27
Simple push button.
Definition: button.hpp:35
void publish_selected_addon(window &window)
Definition: manager.hpp:117
void on_filtertext_changed(text_box_base *textbox)
Definition: manager.cpp:248
version_info version
Definition: info.hpp:37
void show_error_message(const std::string &msg, bool message_use_markup)
Shows an error message to the user.
Definition: message.cpp:205
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:76
void set_values(const std::vector<::config > &values)
Set the available menu options.
Stores additional status information about add-ons.
Definition: state.hpp:45
int downloads
Definition: info.hpp:42
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:93
void update_selected_addon(window &window)
Definition: manager.hpp:111
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:196
bool request_distribution_terms(std::string &terms)
Request the add-ons server distribution terms message.
Definition: client.cpp:96
void update_addon(const addon_info &addon, window &window)
Definition: manager.cpp:688
std::map< std::string, addon_info > addons_list
Definition: info.hpp:26
base class of top level items, the only item which needs to store the final canvases to draw on ...
Definition: window.hpp:62
install_result install_addon_with_checks(const addons_list &addons, const addon_info &addon)
Do a 'smart' fetch of an add-on, checking to avoid overwrites for devs and resolving dependencies...
Definition: client.cpp:466
Single-player campaign.
Definition: validation.hpp:43
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:183
bool use_twelve_hour_clock_format()
Definition: general.cpp:1044
static const int DEFAULT_ACTION_RETVAL
Special retval for the toggle panels in the addons list.
Definition: addon_list.hpp:46
virtual void pre_show(window &window) override
Inherited from modal_dialog.
Definition: manager.cpp:308
bool request_addons_list(config &cfg)
Request the add-ons list from the server.
Definition: client.cpp:79
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:52
Multiplayer campaign.
Definition: validation.hpp:46
#define GETTEXT_DOMAIN
Definition: manager.cpp:15
std::string author
Definition: info.hpp:39
static const std::vector< addon_order > all_orders_
Definition: manager.hpp:91
virtual void set_active(const bool active) override
See styled_widget::set_active.
Version in the server matches local installation.
Definition: state.hpp:25
void publish_addon(const addon_info &addon, window &window)
Performs all backend and UI actions for publishing the specified add-on.
Definition: manager.cpp:716
time_t updated
Definition: info.hpp:56