← Back to team overview

widelands-dev team mailing list archive

[Merge] lp:~widelands-dev/widelands/editor_infotool_rendering into lp:widelands

 

GunChleoc has proposed merging lp:~widelands-dev/widelands/editor_infotool_rendering into lp:widelands.

Commit message:
Converted editor infotool to new font renderer and added some layout tweaks. Pulled out common layout functions into text_layout.

Requested reviews:
  Widelands Developers (widelands-dev)

For more details, see:
https://code.launchpad.net/~widelands-dev/widelands/editor_infotool_rendering/+merge/349093
-- 
Your team Widelands Developers is requested to review the proposed merge of lp:~widelands-dev/widelands/editor_infotool_rendering into lp:widelands.
=== modified file 'src/editor/tools/info_tool.cc'
--- src/editor/tools/info_tool.cc	2018-04-27 06:11:05 +0000
+++ src/editor/tools/info_tool.cc	2018-07-08 09:28:57 +0000
@@ -26,6 +26,7 @@
 
 #include "base/i18n.h"
 #include "editor/editorinteractive.h"
+#include "graphic/text_layout.h"
 #include "logic/map_objects/world/editor_category.h"
 #include "logic/map_objects/world/terrain_description.h"
 #include "ui_basic/multilinetextarea.h"
@@ -37,21 +38,21 @@
                                           EditorInteractive& parent,
                                           EditorActionArgs* /* args */,
                                           Widelands::Map* map) {
+
+	static constexpr int kListFontsize = UI_FONT_SIZE_MESSAGE;
 	parent.stop_painting();
 
 	UI::Window* const w =
 	   new UI::Window(&parent, "field_information", 30, 30, 400, 200, _("Field Information"));
 	UI::MultilineTextarea* const multiline_textarea =
 	   new UI::MultilineTextarea(w, 0, 0, w->get_inner_w(), w->get_inner_h(), UI::PanelStyle::kWui);
+	multiline_textarea->force_new_renderer();
 
 	Widelands::Field& f = (*map)[center.node];
 
 	// *** Node info
-	std::string buf = _("Node:");
-	buf += "\n";
-	buf += "• " +
-	       (boost::format(_("Coordinates: (%1$i, %2$i)")) % center.node.x % center.node.y).str() +
-	       "\n";
+	std::string buf = as_heading(_("Node"), UI::PanelStyle::kWui, true);
+	buf += as_listitem((boost::format(_("Coordinates: (%1$i, %2$i)")) % center.node.x % center.node.y).str(), kListFontsize);
 
 	std::vector<std::string> caps_strings;
 	Widelands::NodeCaps const caps = f.nodecaps();
@@ -95,92 +96,122 @@
 		caps_strings.push_back(_("swimmable"));
 	}
 
-	buf += std::string("• ") +
+	buf += as_listitem(
 	       (boost::format(_("Caps: %s")) %
 	        i18n::localize_list(caps_strings, i18n::ConcatenateWith::COMMA))
-	          .str() +
-	       "\n";
+	          .str(), kListFontsize);
 
 	if (f.get_owned_by() > 0) {
-		buf += std::string("• ") +
+		buf += as_listitem(
 		       (boost::format(_("Owned by: Player %u")) % static_cast<unsigned int>(f.get_owned_by()))
-		          .str() +
-		       "\n";
+		          .str(), kListFontsize);
 	} else {
-		buf += std::string("• ") + _("Owned by: —") + "\n";
+		buf += as_listitem(_("Owned by: —"), kListFontsize);
 	}
 
-	std::string temp = f.get_immovable() ? _("Has immovable") : _("No immovable");
-	buf += "• " + temp + "\n";
-
-	temp = f.get_first_bob() ? _("Has animals") : _("No animals");
-	buf += "• " + temp + "\n";
-
 	// *** Terrain info
-	buf += std::string("\n") + _("Terrain:") + "\n";
+	buf += as_heading(_("Terrain"), UI::PanelStyle::kWui);
 
 	const Widelands::Field& tf = (*map)[center.triangle.node];
 	const Widelands::TerrainDescription& ter = world.terrain_descr(
 	   center.triangle.t == Widelands::TriangleIndex::D ? tf.terrain_d() : tf.terrain_r());
 
 	buf +=
