Logo Search packages:      
Sourcecode: wesnoth-1.8 version File versions  Download package

help.cpp

Go to the documentation of this file.
/* $Id: help.cpp 40601 2010-01-07 19:20:44Z silene $ */
/*
   Copyright (C) 2003 - 2010 by David White <dave@whitevine.net>
   Part of the Battle for Wesnoth Project http://www.wesnoth.org/

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License version 2
   or at your option any later version.
   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY.

   See the COPYING file for more details.
*/

/**
 * @file help.cpp
 * Routines for showing the help-dialog.
 */

#include "global.hpp"

#include "help.hpp"

#include "about.hpp"
#include "display.hpp"
#include "foreach.hpp"
#include "game_preferences.hpp"
#include "gettext.hpp"
#include "gui/dialogs/transient_message.hpp"
#include "map.hpp"
#include "marked-up_text.hpp"
#include "log.hpp"
#include "sound.hpp"
#include "unit.hpp"
#include "wml_separators.hpp"
#include "serialization/parser.hpp"

#include <queue>

static lg::log_domain log_display("display");
#define WRN_DP LOG_STREAM(warn, log_display)

namespace help {

help_button::help_button(display& disp, const std::string &help_topic)
      : dialog_button(disp.video(), _("Help")), disp_(disp), topic_(help_topic), help_hand_(NULL)
{}

help_button::~help_button() {
      delete help_hand_;
}

int help_button::action(gui::dialog_process_info &info) {
      if(!topic_.empty()) {
            show_help();
            info.clear_buttons();
      }
      return gui::CONTINUE_DIALOG;
}

void help_button::show_help()
{
      help::show_help(disp_, topic_);
}

bool help_button::can_execute_command(hotkey::HOTKEY_COMMAND cmd, int/*index*/) const
{
      return (topic_.empty() == false && cmd == hotkey::HOTKEY_HELP) || cmd == hotkey::HOTKEY_SCREENSHOT;
}

void help_button::join() {
      dialog_button::join();

      //wait until we join the event context to start a hotkey handler
      delete help_hand_;
      help_hand_ = new hotkey::basic_handler(&disp_, this);
}

void help_button::leave() {
      dialog_button::leave();

      //now kill the hotkey handler
      delete help_hand_;
      help_hand_ = NULL;
}

/// Generate the help contents from the configurations given to the
/// manager.
static void generate_contents();

struct section;

typedef std::vector<section *> section_list;

/// Generate a topic text on the fly.
00096 class topic_generator
{
      unsigned count;
      friend class topic_text;
public:
      topic_generator(): count(1) {}
      virtual std::string operator()() const = 0;
      virtual ~topic_generator() {}
};

class text_topic_generator: public topic_generator {
      std::string text_;
public:
      text_topic_generator(std::string const &t): text_(t) {}
      virtual std::string operator()() const { return text_; }
};

/// The text displayed in a topic. It is generated on the fly with the information
/// contained in generator_.
00115 class topic_text
{
      mutable std::vector< std::string > parsed_text_;
      mutable topic_generator *generator_;
public:
      ~topic_text();
      topic_text():
            parsed_text_(),
            generator_(NULL)
      {
      }

      topic_text(std::string const &t):
            parsed_text_(),
            generator_(new text_topic_generator(t))
      {
      }

      explicit topic_text(topic_generator *g):
            parsed_text_(),
            generator_(g)
      {
      }
      topic_text &operator=(topic_generator *g);
      topic_text(topic_text const &t);

    const std::vector<std::string>& parsed_text() const;
};

/// A topic contains a title, an id and some text.
00145 struct topic
{
      topic() :
            title(),
            id(),
            text()
      {
      }

      topic(const std::string &_title, const std::string &_id) :
            title(_title),
            id(_id),
            text()
      {
      }

      topic(const std::string &_title, const std::string &_id, const std::string &_text)
            : title(_title), id(_id), text(_text) {}
      topic(const std::string &_title, const std::string &_id, topic_generator *g)
            : title(_title), id(_id), text(g) {}
      /// Two topics are equal if their IDs are equal.
      bool operator==(const topic &) const;
      bool operator!=(const topic &t) const { return !operator==(t); }
      /// Comparison on the ID.
      bool operator<(const topic &) const;
      std::string title, id;
      mutable topic_text text;
};

typedef std::list<topic> topic_list;

/// A section contains topics and sections along with title and ID.
00177 struct section {
      section() :
            title(""),
            id(""),
            topics(),
            sections(),
            level()
      {
      }

      section(const section&);
      section& operator=(const section&);
      ~section();
      /// Two sections are equal if their IDs are equal.
      bool operator==(const section &) const;
      /// Comparison on the ID.
      bool operator<(const section &) const;

      /// Allocate memory for and add the section.
      void add_section(const section &s);

      void clear();
      std::string title, id;
      topic_list topics;
      section_list sections;
      int level;
};


/// To be used as a function object to locate sections and topics
/// with a specified ID.
00208 class has_id
{
public:
      has_id(const std::string &id) : id_(id) {}
      bool operator()(const topic &t) { return t.id == id_; }
      bool operator()(const section &s) { return s.id == id_; }
      bool operator()(const section *s) { return s != NULL && s->id == id_; }
private:
      const std::string id_;
};

/// To be used as a function object when sorting topic lists on the title.
00220 class title_less
{
public:
      bool operator()(const topic &t1, const topic &t2) {
            return strcoll(t1.title.c_str(), t2.title.c_str()) < 0; }
};

/// To be used as a function object when sorting section lists on the title.
00228 class section_less
{
public:
      bool operator()(const section* s1, const section* s2) {
            return strcoll(s1->title.c_str(), s2->title.c_str()) < 0; }
};


struct delete_section
{
      void operator()(section *s) { delete s; }
};

struct create_section
{
      section *operator()(const section *s) { return new section(*s); }
      section *operator()(const section &s) { return new section(s); }
};

/// The menu to the left in the help browser, where topics can be
/// navigated through and chosen.
00249 class help_menu : public gui::menu
{
public:
      help_menu(CVideo &video, const section &toplevel, int max_height=-1);
      int process();

      /// Make the topic the currently selected one, and expand all
      /// sections that need to be expanded to show it.
      void select_topic(const topic &t);

      /// If a topic has been chosen, return that topic, otherwise
      /// NULL. If one topic is returned, it will not be returned again,
      /// if it is not re-chosen.
      const topic *chosen_topic();

private:
      /// Information about an item that is visible in the menu.
00266       struct visible_item {
            visible_item(const section *_sec, const std::string &visible_string);
            visible_item(const topic *_t, const std::string &visible_string);
            // Invariant, one if these should be NULL. The constructors
            // enforce it.
            const topic *t;
            const section *sec;
            std::string visible_string;
            bool operator==(const visible_item &vis_item) const;
            bool operator==(const section &sec) const;
            bool operator==(const topic &t) const;
      };

      /// Regenerate what items are visible by checking what sections are
      /// expanded.
      void update_visible_items(const section &top_level, unsigned starting_level=0);

      /// Return true if the section is expanded.
      bool expanded(const section &sec);

      /// Mark a section as expanded. Do not update the visible items or
      /// anything.
      void expand(const section &sec);

      /// Contract (close) a section. That is, mark it as not expanded,
      /// visible items are not updated.
      void contract(const section &sec);

      /// Return the string to use as the prefix for the icon part of the
      /// menu-string at the specified level.
      std::string indented_icon(const std::string &icon, const unsigned level);
      /// Return the string to use as the menu-string for sections at the
      /// specified level.
      std::string get_string_to_show(const section &sec, const unsigned level);
      /// Return the string to use as the menu-string for topics at the
      /// specified level.
      std::string get_string_to_show(const topic &topic, const unsigned level);

      /// Draw the currently visible items.
      void display_visible_items();

      /// Internal recursive thingie. did_expand will be true if any
      /// section was expanded, otherwise untouched.
      bool select_topic_internal(const topic &t, const section &sec);

      std::vector<visible_item> visible_items_;
      const section &toplevel_;
      std::set<const section*> expanded_;
      surface_restorer restorer_;
      SDL_Rect rect_;
      topic const *chosen_topic_;
      visible_item selected_item_;
};

/// Thrown when the help system fails to parse something.
00321 struct parse_error
{
      parse_error(const std::string& msg) : message(msg) {}
      std::string message;
};

/// The area where the content is shown in the help browser.
00328 class help_text_area : public gui::scrollarea
{
public:
      help_text_area(CVideo &video, const section &toplevel);
      /// Display the topic.
      void show_topic(const topic &t);

      /// Return the ID that is crossreferenced at the (screen)
      /// coordinates x, y. If no cross-reference is there, return the
      /// empty string.
      std::string ref_at(const int x, const int y);

protected:
      virtual void scroll(unsigned int pos);
      virtual void set_inner_location(const SDL_Rect& rect);

private:
      enum ALIGNMENT {LEFT, MIDDLE, RIGHT, HERE};
      /// Convert a string to an alignment. Throw parse_error if
      /// unsuccesful.
      ALIGNMENT str_to_align(const std::string &s);

      /// An item that is displayed in the text area. Contains the surface
      /// that should be blitted along with some other information.
00352       struct item {

            item(surface surface, int x, int y, const std::string& text="",
                   const std::string& reference_to="", bool floating=false,
                   bool box=false, ALIGNMENT alignment=HERE);

            item(surface surface, int x, int y,
                   bool floating, bool box=false, ALIGNMENT=HERE);

            /// Relative coordinates of this item.
00362             SDL_Rect rect;

            surface surf;

            // If this item contains text, this will contain that text.
            std::string text;

            // If this item contains a cross-reference, this is the id
            // of the referenced topic.
            std::string ref_to;

            // If this item is floating, that is, if things should be filled
            // around it.
            bool floating;
            bool box;
            ALIGNMENT align;
      };

      /// Function object to find an item at the specified coordinates.
00381       class item_at {
      public:
            item_at(const int x, const int y) : x_(x), y_(y) {}
            bool operator()(const item&) const;
      private:
            const int x_, y_;
      };

      /// Update the vector with the items of the shown topic, creating
      /// surfaces for everything and putting things where they belong.
      void set_items();

      // Create appropriate items from configs. Items will be added to the
      // internal vector. These methods check that the necessary
      // attributes are specified.
      void handle_ref_cfg(const config &cfg);
      void handle_img_cfg(const config &cfg);
      void handle_bold_cfg(const config &cfg);
      void handle_italic_cfg(const config &cfg);
      void handle_header_cfg(const config &cfg);
      void handle_jump_cfg(const config &cfg);
      void handle_format_cfg(const config &cfg);

      void draw_contents();

      /// Add an item with text. If ref_dst is something else than the
      /// empty string, the text item will be underlined to show that it
      /// is a cross-reference. The item will also remember what the
      /// reference points to. If font_size is below zero, the default
      /// will be used.
      void add_text_item(const std::string& text, const std::string& ref_dst="",
                                 bool broken_link = false,
                                 int font_size=-1, bool bold=false, bool italic=false,
                                 SDL_Color color=font::NORMAL_COLOUR);

      /// Add an image item with the specified attributes.
      void add_img_item(const std::string& path, const std::string& alignment, const bool floating,
                                const bool box);

      /// Move the current input point to the next line.
      void down_one_line();

      /// Adjust the heights of the items in the last row to make it look
      /// good .
      void adjust_last_row();

      /// Return the width that remain on the line the current input point is at.
      int get_remaining_width();

      /// Return the least x coordinate at which something of the
      /// specified height can be drawn at the specified y coordinate
      /// without interfering with floating images.
      int get_min_x(const int y, const int height=0);

      /// Analogous with get_min_x but return the maximum X.
      int get_max_x(const int y, const int height=0);

      /// Find the lowest y coordinate where a floating img of the
      /// specified width and at the specified x coordinate can be
      /// placed. Start looking at desired_y and continue downwards. Only
      /// check against other floating things, since text and inline
      /// images only can be above this place if called correctly.
      int get_y_for_floating_img(const int width, const int x, const int desired_y);

      /// Add an item to the internal list, update the locations and row
      /// height.
      void add_item(const item& itm);

      std::list<item> items_;
      std::list<item *> last_row_;
      const section &toplevel_;
      topic const *shown_topic_;
      const int title_spacing_;
      // The current input location when creating items.
      std::pair<int, int> curr_loc_;
      const unsigned min_row_height_;
      unsigned curr_row_height_;
      /// The height of all items in total.
00459       int contents_height_;
};

/// A help browser widget.
00463 class help_browser : public gui::widget
{
public:
      help_browser(display &disp, const section &toplevel);

      void adjust_layout();

      /// Display the topic with the specified identifier. Open the menu
      /// on the right location and display the topic in the text area.
      void show_topic(const std::string &topic_id);

protected:
      virtual void update_location(SDL_Rect const &rect);
      virtual void process_event();
      virtual void handle_event(const SDL_Event &event);

private:
      /// Update the current cursor, set it to the reference cursor if
      /// mousex, mousey is over a cross-reference, otherwise, set it to
      /// the normal cursor.
      void update_cursor();
      void show_topic(const topic &t, bool save_in_history=true);
      /// Move in the topic history. Pop an element from from and insert
      /// it in to. Pop at the fronts if the maximum number of elements is
      /// exceeded.
      void move_in_history(std::deque<const topic *> &from, std::deque<const topic *> &to);
      display &disp_;
      help_menu menu_;
      help_text_area text_area_;
      const section &toplevel_;
      bool ref_cursor_; // If the cursor currently is the hyperlink cursor.
      std::deque<const topic *> back_topics_, forward_topics_;
      gui::button back_button_, forward_button_;
      topic const *shown_topic_;
};

// Generator stuff below. Maybe move to a separate file? This one is
// getting crowded. Dunno if much more is needed though so I'll wait and
// see.

/// Dispatch generators to their appropriate functions.
static void generate_sections(const config *help_cfg, const std::string &generator, section &sec, int level);
static std::vector<topic> generate_topics(const bool sort_topics,const std::string &generator);
static std::string generate_topic_text(const std::string &generator, const config *help_cfg,
const section &sec, const std::vector<topic>& generated_topics);
static std::string generate_about_text();
static std::string generate_contents_links(const std::string& section_name, config const *help_cfg);
static std::string generate_contents_links(const section &sec, const std::vector<topic>& topics);

/// return a hyperlink with the unit's name and pointing to the unit page
/// return empty string if this unit is hidden. If not yet discovered add the (?) suffix
static std::string make_unit_link(const std::string& type_id);
/// return a list of hyperlinks to unit's pages (ordered or not)
static std::vector<std::string> make_unit_links_list(
            const std::vector<std::string>& type_id_list, bool ordered = false);

static void generate_races_sections(const config *help_cfg, section &sec, int level);
static std::vector<topic> generate_unit_topics(const bool, const std::string& race);
enum UNIT_DESCRIPTION_TYPE {FULL_DESCRIPTION, NO_DESCRIPTION, NON_REVEALING_DESCRIPTION};
/// Return the type of description that should be shown for a unit of
/// the given kind. This method is intended to filter out information
/// about units that should not be shown, for example due to not being
/// encountered.
static UNIT_DESCRIPTION_TYPE description_type(const unit_type &type);
static std::vector<topic> generate_ability_topics(const bool);
static std::vector<topic> generate_weapon_special_topics(const bool);
static std::vector<topic> generate_faction_topics(const bool);

/// Parse a help config, return the top level section. Return an empty
/// section if cfg is NULL.
static section parse_config(const config *cfg);
/// Recursive function used by parse_config.
static void parse_config_internal(const config *help_cfg, const config *section_cfg,
                                       section &sec, int level=0);

/// Return true if the section with id section_id is referenced from
/// another section in the config, or the toplevel.
static bool section_is_referenced(const std::string &section_id, const config &cfg);
/// Return true if the topic with id topic_id is referenced from
/// another section in the config, or the toplevel.
static bool topic_is_referenced(const std::string &topic_id, const config &cfg);

/// Search for the topic with the specified identifier in the section
/// and its subsections. Return the found topic, or NULL if none could
/// be found.
static const topic *find_topic(const section &sec, const std::string &id);

/// Search for the section with the specified identifier in the section
/// and its subsections. Return the found section or NULL if none could
/// be found.
static const section *find_section(const section &sec, const std::string &id);

/// Parse a text string. Return a vector with the different parts of the
/// text. Each markup item is a separate part while the text between
/// markups are separate parts.
static std::vector<std::string> parse_text(const std::string &text);

/// Convert the contents to wml attributes, surrounded within
/// [element_name]...[/element_name]. Return the resulting WML.
static std::string convert_to_wml(const std::string &element_name, const std::string &contents);

/// Return the color the string represents. Return font::NORMAL_COLOUR if
/// the string is empty or can't be matched against any other color.
static SDL_Color string_to_color(const std::string &s);

/// Make a best effort to word wrap s. All parts are less than width.
static std::vector<std::string> split_in_width(const std::string &s, const int font_size, const unsigned width);

static std::string remove_first_space(const std::string& text);

/// Prepend all chars with meaning inside attributes with a backslash.
static std::string escape(const std::string &s)
{
      return utils::escape(s, "'\\");
}

/// Return the first word in s, not removing any spaces in the start of
/// it.
static std::string get_first_word(const std::string &s);

} // namespace help

