help.cpp

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

Generated by doxygen 1.5.5 on 23 May 2008 for The Battle for Wesnoth
Gna! | Forum | Wiki | CIA | devdocs