The Battle for Wesnoth  1.15.0-dev
addon_list.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2016 - 2018 by the Battle for Wesnoth Project https://www.wesnoth.org/
3 
4  This program is free software; you can redistribute it and/or modify
5  it under the terms of the GNU General Public License as published by
6  the Free Software Foundation; either version 2 of the License, or
7  (at your option) any later version.
8  This program is distributed in the hope that it will be useful,
9  but WITHOUT ANY WARRANTY.
10 
11  See the COPYING file for more details.
12 */
13 
14 #define GETTEXT_DOMAIN "wesnoth-lib"
15 
17 
18 #include "addon/client.hpp"
19 #include "color.hpp"
20 #include "font/text_formatting.hpp"
21 #include "formatter.hpp"
22 #include "gettext.hpp"
26 #include "gui/widgets/button.hpp"
28 #include "gui/widgets/label.hpp"
29 #include "gui/widgets/listbox.hpp"
30 #include "gui/widgets/settings.hpp"
33 #include "gui/widgets/window.hpp"
34 #include "wml_exception.hpp"
35 
36 #include <algorithm>
37 
38 namespace gui2
39 {
40 namespace
41 {
42 const color_t color_outdated {255, 127, 0};
43 
44 const unsigned CONTROL_STACK_LAYER_INSTALL = 0;
45 const unsigned CONTROL_STACK_LAYER_UPDATE = 1;
46 const unsigned CONTROL_STACK_LAYER_PUBLISH = 2;
47 
48 } // end anon namespace
49 
50 REGISTER_WIDGET(addon_list)
51 
52 addon_list::addon_list(const implementation::builder_addon_list& builder)
53  : container_base(builder, type())
54  , addon_vector_()
55  , install_status_visibility_(visibility::visible)
56  , install_buttons_visibility_(visibility::invisible)
57  , install_function_()
58  , uninstall_function_()
59  , publish_function_()
60  , delete_function_()
61 {
62 }
63 
64 std::string addon_list::colorize_addon_state_string(const std::string& str, ADDON_STATUS state, bool verbose)
65 {
66  color_t colorname = font::NORMAL_COLOR;
67 
68  switch(state) {
69  case ADDON_NONE:
70  if(!verbose) {
71  return str;
72  }
73  colorname = font::weapon_details_color;
74  break;
75  case ADDON_INSTALLED:
77  case ADDON_NOT_TRACKED:
78  colorname = font::GOOD_COLOR;
79  break;
81  colorname = font::YELLOW_COLOR;
82  break;
84  colorname = color_outdated;
85  break;
87  colorname = font::BAD_COLOR;
88  break;
89  default:
90  colorname = font::GRAY_COLOR;
91  break;
92  }
93 
94  return font::span_color(colorname) + str + "</span>";
95 }
96 
98 {
99  std::string tx;
100 
101  switch(info.state) {
102  case ADDON_NONE:
103  tx = info.can_publish ? _("addon_state^Published, not installed") : _("addon_state^Not installed");
104  break;
105 
106  case ADDON_INSTALLED:
107  case ADDON_NOT_TRACKED:
108  // Consider add-ons without version information as installed
109  // for the main display. Their Description info should elaborate
110  // on their status.
111  tx = info.can_publish ? _("addon_state^Published") : _("addon_state^Installed");
112  break;
114  tx = info.can_publish ? _("addon_state^Ready to publish") : _("addon_state^Installed, not ready to publish");
115  break;
116 
118  tx = info.can_publish ? _("addon_state^Published, upgradable") : _("addon_state^Installed, upgradable");
119  break;
120 
122  tx = info.can_publish ? _("addon_state^Published, outdated on server") : _("addon_state^Installed, outdated on server");
123  break;
124 
126  tx = info.can_publish ? _("addon_state^Published, broken") : _("addon_state^Installed, broken");
127  break;
128 
129  default:
130  tx = _("addon_state^Unknown");
131  }
132 
133  return colorize_addon_state_string(tx, info.state, true);
134 }
135 
136 void addon_list::addon_action_wrapper(addon_op_func_t& func, const addon_info& addon, bool& handled, bool& halt)
137 {
138  try {
139  func(addon);
140 
141  handled = halt = true;
142  } catch(const addons_client::user_exit&) {
143  // User canceled the op.
144  }
145 }
146 
148 {
149  listbox& list = get_listbox();
150  list.clear();
151 
152  addon_vector_.clear();
153 
154  for(const auto& a : addons) {
155  const addon_info& addon = a.second;
156  addon_tracking_info tracking_info = get_addon_tracking_info(addon);
157 
158  addon_vector_.push_back(&addon);
159 
160  std::map<std::string, string_map> data;
161  string_map item;
162 
163  if(!tracking_info.can_publish) {
164  item["label"] = addon.display_icon();
165  data.emplace("icon", item);
166 
167  item["label"] = addon.display_title();
168  data.emplace("name", item);
169  } else {
170  item["label"] = addon.display_icon() + "~SCALE(72,72)~BLIT(icons/icon-addon-publish.png,8,8)";
171  data.emplace("icon", item);
172 
173  const std::string publish_name = formatter()
175  << addon.display_title()
176  << "</span>";
177 
178  item["label"] = publish_name;
179  data.emplace("name", item);
180  }
181 
182  item["label"] = describe_status(tracking_info);
183  data.emplace("installation_status", item);
184 
185  // If the addon is upgradable or ourdated on server, we display the two relevant
186  // versions directly in the list for convenience.
187  const bool special_version_display =
188  tracking_info.state == ADDON_INSTALLED_UPGRADABLE ||
189  tracking_info.state == ADDON_INSTALLED_OUTDATED;
190 
191  std::ostringstream ss;
192  if(special_version_display) {
193  ss << tracking_info.installed_version.str() << "\n";
194  }
195 
196  ss << addon.version.str();
197 
198  if(special_version_display) {
199  ss.str(colorize_addon_state_string(ss.str(), tracking_info.state, false));
200  }
201 
202  item["label"] = ss.str();
203  data.emplace("version", item);
204 
205  item["label"] = addon.author;
206  data.emplace("author", item);
207 
208  item["label"] = size_display_string(addon.size);
209  data.emplace("size", item);
210 
211  item["label"] = std::to_string(addon.downloads);
212  data.emplace("downloads", item);
213 
214  item["label"] = addon.display_type();
215  data.emplace("type", item);
216 
217  grid* row_grid = &list.add_row(data);
218 
219  // Set special retval for the toggle panels
220  find_widget<toggle_panel>(row_grid, "list_panel", false).set_retval(DEFAULT_ACTION_RETVAL);
221 
222  // The control button grid is excluded on lower resolutions.
223  grid* control_grid = find_widget<grid>(row_grid, "single_install_buttons", false, false);
224  if(!control_grid) {
225  continue;
226  }
227 
228  //
229  // Set up the inline control buttons.
230  //
231  stacked_widget& install_update_stack = find_widget<stacked_widget>(control_grid, "install_update_stack", false);
232 
233  // These three buttons are in the install_update_stack. Only one is shown depending on the addon's state.
234  button& install_button = find_widget<button>(control_grid, "single_install", false);
235  button& update_button = find_widget<button>(control_grid, "single_update", false);
236  button& publish_button = find_widget<button>(control_grid, "single_publish", false);
237 
238  // This button is always shown.
239  button& uninstall_button = find_widget<button>(control_grid, "single_uninstall", false);
240 
241  const bool is_installed = is_installed_addon_status(tracking_info.state);
242  const bool is_local = tracking_info.state == ADDON_INSTALLED_LOCAL_ONLY;
243 
244  // Select the right button layer and set its callback.
245  if(tracking_info.can_publish) {
246  install_update_stack.select_layer(CONTROL_STACK_LAYER_PUBLISH);
247 
248  publish_button.set_active(true);
249 
250  if(publish_function_ != nullptr) {
251  connect_signal_mouse_left_click(publish_button,
252  std::bind(&addon_list::addon_action_wrapper, this, publish_function_, std::ref(addon), _3, _4));
253 
254  install_button.set_tooltip(_("Publish add-on"));
255  }
256  } else if(tracking_info.state == ADDON_INSTALLED_UPGRADABLE) {
257  install_update_stack.select_layer(CONTROL_STACK_LAYER_UPDATE);
258 
259  update_button.set_active(true);
260 
261  if(update_function_ != nullptr) {
262  connect_signal_mouse_left_click(update_button,
263  std::bind(&addon_list::addon_action_wrapper, this, update_function_, std::ref(addon), _3, _4));
264  }
265  } else {
266  install_update_stack.select_layer(CONTROL_STACK_LAYER_INSTALL);
267 
268  install_button.set_active(!is_installed);
269 
270  if(install_function_ != nullptr) {
271  connect_signal_mouse_left_click(install_button,
272  std::bind(&addon_list::addon_action_wrapper, this, install_function_, std::ref(addon), _3, _4));
273  }
274  }
275 
276  // Set up the Uninstall button.
277  if(tracking_info.can_publish) {
278  // Use the uninstall button as a delete-from-server button if the addon's already been published...
279  uninstall_button.set_active(!is_local);
280 
281  if(!is_local && delete_function_ != nullptr) {
282  connect_signal_mouse_left_click(uninstall_button,
283  std::bind(&addon_list::addon_action_wrapper, this, delete_function_, std::ref(addon), _3, _4));
284 
285  uninstall_button.set_tooltip(_("Delete add-on from server"));
286  }
287  } else {
288  // ... else it functions as normal.
289  uninstall_button.set_active(is_installed);
290 
291  if(is_installed && uninstall_function_ != nullptr) {
292  connect_signal_mouse_left_click(uninstall_button,
293  std::bind(&addon_list::addon_action_wrapper, this, uninstall_function_, std::ref(addon), _3, _4));
294  }
295  }
296 
297  control_grid->set_visible(install_buttons_visibility_);
298  find_widget<label>(row_grid, "installation_status", false).set_visible(install_status_visibility_);
299  }
300 
302 }
303 
305 {
306  const listbox& list = find_widget<const listbox>(&get_grid(), "addons", false);
307 
308  try {
309  return addon_vector_.at(list.get_selected_row());
310  } catch(const std::out_of_range&) {
311  return nullptr;
312  }
313 }
314 
316 {
317  const addon_info* addon = get_selected_addon();
318  if(addon == nullptr || !get_addon_tracking_info(*addon).can_publish) {
319  return "";
320  } else {
321  return addon->id;
322  }
323 }
324 
325 void addon_list::select_addon(const std::string& id)
326 {
327  listbox& list = get_listbox();
328 
329  auto iter = std::find_if(addon_vector_.begin(), addon_vector_.end(),
330  [&id](const addon_info* a) { return a->id == id; }
331  );
332 
333  // Corner case: if you publish an addon with an out-of-folder .pbl file and
334  // delete it locally before deleting it from the server, the game will try
335  // to reselect an addon that now doesn't exist.
336  //
337  // I don't anticipate this check will match very often, but in the case that
338  // some other weird corner case crops up, it's probably better to just exit
339  // silently than asserting, as was the pervious behavior.
340  if(iter == addon_vector_.end()) {
341  return;
342  }
343 
344  const addon_info& info = **iter;
345 
346  for(unsigned int i = 0u; i < list.get_item_count(); ++i) {
347  grid* row = list.get_row_grid(i);
348 
349  const label& name_label = find_widget<label>(row, "name", false);
350  if(name_label.get_label().base_str() == info.display_title()) {
351  list.select_row(i);
352  }
353  }
354 }
355 
357 {
358  return find_widget<listbox>(&get_grid(), "addons", false);
359 }
360 
362 {
363  if(window* window = get_window()) {
365  }
366 }
367 
369 {
370  listbox& list = get_listbox();
371 
372  list.register_translatable_sorting_option(0, [this](const int i) { return addon_vector_[i]->title; });
373  list.register_sorting_option(1, [this](const int i) { return addon_vector_[i]->author; });
374  list.register_sorting_option(2, [this](const int i) { return addon_vector_[i]->size; });
375  list.register_sorting_option(3, [this](const int i) { return addon_vector_[i]->downloads; });
376  list.register_translatable_sorting_option(4, [this](const int i) { return addon_vector_[i]->display_type(); });
377 
378  auto order = std::make_pair(0, gui2::listbox::SORT_ASCENDING);
379  list.set_active_sorting_option(order);
380 }
381 
383 {
384  listbox& list = get_listbox();
385 
386  generator_base::order_func generator_func = [this, func](unsigned a, unsigned b)
387  {
388  return func(*addon_vector_[a], *addon_vector_[b]);
389  };
390 
391  list.mark_as_unsorted();
392  list.order_by(generator_func);
393 }
394 
396 {
397  if(addon_vector_.empty()) {
398  // Happens in the dialog unit test.
399  return;
400  }
401 
402  const addon_info* first_addon = addon_vector_[0];
403 
404  for(const addon_info* a : addon_vector_) {
405  if(a->display_title().compare(first_addon->display_title()) < 0) {
406  first_addon = a;
407  }
408  }
409 
410  select_addon(first_addon->id);
411 }
412 
415 {
416  DBG_GUI_P << "Parsing add-on list " << id << "\n";
417 
418  load_resolutions<resolution>(cfg);
419 }
420 
422  : resolution_definition(cfg), grid(nullptr)
423 {
424  // Add a dummy state since every widget needs a state.
425  static config dummy("draw");
426  state.emplace_back(dummy);
427 
428  const config& child = cfg.child("grid");
429  VALIDATE(child, _("No grid defined."));
430 
431  grid = std::make_shared<builder_grid>(child);
432 }
433 
434 namespace implementation
435 {
436 
437 static widget::visibility parse_visibility(const std::string& str)
438 {
439  if(str == "visible") {
441  } else if(str == "hidden") {
443  } else if(str == "invisible") {
445  } else {
446  FAIL("Invalid visibility value");
447  }
448 }
449 
450 builder_addon_list::builder_addon_list(const config& cfg)
451  : builder_styled_widget(cfg)
452  , install_status_visibility_(widget::visibility::visible)
453  , install_buttons_visibility_(widget::visibility::invisible)
454 {
455  if(cfg.has_attribute("install_status_visibility")) {
456  install_status_visibility_ = parse_visibility(cfg["install_status_visibility"]);
457  }
458 
459  if(cfg.has_attribute("install_buttons_visibility")) {
460  install_buttons_visibility_ = parse_visibility(cfg["install_buttons_visibility"]);
461  }
462 }
463 
465 {
466  addon_list* widget = new addon_list(*this);
467 
468  DBG_GUI_G << "Window builder: placed add-on list '" << id <<
469  "' with definition '" << definition << "'.\n";
470 
471  const auto conf = widget->cast_config_to<addon_list_definition>();
472  assert(conf != nullptr);
473 
474  widget->init_grid(conf->grid);
475 
478 
479  widget->finalize_setup();
480 
481  return widget;
482 }
483 
484 } // end namespace implementation
485 
486 } // end namespace gui2
int size
Definition: info.hpp:41
Base class of a resolution, contains the common keys for a resolution.
listbox & get_listbox()
Returns the underlying list box.
Definition: addon_list.cpp:356
#define DBG_GUI_P
Definition: log.hpp:68
visibility install_status_visibility_
Definition: addon_list.hpp:160
void set_active_sorting_option(const order_pair &sort_by, const bool select_first=false)
Sorts the listbox by a pre-set sorting option.
Definition: listbox.cpp:640
ADDON_STATUS state
Definition: state.hpp:56
void addon_action_wrapper(addon_op_func_t &func, const addon_info &addon, bool &handled, bool &halt)
Helper to wrap the execution of any of the addon operation functions.
Definition: addon_list.cpp:136
config & child(config_key_type 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:423
static std::string describe_status(const addon_tracking_info &info)
Definition: addon_list.cpp:97
std::vector< state_definition > state
std::function< bool(const addon_info &, const addon_info &)> addon_sort_func
Definition: addon_list.hpp:41
int dummy
Definition: lstrlib.cpp:1125
const addon_info * get_selected_addon() const
Returns the selected add-on.
Definition: addon_list.cpp:304
void mark_as_unsorted()
Deactivates all sorting toggle buttons at the top, making the list look like it&#39;s not sorted...
Definition: listbox.cpp:672
const color_t GRAY_COLOR
const grid & get_grid() const
std::string display_title() const
Get a title or automatic title for display.
Definition: info.cpp:126
Add a special kind of assert to validate whether the input from WML doesn&#39;t contain any problems that...
const color_t GOOD_COLOR
addon_list_definition(const config &cfg)
Definition: addon_list.cpp:413
bool has_attribute(config_key_type key) const
Definition: config.cpp:217
logger & info()
Definition: log.cpp:90
void add_list_to_keyboard_chain()
Adds the internal listbox to the keyboard event chain.
Definition: addon_list.cpp:361
#define a
static widget::visibility parse_visibility(const std::string &str)
Definition: addon_list.cpp:437
const std::string & id() const
Definition: widget.cpp:107
This file contains the window object, this object is a top level container which has the event manage...
ADDON_STATUS
Defines various add-on installation statuses.
Definition: state.hpp:21
Base class for all widgets.
Definition: widget.hpp:47
std::function< bool(unsigned, unsigned)> order_func
Definition: generator.hpp:248
std::string get_remote_addon_id()
Returns the selected add-on id, for use with remote publish/delete ops.
Definition: addon_list.cpp:315
addon_op_func_t delete_function_
Definition: addon_list.hpp:168
void register_translatable_sorting_option(const int col, translatable_sorter_func_t f)
Registers a special sorting function specifically for translatable values.
Definition: listbox.cpp:632
void set_install_status_visibility(visibility visibility)
Determines if install status of each widget is shown.
Definition: addon_list.hpp:120
Label showing a text.
Definition: label.hpp:32
int get_selected_row() const
Returns the first selected row.
Definition: listbox.cpp:272
void add_to_keyboard_chain(widget *widget)
Adds the widget to the keyboard chain.
Definition: window.cpp:1288
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
bool select_row(const unsigned row, const bool select=true)
Selects a row.
Definition: listbox.cpp:250
Generic file dialog.
Definition: field-fwd.hpp:22
#define b
The listbox class.
Definition: listbox.hpp:40
std::string span_color(const color_t &color)
Returns a Pango formatting string using the provided color_t object.
Base container class.
Definition: grid.hpp:30
visibility install_buttons_visibility_
Definition: addon_list.hpp:161
static UNUSEDNOWARN std::string _(const char *str)
Definition: gettext.hpp:89
std::string definition
Parameters for the styled_widget.
Version in the server is older than local installation.
Definition: state.hpp:29
bool is_installed_addon_status(ADDON_STATUS s)
Definition: state.hpp:39
#define VALIDATE(cond, message)
The macro to use for the validation of WML.
void set_addon_order(addon_sort_func func)
Definition: addon_list.cpp:382
This file contains the settings handling of the widget library.
std::ostringstream wrapper.
Definition: formatter.hpp:38
void clear()
Removes all the rows in the listbox, clearing it.
Definition: listbox.cpp:125
void set_visible(const visibility visible)
Definition: widget.cpp:473
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:233
std::string display_icon() const
Get an icon path fixed for display (e.g.
Definition: info.cpp:135
std::string id
Definition: info.hpp:31
Version in the server is newer than local installation.
Definition: state.hpp:27
std::string display_type() const
Get an add-on type identifier for display in the user&#39;s language.
Definition: info.cpp:152
std::vector< const addon_info * > addon_vector_
Definition: addon_list.hpp:158
unsigned get_item_count() const
Returns the number of items in the listbox.
Definition: listbox.cpp:131
void set_addons(const addons_list &addons)
Sets the add-ons to show.
Definition: addon_list.cpp:147
const color_t YELLOW_COLOR
Dependencies not satisfied.
Definition: state.hpp:34
widget::visibility install_buttons_visibility_
Definition: addon_list.hpp:221
void init_grid(const std::shared_ptr< builder_grid > &grid_builder)
Initializes and builds the grid.
const color_t NORMAL_COLOR
#define REGISTER_WIDGET(id)
Wrapper for REGISTER_WIDGET3.
void order_by(const generator_base::order_func &func)
Definition: listbox.cpp:615
const color_t weapon_details_color
void select_addon(const std::string &id)
Selects the add-on with the given ID.
Definition: addon_list.cpp:325
void select_first_addon()
Needed because otherwise the add-on with the first ID would be initially selected.
Definition: addon_list.cpp:395
addon_op_func_t uninstall_function_
Definition: addon_list.hpp:164
std::size_t i
Definition: function.cpp:933
std::shared_ptr< const typename T::resolution > cast_config_to() const
Casts the current resolution definition config to the respective type of a derived widget...
The user set the widget invisible, that means:
window * get_window()
Get the parent window.
Definition: widget.cpp:114
std::string size_display_string(double size)
Get a human-readable representation of the specified byte count.
Definition: info.cpp:217
std::map< std::string, t_string > string_map
Definition: widget.hpp:24
visibility
Visibility settings done by the user.
Definition: widget.hpp:57
grid & add_row(const string_map &item, const int index=-1)
When an item in the list is selected by the user we need to update the state.
Definition: listbox.cpp:66
No version in the server.
Definition: state.hpp:31
A generic container base class.
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
addon_op_func_t publish_function_
Definition: addon_list.hpp:167
const grid * get_row_grid(const unsigned row) const
Returns the grid of the wanted row.
Definition: listbox.cpp:237
const t_string & get_label() const
The user sets the widget visible, that means:
addon_op_func_t install_function_
Definition: addon_list.hpp:163
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
The user sets the widget hidden, that means:
Simple push button.
Definition: button.hpp:35
std::string str() const
Serializes the version number into string form.
void set_install_buttons_visibility(visibility visibility)
Determines if install/uninstall buttons are shown for each widget.
Definition: addon_list.hpp:126
version_info version
Definition: info.hpp:37
std::string base_str() const
Definition: tstring.hpp:190
#define FAIL(message)
Stores additional status information about add-ons.
Definition: state.hpp:45
std::function< void(const addon_info &)> addon_op_func_t
Definition: addon_list.hpp:66
int downloads
Definition: info.hpp:42
void register_sorting_option(const int col, const Func &f)
Definition: listbox.hpp:269
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:92
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:63
#define DBG_GUI_G
Definition: log.hpp:40
static const int DEFAULT_ACTION_RETVAL
Special retval for the toggle panels in the addons list.
Definition: addon_list.hpp:46
std::string author
Definition: info.hpp:39
Contains the implementation details for lexical_cast and shouldn&#39;t be used directly.
const color_t BAD_COLOR
Version in the server matches local installation.
Definition: state.hpp:25
addon_op_func_t update_function_
Definition: addon_list.hpp:165