namespace {
      const config *game_cfg = NULL;
      gamemap *map = NULL;
      // The default toplevel.
      help::section toplevel;
      // All sections and topics not referenced from the default toplevel.
      help::section hidden_sections;

      int last_num_encountered_units = -1;
      int last_num_encountered_terrains = -1;
      bool last_debug_state = game_config::debug;

      config dummy_cfg;
      std::vector<std::string> empty_string_vector;
      const int max_section_level = 15;
      const int menu_font_size = font::SIZE_NORMAL;
      const int title_size = font::SIZE_LARGE;
      const int title2_size = font::SIZE_15;
      const int box_width = 2;
      const int normal_font_size = font::SIZE_SMALL;
      const unsigned max_history = 100;
      const std::string topic_img = "help/topic.png";
      const std::string closed_section_img = "help/closed_section.png";
      const std::string open_section_img = "help/open_section.png";
      const std::string indentation_img = "help/indentation.png";
      // The topic to open by default when opening the help dialog.
      const std::string default_show_topic = "introduction_topic";
      const std::string unknown_unit_topic = ".unknown_unit";
      const std::string unit_prefix = "unit_";
      const std::string race_prefix = "race_";
      const std::string faction_prefix = "faction_";

      // id starting with '.' are hidden
      static std::string hidden_symbol(bool hidden = true) {
            return (hidden ? "." : "");
      }

      static bool is_visible_id(const std::string &id) {
            return (id.empty() || id[0] != '.');
      }

      /// Return true if the id is valid for user defined topics and
      /// sections. Some IDs are special, such as toplevel and may not be
      /// be defined in the config.
00629       static bool is_valid_id(const std::string &id) {
            if (id == "toplevel") {
                  return false;
            }
            if (id.find(unit_prefix) == 0 || id.find(hidden_symbol() + unit_prefix) == 0) {
                  return false;
            }
            if (id.find("ability_") == 0) {
                  return false;
            }
            if (id.find("weaponspecial_") == 0) {
                  return false;
            }
            if (id == "hidden") {
                  return false;
            }
            return true;
      }

      /// Class to be used as a function object when generating the about
      /// text. Translate the about dialog formatting to format suitable
      /// for the help dialog.
      class about_text_formatter {
      public:
            std::string operator()(const std::string &s) {
                  if (s.empty()) return s;
                  // Format + as headers, and the rest as normal text.
                  if (s[0] == '+')
                        return " \n<header>text='" + help::escape(s.substr(1)) + "'</header>";
                  if (s[0] == '-')
                        return s.substr(1);
                  return s;
            }
      };

}

      // Helpers for making generation of topics easier.

static std::string make_link(const std::string& text, const std::string& dst)
      {
            // some sorting done on list of links may rely on the fact that text is first
            return "<ref>text='" + help::escape(text) + "' dst='" + help::escape(dst) + "'</ref>";
      }

static std::string jump_to(const unsigned pos)
      {
            std::stringstream ss;
            ss << "<jump>to=" << pos << "</jump>";
            return ss.str();
      }

static std::string jump(const unsigned amount)
      {
            std::stringstream ss;
            ss << "<jump>amount=" << amount << "</jump>";
            return ss.str();
      }

static std::string bold(const std::string &s)
      {
            std::stringstream ss;
            ss << "<bold>text='" << help::escape(s) << "'</bold>";
            return ss.str();
      }

      typedef std::vector<std::vector<std::pair<std::string, unsigned int > > > table_spec;
      // Create a table using the table specs. Return markup with jumps
      // that create a table. The table spec contains a vector with
      // vectors with pairs. The pairs are the markup string that should
      // be in a cell, and the width of that cell.
static std::string generate_table(const table_spec &tab, const unsigned int spacing=font::relative_size(20))
  {
            table_spec::const_iterator row_it;
            std::vector<std::pair<std::string, unsigned> >::const_iterator col_it;
            unsigned int num_cols = 0;
            for (row_it = tab.begin(); row_it != tab.end(); ++row_it) {
                  if (row_it->size() > num_cols) {
                        num_cols = row_it->size();
                  }
            }
            std::vector<unsigned int> col_widths(num_cols, 0);
            // Calculate the width of all columns, including spacing.
            for (row_it = tab.begin(); row_it != tab.end(); ++row_it) {
                  unsigned int col = 0;
                  for (col_it = row_it->begin(); col_it != row_it->end(); ++col_it) {
                        if (col_widths[col] < col_it->second + spacing) {
                              col_widths[col] = col_it->second + spacing;
                        }
                        ++col;
                  }
            }
            std::vector<unsigned int> col_starts(num_cols);
            // Calculate the starting positions of all columns
            for (unsigned int i = 0; i < num_cols; ++i) {
                  unsigned int this_col_start = 0;
                  for (unsigned int j = 0; j < i; ++j) {
                        this_col_start += col_widths[j];
                  }
                  col_starts[i] = this_col_start;
            }
            std::stringstream ss;
            for (row_it = tab.begin(); row_it != tab.end(); ++row_it) {
                  unsigned int col = 0;
                  for (col_it = row_it->begin(); col_it != row_it->end(); ++col_it) {
                        ss << jump_to(col_starts[col]) << col_it->first;
                        ++col;
                  }
                  ss << "\n";
            }
            return ss.str();
      }

      // Return the width for the image with filename.
static unsigned image_width(const std::string &filename)
      {
            image::locator loc(filename);
            surface surf(image::get_image(loc));
            if (surf != NULL) {
                  return surf->w;
            }
            return 0;
      }

static void push_tab_pair(std::vector<std::pair<std::string, unsigned int> > &v, const std::string &s)
      {
            v.push_back(std::make_pair(s, font::line_width(s, normal_font_size)));
      }