-	   "• " + (boost::format(pgettext("terrain_name", "Name: %s")) % ter.descname()).str() + "\n";
+	   as_listitem((boost::format(pgettext("terrain_name", "Name: %s")) % ter.descname()).str(), kListFontsize);
 
 	std::vector<std::string> terrain_is_strings;
 	for (const Widelands::TerrainDescription::Type& terrain_type : ter.get_types()) {
 		terrain_is_strings.push_back(terrain_type.descname);
 	}
 
-	buf += "• " +
-	       /** TRANSLATORS: "Is" is a list of terrain properties, e.g. "arable", "unreachable and
-	        * unwalkable" */
-	       /** TRANSLATORS: You can also translate this as "Category: %s" or "Property: %s" */
+	buf += as_listitem(
+	       /** TRANSLATORS: "Is" is a list of terrain properties, e.g. "arable, unreachable and
+	        * unwalkable". You can also translate this as "Category: %s" or "Property: %s" */
 	       (boost::format(_("Is: %s")) %
 	        i18n::localize_list(terrain_is_strings, i18n::ConcatenateWith::AMPERSAND))
-	          .str() +
-	       "\n";
-	buf += "• " +
-	       (boost::format(_("Editor Category: %s")) % ter.editor_category()->descname()).str() +
-	       "\n";
+	          .str(), kListFontsize);
+	buf += as_listitem(
+	       (boost::format(_("Editor Category: %s")) % ter.editor_category()->descname()).str(), kListFontsize);
+
+	// *** Map Object info
+	const Widelands::BaseImmovable* immovable = f.get_immovable();
+	Widelands::Bob* bob =f.get_first_bob();
+	if (immovable || bob) {
+		/** TRANSLATORS: Heading for immovables and animals in editor info tool */
+		buf += as_heading(_("Objects"), UI::PanelStyle::kWui);
+		if (immovable) {
+			buf += as_listitem(
+				   (boost::format(_("Immovable: %s")) % immovable->descr().descname()).str(), kListFontsize);
+		}
+
+		if (bob) {
+			// Collect bobs
+			std::vector<std::string> critternames;
+			std::vector<std::string> shipnames;
+			std::vector<std::string> workernames;
+			do {
+				switch (bob->descr().type()) {
+				case (Widelands::MapObjectType::CRITTER):
+					critternames.push_back(bob->descr().descname());
+					break;
+				case (Widelands::MapObjectType::SHIP): {
+					if (upcast(Widelands::Ship, ship, bob)) {
+						shipnames.push_back(ship->get_shipname());
+					}
+				} break;
+				case (Widelands::MapObjectType::WORKER):
+				case (Widelands::MapObjectType::CARRIER):
+				case (Widelands::MapObjectType::SOLDIER):
+					workernames.push_back(bob->descr().descname());
+					break;
+				default:
+					break;
+				}
+			} while ((bob = bob->get_next_bob()));
+
+			// Add bobs
+			if (!critternames.empty()) {
+				buf += as_listitem(
+							(boost::format(_("Animals: %s")) % i18n::localize_list(critternames, i18n::ConcatenateWith::COMMA)).str(), kListFontsize);
+			}
+			if (!workernames.empty()) {
+				buf += as_listitem(
+							(boost::format(_("Workers: %s")) % i18n::localize_list(workernames, i18n::ConcatenateWith::COMMA)).str(), kListFontsize);
+			}
+			if (!workernames.empty()) {
+				buf += as_listitem(
+							(boost::format(_("Ships: %s")) % i18n::localize_list(shipnames, i18n::ConcatenateWith::COMMA)).str(), kListFontsize);
+			}
+		}
+	}
 
 	// *** Resources info
-	buf += std::string("\n") + _("Resources:") + "\n";
-
-	Widelands::DescriptionIndex ridx = f.get_resources();
 	Widelands::ResourceAmount ramount = f.get_resources_amount();
-
 	if (ramount > 0) {
+		buf += as_heading(_("Resources"), UI::PanelStyle::kWui);
 		buf +=
-		   "• " +
-		   (boost::format(_("Resource name: %s")) % world.get_resource(ridx)->name().c_str()).str() +
-		   "\n";
-		buf += "• " +
-		       (boost::format(_("Resource amount: %i")) % static_cast<unsigned int>(ramount)).str() +
-		       "\n";
-	} else {
-		buf += "• " + std::string(_("No resources")) + "\n";
+		   as_listitem(
+		   (boost::format(pgettext("resources", "%1%x %2%")) % static_cast<unsigned int>(ramount) % world.get_resource(f.get_resources())->descname()).str(), kListFontsize);
 	}
 
 	// *** Map info
