help.cpp

Go to the documentation of this file.
00001 /* $Id: help.cpp 54122 2012-05-08 00:05:33Z fendrin $ */
00002 /*
00003    Copyright (C) 2003 - 2012 by David White <dave@whitevine.net>
00004    Part of the Battle for Wesnoth Project http://www.wesnoth.org/
00005 
00006    This program is free software; you can redistribute it and/or modify
00007    it under the terms of the GNU General Public License as published by
00008    the Free Software Foundation; either version 2 of the License, or
00009    (at your option) any later version.
00010    This program is distributed in the hope that it will be useful,
00011    but WITHOUT ANY WARRANTY.
00012 
00013    See the COPYING file for more details.
00014 */
00015 
00016 /**
00017  * @file
00018  * Routines for showing the help-dialog.
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/*index*/) 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     //wait until we join the event context to start a hotkey handler
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     //now kill the hotkey handler
00088     delete help_hand_;
00089     help_hand_ = NULL;
00090 }
00091 
00092 /// Generate the help contents from the configurations given to the
00093 /// manager.
00094 static void generate_contents();
00095 
00096 struct section;
00097 
00098 typedef std::vector<section *> section_list;
00099 
00100 /// Generate a topic text on the fly.
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 /// The text displayed in a topic. It is generated on the fly with the information
00119 /// contained in generator_.
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 /// A topic contains a title, an id and some text.
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     /// Two topics are equal if their IDs are equal.
00171     bool operator==(const topic &) const;
00172     bool operator!=(const topic &t) const { return !operator==(t); }
00173     /// Comparison on the ID.
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 /// A section contains topics and sections along with title and ID.
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     /// Two sections are equal if their IDs are equal.
00196     bool operator==(const section &) const;
00197     /// Comparison on the ID.
00198     bool operator<(const section &) const;
00199 
00200     /// Allocate memory for and add the section.
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 /// To be used as a function object to locate sections and topics
00212 /// with a specified ID.
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 /// To be used as a function object when sorting topic lists on the title.
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 /// To be used as a function object when sorting section lists on the title.
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 /// The menu to the left in the help browser, where topics can be
00260 /// navigated through and chosen.
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     /// Make the topic the currently selected one, and expand all
00268     /// sections that need to be expanded to show it.
00269     void select_topic(const topic &t);
00270 
00271     /// If a topic has been chosen, return that topic, otherwise
00272     /// NULL. If one topic is returned, it will not be returned again,
00273     /// if it is not re-chosen.
00274     const topic *chosen_topic();
00275 
00276 private:
00277     /// Information about an item that is visible in the menu.
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         // Invariant, one if these should be NULL. The constructors
00282         // enforce it.
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     /// Regenerate what items are visible by checking what sections are
00292     /// expanded.
00293     void update_visible_items(const section &top_level, unsigned starting_level=0);
00294 
00295     /// Return true if the section is expanded.
00296     bool expanded(const section &sec);
00297 
00298     /// Mark a section as expanded. Do not update the visible items or
00299     /// anything.
00300     void expand(const section &sec);
00301 
00302     /// Contract (close) a section. That is, mark it as not expanded,
00303     /// visible items are not updated.
00304     void contract(const section &sec);
00305 
00306     /// Return the string to use as the prefix for the icon part of the
00307     /// menu-string at the specified level.
00308     std::string indented_icon(const std::string &icon, const unsigned level);
00309     /// Return the string to use as the menu-string for sections at the
00310     /// specified level.
00311     std::string get_string_to_show(const section &sec, const unsigned level);
00312     /// Return the string to use as the menu-string for topics at the
00313     /// specified level.
00314     std::string get_string_to_show(const topic &topic, const unsigned level);
00315 
00316     /// Draw the currently visible items.
00317     void display_visible_items();
00318 
00319     /// Internal recursive thingie. did_expand will be true if any
00320     /// section was expanded, otherwise untouched.
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 /// Thrown when the help system fails to parse something.
00333 struct parse_error : public game::error
00334 {
00335     parse_error(const std::string& msg) : game::error(msg) {}
00336 };
00337 
00338 /// The area where the content is shown in the help browser.
00339 class help_text_area : public gui::scrollarea
00340 {
00341 public:
00342     help_text_area(CVideo &video, const section &toplevel);
00343     /// Display the topic.
00344     void show_topic(const topic &t);
00345 
00346     /// Return the ID that is cross-referenced at the (screen)
00347     /// coordinates x, y. If no cross-reference is there, return the
00348     /// empty string.
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     /// Convert a string to an alignment. Throw parse_error if
00358     /// unsuccessful.
00359     ALIGNMENT str_to_align(const std::string &s);
00360 
00361     /// An item that is displayed in the text area. Contains the surface
00362     /// that should be blitted along with some other information.
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         /// Relative coordinates of this item.
00373         SDL_Rect rect;
00374 
00375         surface surf;
00376 
00377         // If this item contains text, this will contain that text.
00378         std::string text;
00379 
00380         // If this item contains a cross-reference, this is the id
00381         // of the referenced topic.
00382         std::string ref_to;
00383 
00384         // If this item is floating, that is, if things should be filled
00385         // around it.
00386         bool floating;
00387         bool box;
00388         ALIGNMENT align;
00389     };
00390 
00391     /// Function object to find an item at the specified coordinates.
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     /// Update the vector with the items of the shown topic, creating
00401     /// surfaces for everything and putting things where they belong.
00402     void set_items();
00403 
00404     // Create appropriate items from configs. Items will be added to the
00405     // internal vector. These methods check that the necessary
00406     // attributes are specified.
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     /// Add an item with text. If ref_dst is something else than the
00418     /// empty string, the text item will be underlined to show that it
00419     /// is a cross-reference. The item will also remember what the
00420     /// reference points to. If font_size is below zero, the default
00421     /// will be used.
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     /// Add an image item with the specified attributes.
00428     void add_img_item(const std::string& path, const std::string& alignment, const bool floating,
00429                       const bool box);
00430 
00431     /// Move the current input point to the next line.
00432     void down_one_line();
00433 
00434     /// Adjust the heights of the items in the last row to make it look
00435     /// good .
00436     void adjust_last_row();
00437 
00438     /// Return the width that remain on the line the current input point is at.
00439     int get_remaining_width();
00440 
00441     /// Return the least x coordinate at which something of the
00442     /// specified height can be drawn at the specified y coordinate
00443     /// without interfering with floating images.
00444     int get_min_x(const int y, const int height=0);
00445 
00446     /// Analogous with get_min_x but return the maximum X.
00447     int get_max_x(const int y, const int height=0);
00448 
00449     /// Find the lowest y coordinate where a floating img of the
00450     /// specified width and at the specified x coordinate can be
00451     /// placed. Start looking at desired_y and continue downwards. Only
00452     /// check against other floating things, since text and inline
00453     /// images only can be above this place if called correctly.
00454     int get_y_for_floating_img(const int width, const int x, const int desired_y);
00455 
00456     /// Add an item to the internal list, update the locations and row
00457     /// height.
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     // The current input location when creating items.
00466     std::pair<int, int> curr_loc_;
00467     const unsigned min_row_height_;
00468     unsigned curr_row_height_;
00469     /// The height of all items in total.
00470     int contents_height_;
00471 };
00472 
00473 /// A help browser widget.
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     /// Display the topic with the specified identifier. Open the menu
00482     /// on the right location and display the topic in the text area.
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     /// Update the current cursor, set it to the reference cursor if
00492     /// mousex, mousey is over a cross-reference, otherwise, set it to
00493     /// the normal cursor.
00494     void update_cursor();
00495     void show_topic(const topic &t, bool save_in_history=true);
00496     /// Move in the topic history. Pop an element from from and insert
00497     /// it in to. Pop at the fronts if the maximum number of elements is
00498     /// exceeded.
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_; // If the cursor currently is the hyperlink 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 // Generator stuff below. Maybe move to a separate file? This one is
00511 // getting crowded. Dunno if much more is needed though so I'll wait and
00512 // see.
00513 
00514 /// Dispatch generators to their appropriate functions.
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 /// return a hyperlink with the unit's name and pointing to the unit page
00524 /// return empty string if this unit is hidden. If not yet discovered add the (?) suffix
00525 static std::string make_unit_link(const std::string& type_id);
00526 /// return a list of hyperlinks to unit's pages (ordered or not)
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 /// Return the type of description that should be shown for a unit of
00534 /// the given kind. This method is intended to filter out information
00535 /// about units that should not be shown, for example due to not being
00536 /// encountered.
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 /// Parse a help config, return the top level section. Return an empty
00543 /// section if cfg is NULL.
00544 static section parse_config(const config *cfg);
00545 /// Recursive function used by parse_config.
00546 static void parse_config_internal(const config *help_cfg, const config *section_cfg,
00547                            section &sec, int level=0);
00548 
00549 /// Return true if the section with id section_id is referenced from
00550 /// another section in the config, or the toplevel.
00551 static bool section_is_referenced(const std::string &section_id, const config &cfg);
00552 /// Return true if the topic with id topic_id is referenced from
00553 /// another section in the config, or the toplevel.
00554 static bool topic_is_referenced(const std::string &topic_id, const config &cfg);
00555 
00556 /// Search for the topic with the specified identifier in the section
00557 /// and its subsections. Return the found topic, or NULL if none could
00558 /// be found.
00559 static const topic *find_topic(const section &sec, const std::string &id);
00560 
00561 /// Search for the section with the specified identifier in the section
00562 /// and its subsections. Return the found section or NULL if none could
00563 /// be found.
00564 static const section *find_section(const section &sec, const std::string &id);
00565 
00566 /// Parse a text string. Return a vector with the different parts of the
00567 /// text. Each markup item is a separate part while the text between
00568 /// markups are separate parts.
00569 static std::vector<std::string> parse_text(const std::string &text);
00570 
00571 /// Convert the contents to wml attributes, surrounded within
00572 /// [element_name]...[/element_name]. Return the resulting WML.
00573 static std::string convert_to_wml(const std::string &element_name, const std::string &contents);
00574 
00575 /// Return the color the string represents. Return font::NORMAL_COLOR if
00576 /// the string is empty or can't be matched against any other color.
00577 static SDL_Color string_to_color(const std::string &s);
00578 
00579 /// Make a best effort to word wrap s. All parts are less than width.
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 /// Prepend all chars with meaning inside attributes with a backslash.
00585 static std::string escape(const std::string &s)
00586 {
00587     return utils::escape(s, "'\\");
00588 }
00589 
00590 /// Return the first word in s, not removing any spaces in the start of
00591 /// it.
00592 static std::string get_first_word(const std::string &s);
00593 
00594 } // namespace help
00595 
00596 namespace {
00597     const config *game_cfg = NULL;
00598     gamemap *map = NULL;
00599     // The default toplevel.
00600     help::section toplevel;
00601     // All sections and topics not referenced from the default toplevel.
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     // The topic to open by default when opening the help dialog.
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     // id starting with '.' are hidden
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     /// Return true if the id is valid for user defined topics and
00638     /// sections. Some IDs are special, such as toplevel and may not be
00639     /// be defined in the config.
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     /// Class to be used as a function object when generating the about
00660     /// text. Translate the about dialog formatting to format suitable
00661     /// for the help dialog.
00662     class about_text_formatter {
00663     public:
00664         std::string operator()(const std::string &s) {
00665             if (s.empty()) return s;
00666             // Format + as headers, and the rest as normal text.
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     // Helpers for making generation of topics easier.
00678 
00679 static std::string make_link(const std::string& text, const std::string& dst)
00680     {
00681         // some sorting done on list of links may rely on the fact that text is first
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     // Create a table using the table specs. Return markup with jumps
00708     // that create a table. The table spec contains a vector with
00709     // vectors with pairs. The pairs are the markup string that should
00710     // be in a cell, and the width of that cell.
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         // Calculate the width of all columns, including spacing.
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         // Calculate the starting positions of all columns
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     // Return the width for the image with filename.
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             // Create a config object that contains everything that is
00789             // not referenced from the toplevel element. Read this
00790             // config and save these sections and topics so that they
00791             // can be referenced later on when showing help about
00792             // specified things, but that should not be shown when
00793             // opening the help browser in the default manner.
00794             config hidden_toplevel;
00795             std::stringstream ss;
00796             foreach (const config &section, help_config->child_range("section"))
00797             {
00798                 const std::string id = section["id"];
00799                 if (find_section(toplevel, id) == NULL) {
00800                     // This section does not exist referenced from the
00801                     // toplevel. Hence, add it to the hidden ones if it
00802                     // is not referenced from another section.
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             // Change the toplevel to our new, custom built one.
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     // These last numbers must be reset so that the content is regenreated.
00847     // Upon next start.
00848     last_num_encountered_units = -1;
00849     last_num_encountered_terrains = -1;
00850 }
00851 
00852 bool section_is_referenced(const std::string &section_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 &section, 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 &section, 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         // Find all child sections.
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         //TODO: harmonize topics/sections sorting
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         // Find all topics in this section.
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         // Only show the weapon special if we find it on a unit that
01110         // detailed description should be shown about.
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                         // Remove the first colon and the following newline.
01127                         description.erase(0, colon_pos + 2);
01128                     }
01129                     special_description[*sp_it] = description;
01130                 }
01131 
01132                 if (!type.hide_help()) {
01133                     //add a link in the list of units having this special
01134                     std::string type_name = type.type_name();
01135                     std::string ref_id = unit_prefix + type.id();
01136                     //we put the translated name at the beginning of the hyperlink,
01137                     //so the automatic alphabetic sorting of std::set can use it
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         // use untranslated name to have universal topic id
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     // Look through all the unit types, check if a unit of this type
01171     // should have a full description, if so, add this units abilities
01172     // for display. We do not want to show abilities that the user has
01173     // not encountered yet.
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                         //new ability, generate a descripion
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                                 // Remove the first colon and the following newline.
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                         //add a link in the list of units having this ability
01210                         std::string type_name = type.type_name();
01211                         std::string ref_id = unit_prefix +  type.id();
01212                         //we put the translated name at the beginning of the hyperlink,
01213                         //so the automatic alphabetic sorting of std::set can use it
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         // we generate topic's id using the untranslated version of the ability's name
01224         std::string id = "ability_" + a->first.base_str();
01225         std::stringstream text;
01226         text << a->second;  //description
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         // this will force the lazy loading to build this unit
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         // Show the unit's image and its level.
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         // Print cross-references to units that this unit advances from/to.
01361         // Cross reference to the topics containing information about those units.
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"; //added even if empty, to avoid shifting
01394 
01395             reverse = !reverse; //switch direction
01396         } while(reverse != first_reverse_value); // don't restart
01397 
01398         // Print the race of the unit, cross-reference it to the
01399         // respective topic.
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         // Print the abilities the units has, cross-reference them
01412         // to their respective topics.
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         // Print the extra AMLA upgrage abilities, cross-reference them
01429         // to their respective topics.
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         // Print some basic information such as HP and movement points.
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         // Print the detailed description about the unit.
01463         ss << "\n\n" << detailed_description;
01464 
01465         // Print the different attacks a unit has, if it has any.
01466         std::vector<attack_type> attacks = type_.attacks();
01467         if (!attacks.empty()) {
01468             // Print headers for the table.
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             // Dummy element, icons are below.
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             // Print information about every attack.
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                 // Show this attack's special, if it has any. Cross
01501                 // reference it to the section describing the
01502                 // special.
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 << ", "; //comma placed before next special
01517                         }
01518                         ++sp_it; //skip description
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         // Print the resistance table of the unit.
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);    // range: -100% .. +70%
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             // Print the terrain modifier table of the unit.
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                     //defense  -  range: +10 % .. +70 %
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                     //movement  -  range: 1 .. 5, unit_movement_type::UNREACHABLE=impassable
01626                     str.str(clear_stringstream);
01627                     const bool cannot_move = moves > type_.movement();
01628                     if (cannot_move)        // cannot move in this terrain
01629                         color = "red";
01630                     else if (moves > 1)
01631                         color = "yellow";
01632                     else
01633                         color = "white";
01634 
01635                     str << "<format>color=" << color << " text='";
01636                     // A 5 MP margin; if the movement costs go above
01637                     // the unit's max moves + 5, we replace it with dashes.
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         // don't return an hyperlink (no page)
01663         // instead show the id (as hint)
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     } // if hide_help then link is an empty string
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, &section_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             // we also record an hyperlink of this unit
01761             // in the list used for the race topic
01762             std::string link =  "<ref>text='" + escape(type_name) + "' dst='" + escape(ref_id) + "'</ref>";
01763             race_units.insert(link);
01764         }
01765     }
01766 
01767     //generate the hidden race description topic
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         // if (description.empty()) description =  _("No description Available");
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             //topic additional_topic(title, id, text);
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         // description =  _("Here put the description of the Miscellaneous race");
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 &section_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         // we use an intermediate structure to allow a conditional sorting
01841         typedef std::pair<std::string,std::string> link;
01842         std::vector<link> topics_links;
01843 
01844         std::vector<std::string>::iterator t;
01845         // Find all topics in this section.
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; //silence the default menu sounds
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         // Clear if this is the top level, otherwise append items.
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         // topic starting with ".." are assumed as rooted in the parent section
02043         // and so only expand the parent when selected
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         // The requested topic is already selected.
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             // Check how we click on the section
02090             int x = mousex - menu::location().x;
02091 
02092             const std::string icon_img = expanded(*sec) ? open_section_img : closed_section_img;
02093             // we remove the right tickness (ne present bewteen icon and text)
02094             int text_start = style_->item_size(indented_icon(icon_img, sec->level)).w - style_->get_thickness();
02095 
02096             // NOTE: if you want to forbid click to the left of the icon
02097             // also check x >= text_start-image_width(icon_img)
02098             if (menu::double_clicked() || x < text_start) {
02099                 // Open or close a section if we double-click on it
02100                 // or do simple click on the icon.
02101                 expanded(*sec) ? contract(*sec) : expand(*sec);
02102                 update_visible_items(toplevel_);
02103                 display_visible_items();
02104             } else if (x >= text_start){
02105                 // click on title open the topic associated to this section
02106                 chosen_topic_ = find_topic(toplevel, ".."+sec->id );
02107             }
02108         } else if (selected_item_.t != NULL) {
02109             /// Choose a topic if it is clicked.
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     // Add the title item.
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     // Parse and add the text.
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             // Should be parsed as WML.
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(); // End the last 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         // detect the broken link but quietly silence the hyperlink for normal user
02296         add_text_item(text, game_config::debug ? dst : "", true);
02297 
02298         // FIXME: workaround: if different campaigns define different
02299         // terrains, some terrains available in one campaign will
02300         // appear in the list of seen terrains, and be displayed in the
02301         // help, even if the current campaign does not handle such
02302         // terrains. This will lead to the unit page generator creating
02303         // invalid references.
02304         //
02305         // Disabling this is a kludgy workaround until the
02306         // encountered_terrains system is fixed
02307         //
02308         // -- Ayin apr 8 2005
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         // The first word does not fit, and we are not at the start of
02441         // the line. Move down.
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         // Always override the color if we have a cross reference.
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                 // If the removal of the space made this word fit, we
02471                 // must move down a line, otherwise it will be drawn
02472                 // without a space at the end of the line.
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 == "") { // Make the empty string be "here" alignment.
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     // Nothing will be done on the actual scroll event. The scroll
02668     // position is checked when drawing instead and things drawn
02669     // accordingly.
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     // Hide the buttons at first since we do not have any forward or
02710     // back topics at this point. They will be unhidden when history
02711     // appears.
02712     back_button_.hide(true);
02713     forward_button_.hide(true);
02714     // Set sizes to some default values.
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     /// Fake focus functionality for the menu, only process it if it has focus.
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             /// A new topic has been chosen in the menu, display it.
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             // Did the user click a cross-reference?
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                     // End of this name.
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         // Add the last string.
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     // Find the different attributes.
02994     // No checks are made for the equal sign or something like that.
02995     // Attributes are just separated by spaces or newlines.
02996     // Attributes that contain spaces must be in single quotes.
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                 // Space or newline, end of attribute.
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     // Create the WML.
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         // This word is '\n'.
03093         first_word_end = first_word_start+1;
03094     }
03095 
03096     //if no gap(' ' or '\n') found, test if it is CJK character
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  * Open the help browser, show topic with id show_topic.
03112  *
03113  * If show_topic is the empty string, the default topic will be shown.
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  * Open the help browser, show unit with id unit_id.
03122  *
03123  * If show_topic is the empty string, the default topic will be shown.
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  * Open a help dialog using a toplevel other than the default.
03132  *
03133  * This allows for complete customization of the contents, although not in a
03134  * very easy way.
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     // If not both locations were supplied, put the dialog in the middle
03155     // of the screen.
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     // Find all unit_types that have not been constructed yet and fill in the information
03170     // needed to create the help topics
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         // More units or terrains encountered, update the contents.
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                 // Escape quits from the dialog.
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                     // There is only one button, close.
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 } // End namespace help.
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Defines

Generated by doxygen 1.7.1 on Fri May 25 2012 01:03:00 for The Battle for Wesnoth
Gna! | Forum | Wiki | CIA | devdocs