namespace help {

help_manager::help_manager(const config *cfg, gamemap *_map)
{
      game_cfg = cfg == NULL ? &dummy_cfg : cfg;
      map = _map;
}

void generate_contents()
{
      toplevel.clear();
      hidden_sections.clear();
      if (game_cfg != NULL) {
            const config *help_config = &game_cfg->child("help");
            if (!*help_config) {
                  help_config = &dummy_cfg;
            }
            try {
                  toplevel = parse_config(help_config);
                  // Create a config object that contains everything that is
                  // not referenced from the toplevel element. Read this
                  // config and save these sections and topics so that they
                  // can be referenced later on when showing help about
                  // specified things, but that should not be shown when
                  // opening the help browser in the default manner.
                  config hidden_toplevel;
                  std::stringstream ss;
                  foreach (const config &section, help_config->child_range("section"))
                  {
                        const std::string id = section["id"];
                        if (find_section(toplevel, id) == NULL) {
                              // This section does not exist referenced from the
                              // toplevel. Hence, add it to the hidden ones if it
                              // is not referenced from another section.
                              if (!section_is_referenced(id, *help_config)) {
                                    if (ss.str() != "") {
                                          ss << ",";
                                    }
                                    ss << id;
                              }
                        }
                  }
                  hidden_toplevel["sections"] = ss.str();
                  ss.str("");
                  foreach (const config &topic, help_config->child_range("topic"))
                  {
                        const std::string id = topic["id"];
                        if (find_topic(toplevel, id) == NULL) {
                              if (!topic_is_referenced(id, *help_config)) {
                                    if (ss.str() != "") {
                                          ss << ",";
                                    }
                                    ss << id;
                              }
                        }
                  }
                  hidden_toplevel["topics"] = ss.str();
                  config hidden_cfg = *help_config;
                  // Change the toplevel to our new, custom built one.
                  hidden_cfg.clear_children("toplevel");
                  hidden_cfg.add_child("toplevel", hidden_toplevel);
                  hidden_sections = parse_config(&hidden_cfg);
            }
            catch (parse_error e) {
                  std::stringstream msg;
                  msg << "Parse error when parsing help text: '" << e.message << "'";
                  std::cerr << msg.str() << std::endl;
            }
      }
}

help_manager::~help_manager()
{
      game_cfg = NULL;
      map = NULL;
      toplevel.clear();
      hidden_sections.clear();
    // These last numbers must be reset so that the content is regenreated.
    // Upon next start.
      last_num_encountered_units = -1;
      last_num_encountered_terrains = -1;
}

bool section_is_referenced(const std::string &section_id, const config &cfg)
{
      if (const config &toplevel = cfg.child("toplevel"))
      {
            const std::vector<std::string> toplevel_refs
                  = utils::quoted_split(toplevel["sections"]);
            if (std::find(toplevel_refs.begin(), toplevel_refs.end(), section_id)
                  != toplevel_refs.end()) {
                  return true;
            }
      }

      foreach (const config &section, cfg.child_range("section"))
      {
            const std::vector<std::string> sections_refd
                  = utils::quoted_split(section["sections"]);
            if (std::find(sections_refd.begin(), sections_refd.end(), section_id)
                  != sections_refd.end()) {
                  return true;
            }
      }
      return false;
}

bool topic_is_referenced(const std::string &topic_id, const config &cfg)
{
      if (const config &toplevel = cfg.child("toplevel"))
      {
            const std::vector<std::string> toplevel_refs
                  = utils::quoted_split(toplevel["topics"]);
            if (std::find(toplevel_refs.begin(), toplevel_refs.end(), topic_id)
                  != toplevel_refs.end()) {
                  return true;
            }
      }

      foreach (const config &section, cfg.child_range("section"))
      {
            const std::vector<std::string> topics_refd
                  = utils::quoted_split(section["topics"]);
            if (std::find(topics_refd.begin(), topics_refd.end(), topic_id)
                  != topics_refd.end()) {
                  return true;
            }
      }
      return false;
}

void parse_config_internal(const config *help_cfg, const config *section_cfg,
                                       section &sec, int level)
{
      if (level > max_section_level) {
            std::cerr << "Maximum section depth has been reached. Maybe circular dependency?"
                          << std::endl;
      }
      else if (section_cfg != NULL) {
            const std::vector<std::string> sections = utils::quoted_split((*section_cfg)["sections"]);
            sec.level = level;
            const std::string id = level == 0 ? "toplevel" : (*section_cfg)["id"];
            if (level != 0) {
                  if (!is_valid_id(id)) {
                        std::stringstream ss;
                        ss << "Invalid ID, used for internal purpose: '" << id << "'";
                        throw parse_error(ss.str());
                  }
            }
            const std::string title = level == 0 ? "" : (*section_cfg)["title"];
            sec.id = id;
            sec.title = title;
            std::vector<std::string>::const_iterator it;
            // Find all child sections.
            for (it = sections.begin(); it != sections.end(); ++it) {
                  if (const config &child_cfg = help_cfg->find_child("section", "id", *it))
                  {
                        section child_section;
                        parse_config_internal(help_cfg, &child_cfg, child_section, level + 1);
                        sec.add_section(child_section);
                  }
                  else {
                        std::stringstream ss;
                        ss << "Help-section '" << *it << "' referenced from '"
                           << id << "' but could not be found.";
                        throw parse_error(ss.str());
                  }
            }

            generate_sections(help_cfg, (*section_cfg)["sections_generator"], sec, level);
            //TODO: harmonize topics/sections sorting
            if ((*section_cfg)["sort_sections"] == "yes") {
                  std::sort(sec.sections.begin(),sec.sections.end(), section_less());
            }

            bool sort_topics = false;
            bool sort_generated = true;

            if ((*section_cfg)["sort_topics"] == "yes") {
              sort_topics = true;
              sort_generated = false;
            } else if ((*section_cfg)["sort_topics"] == "no") {
              sort_topics = false;
        sort_generated = false;
            } else if ((*section_cfg)["sort_topics"] == "generated") {
              sort_topics = false;
              sort_generated = true;
            } else if ((*section_cfg)["sort_topics"] != "") {
              std::stringstream ss;
              ss << "Invalid sort option: '" << (*section_cfg)["sort_topics"] << "'";
              throw parse_error(ss.str());
            }

            std::vector<topic> generated_topics =
            generate_topics(sort_generated,(*section_cfg)["generator"]);

            const std::vector<std::string> topics_id = utils::quoted_split((*section_cfg)["topics"]);
            std::vector<topic> topics;

            // Find all topics in this section.
            for (it = topics_id.begin(); it != topics_id.end(); ++it) {
                  if (const config &topic_cfg = help_cfg->find_child("topic", "id", *it))
                  {
                        std::string text = topic_cfg["text"];
                        text += generate_topic_text(topic_cfg["generator"], help_cfg, sec, generated_topics);
                        topic child_topic(topic_cfg["title"], topic_cfg["id"], text);
                        if (!is_valid_id(child_topic.id)) {
                              std::stringstream ss;
                              ss << "Invalid ID, used for internal purpose: '" << id << "'";
                              throw parse_error(ss.str());
                        }
                        topics.push_back(child_topic);
                  }
                  else {
                        std::stringstream ss;
                        ss << "Help-topic '" << *it << "' referenced from '" << id
                           << "' but could not be found." << std::endl;
                        throw parse_error(ss.str());
                  }
            }

            if (sort_topics) {
                  std::sort(topics.begin(),topics.end(), title_less());
                  std::sort(generated_topics.begin(),
                    generated_topics.end(), title_less());
                  std::merge(generated_topics.begin(),
                    generated_topics.end(),topics.begin(),topics.end()
                    ,std::back_inserter(sec.topics),title_less());
            }
            else {
                  std::copy(topics.begin(), topics.end(),
                    std::back_inserter(sec.topics));
                  std::copy(generated_topics.begin(),
                    generated_topics.end(),
                    std::back_inserter(sec.topics));
            }
      }
}

section parse_config(const config *cfg)
{
      section sec;
      if (cfg != NULL) {
            config const &toplevel_cfg = cfg->child("toplevel");
            parse_config_internal(cfg, toplevel_cfg ? &toplevel_cfg : NULL, sec);
      }
      return sec;
}

std::vector<topic> generate_topics(const bool sort_generated,const std::string &generator)
{
      std::vector<topic> res;
      if (generator == "") {
            return res;
      }

      if (generator == "abilities") {
            res = generate_ability_topics(sort_generated);
      } else if (generator == "weapon_specials") {
            res = generate_weapon_special_topics(sort_generated);
      } else if (generator == "factions") {
            res = generate_faction_topics(sort_generated);
      } else {
            std::vector<std::string> parts = utils::split(generator, ':', utils::STRIP_SPACES);
            if (parts[0] == "units" && parts.size()>1) {
                  res = generate_unit_topics(sort_generated, parts[1]);
            }
      }

      return res;
}

void generate_sections(const config *help_cfg, const std::string &generator, section &sec, int level)
{
      if (generator == "races") {
            generate_races_sections(help_cfg, sec, level);
      }
}

std::string generate_topic_text(const std::string &generator, const config *help_cfg, const section &sec, const std::vector<topic>& generated_topics)
{
      std::string empty_string = "";
      if (generator == "") {
            return empty_string;
      } else if (generator == "about") {
            return generate_about_text();
      } else {
            std::vector<std::string> parts = utils::split(generator, ':');
            if (parts.size()>1 && parts[0] == "contents") {
                  if (parts[1] == "generated") {
                        return generate_contents_links(sec, generated_topics);
                  } else {
                        return generate_contents_links(parts[1], help_cfg);
                  }
            }
      }
      return empty_string;
}

topic_text::~topic_text()
{
      if (generator_ && --generator_->count == 0)
            delete generator_;
}

topic_text::topic_text(topic_text const &t): parsed_text_(t.parsed_text_), generator_(t.generator_)
{
      if (generator_)
            ++generator_->count;
}

topic_text &topic_text::operator=(topic_generator *g)
{
      if (generator_ && --generator_->count == 0)
            delete generator_;
      generator_ = g;
      return *this;
}

const std::vector<std::string>& topic_text::parsed_text() const
{
      if (generator_) {
            parsed_text_ = parse_text((*generator_)());
            if (--generator_->count == 0)
                  delete generator_;
            generator_ = NULL;
      }
      return parsed_text_;
}

std::vector<topic> generate_weapon_special_topics(const bool sort_generated)
{
      std::vector<topic> topics;

      std::map<t_string, std::string> special_description;
      std::map<t_string, std::set<std::string> > special_units;

      foreach (const unit_type_data::unit_type_map::value_type &i, unit_types.types())
      {
            const unit_type &type = i.second;
            // Only show the weapon special if we find it on a unit that
            // detailed description should be shown about.
            if (description_type(type) != FULL_DESCRIPTION)
                  continue;

            std::vector<attack_type> attacks = type.attacks();
            for (std::vector<attack_type>::const_iterator it = attacks.begin();
                              it != attacks.end(); ++it) {

                  std::vector<t_string> specials = (*it).special_tooltips(true);
                  for (std::vector<t_string>::iterator sp_it = specials.begin();
                              sp_it != specials.end() && sp_it+1 != specials.end(); sp_it+=2)
                  {
                        if (special_description.find(*sp_it) == special_description.end()) {
                              std::string description = *(sp_it+1);
                              const size_t colon_pos = description.find(':');
                              if (colon_pos != std::string::npos) {
                                    // Remove the first colon and the following newline.
                                    description.erase(0, colon_pos + 2);
                              }
                              special_description[*sp_it] = description;
                        }

                        if (!type.hide_help()) {
                              //add a link in the list of units having this special
                              std::string type_name = type.type_name();
                              std::string ref_id = unit_prefix + type.id();
                              //we put the translated name at the beginning of the hyperlink,
                              //so the automatic alphabetic sorting of std::set can use it
                              std::string link =  "<ref>text='" + escape(type_name) + "' dst='" + escape(ref_id) + "'</ref>";
                              special_units[*sp_it].insert(link);
                        }
                  }
            }
      }

      for (std::map<t_string, std::string>::iterator s = special_description.begin();
                  s != special_description.end(); ++s) {
            // use untranslated name to have universal topic id
            std::string id = "weaponspecial_" + s->first.base_str();
            std::stringstream text;
            text << s->second;
            text << "\n\n" << _("<header>text='Units having this special attack'</header>") << "\n";
            std::set<std::string>& units = special_units[s->first];
            for (std::set<std::string>::iterator u = units.begin(); u != units.end();++u) {
                  text << (*u) << "\n";
            }

            topics.push_back( topic(s->first, id, text.str()) );
      }

      if (sort_generated)
            std::sort(topics.begin(), topics.end(), title_less());
      return topics;
}

std::vector<topic> generate_ability_topics(const bool sort_generated)
{
      std::vector<topic> topics;
      std::map<t_string, std::string> ability_description;
      std::map<t_string, std::set<std::string> > ability_units;
      // Look through all the unit types, check if a unit of this type
      // should have a full description, if so, add this units abilities
      // for display. We do not want to show abilities that the user has
      // not encountered yet.
      foreach (const unit_type_data::unit_type_map::value_type &i, unit_types.types())
      {
            const unit_type &type = i.second;
            if (description_type(type) == FULL_DESCRIPTION) {

                  std::vector<t_string> const* abil_vecs[2];
                  abil_vecs[0] = &type.abilities();
                  abil_vecs[1] = &type.adv_abilities();

                  std::vector<std::string> const* desc_vecs[2];
                  desc_vecs[0] = &type.ability_tooltips();
                  desc_vecs[1] = &type.adv_ability_tooltips();

                  for(int i=0; i<2; ++i) {
                        std::vector<t_string> const& abil_vec = *abil_vecs[i];
                        std::vector<std::string> const& desc_vec = *desc_vecs[i];
                        for(size_t j=0; j < abil_vec.size(); ++j) {
                              t_string const& abil_name = abil_vec[j];
                              if (ability_description.find(abil_name) == ability_description.end()) {
                                    //new ability, generate a descripion
                                    if(j >= desc_vec.size()) {
                                          ability_description[abil_name] = "";
                                    } else {
                                          std::string const& abil_desc = desc_vec[j];
                                          const size_t colon_pos = abil_desc.find(':');
                                          if(colon_pos != std::string::npos && colon_pos + 1 < abil_desc.length()) {
                                                // Remove the first colon and the following newline.
                                                ability_description[abil_name] = abil_desc.substr(colon_pos + 2);
                                          } else {
                                                ability_description[abil_name] = abil_desc;
                                          }
                                    }
                              }

                              if (!type.hide_help()) {
                                    //add a link in the list of units having this ability
                                    std::string type_name = type.type_name();
                                    std::string ref_id = unit_prefix +  type.id();
                                    //we put the translated name at the beginning of the hyperlink,
                                    //so the automatic alphabetic sorting of std::set can use it
                                    std::string link =  "<ref>text='" + escape(type_name) + "' dst='" + escape(ref_id) + "'</ref>";
                                    ability_units[abil_name].insert(link);
                              }
                        }
                  }
            }
      }

      for (std::map<t_string, std::string>::iterator a = ability_description.begin(); a != ability_description.end(); ++a) {
            // we generate topic's id using the untranslated version of the ability's name
            std::string id = "ability_" + a->first.base_str();
            std::stringstream text;
            text << a->second;  //description
            text << "\n\n" << _("<header>text='Units having this ability'</header>") << "\n";
            std::set<std::string>& units = ability_units[a->first];
            for (std::set<std::string>::iterator u = units.begin(); u != units.end();++u) {
                  text << (*u) << "\n";
            }

            topics.push_back( topic(a->first, id, text.str()) );
      }

      if (sort_generated)
            std::sort(topics.begin(), topics.end(), title_less());
      return topics;
}

std::vector<topic> generate_faction_topics(const bool sort_generated)
{
      std::vector<topic> topics;
      const config& era = game_cfg->child("era");
      if (era) {
            std::vector<std::string> faction_links;
            foreach (const config &f, era.child_range("multiplayer_side")) {
                  const std::string& id = f["id"];
                  if (id == "Random")
                        continue;

                  std::stringstream text;

                  const std::string& description = f["description"];
                  if (!description.empty()) {
                        text << description << "\n";
                        text << "\n";
                  }

                  text << "<header>text='" << _("Leaders:") << "'</header>" << "\n";
                  const std::vector<std::string> leaders =
                              make_unit_links_list( utils::split(f["leader"]), true );
                  foreach (const std::string &link, leaders) {
                        text << link << "\n";
                  }

                  text << "\n";

                  text << "<header>text='" << _("Recruits:") << "'</header>" << "\n";
                  const std::vector<std::string> recruits =
                              make_unit_links_list( utils::split(f["recruit"]), true );
                  foreach (const std::string &link, recruits) {
                        text << link << "\n";
                  }

                  const std::string name = f["name"];
                  const std::string ref_id = faction_prefix + id;
                  topics.push_back( topic(name, ref_id, text.str()) );
                  faction_links.push_back(make_link(name, ref_id));
            }

            std::stringstream text;
            text << "<header>text='" << _("Era:") << " " << era["name"] << "'</header>" << "\n";
            text << "\n";
            const std::string& description = era["description"];
            if (!description.empty()) {
                  text << description << "\n";
                  text << "\n";
            }

            text << "<header>text='" << _("Factions:") << "'</header>" << "\n";

            std::sort(faction_links.begin(), faction_links.end());
            foreach (const std::string &link, faction_links) {
                  text << link << "\n";
            }

            topics.push_back( topic(_("Factions"), "..factions_section", text.str()) );
      } else {
            topics.push_back( topic( _("Factions"), "..factions_section",
                  _("Factions are only used in multiplayer")) );
      }

      if (sort_generated)
            std::sort(topics.begin(), topics.end(), title_less());
      return topics;
}


class unit_topic_generator: public topic_generator
{
      const unit_type& type_;
      typedef std::pair< std::string, unsigned > item;
      void push_header(std::vector< item > &row, char const *name) const {
            row.push_back(item(bold(name), font::line_width(name, normal_font_size, TTF_STYLE_BOLD)));
      }
public:
      unit_topic_generator(const unit_type &t): type_(t) {}
      virtual std::string operator()() const {
            // this will force the lazy loading to build this unit
            unit_types.find(type_.id(), unit_type::WITHOUT_ANIMATIONS);

            std::stringstream ss;
            std::string clear_stringstream;
            const std::string detailed_description = type_.unit_description();
            const unit_type& female_type = type_.get_gender_unit_type(unit_race::FEMALE);
            const unit_type& male_type = type_.get_gender_unit_type(unit_race::MALE);

            // Show the unit's image and its level.
#ifdef LOW_MEM
            ss << "<img>src='" << male_type.image() << "'</img> ";
#else
            ss << "<img>src='" << male_type.image() << "~RC(" << male_type.flag_rgb() << ">1)" << "'</img> ";
#endif

            if (&female_type != &male_type)
#ifdef LOW_MEM
                  ss << "<img>src='" << female_type.image() << "'</img> ";
#else
                  ss << "<img>src='" << female_type.image() << "~RC(" << female_type.flag_rgb() << ">1)" << "'</img> ";
#endif


            ss << "<format>font_size=" << font::relative_size(11) << " text=' " << escape(_("level"))
               << " " << type_.level() << "'</format>";

            const std::string& male_portrait = male_type.image_profile();
            const std::string& female_portrait = female_type.image_profile();

            if (male_portrait.empty() == false && male_portrait != male_type.image()) {
                  ss << "<img>src='" << male_portrait << "' align='right'</img> ";
            }

            if (female_portrait.empty() == false && female_portrait != male_portrait && female_portrait != female_type.image()) {
                  ss << "<img>src='" << female_portrait << "' align='right'</img> ";
            }

            ss << "\n";

            // Print cross-references to units that this unit advances from/to.
            // Cross reference to the topics containing information about those units.
            const bool first_reverse_value = true;
            bool reverse = first_reverse_value;
            do {
                  std::vector<std::string> adv_units =
                        reverse ? type_.advances_from() : type_.advances_to();
                  bool first = true;

                  foreach (const std::string &adv, adv_units)
                  {
                        const unit_type *type = unit_types.find(adv);
                        if (!type || type->hide_help()) continue;

                        if (first) {
                              if (reverse)
                                    ss << _("Advances from: ");
                              else
                                    ss << _("Advances to: ");
                              first = false;
                        } else
                              ss << ", ";

                        std::string lang_unit = type->type_name();
                        std::string ref_id;
                        if (description_type(*type) == FULL_DESCRIPTION) {
                              ref_id = unit_prefix + type->id();
                        } else {
                              ref_id = unknown_unit_topic;
                              lang_unit += " (?)";
                        }
                        ss << "<ref>dst='" << escape(ref_id) << "' text='" << escape(lang_unit) << "'</ref>";
                  }
                  ss << "\n"; //added even if empty, to avoid shifting

                  reverse = !reverse; //switch direction
            } while(reverse != first_reverse_value); // don't restart

            // Print the race of the unit, cross-reference it to the
            // respective topic.
            const std::string race_id = type_.race();
            std::string race_name;
            if (const unit_race *r = unit_types.find_race(race_id)) {
                  race_name = r->plural_name();
            } else {
                  race_name = _ ("race^Miscellaneous");
            }
            ss << _("Race: ");
            ss << "<ref>dst='" << escape("..race_"+race_id) << "' text='" << escape(race_name) << "'</ref>";
            ss << "\n";

            // Print the abilities the units has, cross-reference them
            // to their respective topics.
            if (!type_.abilities().empty()) {
                  ss << _("Abilities: ");
                  for(std::vector<t_string>::const_iterator ability_it = type_.abilities().begin(),
                         ability_end = type_.abilities().end();
                         ability_it != ability_end; ++ability_it) {
                        const std::string ref_id = "ability_" + ability_it->base_str();
                        std::string lang_ability = gettext(ability_it->c_str());
                        ss << "<ref>dst='" << escape(ref_id) << "' text='" << escape(lang_ability)
                           << "'</ref>";
                        if (ability_it + 1 != ability_end)
                              ss << ", ";
                  }
                  ss << "\n";
            }

            // Print the extra AMLA upgrage abilities, cross-reference them
            // to their respective topics.
            if (!type_.adv_abilities().empty()) {
                  ss << _("Ability Upgrades: ");
                  for(std::vector<t_string>::const_iterator ability_it = type_.adv_abilities().begin(),
                         ability_end = type_.adv_abilities().end();
                         ability_it != ability_end; ++ability_it) {
                        const std::string ref_id = "ability_" + ability_it->base_str();
                        std::string lang_ability = gettext(ability_it->c_str());
                        ss << "<ref>dst='" << escape(ref_id) << "' text='" << escape(lang_ability)
                           << "'</ref>";
                        if (ability_it + 1 != ability_end)
                              ss << ", ";
                  }
                  ss << "\n";
            }
            ss << "\n";

            // Print some basic information such as HP and movement points.
            ss << _("HP: ") << type_.hitpoints() << jump(30)
               << _("Moves: ") << type_.movement() << jump(30)
               << _("Cost: ") << type_.cost() << jump(30)
               << _("Alignment: ")
               << "<ref>dst='time_of_day' text='"
               << type_.alignment_description(type_.alignment(), type_.genders().front())
               << "'</ref>"
               << jump(30);
            if (type_.can_advance())
                  ss << _("Required XP: ") << type_.experience_needed();

            // Print the detailed description about the unit.
            ss << "\n\n" << detailed_description;

            // Print the different attacks a unit has, if it has any.
            std::vector<attack_type> attacks = type_.attacks();
            if (!attacks.empty()) {
                  // Print headers for the table.
                  ss << "\n\n<header>text='" << escape(_("unit help^Attacks"))
                     << "'</header>\n\n";
                  table_spec table;

                  std::vector<item> first_row;
                  // Dummy element, icons are below.
                  first_row.push_back(item("", 0));
                  push_header(first_row, _("unit help^Name"));
                  push_header(first_row, _("Type"));
                  push_header(first_row, _("Strikes"));
                  push_header(first_row, _("Range"));
                  push_header(first_row, _("Special"));
                  table.push_back(first_row);
                  // Print information about every attack.
                  for(std::vector<attack_type>::const_iterator attack_it = attacks.begin(),
                         attack_end = attacks.end();
                         attack_it != attack_end; ++attack_it) {
                        std::string lang_weapon = attack_it->name();
                        std::string lang_type = gettext(attack_it->type().c_str());
                        std::vector<item> row;
                        std::stringstream attack_ss;
                        attack_ss << "<img>src='" << (*attack_it).icon() << "'</img>";
                        row.push_back(std::make_pair(attack_ss.str(),
                                               image_width(attack_it->icon())));
                        push_tab_pair(row, lang_weapon);
                        push_tab_pair(row, lang_type);
                        attack_ss.str(clear_stringstream);
                        attack_ss << attack_it->damage() << '-' << attack_it->num_attacks() << " " << attack_it->accuracy_parry_description();
                        push_tab_pair(row, attack_ss.str());
                        attack_ss.str(clear_stringstream);
                        push_tab_pair(row, _((*attack_it).range().c_str()));
                        // Show this attack's special, if it has any. Cross
                        // reference it to the section describing the
                        // special.
                        std::vector<t_string> specials = attack_it->special_tooltips(true);
                        if(!specials.empty())
                        {
                              std::string lang_special = "";
                              std::vector<t_string>::iterator sp_it;
                              for (sp_it = specials.begin(); sp_it != specials.end(); ++sp_it) {
                                    const std::string ref_id = std::string("weaponspecial_")
                                          + sp_it->base_str();
                                    lang_special = (*sp_it);
                                    attack_ss << "<ref>dst='" << escape(ref_id)
                                                  << "' text='" << escape(lang_special) << "'</ref>";
                                    if((sp_it + 1) != specials.end() && (sp_it + 2) != specials.end())
                                    {
                                          attack_ss << ", "; //comma placed before next special
                                    }
                                    ++sp_it; //skip description
                              }
                              row.push_back(std::make_pair(attack_ss.str(),
                                    font::line_width(lang_special, normal_font_size)));

                        }
                        table.push_back(row);
                  }
                  ss << generate_table(table);
            }

            // Print the resistance table of the unit.
            ss << "\n\n<header>text='" << escape(_("Resistances"))
               << "'</header>\n\n";
            table_spec resistance_table;
            std::vector<item> first_res_row;
            push_header(first_res_row, _("Attack Type"));
            push_header(first_res_row, _("Resistance"));
            resistance_table.push_back(first_res_row);
            const unit_movement_type &movement_type = type_.movement_type();
            string_map dam_tab = movement_type.damage_table();
            for(string_map::const_iterator dam_it = dam_tab.begin(), dam_end = dam_tab.end();
                   dam_it != dam_end; ++dam_it) {
                  std::vector<item> row;
                  int resistance = 100 - atoi((*dam_it).second.c_str());
                  char resi[16];
                  snprintf(resi,sizeof(resi),"% 4d%%",resistance);      // range: -100% .. +70%
                  std::string color;
                  if (resistance < 0)
                        color = "red";
                  else if (resistance <= 20)
                        color = "yellow";
                  else if (resistance <= 40)
                        color = "white";
                  else
                        color = "green";

                  std::string lang_weapon = gettext(dam_it->first.c_str());
                  push_tab_pair(row, lang_weapon);
                  std::stringstream str;
                  str << "<format>color=" << color << " text='"<< resi << "'</format>";
                  const std::string markup = str.str();
                  str.str(clear_stringstream);
                  str << resi;
                  row.push_back(std::make_pair(markup,
                                         font::line_width(str.str(), normal_font_size)));
                  resistance_table.push_back(row);
            }
            ss << generate_table(resistance_table);

            if (map != NULL) {
                  // Print the terrain modifier table of the unit.
                  ss << "\n\n<header>text='" << escape(_("Terrain Modifiers"))
                     << "'</header>\n\n";
                  std::vector<item> first_row;
                  table_spec table;
                  push_header(first_row, _("Terrain"));
                  push_header(first_row, _("Defense"));
                  push_header(first_row, _("Movement Cost"));

                  table.push_back(first_row);
                  std::set<t_translation::t_terrain>::const_iterator terrain_it =
                        preferences::encountered_terrains().begin();

                  for (; terrain_it != preferences::encountered_terrains().end();
                        ++terrain_it) {
                        const t_translation::t_terrain terrain = *terrain_it;
                        if (terrain == t_translation::FOGGED || terrain == t_translation::VOID_TERRAIN || terrain == t_translation::OFF_MAP_USER)
                              continue;
                        const terrain_type& info = map->get_terrain_info(terrain);

                        if (info.union_type().size() == 1 && info.union_type()[0] == info.number() && info.is_nonnull()) {
                              std::vector<item> row;
                              const std::string& name = info.name();
                              const std::string id = info.id();
                              const int moves = movement_type.movement_cost(*map,terrain);
                              std::stringstream str;
                              str << "<ref>text='" << escape(name) << "' dst='"
                                    << escape(std::string("terrain_") + id) << "'</ref>";
                              row.push_back(std::make_pair(str.str(),
                                    font::line_width(name, normal_font_size)));

                              //defense  -  range: +10 % .. +70 %
                              str.str(clear_stringstream);
                              const int defense =
                                    100 - movement_type.defense_modifier(*map,terrain);
                              std::string color;
                              if (defense <= 10)
                                    color = "red";
                              else if (defense <= 30)
                                    color = "yellow";
                              else if (defense <= 50)
                                    color = "white";
                              else
                                    color = "green";

                              str << "<format>color=" << color << " text='"<< defense << "%'</format>";
                              const std::string markup = str.str();
                              str.str(clear_stringstream);
                              str << defense << "%";
                              row.push_back(std::make_pair(markup,
                                         font::line_width(str.str(), normal_font_size)));

                              //movement  -  range: 1 .. 5, unit_movement_type::UNREACHABLE=impassable
                              str.str(clear_stringstream);
                              const bool cannot_move = moves > type_.movement();
                              if (cannot_move)        // cannot move in this terrain
                                    color = "red";
                              else if (moves > 1)
                                    color = "yellow";
                              else
                                    color = "white";

                              str << "<format>color=" << color << " text='";
                              // A 5 MP margin; if the movement costs go above
                              // the unit's max moves + 5, we replace it with dashes.
                              if(cannot_move && (moves > type_.movement() + 5)) {
                                    str << "'-'";
                              } else {
                                    str << moves;
                              }
                              str << "'</format>";
                              push_tab_pair(row, str.str());

                              table.push_back(row);
                        }
                  }
                  ss << generate_table(table);
            }
            return ss.str();
      }
};

std::string make_unit_link(const std::string& type_id)
{
      std::string link;

      const unit_type *type = unit_types.find(type_id);
      if (!type) {
            std::cerr << "Unknown unit type : " << type_id << "\n";
            // don't return an hyperlink (no page)
            // instead show the id (as hint)
            link = type_id;
      } else if (!type->hide_help()) {
            std::string name = type->type_name();
            std::string ref_id;
            if (description_type(*type) == FULL_DESCRIPTION) {
                  ref_id = unit_prefix + type->id();
            } else {
                  ref_id = unknown_unit_topic;
                  name += " (?)";
            }
            link =  make_link(name, ref_id);
      } // if hide_help then link is an empty string

      return link;
}

std::vector<std::string> make_unit_links_list(const std::vector<std::string>& type_id_list, bool ordered)
{
      std::vector<std::string> links_list;
      foreach (const std::string &type_id, type_id_list) {
            std::string unit_link = make_unit_link(type_id);
            if (!unit_link.empty())
                  links_list.push_back(unit_link);
      }

      if (ordered)
            std::sort(links_list.begin(), links_list.end());

      return links_list;
}

void generate_races_sections(const config *help_cfg, section &sec, int level)
{
      std::set<std::string> races;
      std::set<std::string> visible_races;

      foreach (const unit_type_data::unit_type_map::value_type &i, unit_types.types())
      {
            const unit_type &type = i.second;
            UNIT_DESCRIPTION_TYPE desc_type = description_type(type);
            if (desc_type == FULL_DESCRIPTION) {
                  races.insert(type.race());
                  if (!type.hide_help())
                        visible_races.insert(type.race());
            }
      }

      std::stringstream text;

      for(std::set<std::string>::iterator it = races.begin(); it != races.end(); ++it) {
            section race_section;
            config section_cfg;

            bool hidden = (visible_races.count(*it) == 0);

            section_cfg["id"] = hidden_symbol(hidden) + race_prefix + *it;

            std::string title;
            if (const unit_race *r = unit_types.find_race(*it)) {
                  title = r->plural_name();
            } else {
                  title = _ ("race^Miscellaneous");
            }
            section_cfg["title"] = title;

            section_cfg["generator"] = "units:" + *it;

            parse_config_internal(help_cfg, &section_cfg, race_section, level+1);
            sec.add_section(race_section);
      }
}


std::vector<topic> generate_unit_topics(const bool sort_generated, const std::string& race)
{
      std::vector<topic> topics;
      std::set<std::string> race_units;

      foreach (const unit_type_data::unit_type_map::value_type &i, unit_types.types())
      {
            const unit_type &type = i.second;

            if (type.race() != race)
                  continue;
            UNIT_DESCRIPTION_TYPE desc_type = description_type(type);
            if (desc_type != FULL_DESCRIPTION)
                  continue;

            const std::string type_name = type.type_name();
            const std::string ref_id = hidden_symbol(type.hide_help()) + unit_prefix +  type.id();
            topic unit_topic(type_name, ref_id, "");
            unit_topic.text = new unit_topic_generator(type);
            topics.push_back(unit_topic);

            if (!type.hide_help()) {
                  // we also record an hyperlink of this unit
                  // in the list used for the race topic
                  std::string link =  "<ref>text='" + escape(type_name) + "' dst='" + escape(ref_id) + "'</ref>";
                  race_units.insert(link);
            }
      }

      //generate the hidden race description topic
      std::string race_id = "..race_"+race;
      std::string race_name;
      std::string race_description;
      if (const unit_race *r = unit_types.find_race(race)) {
            race_name = r->plural_name();
            race_description = r->description();
            // if (description.empty()) description =  _("No description Available");
      } else {
            race_name = _ ("race^Miscellaneous");
            // description =  _("Here put the description of the Miscellaneous race");
      }

      std::stringstream text;
      text << race_description;
      text << "\n\n" << _("<header>text='Units of this race'</header>") << "\n";
      for (std::set<std::string>::iterator u = race_units.begin(); u != race_units.end();++u) {
            text << (*u) << "\n";
      }
      topics.push_back(topic(race_name, race_id, text.str()) );

      if (sort_generated)
            std::sort(topics.begin(), topics.end(), title_less());
      return topics;
}

UNIT_DESCRIPTION_TYPE description_type(const unit_type &type)
{
      if (game_config::debug) {
            return FULL_DESCRIPTION;
      }

      const std::set<std::string> &encountered_units = preferences::encountered_units();
      if (encountered_units.find(type.id()) != encountered_units.end()) {
            return FULL_DESCRIPTION;
      }
      return NO_DESCRIPTION;
}

std::string generate_about_text()
{
      std::vector<std::string> about_lines = about::get_text();
      std::vector<std::string> res_lines;
      std::transform(about_lines.begin(), about_lines.end(), std::back_inserter(res_lines),
                           about_text_formatter());
      res_lines.erase(std::remove(res_lines.begin(), res_lines.end(), ""), res_lines.end());
      std::string text = utils::join(res_lines, '\n');
      return text;
}

std::string generate_contents_links(const std::string& section_name, config const *help_cfg)
{
      config const &section_cfg = help_cfg->find_child("section", "id", section_name);
      if (!section_cfg) {
            return std::string();
      }

      std::ostringstream res;

            std::vector<std::string> topics = utils::quoted_split(section_cfg["topics"]);

            // we use an intermediate structure to allow a conditional sorting
            typedef std::pair<std::string,std::string> link;
            std::vector<link> topics_links;

            std::vector<std::string>::iterator t;
            // Find all topics in this section.
            for (t = topics.begin(); t != topics.end(); ++t) {
                  if (config const &topic_cfg = help_cfg->find_child("topic", "id", *t)) {
                        std::string id = topic_cfg["id"];
                        if (is_visible_id(id))
                              topics_links.push_back(link(topic_cfg["title"], id));
                  }
            }

            if (section_cfg["sort_topics"] == "yes") {
                  std::sort(topics_links.begin(),topics_links.end());
            }

            std::vector<link>::iterator l;
            for (l = topics_links.begin(); l != topics_links.end(); ++l) {
                  std::string link =  "<ref>text='" + escape(l->first) + "' dst='" + escape(l->second) + "'</ref>";
                  res << link <<"\n";
            }

            return res.str();
}

std::string generate_contents_links(const section &sec, const std::vector<topic>& topics)
{
            std::stringstream res;

            section_list::const_iterator s;
            for (s = sec.sections.begin(); s != sec.sections.end(); ++s) {
                  if (is_visible_id((*s)->id)) {
                        std::string link =  "<ref>text='" + escape((*s)->title) + "' dst='.." + escape((*s)->id) + "'</ref>";
                        res << link <<"\n";
                  }
            }

            std::vector<topic>::const_iterator t;
            for (t = topics.begin(); t != topics.end(); ++t) {
                  if (is_visible_id(t->id)) {
                        std::string link =  "<ref>text='" + escape(t->title) + "' dst='" + escape(t->id) + "'</ref>";
                        res << link <<"\n";
                  }
            }

            return res.str();
}

01858 bool topic::operator==(const topic &t) const
{
      return t.id == id;
}

01863 bool topic::operator<(const topic &t) const
{
      return id < t.id;
}

section::~section()
{
      std::for_each(sections.begin(), sections.end(), delete_section());
}

section::section(const section &sec) :
      title(sec.title),
      id(sec.id),
      topics(sec.topics),
      sections(),
      level(sec.level)
{
      std::transform(sec.sections.begin(), sec.sections.end(),
                           std::back_inserter(sections), create_section());
}

section& section::operator=(const section &sec)
{
      title = sec.title;
      id = sec.id;
      level = sec.level;
      std::copy(sec.topics.begin(), sec.topics.end(), std::back_inserter(topics));
      std::transform(sec.sections.begin(), sec.sections.end(),
                           std::back_inserter(sections), create_section());
      return *this;
}


01896 bool section::operator==(const section &sec) const
{
      return sec.id == id;
}

01901 bool section::operator<(const section &sec) const
{
      return id < sec.id;
}

01906 void section::add_section(const section &s)
{
      sections.push_back(new section(s));
}

void section::clear()
{
      topics.clear();
      std::for_each(sections.begin(), sections.end(), delete_section());
      sections.clear();
}

help_menu::help_menu(CVideo &video, section const &toplevel, int max_height) :
      gui::menu(video, empty_string_vector, true, max_height, -1, NULL, &gui::menu::bluebg_style),
      visible_items_(),
      toplevel_(toplevel),
      expanded_(),
      restorer_(),
      rect_(),
      chosen_topic_(NULL),
      selected_item_(&toplevel, "")
{
      silent_ = true; //silence the default menu sounds
      update_visible_items(toplevel_);
      display_visible_items();
      if (!visible_items_.empty())
            selected_item_ = visible_items_.front();
}

01935 bool help_menu::expanded(const section &sec)
{
      return expanded_.find(&sec) != expanded_.end();
}

01940 void help_menu::expand(const section &sec)
{
      if (sec.id != "toplevel" && expanded_.insert(&sec).second) {
            sound::play_UI_sound(game_config::sounds::menu_expand);
      }
}

01947 void help_menu::contract(const section &sec)
{
      if (expanded_.erase(&sec)) {
            sound::play_UI_sound(game_config::sounds::menu_contract);
      }
}

01954 void help_menu::update_visible_items(const section &sec, unsigned level)
{
      if (level == 0) {
            // Clear if this is the top level, otherwise append items.
            visible_items_.clear();
      }
      section_list::const_iterator sec_it;
      for (sec_it = sec.sections.begin(); sec_it != sec.sections.end(); ++sec_it) {
            if (is_visible_id((*sec_it)->id)) {
                  const std::string vis_string = get_string_to_show(*(*sec_it), level + 1);
                  visible_items_.push_back(visible_item(*sec_it, vis_string));
                  if (expanded(*(*sec_it))) {
                        update_visible_items(*(*sec_it), level + 1);
                  }
            }
      }
      topic_list::const_iterator topic_it;
      for (topic_it = sec.topics.begin(); topic_it != sec.topics.end(); ++topic_it) {
            if (is_visible_id(topic_it->id)) {
                  const std::string vis_string = get_string_to_show(*topic_it, level + 1);
                  visible_items_.push_back(visible_item(&(*topic_it), vis_string));
            }
      }
}

01979 std::string help_menu::indented_icon(const std::string& icon, const unsigned level) {
      std::stringstream to_show;
      for (unsigned i = 1; i < level; ++i)      {
            to_show << IMAGE_PREFIX << indentation_img << IMG_TEXT_SEPARATOR;
      }

      to_show << IMAGE_PREFIX << icon;
      return to_show.str();
}

01989 std::string help_menu::get_string_to_show(const section &sec, const unsigned level)
{
      std::stringstream to_show;
      to_show << indented_icon(expanded(sec) ? open_section_img : closed_section_img, level)
             << IMG_TEXT_SEPARATOR << sec.title;
      return to_show.str();
}

01997 std::string help_menu::get_string_to_show(const topic &topic, const unsigned level)
{
      std::stringstream to_show;
      to_show <<  indented_icon(topic_img, level)
            << IMG_TEXT_SEPARATOR << topic.title;
      return to_show.str();
}

02005 bool help_menu::select_topic_internal(const topic &t, const section &sec)
{
      topic_list::const_iterator tit =
            std::find(sec.topics.begin(), sec.topics.end(), t);
      if (tit != sec.topics.end()) {
            // topic starting with ".." are assumed as rooted in the parent section
            // and so only expand the parent when selected
            if (t.id.size()<2 || t.id[0] != '.' || t.id[1] != '.')
                  expand(sec);
            return true;
      }
      section_list::const_iterator sit;
      for (sit = sec.sections.begin(); sit != sec.sections.end(); ++sit) {
            if (select_topic_internal(t, *(*sit))) {
                  expand(sec);
                  return true;
            }
      }
      return false;
}

02026 void help_menu::select_topic(const topic &t)
{
      if (selected_item_ == t) {
            // The requested topic is already selected.
            return;
      }
      if (select_topic_internal(t, toplevel_)) {
            update_visible_items(toplevel_);
            for (std::vector<visible_item>::const_iterator it = visible_items_.begin();
                   it != visible_items_.end(); ++it) {
                  if (*it == t) {
                        selected_item_ = *it;
                        break;
                  }
            }
            display_visible_items();
      }
}

02045 int help_menu::process()
{
      int res = menu::process();
      int mousex, mousey;
      SDL_GetMouseState(&mousex,&mousey);

      if (!visible_items_.empty() &&
            static_cast<size_t>(res) < visible_items_.size()) {

            selected_item_ = visible_items_[res];
            const section* sec = selected_item_.sec;
            if (sec != NULL) {
                  // Check how we click on the section
                  int x = mousex - menu::location().x;

                  const std::string icon_img = expanded(*sec) ? open_section_img : closed_section_img;
                  // we remove the right tickness (ne present bewteen icon and text)
                  int text_start = style_->item_size(indented_icon(icon_img, sec->level)).w - style_->get_thickness();

                  // NOTE: if you want to forbid click to the left of the icon
                  // also check x >= text_start-image_width(icon_img)
                  if (menu::double_clicked() || x < text_start) {
                        // Open or close a section if we double-click on it
                        // or do simple click on the icon.
                        expanded(*sec) ? contract(*sec) : expand(*sec);
                        update_visible_items(toplevel_);
                        display_visible_items();
                  } else if (x >= text_start){
                        // click on title open the topic associated to this section
                        chosen_topic_ = find_topic(toplevel, ".."+sec->id );
                  }
            } else if (selected_item_.t != NULL) {
                  /// Choose a topic if it is clicked.
                  chosen_topic_ = selected_item_.t;
            }
      }
      return res;
}

02084 const topic *help_menu::chosen_topic()
{
      const topic *ret = chosen_topic_;
      chosen_topic_ = NULL;
      return ret;
}

02091 void help_menu::display_visible_items()
{
      std::vector<std::string> menu_items;
      for(std::vector<visible_item>::const_iterator items_it = visible_items_.begin(),
             end = visible_items_.end(); items_it != end; ++items_it) {
            std::string to_show = items_it->visible_string;
            if (selected_item_ == *items_it)
                  to_show = std::string("*") + to_show;
            menu_items.push_back(to_show);
      }
      set_items(menu_items, false, true);
}

help_menu::visible_item::visible_item(const section *_sec, const std::string &vis_string) :
      t(NULL), sec(_sec), visible_string(vis_string) {}

help_menu::visible_item::visible_item(const topic *_t, const std::string &vis_string) :
      t(_t), sec(NULL), visible_string(vis_string) {}

bool help_menu::visible_item::operator==(const section &_sec) const
{
      return sec != NULL && *sec == _sec;
}

bool help_menu::visible_item::operator==(const topic &_t) const
{
      return t != NULL && *t == _t;
}

bool help_menu::visible_item::operator==(const visible_item &vis_item) const
{
      return t == vis_item.t && sec == vis_item.sec;
}

help_text_area::help_text_area(CVideo &video, const section &toplevel) :
      gui::scrollarea(video),
      items_(),
      last_row_(),
      toplevel_(toplevel),
      shown_topic_(NULL),
      title_spacing_(16),
      curr_loc_(0, 0),
      min_row_height_(font::get_max_height(normal_font_size)),
      curr_row_height_(min_row_height_),
      contents_height_(0)
{
      set_scroll_rate(40);
}

void help_text_area::set_inner_location(SDL_Rect const &rect)
{
      bg_register(rect);
      if (shown_topic_)
            set_items();
}

02147 void help_text_area::show_topic(const topic &t)
{
      shown_topic_ = &t;
      set_items();
      set_dirty(true);
}


help_text_area::item::item(surface surface, int x, int y, const std::string& _text,
                                       const std::string& reference_to, bool _floating,
                                       bool _box, ALIGNMENT alignment) :
      rect(),
      surf(surface),
      text(_text),
      ref_to(reference_to),
      floating(_floating), box(_box),
      align(alignment)
{
      rect.x = x;
      rect.y = y;
      rect.w = box ? surface->w + box_width * 2 : surface->w;
      rect.h = box ? surface->h + box_width * 2 : surface->h;
}

help_text_area::item::item(surface surface, int x, int y, bool _floating,
                                       bool _box, ALIGNMENT alignment) :
      rect(),
      surf(surface),
      text(""),
      ref_to(""),
      floating(_floating),
      box(_box), align(alignment)
{
      rect.x = x;
      rect.y = y;
      rect.w = box ? surface->w + box_width * 2 : surface->w;
      rect.h = box ? surface->h + box_width * 2 : surface->h;
}

02186 void help_text_area::set_items()
{
      last_row_.clear();
      items_.clear();
      curr_loc_.first = 0;
      curr_loc_.second = 0;
      curr_row_height_ = min_row_height_;
      // Add the title item.
      const std::string show_title =
            font::make_text_ellipsis(shown_topic_->title, title_size, inner_location().w);
      surface surf(font::get_rendered_text(show_title, title_size,
                                   font::NORMAL_COLOUR, TTF_STYLE_BOLD));
      if (surf != NULL) {
            add_item(item(surf, 0, 0, show_title));
            curr_loc_.second = title_spacing_;
            contents_height_ = title_spacing_;
            down_one_line();
      }
      // Parse and add the text.
      std::vector<std::string> const &parsed_items = shown_topic_->text.parsed_text();
      std::vector<std::string>::const_iterator it;
      for (it = parsed_items.begin(); it != parsed_items.end(); ++it) {
            if (*it != "" && (*it)[0] == '[') {
                  // Should be parsed as WML.
                  try {
                        config cfg;
                        std::istringstream stream(*it);
                        read(cfg, stream);

#define TRY(name) do { \
                        if (config &child = cfg.child(#name)) \
                              handle_##name##_cfg(child); \
                        } while (0)

                        TRY(ref);
                        TRY(img);
                        TRY(bold);
                        TRY(italic);
                        TRY(header);
                        TRY(jump);
                        TRY(format);

#undef TRY

                  }
                  catch (config::error e) {
                        std::stringstream msg;
                        msg << "Error when parsing help markup as WML: '" << e.message << "'";
                        throw parse_error(msg.str());
                  }
            }
            else {
                  add_text_item(*it);
            }
      }
      down_one_line(); // End the last line.
      int h = height();
      set_position(0);
      set_full_size(contents_height_);
      set_shown_size(h);
}

void help_text_area::handle_ref_cfg(const config &cfg)
{
      const std::string dst = cfg["dst"];
      const std::string text = cfg["text"];
      const bool force = utils::string_bool(cfg["force"], false);

      if (dst == "") {
            std::stringstream msg;
            msg << "Ref markup must have dst attribute. Please submit a bug"
                   " report if you have not modified the game files yourself. Erroneous config: ";
            write(msg, cfg);
            throw parse_error(msg.str());
      }

      if (find_topic(toplevel_, dst) == NULL && !force) {
            // detect the broken link but quietly silence the hyperlink for normal user
            add_text_item(text, game_config::debug ? dst : "", true);

            // FIXME: workaround: if different campaigns define different
            // terrains, some terrains available in one campaign will
            // appear in the list of seen terrains, and be displayed in the
            // help, even if the current campaign does not handle such
            // terrains. This will lead to the unit page generator creating
            // invalid references.
            //
            // Disabling this is a kludgy workaround until the
            // encountered_terrains system is fixed
            //
            // -- Ayin apr 8 2005
#if 0
            if (game_config::debug) {
                  std::stringstream msg;
                  msg << "Reference to non-existent topic '" << dst
                      << "'. Please submit a bug report if you have not"
                         "modified the game files yourself. Erroneous config: ";
                  write(msg, cfg);
                  throw parse_error(msg.str());
            }
#endif
      } else {
            add_text_item(text, dst);
      }
}

void help_text_area::handle_img_cfg(const config &cfg)
{
      const std::string src = cfg["src"];
      const std::string align = cfg["align"];
      const bool floating = utils::string_bool(cfg["float"], false);
      bool box = true;
      if (cfg["box"] != "" && !utils::string_bool(cfg["box"], false)) {
            box = false;
      }
      if (src == "") {
            throw parse_error("Img markup must have src attribute.");
      }
      add_img_item(src, align, floating, box);
}

void help_text_area::handle_bold_cfg(const config &cfg)
{
      const std::string text = cfg["text"];
      if (text == "") {
            throw parse_error("Bold markup must have text attribute.");
      }
      add_text_item(text, "", false, -1, true);
}

void help_text_area::handle_italic_cfg(const config &cfg)
{
      const std::string text = cfg["text"];
      if (text == "") {
            throw parse_error("Italic markup must have text attribute.");
      }
      add_text_item(text, "", false, -1, false, true);
}

void help_text_area::handle_header_cfg(const config &cfg)
{
      const std::string text = cfg["text"];
      if (text == "") {
            throw parse_error("Header markup must have text attribute.");
      }
      add_text_item(text, "", false, title2_size, true);
}

void help_text_area::handle_jump_cfg(const config &cfg)
{
      const std::string amount_str = cfg["amount"];
      const std::string to_str = cfg["to"];
      if (amount_str == "" && to_str == "") {
            throw parse_error("Jump markup must have either a to or an amount attribute.");
      }
      unsigned jump_to = curr_loc_.first;
      if (amount_str != "") {
            unsigned amount;
            try {
                  amount = lexical_cast<unsigned, std::string>(amount_str);
            }
            catch (bad_lexical_cast) {
                  throw parse_error("Invalid amount the amount attribute in jump markup.");
            }
            jump_to += amount;
      }
      if (to_str != "") {
            unsigned to;
            try {
                  to = lexical_cast<unsigned, std::string>(to_str);
            }
            catch (bad_lexical_cast) {
                  throw parse_error("Invalid amount in the to attribute in jump markup.");
            }
            if (to < jump_to) {
                  down_one_line();
            }
            jump_to = to;
      }
      if (jump_to != 0 && static_cast<int>(jump_to) <
            get_max_x(curr_loc_.first, curr_row_height_)) {

            curr_loc_.first = jump_to;
      }
}

void help_text_area::handle_format_cfg(const config &cfg)
{
      const std::string text = cfg["text"];
      if (text == "") {
            throw parse_error("Format markup must have text attribute.");
      }
      const bool bold = utils::string_bool(cfg["bold"], false);
      const bool italic = utils::string_bool(cfg["italic"], false);
      int font_size = normal_font_size;
      if (cfg["font_size"] != "") {
            try {
                  font_size = lexical_cast<int, std::string>(cfg["font_size"]);
            } catch (bad_lexical_cast) {
                  throw parse_error("Invalid font_size in format markup.");
            }
      }
      SDL_Color color = string_to_color(cfg["color"]);
      add_text_item(text, "", false, font_size, bold, italic, color);
}

02392 void help_text_area::add_text_item(const std::string& text, const std::string& ref_dst,
                                                   bool broken_link, int _font_size, bool bold, bool italic,
                                                   SDL_Color text_color
)
{
      const int font_size = _font_size < 0 ? normal_font_size : _font_size;
      if (text.empty())
            return;
      const int remaining_width = get_remaining_width();
      size_t first_word_start = text.find_first_not_of(" ");
      if (first_word_start == std::string::npos) {
            first_word_start = 0;
      }
      if (text[first_word_start] == '\n') {
            down_one_line();
            std::string rest_text = text;
            rest_text.erase(0, first_word_start + 1);
            add_text_item(rest_text, ref_dst, broken_link, _font_size, bold, italic, text_color);
            return;
      }
      const std::string first_word = get_first_word(text);
      int state = ref_dst == "" ? 0 : TTF_STYLE_UNDERLINE;
      state |= bold ? TTF_STYLE_BOLD : 0;
      state |= italic ? TTF_STYLE_ITALIC : 0;
      if (curr_loc_.first != get_min_x(curr_loc_.second, curr_row_height_)
            && remaining_width < font::line_width(first_word, font_size, state)) {
            // The first word does not fit, and we are not at the start of
            // the line. Move down.
            down_one_line();
            std::string s = remove_first_space(text);
            add_text_item(s, ref_dst, broken_link, _font_size, bold, italic, text_color);
      }
      else {
            std::vector<std::string> parts = split_in_width(text, font_size, remaining_width);
            std::string first_part = parts.front();
            // Always override the color if we have a cross reference.
            SDL_Color color;
            if(ref_dst.empty())
                  color = text_color;
            else if(broken_link)
                  color = font::BAD_COLOUR;
            else
                  color = font::YELLOW_COLOUR;

            surface surf(font::get_rendered_text(first_part, font_size, color, state));
            if (!surf.null())
                  add_item(item(surf, curr_loc_.first, curr_loc_.second, first_part, ref_dst));
            if (parts.size() > 1) {

                  std::string& s = parts.back();

                  const std::string first_word_before = get_first_word(s);
                  const std::string first_word_after = get_first_word(remove_first_space(s));
                  if (get_remaining_width() >= font::line_width(first_word_after, font_size, state)
                        && get_remaining_width()
                        < font::line_width(first_word_before, font_size, state)) {
                        // If the removal of the space made this word fit, we
                        // must move down a line, otherwise it will be drawn
                        // without a space at the end of the line.
                        s = remove_first_space(s);
                        down_one_line();
                  }
                  else if (!(font::line_width(first_word_before, font_size, state)
                                 < get_remaining_width())) {
                        s = remove_first_space(s);
                  }
                  add_text_item(s, ref_dst, broken_link, _font_size, bold, italic, text_color);

            }
      }
}

02464 void help_text_area::add_img_item(const std::string& path, const std::string& alignment,
                                                  const bool floating, const bool box)
{
      surface surf(image::get_image(path));
      if (surf.null())
            return;
      ALIGNMENT align = str_to_align(alignment);
      if (align == HERE && floating) {
            WRN_DP << "Floating image with align HERE, aligning left.\n";
            align = LEFT;
      }
      const int width = surf->w + (box ? box_width * 2 : 0);
      int xpos;
      int ypos = curr_loc_.second;
      int text_width = inner_location().w;
      switch (align) {
      case HERE:
            xpos = curr_loc_.first;
            break;
      case LEFT:
      default:
            xpos = 0;
            break;
      case MIDDLE:
            xpos = text_width / 2 - width / 2 - (box ? box_width : 0);
            break;
      case RIGHT:
            xpos = text_width - width - (box ? box_width * 2 : 0);
            break;
      }
      if (curr_loc_.first != get_min_x(curr_loc_.second, curr_row_height_)
            && (xpos < curr_loc_.first || xpos + width > text_width)) {
            down_one_line();
            add_img_item(path, alignment, floating, box);
      }
      else {
            if (!floating) {
                  curr_loc_.first = xpos;
            }
            else {
                  ypos = get_y_for_floating_img(width, xpos, ypos);
            }
            add_item(item(surf, xpos, ypos, floating, box, align));
      }
}

02510 int help_text_area::get_y_for_floating_img(const int width, const int x, const int desired_y)
{
      int min_y = desired_y;
      for (std::list<item>::const_iterator it = items_.begin(); it != items_.end(); ++it) {
            const item& itm = *it;
            if (itm.floating) {
                  if ((itm.rect.x + itm.rect.w > x && itm.rect.x < x + width)
                        || (itm.rect.x > x && itm.rect.x < x + width)) {
                        min_y = std::max<int>(min_y, itm.rect.y + itm.rect.h);
                  }
            }
      }
      return min_y;
}

02525 int help_text_area::get_min_x(const int y, const int height)
{
      int min_x = 0;
      for (std::list<item>::const_iterator it = items_.begin(); it != items_.end(); ++it) {
            const item& itm = *it;
            if (itm.floating) {
                  if (itm.rect.y < y + height && itm.rect.y + itm.rect.h > y && itm.align == LEFT) {
                        min_x = std::max<int>(min_x, itm.rect.w + 5);
                  }
            }
      }
      return min_x;
}

02539 int help_text_area::get_max_x(const int y, const int height)
{
      int text_width = inner_location().w;
      int max_x = text_width;
      for (std::list<item>::const_iterator it = items_.begin(); it != items_.end(); ++it) {
            const item& itm = *it;
            if (itm.floating) {
                  if (itm.rect.y < y + height && itm.rect.y + itm.rect.h > y) {
                        if (itm.align == RIGHT) {
                              max_x = std::min<int>(max_x, text_width - itm.rect.w - 5);
                        } else if (itm.align == MIDDLE) {
                              max_x = std::min<int>(max_x, text_width / 2 - itm.rect.w / 2 - 5);
                        }
                  }
            }
      }
      return max_x;
}

02558 void help_text_area::add_item(const item &itm)
{
      items_.push_back(itm);
      if (!itm.floating) {
            curr_loc_.first += itm.rect.w;
            curr_row_height_ = std::max<int>(itm.rect.h, curr_row_height_);
            contents_height_ = std::max<int>(contents_height_, curr_loc_.second + curr_row_height_);
            last_row_.push_back(&items_.back());
      }
      else {
            if (itm.align == LEFT) {
                  curr_loc_.first = itm.rect.w + 5;
            }
            contents_height_ = std::max<int>(contents_height_, itm.rect.y + itm.rect.h);
      }
}


02576 help_text_area::ALIGNMENT help_text_area::str_to_align(const std::string &cmp_str)
{
      if (cmp_str == "left") {
            return LEFT;
      } else if (cmp_str == "middle") {
            return MIDDLE;
      } else if (cmp_str == "right") {
            return RIGHT;
      } else if (cmp_str == "here" || cmp_str == "") { // Make the empty string be "here" alignment.
            return HERE;
      }
      std::stringstream msg;
      msg << "Invalid alignment string: '" << cmp_str << "'";
      throw parse_error(msg.str());
}

02592 void help_text_area::down_one_line()
{
      adjust_last_row();
      last_row_.clear();
      curr_loc_.second += curr_row_height_ + (curr_row_height_ == min_row_height_ ? 0 : 2);
      curr_row_height_ = min_row_height_;
      contents_height_ = std::max<int>(curr_loc_.second + curr_row_height_, contents_height_);
      curr_loc_.first = get_min_x(curr_loc_.second, curr_row_height_);
}

02602 void help_text_area::adjust_last_row()
{
      for (std::list<item *>::iterator it = last_row_.begin(); it != last_row_.end(); ++it) {
            item &itm = *(*it);
            const int gap = curr_row_height_ - itm.rect.h;
            itm.rect.y += gap / 2;
      }
}

02611 int help_text_area::get_remaining_width()
{
      const int total_w = get_max_x(curr_loc_.second, curr_row_height_);
      return total_w - curr_loc_.first;
}

void help_text_area::draw_contents()
{
      SDL_Rect const &loc = inner_location();
      bg_restore();
      surface const screen = video().getSurface();
      clip_rect_setter clip_rect_set(screen, loc);
      for(std::list<item>::const_iterator it = items_.begin(), end = items_.end(); it != end; ++it) {
            SDL_Rect dst = it->rect;
            dst.y -= get_position();
            if (dst.y < static_cast<int>(loc.h) && dst.y + it->rect.h > 0) {
                  dst.x += loc.x;
                  dst.y += loc.y;
                  if (it->box) {
                        for (int i = 0; i < box_width; ++i) {
                              draw_rectangle(dst.x, dst.y, it->rect.w - i * 2, it->rect.h - i * 2,
                                                  0, screen);
                              ++dst.x;
                              ++dst.y;
                        }
                  }
                  SDL_BlitSurface(it->surf, NULL, screen, &dst);
            }
      }
      update_rect(loc);
}

void help_text_area::scroll(unsigned int)
{
      // Nothing will be done on the actual scroll event. The scroll
      // position is checked when drawing instead and things drawn
      // accordingly.
      set_dirty(true);
}

bool help_text_area::item_at::operator()(const item& item) const {
      return point_in_rect(x_, y_, item.rect);
}

02655 std::string help_text_area::ref_at(const int x, const int y)
{
      const int local_x = x - location().x;
      const int local_y = y - location().y;
      if (local_y < static_cast<int>(height()) && local_y > 0) {
            const int cmp_y = local_y + get_position();
            const std::list<item>::const_iterator it =
                  std::find_if(items_.begin(), items_.end(), item_at(local_x, cmp_y));
            if (it != items_.end()) {
                  if ((*it).ref_to != "") {
                        return ((*it).ref_to);
                  }
            }
      }
      return "";
}



help_browser::help_browser(display &disp, const section &toplevel) :
      gui::widget(disp.video()),
      disp_(disp),
      menu_(disp.video(),
      toplevel),
      text_area_(disp.video(), toplevel), toplevel_(toplevel),
      ref_cursor_(false),
      back_topics_(),
      forward_topics_(),
      back_button_(disp.video(), _(" < Back"), gui::button::TYPE_PRESS),
      forward_button_(disp.video(), _("Forward >"), gui::button::TYPE_PRESS),
      shown_topic_(NULL)
{
      // Hide the buttons at first since we do not have any forward or
      // back topics at this point. They will be unhidden when history
      // appears.
      back_button_.hide(true);
      forward_button_.hide(true);
      // Set sizes to some default values.
      set_measurements(font::relative_size(400), font::relative_size(500));
}

void help_browser::adjust_layout()
{
  const int menu_buttons_padding = font::relative_size(10);
      const int menu_y = location().y;
      const int menu_x = location().x;
#ifdef USE_TINY_GUI
      const int menu_w = 120;
#else
      const int menu_w = 250;
#endif
      const int menu_h = height() - back_button_.height() - menu_buttons_padding;

      const int menu_text_area_padding = font::relative_size(10);
      const int text_area_y = location().y;
      const int text_area_x = menu_x + menu_w + menu_text_area_padding;
      const int text_area_w = width() - menu_w - menu_text_area_padding;
      const int text_area_h = height();

      const int button_border_padding = 0;
      const int button_button_padding = font::relative_size(10);
      const int back_button_x = location().x + button_border_padding;
      const int back_button_y = menu_y + menu_h + menu_buttons_padding;
      const int forward_button_x = back_button_x + back_button_.width() + button_button_padding;
      const int forward_button_y = back_button_y;

      menu_.set_width(menu_w);
      menu_.set_location(menu_x, menu_y);
      menu_.set_max_height(menu_h);
      menu_.set_max_width(menu_w);

      text_area_.set_location(text_area_x, text_area_y);
      text_area_.set_width(text_area_w);
      text_area_.set_height(text_area_h);

      back_button_.set_location(back_button_x, back_button_y);
      forward_button_.set_location(forward_button_x, forward_button_y);

      set_dirty(true);
}

void help_browser::update_location(SDL_Rect const &)
{
      adjust_layout();
}

02741 void help_browser::process_event()
{
      CKey key;
      int mousex, mousey;
      SDL_GetMouseState(&mousex,&mousey);

      /// Fake focus functionality for the menu, only process it if it has focus.
      if (point_in_rect(mousex, mousey, menu_.location())) {
            menu_.process();
            const topic *chosen_topic = menu_.chosen_topic();
            if (chosen_topic != NULL && chosen_topic != shown_topic_) {
                  /// A new topic has been chosen in the menu, display it.
                  show_topic(*chosen_topic);
            }
      }
      if (back_button_.pressed()) {
            move_in_history(back_topics_, forward_topics_);
      }
      if (forward_button_.pressed()) {
            move_in_history(forward_topics_, back_topics_);
      }
      back_button_.hide(back_topics_.empty());
      forward_button_.hide(forward_topics_.empty());
}

02766 void help_browser::move_in_history(std::deque<const topic *> &from,
            std::deque<const topic *> &to)
{
      if (!from.empty()) {
            const topic *to_show = from.back();
            from.pop_back();
            if (shown_topic_ != NULL) {
                  if (to.size() > max_history) {
                        to.pop_front();
                  }
                  to.push_back(shown_topic_);
            }
            show_topic(*to_show, false);
      }
}


void help_browser::handle_event(const SDL_Event &event)
{
      SDL_MouseButtonEvent mouse_event = event.button;
      if (event.type == SDL_MOUSEBUTTONDOWN) {
            if (mouse_event.button == SDL_BUTTON_LEFT) {
                  // Did the user click a cross-reference?
                  const int mousex = mouse_event.x;
                  const int mousey = mouse_event.y;
                  const std::string ref = text_area_.ref_at(mousex, mousey);
                  if (ref != "") {
                        const topic *t = find_topic(toplevel_, ref);
                        if (t == NULL) {
                              std::stringstream msg;
                              msg << _("Reference to unknown topic: ") << "'" << ref << "'.";
                              gui2::show_transient_message(disp_.video(), "", msg.str());
                              update_cursor();
                        }
                        else {
                              show_topic(*t);
                              update_cursor();
                        }
                  }
            }
      }
      else if (event.type == SDL_MOUSEMOTION) {
            update_cursor();
      }
}

02812 void help_browser::update_cursor()
{
      int mousex, mousey;
      SDL_GetMouseState(&mousex,&mousey);
      const std::string ref = text_area_.ref_at(mousex, mousey);
      if (ref != "" && !ref_cursor_) {
            cursor::set(cursor::HYPERLINK);
            ref_cursor_ = true;
      }
      else if (ref == "" && ref_cursor_) {
            cursor::set(cursor::NORMAL);
            ref_cursor_ = false;
      }
}


const topic *find_topic(const section &sec, const std::string &id)
{
      topic_list::const_iterator tit =
            std::find_if(sec.topics.begin(), sec.topics.end(), has_id(id));
      if (tit != sec.topics.end()) {
            return &(*tit);
      }
      section_list::const_iterator sit;
      for (sit = sec.sections.begin(); sit != sec.sections.end(); ++sit) {
            const topic *t = find_topic(*(*sit), id);
            if (t != NULL) {
                  return t;
            }
      }
      return NULL;
}

const section *find_section(const section &sec, const std::string &id)
{
      section_list::const_iterator sit =
            std::find_if(sec.sections.begin(), sec.sections.end(), has_id(id));
      if (sit != sec.sections.end()) {
            return *sit;
      }
      for (sit = sec.sections.begin(); sit != sec.sections.end(); ++sit) {
            const section *s = find_section(*(*sit), id);
            if (s != NULL) {
                  return s;
            }
      }
      return NULL;
}

02861 void help_browser::show_topic(const std::string &topic_id)
{
      const topic *t = find_topic(toplevel_, topic_id);

      if (t != NULL) {
            show_topic(*t);
      } else if (topic_id.find(unit_prefix)==0 || topic_id.find(hidden_symbol() + unit_prefix)==0) {
            show_topic(unknown_unit_topic);
      } else {
            std::cerr << "Help browser tried to show topic with id '" << topic_id
                          << "' but that topic could not be found." << std::endl;
      }
}

void help_browser::show_topic(const topic &t, bool save_in_history)
{
      log_scope("show_topic");

      if (save_in_history) {
            forward_topics_.clear();
            if (shown_topic_ != NULL) {
                  if (back_topics_.size() > max_history) {
                        back_topics_.pop_front();
                  }
                  back_topics_.push_back(shown_topic_);
            }
      }

      shown_topic_ = &t;
      text_area_.show_topic(t);
      menu_.select_topic(t);
      update_cursor();
}

std::vector<std::string> parse_text(const std::string &text)
{
      std::vector<std::string> res;
      bool last_char_escape = false;
      const char escape_char = '\\';
      std::stringstream ss;
      size_t pos;
      enum { ELEMENT_NAME, OTHER } state = OTHER;
      for (pos = 0; pos < text.size(); ++pos) {
            const char c = text[pos];
            if (c == escape_char && !last_char_escape) {
                  last_char_escape = true;
            }
            else {
                  if (state == OTHER) {
                        if (c == '<') {
                              if (last_char_escape) {
                                    ss << c;
                              }
                              else {
                                    res.push_back(ss.str());
                                    ss.str("");
                                    state = ELEMENT_NAME;
                              }
                        }
                        else {
                              ss << c;
                        }
                  }
                  else if (state == ELEMENT_NAME) {
                        if (c == '/') {
                              std::string msg = "Erroneous / in element name.";
                              throw parse_error(msg);
                        }
                        else if (c == '>') {
                              // End of this name.
                              std::stringstream s;
                              const std::string element_name = ss.str();
                              ss.str("");
                              s << "</" << element_name << ">";
                              const std::string end_element_name = s.str();
                              size_t end_pos = text.find(end_element_name, pos);
                              if (end_pos == std::string::npos) {
                                    std::stringstream msg;
                                    msg << "Unterminated element: " << element_name;
                                    throw parse_error(msg.str());
                              }
                              s.str("");
                              const std::string contents = text.substr(pos + 1, end_pos - pos - 1);
                              const std::string element = convert_to_wml(element_name, contents);
                              res.push_back(element);
                              pos = end_pos + end_element_name.size() - 1;
                              state = OTHER;
                        }
                        else {
                              ss << c;
                        }
                  }
                  last_char_escape = false;
            }
      }
      if (state == ELEMENT_NAME) {
            std::stringstream msg;
            msg << "Element '" << ss.str() << "' continues through end of string.";
            throw parse_error(msg.str());
      }
      if (ss.str() != "") {
            // Add the last string.
            res.push_back(ss.str());
      }
      return res;
}

std::string convert_to_wml(const std::string &element_name, const std::string &contents)
{
      std::stringstream ss;
      bool in_quotes = false;
      bool last_char_escape = false;
      const char escape_char = '\\';
      std::vector<std::string> attributes;
      // Find the different attributes.
      // No checks are made for the equal sign or something like that.
      // Attributes are just separated by spaces or newlines.
      // Attributes that contain spaces must be in single quotes.
      for (size_t pos = 0; pos < contents.size(); ++pos) {
            const char c = contents[pos];
            if (c == escape_char && !last_char_escape) {
                  last_char_escape = true;
            }
            else {
                  if (c == '\'' && !last_char_escape) {
                        ss << '"';
                        in_quotes = !in_quotes;
                  }
                  else if ((c == ' ' || c == '\n') && !last_char_escape && !in_quotes) {
                        // Space or newline, end of attribute.
                        attributes.push_back(ss.str());
                        ss.str("");
                  }
                  else {
                        ss << c;
                  }
                  last_char_escape = false;
            }
      }
      if (in_quotes) {
            std::stringstream msg;
            msg << "Unterminated single quote after: '" << ss.str() << "'";
            throw parse_error(msg.str());
      }
      if (ss.str() != "") {
            attributes.push_back(ss.str());
      }
      ss.str("");
      // Create the WML.
      ss << "[" << element_name << "]\n";
      for (std::vector<std::string>::const_iterator it = attributes.begin();
             it != attributes.end(); ++it) {
            ss << *it << "\n";
      }
      ss << "[/" << element_name << "]\n";
      return ss.str();
}

SDL_Color string_to_color(const std::string &cmp_str)
{
      if (cmp_str == "green") {
            return font::GOOD_COLOUR;
      }
      if (cmp_str == "red") {
            return font::BAD_COLOUR;
      }
      if (cmp_str == "black") {
            return font::BLACK_COLOUR;
      }
      if (cmp_str == "yellow") {
            return font::YELLOW_COLOUR;
      }
      if (cmp_str == "white") {
            return font::BIGMAP_COLOUR;
      }
      return font::NORMAL_COLOUR;
}

std::vector<std::string> split_in_width(const std::string &s, const int font_size,
            const unsigned width)
{
      std::vector<std::string> res;
      try {
      const std::string& first_line = font::word_wrap_text(s, font_size, width, -1, 1, true);
      res.push_back(first_line);
      if(s.size() > first_line.size()) {
            res.push_back(s.substr(first_line.size()));
      }
      }
      catch (utils::invalid_utf8_exception e)
      {
            throw parse_error (_("corrupted original file"));
      }

      return res;
}

std::string remove_first_space(const std::string& text)
{
      if (text.length() > 0 && text[0] == ' ') {
            return text.substr(1);
      }
      return text;
}

std::string get_first_word(const std::string &s)
{
      size_t first_word_start = s.find_first_not_of(' ');
      if (first_word_start == std::string::npos) {
            return s;
      }
      size_t first_word_end = s.find_first_of(" \n", first_word_start);
      if( first_word_end == first_word_start ) {
            // This word is '\n'.
            first_word_end = first_word_start+1;
      }

      //if no gap(' ' or '\n') found, test if it is CJK character
      std::string re = s.substr(0, first_word_end);

      utils::utf8_iterator ch(re);
      if (ch == utils::utf8_iterator::end(re))
            return re;

      wchar_t firstchar = *ch;
      if (font::is_cjk_char(firstchar)) {
            re = utils::wchar_to_string(firstchar);
      }
      return re;
}

/**
 * Open the help browser, show topic with id show_topic.
 *
 * If show_topic is the empty string, the default topic will be shown.
 */
void show_help(display &disp, const std::string& show_topic, int xloc, int yloc)
{
      show_help(disp, toplevel, show_topic, xloc, yloc);
}

/**
 * Open the help browser, show unit with id unit_id.
 *
 * If show_topic is the empty string, the default topic will be shown.
 */
void show_unit_help(display &disp, const std::string& show_topic, bool hidden, int xloc, int yloc)
{
      show_help(disp, toplevel, hidden_symbol(hidden) + unit_prefix + show_topic, xloc, yloc);
}

/**
 * Open a help dialog using a toplevel other than the default.
 *
 * This allows for complete customization of the contents, although not in a
 * very easy way.
 */
void show_help(display &disp, const section &toplevel_sec,
                     const std::string& show_topic,
                     int xloc, int yloc)
{
      const events::event_context dialog_events_context;
      const gui::dialog_manager manager;
      const resize_lock prevent_resizing;

      CVideo& screen = disp.video();
      surface const scr = screen.getSurface();

      const int width  = std::min<int>(font::relative_size(900), scr->w - font::relative_size(20));
      const int height = std::min<int>(font::relative_size(800), scr->h - font::relative_size(150));
      const int left_padding = font::relative_size(10);
      const int right_padding = font::relative_size(10);
      const int top_padding = font::relative_size(10);
      const int bot_padding = font::relative_size(10);

      // If not both locations were supplied, put the dialog in the middle
      // of the screen.
      if (yloc <= -1 || xloc <= -1) {
            xloc = scr->w / 2 - width / 2;
            yloc = scr->h / 2 - height / 2;
      }
      std::vector<gui::button*> buttons_ptr;
      gui::button close_button_(disp.video(), _("Close"));
      buttons_ptr.push_back(&close_button_);

      gui::dialog_frame f(disp.video(), _("The Battle for Wesnoth Help"), gui::dialog_frame::default_style,
                               true, &buttons_ptr);
      f.layout(xloc, yloc, width, height);
      f.draw();

    // Find all unit_types that have not been constructed yet and fill in the information
    // needed to create the help topics
      unit_types.build_all(unit_type::HELP_INDEX);

      if (preferences::encountered_units().size() != size_t(last_num_encountered_units) ||
          preferences::encountered_terrains().size() != size_t(last_num_encountered_terrains) ||
          last_debug_state != game_config::debug ||
            last_num_encountered_units < 0) {
            // More units or terrains encountered, update the contents.
            last_num_encountered_units = preferences::encountered_units().size();
            last_num_encountered_terrains = preferences::encountered_terrains().size();
            last_debug_state = game_config::debug;
            generate_contents();
      }
      try {
            help_browser hb(disp, toplevel_sec);
            hb.set_location(xloc + left_padding, yloc + top_padding);
            hb.set_width(width - left_padding - right_padding);
            hb.set_height(height - top_padding - bot_padding);
            if (show_topic != "") {
                  hb.show_topic(show_topic);
            }
            else {
                  hb.show_topic(default_show_topic);
            }
            hb.set_dirty(true);
            events::raise_draw_event();
            disp.flip();
            disp.invalidate_all();
            CKey key;
            for (;;) {
                  events::pump();
                  events::raise_process_event();
                  events::raise_draw_event();
                  if (key[SDLK_ESCAPE]) {
                        // Escape quits from the dialog.
                        return;
                  }
                  for (std::vector<gui::button*>::iterator button_it = buttons_ptr.begin();
                         button_it != buttons_ptr.end(); ++button_it) {
                        if ((*button_it)->pressed()) {
                              // There is only one button, close.
                              return;
                        }
                  }
                  disp.flip();
                  disp.delay(10);
            }
      }
      catch (parse_error e) {
            std::stringstream msg;
            msg << _("Parse error when parsing help text: ") << "'" << e.message << "'";
            gui2::show_transient_message(disp.video(), "", msg.str());
      }
}

} // End namespace help.

Generated by  Doxygen 1.6.0   Back to index