-	buf += std::string("\n") + _("Map:") + "\n";
-	buf += "• " + (boost::format(pgettext("map_name", "Name: %s")) % map->get_name().c_str()).str() +
-	       "\n";
-	buf += "• " +
-	       (boost::format(_("Size: %1$ix%2$i")) % map->get_width() % map->get_height()).str() + "\n";
+	buf += as_heading(_("Map"), UI::PanelStyle::kWui);
+	buf += as_listitem((boost::format(pgettext("map_name", "Name: %s")) % map->get_name()).str(), kListFontsize);
+	buf += as_listitem(
+	       (boost::format(_("Size: %1% x %2%")) % map->get_width() % map->get_height()).str(), kListFontsize);
 
 	if (map->get_nrplayers() > 0) {
 		buf +=
-		   "• " +
-		   (boost::format(_("Players: %u")) % static_cast<unsigned int>(map->get_nrplayers())).str() +
-		   "\n";
+		   as_listitem(
+		   (boost::format(_("Players: %u")) % static_cast<unsigned int>(map->get_nrplayers())).str(), kListFontsize);
 	} else {
-		buf += "• " + std::string(_("Players: –")) + "\n";
+		buf += as_listitem(_("Players: –"), kListFontsize);
 	}
 
-	buf += "• " + (boost::format(_("Author: %s")) % map->get_author()).str() + "\n";
-	buf += "• " + (boost::format(_("Descr: %s")) % map->get_description().c_str()).str() + "\n";
+	buf += as_listitem((boost::format(_("Author: %s")) % map->get_author()).str(), kListFontsize);
+	buf += as_listitem((boost::format(_("Description: %s")) % map->get_description()).str(), kListFontsize);
 
-	multiline_textarea->set_text(buf.c_str());
+	multiline_textarea->set_text(as_richtext(buf));
 
 	return 0;
 }

=== modified file 'src/graphic/text_layout.cc'
--- src/graphic/text_layout.cc	2018-06-01 08:54:25 +0000
+++ src/graphic/text_layout.cc	2018-07-08 09:28:57 +0000
@@ -189,6 +189,57 @@
 	      .str());
 }
 
