The Battle for Wesnoth  1.13.4+dev
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Modules Pages
manager_ui.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2003 - 2008 by David White <[email protected]>
3  2008 - 2015 by Ignacio Riquelme Morelle <[email protected]>
4  Part of the Battle for Wesnoth Project http://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 #include "addon/manager_ui.hpp"
17 
18 #include "addon/info.hpp"
19 #include "addon/manager.hpp"
20 #include "addon/state.hpp"
21 #include "dialogs.hpp"
22 #include "filesystem.hpp"
23 #include "formatter.hpp"
24 #include "formula/string_utils.hpp"
25 #include "game_preferences.hpp"
26 #include "gettext.hpp"
32 #include "gui/dialogs/message.hpp"
35 #include "gui/widgets/window.hpp"
36 #include "gui/widgets/settings.hpp"
37 #include "help/help_button.hpp"
38 #include "image.hpp"
39 #include "log.hpp"
40 #include "marked-up_text.hpp"
41 #include "wml_separators.hpp"
42 #include "wml_exception.hpp"
43 
44 #include <boost/scoped_ptr.hpp>
45 
46 #include "addon/client.hpp"
47 
48 static lg::log_domain log_config("config");
49 static lg::log_domain log_network("network");
50 static lg::log_domain log_filesystem("filesystem");
51 static lg::log_domain log_addons_client("addons-client");
52 
53 #define ERR_CFG LOG_STREAM(err, log_config)
54 
55 #define ERR_NET LOG_STREAM(err, log_network)
56 
57 #define ERR_FS LOG_STREAM(err, log_filesystem)
58 
59 #define ERR_AC LOG_STREAM(err , log_addons_client)
60 #define WRN_AC LOG_STREAM(warn, log_addons_client)
61 #define LOG_AC LOG_STREAM(info, log_addons_client)
62 #define DBG_AC LOG_STREAM(debug, log_addons_client)
63 
64 namespace {
65 
66 inline const addon_info& addon_at(const std::string& id, const addons_list& addons)
67 {
68  addons_list::const_iterator it = addons.find(id);
69  assert(it != addons.end());
70  return it->second;
71 }
72 
73 bool get_addons_list(addons_client& client, addons_list& list)
74 {
75  list.clear();
76 
77  config cfg;
78  client.request_addons_list(cfg);
79 
80  if(!cfg) {
81  return false;
82  }
83 
84  read_addons_list(cfg, list);
85 
86  return true;
87 }
88 
89 const std::string color_upgradable = font::color2markup(font::YELLOW_COLOR);
90 const std::string color_outdated = "<255,127,0>";
91 
92 std::string describe_addon_status(const addon_tracking_info& info)
93 {
94  switch(info.state) {
95  case ADDON_NONE:
96  return info.can_publish ? _("addon_state^Published, not installed") : _("addon_state^Not installed");
97  case ADDON_INSTALLED:
98  case ADDON_NOT_TRACKED:
99  // Consider add-ons without version information as installed
100  // for the main display. Their Description info should elaborate
101  // on their status.
102  return font::GOOD_TEXT + std::string(
103  info.can_publish ? _("addon_state^Published") : _("addon_state^Installed"));
105  return color_upgradable + std::string(
106  info.can_publish ? _("addon_state^Published, upgradable") : _("addon_state^Installed, upgradable"));
108  return color_outdated + std::string(
109  info.can_publish ? _("addon_state^Published, outdated on server") : _("addon_state^Installed, outdated on server"));
111  return font::BAD_TEXT + std::string(
112  info.can_publish ? _("addon_state^Published, broken") : _("addon_state^Installed, broken"));
113  default:
114  return font::color2markup(font::GRAY_COLOR) + _("addon_state^Unknown");
115  }
116 }
117 
118 // Asks the client to download and install an addon, reporting errors in a gui dialog. Returns true if new content was installed, false otherwise.
119 bool try_fetch_addon(CVideo & v, addons_client & client, const addon_info & addon)
120 {
121  config archive;
122 
123  if(!(
124  client.download_addon(archive, addon.id, addon.title, !is_addon_installed(addon.id)) &&
125  client.install_addon(archive, addon)
126  )) {
127  const std::string& server_error = client.get_last_server_error();
128  if(!server_error.empty()) {
130  std::string(_("The server responded with an error:")) + "\n" + server_error);
131  }
132  return false;
133  } else {
134  return true;
135  }
136 }
137 
138 enum OUTCOME { SUCCESS, FAILURE, ABORT };
139 
140 // A structure which summarizes the outcome of one or more add-on install operations.
141 struct addon_op_result
142 {
143  OUTCOME outcome;
144  bool wml_changed;
145 };
146 
147 /** Warns the user about unresolved dependencies and installs them if they choose to do so.
148  * Returns: outcome: ABORT in case the user chose to abort because of an issue
149  * SUCCESS otherwise
150  * wml_change: indicates if new wml content was installed
151  */
152 addon_op_result do_resolve_addon_dependencies(CVideo& v, addons_client& client, const addons_list& addons, const addon_info& addon)
153 {
154  addon_op_result result;
155  result.outcome = SUCCESS;
156  result.wml_changed = false;
157 
158  boost::scoped_ptr<cursor::setter> cursor_setter(new cursor::setter(cursor::WAIT));
159 
160  // TODO: We don't currently check for the need to upgrade. I'll probably
161  // work on that when implementing dependency tiers later.
162 
163  const std::set<std::string>& deps = addon.resolve_dependencies(addons);
164 
165  std::vector<std::string> missing_deps;
166  std::vector<std::string> broken_deps;
167 
168  for(const std::string& dep : deps) {
169  if(!is_addon_installed(dep)) {
170  if(addons.find(dep) != addons.end()) {
171  missing_deps.push_back(dep);
172  } else {
173  broken_deps.push_back(dep);
174  }
175  }
176  }
177 
178  cursor_setter.reset();
179 
180  if(!broken_deps.empty()) {
181  std::string broken_deps_report;
182 
183  broken_deps_report = _n(
184  "The selected add-on has the following dependency, which is not currently installed or available from the server. Do you wish to continue?",
185  "The selected add-on has the following dependencies, which are not currently installed or available from the server. Do you wish to continue?",
186  broken_deps.size());
187  broken_deps_report += "\n";
188 
189  for(const std::string& broken_dep_id : broken_deps) {
190  broken_deps_report += "\n " + utils::unicode_bullet + " " + make_addon_title(broken_dep_id);
191  }
192 
193  if(gui2::show_message(v, _("Broken Dependencies"), broken_deps_report, gui2::tmessage::yes_no_buttons) != gui2::twindow::OK) {
194  result.outcome = ABORT;
195  return result; // canceled by user
196  }
197  }
198 
199  if(missing_deps.empty()) {
200  // No dependencies to install, carry on.
201  return result;
202  }
203 
204  //
205  // Prepare UI report on available dependencies.
206  //
207 
208  const std::string sep(1, COLUMN_SEPARATOR);
209  const std::string& header = (formatter() << HEADING_PREFIX << sep <<
210  _("Name") << sep << _("Version") << sep << _("Author") << sep <<
211  _("Size") << sep << _("Type")).str();
212 
213  std::vector<std::string> options(1, header);
214  std::vector<int> sort_sizes;
215 
216  cursor_setter.reset(new cursor::setter(cursor::WAIT));
217 
218  for(const std::string& dep : missing_deps) {
219  const addon_info& addon = addon_at(dep, addons);
220 
221  const std::string& display_size = size_display_string(addon.size);
222  const std::string& display_type = addon.display_type();
223  const std::string& display_icon = addon.display_icon();
224  const std::string& display_version = addon.version.str();
225 
226  const std::string& display_title = font::word_wrap_text(addon.display_title(), font::SIZE_NORMAL, 150, -1, 2);
227  std::string display_author = addon.author;
228  utils::ellipsis_truncate(display_author, 14);
229 
230  // Add negative sizes to reverse the sort order.
231  sort_sizes.push_back(-addon.size);
232 
233  // NOTE: NULL_MARKUP used to escape abuse of formatting chars in add-on titles
234  options.push_back(IMAGE_PREFIX + display_icon + sep +
235  font::NULL_MARKUP + display_title + sep + display_version + sep +
236  display_author + sep + display_size + sep +
237  display_type);
238  }
239 
240  /* do */ {
241  gui::dialog dlg(v, _("Install Dependencies"),
242  _n("The selected add-on has the following dependency, which is not currently installed. Do you wish to install it before continuing?",
243  "The selected add-on has the following dependencies, which are not currently installed. Do you wish to install them before continuing?",
244  missing_deps.size()),
245  gui::YES_NO);
247  addon_style.scale_images(font::relative_size(72), font::relative_size(72));
248  gui::menu* addon_menu = new gui::menu(
249  v, options, false, -1,
250  gui::dialog::max_menu_width, nullptr, &addon_style, false);
251  dlg.set_menu(addon_menu);
252 
253  cursor_setter.reset();
254 
255  if(dlg.show() < 0) {
256  return result; // the user has chosen to continue without installing anything.
257  }
258  }
259 
260  //
261  // Install dependencies now.
262  //
263 
264  std::vector<std::string> failed_titles;
265 
266  for(const std::string& dep : missing_deps) {
267  const addon_info& addon = addon_at(dep, addons);
268 
269  if(!try_fetch_addon(v, client, addon)) {
270  failed_titles.push_back(addon.title);
271  } else {
272  result.wml_changed = true;
273  }
274  }
275 
276  if(!failed_titles.empty()) {
277  const std::string& failed_deps_report = _n(
278  "The following dependency could not be installed. Do you still wish to continue?",
279  "The following dependencies could not be installed. Do you still wish to continue?",
280  failed_titles.size()) + std::string("\n\n") + utils::bullet_list(failed_titles);
281 
282  result.outcome = gui2::show_message(v, _("Dependencies Installation Failed"), failed_deps_report, gui2::tmessage::yes_no_buttons) == gui2::twindow::OK ? SUCCESS : ABORT; // If the user cancels, return ABORT. Otherwise, return SUCCESS, since the user chose to ignore the failure.
283  return result;
284  }
285 
286  return result;
287 }
288 
289 /** Checks whether the given add-on has local .pbl or VCS information and asks before overwriting it. */
290 bool do_check_before_overwriting_addon(CVideo& video, const addon_info& addon)
291 {
292  const std::string& addon_id = addon.id;
293 
294  const bool pbl = have_addon_pbl_info(addon_id);
295  const bool vcs = have_addon_in_vcs_tree(addon_id);
296 
297  if(!pbl && !vcs) {
298  return true;
299  }
300 
301  utils::string_map symbols;
302  symbols["addon"] = addon.title;
303  std::string text;
304  std::vector<std::string> extra_items;
305 
306  text = vgettext("The add-on '$addon|' is already installed and contains additional information that will be permanently lost if you continue:", symbols);
307  text += "\n\n";
308 
309  if(pbl) {
310  extra_items.push_back(_("Publishing information file (.pbl)"));
311  }
312 
313  if(vcs) {
314  extra_items.push_back(_("Version control system (VCS) information"));
315  }
316 
317  text += utils::bullet_list(extra_items) + "\n\n";
318  text += _("Do you really wish to continue?");
319 
320  return gui2::show_message(video, _("Confirm"), text, gui2::tmessage::yes_no_buttons) == gui2::twindow::OK;
321 }
322 
323 /** Do a 'smart' fetch of an add-on, checking to avoid overwrites for devs and resolving dependencies, using gui interaction to handle issues that arise
324  * Returns: outcome: ABORT in case the user chose to abort because of an issue
325  * FAILURE in case we resolved checks and dependencies, but fetching this particular add-on failed
326  * SUCCESS otherwise
327  * wml_changed: indicates if new wml content was installed at any point
328  */
329 addon_op_result try_fetch_addon_with_checks(CVideo & v, addons_client& client, const addons_list& addons, const addon_info& addon)
330 {
331  if(!(do_check_before_overwriting_addon(v, addon))) {
332  // Just do nothing and leave.
333  addon_op_result result;
334  result.outcome = ABORT;
335  result.wml_changed = false;
336 
337  return result;
338  }
339 
340  // Resolve any dependencies
341  addon_op_result res = do_resolve_addon_dependencies(v, client, addons, addon);
342  if (res.outcome != SUCCESS) { // this function only returns SUCCESS and ABORT as outcomes
343  return res; // user aborted
344  }
345 
346  if(!try_fetch_addon(v, client, addon)) {
347  res.outcome = FAILURE;
348  return res; //wml_changed should have whatever value was obtained in resolving dependencies
349  } else {
350  res.wml_changed = true;
351  return res; //we successfully installed something, so now the wml was definitely changed
352  }
353 }
354 
355 /** Performs all backend and UI actions for taking down the specified add-on. */
356 void do_remote_addon_delete(CVideo& video, addons_client& client, const std::string& addon_id)
357 {
358  utils::string_map symbols;
359  symbols["addon"] = make_addon_title(addon_id); // FIXME: need the real title!
360  const std::string& text = vgettext("Deleting '$addon|' will permanently erase its download and upload counts on the add-ons server. Do you really wish to continue?", symbols);
361 
362  const int res = gui2::show_message(
363  video, _("Confirm"), text, gui2::tmessage::yes_no_buttons);
364 
365  if(res != gui2::twindow::OK) {
366  return;
367  }
368 
369  std::string server_msg;
370  if(!client.delete_remote_addon(addon_id, server_msg)) {
372  std::string(_("The server responded with an error:")) + "\n" +
373  client.get_last_server_error());
374  } else {
375  // FIXME: translation needed!
376  gui2::show_transient_message(video, _("Response"), server_msg);
377  }
378 }
379 
380 /** Performs all backend and UI actions for publishing the specified add-on. */
381 void do_remote_addon_publish(CVideo& video, addons_client& client, const std::string& addon_id, const version_info& remote_version)
382 {
383  std::string server_msg;
384 
385  config cfg;
386  get_addon_pbl_info(addon_id, cfg);
387 
388  const version_info& version_to_publish = cfg["version"].str();
389 
390  if(version_to_publish <= remote_version) {
391  const int res = gui2::show_message(video, _("Warning"),
392  _("The remote version of this add-on is greater or equal to the version being uploaded. Do you really wish to continue?"),
394 
395  if(res != gui2::twindow::OK) {
396  return;
397  }
398  }
399 
400  if(!image::exists(cfg["icon"].str())) {
401  gui2::show_error_message(video, _("Invalid icon path. Make sure the path points to a valid image."));
402  } else if(!client.request_distribution_terms(server_msg)) {
404  std::string(_("The server responded with an error:")) + "\n" +
405  client.get_last_server_error());
406  } else if(gui2::show_message(video, _("Terms"), server_msg, gui2::tmessage::ok_cancel_buttons) == gui2::twindow::OK) {
407  if(!client.upload_addon(addon_id, server_msg, cfg)) {
409  std::string(_("The server responded with an error:")) + "\n" +
410  client.get_last_server_error());
411  } else {
412  gui2::show_transient_message(video, _("Response"), server_msg);
413  }
414  }
415 }
416 
417 /** GUI1 support class handling the button used to display add-on descriptions. */
418 class description_display_action : public gui::dialog_button_action
419 {
420  CVideo& v_;
421  std::vector<std::string> display_ids_;
422  addons_list addons_;
423  addons_tracking_list tracking_;
425 
426 public:
427  description_display_action(CVideo& v, const std::vector<std::string>& display_ids, const addons_list& addons, const addons_tracking_list& tracking, gui::filter_textbox* filter)
428  : v_(v) , display_ids_(display_ids), addons_(addons), tracking_(tracking), filter_(filter)
429  {}
430 
431  virtual gui::dialog_button_action::RESULT button_pressed(int filter_choice)
432  {
433  assert(filter_ != nullptr);
434 
435  const int menu_selection = filter_->get_index(filter_choice);
436  if(menu_selection < 0) { return gui::CONTINUE_DIALOG; }
437 
438  const size_t choice = static_cast<size_t>(menu_selection);
439  if(choice < display_ids_.size()) {
440  const std::string& id = display_ids_[choice];
441  assert(tracking_.find(id) != tracking_.end());
442  gui2::taddon_description::display(id, addons_, tracking_, v_);
443  }
444 
445  return gui::CONTINUE_DIALOG;
446  }
447 };
448 
449 /** Struct type for storing filter options. */
450 struct addons_filter_state
451 {
452  std::string keywords;
453  std::vector<bool> types;
454  ADDON_STATUS_FILTER status;
455  // Yes, the sorting criterion and direction are part of the
456  // filter options since changing them requires rebuilding the
457  // dialog list contents.
459  ADDON_SORT_DIRECTION direction;
460  bool changed;
461 
462  addons_filter_state()
463  : keywords()
464  , types(ADDON_TYPES_COUNT, true)
465  , status(FILTER_ALL)
466  , sort(SORT_NAMES)
467  , direction(DIRECTION_ASCENDING)
468  , changed(false)
469  {}
470 };
471 
472 /** GUI1 support class handling the filter options button. */
473 class filter_options_action : public gui::dialog_button_action
474 {
475  CVideo& video_;
476  addons_filter_state& f_;
477 
478 public:
479  filter_options_action(CVideo& video, addons_filter_state& filter)
480  : video_(video)
481  , f_(filter)
482  {}
483 
485  {
487 
488  dlg.set_displayed_status(f_.status);
489  dlg.set_displayed_types(f_.types);
490  dlg.set_sort(f_.sort);
491  dlg.set_direction(f_.direction);
492 
493  dlg.show(video_);
494 
495  const std::vector<bool> new_types = dlg.displayed_types();
496  const ADDON_STATUS_FILTER new_status = dlg.displayed_status();
497  const ADDON_SORT new_sort = dlg.sort();
498  const ADDON_SORT_DIRECTION new_direction = dlg.direction();
499 
500  assert(f_.types.size() == new_types.size());
501 
502  if(std::equal(f_.types.begin(), f_.types.end(), new_types.begin()) && f_.status == new_status &&
503  f_.sort == new_sort && f_.direction == new_direction) {
504  // Close the manager dialog only if the filter options changed.
505  return gui::CONTINUE_DIALOG;
506  }
507 
508  f_.types = new_types;
509  f_.status = new_status;
510  f_.sort = new_sort;
511  f_.direction = new_direction;
512  f_.changed = true;
513 
514  return gui::CLOSE_DIALOG;
515  }
516 };
517 
518 /**
519  * Comparator type used for sorting the add-ons list according to the user's preferences.
520  */
521 struct addon_pointer_list_sorter
522 {
523  addon_pointer_list_sorter(ADDON_SORT sort, ADDON_SORT_DIRECTION direction)
524  : sort_(sort), dir_(direction)
525  {}
526 
527  inline bool operator()(const addons_list::value_type* a, const addons_list::value_type* b) {
528  assert(a != nullptr && b != nullptr);
529 
530  if(dir_ == DIRECTION_DESCENDING) {
531  const addons_list::value_type* c = a;
532  a = b;
533  b = c;
534  }
535 
536  switch(sort_) {
537  case SORT_NAMES:
538  // Alphanumerical by name, case insensitive.
539  return utf8::lowercase(a->second.title) < utf8::lowercase(b->second.title);
540  case SORT_UPDATED:
541  // Numerical by last upload TS.
542  return a->second.updated < b->second.updated;
543  case SORT_CREATED:
544  default:
545  // Numerical by first upload TS (or the equivalent campaignd WML order).
546  return a->second.order < b->second.order;
547  }
548  }
549 
550 private:
551  ADDON_SORT sort_;
553 };
554 
555 /** Shorthand type for the sorted add-ons list. */
556 typedef std::vector<const addons_list::value_type*> sorted_addon_pointer_list;
557 
558 /**
559  * Sorts the user-visible add-ons list according to the user's preferences.
560  *
561  * The internal add-ons list is actually implemented employing an associative
562  * container to map individual list entries to add-on ids for faster look-ups.
563  * The visible form of the list may actually include more elements than just
564  * the contents of the add-ons server; more specifically, it may include
565  * Publish and Delete entries for local add-ons with .pbl files.
566  *
567  * The GUI1 list/menu class does not support horizontal scrolling, which
568  * results in a very limited set of information columns that can be displayed
569  * safely without running out of space and causing content to be omitted, and
570  * clicking on any column header to change the sort also affects the
571  * Publish/Delete entries by necessity. These two factors combined make it
572  * inconvenient at this time to just use the GUI1 widget's interface to make
573  * it default to a specific sorting criterion.
574  *
575  * Thus, we need a "neutral" or "fallback" sorting step before feeding the
576  * add-ons list's data to the widget and appending Publish/Delete options to
577  * it. Since this is definitely not the most evident UI concept in use in this
578  * dialog, it is hidden behind the Options dialog and has sensible defaults
579  * intended to optimize the add-ons experience; alphanumerical sorting feels
580  * natural and breaks any illusion of quality rating or any such that could
581  * result from a list default-sorted by first-upload order as done in all
582  * versions prior to 1.11.0.
583  *
584  * This function takes care of sorting the list with minimal memory footprint
585  * by passing around a set of pointers to items of the source list in
586  * @a addons for use in the dialog building code.
587  *
588  * @param addons The source add-ons list.
589  * @param sort Sorting criterion.
590  * @param direction Sorting order (ascending/descending).
591  *
592  * @return A vector containing pointers to items from @a addons sorted
593  * accordingly. Iterators to items from @a addons <b>must</b> remain
594  * valid for the whole lifespan of this vector.
595  */
596 sorted_addon_pointer_list sort_addons_list(addons_list& addons, ADDON_SORT sort, ADDON_SORT_DIRECTION direction)
597 {
598  sorted_addon_pointer_list res;
599  addon_pointer_list_sorter sorter(sort, direction);
600 
601  for(const addons_list::value_type& entry : addons) {
602  res.push_back(&entry);
603  }
604 
605  std::stable_sort(res.begin(), res.end(), sorter);
606 
607  return res;
608 }
609 
610 void show_addons_manager_dialog(CVideo& v, addons_client& client, addons_list& addons, std::string& last_addon_id, bool& stay_in_ui, bool& wml_changed, addons_filter_state& filter)
611 {
612  boost::scoped_ptr<cursor::setter> cursor_setter(new cursor::setter(cursor::WAIT));
613 
614  stay_in_ui = false;
615  filter.changed = false;
616 
617  const ADDON_STATUS_FILTER prev_view = filter.status;
618  assert(prev_view < FILTER_COUNT);
619 
620  const bool updates_only =
621  filter.status == FILTER_UPGRADABLE;
622 
623  const bool show_publish_delete = !updates_only;
624 
625  // Currently installed add-ons, which we'll need to check when updating.
626  // const std::vector<std::string>& installed_addons_list = installed_addons();
627 
628  // Add-ons available for publishing in the remote
629  // (i.e. we have .pbl files for them).
630  const std::vector<std::string>& can_publish_ids = available_addons();
631 
632  // Add-ons available for deleting in the remote
633  // (i.e. already published, and we have .pbl files for them).
634  std::vector<std::string> can_delete_ids;
635 
636  // Status tracking information about add-ons.
637  addons_tracking_list tracking;
638 
639  // UI markup.
640  const std::string sep(1, COLUMN_SEPARATOR);
641 
642  // List and list filter control contents.
643  std::vector<std::string> options, filter_options;
644  std::string header;
645 
646  // The add-on ids actually available for the user to pick from in the UI.
647  std::vector<std::string> option_ids;
648 
649  // UI sorting detail.
650  std::vector<int> sort_sizes;
651 
652  header = HEADING_PREFIX + sep + _("Name") + sep;
653  if(updates_only) {
654  header += _("Old Version") + sep + _("New Version") + sep;
655  } else {
656  header += _("Version") + sep;
657  }
658  header += _("Author") + sep + _("Size");
659  // The Type and Downloads columns aren't displayed when updating because of
660  // display space constraints. Presumably, the user doesn't care about that
661  // information since the add-on is already installed.
662  //
663  // Type is also always displayed last so it can get automatically truncated
664  // if its translated contents don't fit, instead of truncating other, more
665  // important columns such as Size.
666  if(!updates_only) {
667  header += sep + _("Downloads") + sep + _("Type");
668  }
669  // end of list header
670 
671  options.push_back(header);
672  filter_options.push_back(header);
673 
674  //
675  // Prepare the add-ons list for display and get status
676  // information.
677  //
678 
679  const sorted_addon_pointer_list& sorted_addons = sort_addons_list(addons, filter.sort, filter.direction);
680 
681  bool have_upgradable_addons = false;
682 
683  for(const sorted_addon_pointer_list::value_type& sorted_entry : sorted_addons) {
684  const addons_list::value_type& entry = *sorted_entry;
685  const addon_info& addon = entry.second;
686  tracking[addon.id] = get_addon_tracking_info(addon);
687 
688  const ADDON_STATUS state = tracking[addon.id].state;
689 
690  if((filter.status == FILTER_UPGRADABLE && state != ADDON_INSTALLED_UPGRADABLE) ||
691  (filter.status == FILTER_NOT_INSTALLED && state != ADDON_NONE) ||
692  (filter.status == FILTER_INSTALLED && !is_installed_addon_status(state)) ||
693  (!filter.types[addon.type])
694  )
695  continue;
696 
697  if(state == ADDON_INSTALLED_UPGRADABLE) {
698  have_upgradable_addons = true;
699  }
700 
701  option_ids.push_back(addon.id);
702 
703  if(tracking[addon.id].can_publish) {
704  can_delete_ids.push_back(addon.id);
705  }
706 
707  const std::string& display_sep = sep;
708  const std::string& display_size = size_display_string(addon.size);
709  const std::string& display_type = addon.display_type();
710  const std::string& display_down = std::to_string(addon.downloads);
711  const std::string& display_icon = addon.display_icon();
712  const std::string& display_status = describe_addon_status(tracking[addon.id]);
713 
714  std::string display_version = addon.version.str();
715  std::string display_old_version = tracking[addon.id].installed_version;
716  std::string display_title = addon.display_title();
717  std::string display_author = addon.author;
718 
719  // Add negative sizes to reverse the sort order.
720  sort_sizes.push_back(-addon.size);
721 
723 
724  // First we enter information that's used only for filtering.
725  // This includes the description, which we cannot display
726  // as a normal list row due to space constraints.
727 
728  row = display_title + sep;
729  if(updates_only) {
730  row += display_old_version + sep;
731  }
732  row += display_version + sep + display_author + sep +
733  display_size + sep + display_down + sep +
734  display_type + sep + addon.description;
735 
736  filter_options.push_back(row);
737 
738  // Now we enter information for list row display.
739  // Three fields are truncated to accommodate for GUI1's limitations.
740 
741  utils::ellipsis_truncate(display_author, 14);
742 
743  // Word-wrap the title field to a limit of two lines.
744  display_title = font::word_wrap_text(display_title, font::SIZE_NORMAL, 150, -1, 2);
745 
746  // Versions are too important in upgrades mode, so don't
747  // truncate them then.
748  if(!updates_only) {
749  utf8::truncate_as_ucs4(display_version, 12);
750 
751  if(state == ADDON_INSTALLED_UPGRADABLE || state == ADDON_INSTALLED_OUTDATED) {
752  utf8::truncate_as_ucs4(display_old_version, 12);
753 
754  if(state == ADDON_INSTALLED_UPGRADABLE) {
755  display_version =
756  color_upgradable + display_old_version +
757  "\n" + color_upgradable + display_version;
758  } else {
759  display_version =
760  color_outdated + display_old_version +
761  "\n" + color_outdated + display_version;
762  }
763  }
764  }
765 
766  // NOTE: NULL_MARKUP used to escape abuse of formatting chars in add-on titles
767  row = IMAGE_PREFIX + display_icon + display_sep + font::NULL_MARKUP + display_title + "\n" + font::color2markup(font::TITLE_COLOR) + font::SMALL_TEXT + display_status + display_sep;
768  if(updates_only) {
769  row += display_old_version + display_sep;
770  }
771  row += display_version + display_sep + display_author + display_sep + display_size;
772  if(!updates_only) {
773  row += display_sep + display_down + display_sep + display_type;
774  }
775 
776  options.push_back(row);
777  }
778 
779  if(show_publish_delete) {
780  utils::string_map i18n_syms;
781 
782  // Enter publish and remote deletion options
783  for(const std::string& pub_id : can_publish_ids) {
784  i18n_syms["addon_title"] = make_addon_title(pub_id);
785 
786  static const std::string publish_icon = "icons/icon-game.png~BLIT(icons/icon-addon-publish.png)";
787  const std::string& text = vgettext("Publish: $addon_title", i18n_syms);
788 
789  options.push_back(IMAGE_PREFIX + publish_icon + COLUMN_SEPARATOR + font::GOOD_TEXT + text);
790  filter_options.push_back(text);
791  }
792  for(const std::string& del_id : can_delete_ids) {
793  i18n_syms["addon_title"] = make_addon_title(del_id);
794 
795  static const std::string delete_icon = "icons/icon-game.png~BLIT(icons/icon-addon-delete.png)";
796  const std::string& text = vgettext("Delete: $addon_title", i18n_syms);
797 
798  options.push_back(IMAGE_PREFIX + delete_icon + COLUMN_SEPARATOR + font::BAD_TEXT + text);
799  filter_options.push_back(text);
800  }
801  }
802 
803  // If the options vector is empty it means we don't have publish/delete
804  // entries to display, either because there are no add-ons on the server
805  // at all, or none match the selected criteria. In such cases, insert a
806  // message row informing the player of the situation.
807 
808  const bool dummy_addons_list = options.size() == 1; // The header is always there.
809 
810  int result;
811  // Magic constant assigned to the Update All button as its return value.
812  static const int update_all_value = -255;
813 
814  /* do */ {
815  //
816  // Set-up the actual GUI1 dialog and its children.
817  //
818 
819  std::string dlg_message;
820 
821  if(dummy_addons_list) {
822  dlg_message = addons.empty()
823  ? _("There are no add-ons available for download from this server.")
824  : _("There are no add-ons matching the specified criteria on this server.");
825  }
826 
827  gui::dialog dlg(v, _("Add-ons Manager"), dlg_message, gui::OK_CANCEL);
828 
831  if(!updates_only) {
832  sorter.set_position_sort(4, sort_sizes).set_numeric_sort(5).set_alpha_sort(6);
833  } else {
834  sorter.set_alpha_sort(4).set_position_sort(5, sort_sizes);
835  }
836 
838  addon_style.scale_images(font::relative_size(72), font::relative_size(72));
839 
840  gui::menu* addons_list_menu = new gui::menu(v, options, false, -1,
841  gui::dialog::max_menu_width, &sorter, &addon_style, false);
842  dlg.set_menu(addons_list_menu);
843 
844  std::string filter_label;
845  if(!dummy_addons_list) {
846  filter_label = _("Filter: ");
847  }
848 
849  gui::filter_textbox* filter_box = new gui::filter_textbox(v,
850  filter_label, options, filter_options, 1, dlg, 300);
851  filter_box->set_text(filter.keywords);
852  dlg.set_textbox(filter_box);
853 
854  description_display_action description_helper(v, option_ids, addons, tracking, filter_box);
855  gui::dialog_button* description_button = new gui::dialog_button(v,
856  _("Description"), gui::button::TYPE_PRESS, gui::CONTINUE_DIALOG, &description_helper);
857  dlg.add_button(description_button, gui::dialog::BUTTON_EXTRA);
858 
859  gui::dialog_button* update_all_button = new gui::dialog_button(v, _("Update All"),
860  gui::button::TYPE_PRESS, update_all_value);
861  update_all_button->enable(have_upgradable_addons);
862  dlg.add_button(update_all_button, gui::dialog::BUTTON_EXTRA);
863 
864  filter_options_action filter_opts_helper(v, filter);
865  gui::dialog_button* filter_opts_button = new gui::dialog_button(v,
866  _("filter^Options"), gui::button::TYPE_PRESS, gui::CONTINUE_DIALOG, &filter_opts_helper);
867  dlg.add_button(filter_opts_button, gui::dialog::BUTTON_TOP);
868 
869  help::help_button* help_button = new help::help_button(v, "installing_addons");
870  dlg.add_button(help_button, gui::dialog::BUTTON_HELP);
871 
872  // Disable some buttons when there's nothing to display.
873  if(dummy_addons_list) {
874  filter_box->hide(true);
875  description_button->enable(false);
876  update_all_button->enable(false);
877  addons_list_menu->hide(true);
878  }
879 
880  // Focus the menu on the previous selection.
881  std::vector<std::string>::iterator it = !last_addon_id.empty() ?
882  std::find(option_ids.begin(), option_ids.end(), last_addon_id) :
883  option_ids.end();
884 
885  if(it != option_ids.end()) {
886  addons_list_menu->move_selection(std::distance(option_ids.begin(), it));
887  }
888 
889  cursor_setter.reset();
890 
891  //
892  // Execute the dialog.
893  //
894  result = filter_box->get_index(dlg.show());
895 
896  filter.keywords = filter_box->text();
897  }
898 
899  const bool update_everything = result == update_all_value;
900 
901  if(result < 0 && !(update_everything || filter.changed)) {
902  // User canceled the dialog.
903  return;
904  }
905 
906  stay_in_ui = true;
907 
908  if(filter.changed) {
909  // The caller will run this function again.
910  return;
911  }
912 
913  if(show_publish_delete) {
914  if(result >= int(option_ids.size() + can_publish_ids.size())) {
915  // Handle remote deletion.
916  const std::string& id = can_delete_ids[result - int(option_ids.size() + can_publish_ids.size())];
917  do_remote_addon_delete(v, client, id);
918  return;
919  } else if(result >= int(option_ids.size())) {
920  // Handle remote publishing.
921  const std::string& id = can_publish_ids[result - int(option_ids.size())];
922  do_remote_addon_publish(v, client, id, tracking[id].remote_version);
923  return;
924  }
925  }
926 
927  std::vector<std::string> ids_to_install;
928  std::vector<std::string> failed_titles;
929 
930  if(update_everything) {
931  for(const std::string& id : option_ids) {
932  if(tracking[id].state == ADDON_INSTALLED_UPGRADABLE) {
933  ids_to_install.push_back(id);
934  }
935  }
936  } else {
937  assert(result >= 0 && size_t(result) < option_ids.size());
938  last_addon_id = option_ids[result];
939  ids_to_install.push_back(option_ids[result]);
940  }
941 
942  for(const std::string& id : ids_to_install) {
943  const addon_info& addon = addon_at(id, addons);
944 
945  addon_op_result res = try_fetch_addon_with_checks(v, client, addons, addon);
946  wml_changed |= res.wml_changed; // take note if any wml_changes occurred
947  if (res.outcome == ABORT) {
948  return; // the user aborted because of some issue encountered
949  } else if (res.outcome == FAILURE) {
950  failed_titles.push_back(addon.title); // we resolved dependencies, but fetching this particular addon failed.
951  } else { // res.outcome == SUCCESS
952  wml_changed = true;
953  }
954  }
955 
956  std::string msg_title;
957  std::string msg_text;
958 
959  // Use the Update terminology when using Update All or working with the
960  // Upgradable add-ons view.
961  const bool updating = update_everything || updates_only;
962 
963  if(ids_to_install.size() == 1 && failed_titles.empty()) {
964  utils::string_map syms;
965  syms["addon_title"] = addons[ids_to_install[0]].title;
966 
967  msg_title = !updating ? _("Add-on Installed") : _("Add-on Updated");
968  msg_text = !updating ? _("The add-on '$addon_title|' has been successfully installed.") : _("The add-on '$addon_title|' has been successfully updated.");
969 
971  msg_title, utils::interpolate_variables_into_string(msg_text, &syms));
972  } else if(failed_titles.empty()) {
973  msg_title = !updating ? _("Add-ons Installed") : _("Add-ons Updated");
974  msg_text = !updating ? _("All add-ons installed successfully.") : _("All add-ons updated successfully.");
975 
976  gui2::show_transient_message(v, msg_title, msg_text);
977  } else {
978  msg_title = !updating ? _("Installation Failed") : _("Update Failed");
979  msg_text = _n(
980  "The following add-on could not be downloaded or installed successfully:",
981  "The following add-ons could not be downloaded or installed successfully:",
982  failed_titles.size());
983 
984  gui2::show_message(v, msg_title, msg_text + std::string("\n\n") + utils::bullet_list(failed_titles), gui2::tmessage::ok_button);
985  }
986 }
987 
988 bool addons_manager_ui(CVideo& v, const std::string& remote_address)
989 {
990  bool stay_in_manager_ui = false;
991  bool need_wml_cache_refresh = false;
992  std::string last_addon_id;
993  addons_filter_state filter;
994 
995  preferences::set_campaign_server(remote_address);
996 
997  try {
998  do {
999  if(need_wml_cache_refresh) {
1000  // The version info cache has gone stale because we installed/upgraded
1001  // an add-on in the previous iteration. Normally this cache is refreshed
1002  // along with all other caches, but we don't want to do all that here.
1003  // Thus, we refresh this specific cache when required, so that add-ons
1004  // are properly reported as installed/upgraded before leaving the
1005  // manager UI.
1007  }
1008 
1009  // TODO: don't create a new client instance each time we return to the UI,
1010  // but for that we need to make sure any pending network operations are canceled
1011  // whenever addons_client throws user_exit even before it gets destroyed
1012  addons_client client(v, remote_address);
1013  client.connect();
1014 
1015  addons_list addons;
1016 
1017  if(gui2::new_widgets) {
1018  config cfg;
1019  client.request_addons_list(cfg);
1020  if(!cfg) {
1022  v
1023  , _("An error occurred while downloading the "
1024  "add-ons list from the server."));
1025  return need_wml_cache_refresh;
1026  }
1027  gui2::taddon_list dlg(cfg);
1028  dlg.show(v);
1029  return need_wml_cache_refresh;
1030  }
1031 
1032  if(!get_addons_list(client, addons)) {
1033  gui2::show_error_message(v, _("An error occurred while downloading the add-ons list from the server."));
1034  return need_wml_cache_refresh;
1035  }
1036 
1037  try {
1038  // Don't reconnect when switching between view modes.
1039  do {
1040  show_addons_manager_dialog(v, client, addons, last_addon_id, stay_in_manager_ui, need_wml_cache_refresh, filter);
1041  } while(filter.changed);
1042  } catch(const addons_client::user_exit&) {
1043  // Don't do anything; just go back to the addons manager UI
1044  // if the user cancels a download or other network operation
1045  // after fetching the add-ons list above.
1046  LOG_AC << "operation canceled by user; returning to add-ons manager\n";
1047  }
1048  } while(stay_in_manager_ui);
1049  } catch(const config::error& e) {
1050  ERR_CFG << "config::error thrown during transaction with add-on server; \""<< e.message << "\"" << std::endl;
1051  gui2::show_error_message(v, _("Network communication error."));
1052  } catch(const network_asio::error& e) {
1053  ERR_NET << "network_asio::error thrown during transaction with add-on server; \""<< e.what() << "\"" << std::endl;
1054  gui2::show_error_message(v, _("Remote host disconnected."));
1055  } catch(const filesystem::io_exception& e) {
1056  ERR_FS << "filesystem::io_exception thrown while installing an addon; \"" << e.what() << "\"" << std::endl;
1057  gui2::show_error_message(v, _("A problem occurred when trying to create the files necessary to install this add-on."));
1058  } catch(const invalid_pbl_exception& e) {
1059  ERR_CFG << "could not read .pbl file " << e.path << ": " << e.message << std::endl;
1060 
1061  utils::string_map symbols;
1062  symbols["path"] = e.path;
1063  symbols["msg"] = e.message;
1064 
1066  vgettext("A local file with add-on publishing information could not be read.\n\nFile: $path\nError message: $msg", symbols));
1067  } catch(twml_exception& e) {
1068  e.show(v);
1069  } catch(const addons_client::user_exit&) {
1070  LOG_AC << "initial connection canceled by user\n";
1071  } catch(const addons_client::invalid_server_address&) {
1072  gui2::show_error_message(v, _("The add-ons server address specified is not valid."));
1073  }
1074 
1075  return need_wml_cache_refresh;
1076 }
1077 
1078 bool uninstall_local_addons(CVideo& v)
1079 {
1080  const std::string list_lead = "\n\n";
1081 
1082  const std::vector<std::string>& addons = installed_addons();
1083 
1084  if(addons.empty()) {
1086  _("You have no add-ons installed."));
1087  return false;
1088  }
1089 
1090  std::map<std::string, std::string> addon_titles_map;
1091 
1092  for(const std::string& id : addons) {
1093  std::string title;
1094 
1095  if(have_addon_install_info(id)) {
1096  // _info.cfg may have the add-on's title starting with 1.11.7,
1097  // if the add-on was downloading using the revised _info.cfg writer.
1098  config cfg;
1099  get_addon_install_info(id, cfg);
1100 
1101  const config& info_cfg = cfg.child("info");
1102 
1103  if(info_cfg) {
1104  title = info_cfg["title"].str();
1105  }
1106  }
1107 
1108  if(title.empty()) {
1109  // Transform the id into a title as a last resort.
1110  title = make_addon_title(id);
1111  }
1112 
1113  addon_titles_map[id] = title;
1114  }
1115 
1116  int res;
1117 
1118  std::vector<std::string> remove_ids;
1119  std::set<std::string> remove_names;
1120 
1121  do {
1122  gui2::taddon_uninstall_list dlg(addon_titles_map);
1123  dlg.show(v);
1124 
1125  remove_ids = dlg.selected_addons();
1126  if(remove_ids.empty()) {
1127  return false;
1128  }
1129 
1130  remove_names.clear();
1131 
1132  for(const std::string& id : remove_ids) {
1133  remove_names.insert(addon_titles_map[id]);
1134  }
1135 
1136  const std::string confirm_message = _n(
1137  "Are you sure you want to remove the following installed add-on?",
1138  "Are you sure you want to remove the following installed add-ons?",
1139  remove_ids.size()) + list_lead + utils::bullet_list(remove_names);
1140 
1141  res = gui2::show_message(v
1142  , _("Confirm")
1143  , confirm_message
1145  } while (res != gui2::twindow::OK);
1146 
1147  std::set<std::string> failed_names, skipped_names, succeeded_names;
1148 
1149  for(const std::string& id : remove_ids) {
1150  const std::string& name = addon_titles_map[id];
1151 
1153  skipped_names.insert(name);
1154  } else if(remove_local_addon(id)) {
1155  succeeded_names.insert(name);
1156  } else {
1157  failed_names.insert(name);
1158  }
1159  }
1160 
1161  if(!skipped_names.empty()) {
1162  const std::string dlg_msg = _n(
1163  "The following add-on appears to have publishing or version control information stored locally, and will not be removed:",
1164  "The following add-ons appear to have publishing or version control information stored locally, and will not be removed:",
1165  skipped_names.size());
1166 
1168  v, dlg_msg + list_lead + utils::bullet_list(skipped_names));
1169  }
1170 
1171  if(!failed_names.empty()) {
1173  "The following add-on could not be deleted properly:",
1174  "The following add-ons could not be deleted properly:",
1175  failed_names.size()) + list_lead + utils::bullet_list(failed_names));
1176  }
1177 
1178  if(!succeeded_names.empty()) {
1179  const std::string dlg_title =
1180  _n("Add-on Deleted", "Add-ons Deleted", succeeded_names.size());
1181  const std::string dlg_msg = _n(
1182  "The following add-on was successfully deleted:",
1183  "The following add-ons were successfully deleted:",
1184  succeeded_names.size());
1185 
1187  v, dlg_title,
1188  dlg_msg + list_lead + utils::bullet_list(succeeded_names));
1189 
1190  return true;
1191  }
1192 
1193  return false;
1194 }
1195 
1196 } // end anonymous namespace
1197 
1199 {
1200  static const int addon_download = 0;
1201  // NOTE: the following two values are also known by WML, so don't change them.
1202  static const int addon_uninstall = 2;
1203 
1205  const bool have_addons = !installed_addons().empty();
1206 
1207  gui2::taddon_connect addon_dlg(host_name, have_addons);
1208  addon_dlg.show(v);
1209  int res = addon_dlg.get_retval();
1210 
1211  if(res == gui2::twindow::OK) {
1212  res = addon_download;
1213  }
1214 
1215  switch(res) {
1216  case addon_download:
1217  return addons_manager_ui(v, host_name);
1218  case addon_uninstall:
1219  return uninstall_local_addons(v);
1220  default:
1221  return false;
1222  }
1223 }
1224 
1225 bool ad_hoc_addon_fetch_session(CVideo& v, const std::vector<std::string>& addon_ids)
1226 {
1227  std::string remote_address = preferences::campaign_server();
1228 
1229  // These exception handlers copied from addon_manager_ui fcn above.
1230  try {
1231 
1232  addons_client client(v, remote_address);
1233  client.connect();
1234 
1235  addons_list addons;
1236 
1237  if(!get_addons_list(client, addons)) {
1238  gui2::show_error_message(v, _("An error occurred while downloading the add-ons list from the server."));
1239  return false;
1240  }
1241 
1242  bool return_value = true;
1243  for(const std::string & addon_id : addon_ids) {
1244  addons_list::const_iterator it = addons.find(addon_id);
1245  if(it != addons.end()) {
1246  const addon_info& addon = it->second;
1247  addon_op_result res = try_fetch_addon_with_checks(v, client, addons, addon);
1248  return_value = return_value && (res.outcome == SUCCESS);
1249  } else {
1250  utils::string_map symbols;
1251  symbols["addon_id"] = addon_id;
1252  gui2::show_error_message(v, vgettext("Could not find an add-on matching id $addon_id on the add-on server.", symbols));
1253  return_value = false;
1254  }
1255  }
1256 
1257  return return_value;
1258 
1259  } catch(const config::error& e) {
1260  ERR_CFG << "config::error thrown during transaction with add-on server; \""<< e.message << "\"" << std::endl;
1261  gui2::show_error_message(v, _("Network communication error."));
1262  } catch(const network_asio::error& e) {
1263  ERR_NET << "network_asio::error thrown during transaction with add-on server; \""<< e.what() << "\"" << std::endl;
1264  gui2::show_error_message(v, _("Remote host disconnected."));
1265  } catch(const filesystem::io_exception& e) {
1266  ERR_FS << "io_exception thrown while installing an addon; \"" << e.what() << "\"" << std::endl;
1267  gui2::show_error_message(v, _("A problem occurred when trying to create the files necessary to install this add-on."));
1268  } catch(const invalid_pbl_exception& e) {
1269  ERR_CFG << "could not read .pbl file " << e.path << ": " << e.message << std::endl;
1270 
1271  utils::string_map symbols;
1272  symbols["path"] = e.path;
1273  symbols["msg"] = e.message;
1274 
1276  vgettext("A local file with add-on publishing information could not be read.\n\nFile: $path\nError message: $msg", symbols));
1277  } catch(twml_exception& e) {
1278  e.show(v);
1279  } catch(const addons_client::user_exit&) {
1280  LOG_AC << "initial connection canceled by user\n";
1281  } catch(const addons_client::invalid_server_address&) {
1282  gui2::show_error_message(v, _("The add-ons server address specified is not valid."));
1283  }
1284 
1285  return false;
1286 }
basic_sorter & set_alpha_sort(int column)
Definition: menu.cpp:48
int size
Definition: info.hpp:41
bool new_widgets
Do we wish to use the new library or not.
Definition: settings.cpp:40
ADDON_STATUS state
Definition: state.hpp:55
void show_error_message(CVideo &video, const std::string &message, bool message_use_markup)
Shows an error message to the user.
Definition: message.cpp:198
void set_campaign_server(const std::string &host)
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 ...
void read_addons_list(const config &cfg, addons_list &dest)
Definition: info.cpp:199
std::string bullet_list(const T &v, size_t indent=4, const std::string &bullet=unicode_bullet)
Generates a new string containing a bullet list.
const SDL_Color TITLE_COLOR
Definition: font.cpp:573
int get_retval() const
Definition: dialog.hpp:161
const std::string path
Path to the faulty .pbl file.
Definition: manager.hpp:44
int get_index(int selection) const
#define ERR_FS
Definition: manager_ui.cpp:57
ADDON_SORT
Add-on fallback/default sorting criteria for the user interface.
Definition: state.hpp:93
const char * what() const
Definition: exceptions.hpp:35
#define ERR_CFG
Definition: manager_ui.cpp:53
std::string campaign_server()
const GLfloat * c
Definition: glew.h:12741
bool download_addon(config &archive_cfg, const std::string &id, const std::string &title, bool increase_downloads=true)
Downloads the specified add-on from the server.
Definition: client.cpp:184
Add a special kind of assert to validate whether the input from WML doesn't contain any problems that...
bool show(CVideo &video, const unsigned auto_close_time=0)
Shows the window.
Definition: dialog.cpp:34
static void display(const std::string &addon_id, const addons_list &addons_list, const addons_tracking_list &addon_states, CVideo &video)
The display function.
Definition: description.hpp:47
logger & info()
Definition: log.cpp:91
Exception thrown when the WML parser fails to read a .pbl file.
Definition: manager.hpp:31
#define LOG_AC
Definition: manager_ui.cpp:61
std::string size_display_string(double size)
Get a human-readable representation of the specified byte count.
Definition: info.cpp:219
Definition: video.hpp:58
void truncate_as_ucs4(utf8::string &str, const size_t size)
Truncates a UTF-8 string to the specified number of characters.
Definition: unicode.cpp:124
int relative_size(int size)
Definition: font.hpp:72
virtual void enable(bool new_val=true)
Definition: button.cpp:386
ADDON_SORT sort() const
This file contains the window object, this object is a top level container which has the event manage...
bool is_addon_installed(const std::string &addon_name)
Check whether the specified add-on is currently installed.
Definition: manager.cpp:166
basic_sorter & set_position_sort(int column, const std::vector< int > &pos)
Definition: menu.cpp:88
ADDON_STATUS
Defines various add-on installation statuses.
Definition: state.hpp:22
void show(CVideo &video)
Shows the error in a dialog.
std::string description
Definition: info.hpp:33
utf8::string lowercase(const utf8::string &s)
Returns a lowercased version of the string.
Definition: unicode.cpp:53
void get_addon_install_info(const std::string &addon_name, config &cfg)
Gets the installation info (_info.cfg) for an add-on.
Definition: manager.cpp:107
std::string word_wrap_text(const std::string &unwrapped_text, int font_size, int max_width, int max_height, int max_lines, bool partial_line)
Wrap text.
void set_text(const std::string &text, const SDL_Color &color=font::NORMAL_COLOR)
Definition: textbox.cpp:76
Addon connect dialog.
Definition: connect.hpp:26
bool manage_addons(CVideo &v)
Shows the add-ons server connection dialog, for access to the various management front-ends.
void show_transient_message(CVideo &video, 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.
bool install_addon(config &archive_cfg, const addon_info &info)
Installs the specified add-on using an archive received from the server.
Definition: client.cpp:205
const std::string text() const
Definition: textbox.cpp:69
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:71
#define ERR_NET
Definition: manager_ui.cpp:55
OUTCOME
Definition: manager_ui.cpp:138
static imgsel_style bluebg_style
Definition: menu.hpp:118
GLsizei GLenum GLenum * types
Definition: glew.h:3155
No tracking information available.
Definition: state.hpp:35
void set_displayed_status(ADDON_STATUS_FILTER status)
Add-on is not installed.
Definition: state.hpp:24
char const IMAGE_PREFIX
static lg::log_domain log_network("network")
const int SIZE_NORMAL
Definition: font.hpp:58
std::vector< std::string > available_addons()
Returns a list of local add-ons that can be published.
Definition: manager.cpp:134
GLdouble GLdouble GLdouble b
Definition: glew.h:6966
Sort by creation time.
Definition: state.hpp:96
bool have_addon_install_info(const std::string &addon_name)
Returns true if there is a local installation info (_info.cfg) file for the add-on.
Definition: manager.cpp:102
gui::filter_textbox & filter_
Definition: dialogs.cpp:99
bool exists(const image::locator &i_locator)
returns true if the given image actually exists, without loading it.
Definition: image.cpp:1187
ADDON_SORT_DIRECTION
Add-on fallback/default sorting direction.
Definition: state.hpp:102
const config & options()
void ellipsis_truncate(std::string &str, const size_t size)
Truncates a string to a given utf-8 character count and then appends an ellipsis. ...
const std::string & get_last_server_error() const
Returns the last error message sent by the server, or an empty string.
Definition: client.hpp:55
static UNUSEDNOWARN std::string _(const char *str)
Definition: gettext.hpp:82
Version in the server is older than local installation.
Definition: state.hpp:30
const char BAD_TEXT
GLuint64EXT * result
Definition: glew.h:10727
Dialog is closed with ok button.
Definition: window.hpp:125
std::map< std::string, t_string > string_map
GLuint id
Definition: glew.h:1647
bool is_installed_addon_status(ADDON_STATUS s)
Definition: state.hpp:38
bool ad_hoc_addon_fetch_session(CVideo &v, const std::vector< std::string > &addon_ids)
Conducts an ad-hoc add-ons server connection to download an add-on with a particular id and all it's ...
This file contains the settings handling of the widget library.
Descending sort.
Definition: state.hpp:104
ADDON_STATUS_FILTER
Add-on installation status filters for the user interface.
Definition: state.hpp:82
std::ostringstream wrapper.
Definition: formatter.hpp:32
std::string display_title() const
Get a title or automatic title for display.
Definition: info.cpp:124
const std::string message
Error message to display.
Definition: manager.hpp:47
std::map< std::string, addon_info > addons_list
Definition: info.hpp:26
const GLdouble * v
Definition: glew.h:1359
void show_message(CVideo &video, const std::string &title, const std::string &message, const std::string &button_caption, const bool auto_close, const bool message_use_markup)
Shows a message to the user.
Definition: message.cpp:143
std::string id
Definition: info.hpp:31
Version in the server is newer than local installation.
Definition: state.hpp:28
GLboolean GLboolean GLboolean GLboolean a
Definition: glew.h:7319
static UNUSEDNOWARN std::string _n(const char *str1, const char *str2, int n)
Definition: gettext.hpp:86
Shows an ok and cancel button.
Definition: message.hpp:71
std::vector< std::string > installed_addons()
Retrieves the names of all installed add-ons.
Definition: manager.cpp:150
void refresh_addon_version_info_cache()
Refreshes the per-session cache of add-on's version information structs.
Definition: manager.cpp:337
bool remove_local_addon(const std::string &addon)
Definition: manager.cpp:120
void connect()
Try to establish a connection to the add-ons server.
Definition: client.cpp:57
ADDON_TYPE type
Definition: info.hpp:45
Dependencies not satisfied.
Definition: state.hpp:33
ADDON_SORT_DIRECTION direction() const
typedef int(WINAPI *PFNWGLRELEASEPBUFFERDCARBPROC)(HPBUFFERARB hPbuffer
Add-ons (campaignd) client class.
Definition: client.hpp:32
std::string str() const
Serializes the version number into string form.
Definition: version.cpp:90
virtual RESULT button_pressed(int menu_selection)=0
Ascending sort.
Definition: state.hpp:103
std::string color2markup(const SDL_Color &color)
Create string of color-markup, such as "<255,255,0>" for yellow.
std::string display_icon() const
Get an icon path fixed for display (e.g.
Definition: info.cpp:133
void set_direction(ADDON_SORT_DIRECTION direction)
void move_selection(size_t id)
Definition: menu.cpp:567
GLuint res
Definition: glew.h:9258
static lg::log_domain log_addons_client("addons-client")
const char NULL_MARKUP
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:80
std::string display_type() const
Get an add-on type identifier for display in the user's language.
Definition: info.cpp:154
An exception object used when an IO error occurs.
Definition: filesystem.hpp:40
Sort by add-on name.
Definition: state.hpp:94
Declarations for File-IO.
static int sort(lua_State *L)
Definition: ltablib.cpp:246
DIALOG_RESULT
Definition: show_dialog.hpp:33
basic_sorter & set_numeric_sort(int column)
Definition: menu.cpp:54
Represents version numbers.
Definition: version.hpp:44
virtual void hide(bool value=true)
Definition: scrollarea.cpp:78
std::string vgettext(const char *msgid, const utils::string_map &symbols)
Sort by last upload time.
Definition: state.hpp:95
GLuint const GLchar * name
Definition: glew.h:1782
void set_sort(ADDON_SORT sort)
GLint GLint GLint GLint GLint GLint GLint GLbitfield GLenum filter
Definition: glew.h:3448
static lg::log_domain log_filesystem("filesystem")
GLenum GLenum GLvoid * row
Definition: glew.h:3805
char const HEADING_PREFIX
std::string make_addon_title(const std::string &id)
Replaces underscores to dress up file or dirnames as add-on titles.
Definition: info.cpp:228
void get_addon_pbl_info(const std::string &addon_name, config &cfg)
Gets the publish information for an add-on.
Definition: manager.cpp:85
char const COLUMN_SEPARATOR
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:26
bool find(E event, F functor)
Tests whether an event handler is available.
const char SMALL_TEXT
static const int max_menu_width
std::map< std::string, addon_tracking_info > addons_tracking_list
Definition: state.hpp:62
std::string title
Definition: info.hpp:32
config & child(const std::string &key, int n=0)
Returns the nth child with the given key, or a reference to an invalid config if there is none...
Definition: config.cpp:658
const char GOOD_TEXT
const SDL_Color GRAY_COLOR
Definition: font.cpp:565
bool upload_addon(const std::string &id, std::string &response_message, config &cfg)
Requests the specified add-on to be uploaded.
Definition: client.cpp:102
Standard logging facilities (interface).
Shows the list of addons on the server.
Definition: list.hpp:35
std::string message
Definition: exceptions.hpp:29
#define c
Definition: glew.h:12743
version_info version
Definition: info.hpp:37
#define e
Shows an ok button.
Definition: message.hpp:67
std::vector< bool > displayed_types() const
Stores additional status information about add-ons.
Definition: state.hpp:44
const SDL_Color YELLOW_COLOR
Definition: font.cpp:570
int downloads
Definition: info.hpp:42
std::string::const_iterator iterator
Definition: tokenizer.hpp:21
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:83
Helper class, don't construct this directly.
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:151
bool request_distribution_terms(std::string &terms)
Request the add-ons server distribution terms message.
Definition: client.cpp:86
int f_
Definition: formula.cpp:689
GLsizei const GLcharARB ** string
Definition: glew.h:4503
void set_displayed_types(const std::vector< bool > &types)
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:186
ADDON_STATUS_FILTER displayed_status() const
bool request_addons_list(config &cfg)
Request the add-ons list from the server.
Definition: client.cpp:70
Shows a yes and no button.
Definition: message.hpp:75
std::string author
Definition: info.hpp:39
const std::string unicode_bullet
Version in the server matches local installation.
Definition: state.hpp:26
static lg::log_domain log_config("config")