00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021 #define GETTEXT_DOMAIN "wesnoth-help"
00022
00023 #include "global.hpp"
00024
00025 #include "help.hpp"
00026
00027 #include "about.hpp"
00028 #include "display.hpp"
00029 #include "exceptions.hpp"
00030 #include "foreach.hpp"
00031 #include "game_preferences.hpp"
00032 #include "gettext.hpp"
00033 #include "gui/dialogs/transient_message.hpp"
00034 #include "language.hpp"
00035 #include "log.hpp"
00036 #include "map.hpp"
00037 #include "marked-up_text.hpp"
00038 #include "sound.hpp"
00039 #include "unit.hpp"
00040 #include "wml_separators.hpp"
00041 #include "serialization/parser.hpp"
00042
00043 #include <queue>
00044
00045 static lg::log_domain log_display("display");
00046 #define WRN_DP LOG_STREAM(warn, log_display)
00047
00048 namespace help {
00049
00050 help_button::help_button(display& disp, const std::string &help_topic)
00051 : dialog_button(disp.video(), _("Help")), disp_(disp), topic_(help_topic), help_hand_(NULL)
00052 {}
00053
00054 help_button::~help_button() {
00055 delete help_hand_;
00056 }
00057
00058 int help_button::action(gui::dialog_process_info &info) {
00059 if(!topic_.empty()) {
00060 show_help();
00061 info.clear_buttons();
00062 }
00063 return gui::CONTINUE_DIALOG;
00064 }
00065
00066 void help_button::show_help()
00067 {
00068 help::show_help(disp_, topic_);
00069 }
00070
00071 bool help_button::can_execute_command(hotkey::HOTKEY_COMMAND cmd, int) const
00072 {
00073 return (topic_.empty() == false && cmd == hotkey::HOTKEY_HELP) || cmd == hotkey::HOTKEY_SCREENSHOT;
00074 }
00075
00076 void help_button::join() {
00077 dialog_button::join();
00078
00079
00080 delete help_hand_;
00081 help_hand_ = new hotkey::basic_handler(&disp_, this);
00082 }
00083
00084 void help_button::leave() {
00085 dialog_button::leave();
00086
00087
00088 delete help_hand_;
00089 help_hand_ = NULL;
00090 }
00091
00092
00093
00094 static void generate_contents();
00095
00096 struct section;
00097
00098 typedef std::vector<section *> section_list;
00099
00100
00101 class topic_generator
00102 {
00103 unsigned count;
00104 friend class topic_text;
00105 public:
00106 topic_generator(): count(1) {}
00107 virtual std::string operator()() const = 0;
00108 virtual ~topic_generator() {}
00109 };
00110
00111 class text_topic_generator: public topic_generator {
00112 std::string text_;
00113 public:
00114 text_topic_generator(std::string const &t): text_(t) {}
00115 virtual std::string operator()() const { return text_; }
00116 };
00117
00118
00119
00120 class topic_text
00121 {
00122 mutable std::vector< std::string > parsed_text_;
00123 mutable topic_generator *generator_;
00124 public:
00125 ~topic_text();
00126 topic_text():
00127 parsed_text_(),
00128 generator_(NULL)
00129 {
00130 }
00131
00132 topic_text(std::string const &t):
00133 parsed_text_(),
00134 generator_(new text_topic_generator(t))
00135 {
00136 }
00137
00138 explicit topic_text(topic_generator *g):
00139 parsed_text_(),
00140 generator_(g)
00141 {
00142 }
00143 topic_text &operator=(topic_generator *g);
00144 topic_text(topic_text const &t);
00145
00146 const std::vector<std::string>& parsed_text() const;
00147 };
00148
00149
00150 struct topic
00151 {
00152 topic() :
00153 title(),
00154 id(),
00155 text()
00156 {
00157 }
00158
00159 topic(const std::string &_title, const std::string &_id) :
00160 title(_title),
00161 id(_id),
00162 text()
00163 {
00164 }
00165
00166 topic(const std::string &_title, const std::string &_id, const std::string &_text)
00167 : title(_title), id(_id), text(_text) {}
00168 topic(const std::string &_title, const std::string &_id, topic_generator *g)
00169 : title(_title), id(_id), text(g) {}
00170
00171 bool operator==(const topic &) const;
00172 bool operator!=(const topic &t) const { return !operator==(t); }
00173
00174 bool operator<(const topic &) const;
00175 std::string title, id;
00176 mutable topic_text text;
00177 };
00178
00179 typedef std::list<topic> topic_list;
00180
00181
00182 struct section {
00183 section() :
00184 title(""),
00185 id(""),
00186 topics(),
00187 sections(),
00188 level()
00189 {
00190 }
00191
00192 section(const section&);
00193 section& operator=(const section&);
00194 ~section();
00195
00196 bool operator==(const section &) const;
00197
00198 bool operator<(const section &) const;
00199
00200
00201 void add_section(const section &s);
00202
00203 void clear();
00204 std::string title, id;
00205 topic_list topics;
00206 section_list sections;
00207 int level;
00208 };
00209
00210
00211
00212
00213 class has_id
00214 {
00215 public:
00216 has_id(const std::string &id) : id_(id) {}
00217 bool operator()(const topic &t) { return t.id == id_; }
00218 bool operator()(const section &s) { return s.id == id_; }
00219 bool operator()(const section *s) { return s != NULL && s->id == id_; }
00220 private:
00221 const std::string id_;
00222 };
00223
00224
00225 class title_less
00226 {
00227 public:
00228 bool operator()(const topic &t1, const topic &t2) {
00229 return strcoll(t1.title.c_str(), t2.title.c_str()) < 0; }
00230 };
00231
00232
00233 class section_less
00234 {
00235 public:
00236 bool operator()(const section* s1, const section* s2) {
00237 return strcoll(s1->title.c_str(), s2->title.c_str()) < 0; }
00238 };
00239
00240 class string_less
00241 {
00242 public:
00243 bool operator() (const std::string &s1, const std::string &s2) const {
00244 return strcoll(s1.c_str(), s2.c_str()) < 0;
00245 }
00246 };
00247
00248 struct delete_section
00249 {
00250 void operator()(section *s) { delete s; }
00251 };
00252
00253 struct create_section
00254 {
00255 section *operator()(const section *s) { return new section(*s); }
00256 section *operator()(const section &s) { return new section(s); }
00257 };
00258
00259
00260
00261 class help_menu : public gui::menu
00262 {
00263 public:
00264 help_menu(CVideo &video, const section &toplevel, int max_height=-1);
00265 int process();
00266
00267
00268
00269 void select_topic(const topic &t);
00270
00271
00272
00273
00274 const topic *chosen_topic();
00275
00276 private:
00277
00278 struct visible_item {
00279 visible_item(const section *_sec, const std::string &visible_string);
00280 visible_item(const topic *_t, const std::string &visible_string);
00281
00282
00283 const topic *t;
00284 const section *sec;
00285 std::string visible_string;
00286 bool operator==(const visible_item &vis_item) const;
00287 bool operator==(const section &sec) const;
00288 bool operator==(const topic &t) const;
00289 };
00290
00291
00292
00293 void update_visible_items(const section &top_level, unsigned starting_level=0);
00294
00295
00296 bool expanded(const section &sec);
00297
00298
00299
00300 void expand(const section &sec);
00301
00302
00303
00304 void contract(const section &sec);
00305
00306
00307
00308 std::string indented_icon(const std::string &icon, const unsigned level);
00309
00310
00311 std::string get_string_to_show(const section &sec, const unsigned level);
00312
00313
00314 std::string get_string_to_show(const topic &topic, const unsigned level);
00315
00316
00317 void display_visible_items();
00318
00319
00320
00321 bool select_topic_internal(const topic &t, const section &sec);
00322
00323 std::vector<visible_item> visible_items_;
00324 const section &toplevel_;
00325 std::set<const section*> expanded_;
00326 surface_restorer restorer_;
00327 SDL_Rect rect_;
00328 topic const *chosen_topic_;
00329 visible_item selected_item_;
00330 };
00331
00332
00333 struct parse_error : public game::error
00334 {
00335 parse_error(const std::string& msg) : game::error(msg) {}
00336 };
00337
00338
00339 class help_text_area : public gui::scrollarea
00340 {
00341 public:
00342 help_text_area(CVideo &video, const section &toplevel);
00343
00344 void show_topic(const topic &t);
00345
00346
00347
00348
00349 std::string ref_at(const int x, const int y);
00350
00351 protected:
00352 virtual void scroll(unsigned int pos);
00353 virtual void set_inner_location(const SDL_Rect& rect);
00354
00355 private:
00356 enum ALIGNMENT {LEFT, MIDDLE, RIGHT, HERE};
00357
00358
00359 ALIGNMENT str_to_align(const std::string &s);
00360
00361
00362
00363 struct item {
00364
00365 item(surface surface, int x, int y, const std::string& text="",
00366 const std::string& reference_to="", bool floating=false,
00367 bool box=false, ALIGNMENT alignment=HERE);
00368
00369 item(surface surface, int x, int y,
00370 bool floating, bool box=false, ALIGNMENT=HERE);
00371
00372
00373 SDL_Rect rect;
00374
00375 surface surf;
00376
00377
00378 std::string text;
00379
00380
00381
00382 std::string ref_to;
00383
00384
00385
00386 bool floating;
00387 bool box;
00388 ALIGNMENT align;
00389 };
00390
00391
00392 class item_at {
00393 public:
00394 item_at(const int x, const int y) : x_(x), y_(y) {}
00395 bool operator()(const item&) const;
00396 private:
00397 const int x_, y_;
00398 };
00399
00400
00401
00402 void set_items();
00403
00404
00405
00406
00407 void handle_ref_cfg(const config &cfg);
00408 void handle_img_cfg(const config &cfg);
00409 void handle_bold_cfg(const config &cfg);
00410 void handle_italic_cfg(const config &cfg);
00411 void handle_header_cfg(const config &cfg);
00412 void handle_jump_cfg(const config &cfg);
00413 void handle_format_cfg(const config &cfg);
00414
00415 void draw_contents();
00416
00417
00418
00419
00420
00421
00422 void add_text_item(const std::string& text, const std::string& ref_dst="",
00423 bool broken_link = false,
00424 int font_size=-1, bool bold=false, bool italic=false,
00425 SDL_Color color=font::NORMAL_COLOR);
00426
00427
00428 void add_img_item(const std::string& path, const std::string& alignment, const bool floating,
00429 const bool box);
00430
00431
00432 void down_one_line();
00433
00434
00435
00436 void adjust_last_row();
00437
00438
00439 int get_remaining_width();
00440
00441
00442
00443
00444 int get_min_x(const int y, const int height=0);
00445
00446
00447 int get_max_x(const int y, const int height=0);
00448
00449
00450
00451
00452
00453
00454 int get_y_for_floating_img(const int width, const int x, const int desired_y);
00455
00456
00457
00458 void add_item(const item& itm);
00459
00460 std::list<item> items_;
00461 std::list<item *> last_row_;
00462 const section &toplevel_;
00463 topic const *shown_topic_;
00464 const int title_spacing_;
00465
00466 std::pair<int, int> curr_loc_;
00467 const unsigned min_row_height_;
00468 unsigned curr_row_height_;
00469
00470 int contents_height_;
00471 };
00472
00473
00474 class help_browser : public gui::widget
00475 {
00476 public:
00477 help_browser(display &disp, const section &toplevel);
00478
00479 void adjust_layout();
00480
00481
00482
00483 void show_topic(const std::string &topic_id);
00484
00485 protected:
00486 virtual void update_location(SDL_Rect const &rect);
00487 virtual void process_event();
00488 virtual void handle_event(const SDL_Event &event);
00489
00490 private:
00491
00492
00493
00494 void update_cursor();
00495 void show_topic(const topic &t, bool save_in_history=true);
00496
00497
00498
00499 void move_in_history(std::deque<const topic *> &from, std::deque<const topic *> &to);
00500 display &disp_;
00501 help_menu menu_;
00502 help_text_area text_area_;
00503 const section &toplevel_;
00504 bool ref_cursor_;
00505 std::deque<const topic *> back_topics_, forward_topics_;
00506 gui::button back_button_, forward_button_;
00507 topic const *shown_topic_;
00508 };
00509
00510
00511
00512
00513
00514
00515 static void generate_sections(const config *help_cfg, const std::string &generator, section &sec, int level);
00516 static std::vector<topic> generate_topics(const bool sort_topics,const std::string &generator);
00517 static std::string generate_topic_text(const std::string &generator, const config *help_cfg,
00518 const section &sec, const std::vector<topic>& generated_topics);
00519 static std::string generate_about_text();
00520 static std::string generate_contents_links(const std::string& section_name, config const *help_cfg);
00521 static std::string generate_contents_links(const section &sec, const std::vector<topic>& topics);
00522
00523
00524
00525 static std::string make_unit_link(const std::string& type_id);
00526
00527 static std::vector<std::string> make_unit_links_list(
00528 const std::vector<std::string>& type_id_list, bool ordered = false);
00529
00530 static void generate_races_sections(const config *help_cfg, section &sec, int level);
00531 static std::vector<topic> generate_unit_topics(const bool, const std::string& race);
00532 enum UNIT_DESCRIPTION_TYPE {FULL_DESCRIPTION, NO_DESCRIPTION, NON_REVEALING_DESCRIPTION};
00533
00534
00535
00536
00537 static UNIT_DESCRIPTION_TYPE description_type(const unit_type &type);
00538 static std::vector<topic> generate_ability_topics(const bool);
00539 static std::vector<topic> generate_weapon_special_topics(const bool);
00540 static std::vector<topic> generate_faction_topics(const bool);
00541
00542
00543
00544 static section parse_config(const config *cfg);
00545
00546 static void parse_config_internal(const config *help_cfg, const config *section_cfg,
00547 section &sec, int level=0);
00548
00549
00550
00551 static bool section_is_referenced(const std::string §ion_id, const config &cfg);
00552
00553
00554 static bool topic_is_referenced(const std::string &topic_id, const config &cfg);
00555
00556
00557
00558
00559 static const topic *find_topic(const section &sec, const std::string &id);
00560
00561
00562
00563
00564 static const section *find_section(const section &sec, const std::string &id);
00565
00566
00567
00568
00569 static std::vector<std::string> parse_text(const std::string &text);
00570
00571
00572
00573 static std::string convert_to_wml(const std::string &element_name, const std::string &contents);
00574
00575
00576
00577 static SDL_Color string_to_color(const std::string &s);
00578
00579
00580 static std::vector<std::string> split_in_width(const std::string &s, const int font_size, const unsigned width);
00581
00582 static std::string remove_first_space(const std::string& text);
00583
00584
00585 static std::string escape(const std::string &s)
00586 {
00587 return utils::escape(s, "'\\");
00588 }
00589
00590
00591
00592 static std::string get_first_word(const std::string &s);
00593
00594 }
00595
00596 namespace {
00597 const config *game_cfg = NULL;
00598 gamemap *map = NULL;
00599
00600 help::section toplevel;
00601
00602 help::section hidden_sections;
00603
00604 int last_num_encountered_units = -1;
00605 int last_num_encountered_terrains = -1;
00606 bool last_debug_state = game_config::debug;
00607
00608 config dummy_cfg;
00609 std::vector<std::string> empty_string_vector;
00610 const int max_section_level = 15;
00611 const int menu_font_size = font::SIZE_NORMAL;
00612 const int title_size = font::SIZE_LARGE;
00613 const int title2_size = font::SIZE_15;
00614 const int box_width = 2;
00615 const int normal_font_size = font::SIZE_SMALL;
00616 const unsigned max_history = 100;
00617 const std::string topic_img = "help/topic.png";
00618 const std::string closed_section_img = "help/closed_section.png";
00619 const std::string open_section_img = "help/open_section.png";
00620 const std::string indentation_img = "help/indentation.png";
00621
00622 const std::string default_show_topic = "introduction_topic";
00623 const std::string unknown_unit_topic = ".unknown_unit";
00624 const std::string unit_prefix = "unit_";
00625 const std::string race_prefix = "race_";
00626 const std::string faction_prefix = "faction_";
00627
00628
00629 static std::string hidden_symbol(bool hidden = true) {
00630 return (hidden ? "." : "");
00631 }
00632
00633 static bool is_visible_id(const std::string &id) {
00634 return (id.empty() || id[0] != '.');
00635 }
00636
00637
00638
00639
00640 static bool is_valid_id(const std::string &id) {
00641 if (id == "toplevel") {
00642 return false;
00643 }
00644 if (id.find(unit_prefix) == 0 || id.find(hidden_symbol() + unit_prefix) == 0) {
00645 return false;
00646 }
00647 if (id.find("ability_") == 0) {
00648 return false;
00649 }
00650 if (id.find("weaponspecial_") == 0) {
00651 return false;
00652 }
00653 if (id == "hidden") {
00654 return false;
00655 }
00656 return true;
00657 }
00658
00659
00660
00661
00662 class about_text_formatter {
00663 public:
00664 std::string operator()(const std::string &s) {
00665 if (s.empty()) return s;
00666
00667 if (s[0] == '+')
00668 return " \n<header>text='" + help::escape(s.substr(1)) + "'</header>";
00669 if (s[0] == '-')
00670 return s.substr(1);
00671 return s;
00672 }
00673 };
00674
00675 }
00676
00677
00678
00679 static std::string make_link(const std::string& text, const std::string& dst)
00680 {
00681
00682 return "<ref>text='" + help::escape(text) + "' dst='" + help::escape(dst) + "'</ref>";
00683 }
00684
00685 static std::string jump_to(const unsigned pos)
00686 {
00687 std::stringstream ss;
00688 ss << "<jump>to=" << pos << "</jump>";
00689 return ss.str();
00690 }
00691
00692 static std::string jump(const unsigned amount)
00693 {
00694 std::stringstream ss;
00695 ss << "<jump>amount=" << amount << "</jump>";
00696 return ss.str();
00697 }
00698
00699 static std::string bold(const std::string &s)
00700 {
00701 std::stringstream ss;
00702 ss << "<bold>text='" << help::escape(s) << "'</bold>";
00703 return ss.str();
00704 }
00705
00706 typedef std::vector<std::vector<std::pair<std::string, unsigned int > > > table_spec;
00707
00708
00709
00710
00711 static std::string generate_table(const table_spec &tab, const unsigned int spacing=font::relative_size(20))
00712 {
00713 table_spec::const_iterator row_it;
00714 std::vector<std::pair<std::string, unsigned> >::const_iterator col_it;
00715 unsigned int num_cols = 0;
00716 for (row_it = tab.begin(); row_it != tab.end(); ++row_it) {
00717 if (row_it->size() > num_cols) {
00718 num_cols = row_it->size();
00719 }
00720 }
00721 std::vector<unsigned int> col_widths(num_cols, 0);
00722
00723 for (row_it = tab.begin(); row_it != tab.end(); ++row_it) {
00724 unsigned int col = 0;
00725 for (col_it = row_it->begin(); col_it != row_it->end(); ++col_it) {
00726 if (col_widths[col] < col_it->second + spacing) {
00727 col_widths[col] = col_it->second + spacing;
00728 }
00729 ++col;
00730 }
00731 }
00732 std::vector<unsigned int> col_starts(num_cols);
00733
00734 for (unsigned int i = 0; i < num_cols; ++i) {
00735 unsigned int this_col_start = 0;
00736 for (unsigned int j = 0; j < i; ++j) {
00737 this_col_start += col_widths[j];
00738 }
00739 col_starts[i] = this_col_start;
00740 }
00741 std::stringstream ss;
00742 for (row_it = tab.begin(); row_it != tab.end(); ++row_it) {
00743 unsigned int col = 0;
00744 for (col_it = row_it->begin(); col_it != row_it->end(); ++col_it) {
00745 ss << jump_to(col_starts[col]) << col_it->first;
00746 ++col;
00747 }
00748 ss << "\n";
00749 }
00750 return ss.str();
00751 }
00752
00753
00754 static unsigned image_width(const std::string &filename)
00755 {
00756 image::locator loc(filename);
00757 surface surf(image::get_image(loc));
00758 if (surf != NULL) {
00759 return surf->w;
00760 }
00761 return 0;
00762 }
00763
00764 static void push_tab_pair(std::vector<std::pair<std::string, unsigned int> > &v, const std::string &s)
00765 {
00766 v.push_back(std::make_pair(s, font::line_width(s, normal_font_size)));
00767 }
00768
00769 namespace help {
00770
00771 help_manager::help_manager(const config *cfg, gamemap *_map)
00772 {
00773 game_cfg = cfg == NULL ? &dummy_cfg : cfg;
00774 map = _map;
00775 }
00776
00777 void generate_contents()
00778 {
00779 toplevel.clear();
00780 hidden_sections.clear();
00781 if (game_cfg != NULL) {
00782 const config *help_config = &game_cfg->child("help");
00783 if (!*help_config) {
00784 help_config = &dummy_cfg;
00785 }
00786 try {
00787 toplevel = parse_config(help_config);
00788
00789
00790
00791
00792
00793
00794 config hidden_toplevel;
00795 std::stringstream ss;
00796 foreach (const config §ion, help_config->child_range("section"))
00797 {
00798 const std::string id = section["id"];
00799 if (find_section(toplevel, id) == NULL) {
00800
00801
00802
00803 if (!section_is_referenced(id, *help_config)) {
00804 if (ss.str() != "") {
00805 ss << ",";
00806 }
00807 ss << id;
00808 }
00809 }
00810 }
00811 hidden_toplevel["sections"] = ss.str();
00812 ss.str("");
00813 foreach (const config &topic, help_config->child_range("topic"))
00814 {
00815 const std::string id = topic["id"];
00816 if (find_topic(toplevel, id) == NULL) {
00817 if (!topic_is_referenced(id, *help_config)) {
00818 if (ss.str() != "") {
00819 ss << ",";
00820 }
00821 ss << id;
00822 }
00823 }
00824 }
00825 hidden_toplevel["topics"] = ss.str();
00826 config hidden_cfg = *help_config;
00827
00828 hidden_cfg.clear_children("toplevel");
00829 hidden_cfg.add_child("toplevel", hidden_toplevel);
00830 hidden_sections = parse_config(&hidden_cfg);
00831 }
00832 catch (parse_error& e) {
00833 std::stringstream msg;
00834 msg << "Parse error when parsing help text: '" << e.message << "'";
00835 std::cerr << msg.str() << std::endl;
00836 }
00837 }
00838 }
00839
00840 help_manager::~help_manager()
00841 {
00842 game_cfg = NULL;
00843 map = NULL;
00844 toplevel.clear();
00845 hidden_sections.clear();
00846
00847
00848 last_num_encountered_units = -1;
00849 last_num_encountered_terrains = -1;
00850 }
00851
00852 bool section_is_referenced(const std::string §ion_id, const config &cfg)
00853 {
00854 if (const config &toplevel = cfg.child("toplevel"))
00855 {
00856 const std::vector<std::string> toplevel_refs
00857 = utils::quoted_split(toplevel["sections"]);
00858 if (std::find(toplevel_refs.begin(), toplevel_refs.end(), section_id)
00859 != toplevel_refs.end()) {
00860 return true;
00861 }
00862 }
00863
00864 foreach (const config §ion, cfg.child_range("section"))
00865 {
00866 const std::vector<std::string> sections_refd
00867 = utils::quoted_split(section["sections"]);
00868 if (std::find(sections_refd.begin(), sections_refd.end(), section_id)
00869 != sections_refd.end()) {
00870 return true;
00871 }
00872 }
00873 return false;
00874 }
00875
00876 bool topic_is_referenced(const std::string &topic_id, const config &cfg)
00877 {
00878 if (const config &toplevel = cfg.child("toplevel"))
00879 {
00880 const std::vector<std::string> toplevel_refs
00881 = utils::quoted_split(toplevel["topics"]);
00882 if (std::find(toplevel_refs.begin(), toplevel_refs.end(), topic_id)
00883 != toplevel_refs.end()) {
00884 return true;
00885 }
00886 }
00887
00888 foreach (const config §ion, cfg.child_range("section"))
00889 {
00890 const std::vector<std::string> topics_refd
00891 = utils::quoted_split(section["topics"]);
00892 if (std::find(topics_refd.begin(), topics_refd.end(), topic_id)
00893 != topics_refd.end()) {
00894 return true;
00895 }
00896 }
00897 return false;
00898 }
00899
00900 void parse_config_internal(const config *help_cfg, const config *section_cfg,
00901 section &sec, int level)
00902 {
00903 if (level > max_section_level) {
00904 std::cerr << "Maximum section depth has been reached. Maybe circular dependency?"
00905 << std::endl;
00906 }
00907 else if (section_cfg != NULL) {
00908 const std::vector<std::string> sections = utils::quoted_split((*section_cfg)["sections"]);
00909 sec.level = level;
00910 std::string id = level == 0 ? "toplevel" : (*section_cfg)["id"].str();
00911 if (level != 0) {
00912 if (!is_valid_id(id)) {
00913 std::stringstream ss;
00914 ss << "Invalid ID, used for internal purpose: '" << id << "'";
00915 throw parse_error(ss.str());
00916 }
00917 }
00918 std::string title = level == 0 ? "" : (*section_cfg)["title"].str();
00919 sec.id = id;
00920 sec.title = title;
00921 std::vector<std::string>::const_iterator it;
00922
00923 for (it = sections.begin(); it != sections.end(); ++it) {
00924 if (const config &child_cfg = help_cfg->find_child("section", "id", *it))
00925 {
00926 section child_section;
00927 parse_config_internal(help_cfg, &child_cfg, child_section, level + 1);
00928 sec.add_section(child_section);
00929 }
00930 else {
00931 std::stringstream ss;
00932 ss << "Help-section '" << *it << "' referenced from '"
00933 << id << "' but could not be found.";
00934 throw parse_error(ss.str());
00935 }
00936 }
00937
00938 generate_sections(help_cfg, (*section_cfg)["sections_generator"], sec, level);
00939
00940 if ((*section_cfg)["sort_sections"] == "yes") {
00941 std::sort(sec.sections.begin(),sec.sections.end(), section_less());
00942 }
00943
00944 bool sort_topics = false;
00945 bool sort_generated = true;
00946
00947 if ((*section_cfg)["sort_topics"] == "yes") {
00948 sort_topics = true;
00949 sort_generated = false;
00950 } else if ((*section_cfg)["sort_topics"] == "no") {
00951 sort_topics = false;
00952 sort_generated = false;
00953 } else if ((*section_cfg)["sort_topics"] == "generated") {
00954 sort_topics = false;
00955 sort_generated = true;
00956 } else if ((*section_cfg)["sort_topics"] != "") {
00957 std::stringstream ss;
00958 ss << "Invalid sort option: '" << (*section_cfg)["sort_topics"] << "'";
00959 throw parse_error(ss.str());
00960 }
00961
00962 std::vector<topic> generated_topics =
00963 generate_topics(sort_generated,(*section_cfg)["generator"]);
00964
00965 const std::vector<std::string> topics_id = utils::quoted_split((*section_cfg)["topics"]);
00966 std::vector<topic> topics;
00967
00968
00969 for (it = topics_id.begin(); it != topics_id.end(); ++it) {
00970 if (const config &topic_cfg = help_cfg->find_child("topic", "id", *it))
00971 {
00972 std::string text = topic_cfg["text"];
00973 text += generate_topic_text(topic_cfg["generator"], help_cfg, sec, generated_topics);
00974 topic child_topic(topic_cfg["title"], topic_cfg["id"], text);
00975 if (!is_valid_id(child_topic.id)) {
00976 std::stringstream ss;
00977 ss << "Invalid ID, used for internal purpose: '" << id << "'";
00978 throw parse_error(ss.str());
00979 }
00980 topics.push_back(child_topic);
00981 }
00982 else {
00983 std::stringstream ss;
00984 ss << "Help-topic '" << *it << "' referenced from '" << id
00985 << "' but could not be found." << std::endl;
00986 throw parse_error(ss.str());
00987 }
00988 }
00989
00990 if (sort_topics) {
00991 std::sort(topics.begin(),topics.end(), title_less());
00992 std::sort(generated_topics.begin(),
00993 generated_topics.end(), title_less());
00994 std::merge(generated_topics.begin(),
00995 generated_topics.end(),topics.begin(),topics.end()
00996 ,std::back_inserter(sec.topics),title_less());
00997 }
00998 else {
00999 std::copy(topics.begin(), topics.end(),
01000 std::back_inserter(sec.topics));
01001 std::copy(generated_topics.begin(),
01002 generated_topics.end(),
01003 std::back_inserter(sec.topics));
01004 }
01005 }
01006 }
01007
01008 section parse_config(const config *cfg)
01009 {
01010 section sec;
01011 if (cfg != NULL) {
01012 config const &toplevel_cfg = cfg->child("toplevel");
01013 parse_config_internal(cfg, toplevel_cfg ? &toplevel_cfg : NULL, sec);
01014 }
01015 return sec;
01016 }
01017
01018 std::vector<topic> generate_topics(const bool sort_generated,const std::string &generator)
01019 {
01020 std::vector<topic> res;
01021 if (generator == "") {
01022 return res;
01023 }
01024
01025 if (generator == "abilities") {
01026 res = generate_ability_topics(sort_generated);
01027 } else if (generator == "weapon_specials") {
01028 res = generate_weapon_special_topics(sort_generated);
01029 } else if (generator == "factions") {
01030 res = generate_faction_topics(sort_generated);
01031 } else {
01032 std::vector<std::string> parts = utils::split(generator, ':', utils::STRIP_SPACES);
01033 if (parts[0] == "units" && parts.size()>1) {
01034 res = generate_unit_topics(sort_generated, parts[1]);
01035 }
01036 }
01037
01038 return res;
01039 }
01040
01041 void generate_sections(const config *help_cfg, const std::string &generator, section &sec, int level)
01042 {
01043 if (generator == "races") {
01044 generate_races_sections(help_cfg, sec, level);
01045 }
01046 }
01047
01048 std::string generate_topic_text(const std::string &generator, const config *help_cfg, const section &sec, const std::vector<topic>& generated_topics)
01049 {
01050 std::string empty_string = "";
01051 if (generator == "") {
01052 return empty_string;
01053 } else if (generator == "about") {
01054 return generate_about_text();
01055 } else {
01056 std::vector<std::string> parts = utils::split(generator, ':');
01057 if (parts.size()>1 && parts[0] == "contents") {
01058 if (parts[1] == "generated") {
01059 return generate_contents_links(sec, generated_topics);
01060 } else {
01061 return generate_contents_links(parts[1], help_cfg);
01062 }
01063 }
01064 }
01065 return empty_string;
01066 }
01067
01068 topic_text::~topic_text()
01069 {
01070 if (generator_ && --generator_->count == 0)
01071 delete generator_;
01072 }
01073
01074 topic_text::topic_text(topic_text const &t): parsed_text_(t.parsed_text_), generator_(t.generator_)
01075 {
01076 if (generator_)
01077 ++generator_->count;
01078 }
01079
01080 topic_text &topic_text::operator=(topic_generator *g)
01081 {
01082 if (generator_ && --generator_->count == 0)
01083 delete generator_;
01084 generator_ = g;
01085 return *this;
01086 }
01087
01088 const std::vector<std::string>& topic_text::parsed_text() const
01089 {
01090 if (generator_) {
01091 parsed_text_ = parse_text((*generator_)());
01092 if (--generator_->count == 0)
01093 delete generator_;
01094 generator_ = NULL;
01095 }
01096 return parsed_text_;
01097 }
01098
01099 std::vector<topic> generate_weapon_special_topics(const bool sort_generated)
01100 {
01101 std::vector<topic> topics;
01102
01103 std::map<t_string, std::string> special_description;
01104 std::map<t_string, std::set<std::string, string_less> > special_units;
01105
01106 foreach (const unit_type_data::unit_type_map::value_type &i, unit_types.types())
01107 {
01108 const unit_type &type = i.second;
01109
01110
01111 if (description_type(type) != FULL_DESCRIPTION)
01112 continue;
01113
01114 std::vector<attack_type> attacks = type.attacks();
01115 for (std::vector<attack_type>::const_iterator it = attacks.begin();
01116 it != attacks.end(); ++it) {
01117
01118 std::vector<t_string> specials = (*it).special_tooltips(true);
01119 for (std::vector<t_string>::iterator sp_it = specials.begin();
01120 sp_it != specials.end() && sp_it+1 != specials.end(); sp_it+=2)
01121 {
01122 if (special_description.find(*sp_it) == special_description.end()) {
01123 std::string description = *(sp_it+1);
01124 const size_t colon_pos = description.find(':');
01125 if (colon_pos != std::string::npos) {
01126
01127 description.erase(0, colon_pos + 2);
01128 }
01129 special_description[*sp_it] = description;
01130 }
01131
01132 if (!type.hide_help()) {
01133
01134 std::string type_name = type.type_name();
01135 std::string ref_id = unit_prefix + type.id();
01136
01137
01138 std::string link = "<ref>text='" + escape(type_name) + "' dst='" + escape(ref_id) + "'</ref>";
01139 special_units[*sp_it].insert(link);
01140 }
01141 }
01142 }
01143 }
01144
01145 for (std::map<t_string, std::string>::iterator s = special_description.begin();
01146 s != special_description.end(); ++s) {
01147
01148 std::string id = "weaponspecial_" + s->first.base_str();
01149 std::stringstream text;
01150 text << s->second;
01151 text << "\n\n" << _("<header>text='Units having this special attack'</header>") << "\n";
01152 std::set<std::string, string_less> &units = special_units[s->first];
01153 for (std::set<std::string, string_less>::iterator u = units.begin(); u != units.end(); ++u) {
01154 text << (*u) << "\n";
01155 }
01156
01157 topics.push_back( topic(s->first, id, text.str()) );
01158 }
01159
01160 if (sort_generated)
01161 std::sort(topics.begin(), topics.end(), title_less());
01162 return topics;
01163 }
01164
01165 std::vector<topic> generate_ability_topics(const bool sort_generated)
01166 {
01167 std::vector<topic> topics;
01168 std::map<t_string, std::string> ability_description;
01169 std::map<t_string, std::set<std::string, string_less> > ability_units;
01170
01171
01172
01173
01174 foreach (const unit_type_data::unit_type_map::value_type &i, unit_types.types())
01175 {
01176 const unit_type &type = i.second;
01177 if (description_type(type) == FULL_DESCRIPTION) {
01178
01179 std::vector<t_string> const* abil_vecs[2];
01180 abil_vecs[0] = &type.abilities();
01181 abil_vecs[1] = &type.adv_abilities();
01182
01183 std::vector<t_string> const* desc_vecs[2];
01184 desc_vecs[0] = &type.ability_tooltips();
01185 desc_vecs[1] = &type.adv_ability_tooltips();
01186
01187 for(int i=0; i<2; ++i) {
01188 std::vector<t_string> const& abil_vec = *abil_vecs[i];
01189 std::vector<t_string> const& desc_vec = *desc_vecs[i];
01190 for(size_t j=0; j < abil_vec.size(); ++j) {
01191 t_string const& abil_name = abil_vec[j];
01192 if (ability_description.find(abil_name) == ability_description.end()) {
01193
01194 if(j >= desc_vec.size()) {
01195 ability_description[abil_name] = "";
01196 } else {
01197 std::string const& abil_desc = desc_vec[j];
01198 const size_t colon_pos = abil_desc.find(':');
01199 if(colon_pos != std::string::npos && colon_pos + 1 < abil_desc.length()) {
01200
01201 ability_description[abil_name] = abil_desc.substr(colon_pos + 2);
01202 } else {
01203 ability_description[abil_name] = abil_desc;
01204 }
01205 }
01206 }
01207
01208 if (!type.hide_help()) {
01209
01210 std::string type_name = type.type_name();
01211 std::string ref_id = unit_prefix + type.id();
01212
01213
01214 std::string link = "<ref>text='" + escape(type_name) + "' dst='" + escape(ref_id) + "'</ref>";
01215 ability_units[abil_name].insert(link);
01216 }
01217 }
01218 }
01219 }
01220 }
01221
01222 for (std::map<t_string, std::string>::iterator a = ability_description.begin(); a != ability_description.end(); ++a) {
01223
01224 std::string id = "ability_" + a->first.base_str();
01225 std::stringstream text;
01226 text << a->second;
01227 text << "\n\n" << _("<header>text='Units having this ability'</header>") << "\n";
01228 std::set<std::string, string_less> &units = ability_units[a->first];
01229 for (std::set<std::string, string_less>::iterator u = units.begin(); u != units.end(); ++u) {
01230 text << (*u) << "\n";
01231 }
01232
01233 topics.push_back( topic(a->first, id, text.str()) );
01234 }
01235
01236 if (sort_generated)
01237 std::sort(topics.begin(), topics.end(), title_less());
01238 return topics;
01239 }
01240
01241 std::vector<topic> generate_faction_topics(const bool sort_generated)
01242 {
01243 std::vector<topic> topics;
01244 const config& era = game_cfg->child("era");
01245 if (era) {
01246 std::vector<std::string> faction_links;
01247 foreach (const config &f, era.child_range("multiplayer_side")) {
01248 const std::string& id = f["id"];
01249 if (id == "Random")
01250 continue;
01251
01252 std::stringstream text;
01253
01254 const config::attribute_value& description = f["description"];
01255 if (!description.empty()) {
01256 text << description.t_str() << "\n";
01257 text << "\n";
01258 }
01259
01260 text << "<header>text='" << _("Leaders:") << "'</header>" << "\n";
01261 const std::vector<std::string> leaders =
01262 make_unit_links_list( utils::split(f["leader"]), true );
01263 foreach (const std::string &link, leaders) {
01264 text << link << "\n";
01265 }
01266
01267 text << "\n";
01268
01269 text << "<header>text='" << _("Recruits:") << "'</header>" << "\n";
01270 const std::vector<std::string> recruits =
01271 make_unit_links_list( utils::split(f["recruit"]), true );
01272 foreach (const std::string &link, recruits) {
01273 text << link << "\n";
01274 }
01275
01276 const std::string name = f["name"];
01277 const std::string ref_id = faction_prefix + id;
01278 topics.push_back( topic(name, ref_id, text.str()) );
01279 faction_links.push_back(make_link(name, ref_id));
01280 }
01281
01282 std::stringstream text;
01283 text << "<header>text='" << _("Era:") << " " << era["name"] << "'</header>" << "\n";
01284 text << "\n";
01285 const config::attribute_value& description = era["description"];
01286 if (!description.empty()) {
01287 text << description.t_str() << "\n";
01288 text << "\n";
01289 }
01290
01291 text << "<header>text='" << _("Factions:") << "'</header>" << "\n";
01292
01293 std::sort(faction_links.begin(), faction_links.end());
01294 foreach (const std::string &link, faction_links) {
01295 text << link << "\n";
01296 }
01297
01298 topics.push_back( topic(_("Factions"), "..factions_section", text.str()) );
01299 } else {
01300 topics.push_back( topic( _("Factions"), "..factions_section",
01301 _("Factions are only used in multiplayer")) );
01302 }
01303
01304 if (sort_generated)
01305 std::sort(topics.begin(), topics.end(), title_less());
01306 return topics;
01307 }
01308
01309
01310 class unit_topic_generator: public topic_generator
01311 {
01312 const unit_type& type_;
01313 typedef std::pair< std::string, unsigned > item;
01314 void push_header(std::vector< item > &row, char const *name) const {
01315 row.push_back(item(bold(name), font::line_width(name, normal_font_size, TTF_STYLE_BOLD)));
01316 }
01317 public:
01318 unit_topic_generator(const unit_type &t): type_(t) {}
01319 virtual std::string operator()() const {
01320
01321 unit_types.find(type_.id(), unit_type::WITHOUT_ANIMATIONS);
01322
01323 std::stringstream ss;
01324 std::string clear_stringstream;
01325 const std::string detailed_description = type_.unit_description();
01326 const unit_type& female_type = type_.get_gender_unit_type(unit_race::FEMALE);
01327 const unit_type& male_type = type_.get_gender_unit_type(unit_race::MALE);
01328
01329
01330 #ifdef LOW_MEM
01331 ss << "<img>src='" << male_type.image() << "'</img> ";
01332 #else
01333 ss << "<img>src='" << male_type.image() << "~RC(" << male_type.flag_rgb() << ">1)" << "'</img> ";
01334 #endif
01335
01336 if (&female_type != &male_type)
01337 #ifdef LOW_MEM
01338 ss << "<img>src='" << female_type.image() << "'</img> ";
01339 #else
01340 ss << "<img>src='" << female_type.image() << "~RC(" << female_type.flag_rgb() << ">1)" << "'</img> ";
01341 #endif
01342
01343
01344 ss << "<format>font_size=" << font::relative_size(11) << " text=' " << escape(_("level"))
01345 << " " << type_.level() << "'</format>";
01346
01347 const std::string &male_portrait = male_type.small_profile();
01348 const std::string &female_portrait = female_type.small_profile();
01349
01350 if (male_portrait.empty() == false && male_portrait != male_type.image()) {
01351 ss << "<img>src='" << male_portrait << "~BG()' align='right'</img> ";
01352 }
01353
01354 if (female_portrait.empty() == false && female_portrait != male_portrait && female_portrait != female_type.image()) {
01355 ss << "<img>src='" << female_portrait << "~BG()' align='right'</img> ";
01356 }
01357
01358 ss << "\n";
01359
01360
01361
01362 const bool first_reverse_value = true;
01363 bool reverse = first_reverse_value;
01364 do {
01365 std::vector<std::string> adv_units =
01366 reverse ? type_.advances_from() : type_.advances_to();
01367 bool first = true;
01368
01369 foreach (const std::string &adv, adv_units)
01370 {
01371 const unit_type *type = unit_types.find(adv);
01372 if (!type || type->hide_help()) continue;
01373
01374 if (first) {
01375 if (reverse)
01376 ss << _("Advances from: ");
01377 else
01378 ss << _("Advances to: ");
01379 first = false;
01380 } else
01381 ss << ", ";
01382
01383 std::string lang_unit = type->type_name();
01384 std::string ref_id;
01385 if (description_type(*type) == FULL_DESCRIPTION) {
01386 ref_id = unit_prefix + type->id();
01387 } else {
01388 ref_id = unknown_unit_topic;
01389 lang_unit += " (?)";
01390 }
01391 ss << "<ref>dst='" << escape(ref_id) << "' text='" << escape(lang_unit) << "'</ref>";
01392 }
01393 ss << "\n";
01394
01395 reverse = !reverse;
01396 } while(reverse != first_reverse_value);
01397
01398
01399
01400 const std::string race_id = type_.race();
01401 std::string race_name;
01402 if (const unit_race *r = unit_types.find_race(race_id)) {
01403 race_name = r->plural_name();
01404 } else {
01405 race_name = _ ("race^Miscellaneous");
01406 }
01407 ss << _("Race: ");
01408 ss << "<ref>dst='" << escape("..race_"+race_id) << "' text='" << escape(race_name) << "'</ref>";
01409 ss << "\n";
01410
01411
01412
01413 if (!type_.abilities().empty()) {
01414 ss << _("Abilities: ");
01415 for(std::vector<t_string>::const_iterator ability_it = type_.abilities().begin(),
01416 ability_end = type_.abilities().end();
01417 ability_it != ability_end; ++ability_it) {
01418 const std::string ref_id = "ability_" + ability_it->base_str();
01419 std::string lang_ability = gettext(ability_it->c_str());
01420 ss << "<ref>dst='" << escape(ref_id) << "' text='" << escape(lang_ability)
01421 << "'</ref>";
01422 if (ability_it + 1 != ability_end)
01423 ss << ", ";
01424 }
01425 ss << "\n";
01426 }
01427
01428
01429
01430 if (!type_.adv_abilities().empty()) {
01431 ss << _("Ability Upgrades: ");
01432 for(std::vector<t_string>::const_iterator ability_it = type_.adv_abilities().begin(),
01433 ability_end = type_.adv_abilities().end();
01434 ability_it != ability_end; ++ability_it) {
01435 const std::string ref_id = "ability_" + ability_it->base_str();
01436 std::string lang_ability = gettext(ability_it->c_str());
01437 ss << "<ref>dst='" << escape(ref_id) << "' text='" << escape(lang_ability)
01438 << "'</ref>";
01439 if (ability_it + 1 != ability_end)
01440 ss << ", ";
01441 }
01442 ss << "\n";
01443 }
01444 ss << "\n";
01445
01446
01447 ss << _("HP: ") << type_.hitpoints() << jump(30)
01448 << _("Moves: ") << type_.movement() << jump(30);
01449 if (type_.vision() >= 0)
01450 ss << _("Vision: ") << type_.vision() << jump(30);
01451 if (type_.jamming() > 0)
01452 ss << _("Jamming: ") << type_.jamming() << jump(30);
01453 ss << _("Cost: ") << type_.cost() << jump(30)
01454 << _("Alignment: ")
01455 << "<ref>dst='time_of_day' text='"
01456 << type_.alignment_description(type_.alignment(), type_.genders().front())
01457 << "'</ref>"
01458 << jump(30);
01459 if (type_.can_advance())
01460 ss << _("Required XP: ") << type_.experience_needed();
01461
01462
01463 ss << "\n\n" << detailed_description;
01464
01465
01466 std::vector<attack_type> attacks = type_.attacks();
01467 if (!attacks.empty()) {
01468
01469 ss << "\n\n<header>text='" << escape(_("unit help^Attacks"))
01470 << "'</header>\n\n";
01471 table_spec table;
01472
01473 std::vector<item> first_row;
01474
01475 first_row.push_back(item("", 0));
01476 push_header(first_row, _("unit help^Name"));
01477 push_header(first_row, _("Type"));
01478 push_header(first_row, _("Strikes"));
01479 push_header(first_row, _("Range"));
01480 push_header(first_row, _("Special"));
01481 table.push_back(first_row);
01482
01483 for(std::vector<attack_type>::const_iterator attack_it = attacks.begin(),
01484 attack_end = attacks.end();
01485 attack_it != attack_end; ++attack_it) {
01486 std::string lang_weapon = attack_it->name();
01487 std::string lang_type = string_table["type_" + attack_it->type()];
01488 std::vector<item> row;
01489 std::stringstream attack_ss;
01490 attack_ss << "<img>src='" << (*attack_it).icon() << "'</img>";
01491 row.push_back(std::make_pair(attack_ss.str(),
01492 image_width(attack_it->icon())));
01493 push_tab_pair(row, lang_weapon);
01494 push_tab_pair(row, lang_type);
01495 attack_ss.str(clear_stringstream);
01496 attack_ss << attack_it->damage() << utils::unicode_en_dash << attack_it->num_attacks() << " " << attack_it->accuracy_parry_description();
01497 push_tab_pair(row, attack_ss.str());
01498 attack_ss.str(clear_stringstream);
01499 push_tab_pair(row, string_table["range_" + (*attack_it).range()]);
01500
01501
01502
01503 std::vector<t_string> specials = attack_it->special_tooltips(true);
01504 if(!specials.empty())
01505 {
01506 std::string lang_special = "";
01507 std::vector<t_string>::iterator sp_it;
01508 for (sp_it = specials.begin(); sp_it != specials.end(); ++sp_it) {
01509 const std::string ref_id = std::string("weaponspecial_")
01510 + sp_it->base_str();
01511 lang_special = (*sp_it);
01512 attack_ss << "<ref>dst='" << escape(ref_id)
01513 << "' text='" << escape(lang_special) << "'</ref>";
01514 if((sp_it + 1) != specials.end() && (sp_it + 2) != specials.end())
01515 {
01516 attack_ss << ", ";
01517 }
01518 ++sp_it;
01519 }
01520 row.push_back(std::make_pair(attack_ss.str(),
01521 font::line_width(lang_special, normal_font_size)));
01522
01523 }
01524 table.push_back(row);
01525 }
01526 ss << generate_table(table);
01527 }
01528
01529
01530 ss << "\n\n<header>text='" << escape(_("Resistances"))
01531 << "'</header>\n\n";
01532 table_spec resistance_table;
01533 std::vector<item> first_res_row;
01534 push_header(first_res_row, _("Attack Type"));
01535 push_header(first_res_row, _("Resistance"));
01536 resistance_table.push_back(first_res_row);
01537 const unit_movement_type &movement_type = type_.movement_type();
01538 utils::string_map dam_tab = movement_type.damage_table();
01539 for(utils::string_map::const_iterator dam_it = dam_tab.begin(), dam_end = dam_tab.end();
01540 dam_it != dam_end; ++dam_it) {
01541 std::vector<item> row;
01542 int resistance = 100 - atoi((*dam_it).second.c_str());
01543 char resi[16];
01544 snprintf(resi,sizeof(resi),"% 4d%%",resistance);
01545 std::string resist = resi;
01546 const size_t pos = resist.find('-');
01547 if (pos != std::string::npos)
01548 resist.replace(pos, 1, utils::unicode_minus);
01549 std::string color;
01550 if (resistance < 0)
01551 color = "red";
01552 else if (resistance <= 20)
01553 color = "yellow";
01554 else if (resistance <= 40)
01555 color = "white";
01556 else
01557 color = "green";
01558
01559 std::string lang_weapon = string_table["type_" + dam_it->first];
01560 push_tab_pair(row, lang_weapon);
01561 std::stringstream str;
01562 str << "<format>color=" << color << " text='"<< resist << "'</format>";
01563 const std::string markup = str.str();
01564 str.str(clear_stringstream);
01565 str << resist;
01566 row.push_back(std::make_pair(markup,
01567 font::line_width(str.str(), normal_font_size)));
01568 resistance_table.push_back(row);
01569 }
01570 ss << generate_table(resistance_table);
01571
01572 if (map != NULL) {
01573
01574 ss << "\n\n<header>text='" << escape(_("Terrain Modifiers"))
01575 << "'</header>\n\n";
01576 std::vector<item> first_row;
01577 table_spec table;
01578 push_header(first_row, _("Terrain"));
01579 push_header(first_row, _("Defense"));
01580 push_header(first_row, _("Movement Cost"));
01581
01582 table.push_back(first_row);
01583 std::set<t_translation::t_terrain>::const_iterator terrain_it =
01584 preferences::encountered_terrains().begin();
01585
01586 for (; terrain_it != preferences::encountered_terrains().end();
01587 ++terrain_it) {
01588 const t_translation::t_terrain terrain = *terrain_it;
01589 if (terrain == t_translation::FOGGED || terrain == t_translation::VOID_TERRAIN || terrain == t_translation::OFF_MAP_USER)
01590 continue;
01591 const terrain_type& info = map->get_terrain_info(terrain);
01592
01593 if (info.union_type().size() == 1 && info.union_type()[0] == info.number() && info.is_nonnull()) {
01594 std::vector<item> row;
01595 const std::string& name = info.name();
01596 const std::string id = info.id();
01597 const int moves = movement_type.movement_cost(*map,terrain);
01598 std::stringstream str;
01599 str << "<ref>text='" << escape(name) << "' dst='"
01600 << escape(std::string("terrain_") + id) << "'</ref>";
01601 row.push_back(std::make_pair(str.str(),
01602 font::line_width(name, normal_font_size)));
01603
01604
01605 str.str(clear_stringstream);
01606 const int defense =
01607 100 - movement_type.defense_modifier(*map,terrain);
01608 std::string color;
01609 if (defense <= 10)
01610 color = "red";
01611 else if (defense <= 30)
01612 color = "yellow";
01613 else if (defense <= 50)
01614 color = "white";
01615 else
01616 color = "green";
01617
01618 str << "<format>color=" << color << " text='"<< defense << "%'</format>";
01619 const std::string markup = str.str();
01620 str.str(clear_stringstream);
01621 str << defense << "%";
01622 row.push_back(std::make_pair(markup,
01623 font::line_width(str.str(), normal_font_size)));
01624
01625
01626 str.str(clear_stringstream);
01627 const bool cannot_move = moves > type_.movement();
01628 if (cannot_move)
01629 color = "red";
01630 else if (moves > 1)
01631 color = "yellow";
01632 else
01633 color = "white";
01634
01635 str << "<format>color=" << color << " text='";
01636
01637
01638 if(cannot_move && (moves > type_.movement() + 5)) {
01639 str << utils::unicode_figure_dash;
01640 } else {
01641 str << moves;
01642 }
01643 str << "'</format>";
01644 push_tab_pair(row, str.str());
01645
01646 table.push_back(row);
01647 }
01648 }
01649 ss << generate_table(table);
01650 }
01651 return ss.str();
01652 }
01653 };
01654
01655 std::string make_unit_link(const std::string& type_id)
01656 {
01657 std::string link;
01658
01659 const unit_type *type = unit_types.find(type_id);
01660 if (!type) {
01661 std::cerr << "Unknown unit type : " << type_id << "\n";
01662
01663
01664 link = type_id;
01665 } else if (!type->hide_help()) {
01666 std::string name = type->type_name();
01667 std::string ref_id;
01668 if (description_type(*type) == FULL_DESCRIPTION) {
01669 ref_id = unit_prefix + type->id();
01670 } else {
01671 ref_id = unknown_unit_topic;
01672 name += " (?)";
01673 }
01674 link = make_link(name, ref_id);
01675 }
01676
01677 return link;
01678 }
01679
01680 std::vector<std::string> make_unit_links_list(const std::vector<std::string>& type_id_list, bool ordered)
01681 {
01682 std::vector<std::string> links_list;
01683 foreach (const std::string &type_id, type_id_list) {
01684 std::string unit_link = make_unit_link(type_id);
01685 if (!unit_link.empty())
01686 links_list.push_back(unit_link);
01687 }
01688
01689 if (ordered)
01690 std::sort(links_list.begin(), links_list.end());
01691
01692 return links_list;
01693 }
01694
01695 void generate_races_sections(const config *help_cfg, section &sec, int level)
01696 {
01697 std::set<std::string, string_less> races;
01698 std::set<std::string, string_less> visible_races;
01699
01700 foreach (const unit_type_data::unit_type_map::value_type &i, unit_types.types())
01701 {
01702 const unit_type &type = i.second;
01703 UNIT_DESCRIPTION_TYPE desc_type = description_type(type);
01704 if (desc_type == FULL_DESCRIPTION) {
01705 races.insert(type.race());
01706 if (!type.hide_help())
01707 visible_races.insert(type.race());
01708 }
01709 }
01710
01711 std::stringstream text;
01712
01713 for(std::set<std::string, string_less>::iterator it = races.begin(); it != races.end(); ++it) {
01714 section race_section;
01715 config section_cfg;
01716
01717 bool hidden = (visible_races.count(*it) == 0);
01718
01719 section_cfg["id"] = hidden_symbol(hidden) + race_prefix + *it;
01720
01721 std::string title;
01722 if (const unit_race *r = unit_types.find_race(*it)) {
01723 title = r->plural_name();
01724 } else {
01725 title = _ ("race^Miscellaneous");
01726 }
01727 section_cfg["title"] = title;
01728
01729 section_cfg["generator"] = "units:" + *it;
01730
01731 parse_config_internal(help_cfg, §ion_cfg, race_section, level+1);
01732 sec.add_section(race_section);
01733 }
01734 }
01735
01736
01737 std::vector<topic> generate_unit_topics(const bool sort_generated, const std::string& race)
01738 {
01739 std::vector<topic> topics;
01740 std::set<std::string, string_less> race_units;
01741 std::set<std::string, string_less> race_topics;
01742
01743 foreach (const unit_type_data::unit_type_map::value_type &i, unit_types.types())
01744 {
01745 const unit_type &type = i.second;
01746
01747 if (type.race() != race)
01748 continue;
01749 UNIT_DESCRIPTION_TYPE desc_type = description_type(type);
01750 if (desc_type != FULL_DESCRIPTION)
01751 continue;
01752
01753 const std::string type_name = type.type_name();
01754 const std::string ref_id = hidden_symbol(type.hide_help()) + unit_prefix + type.id();
01755 topic unit_topic(type_name, ref_id, "");
01756 unit_topic.text = new unit_topic_generator(type);
01757 topics.push_back(unit_topic);
01758
01759 if (!type.hide_help()) {
01760
01761
01762 std::string link = "<ref>text='" + escape(type_name) + "' dst='" + escape(ref_id) + "'</ref>";
01763 race_units.insert(link);
01764 }
01765 }
01766
01767
01768 std::string race_id = "..race_"+race;
01769 std::string race_name;
01770 std::string race_description;
01771 if (const unit_race *r = unit_types.find_race(race)) {
01772 race_name = r->plural_name();
01773 race_description = r->description();
01774
01775 foreach (const config &additional_topic, r->additional_topics())
01776 {
01777 std::string id = additional_topic["id"];
01778 std::string title = additional_topic["title"];
01779 std::string text = additional_topic["text"];
01780
01781 topics.push_back(topic(title,id,text));
01782 std::string link = "<ref>text='" + escape(title) + "' dst='" + escape(id) + "'</ref>";
01783 race_topics.insert(link);
01784 }
01785 } else {
01786 race_name = _ ("race^Miscellaneous");
01787
01788 }
01789
01790 std::stringstream text;
01791 text << race_description;
01792 text << "\n\n" << _("<header>text='Units of this race'</header>") << "\n";
01793 for (std::set<std::string, string_less>::iterator u = race_units.begin(); u != race_units.end(); ++u) {
01794 text << (*u) << "\n";
01795 }
01796
01797 topics.push_back(topic(race_name, race_id, text.str()) );
01798
01799 if (sort_generated)
01800 std::sort(topics.begin(), topics.end(), title_less());
01801
01802 return topics;
01803 }
01804
01805 UNIT_DESCRIPTION_TYPE description_type(const unit_type &type)
01806 {
01807 if (game_config::debug || preferences::show_all_units_in_help()) {
01808 return FULL_DESCRIPTION;
01809 }
01810
01811 const std::set<std::string> &encountered_units = preferences::encountered_units();
01812 if (encountered_units.find(type.id()) != encountered_units.end()) {
01813 return FULL_DESCRIPTION;
01814 }
01815 return NO_DESCRIPTION;
01816 }
01817
01818 std::string generate_about_text()
01819 {
01820 std::vector<std::string> about_lines = about::get_text();
01821 std::vector<std::string> res_lines;
01822 std::transform(about_lines.begin(), about_lines.end(), std::back_inserter(res_lines),
01823 about_text_formatter());
01824 res_lines.erase(std::remove(res_lines.begin(), res_lines.end(), ""), res_lines.end());
01825 std::string text = utils::join(res_lines, "\n");
01826 return text;
01827 }
01828
01829 std::string generate_contents_links(const std::string& section_name, config const *help_cfg)
01830 {
01831 config const §ion_cfg = help_cfg->find_child("section", "id", section_name);
01832 if (!section_cfg) {
01833 return std::string();
01834 }
01835
01836 std::ostringstream res;
01837
01838 std::vector<std::string> topics = utils::quoted_split(section_cfg["topics"]);
01839
01840
01841 typedef std::pair<std::string,std::string> link;
01842 std::vector<link> topics_links;
01843
01844 std::vector<std::string>::iterator t;
01845
01846 for (t = topics.begin(); t != topics.end(); ++t) {
01847 if (config const &topic_cfg = help_cfg->find_child("topic", "id", *t)) {
01848 std::string id = topic_cfg["id"];
01849 if (is_visible_id(id))
01850 topics_links.push_back(link(topic_cfg["title"], id));
01851 }
01852 }
01853
01854 if (section_cfg["sort_topics"] == "yes") {
01855 std::sort(topics_links.begin(),topics_links.end());
01856 }
01857
01858 std::vector<link>::iterator l;
01859 for (l = topics_links.begin(); l != topics_links.end(); ++l) {
01860 std::string link = "<ref>text='" + escape(l->first) + "' dst='" + escape(l->second) + "'</ref>";
01861 res << link <<"\n";
01862 }
01863
01864 return res.str();
01865 }
01866
01867 std::string generate_contents_links(const section &sec, const std::vector<topic>& topics)
01868 {
01869 std::stringstream res;
01870
01871 section_list::const_iterator s;
01872 for (s = sec.sections.begin(); s != sec.sections.end(); ++s) {
01873 if (is_visible_id((*s)->id)) {
01874 std::string link = "<ref>text='" + escape((*s)->title) + "' dst='.." + escape((*s)->id) + "'</ref>";
01875 res << link <<"\n";
01876 }
01877 }
01878
01879 std::vector<topic>::const_iterator t;
01880 for (t = topics.begin(); t != topics.end(); ++t) {
01881 if (is_visible_id(t->id)) {
01882 std::string link = "<ref>text='" + escape(t->title) + "' dst='" + escape(t->id) + "'</ref>";
01883 res << link <<"\n";
01884 }
01885 }
01886
01887 return res.str();
01888 }
01889
01890 bool topic::operator==(const topic &t) const
01891 {
01892 return t.id == id;
01893 }
01894
01895 bool topic::operator<(const topic &t) const
01896 {
01897 return id < t.id;
01898 }
01899
01900 section::~section()
01901 {
01902 std::for_each(sections.begin(), sections.end(), delete_section());
01903 }
01904
01905 section::section(const section &sec) :
01906 title(sec.title),
01907 id(sec.id),
01908 topics(sec.topics),
01909 sections(),
01910 level(sec.level)
01911 {
01912 std::transform(sec.sections.begin(), sec.sections.end(),
01913 std::back_inserter(sections), create_section());
01914 }
01915
01916 section& section::operator=(const section &sec)
01917 {
01918 title = sec.title;
01919 id = sec.id;
01920 level = sec.level;
01921 std::copy(sec.topics.begin(), sec.topics.end(), std::back_inserter(topics));
01922 std::transform(sec.sections.begin(), sec.sections.end(),
01923 std::back_inserter(sections), create_section());
01924 return *this;
01925 }
01926
01927
01928 bool section::operator==(const section &sec) const
01929 {
01930 return sec.id == id;
01931 }
01932
01933 bool section::operator<(const section &sec) const
01934 {
01935 return id < sec.id;
01936 }
01937
01938 void section::add_section(const section &s)
01939 {
01940 sections.push_back(new section(s));
01941 }
01942
01943 void section::clear()
01944 {
01945 topics.clear();
01946 std::for_each(sections.begin(), sections.end(), delete_section());
01947 sections.clear();
01948 }
01949
01950 help_menu::help_menu(CVideo &video, section const &toplevel, int max_height) :
01951 gui::menu(video, empty_string_vector, true, max_height, -1, NULL, &gui::menu::bluebg_style),
01952 visible_items_(),
01953 toplevel_(toplevel),
01954 expanded_(),
01955 restorer_(),
01956 rect_(),
01957 chosen_topic_(NULL),
01958 selected_item_(&toplevel, "")
01959 {
01960 silent_ = true;
01961 update_visible_items(toplevel_);
01962 display_visible_items();
01963 if (!visible_items_.empty())
01964 selected_item_ = visible_items_.front();
01965 }
01966
01967 bool help_menu::expanded(const section &sec)
01968 {
01969 return expanded_.find(&sec) != expanded_.end();
01970 }
01971
01972 void help_menu::expand(const section &sec)
01973 {
01974 if (sec.id != "toplevel" && expanded_.insert(&sec).second) {
01975 sound::play_UI_sound(game_config::sounds::menu_expand);
01976 }
01977 }
01978
01979 void help_menu::contract(const section &sec)
01980 {
01981 if (expanded_.erase(&sec)) {
01982 sound::play_UI_sound(game_config::sounds::menu_contract);
01983 }
01984 }
01985
01986 void help_menu::update_visible_items(const section &sec, unsigned level)
01987 {
01988 if (level == 0) {
01989
01990 visible_items_.clear();
01991 }
01992 section_list::const_iterator sec_it;
01993 for (sec_it = sec.sections.begin(); sec_it != sec.sections.end(); ++sec_it) {
01994 if (is_visible_id((*sec_it)->id)) {
01995 const std::string vis_string = get_string_to_show(*(*sec_it), level + 1);
01996 visible_items_.push_back(visible_item(*sec_it, vis_string));
01997 if (expanded(*(*sec_it))) {
01998 update_visible_items(*(*sec_it), level + 1);
01999 }
02000 }
02001 }
02002 topic_list::const_iterator topic_it;
02003 for (topic_it = sec.topics.begin(); topic_it != sec.topics.end(); ++topic_it) {
02004 if (is_visible_id(topic_it->id)) {
02005 const std::string vis_string = get_string_to_show(*topic_it, level + 1);
02006 visible_items_.push_back(visible_item(&(*topic_it), vis_string));
02007 }
02008 }
02009 }
02010
02011 std::string help_menu::indented_icon(const std::string& icon, const unsigned level) {
02012 std::stringstream to_show;
02013 for (unsigned i = 1; i < level; ++i) {
02014 to_show << IMAGE_PREFIX << indentation_img << IMG_TEXT_SEPARATOR;
02015 }
02016
02017 to_show << IMAGE_PREFIX << icon;
02018 return to_show.str();
02019 }
02020
02021 std::string help_menu::get_string_to_show(const section &sec, const unsigned level)
02022 {
02023 std::stringstream to_show;
02024 to_show << indented_icon(expanded(sec) ? open_section_img : closed_section_img, level)
02025 << IMG_TEXT_SEPARATOR << sec.title;
02026 return to_show.str();
02027 }
02028
02029 std::string help_menu::get_string_to_show(const topic &topic, const unsigned level)
02030 {
02031 std::stringstream to_show;
02032 to_show << indented_icon(topic_img, level)
02033 << IMG_TEXT_SEPARATOR << topic.title;
02034 return to_show.str();
02035 }
02036
02037 bool help_menu::select_topic_internal(const topic &t, const section &sec)
02038 {
02039 topic_list::const_iterator tit =
02040 std::find(sec.topics.begin(), sec.topics.end(), t);
02041 if (tit != sec.topics.end()) {
02042
02043
02044 if (t.id.size()<2 || t.id[0] != '.' || t.id[1] != '.')
02045 expand(sec);
02046 return true;
02047 }
02048 section_list::const_iterator sit;
02049 for (sit = sec.sections.begin(); sit != sec.sections.end(); ++sit) {
02050 if (select_topic_internal(t, *(*sit))) {
02051 expand(sec);
02052 return true;
02053 }
02054 }
02055 return false;
02056 }
02057
02058 void help_menu::select_topic(const topic &t)
02059 {
02060 if (selected_item_ == t) {
02061
02062 return;
02063 }
02064 if (select_topic_internal(t, toplevel_)) {
02065 update_visible_items(toplevel_);
02066 for (std::vector<visible_item>::const_iterator it = visible_items_.begin();
02067 it != visible_items_.end(); ++it) {
02068 if (*it == t) {
02069 selected_item_ = *it;
02070 break;
02071 }
02072 }
02073 display_visible_items();
02074 }
02075 }
02076
02077 int help_menu::process()
02078 {
02079 int res = menu::process();
02080 int mousex, mousey;
02081 SDL_GetMouseState(&mousex,&mousey);
02082
02083 if (!visible_items_.empty() &&
02084 static_cast<size_t>(res) < visible_items_.size()) {
02085
02086 selected_item_ = visible_items_[res];
02087 const section* sec = selected_item_.sec;
02088 if (sec != NULL) {
02089
02090 int x = mousex - menu::location().x;
02091
02092 const std::string icon_img = expanded(*sec) ? open_section_img : closed_section_img;
02093
02094 int text_start = style_->item_size(indented_icon(icon_img, sec->level)).w - style_->get_thickness();
02095
02096
02097
02098 if (menu::double_clicked() || x < text_start) {
02099
02100
02101 expanded(*sec) ? contract(*sec) : expand(*sec);
02102 update_visible_items(toplevel_);
02103 display_visible_items();
02104 } else if (x >= text_start){
02105
02106 chosen_topic_ = find_topic(toplevel, ".."+sec->id );
02107 }
02108 } else if (selected_item_.t != NULL) {
02109
02110 chosen_topic_ = selected_item_.t;
02111 }
02112 }
02113 return res;
02114 }
02115
02116 const topic *help_menu::chosen_topic()
02117 {
02118 const topic *ret = chosen_topic_;
02119 chosen_topic_ = NULL;
02120 return ret;
02121 }
02122
02123 void help_menu::display_visible_items()
02124 {
02125 std::vector<std::string> menu_items;
02126 for(std::vector<visible_item>::const_iterator items_it = visible_items_.begin(),
02127 end = visible_items_.end(); items_it != end; ++items_it) {
02128 std::string to_show = items_it->visible_string;
02129 if (selected_item_ == *items_it)
02130 to_show = std::string("*") + to_show;
02131 menu_items.push_back(to_show);
02132 }
02133 set_items(menu_items, false, true);
02134 }
02135
02136 help_menu::visible_item::visible_item(const section *_sec, const std::string &vis_string) :
02137 t(NULL), sec(_sec), visible_string(vis_string) {}
02138
02139 help_menu::visible_item::visible_item(const topic *_t, const std::string &vis_string) :
02140 t(_t), sec(NULL), visible_string(vis_string) {}
02141
02142 bool help_menu::visible_item::operator==(const section &_sec) const
02143 {
02144 return sec != NULL && *sec == _sec;
02145 }
02146
02147 bool help_menu::visible_item::operator==(const topic &_t) const
02148 {
02149 return t != NULL && *t == _t;
02150 }
02151
02152 bool help_menu::visible_item::operator==(const visible_item &vis_item) const
02153 {
02154 return t == vis_item.t && sec == vis_item.sec;
02155 }
02156
02157 help_text_area::help_text_area(CVideo &video, const section &toplevel) :
02158 gui::scrollarea(video),
02159 items_(),
02160 last_row_(),
02161 toplevel_(toplevel),
02162 shown_topic_(NULL),
02163 title_spacing_(16),
02164 curr_loc_(0, 0),
02165 min_row_height_(font::get_max_height(normal_font_size)),
02166 curr_row_height_(min_row_height_),
02167 contents_height_(0)
02168 {
02169 set_scroll_rate(40);
02170 }
02171
02172 void help_text_area::set_inner_location(SDL_Rect const &rect)
02173 {
02174 bg_register(rect);
02175 if (shown_topic_)
02176 set_items();
02177 }
02178
02179 void help_text_area::show_topic(const topic &t)
02180 {
02181 shown_topic_ = &t;
02182 set_items();
02183 set_dirty(true);
02184 }
02185
02186
02187 help_text_area::item::item(surface surface, int x, int y, const std::string& _text,
02188 const std::string& reference_to, bool _floating,
02189 bool _box, ALIGNMENT alignment) :
02190 rect(),
02191 surf(surface),
02192 text(_text),
02193 ref_to(reference_to),
02194 floating(_floating), box(_box),
02195 align(alignment)
02196 {
02197 rect.x = x;
02198 rect.y = y;
02199 rect.w = box ? surface->w + box_width * 2 : surface->w;
02200 rect.h = box ? surface->h + box_width * 2 : surface->h;
02201 }
02202
02203 help_text_area::item::item(surface surface, int x, int y, bool _floating,
02204 bool _box, ALIGNMENT alignment) :
02205 rect(),
02206 surf(surface),
02207 text(""),
02208 ref_to(""),
02209 floating(_floating),
02210 box(_box), align(alignment)
02211 {
02212 rect.x = x;
02213 rect.y = y;
02214 rect.w = box ? surface->w + box_width * 2 : surface->w;
02215 rect.h = box ? surface->h + box_width * 2 : surface->h;
02216 }
02217
02218 void help_text_area::set_items()
02219 {
02220 last_row_.clear();
02221 items_.clear();
02222 curr_loc_.first = 0;
02223 curr_loc_.second = 0;
02224 curr_row_height_ = min_row_height_;
02225
02226 const std::string show_title =
02227 font::make_text_ellipsis(shown_topic_->title, title_size, inner_location().w);
02228 surface surf(font::get_rendered_text(show_title, title_size,
02229 font::NORMAL_COLOR, TTF_STYLE_BOLD));
02230 if (surf != NULL) {
02231 add_item(item(surf, 0, 0, show_title));
02232 curr_loc_.second = title_spacing_;
02233 contents_height_ = title_spacing_;
02234 down_one_line();
02235 }
02236
02237 std::vector<std::string> const &parsed_items = shown_topic_->text.parsed_text();
02238 std::vector<std::string>::const_iterator it;
02239 for (it = parsed_items.begin(); it != parsed_items.end(); ++it) {
02240 if (*it != "" && (*it)[0] == '[') {
02241
02242 try {
02243 config cfg;
02244 std::istringstream stream(*it);
02245 read(cfg, stream);
02246
02247 #define TRY(name) do { \
02248 if (config &child = cfg.child(#name)) \
02249 handle_##name##_cfg(child); \
02250 } while (0)
02251
02252 TRY(ref);
02253 TRY(img);
02254 TRY(bold);
02255 TRY(italic);
02256 TRY(header);
02257 TRY(jump);
02258 TRY(format);
02259
02260 #undef TRY
02261
02262 }
02263 catch (config::error& e) {
02264 std::stringstream msg;
02265 msg << "Error when parsing help markup as WML: '" << e.message << "'";
02266 throw parse_error(msg.str());
02267 }
02268 }
02269 else {
02270 add_text_item(*it);
02271 }
02272 }
02273 down_one_line();
02274 int h = height();
02275 set_position(0);
02276 set_full_size(contents_height_);
02277 set_shown_size(h);
02278 }
02279
02280 void help_text_area::handle_ref_cfg(const config &cfg)
02281 {
02282 const std::string dst = cfg["dst"];
02283 const std::string text = cfg["text"];
02284 bool force = cfg["force"].to_bool();
02285
02286 if (dst == "") {
02287 std::stringstream msg;
02288 msg << "Ref markup must have dst attribute. Please submit a bug"
02289 " report if you have not modified the game files yourself. Erroneous config: ";
02290 write(msg, cfg);
02291 throw parse_error(msg.str());
02292 }
02293
02294 if (find_topic(toplevel_, dst) == NULL && !force) {
02295
02296 add_text_item(text, game_config::debug ? dst : "", true);
02297
02298
02299
02300
02301
02302
02303
02304
02305
02306
02307
02308
02309 #if 0
02310 if (game_config::debug) {
02311 std::stringstream msg;
02312 msg << "Reference to non-existent topic '" << dst
02313 << "'. Please submit a bug report if you have not"
02314 "modified the game files yourself. Erroneous config: ";
02315 write(msg, cfg);
02316 throw parse_error(msg.str());
02317 }
02318 #endif
02319 } else {
02320 add_text_item(text, dst);
02321 }
02322 }
02323
02324 void help_text_area::handle_img_cfg(const config &cfg)
02325 {
02326 const std::string src = cfg["src"];
02327 const std::string align = cfg["align"];
02328 bool floating = cfg["float"].to_bool();
02329 bool box = cfg["box"].to_bool(true);
02330 if (src == "") {
02331 throw parse_error("Img markup must have src attribute.");
02332 }
02333 add_img_item(src, align, floating, box);
02334 }
02335
02336 void help_text_area::handle_bold_cfg(const config &cfg)
02337 {
02338 const std::string text = cfg["text"];
02339 if (text == "") {
02340 throw parse_error("Bold markup must have text attribute.");
02341 }
02342 add_text_item(text, "", false, -1, true);
02343 }
02344
02345 void help_text_area::handle_italic_cfg(const config &cfg)
02346 {
02347 const std::string text = cfg["text"];
02348 if (text == "") {
02349 throw parse_error("Italic markup must have text attribute.");
02350 }
02351 add_text_item(text, "", false, -1, false, true);
02352 }
02353
02354 void help_text_area::handle_header_cfg(const config &cfg)
02355 {
02356 const std::string text = cfg["text"];
02357 if (text == "") {
02358 throw parse_error("Header markup must have text attribute.");
02359 }
02360 add_text_item(text, "", false, title2_size, true);
02361 }
02362
02363 void help_text_area::handle_jump_cfg(const config &cfg)
02364 {
02365 const std::string amount_str = cfg["amount"];
02366 const std::string to_str = cfg["to"];
02367 if (amount_str == "" && to_str == "") {
02368 throw parse_error("Jump markup must have either a to or an amount attribute.");
02369 }
02370 unsigned jump_to = curr_loc_.first;
02371 if (amount_str != "") {
02372 unsigned amount;
02373 try {
02374 amount = lexical_cast<unsigned, std::string>(amount_str);
02375 }
02376 catch (bad_lexical_cast) {
02377 throw parse_error("Invalid amount the amount attribute in jump markup.");
02378 }
02379 jump_to += amount;
02380 }
02381 if (to_str != "") {
02382 unsigned to;
02383 try {
02384 to = lexical_cast<unsigned, std::string>(to_str);
02385 }
02386 catch (bad_lexical_cast) {
02387 throw parse_error("Invalid amount in the to attribute in jump markup.");
02388 }
02389 if (to < jump_to) {
02390 down_one_line();
02391 }
02392 jump_to = to;
02393 }
02394 if (jump_to != 0 && static_cast<int>(jump_to) <
02395 get_max_x(curr_loc_.first, curr_row_height_)) {
02396
02397 curr_loc_.first = jump_to;
02398 }
02399 }
02400
02401 void help_text_area::handle_format_cfg(const config &cfg)
02402 {
02403 const std::string text = cfg["text"];
02404 if (text == "") {
02405 throw parse_error("Format markup must have text attribute.");
02406 }
02407 bool bold = cfg["bold"].to_bool();
02408 bool italic = cfg["italic"].to_bool();
02409 int font_size = cfg["font_size"].to_int(normal_font_size);
02410 SDL_Color color = string_to_color(cfg["color"]);
02411 add_text_item(text, "", false, font_size, bold, italic, color);
02412 }
02413
02414 void help_text_area::add_text_item(const std::string& text, const std::string& ref_dst,
02415 bool broken_link, int _font_size, bool bold, bool italic,
02416 SDL_Color text_color
02417 )
02418 {
02419 const int font_size = _font_size < 0 ? normal_font_size : _font_size;
02420 if (text.empty())
02421 return;
02422 const int remaining_width = get_remaining_width();
02423 size_t first_word_start = text.find_first_not_of(" ");
02424 if (first_word_start == std::string::npos) {
02425 first_word_start = 0;
02426 }
02427 if (text[first_word_start] == '\n') {
02428 down_one_line();
02429 std::string rest_text = text;
02430 rest_text.erase(0, first_word_start + 1);
02431 add_text_item(rest_text, ref_dst, broken_link, _font_size, bold, italic, text_color);
02432 return;
02433 }
02434 const std::string first_word = get_first_word(text);
02435 int state = ref_dst == "" ? 0 : TTF_STYLE_UNDERLINE;
02436 state |= bold ? TTF_STYLE_BOLD : 0;
02437 state |= italic ? TTF_STYLE_ITALIC : 0;
02438 if (curr_loc_.first != get_min_x(curr_loc_.second, curr_row_height_)
02439 && remaining_width < font::line_width(first_word, font_size, state)) {
02440
02441
02442 down_one_line();
02443 std::string s = remove_first_space(text);
02444 add_text_item(s, ref_dst, broken_link, _font_size, bold, italic, text_color);
02445 }
02446 else {
02447 std::vector<std::string> parts = split_in_width(text, font_size, remaining_width);
02448 std::string first_part = parts.front();
02449
02450 SDL_Color color;
02451 if(ref_dst.empty())
02452 color = text_color;
02453 else if(broken_link)
02454 color = font::BAD_COLOR;
02455 else
02456 color = font::YELLOW_COLOR;
02457
02458 surface surf(font::get_rendered_text(first_part, font_size, color, state));
02459 if (!surf.null())
02460 add_item(item(surf, curr_loc_.first, curr_loc_.second, first_part, ref_dst));
02461 if (parts.size() > 1) {
02462
02463 std::string& s = parts.back();
02464
02465 const std::string first_word_before = get_first_word(s);
02466 const std::string first_word_after = get_first_word(remove_first_space(s));
02467 if (get_remaining_width() >= font::line_width(first_word_after, font_size, state)
02468 && get_remaining_width()
02469 < font::line_width(first_word_before, font_size, state)) {
02470
02471
02472
02473 s = remove_first_space(s);
02474 down_one_line();
02475 }
02476 else if (!(font::line_width(first_word_before, font_size, state)
02477 < get_remaining_width())) {
02478 s = remove_first_space(s);
02479 }
02480 add_text_item(s, ref_dst, broken_link, _font_size, bold, italic, text_color);
02481
02482 }
02483 }
02484 }
02485
02486 void help_text_area::add_img_item(const std::string& path, const std::string& alignment,
02487 const bool floating, const bool box)
02488 {
02489 surface surf(image::get_image(path));
02490 if (surf.null())
02491 return;
02492 ALIGNMENT align = str_to_align(alignment);
02493 if (align == HERE && floating) {
02494 WRN_DP << "Floating image with align HERE, aligning left.\n";
02495 align = LEFT;
02496 }
02497 const int width = surf->w + (box ? box_width * 2 : 0);
02498 int xpos;
02499 int ypos = curr_loc_.second;
02500 int text_width = inner_location().w;
02501 switch (align) {
02502 case HERE:
02503 xpos = curr_loc_.first;
02504 break;
02505 case LEFT:
02506 default:
02507 xpos = 0;
02508 break;
02509 case MIDDLE:
02510 xpos = text_width / 2 - width / 2 - (box ? box_width : 0);
02511 break;
02512 case RIGHT:
02513 xpos = text_width - width - (box ? box_width * 2 : 0);
02514 break;
02515 }
02516 if (curr_loc_.first != get_min_x(curr_loc_.second, curr_row_height_)
02517 && (xpos < curr_loc_.first || xpos + width > text_width)) {
02518 down_one_line();
02519 add_img_item(path, alignment, floating, box);
02520 }
02521 else {
02522 if (!floating) {
02523 curr_loc_.first = xpos;
02524 }
02525 else {
02526 ypos = get_y_for_floating_img(width, xpos, ypos);
02527 }
02528 add_item(item(surf, xpos, ypos, floating, box, align));
02529 }
02530 }
02531
02532 int help_text_area::get_y_for_floating_img(const int width, const int x, const int desired_y)
02533 {
02534 int min_y = desired_y;
02535 for (std::list<item>::const_iterator it = items_.begin(); it != items_.end(); ++it) {
02536 const item& itm = *it;
02537 if (itm.floating) {
02538 if ((itm.rect.x + itm.rect.w > x && itm.rect.x < x + width)
02539 || (itm.rect.x > x && itm.rect.x < x + width)) {
02540 min_y = std::max<int>(min_y, itm.rect.y + itm.rect.h);
02541 }
02542 }
02543 }
02544 return min_y;
02545 }
02546
02547 int help_text_area::get_min_x(const int y, const int height)
02548 {
02549 int min_x = 0;
02550 for (std::list<item>::const_iterator it = items_.begin(); it != items_.end(); ++it) {
02551 const item& itm = *it;
02552 if (itm.floating) {
02553 if (itm.rect.y < y + height && itm.rect.y + itm.rect.h > y && itm.align == LEFT) {
02554 min_x = std::max<int>(min_x, itm.rect.w + 5);
02555 }
02556 }
02557 }
02558 return min_x;
02559 }
02560
02561 int help_text_area::get_max_x(const int y, const int height)
02562 {
02563 int text_width = inner_location().w;
02564 int max_x = text_width;
02565 for (std::list<item>::const_iterator it = items_.begin(); it != items_.end(); ++it) {
02566 const item& itm = *it;
02567 if (itm.floating) {
02568 if (itm.rect.y < y + height && itm.rect.y + itm.rect.h > y) {
02569 if (itm.align == RIGHT) {
02570 max_x = std::min<int>(max_x, text_width - itm.rect.w - 5);
02571 } else if (itm.align == MIDDLE) {
02572 max_x = std::min<int>(max_x, text_width / 2 - itm.rect.w / 2 - 5);
02573 }
02574 }
02575 }
02576 }
02577 return max_x;
02578 }
02579
02580 void help_text_area::add_item(const item &itm)
02581 {
02582 items_.push_back(itm);
02583 if (!itm.floating) {
02584 curr_loc_.first += itm.rect.w;
02585 curr_row_height_ = std::max<int>(itm.rect.h, curr_row_height_);
02586 contents_height_ = std::max<int>(contents_height_, curr_loc_.second + curr_row_height_);
02587 last_row_.push_back(&items_.back());
02588 }
02589 else {
02590 if (itm.align == LEFT) {
02591 curr_loc_.first = itm.rect.w + 5;
02592 }
02593 contents_height_ = std::max<int>(contents_height_, itm.rect.y + itm.rect.h);
02594 }
02595 }
02596
02597
02598 help_text_area::ALIGNMENT help_text_area::str_to_align(const std::string &cmp_str)
02599 {
02600 if (cmp_str == "left") {
02601 return LEFT;
02602 } else if (cmp_str == "middle") {
02603 return MIDDLE;
02604 } else if (cmp_str == "right") {
02605 return RIGHT;
02606 } else if (cmp_str == "here" || cmp_str == "") {
02607 return HERE;
02608 }
02609 std::stringstream msg;
02610 msg << "Invalid alignment string: '" << cmp_str << "'";
02611 throw parse_error(msg.str());
02612 }
02613
02614 void help_text_area::down_one_line()
02615 {
02616 adjust_last_row();
02617 last_row_.clear();
02618 curr_loc_.second += curr_row_height_ + (curr_row_height_ == min_row_height_ ? 0 : 2);
02619 curr_row_height_ = min_row_height_;
02620 contents_height_ = std::max<int>(curr_loc_.second + curr_row_height_, contents_height_);
02621 curr_loc_.first = get_min_x(curr_loc_.second, curr_row_height_);
02622 }
02623
02624 void help_text_area::adjust_last_row()
02625 {
02626 for (std::list<item *>::iterator it = last_row_.begin(); it != last_row_.end(); ++it) {
02627 item &itm = *(*it);
02628 const int gap = curr_row_height_ - itm.rect.h;
02629 itm.rect.y += gap / 2;
02630 }
02631 }
02632
02633 int help_text_area::get_remaining_width()
02634 {
02635 const int total_w = get_max_x(curr_loc_.second, curr_row_height_);
02636 return total_w - curr_loc_.first;
02637 }
02638
02639 void help_text_area::draw_contents()
02640 {
02641 SDL_Rect const &loc = inner_location();
02642 bg_restore();
02643 surface screen = video().getSurface();
02644 clip_rect_setter clip_rect_set(screen, &loc);
02645 for(std::list<item>::const_iterator it = items_.begin(), end = items_.end(); it != end; ++it) {
02646 SDL_Rect dst = it->rect;
02647 dst.y -= get_position();
02648 if (dst.y < static_cast<int>(loc.h) && dst.y + it->rect.h > 0) {
02649 dst.x += loc.x;
02650 dst.y += loc.y;
02651 if (it->box) {
02652 for (int i = 0; i < box_width; ++i) {
02653 draw_rectangle(dst.x, dst.y, it->rect.w - i * 2, it->rect.h - i * 2,
02654 0, screen);
02655 ++dst.x;
02656 ++dst.y;
02657 }
02658 }
02659 sdl_blit(it->surf, NULL, screen, &dst);
02660 }
02661 }
02662 update_rect(loc);
02663 }
02664
02665 void help_text_area::scroll(unsigned int)
02666 {
02667
02668
02669
02670 set_dirty(true);
02671 }
02672
02673 bool help_text_area::item_at::operator()(const item& item) const {
02674 return point_in_rect(x_, y_, item.rect);
02675 }
02676
02677 std::string help_text_area::ref_at(const int x, const int y)
02678 {
02679 const int local_x = x - location().x;
02680 const int local_y = y - location().y;
02681 if (local_y < static_cast<int>(height()) && local_y > 0) {
02682 const int cmp_y = local_y + get_position();
02683 const std::list<item>::const_iterator it =
02684 std::find_if(items_.begin(), items_.end(), item_at(local_x, cmp_y));
02685 if (it != items_.end()) {
02686 if ((*it).ref_to != "") {
02687 return ((*it).ref_to);
02688 }
02689 }
02690 }
02691 return "";
02692 }
02693
02694
02695
02696 help_browser::help_browser(display &disp, const section &toplevel) :
02697 gui::widget(disp.video()),
02698 disp_(disp),
02699 menu_(disp.video(),
02700 toplevel),
02701 text_area_(disp.video(), toplevel), toplevel_(toplevel),
02702 ref_cursor_(false),
02703 back_topics_(),
02704 forward_topics_(),
02705 back_button_(disp.video(), _(" < Back"), gui::button::TYPE_PRESS),
02706 forward_button_(disp.video(), _("Forward >"), gui::button::TYPE_PRESS),
02707 shown_topic_(NULL)
02708 {
02709
02710
02711
02712 back_button_.hide(true);
02713 forward_button_.hide(true);
02714
02715 set_measurements(font::relative_size(400), font::relative_size(500));
02716 }
02717
02718 void help_browser::adjust_layout()
02719 {
02720 const int menu_buttons_padding = font::relative_size(10);
02721 const int menu_y = location().y;
02722 const int menu_x = location().x;
02723 const int menu_w = 250;
02724 const int menu_h = height() - back_button_.height() - menu_buttons_padding;
02725
02726 const int menu_text_area_padding = font::relative_size(10);
02727 const int text_area_y = location().y;
02728 const int text_area_x = menu_x + menu_w + menu_text_area_padding;
02729 const int text_area_w = width() - menu_w - menu_text_area_padding;
02730 const int text_area_h = height();
02731
02732 const int button_border_padding = 0;
02733 const int button_button_padding = font::relative_size(10);
02734 const int back_button_x = location().x + button_border_padding;
02735 const int back_button_y = menu_y + menu_h + menu_buttons_padding;
02736 const int forward_button_x = back_button_x + back_button_.width() + button_button_padding;
02737 const int forward_button_y = back_button_y;
02738
02739 menu_.set_width(menu_w);
02740 menu_.set_location(menu_x, menu_y);
02741 menu_.set_max_height(menu_h);
02742 menu_.set_max_width(menu_w);
02743
02744 text_area_.set_location(text_area_x, text_area_y);
02745 text_area_.set_width(text_area_w);
02746 text_area_.set_height(text_area_h);
02747
02748 back_button_.set_location(back_button_x, back_button_y);
02749 forward_button_.set_location(forward_button_x, forward_button_y);
02750
02751 set_dirty(true);
02752 }
02753
02754 void help_browser::update_location(SDL_Rect const &)
02755 {
02756 adjust_layout();
02757 }
02758
02759 void help_browser::process_event()
02760 {
02761 CKey key;
02762 int mousex, mousey;
02763 SDL_GetMouseState(&mousex,&mousey);
02764
02765
02766 if (point_in_rect(mousex, mousey, menu_.location())) {
02767 menu_.process();
02768 const topic *chosen_topic = menu_.chosen_topic();
02769 if (chosen_topic != NULL && chosen_topic != shown_topic_) {
02770
02771 show_topic(*chosen_topic);
02772 }
02773 }
02774 if (back_button_.pressed()) {
02775 move_in_history(back_topics_, forward_topics_);
02776 }
02777 if (forward_button_.pressed()) {
02778 move_in_history(forward_topics_, back_topics_);
02779 }
02780 back_button_.hide(back_topics_.empty());
02781 forward_button_.hide(forward_topics_.empty());
02782 }
02783
02784 void help_browser::move_in_history(std::deque<const topic *> &from,
02785 std::deque<const topic *> &to)
02786 {
02787 if (!from.empty()) {
02788 const topic *to_show = from.back();
02789 from.pop_back();
02790 if (shown_topic_ != NULL) {
02791 if (to.size() > max_history) {
02792 to.pop_front();
02793 }
02794 to.push_back(shown_topic_);
02795 }
02796 show_topic(*to_show, false);
02797 }
02798 }
02799
02800
02801 void help_browser::handle_event(const SDL_Event &event)
02802 {
02803 SDL_MouseButtonEvent mouse_event = event.button;
02804 if (event.type == SDL_MOUSEBUTTONDOWN) {
02805 if (mouse_event.button == SDL_BUTTON_LEFT) {
02806
02807 const int mousex = mouse_event.x;
02808 const int mousey = mouse_event.y;
02809 const std::string ref = text_area_.ref_at(mousex, mousey);
02810 if (ref != "") {
02811 const topic *t = find_topic(toplevel_, ref);
02812 if (t == NULL) {
02813 std::stringstream msg;
02814 msg << _("Reference to unknown topic: ") << "'" << ref << "'.";
02815 gui2::show_transient_message(disp_.video(), "", msg.str());
02816 update_cursor();
02817 }
02818 else {
02819 show_topic(*t);
02820 update_cursor();
02821 }
02822 }
02823 }
02824 }
02825 else if (event.type == SDL_MOUSEMOTION) {
02826 update_cursor();
02827 }
02828 }
02829
02830 void help_browser::update_cursor()
02831 {
02832 int mousex, mousey;
02833 SDL_GetMouseState(&mousex,&mousey);
02834 const std::string ref = text_area_.ref_at(mousex, mousey);
02835 if (ref != "" && !ref_cursor_) {
02836 cursor::set(cursor::HYPERLINK);
02837 ref_cursor_ = true;
02838 }
02839 else if (ref == "" && ref_cursor_) {
02840 cursor::set(cursor::NORMAL);
02841 ref_cursor_ = false;
02842 }
02843 }
02844
02845
02846 const topic *find_topic(const section &sec, const std::string &id)
02847 {
02848 topic_list::const_iterator tit =
02849 std::find_if(sec.topics.begin(), sec.topics.end(), has_id(id));
02850 if (tit != sec.topics.end()) {
02851 return &(*tit);
02852 }
02853 section_list::const_iterator sit;
02854 for (sit = sec.sections.begin(); sit != sec.sections.end(); ++sit) {
02855 const topic *t = find_topic(*(*sit), id);
02856 if (t != NULL) {
02857 return t;
02858 }
02859 }
02860 return NULL;
02861 }
02862
02863 const section *find_section(const section &sec, const std::string &id)
02864 {
02865 section_list::const_iterator sit =
02866 std::find_if(sec.sections.begin(), sec.sections.end(), has_id(id));
02867 if (sit != sec.sections.end()) {
02868 return *sit;
02869 }
02870 for (sit = sec.sections.begin(); sit != sec.sections.end(); ++sit) {
02871 const section *s = find_section(*(*sit), id);
02872 if (s != NULL) {
02873 return s;
02874 }
02875 }
02876 return NULL;
02877 }
02878
02879 void help_browser::show_topic(const std::string &topic_id)
02880 {
02881 const topic *t = find_topic(toplevel_, topic_id);
02882
02883 if (t != NULL) {
02884 show_topic(*t);
02885 } else if (topic_id.find(unit_prefix)==0 || topic_id.find(hidden_symbol() + unit_prefix)==0) {
02886 show_topic(unknown_unit_topic);
02887 } else {
02888 std::cerr << "Help browser tried to show topic with id '" << topic_id
02889 << "' but that topic could not be found." << std::endl;
02890 }
02891 }
02892
02893 void help_browser::show_topic(const topic &t, bool save_in_history)
02894 {
02895 log_scope("show_topic");
02896
02897 if (save_in_history) {
02898 forward_topics_.clear();
02899 if (shown_topic_ != NULL) {
02900 if (back_topics_.size() > max_history) {
02901 back_topics_.pop_front();
02902 }
02903 back_topics_.push_back(shown_topic_);
02904 }
02905 }
02906
02907 shown_topic_ = &t;
02908 text_area_.show_topic(t);
02909 menu_.select_topic(t);
02910 update_cursor();
02911 }
02912
02913 std::vector<std::string> parse_text(const std::string &text)
02914 {
02915 std::vector<std::string> res;
02916 bool last_char_escape = false;
02917 const char escape_char = '\\';
02918 std::stringstream ss;
02919 size_t pos;
02920 enum { ELEMENT_NAME, OTHER } state = OTHER;
02921 for (pos = 0; pos < text.size(); ++pos) {
02922 const char c = text[pos];
02923 if (c == escape_char && !last_char_escape) {
02924 last_char_escape = true;
02925 }
02926 else {
02927 if (state == OTHER) {
02928 if (c == '<') {
02929 if (last_char_escape) {
02930 ss << c;
02931 }
02932 else {
02933 res.push_back(ss.str());
02934 ss.str("");
02935 state = ELEMENT_NAME;
02936 }
02937 }
02938 else {
02939 ss << c;
02940 }
02941 }
02942 else if (state == ELEMENT_NAME) {
02943 if (c == '/') {
02944 std::string msg = "Erroneous / in element name.";
02945 throw parse_error(msg);
02946 }
02947 else if (c == '>') {
02948
02949 std::stringstream s;
02950 const std::string element_name = ss.str();
02951 ss.str("");
02952 s << "</" << element_name << ">";
02953 const std::string end_element_name = s.str();
02954 size_t end_pos = text.find(end_element_name, pos);
02955 if (end_pos == std::string::npos) {
02956 std::stringstream msg;
02957 msg << "Unterminated element: " << element_name;
02958 throw parse_error(msg.str());
02959 }
02960 s.str("");
02961 const std::string contents = text.substr(pos + 1, end_pos - pos - 1);
02962 const std::string element = convert_to_wml(element_name, contents);
02963 res.push_back(element);
02964 pos = end_pos + end_element_name.size() - 1;
02965 state = OTHER;
02966 }
02967 else {
02968 ss << c;
02969 }
02970 }
02971 last_char_escape = false;
02972 }
02973 }
02974 if (state == ELEMENT_NAME) {
02975 std::stringstream msg;
02976 msg << "Element '" << ss.str() << "' continues through end of string.";
02977 throw parse_error(msg.str());
02978 }
02979 if (ss.str() != "") {
02980
02981 res.push_back(ss.str());
02982 }
02983 return res;
02984 }
02985
02986 std::string convert_to_wml(const std::string &element_name, const std::string &contents)
02987 {
02988 std::stringstream ss;
02989 bool in_quotes = false;
02990 bool last_char_escape = false;
02991 const char escape_char = '\\';
02992 std::vector<std::string> attributes;
02993
02994
02995
02996
02997 for (size_t pos = 0; pos < contents.size(); ++pos) {
02998 const char c = contents[pos];
02999 if (c == escape_char && !last_char_escape) {
03000 last_char_escape = true;
03001 }
03002 else {
03003 if (c == '\'' && !last_char_escape) {
03004 ss << '"';
03005 in_quotes = !in_quotes;
03006 }
03007 else if ((c == ' ' || c == '\n') && !last_char_escape && !in_quotes) {
03008
03009 attributes.push_back(ss.str());
03010 ss.str("");
03011 }
03012 else {
03013 ss << c;
03014 }
03015 last_char_escape = false;
03016 }
03017 }
03018 if (in_quotes) {
03019 std::stringstream msg;
03020 msg << "Unterminated single quote after: '" << ss.str() << "'";
03021 throw parse_error(msg.str());
03022 }
03023 if (ss.str() != "") {
03024 attributes.push_back(ss.str());
03025 }
03026 ss.str("");
03027
03028 ss << "[" << element_name << "]\n";
03029 for (std::vector<std::string>::const_iterator it = attributes.begin();
03030 it != attributes.end(); ++it) {
03031 ss << *it << "\n";
03032 }
03033 ss << "[/" << element_name << "]\n";
03034 return ss.str();
03035 }
03036
03037 SDL_Color string_to_color(const std::string &cmp_str)
03038 {
03039 if (cmp_str == "green") {
03040 return font::GOOD_COLOR;
03041 }
03042 if (cmp_str == "red") {
03043 return font::BAD_COLOR;
03044 }
03045 if (cmp_str == "black") {
03046 return font::BLACK_COLOR;
03047 }
03048 if (cmp_str == "yellow") {
03049 return font::YELLOW_COLOR;
03050 }
03051 if (cmp_str == "white") {
03052 return font::BIGMAP_COLOR;
03053 }
03054 return font::NORMAL_COLOR;
03055 }
03056
03057 std::vector<std::string> split_in_width(const std::string &s, const int font_size,
03058 const unsigned width)
03059 {
03060 std::vector<std::string> res;
03061 try {
03062 const std::string& first_line = font::word_wrap_text(s, font_size, width, -1, 1, true);
03063 res.push_back(first_line);
03064 if(s.size() > first_line.size()) {
03065 res.push_back(s.substr(first_line.size()));
03066 }
03067 }
03068 catch (utils::invalid_utf8_exception&)
03069 {
03070 throw parse_error (_("corrupted original file"));
03071 }
03072
03073 return res;
03074 }
03075
03076 std::string remove_first_space(const std::string& text)
03077 {
03078 if (text.length() > 0 && text[0] == ' ') {
03079 return text.substr(1);
03080 }
03081 return text;
03082 }
03083
03084 std::string get_first_word(const std::string &s)
03085 {
03086 size_t first_word_start = s.find_first_not_of(' ');
03087 if (first_word_start == std::string::npos) {
03088 return s;
03089 }
03090 size_t first_word_end = s.find_first_of(" \n", first_word_start);
03091 if( first_word_end == first_word_start ) {
03092
03093 first_word_end = first_word_start+1;
03094 }
03095
03096
03097 std::string re = s.substr(0, first_word_end);
03098
03099 utils::utf8_iterator ch(re);
03100 if (ch == utils::utf8_iterator::end(re))
03101 return re;
03102
03103 wchar_t firstchar = *ch;
03104 if (font::is_cjk_char(firstchar)) {
03105 re = utils::wchar_to_string(firstchar);
03106 }
03107 return re;
03108 }
03109
03110
03111
03112
03113
03114
03115 void show_help(display &disp, const std::string& show_topic, int xloc, int yloc)
03116 {
03117 show_help(disp, toplevel, show_topic, xloc, yloc);
03118 }
03119
03120
03121
03122
03123
03124
03125 void show_unit_help(display &disp, const std::string& show_topic, bool hidden, int xloc, int yloc)
03126 {
03127 show_help(disp, toplevel, hidden_symbol(hidden) + unit_prefix + show_topic, xloc, yloc);
03128 }
03129
03130
03131
03132
03133
03134
03135
03136 void show_help(display &disp, const section &toplevel_sec,
03137 const std::string& show_topic,
03138 int xloc, int yloc)
03139 {
03140 const events::event_context dialog_events_context;
03141 const gui::dialog_manager manager;
03142 const resize_lock prevent_resizing;
03143
03144 CVideo& screen = disp.video();
03145 surface const scr = screen.getSurface();
03146
03147 const int width = std::min<int>(font::relative_size(900), scr->w - font::relative_size(20));
03148 const int height = std::min<int>(font::relative_size(800), scr->h - font::relative_size(150));
03149 const int left_padding = font::relative_size(10);
03150 const int right_padding = font::relative_size(10);
03151 const int top_padding = font::relative_size(10);
03152 const int bot_padding = font::relative_size(10);
03153
03154
03155
03156 if (yloc <= -1 || xloc <= -1) {
03157 xloc = scr->w / 2 - width / 2;
03158 yloc = scr->h / 2 - height / 2;
03159 }
03160 std::vector<gui::button*> buttons_ptr;
03161 gui::button close_button_(disp.video(), _("Close"));
03162 buttons_ptr.push_back(&close_button_);
03163
03164 gui::dialog_frame f(disp.video(), _("The Battle for Wesnoth Help"), gui::dialog_frame::default_style,
03165 true, &buttons_ptr);
03166 f.layout(xloc, yloc, width, height);
03167 f.draw();
03168
03169
03170
03171 unit_types.build_all(unit_type::HELP_INDEX);
03172
03173 if (preferences::encountered_units().size() != size_t(last_num_encountered_units) ||
03174 preferences::encountered_terrains().size() != size_t(last_num_encountered_terrains) ||
03175 last_debug_state != game_config::debug ||
03176 last_num_encountered_units < 0) {
03177
03178 last_num_encountered_units = preferences::encountered_units().size();
03179 last_num_encountered_terrains = preferences::encountered_terrains().size();
03180 last_debug_state = game_config::debug;
03181 generate_contents();
03182 }
03183 try {
03184 help_browser hb(disp, toplevel_sec);
03185 hb.set_location(xloc + left_padding, yloc + top_padding);
03186 hb.set_width(width - left_padding - right_padding);
03187 hb.set_height(height - top_padding - bot_padding);
03188 if (show_topic != "") {
03189 hb.show_topic(show_topic);
03190 }
03191 else {
03192 hb.show_topic(default_show_topic);
03193 }
03194 hb.set_dirty(true);
03195 events::raise_draw_event();
03196 disp.flip();
03197 disp.invalidate_all();
03198 CKey key;
03199 for (;;) {
03200 events::pump();
03201 events::raise_process_event();
03202 events::raise_draw_event();
03203 if (key[SDLK_ESCAPE]) {
03204
03205 return;
03206 }
03207 for (std::vector<gui::button*>::iterator button_it = buttons_ptr.begin();
03208 button_it != buttons_ptr.end(); ++button_it) {
03209 if ((*button_it)->pressed()) {
03210
03211 return;
03212 }
03213 }
03214 disp.flip();
03215 disp.delay(10);
03216 }
03217 }
03218 catch (parse_error& e) {
03219 std::stringstream msg;
03220 msg << _("Parse error when parsing help text: ") << "'" << e.message << "'";
03221 gui2::show_transient_message(disp.video(), "", msg.str());
03222 }
03223 }
03224
03225 }