+std::string as_heading_with_content(const std::string& header,
+                                   const std::string& content,
+                                   UI::PanelStyle style,
+                                   bool is_first,
+                                   bool noescape) {
+	switch (style) {
+	case UI::PanelStyle::kFsMenu:
+		return (boost::format(
+		           "<p><font size=%i bold=1 shadow=1>%s%s <font color=D1D1D1>%s</font></font></p>") %
+		        UI_FONT_SIZE_SMALL % (is_first ? "" : "<vspace gap=9>") %
+		        (noescape ? header : richtext_escape(header)) %
+		        (noescape ? content : richtext_escape(content)))
+		   .str();
+	case UI::PanelStyle::kWui:
+		return (boost::format(
+		           "<p><font size=%i>%s<font bold=1 color=D1D1D1>%s</font> %s</font></p>") %
+		        UI_FONT_SIZE_SMALL % (is_first ? "" : "<vspace gap=6>") %
+		        (noescape ? header : richtext_escape(header)) %
+		        (noescape ? content : richtext_escape(content)))
+		   .str();
+	}
+	NEVER_HERE();
+}
+
+std::string as_heading(const std::string& txt, UI::PanelStyle style, bool is_first) {
+	switch (style) {
+	case UI::PanelStyle::kFsMenu:
+		return (boost::format("<p><font size=%i bold=1 shadow=1>%s%s</font></p>") %
+		        UI_FONT_SIZE_SMALL % (is_first ? "" : "<vspace gap=9>") % richtext_escape(txt))
+		   .str();
+	case UI::PanelStyle::kWui:
+		return (boost::format("<p><font size=%i bold=1 color=D1D1D1>%s%s</font></p>") %
+		        UI_FONT_SIZE_SMALL % (is_first ? "" : "<vspace gap=6>") % richtext_escape(txt))
+		   .str();
+	}
+	NEVER_HERE();
+}
+std::string as_content(const std::string& txt, UI::PanelStyle style) {
+	switch (style) {
+	case UI::PanelStyle::kFsMenu:
+		return (boost::format("<p><font size=%i color=D1D1D1 shadow=1><vspace gap=2>%s</font></p>") %
+		        UI_FONT_SIZE_SMALL % richtext_escape(txt))
+		   .str();
+	case UI::PanelStyle::kWui:
+		return (boost::format("<p><font size=%i><vspace gap=2>%s</font></p>") %
+		        (UI_FONT_SIZE_SMALL - 2) % richtext_escape(txt))
+		   .str();
+	}
+	NEVER_HERE();
+}
+
 std::shared_ptr<const UI::RenderedText>
 autofit_ui_text(const std::string& text, int width, RGBColor color, int fontsize) {
 	std::shared_ptr<const UI::RenderedText> result =

=== modified file 'src/graphic/text_layout.h'
--- src/graphic/text_layout.h	2018-06-01 08:50:29 +0000
+++ src/graphic/text_layout.h	2018-07-08 09:28:57 +0000
@@ -26,6 +26,7 @@
 #include "graphic/color.h"
 #include "graphic/font_handler1.h"
 #include "graphic/image.h"
+#include "graphic/panel_styles.h"
 #include "graphic/text/font_set.h"
 #include "graphic/text_constants.h"
 
@@ -95,6 +96,21 @@
 std::string as_message(const std::string& heading, const std::string& body);
 
 /**
+ * 'is_first' omits the vertical gap before the line.
+ * 'noescape' is needed for error message formatting and does not call richtext_escape. */
+std::string as_heading_with_content(const std::string& header,
+                                   const std::string& content,
+                                   UI::PanelStyle style,
+                                   bool is_first = false,
+                                   bool noescape = false);
+
+/**
+ * 'is_first' omits the vertical gap before the line.
+ */
+std::string as_heading(const std::string& txt, UI::PanelStyle style, bool is_first = false);
+std::string as_content(const std::string& txt, UI::PanelStyle style);
+
+/**
   * Render 'text' as ui_font. If 'width' > 0 and the rendered image is too
   * wide, it will first use the condensed font face and then make the text
   * smaller until it fits 'width'. The resulting font size will not go below

=== modified file 'src/ui_fsmenu/launch_mpg.cc'
--- src/ui_fsmenu/launch_mpg.cc	2018-05-26 16:16:46 +0000
+++ src/ui_fsmenu/launch_mpg.cc	2018-07-08 09:28:57 +0000
@@ -584,7 +584,7 @@
 	std::string infotext;
 	infotext += std::string(_("Map details:")) + "\n";
 	infotext += std::string("• ") +
-	            (boost::format(_("Size: %1$u x %2$u")) % map.get_width() % map.get_height()).str() +
+	            (boost::format(_("Size: %1% x %2%")) % map.get_width() % map.get_height()).str() +
 	            "\n";
 	infotext += std::string("• ") +
 	            (boost::format(ngettext("%u Player", "%u Players", nr_players_)) %

=== modified file 'src/wui/gamedetails.cc'
--- src/wui/gamedetails.cc	2018-06-04 19:18:34 +0000
+++ src/wui/gamedetails.cc	2018-07-08 09:28:57 +0000
@@ -34,35 +34,6 @@
 #include "io/filesystem/layered_filesystem.h"
 
 // TODO(GunChleoc): Arabic: line height broken for descriptions for Arabic.
-namespace {
-// 'is_first' omits the vertical gap before the line.
-// 'noescape' is needed for error message formatting and does not call richtext_escape.
-std::string as_header_with_content(const std::string& header,
-                                   const std::string& content,
-                                   UI::PanelStyle style,
-                                   bool is_first = false,
-                                   bool noescape = false) {
-	switch (style) {
-	case UI::PanelStyle::kFsMenu:
-		return (boost::format(
-		           "<p><font size=%i bold=1 shadow=1>%s%s <font color=D1D1D1>%s</font></font></p>") %
-		        UI_FONT_SIZE_SMALL % (is_first ? "" : "<vspace gap=9>") %
-		        (noescape ? header : richtext_escape(header)) %
-		        (noescape ? content : richtext_escape(content)))
-		   .str();
-	case UI::PanelStyle::kWui:
-		return (boost::format(
-		           "<p><font size=%i>%s<font bold=1 color=D1D1D1>%s</font> %s</font></p>") %
-		        UI_FONT_SIZE_SMALL % (is_first ? "" : "<vspace gap=6>") %
-		        (noescape ? header : richtext_escape(header)) %
-		        (noescape ? content : richtext_escape(content)))
-		   .str();
-	}
-	NEVER_HERE();
-}
-
-}  // namespace
-
 SavegameData::SavegameData()
    : gametime(""),
      nrplayers("0"),
@@ -141,12 +112,11 @@
 	if (gamedata.errormessage.empty()) {
 		if (gamedata.filename_list.empty()) {
 			name_label_.set_text(
-			   (boost::format("<rt>%s</rt>") %
-			    as_header_with_content(_("Map Name:"), gamedata.mapname, style_, true))
-			      .str());
+			   as_richtext(
+			    as_heading_with_content(_("Map Name:"), gamedata.mapname, style_, true)));
 
 			// Show game information
-			std::string description = as_header_with_content(
+			std::string description = as_heading_with_content(
 			   mode_ == Mode::kReplay ?
 			      /** TRANSLATORS: The time a replay starts. Shown in the replay loading screen*/
 			      _("Start of Replay:") :
@@ -156,15 +126,15 @@
 			   gamedata.gametime, style_);
 
 			description = (boost::format("%s%s") % description %
-			               as_header_with_content(_("Players:"), gamedata.nrplayers, style_))
-			                 .str();
-
-			description = (boost::format("%s%s") % description %
-			               as_header_with_content(_("Widelands Version:"), gamedata.version, style_))
-			                 .str();
-
-			description = (boost::format("%s%s") % description %
-			               as_header_with_content(_("Win Condition:"), gamedata.wincondition, style_))
+			               as_heading_with_content(_("Players:"), gamedata.nrplayers, style_))
+			                 .str();
+
+			description = (boost::format("%s%s") % description %
+			               as_heading_with_content(_("Widelands Version:"), gamedata.version, style_))
+			                 .str();
+
+			description = (boost::format("%s%s") % description %
+			               as_heading_with_content(_("Win Condition:"), gamedata.wincondition, style_))
 			                 .str();
 
 			std::string filename = gamedata.filename;
