The Battle for Wesnoth  1.13.11+dev
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Modules Pages
help_impl.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2003 - 2018 by David White <dave@whitevine.net>
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 #include "help/help_impl.hpp"
16 
17 #include "about.hpp" // for get_text
18 #include "display.hpp" // for display
19 #include "display_context.hpp" // for display_context
20 #include "game_config.hpp" // for debug, menu_contract, etc
21 #include "game_config_manager.hpp" // for game_config_manager
22 #include "preferences/game.hpp" // for encountered_terrains, etc
23 #include "gettext.hpp" // for _, gettext, N_
25 #include "hotkey/hotkey_command.hpp" // for is_scope_active, etc
26 #include "image.hpp" // for get_image, locator
27 #include "log.hpp" // for LOG_STREAM, logger, etc
28 #include "utils/make_enum.hpp" // for operator<<
29 #include "map/map.hpp" // for gamemap
30 #include "font/marked-up_text.hpp" // for is_cjk_char, word_wrap_text
31 #include "font/standard_colors.hpp" // for NORMAL_COLOR
32 #include "units/race.hpp" // for unit_race, etc
33 #include "resources.hpp" // for tod_manager, config_manager
34 #include "sdl/surface.hpp" // for surface
35 #include "serialization/string_utils.hpp" // for split, quoted_split, etc
36 #include "serialization/unicode_cast.hpp" // for unicode_cast
37 #include "serialization/unicode_types.hpp" // for char_t, etc
38 #include "terrain/terrain.hpp" // for terrain_type
39 #include "terrain/translation.hpp" // for operator==, ter_list, etc
40 #include "terrain/type_data.hpp" // for terrain_type_data, etc
41 #include "time_of_day.hpp" // for time_of_day
42 #include "tod_manager.hpp" // for tod_manager
43 #include "tstring.hpp" // for t_string, operator<<
44 #include "units/types.hpp" // for unit_type, unit_type_data, etc
45 #include "serialization/unicode.hpp" // for iterator
46 #include "color.hpp"
47 
48 #include <cassert> // for assert
49 #include <algorithm> // for sort, find, transform, etc
50 #include <iostream> // for operator<<, basic_ostream, etc
51 #include <iterator> // for back_insert_iterator, etc
52 #include <map> // for map, etc
53 #include <set>
54 #include <SDL.h>
55 
56 static lg::log_domain log_display("display");
57 #define WRN_DP LOG_STREAM(warn, log_display)
58 
59 static lg::log_domain log_help("help");
60 #define WRN_HP LOG_STREAM(warn, log_help)
61 #define DBG_HP LOG_STREAM(debug, log_help)
62 
63 namespace help {
64 
65 const config *game_cfg = nullptr;
66 // The default toplevel.
68 // All sections and topics not referenced from the default toplevel.
70 
74 
76 std::vector<std::string> empty_string_vector;
77 const int max_section_level = 15;
80 const int box_width = 2;
82 const unsigned max_history = 100;
83 const std::string topic_img = "help/topic.png";
84 const std::string closed_section_img = "help/closed_section.png";
85 const std::string open_section_img = "help/open_section.png";
86 // The topic to open by default when opening the help dialog.
87 const std::string default_show_topic = "..introduction";
88 const std::string unknown_unit_topic = ".unknown_unit";
89 const std::string unit_prefix = "unit_";
90 const std::string terrain_prefix = "terrain_";
91 const std::string race_prefix = "race_";
92 const std::string faction_prefix = "faction_";
93 const std::string era_prefix = "era_";
94 const std::string variation_prefix = "variation_";
95 const std::string ability_prefix = "ability_";
96 
97 bool section_is_referenced(const std::string &section_id, const config &cfg)
98 {
99  if (const config &toplevel = cfg.child("toplevel"))
100  {
101  const std::vector<std::string> toplevel_refs
102  = utils::quoted_split(toplevel["sections"]);
103  if (std::find(toplevel_refs.begin(), toplevel_refs.end(), section_id)
104  != toplevel_refs.end()) {
105  return true;
106  }
107  }
108 
109  for (const config &section : cfg.child_range("section"))
110  {
111  const std::vector<std::string> sections_refd
112  = utils::quoted_split(section["sections"]);
113  if (std::find(sections_refd.begin(), sections_refd.end(), section_id)
114  != sections_refd.end()) {
115  return true;
116  }
117  }
118  return false;
119 }
120 
121 bool topic_is_referenced(const std::string &topic_id, const config &cfg)
122 {
123  if (const config &toplevel = cfg.child("toplevel"))
124  {
125  const std::vector<std::string> toplevel_refs
126  = utils::quoted_split(toplevel["topics"]);
127  if (std::find(toplevel_refs.begin(), toplevel_refs.end(), topic_id)
128  != toplevel_refs.end()) {
129  return true;
130  }
131  }
132 
133  for (const config &section : cfg.child_range("section"))
134  {
135  const std::vector<std::string> topics_refd
136  = utils::quoted_split(section["topics"]);
137  if (std::find(topics_refd.begin(), topics_refd.end(), topic_id)
138  != topics_refd.end()) {
139  return true;
140  }
141  }
142  return false;
143 }
144 
145 void parse_config_internal(const config *help_cfg, const config *section_cfg,
146  section &sec, int level)
147 {
148  if (level > max_section_level) {
149  std::cerr << "Maximum section depth has been reached. Maybe circular dependency?"
150  << std::endl;
151  }
152  else if (section_cfg != nullptr) {
153  const std::vector<std::string> sections = utils::quoted_split((*section_cfg)["sections"]);
154  sec.level = level;
155  std::string id = level == 0 ? "toplevel" : (*section_cfg)["id"].str();
156  if (level != 0) {
157  if (!is_valid_id(id)) {
158  std::stringstream ss;
159  ss << "Invalid ID, used for internal purpose: '" << id << "'";
160  throw parse_error(ss.str());
161  }
162  }
163  std::string title = level == 0 ? "" : (*section_cfg)["title"].str();
164  sec.id = id;
165  sec.title = title;
166  std::vector<std::string>::const_iterator it;
167  // Find all child sections.
168  for (it = sections.begin(); it != sections.end(); ++it) {
169  if (const config &child_cfg = help_cfg->find_child("section", "id", *it))
170  {
171  section child_section;
172  parse_config_internal(help_cfg, &child_cfg, child_section, level + 1);
173  sec.add_section(child_section);
174  }
175  else {
176  std::stringstream ss;
177  ss << "Help-section '" << *it << "' referenced from '"
178  << id << "' but could not be found.";
179  throw parse_error(ss.str());
180  }
181  }
182 
183  generate_sections(help_cfg, (*section_cfg)["sections_generator"], sec, level);
184  //TODO: harmonize topics/sections sorting
185  if ((*section_cfg)["sort_sections"] == "yes") {
186  std::sort(sec.sections.begin(),sec.sections.end(), section_less());
187  }
188 
189  bool sort_topics = false;
190  bool sort_generated = true;
191 
192  if ((*section_cfg)["sort_topics"] == "yes") {
193  sort_topics = true;
194  sort_generated = false;
195  } else if ((*section_cfg)["sort_topics"] == "no") {
196  sort_topics = false;
197  sort_generated = false;
198  } else if ((*section_cfg)["sort_topics"] == "generated") {
199  sort_topics = false;
200  sort_generated = true;
201  } else if (!(*section_cfg)["sort_topics"].empty()) {
202  std::stringstream ss;
203  ss << "Invalid sort option: '" << (*section_cfg)["sort_topics"] << "'";
204  throw parse_error(ss.str());
205  }
206 
207  std::vector<topic> generated_topics =
208  generate_topics(sort_generated,(*section_cfg)["generator"]);
209 
210  const std::vector<std::string> topics_id = utils::quoted_split((*section_cfg)["topics"]);
211  std::vector<topic> topics;
212 
213  // Find all topics in this section.
214  for (it = topics_id.begin(); it != topics_id.end(); ++it) {
215  if (const config &topic_cfg = help_cfg->find_child("topic", "id", *it))
216  {
217  std::string text = topic_cfg["text"];
218  text += generate_topic_text(topic_cfg["generator"], help_cfg, sec, generated_topics);
219  topic child_topic(topic_cfg["title"], topic_cfg["id"], text);
220  if (!is_valid_id(child_topic.id)) {
221  std::stringstream ss;
222  ss << "Invalid ID, used for internal purpose: '" << id << "'";
223  throw parse_error(ss.str());
224  }
225  topics.push_back(child_topic);
226  }
227  else {
228  std::stringstream ss;
229  ss << "Help-topic '" << *it << "' referenced from '" << id
230  << "' but could not be found." << std::endl;
231  throw parse_error(ss.str());
232  }
233  }
234 
235  if (sort_topics) {
236  std::sort(topics.begin(),topics.end(), title_less());
237  std::sort(generated_topics.begin(),
238  generated_topics.end(), title_less());
239  std::merge(generated_topics.begin(),
240  generated_topics.end(),topics.begin(),topics.end()
241  ,std::back_inserter(sec.topics),title_less());
242  }
243  else {
244  sec.topics.insert(sec.topics.end(),
245  topics.begin(), topics.end());
246  sec.topics.insert(sec.topics.end(),
247  generated_topics.begin(), generated_topics.end());
248  }
249  }
250 }
251 
253 {
254  section sec;
255  if (cfg != nullptr) {
256  const config& toplevel_cfg = cfg->child("toplevel");
257  parse_config_internal(cfg, toplevel_cfg ? &toplevel_cfg : nullptr, sec);
258  }
259  return sec;
260 }
261 
262 std::vector<topic> generate_topics(const bool sort_generated,const std::string &generator)
263 {
264  std::vector<topic> res;
265  if (generator.empty()) {
266  return res;
267  }
268 
269  if (generator == "abilities") {
270  res = generate_ability_topics(sort_generated);
271  } else if (generator == "weapon_specials") {
272  res = generate_weapon_special_topics(sort_generated);
273  } else if (generator == "time_of_days") {
274  res = generate_time_of_day_topics(sort_generated);
275  } else if (generator == "traits") {
276  res = generate_trait_topics(sort_generated);
277  } else {
278  std::vector<std::string> parts = utils::split(generator, ':', utils::STRIP_SPACES);
279  if (parts.size() > 1 && parts[0] == "units") {
280  res = generate_unit_topics(sort_generated, parts[1]);
281  } else if (parts[0] == "era" && parts.size()>1) {
282  res = generate_era_topics(sort_generated, parts[1]);
283  } else {
284  WRN_HP << "Found a topic generator that I didn't recognize: " << generator << "\n";
285  }
286  }
287 
288  return res;
289 }
290 
291 void generate_sections(const config *help_cfg, const std::string &generator, section &sec, int level)
292 {
293  if (generator == "races") {
294  generate_races_sections(help_cfg, sec, level);
295  } else if (generator == "terrains") {
296  generate_terrain_sections(help_cfg, sec, level);
297  } else if (generator == "eras") {
298  DBG_HP << "Generating eras...\n";
299  generate_era_sections(help_cfg, sec, level);
300  } else {
301  std::vector<std::string> parts = utils::split(generator, ':', utils::STRIP_SPACES);
302  if (parts.size() > 1 && parts[0] == "units") {
303  generate_unit_sections(help_cfg, sec, level, true, parts[1]);
304  } else if (generator.size() > 0) {
305  WRN_HP << "Found a section generator that I didn't recognize: " << generator << "\n";
306  }
307  }
308 }
309 
310 std::string generate_topic_text(const std::string &generator, const config *help_cfg, const section &sec, const std::vector<topic>& generated_topics)
311 {
312  std::string empty_string = "";
313  if (generator.empty()) {
314  return empty_string;
315  } else if (generator == "about") {
316  return generate_about_text();
317  } else {
318  std::vector<std::string> parts = utils::split(generator, ':');
319  if (parts.size() > 1 && parts[0] == "contents") {
320  if (parts[1] == "generated") {
321  return generate_contents_links(sec, generated_topics);
322  } else {
323  return generate_contents_links(parts[1], help_cfg);
324  }
325  }
326  }
327  return empty_string;
328 }
329 
331 {
332  if (generator_ && --generator_->count == 0)
333  delete generator_;
334 }
335 
336 topic_text::topic_text(const topic_text& t): parsed_text_(t.parsed_text_), generator_(t.generator_)
337 {
338  if (generator_)
339  ++generator_->count;
340 }
341 
343 {
344  if (generator_ && --generator_->count == 0)
345  delete generator_;
346  generator_ = g;
347  return *this;
348 }
349 
350 const std::vector<std::string>& topic_text::parsed_text() const
351 {
352  if (generator_) {
354  if (--generator_->count == 0)
355  delete generator_;
356  generator_ = nullptr;
357  }
358  return parsed_text_;
359 }
360 
361 std::vector<topic> generate_time_of_day_topics(const bool /*sort_generated*/)
362 {
363  std::vector<topic> topics;
364  std::stringstream toplevel;
365 
366  if (! resources::tod_manager) {
367  toplevel << N_("Only available during a scenario.");
368  topics.emplace_back("Time of Day Schedule", "..schedule", toplevel.str());
369  return topics;
370  }
371  const std::vector<time_of_day>& times = resources::tod_manager->times();
372  for (const time_of_day& time : times)
373  {
374  const std::string id = "time_of_day_" + time.id;
375  const std::string image = "<img>src='" + time.image + "'</img>";
376  std::stringstream text;
377 
378  toplevel << make_link(time.name.str(), id) << jump_to(160) <<
379  image << jump(30) << time.lawful_bonus << '\n';
380 
381  text << image << '\n' <<
382  time.description.str() << '\n' <<
383  "Lawful Bonus: " << time.lawful_bonus << '\n' <<
384  '\n' << make_link(N_("Schedule"), "..schedule");
385 
386  topics.emplace_back(time.name.str(), id, text.str());
387  }
388 
389  topics.emplace_back("Time of Day Schedule", "..schedule", toplevel.str());
390  return topics;
391 }
392 
393 std::vector<topic> generate_weapon_special_topics(const bool sort_generated)
394 {
395  std::vector<topic> topics;
396 
397  std::map<t_string, std::string> special_description;
398  std::map<t_string, std::set<std::string, string_less>> special_units;
399 
400  for (const unit_type_data::unit_type_map::value_type &type_mapping : unit_types.types())
401  {
402  const unit_type &type = type_mapping.second;
403  // Only show the weapon special if we find it on a unit that
404  // detailed description should be shown about.
405  if (description_type(type) != FULL_DESCRIPTION)
406  continue;
407 
408  for (const attack_type& atk : type.attacks()) {
409 
410  std::vector<std::pair<t_string, t_string>> specials = atk.special_tooltips();
411  for ( size_t i = 0; i != specials.size(); ++i )
412  {
413  special_description.emplace(specials[i].first, specials[i].second);
414 
415  if (!type.hide_help()) {
416  //add a link in the list of units having this special
417  std::string type_name = type.type_name();
418  //check for variations (walking corpse/soulless etc)
419  const std::string section_prefix = type.show_variations_in_help() ? ".." : "";
420  std::string ref_id = section_prefix + unit_prefix + type.id();
421  //we put the translated name at the beginning of the hyperlink,
422  //so the automatic alphabetic sorting of std::set can use it
423  std::string link = make_link(type_name, ref_id);
424  special_units[specials[i].first].insert(link);
425  }
426  }
427  }
428 
429  for(config adv : type.modification_advancements()) {
430  for(config effect : adv.child_range("effect")) {
431  if(effect["apply_to"] == "new_attack" && effect.has_child("specials")) {
432  for(config::any_child spec : effect.child("specials").all_children_range()) {
433  if(!spec.cfg["name"].empty()) {
434  special_description.emplace(spec.cfg["name"].t_str(), spec.cfg["description"].t_str());
435  if(!type.hide_help()) {
436  //add a link in the list of units having this special
437  std::string type_name = type.type_name();
438  //check for variations (walking corpse/soulless etc)
439  const std::string section_prefix = type.show_variations_in_help() ? ".." : "";
440  std::string ref_id = section_prefix + unit_prefix + type.id();
441  //we put the translated name at the beginning of the hyperlink,
442  //so the automatic alphabetic sorting of std::set can use it
443  std::string link = make_link(type_name, ref_id);
444  special_units[spec.cfg["name"]].insert(link);
445  }
446  }
447  }
448  } else if(effect["apply_to"] == "attack" && effect.has_child("set_specials")) {
449  for(config::any_child spec : effect.child("set_specials").all_children_range()) {
450  if(!spec.cfg["name"].empty()) {
451  special_description.emplace(spec.cfg["name"].t_str(), spec.cfg["description"].t_str());
452  if(!type.hide_help()) {
453  //add a link in the list of units having this special
454  std::string type_name = type.type_name();
455  //check for variations (walking corpse/soulless etc)
456  const std::string section_prefix = type.show_variations_in_help() ? ".." : "";
457  std::string ref_id = section_prefix + unit_prefix + type.id();
458  //we put the translated name at the beginning of the hyperlink,
459  //so the automatic alphabetic sorting of std::set can use it
460  std::string link = make_link(type_name, ref_id);
461  special_units[spec.cfg["name"]].insert(link);
462  }
463  }
464  }
465  }
466  }
467  }
468  }
469 
470  for (std::map<t_string, std::string>::iterator s = special_description.begin();
471  s != special_description.end(); ++s) {
472  // use untranslated name to have universal topic id
473  std::string id = "weaponspecial_" + s->first.base_str();
474  std::stringstream text;
475  text << s->second;
476  text << "\n\n" << _("<header>text='Units with this special attack'</header>") << "\n";
477  std::set<std::string, string_less> &units = special_units[s->first];
478  for (std::set<std::string, string_less>::iterator u = units.begin(); u != units.end(); ++u) {
479  text << font::unicode_bullet << " " << (*u) << "\n";
480  }
481 
482  topics.emplace_back(s->first, id, text.str());
483  }
484 
485  if (sort_generated)
486  std::sort(topics.begin(), topics.end(), title_less());
487  return topics;
488 }
489 
490 std::vector<topic> generate_ability_topics(const bool sort_generated)
491 {
492  std::vector<topic> topics;
493 
494  std::map<std::string, const unit_type::ability_metadata*> ability_topic_data;
495  std::map<std::string, std::set<std::string, string_less>> ability_units;
496 
497  const auto parse = [&](const unit_type& type, const unit_type::ability_metadata& ability) {
498  // NOTE: neither ability names nor ability ids are necessarily unique. Creating
499  // topics for either each unique name or each unique id means certain abilities
500  // will be excluded from help. So... the ability topic ref id is a combination
501  // of id and (untranslated) name. It's rather ugly, but it works.
502  const std::string topic_ref = ability.id + ability.name.base_str();
503 
504  ability_topic_data.emplace(topic_ref, &ability);
505 
506  if(!type.hide_help()) {
507  // Add a link in the list of units with this ability
508  // We put the translated name at the beginning of the hyperlink,
509  // so the automatic alphabetic sorting of std::set can use it
510  const std::string link = make_link(type.type_name(), unit_prefix + type.id());
511  ability_units[topic_ref].insert(link);
512  }
513  };
514 
515  // Look through all the unit types. If a unit of that type would have a full
516  // description, add its abilities to the potential topic list. We don't want
517  // to show abilities that the user has not encountered yet.
518  for(const auto& type_mapping : unit_types.types()) {
519  const unit_type& type = type_mapping.second;
520 
521  if(description_type(type) != FULL_DESCRIPTION) {
522  continue;
523  }
524 
525  for(const unit_type::ability_metadata& ability : type.abilities_metadata()) {
526  parse(type, ability);
527  }
528 
529  for(const unit_type::ability_metadata& ability : type.adv_abilities_metadata()) {
530  parse(type, ability);
531  }
532  }
533 
534  for(const auto& a : ability_topic_data) {
535  std::ostringstream text;
536  text << a.second->description;
537  text << "\n\n" << _("<header>text='Units with this ability'</header>") << "\n";
538 
539  for(const auto& u : ability_units[a.first]) { // first is the topic ref id
540  text << font::unicode_bullet << " " << u << "\n";
541  }
542 
543  topics.emplace_back(a.second->name, ability_prefix + a.first, text.str());
544  }
545 
546  if(sort_generated) {
547  std::sort(topics.begin(), topics.end(), title_less());
548  }
549 
550  return topics;
551 }
552 
553 std::vector<topic> generate_era_topics(const bool sort_generated, const std::string & era_id)
554 {
555  std::vector<topic> topics;
556 
557  const config & era = game_cfg->find_child("era","id", era_id);
558  if(era && !era["hide_help"].to_bool()) {
559  topics = generate_faction_topics(era, sort_generated);
560 
561  std::vector<std::string> faction_links;
562  for (const topic & t : topics) {
563  faction_links.push_back(make_link(t.title, t.id));
564  }
565 
566  std::stringstream text;
567  text << "<header>text='" << _("Era:") << " " << era["name"] << "'</header>" << "\n";
568  text << "\n";
569  const config::attribute_value& description = era["description"];
570  if (!description.empty()) {
571  text << description.t_str() << "\n";
572  text << "\n";
573  }
574 
575  text << "<header>text='" << _("Factions") << "'</header>" << "\n";
576 
577  std::sort(faction_links.begin(), faction_links.end());
578  for (const std::string &link : faction_links) {
579  text << font::unicode_bullet << " " << link << "\n";
580  }
581 
582  topic era_topic(era["name"], ".." + era_prefix + era["id"].str(), text.str());
583 
584  topics.push_back( era_topic );
585  }
586  return topics;
587 }
588 
589 std::vector<topic> generate_faction_topics(const config & era, const bool sort_generated)
590 {
591  std::vector<topic> topics;
592  for (const config &f : era.child_range("multiplayer_side")) {
593  const std::string& id = f["id"];
594  if (id == "Random")
595  continue;
596 
597  std::stringstream text;
598 
599  const config::attribute_value& description = f["description"];
600  if (!description.empty()) {
601  text << description.t_str() << "\n";
602  text << "\n";
603  }
604 
605  const std::vector<std::string> recruit_ids = utils::split(f["recruit"]);
606  std::set<std::string> races;
607  std::set<std::string> alignments;
608 
609  for (const std::string & u_id : recruit_ids) {
610  if (const unit_type * t = unit_types.find(u_id, unit_type::HELP_INDEXED)) {
611  assert(t);
612  const unit_type & type = *t;
613 
614  if (const unit_race *r = unit_types.find_race(type.race_id())) {
615  races.insert(make_link(r->plural_name(), std::string("..") + race_prefix + r->id()));
616  }
617  DBG_HP << type.alignment() << " -> " << type.alignment_description(type.alignment(), type.genders().front()) << "\n";
618  alignments.insert(make_link(type.alignment_description(type.alignment(), type.genders().front()), "time_of_day"));
619  }
620  }
621 
622  if (!races.empty()) {
623  std::set<std::string>::iterator it = races.begin();
624  text << _("Races: ") << *(it++);
625  while(it != races.end()) {
626  text << ", " << *(it++);
627  }
628  text << "\n\n";
629  }
630 
631  if (!alignments.empty()) {
632  std::set<std::string>::iterator it = alignments.begin();
633  text << _("Alignments: ") << *(it++);
634  while(it != alignments.end()) {
635  text << ", " << *(it++);
636  }
637  text << "\n\n";
638  }
639 
640  text << "<header>text='" << _("Leaders") << "'</header>" << "\n";
641  const std::vector<std::string> leaders =
642  make_unit_links_list( utils::split(f["leader"]), true );
643  for (const std::string &link : leaders) {
644  text << font::unicode_bullet << " " << link << "\n";
645  }
646 
647  text << "\n";
648 
649  text << "<header>text='" << _("Recruits") << "'</header>" << "\n";
650  const std::vector<std::string> recruit_links =
651  make_unit_links_list( recruit_ids, true );
652  for (const std::string &link : recruit_links) {
653  text << font::unicode_bullet << " " << link << "\n";
654  }
655 
656  const std::string name = f["name"];
657  const std::string ref_id = faction_prefix + era["id"] + "_" + id;
658  topics.emplace_back(name, ref_id, text.str());
659  }
660  if (sort_generated)
661  std::sort(topics.begin(), topics.end(), title_less());
662  return topics;
663 }
664 
665 std::vector<topic> generate_trait_topics(const bool sort_generated)
666 {
667  std::vector<topic> topics;
668  std::map<t_string, const config> trait_list;
669 
670  for (const config & trait : unit_types.traits()) {
671  const std::string trait_id = trait["id"];
672  trait_list.emplace(trait_id, trait);
673  }
674 
675 
676  for (const unit_type_data::unit_type_map::value_type &i : unit_types.types())
677  {
678  const unit_type &type = i.second;
679  if (description_type(type) == FULL_DESCRIPTION) {
680  if (config::const_child_itors traits = type.possible_traits()) {
681  for (const config & trait : traits) {
682  const std::string trait_id = trait["id"];
683  trait_list.emplace(trait_id, trait);
684  }
685  }
686  if (const unit_race *r = unit_types.find_race(type.race_id())) {
687  for (const config & trait : r->additional_traits()) {
688  const std::string trait_id = trait["id"];
689  trait_list.emplace(trait_id, trait);
690  }
691  }
692  }
693  }
694 
695  for (std::map<t_string, const config>::iterator a = trait_list.begin(); a != trait_list.end(); ++a) {
696  std::string id = "traits_" + a->first;
697  const config trait = a->second;
698 
699  std::string name = trait["male_name"].str();
700  if (name.empty()) name = trait["female_name"].str();
701  if (name.empty()) name = trait["name"].str();
702  if (name.empty()) continue; // Hidden trait
703 
704  std::stringstream text;
705  if (!trait["help_text"].empty()) {
706  text << trait["help_text"];
707  } else if (!trait["description"].empty()) {
708  text << trait["description"];
709  } else {
710  text << _("No description available.");
711  }
712  text << "\n\n";
713  if (trait["availability"] == "musthave") {
714  text << _("Availability: ") << _("Must-have") << "\n";
715  } else if (trait["availability"] == "none") {
716  text << _("Availability: ") << _("Unavailable") << "\n";
717  }
718  topics.emplace_back(name, id, text.str());
719  }
720 
721  if (sort_generated)
722  std::sort(topics.begin(), topics.end(), title_less());
723  return topics;
724 }
725 
726 
728 {
729  std::string link;
730 
732  if (!type) {
733  std::cerr << "Unknown unit type : " << type_id << "\n";
734  // don't return an hyperlink (no page)
735  // instead show the id (as hint)
736  link = type_id;
737  } else if (!type->hide_help()) {
738  std::string name = type->type_name();
739  std::string ref_id;
740  if (description_type(*type) == FULL_DESCRIPTION) {
741  const std::string section_prefix = type->show_variations_in_help() ? ".." : "";
742  ref_id = section_prefix + unit_prefix + type->id();
743  } else {
744  ref_id = unknown_unit_topic;
745  name += " (?)";
746  }
747  link = make_link(name, ref_id);
748  } // if hide_help then link is an empty string
749 
750  return link;
751 }
752 
753 std::vector<std::string> make_unit_links_list(const std::vector<std::string>& type_id_list, bool ordered)
754 {
755  std::vector<std::string> links_list;
756  for (const std::string &type_id : type_id_list) {
757  std::string unit_link = make_unit_link(type_id);
758  if (!unit_link.empty())
759  links_list.push_back(unit_link);
760  }
761 
762  if (ordered)
763  std::sort(links_list.begin(), links_list.end());
764 
765  return links_list;
766 }
767 
768 void generate_races_sections(const config *help_cfg, section &sec, int level)
769 {
770  std::set<std::string, string_less> races;
771  std::set<std::string, string_less> visible_races;
772 
773  for (const unit_type_data::unit_type_map::value_type &i : unit_types.types())
774  {
775  const unit_type &type = i.second;
776  UNIT_DESCRIPTION_TYPE desc_type = description_type(type);
777  if (desc_type == FULL_DESCRIPTION) {
778  races.insert(type.race_id());
779  if (!type.hide_help())
780  visible_races.insert(type.race_id());
781  }
782  }
783 
784  for(std::set<std::string, string_less>::iterator it = races.begin(); it != races.end(); ++it) {
785  section race_section;
786  config section_cfg;
787 
788  bool hidden = (visible_races.count(*it) == 0);
789 
790  section_cfg["id"] = hidden_symbol(hidden) + race_prefix + *it;
791 
792  std::string title;
793  if (const unit_race *r = unit_types.find_race(*it)) {
794  title = r->plural_name();
795  } else {
796  title = _ ("race^Miscellaneous");
797  }
798  section_cfg["title"] = title;
799 
800  section_cfg["sections_generator"] = "units:" + *it;
801  section_cfg["generator"] = "units:" + *it;
802 
803  parse_config_internal(help_cfg, &section_cfg, race_section, level+1);
804  sec.add_section(race_section);
805  }
806 }
807 
808 void generate_era_sections(const config* help_cfg, section & sec, int level)
809 {
810  for (const config & era : game_cfg->child_range("era")) {
811  if (era["hide_help"].to_bool()) {
812  continue;
813  }
814 
815  DBG_HP << "Adding help section: " << era["id"].str() << "\n";
816 
817  section era_section;
818  config section_cfg;
819  section_cfg["id"] = era_prefix + era["id"].str();
820  section_cfg["title"] = era["name"];
821 
822  section_cfg["generator"] = "era:" + era["id"].str();
823 
824  DBG_HP << section_cfg.debug() << "\n";
825 
826  parse_config_internal(help_cfg, &section_cfg, era_section, level+1);
827  sec.add_section(era_section);
828  }
829 }
830 
831 void generate_terrain_sections(const config* /*help_cfg*/, section& sec, int /*level*/)
832 {
834 
835  if (!tdata) {
836  WRN_HP << "When building terrain help sections, couldn't acquire terrain types data, aborting.\n";
837  return;
838  }
839 
840  std::map<std::string, section> base_map;
841 
842  const t_translation::ter_list& t_listi = tdata->list();
843 
844  for (const t_translation::terrain_code& t : t_listi) {
845 
846  const terrain_type& info = tdata->get_terrain_info(t);
847 
848  bool hidden = info.is_combined() || info.hide_help();
849 
851  == preferences::encountered_terrains().end() && !info.is_overlay())
852  hidden = true;
853 
854  topic terrain_topic;
855  terrain_topic.title = info.editor_name();
856  terrain_topic.id = hidden_symbol(hidden) + terrain_prefix + info.id();
857  terrain_topic.text = new terrain_topic_generator(info);
858 
859  t_translation::ter_list base_terrains = tdata->underlying_union_terrain(t);
860  for (const t_translation::terrain_code& base : base_terrains) {
861 
862  const terrain_type& base_info = tdata->get_terrain_info(base);
863 
864  if (!base_info.is_nonnull() || base_info.hide_help())
865  continue;
866 
867  section& base_section = base_map[base_info.id()];
868 
869  base_section.id = terrain_prefix + base_info.id();
870  base_section.title = base_info.editor_name();
871 
872  if (base_info.id() == info.id())
873  terrain_topic.id = ".." + terrain_prefix + info.id();
874  base_section.topics.push_back(terrain_topic);
875  }
876  }
877 
878  for (std::map<std::string, section>::const_iterator it = base_map.begin(); it != base_map.end(); ++it) {
879  sec.add_section(it->second);
880  }
881 }
882 
883 void generate_unit_sections(const config* /*help_cfg*/, section& sec, int level, const bool /*sort_generated*/, const std::string& race)
884 {
885  for (const unit_type_data::unit_type_map::value_type &i : unit_types.types()) {
886  const unit_type &type = i.second;
887 
888  if (type.race_id() != race)
889  continue;
890 
891  if (!type.show_variations_in_help())
892  continue;
893 
894  section base_unit;
895  for (const std::string &variation_id : type.variations()) {
896  // TODO: Do we apply encountered stuff to variations?
897  const unit_type &var_type = type.get_variation(variation_id);
898  const std::string topic_name = var_type.type_name() + "\n" + var_type.variation_name();
899  const std::string var_ref = hidden_symbol(var_type.hide_help()) + variation_prefix + var_type.id() + "_" + variation_id;
900 
901  topic var_topic(topic_name, var_ref, "");
902  var_topic.text = new unit_topic_generator(var_type, variation_id);
903  base_unit.topics.push_back(var_topic);
904  }
905 
906  const std::string type_name = type.type_name();
907  const std::string ref_id = hidden_symbol(type.hide_help()) + unit_prefix + type.id();
908 
909  base_unit.id = ref_id;
910  base_unit.title = type_name;
911  base_unit.level = level +1;
912 
913  sec.add_section(base_unit);
914  }
915 }
916 
917 std::vector<topic> generate_unit_topics(const bool sort_generated, const std::string& race)
918 {
919  std::vector<topic> topics;
920  std::set<std::string, string_less> race_units;
921  std::set<std::string, string_less> race_topics;
922  std::set<std::string> alignments;
923 
924  for (const unit_type_data::unit_type_map::value_type &i : unit_types.types())
925  {
926  const unit_type &type = i.second;
927 
928  if (type.race_id() != race)
929  continue;
930 
931  UNIT_DESCRIPTION_TYPE desc_type = description_type(type);
932  if (desc_type != FULL_DESCRIPTION)
933  continue;
934 
935  const std::string type_name = type.type_name();
936  const std::string real_prefix = type.show_variations_in_help() ? ".." : "";
937  const std::string ref_id = hidden_symbol(type.hide_help()) + real_prefix + unit_prefix + type.id();
938  topic unit_topic(type_name, ref_id, "");
939  unit_topic.text = new unit_topic_generator(type);
940  topics.push_back(unit_topic);
941 
942  if (!type.hide_help()) {
943  // we also record an hyperlink of this unit
944  // in the list used for the race topic
945  std::string link = make_link(type_name, ref_id);
946  race_units.insert(link);
947 
948  alignments.insert(make_link(type.alignment_description(type.alignment(), type.genders().front()), "time_of_day"));
949  }
950  }
951 
952  //generate the hidden race description topic
953  std::string race_id = "..race_"+race;
954  std::string race_name;
955  std::string race_description;
956  if (const unit_race *r = unit_types.find_race(race)) {
957  race_name = r->plural_name();
958  race_description = r->description();
959  // if (description.empty()) description = _("No description Available");
960  for (const config &additional_topic : r->additional_topics())
961  {
962  std::string id = additional_topic["id"];
963  std::string title = additional_topic["title"];
964  std::string text = additional_topic["text"];
965  //topic additional_topic(title, id, text);
966  topics.emplace_back(title,id,text);
967  std::string link = make_link(title, id);
968  race_topics.insert(link);
969  }
970  } else {
971  race_name = _ ("race^Miscellaneous");
972  // description = _("Here put the description of the Miscellaneous race");
973  }
974 
975  std::stringstream text;
976 
977  if (!race_description.empty()) {
978  text << race_description << "\n\n";
979  }
980 
981  if (!alignments.empty()) {
982  std::set<std::string>::iterator it = alignments.begin();
983  text << (alignments.size() > 1 ? _("Alignments: ") : _("Alignment: ")) << *(it++);
984  while(it != alignments.end()) {
985  text << ", " << *(it++);
986  }
987  text << "\n\n";
988  }
989 
990  text << _("<header>text='Units of this race'</header>") << "\n";
991  for (std::set<std::string, string_less>::iterator u = race_units.begin(); u != race_units.end(); ++u) {
992  text << font::unicode_bullet << " " << (*u) << "\n";
993  }
994 
995  topics.emplace_back(race_name, race_id, text.str());
996 
997  if (sort_generated)
998  std::sort(topics.begin(), topics.end(), title_less());
999 
1000  return topics;
1001 }
1002 
1004 {
1007  return FULL_DESCRIPTION;
1008  }
1009 
1010  const std::set<std::string> &encountered_units = preferences::encountered_units();
1011  if (encountered_units.find(type.id()) != encountered_units.end()) {
1012  return FULL_DESCRIPTION;
1013  }
1014  return NO_DESCRIPTION;
1015 }
1016 
1018 {
1019  /*std::vector<std::string> about_lines = about::get_text();
1020  std::vector<std::string> res_lines;
1021  std::transform(about_lines.begin(), about_lines.end(), std::back_inserter(res_lines),
1022  about_text_formatter());
1023  res_lines.erase(std::remove(res_lines.begin(), res_lines.end(), ""), res_lines.end());
1024  std::string text = utils::join(res_lines, "\n");
1025  return text;*/
1026  return "";
1027 }
1028 
1029 std::string generate_contents_links(const std::string& section_name, config const *help_cfg)
1030 {
1031  const config& section_cfg = help_cfg->find_child("section", "id", section_name);
1032  if (!section_cfg) {
1033  return std::string();
1034  }
1035 
1036  std::ostringstream res;
1037 
1038  std::vector<std::string> topics = utils::quoted_split(section_cfg["topics"]);
1039 
1040  // we use an intermediate structure to allow a conditional sorting
1041  typedef std::pair<std::string,std::string> link;
1042  std::vector<link> topics_links;
1043 
1045  // Find all topics in this section.
1046  for (t = topics.begin(); t != topics.end(); ++t) {
1047  if (const config& topic_cfg = help_cfg->find_child("topic", "id", *t)) {
1048  std::string id = topic_cfg["id"];
1049  if (is_visible_id(id))
1050  topics_links.emplace_back(topic_cfg["title"], id);
1051  }
1052  }
1053 
1054  if (section_cfg["sort_topics"] == "yes") {
1055  std::sort(topics_links.begin(),topics_links.end());
1056  }
1057 
1059  for (l = topics_links.begin(); l != topics_links.end(); ++l) {
1060  std::string link = make_link(l->first, l->second);
1061  res << font::unicode_bullet << " " << link << "\n";
1062  }
1063 
1064  return res.str();
1065 }
1066 
1067 std::string generate_contents_links(const section &sec, const std::vector<topic>& topics)
1068 {
1069  std::stringstream res;
1070 
1071  section_list::const_iterator s;
1072  for (s = sec.sections.begin(); s != sec.sections.end(); ++s) {
1073  if (is_visible_id((*s)->id)) {
1074  std::string link = make_link((*s)->title, ".."+(*s)->id);
1075  res << font::unicode_bullet << " " << link << "\n";
1076  }
1077  }
1078 
1079  std::vector<topic>::const_iterator t;
1080  for (t = topics.begin(); t != topics.end(); ++t) {
1081  if (is_visible_id(t->id)) {
1082  std::string link = make_link(t->title, t->id);
1083  res << font::unicode_bullet << " " << link << "\n";
1084  }
1085  }
1086  return res.str();
1087 }
1088 
1089 bool topic::operator==(const topic &t) const
1090 {
1091  return t.id == id;
1092 }
1093 
1094 bool topic::operator<(const topic &t) const
1095 {
1096  return id < t.id;
1097 }
1098 
1100 {
1101  std::for_each(sections.begin(), sections.end(), delete_section());
1102 }
1103 
1105  title(sec.title),
1106  id(sec.id),
1107  topics(sec.topics),
1108  sections(),
1109  level(sec.level)
1110 {
1111  std::transform(sec.sections.begin(), sec.sections.end(),
1112  std::back_inserter(sections), create_section());
1113 }
1114 
1116 {
1117  title = sec.title;
1118  id = sec.id;
1119  level = sec.level;
1120  topics.insert(topics.end(), sec.topics.begin(), sec.topics.end());
1121  std::transform(sec.sections.begin(), sec.sections.end(),
1122  std::back_inserter(sections), create_section());
1123  return *this;
1124 }
1125 
1126 
1127 bool section::operator==(const section &sec) const
1128 {
1129  return sec.id == id;
1130 }
1131 
1132 bool section::operator<(const section &sec) const
1133 {
1134  return id < sec.id;
1135 }
1136 
1138 {
1139  sections.push_back(new section(s));
1140 }
1141 
1143 {
1144  topics.clear();
1145  std::for_each(sections.begin(), sections.end(), delete_section());
1146  sections.clear();
1147 }
1148 
1149 
1150 
1151 const topic *find_topic(const section &sec, const std::string &id)
1152 {
1153  topic_list::const_iterator tit =
1154  std::find_if(sec.topics.begin(), sec.topics.end(), has_id(id));
1155  if (tit != sec.topics.end()) {
1156  return &(*tit);
1157  }
1158  section_list::const_iterator sit;
1159  for (sit = sec.sections.begin(); sit != sec.sections.end(); ++sit) {
1160  const topic *t = find_topic(*(*sit), id);
1161  if (t != nullptr) {
1162  return t;
1163  }
1164  }
1165  return nullptr;
1166 }
1167 
1168 const section *find_section(const section &sec, const std::string &id)
1169 {
1170  section_list::const_iterator sit =
1171  std::find_if(sec.sections.begin(), sec.sections.end(), has_id(id));
1172  if (sit != sec.sections.end()) {
1173  return *sit;
1174  }
1175  for (sit = sec.sections.begin(); sit != sec.sections.end(); ++sit) {
1176  const section *s = find_section(*(*sit), id);
1177  if (s != nullptr) {
1178  return s;
1179  }
1180  }
1181  return nullptr;
1182 }
1183 
1184 std::vector<std::string> parse_text(const std::string &text)
1185 {
1186  std::vector<std::string> res;
1187  bool last_char_escape = false;
1188  const char escape_char = '\\';
1189  std::stringstream ss;
1190  size_t pos;
1191  enum { ELEMENT_NAME, OTHER } state = OTHER;
1192  for (pos = 0; pos < text.size(); ++pos) {
1193  const char c = text[pos];
1194  if (c == escape_char && !last_char_escape) {
1195  last_char_escape = true;
1196  }
1197  else {
1198  if (state == OTHER) {
1199  if (c == '<') {
1200  if (last_char_escape) {
1201  ss << c;
1202  }
1203  else {
1204  res.push_back(ss.str());
1205  ss.str("");
1206  state = ELEMENT_NAME;
1207  }
1208  }
1209  else {
1210  ss << c;
1211  }
1212  }
1213  else if (state == ELEMENT_NAME) {
1214  if (c == '/') {
1215  std::string msg = "Erroneous / in element name.";
1216  throw parse_error(msg);
1217  }
1218  else if (c == '>') {
1219  // End of this name.
1220  std::stringstream s;
1221  const std::string element_name = ss.str();
1222  ss.str("");
1223  s << "</" << element_name << ">";
1224  const std::string end_element_name = s.str();
1225  size_t end_pos = text.find(end_element_name, pos);
1226  if (end_pos == std::string::npos) {
1227  std::stringstream msg;
1228  msg << "Unterminated element: " << element_name;
1229  throw parse_error(msg.str());
1230  }
1231  s.str("");
1232  const std::string contents = text.substr(pos + 1, end_pos - pos - 1);
1233  const std::string element = convert_to_wml(element_name, contents);
1234  res.push_back(element);
1235  pos = end_pos + end_element_name.size() - 1;
1236  state = OTHER;
1237  }
1238  else {
1239  ss << c;
1240  }
1241  }
1242  last_char_escape = false;
1243  }
1244  }
1245  if (state == ELEMENT_NAME) {
1246  std::stringstream msg;
1247  msg << "Element '" << ss.str() << "' continues through end of string.";
1248  throw parse_error(msg.str());
1249  }
1250  if (!ss.str().empty()) {
1251  // Add the last string.
1252  res.push_back(ss.str());
1253  }
1254  return res;
1255 }
1256 
1257 std::string convert_to_wml(const std::string &element_name, const std::string &contents)
1258 {
1259  std::stringstream ss;
1260  bool in_quotes = false;
1261  bool last_char_escape = false;
1262  const char escape_char = '\\';
1263  std::vector<std::string> attributes;
1264  // Find the different attributes.
1265  // No checks are made for the equal sign or something like that.
1266  // Attributes are just separated by spaces or newlines.
1267  // Attributes that contain spaces must be in single quotes.
1268  for (size_t pos = 0; pos < contents.size(); ++pos) {
1269  const char c = contents[pos];
1270  if (c == escape_char && !last_char_escape) {
1271  last_char_escape = true;
1272  }
1273  else {
1274  if (c == '\'' && !last_char_escape) {
1275  ss << '"';
1276  in_quotes = !in_quotes;
1277  }
1278  else if ((c == ' ' || c == '\n') && !last_char_escape && !in_quotes) {
1279  // Space or newline, end of attribute.
1280  attributes.push_back(ss.str());
1281  ss.str("");
1282  }
1283  else {
1284  ss << c;
1285  }
1286  last_char_escape = false;
1287  }
1288  }
1289  if (in_quotes) {
1290  std::stringstream msg;
1291  msg << "Unterminated single quote after: '" << ss.str() << "'";
1292  throw parse_error(msg.str());
1293  }
1294  if (!ss.str().empty()) {
1295  attributes.push_back(ss.str());
1296  }
1297  ss.str("");
1298  // Create the WML.
1299  ss << "[" << element_name << "]\n";
1300  for (std::vector<std::string>::const_iterator it = attributes.begin();
1301  it != attributes.end(); ++it) {
1302  ss << *it << "\n";
1303  }
1304  ss << "[/" << element_name << "]\n";
1305  return ss.str();
1306 }
1307 
1309 {
1310  if (cmp_str == "green") {
1311  return font::GOOD_COLOR;
1312  }
1313  if (cmp_str == "red") {
1314  return font::BAD_COLOR;
1315  }
1316  if (cmp_str == "black") {
1317  return font::BLACK_COLOR;
1318  }
1319  if (cmp_str == "yellow") {
1320  return font::YELLOW_COLOR;
1321  }
1322  if (cmp_str == "white") {
1323  return font::BIGMAP_COLOR;
1324  }
1325  // a #rrggbb color in pango format.
1326  if (*cmp_str.c_str() == '#' && cmp_str.size() == 7) {
1327  return color_t::from_argb_bytes(strtoul(cmp_str.c_str() + 1, nullptr, 16));
1328  }
1329  return font::NORMAL_COLOR;
1330 }
1331 
1332 std::vector<std::string> split_in_width(const std::string &s, const int font_size,
1333  const unsigned width)
1334 {
1335  std::vector<std::string> res;
1336  try {
1337  const std::string& first_line = font::word_wrap_text(s, font_size, width, -1, 1, true);
1338  res.push_back(first_line);
1339  if(s.size() > first_line.size()) {
1340  res.push_back(s.substr(first_line.size()));
1341  }
1342  }
1344  {
1345  throw parse_error (_("corrupted original file"));
1346  }
1347 
1348  return res;
1349 }
1350 
1352 {
1353  if (text.length() > 0 && text[0] == ' ') {
1354  return text.substr(1);
1355  }
1356  return text;
1357 }
1358 
1360 {
1361  size_t first_word_start = s.find_first_not_of(' ');
1362  if (first_word_start == std::string::npos) {
1363  return s;
1364  }
1365  size_t first_word_end = s.find_first_of(" \n", first_word_start);
1366  if( first_word_end == first_word_start ) {
1367  // This word is '\n'.
1368  first_word_end = first_word_start+1;
1369  }
1370 
1371  //if no gap(' ' or '\n') found, test if it is CJK character
1372  std::string re = s.substr(0, first_word_end);
1373 
1374  utf8::iterator ch(re);
1375  if (ch == utf8::iterator::end(re))
1376  return re;
1377 
1378  ucs4::char_t firstchar = *ch;
1379  if (font::is_cjk_char(firstchar)) {
1380  re = unicode_cast<utf8::string>(firstchar);
1381  }
1382  return re;
1383 }
1384 
1386 {
1387  default_toplevel.clear();
1388  hidden_sections.clear();
1389  if (game_cfg != nullptr) {
1390  const config *help_config = &game_cfg->child("help");
1391  if (!*help_config) {
1392  help_config = &dummy_cfg;
1393  }
1394  try {
1395  default_toplevel = parse_config(help_config);
1396  // Create a config object that contains everything that is
1397  // not referenced from the toplevel element. Read this
1398  // config and save these sections and topics so that they
1399  // can be referenced later on when showing help about
1400  // specified things, but that should not be shown when
1401  // opening the help browser in the default manner.
1402  config hidden_toplevel;
1403  std::stringstream ss;
1404  for (const config &section : help_config->child_range("section"))
1405  {
1406  const std::string id = section["id"];
1407  if (find_section(default_toplevel, id) == nullptr) {
1408  // This section does not exist referenced from the
1409  // toplevel. Hence, add it to the hidden ones if it
1410  // is not referenced from another section.
1411  if (!section_is_referenced(id, *help_config)) {
1412  if (!ss.str().empty()) {
1413  ss << ",";
1414  }
1415  ss << id;
1416  }
1417  }
1418  }
1419  hidden_toplevel["sections"] = ss.str();
1420  ss.str("");
1421  for (const config &topic : help_config->child_range("topic"))
1422  {
1423  const std::string id = topic["id"];
1424  if (find_topic(default_toplevel, id) == nullptr) {
1425  if (!topic_is_referenced(id, *help_config)) {
1426  if (!ss.str().empty()) {
1427  ss << ",";
1428  }
1429  ss << id;
1430  }
1431  }
1432  }
1433  hidden_toplevel["topics"] = ss.str();
1434  config hidden_cfg = *help_config;
1435  // Change the toplevel to our new, custom built one.
1436  hidden_cfg.clear_children("toplevel");
1437  hidden_cfg.add_child("toplevel", std::move(hidden_toplevel));
1438  hidden_sections = parse_config(&hidden_cfg);
1439  }
1440  catch (parse_error& e) {
1441  std::stringstream msg;
1442  msg << "Parse error when parsing help text: '" << e.message << "'";
1443  std::cerr << msg.str() << std::endl;
1444  }
1445  }
1446 }
1447 
1448 // id starting with '.' are hidden
1450  return (hidden ? "." : "");
1451 }
1452 
1453 bool is_visible_id(const std::string &id) {
1454  return (id.empty() || id[0] != '.');
1455 }
1456 
1457 /// Return true if the id is valid for user defined topics and
1458 /// sections. Some IDs are special, such as toplevel and may not be
1459 /// be defined in the config.
1460 bool is_valid_id(const std::string &id) {
1461  if (id == "toplevel") {
1462  return false;
1463  }
1464  if (id.compare(0, unit_prefix.length(), unit_prefix) == 0 || id.compare(hidden_symbol().length(), unit_prefix.length(), unit_prefix) == 0) {
1465  return false;
1466  }
1467  if (id.compare(0, 8, "ability_") == 0) {
1468  return false;
1469  }
1470  if (id.compare(0, 14, "weaponspecial_") == 0) {
1471  return false;
1472  }
1473  if (id == "hidden") {
1474  return false;
1475  }
1476  return true;
1477 }
1478 
1479 
1480 // Return the width for the image with filename.
1481 unsigned image_width(const std::string &filename)
1482 {
1483  image::locator loc(filename);
1484  surface surf(image::get_image(loc));
1485  if (surf != nullptr) {
1486  return surf->w;
1487  }
1488  return 0;
1489 }
1490 
1491 void push_tab_pair(std::vector<std::pair<std::string, unsigned int>> &v, const std::string &s)
1492 {
1493  v.emplace_back(s, font::line_width(s, normal_font_size));
1494 }
1495 
1496 std::string generate_table(const table_spec &tab, const unsigned int spacing)
1497 {
1498  table_spec::const_iterator row_it;
1499  std::vector<std::pair<std::string, unsigned>>::const_iterator col_it;
1500  unsigned int num_cols = 0;
1501  for (row_it = tab.begin(); row_it != tab.end(); ++row_it) {
1502  if (row_it->size() > num_cols) {
1503  num_cols = row_it->size();
1504  }
1505  }
1506  std::vector<unsigned int> col_widths(num_cols, 0);
1507  // Calculate the width of all columns, including spacing.
1508  for (row_it = tab.begin(); row_it != tab.end(); ++row_it) {
1509  unsigned int col = 0;
1510  for (col_it = row_it->begin(); col_it != row_it->end(); ++col_it) {
1511  if (col_widths[col] < col_it->second + spacing) {
1512  col_widths[col] = col_it->second + spacing;
1513  }
1514  ++col;
1515  }
1516  }
1517  std::vector<unsigned int> col_starts(num_cols);
1518  // Calculate the starting positions of all columns
1519  for (unsigned int i = 0; i < num_cols; ++i) {
1520  unsigned int this_col_start = 0;
1521  for (unsigned int j = 0; j < i; ++j) {
1522  this_col_start += col_widths[j];
1523  }
1524  col_starts[i] = this_col_start;
1525  }
1526  std::stringstream ss;
1527  for (row_it = tab.begin(); row_it != tab.end(); ++row_it) {
1528  unsigned int col = 0;
1529  for (col_it = row_it->begin(); col_it != row_it->end(); ++col_it) {
1530  ss << jump_to(col_starts[col]) << col_it->first;
1531  ++col;
1532  }
1533  ss << "\n";
1534  }
1535  return ss.str();
1536 }
1537 
1538 /// Prepend all chars with meaning inside attributes with a backslash.
1540 {
1541  return utils::escape(s, "'\\");
1542 }
1543 
1544 /// Load the appropriate terrain types data to use
1546  if (display::get_singleton()) {
1548  } else if (game_config_manager::get()){
1550  } else {
1551  return ter_data_cache();
1552  }
1553 }
1554 
1555 
1556 } // end namespace help
std::string jump_to(const unsigned pos)
Definition: help_impl.hpp:382
surface get_image(const image::locator &i_locator, TYPE type)
function to get the surface corresponding to an image.
Definition: image.cpp:920
const std::string ability_prefix
Definition: help_impl.cpp:95
section parse_config(const config *cfg)
Parse a help config, return the top level section.
Definition: help_impl.cpp:252
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:419
std::string id
Definition: help_impl.hpp:164
::tod_manager * tod_manager
Definition: resources.cpp:29
std::string make_unit_link(const std::string &type_id)
return a hyperlink with the unit's name and pointing to the unit page return empty string if this uni...
Definition: help_impl.cpp:727
const std::string open_section_img
Definition: help_impl.cpp:85
std::vector< char_t > string
static display * get_singleton()
Returns the display object if a display object exists.
Definition: display.hpp:87
std::vector< topic > generate_trait_topics(const bool sort_generated)
Definition: help_impl.cpp:665
const std::string unit_prefix
Definition: help_impl.cpp:89
config dummy_cfg
Definition: help_impl.cpp:75
std::vector< topic > generate_unit_topics(const bool sort_generated, const std::string &race)
Definition: help_impl.cpp:917
const std::string era_prefix
Definition: help_impl.cpp:93
void generate_unit_sections(const config *, section &sec, int level, const bool, const std::string &race)
Definition: help_impl.cpp:883
const std::string topic_img
Definition: help_impl.cpp:83
const t_string & type_name() const
The name of the unit in the current language setting.
Definition: types.hpp:136
std::string era()
Definition: game.cpp:700
const int title_size
Definition: help_impl.cpp:78
bool operator==(const section &) const
Two sections are equal if their IDs are equal.
Definition: help_impl.cpp:1127
const std::vector< std::string > & parsed_text() const
Definition: help_impl.cpp:350
const std::string closed_section_img
Definition: help_impl.cpp:84
std::string remove_first_space(const std::string &text)
Definition: help_impl.cpp:1351
bool is_scope_active(scope s)
A section contains topics and sections along with title and ID.
Definition: help_impl.hpp:142
config::const_child_itors modification_advancements() const
Returns two iterators pointing to a range of AMLA configs.
Definition: types.hpp:118
config & find_child(config_key_type key, const std::string &name, const std::string &value)
Returns the first child of tag key with a name attribute containing value.
Definition: config.cpp:782
const std::string unknown_unit_topic
Definition: help_impl.cpp:88
Variant for storing WML attributes.
const std::string race_prefix
Definition: help_impl.cpp:91
const color_t GOOD_COLOR
std::string generate_topic_text(const std::string &generator, const config *help_cfg, const section &sec, const std::vector< topic > &generated_topics)
Definition: help_impl.cpp:310
logger & info()
Definition: log.cpp:91
#define a
#define WRN_HP
Definition: help_impl.cpp:60
int compare(const std::string &s1, const std::string &s2)
Case-sensitive lexicographical comparison.
bool empty() const
Tests for an attribute that either was never set or was set to "".
const_attack_itors attacks() const
Definition: types.cpp:495
ucs4_convert_impl::enableif< TD, typename TS::value_type >::type unicode_cast(const TS &source)
const std::string & variation_name() const
Definition: types.hpp:160
child_itors child_range(config_key_type key)
Definition: config.cpp:362
Thrown when the help system fails to parse something.
Definition: help_impl.hpp:220
std::vector< std::string > empty_string_vector
Definition: help_impl.cpp:76
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.
A terrain string which is converted to a terrain is a string with 1 or 2 layers the layers are separa...
Definition: translation.hpp:49
std::vector< std::string > quoted_split(const std::string &val, char c, int flags, char quote)
This function is identical to split(), except it does not split when it otherwise would if the previo...
bool is_overlay() const
Definition: terrain.hpp:79
unit_type_data unit_types
Definition: types.cpp:1453
const std::string & id() const
Definition: terrain.hpp:37
bool operator<(const topic &) const
Comparison on the ID.
Definition: help_impl.cpp:1094
static void msg(const char *act, debug_info &i, const char *to="", const char *result="")
Definition: debugger.cpp:109
bool is_combined() const
Definition: terrain.hpp:80
const int normal_font_size
Definition: help_impl.cpp:81
std::string debug() const
Definition: config.cpp:1211
topic_generator * generator_
Definition: help_impl.hpp:83
unsigned image_width(const std::string &filename)
Definition: help_impl.cpp:1481
uint32_t char_t
const std::string terrain_prefix
Definition: help_impl.cpp:90
int last_num_encountered_terrains
Definition: help_impl.cpp:72
const config * game_cfg
Definition: help_impl.cpp:65
const std::vector< ability_metadata > & abilities_metadata() const
Definition: types.hpp:209
color_t string_to_color(const std::string &cmp_str)
Return the color the string represents.
Definition: help_impl.cpp:1308
bool is_visible_id(const std::string &id)
Definition: help_impl.cpp:1453
const t_string & editor_name() const
Definition: terrain.hpp:34
virtual const gamemap & map() const =0
std::string generate_about_text()
Definition: help_impl.cpp:1017
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.
std::vector< topic > generate_weapon_special_topics(const bool sort_generated)
Definition: help_impl.cpp:393
std::vector< std::string > make_unit_links_list(const std::vector< std::string > &type_id_list, bool ordered)
return a list of hyperlinks to unit's pages (ordered or not)
Definition: help_impl.cpp:753
A single unit type that the player may recruit.
Definition: types.hpp:42
void generate_sections(const config *help_cfg, const std::string &generator, section &sec, int level)
Dispatch generators to their appropriate functions.
Definition: help_impl.cpp:291
const int SIZE_NORMAL
Definition: constants.cpp:19
help::section hidden_sections
Definition: help_impl.cpp:69
section_list sections
Definition: help_impl.hpp:166
const section * find_section(const section &sec, const std::string &id)
Search for the section with the specified identifier in the section and its subsections.
Definition: help_impl.cpp:1168
std::string id
Definition: help_impl.hpp:135
Object which defines a time of day with associated bonuses, image, sounds etc.
Definition: time_of_day.hpp:57
To be used as a function object to locate sections and topics with a specified ID.
Definition: help_impl.hpp:173
static game_config_manager * get()
static UNUSEDNOWARN std::string _(const char *str)
Definition: gettext.hpp:89
const color_t BIGMAP_COLOR
const unit_race * find_race(const std::string &) const
Definition: types.cpp:1421
The text displayed in a topic.
Definition: help_impl.hpp:80
std::string generate_table(const table_spec &tab, const unsigned int spacing)
Definition: help_impl.cpp:1496
std::string hidden_symbol(bool hidden)
Definition: help_impl.cpp:1449
const unit_type_map & types() const
Definition: types.hpp:359
std::string title
Definition: help_impl.hpp:164
void clear_children(T...keys)
Definition: config.hpp:510
const int SIZE_15
Definition: constants.cpp:25
std::vector< topic > generate_era_topics(const bool sort_generated, const std::string &era_id)
Definition: help_impl.cpp:553
std::vector< std::vector< std::pair< std::string, unsigned int > > > table_spec
Definition: help_impl.hpp:403
REMOVE_EMPTY: remove empty elements.
UNIT_DESCRIPTION_TYPE
Definition: help_impl.hpp:249
void generate_terrain_sections(const config *, section &sec, int)
Definition: help_impl.cpp:831
std::vector< topic > generate_faction_topics(const config &era, const bool sort_generated)
Definition: help_impl.cpp:589
const std::vector< unit_race::GENDER > & genders() const
The returned vector will not be empty, provided this has been built to the HELP_INDEXED status...
Definition: types.hpp:239
bool show_variations_in_help() const
Whether the unit type has at least one help-visible variation.
Definition: types.cpp:787
std::set< t_translation::terrain_code > & encountered_terrains()
Definition: game.cpp:946
map_display and display: classes which take care of displaying the map and game-data on the screen...
std::vector< std::string > parsed_text_
Definition: help_impl.hpp:82
ter_data_cache load_terrain_types_data()
Load the appropriate terrain types data to use.
Definition: help_impl.cpp:1545
static lg::log_domain log_help("help")
const color_t YELLOW_COLOR
bool show_all_units_in_help()
Definition: game.cpp:934
bool is_nonnull() const
Definition: terrain.hpp:52
const color_t NORMAL_COLOR
bool hide_help() const
Definition: terrain.hpp:39
std::string race_id() const
Returns the ID of this type's race without the need to build the type.
Definition: types.hpp:262
void parse_config_internal(const config *help_cfg, const config *section_cfg, section &sec, int level)
Recursive function used by parse_config.
Definition: help_impl.cpp:145
static color_t from_argb_bytes(uint32_t c)
Creates a new color_t object from a uint32_t variable.
Definition: color.cpp:87
const int box_width
Definition: help_impl.cpp:80
std::string escape(const std::string &str, const char *special_chars)
Prepends a configurable set of characters with a backslash.
boost::iterator_range< const_child_iterator > const_child_itors
Definition: config.hpp:239
bool section_is_referenced(const std::string &section_id, const config &cfg)
Return true if the section with id section_id is referenced from another section in the config...
Definition: help_impl.cpp:97
bool topic_is_referenced(const std::string &topic_id, const config &cfg)
Return true if the topic with id topic_id is referenced from another section in the config...
Definition: help_impl.cpp:121
To be used as a function object when sorting section lists on the title.
Definition: help_impl.hpp:193
const color_t BLACK_COLOR
const std::string variation_prefix
Definition: help_impl.cpp:94
Thrown by operations encountering invalid UTF-8 data.
topic_text & operator=(topic_generator *g)
Definition: help_impl.cpp:342
const unit_type & get_variation(const std::string &id) const
Definition: types.cpp:467
config::const_child_itors possible_traits() const
Definition: types.hpp:223
int last_num_encountered_units
Definition: help_impl.cpp:71
std::string make_link(const std::string &text, const std::string &dst)
Definition: help_impl.hpp:376
std::string convert_to_wml(const std::string &element_name, const std::string &contents)
Convert the contents to wml attributes, surrounded within [element_name]...[/element_name].
Definition: help_impl.cpp:1257
static map_location::DIRECTION s
bool is_cjk_char(const ucs4::char_t ch)
Determine if a ucs4::char_t is a CJK character.
double g
Definition: astarsearch.cpp:64
size_t i
Definition: function.cpp:933
std::vector< std::string > variations() const
Definition: types.cpp:770
std::set< std::string > & encountered_units()
Definition: game.cpp:942
void generate_races_sections(const config *help_cfg, section &sec, int level)
Definition: help_impl.cpp:768
#define N_(String)
Definition: gettext.hpp:97
const std::string unicode_bullet
Definition: constants.cpp:42
static int sort(lua_State *L)
Definition: ltablib.cpp:411
bool hide_help() const
Definition: types.cpp:588
static iterator_base end(const string_type &str)
rng * generator
This generator is automatically synced during synced context.
Definition: random.cpp:57
std::string escape(const std::string &s)
Prepend all chars with meaning inside attributes with a backslash.
Definition: help_impl.cpp:1539
std::vector< std::string > parse_text(const std::string &text)
Parse a text string.
Definition: help_impl.cpp:1184
config & add_child(config_key_type key)
Definition: config.cpp:475
const config::const_child_itors traits() const
Definition: types.hpp:361
const unsigned max_history
Definition: help_impl.cpp:82
const int SIZE_LARGE
Definition: constants.cpp:27
std::vector< topic > generate_time_of_day_topics(const bool)
Definition: help_impl.cpp:361
static lg::log_domain log_display("display")
#define DBG_HP
Definition: help_impl.cpp:61
std::string generate_contents_links(const std::string &section_name, config const *help_cfg)
Definition: help_impl.cpp:1029
const std::string faction_prefix
Definition: help_impl.cpp:92
std::string jump(const unsigned amount)
Definition: help_impl.hpp:389
#define f
double t
Definition: astarsearch.cpp:64
const std::string default_show_topic
Definition: help_impl.cpp:87
void generate_contents()
Generate the help contents from the configurations given to the manager.
Definition: help_impl.cpp:1385
bool find(E event, F functor)
Tests whether an event handler is available.
std::vector< topic > generate_topics(const bool sort_generated, const std::string &generator)
Definition: help_impl.cpp:262
const topic * find_topic(const section &sec, const std::string &id)
Search for the topic with the specified identifier in the section and its subsections.
Definition: help_impl.cpp:1151
const ter_data_cache & tdata() const
Definition: map.hpp:67
const std::vector< time_of_day > & times(const map_location &loc=map_location::null_location()) const
bool operator<(const section &) const
Comparison on the ID.
Definition: help_impl.cpp:1132
this module manages the cache of images.
Definition: image.cpp:103
Standard logging facilities (interface).
std::vector< terrain_code > ter_list
Definition: translation.hpp:77
Generate a topic text on the fly.
Definition: help_impl.hpp:61
std::string message
Definition: exceptions.hpp:31
const int title2_size
Definition: help_impl.cpp:79
A topic contains a title, an id and some text.
Definition: help_impl.hpp:110
bool last_debug_state
Definition: help_impl.cpp:73
static const char * name(const std::vector< SDL_Joystick * > &joysticks, const size_t index)
Definition: joystick.cpp:48
const display_context & get_disp_context() const
Definition: display.hpp:167
const unit_type * find(const std::string &key, unit_type::BUILD_STATUS status=unit_type::FULL) const
Finds a unit_type by its id() and makes sure it is built to the specified level.
Definition: types.cpp:1276
#define e
Definition: help.cpp:56
std::string title
Definition: help_impl.hpp:135
bool is_valid_id(const std::string &id)
Return true if the id is valid for user defined topics and sections.
Definition: help_impl.cpp:1460
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:93
static std::string alignment_description(ALIGNMENT align, unit_race::GENDER gender=unit_race::MALE)
mock_char c
std::string get_first_word(const std::string &s)
Return the first word in s, not removing any spaces in the start of it.
Definition: help_impl.cpp:1359
const int font_size
bool operator==(const topic &) const
Two topics are equal if their IDs are equal.
Definition: help_impl.cpp:1089
void add_section(const section &s)
Allocate memory for and add the section.
Definition: help_impl.cpp:1137
UNIT_DESCRIPTION_TYPE description_type(const unit_type &type)
Return the type of description that should be shown for a unit of the given kind. ...
Definition: help_impl.cpp:1003
std::vector< topic > generate_ability_topics(const bool sort_generated)
Definition: help_impl.cpp:490
void generate_era_sections(const config *help_cfg, section &sec, int level)
Definition: help_impl.cpp:808
const ter_data_cache & terrain_types() const
Defines the MAKE_ENUM macro.
int line_width(const std::string &line, int font_size, int style)
Determine the width of a line of text given a certain font size.
Definition: sdl_ttf.cpp:416
std::string::const_iterator iterator
Definition: tokenizer.hpp:24
const int max_section_level
Definition: help_impl.cpp:77
const std::vector< ability_metadata > & adv_abilities_metadata() const
Some extra abilities that may be gained through AMLA advancements.
Definition: types.hpp:212
To be used as a function object when sorting topic lists on the title.
Definition: help_impl.hpp:185
std::vector< std::string > split_in_width(const std::string &s, const int font_size, const unsigned width)
Make a best effort to word wrap s. All parts are less than width.
Definition: help_impl.cpp:1332
const std::string & id() const
The id for this unit_type.
Definition: types.hpp:139
void push_tab_pair(std::vector< std::pair< std::string, unsigned int >> &v, const std::string &s)
Definition: help_impl.cpp:1491
topic_list topics
Definition: help_impl.hpp:165
topic_text text
Definition: help_impl.hpp:136
std::string string
const color_t BAD_COLOR
section & operator=(const section &)
Definition: help_impl.cpp:1115
std::shared_ptr< terrain_type_data > ter_data_cache
help::section default_toplevel
Definition: help_impl.cpp:67