@@ -173,11 +143,10 @@
 			filename.erase(0, filename.find('/') + 1);
 			assert(!filename.empty());
 			description = (boost::format("%s%s") % description %
-			               as_header_with_content(_("Filename:"), filename, style_))
+			               as_heading_with_content(_("Filename:"), filename, style_))
 			                 .str();
 
-			description = (boost::format("<rt>%s</rt>") % description).str();
-			descr_.set_text(description);
+			descr_.set_text(as_richtext(description));
 
 			std::string minimap_path = gamedata.minimap_path;
 			if (!minimap_path.empty()) {
@@ -195,20 +164,16 @@
 		} else {
 			std::string filename_list = richtext_escape(gamedata.filename_list);
 			boost::replace_all(filename_list, "\n", "<br> • ");
-			name_label_.set_text((boost::format("<rt>%s</rt>") %
-			                      as_header_with_content(gamedata.mapname, "", style_, true))
-			                        .str());
+			name_label_.set_text(as_richtext(
+			                      as_heading_with_content(gamedata.mapname, "", style_, true)));
 
-			descr_.set_text((boost::format("<rt>%s</rt>") %
-			                 as_header_with_content("", filename_list, style_, true, true))
-			                   .str());
+			descr_.set_text(as_richtext(
+			                 as_heading_with_content("", filename_list, style_, true, true)));
 			minimap_icon_.set_visible(false);
 		}
 	} else {
 		name_label_.set_text(
-		   (boost::format("<rt>%s</rt>") %
-		    as_header_with_content(_("Error:"), gamedata.errormessage, style_, true, true))
-		      .str());
+		   as_richtext(as_heading_with_content(_("Error:"), gamedata.errormessage, style_, true, true)));
 	}
 	layout();
 }

=== modified file 'src/wui/mapdetails.cc'
--- src/wui/mapdetails.cc	2018-04-27 06:11:05 +0000
+++ src/wui/mapdetails.cc	2018-07-08 09:28:57 +0000
@@ -37,35 +37,6 @@
 #include "ui_basic/scrollbar.h"
 #include "wui/map_tags.h"
 
-namespace {
-std::string as_header(const std::string& txt, UI::PanelStyle style, bool is_first = false) {
-	switch (style) {
-	case UI::PanelStyle::kFsMenu:
-		return (boost::format("<p><font size=%i bold=1 shadow=1>%s%s</font></p>") %
-		        UI_FONT_SIZE_SMALL % (is_first ? "" : "<vspace gap=9>") % richtext_escape(txt))
-		   .str();
-	case UI::PanelStyle::kWui:
-		return (boost::format("<p><font size=%i bold=1 color=D1D1D1>%s%s</font></p>") %
-		        UI_FONT_SIZE_SMALL % (is_first ? "" : "<vspace gap=6>") % richtext_escape(txt))
-		   .str();
-	}
-	NEVER_HERE();
-}
-std::string as_content(const std::string& txt, UI::PanelStyle style) {
-	switch (style) {
-	case UI::PanelStyle::kFsMenu:
-		return (boost::format("<p><font size=%i color=D1D1D1 shadow=1><vspace gap=2>%s</font></p>") %
-		        UI_FONT_SIZE_SMALL % richtext_escape(txt))
-		   .str();
-	case UI::PanelStyle::kWui:
-		return (boost::format("<p><font size=%i><vspace gap=2>%s</font></p>") %
-		        (UI_FONT_SIZE_SMALL - 2) % richtext_escape(txt))
-		   .str();
-	}
-	NEVER_HERE();
-}
-}  // namespace
-
 MapDetails::MapDetails(
    Panel* parent, int32_t x, int32_t y, int32_t w, int32_t h, UI::PanelStyle style)
    : UI::Panel(parent, x, y, w, h),
@@ -119,7 +90,7 @@
 	// Show directory information
 	if (mapdata.maptype == MapData::MapType::kDirectory) {
 		name_label_.set_text((boost::format("<rt>%s%s</rt>") %
-		                      as_header(_("Directory:"), style_, true) %
+		                      as_heading(_("Directory:"), style_, true) %
 		                      as_content(mapdata.localized_name, style_))
 		                        .str());
 		main_box_.set_size(main_box_.get_w(), get_h());
@@ -127,7 +98,7 @@
 	} else {  // Show map information
 		name_label_.set_text(
 		   (boost::format("<rt>%s%s</rt>") %
-		    as_header(mapdata.maptype == MapData::MapType::kScenario ? _("Scenario:") : _("Map:"),
+		    as_heading(mapdata.maptype == MapData::MapType::kScenario ? _("Scenario:") : _("Map:"),
 		              style_, true) %
 		    as_content(localize_mapname ? mapdata.localized_name : mapdata.name, style_))
 		      .str());
@@ -154,7 +125,7 @@
 
 		// Show map information
 		std::string description =
-		   as_header(ngettext("Author:", "Authors:", mapdata.authors.get_number()), style_);
+		   as_heading(ngettext("Author:", "Authors:", mapdata.authors.get_number()), style_);
 		description =
 		   (boost::format("%s%s") % description % as_content(mapdata.authors.get_names(), style_))
 		      .str();
@@ -164,25 +135,24 @@
 			tags.push_back(localize_tag(tag));
 		}
 		std::sort(tags.begin(), tags.end());
-		description = (boost::format("%s%s") % description % as_header(_("Tags:"), style_)).str();
+		description = (boost::format("%s%s") % description % as_heading(_("Tags:"), style_)).str();
 		description = (boost::format("%s%s") % description %
 		               as_content(i18n::localize_list(tags, i18n::ConcatenateWith::COMMA), style_))
 		                 .str();
 
 		description =
-		   (boost::format("%s%s") % description % as_header(_("Description:"), style_)).str();
+		   (boost::format("%s%s") % description % as_heading(_("Description:"), style_)).str();
 		description =
 		   (boost::format("%s%s") % description % as_content(mapdata.description, style_)).str();
 
 		if (!mapdata.hint.empty()) {
 			/** TRANSLATORS: Map hint header when selecting a map. */
-			description = (boost::format("%s%s") % description % as_header(_("Hint:"), style_)).str();
+			description = (boost::format("%s%s") % description % as_heading(_("Hint:"), style_)).str();
 			description =
 			   (boost::format("%s%s") % description % as_content(mapdata.hint, style_)).str();
 		}
 
-		description = (boost::format("<rt>%s</rt>") % description).str();
-		descr_.set_text(description);
+		descr_.set_text(as_richtext(description));
 
 		// Show / hide suggested teams
 		if (mapdata.suggested_teams.empty()) {


Follow ups