1 #include "LinkText.h"
2
3 #include "ClientUI.h"
4 #include "CUIControls.h"
5 #include "../Empire/Empire.h"
6 #include "../universe/UniverseObject.h"
7 #include "../util/AppInterface.h"
8 #include "../util/Logger.h"
9 #include "../util/VarText.h"
10 #include "../util/i18n.h"
11 #include "../util/Directories.h"
12 #include "../universe/Enums.h"
13
14 #include <GG/WndEvent.h>
15 #include <GG/GUI.h>
16 #include <boost/xpressive/xpressive.hpp>
17 #include <boost/algorithm/string.hpp>
18
19 // TextLinker static(s)
20 const std::string TextLinker::ENCYCLOPEDIA_TAG("encyclopedia");
21 const std::string TextLinker::GRAPH_TAG("graph");
22 const std::string TextLinker::URL_TAG("url");
23 const std::string TextLinker::BROWSE_PATH_TAG("browsepath");
24
25 namespace {
26 static const bool RENDER_DEBUGGING_LINK_RECTS = false;
27
28 // closing format tag
29 static const std::string LINK_FORMAT_CLOSE = "</rgba>";
30
AllParamsAsString(const std::vector<GG::Font::Substring> & params)31 std::string AllParamsAsString(const std::vector<GG::Font::Substring>& params) {
32 std::string retval;
33 for (const auto& param : params) {
34 if (!retval.empty())
35 retval.append(" ");
36 retval.append(param);
37 }
38 return retval;
39 }
40
ResolveNestedPathTypes(const std::string & text)41 std::string ResolveNestedPathTypes(const std::string& text) {
42 if (text.empty())
43 return text;
44 std::string new_text = text;
45 for (const auto& path_type : PathTypeStrings()) {
46 std::string path_string = PathToString(GetPath(path_type));
47 boost::replace_all(new_text, path_type, path_string);
48 }
49 return new_text;
50 }
51
52 namespace xpr = boost::xpressive;
53 const xpr::sregex REGEX_NON_BRACKET = *~(xpr::set= '<', '>');
54 const std::string BROWSEPATH_TAG_OPEN_PRE("<" + TextLinker::BROWSE_PATH_TAG);
55 const std::string BROWSEPATH_TAG_CLOSE("</" + TextLinker::BROWSE_PATH_TAG + ">");
56 const xpr::sregex BROWSEPATH_SEARCH = BROWSEPATH_TAG_OPEN_PRE >> xpr::_s >> (xpr::s1 = REGEX_NON_BRACKET) >> ">" >>
57 (xpr::s2 = REGEX_NON_BRACKET) >> BROWSEPATH_TAG_CLOSE;
58
59 /** Parses TextLinker::BROWSE_PATH_TAG%s within @p text, replacing string representation of PathType%s with
60 * current path for that PathType. If link label is empty, inserts resolved link argument as label */
BrowsePathLinkText(const std::string & text)61 std::string BrowsePathLinkText(const std::string& text) {
62 if (!boost::contains(text, BROWSEPATH_TAG_CLOSE))
63 return text;
64
65 std::string retval(text);
66 auto text_it = retval.begin();
67 xpr::smatch match;
68 auto invalid_path_str = PathTypeToString(PATH_INVALID);
69
70 while (true) {
71 if (!xpr::regex_search(text_it, retval.end(), match, BROWSEPATH_SEARCH, xpr::regex_constants::match_default))
72 break;
73
74 auto link_arg = ResolveNestedPathTypes(match[1]);
75 std::string link_label(match[2]);
76 if (link_label.empty())
77 link_label = link_arg;
78 else
79 link_label = ResolveNestedPathTypes(link_label);
80
81 // Only support argument containing at least one valid PathType
82 if (link_arg == match[1].str() || boost::contains(link_arg, invalid_path_str)) {
83 link_arg = invalid_path_str;
84 link_label = UserString("ERROR") + ": " + link_label;
85 }
86
87 auto resolved_link = BROWSEPATH_TAG_OPEN_PRE + " " + link_arg + ">" + link_label + BROWSEPATH_TAG_CLOSE;
88
89 retval.replace(text_it + match.position(), text_it + match.position() + match.length(), resolved_link);
90
91 text_it = retval.end() - match.suffix().length();
92 }
93
94 return retval;
95 }
96 }
97
98 ///////////////////////////////////////
99 // LinkText
100 ///////////////////////////////////////
LinkText(GG::X x,GG::Y y,GG::X w,const std::string & str,const std::shared_ptr<GG::Font> & font,GG::Flags<GG::TextFormat> format,GG::Clr color)101 LinkText::LinkText(GG::X x, GG::Y y, GG::X w, const std::string& str, const std::shared_ptr<GG::Font>& font,
102 GG::Flags<GG::TextFormat> format/* = GG::FORMAT_NONE*/, GG::Clr color/* = GG::CLR_BLACK*/) :
103 GG::TextControl(x, y, w, GG::Y1, str, font, color, format, GG::INTERACTIVE),
104 TextLinker(),
105 m_raw_text(str)
106 {
107 Resize(TextLowerRight() - TextUpperLeft());
108 FindLinks();
109 MarkLinks();
110 }
111
LinkText(GG::X x,GG::Y y,const std::string & str,const std::shared_ptr<GG::Font> & font,GG::Clr color)112 LinkText::LinkText(GG::X x, GG::Y y, const std::string& str, const std::shared_ptr<GG::Font>& font,
113 GG::Clr color/* = GG::CLR_BLACK*/) :
114 GG::TextControl(x, y, GG::X1, GG::Y1, str, font, color, GG::FORMAT_NOWRAP, GG::INTERACTIVE),
115 TextLinker(),
116 m_raw_text(str)
117 {
118 FindLinks();
119 MarkLinks();
120 }
121
Render()122 void LinkText::Render() {
123 GG::TextControl::Render();
124 TextLinker::Render_();
125 }
126
SetText(const std::string & str)127 void LinkText::SetText(const std::string& str) {
128 m_raw_text = str;
129 FindLinks();
130 MarkLinks();
131 }
132
SetLinkedText(const std::string & str)133 void LinkText::SetLinkedText(const std::string& str)
134 { GG::TextControl::SetText(str); }
135
TextUpperLeft() const136 GG::Pt LinkText::TextUpperLeft() const
137 { return GG::TextControl::TextUpperLeft(); }
138
TextLowerRight() const139 GG::Pt LinkText::TextLowerRight() const
140 { return GG::TextControl::TextLowerRight(); }
141
GetLineData() const142 const std::vector<GG::Font::LineData>& LinkText::GetLineData() const
143 { return GG::TextControl::GetLineData(); }
144
GetFont() const145 const std::shared_ptr<GG::Font>& LinkText::GetFont() const
146 { return GG::TextControl::GetFont(); }
147
RawText() const148 const std::string& LinkText::RawText() const
149 { return m_raw_text; }
150
LClick(const GG::Pt & pt,GG::Flags<GG::ModKey> mod_keys)151 void LinkText::LClick(const GG::Pt& pt, GG::Flags<GG::ModKey> mod_keys)
152 { TextLinker::LClick_(pt, mod_keys); }
153
RClick(const GG::Pt & pt,GG::Flags<GG::ModKey> mod_keys)154 void LinkText::RClick(const GG::Pt& pt, GG::Flags<GG::ModKey> mod_keys) {
155
156 auto rclick_action = [this, pt, mod_keys]() { TextLinker::RClick_(pt, mod_keys); };
157 auto copy_action = [this]() { GG::GUI::GetGUI()->CopyWndText(this); };
158
159 // create popup menu
160 auto popup = GG::Wnd::Create<CUIPopupMenu>(pt.x, pt.y);
161 if (GetLinkUnderPt(pt) != -1) {
162 popup->AddMenuItem(GG::MenuItem(UserString("OPEN"), false, false, rclick_action));
163 popup->AddMenuItem(GG::MenuItem(true)); // separator
164 }
165 popup->AddMenuItem(GG::MenuItem(UserString("HOTKEY_COPY"), false, false, copy_action));
166
167 popup->Run();
168 }
169
MouseHere(const GG::Pt & pt,GG::Flags<GG::ModKey> mod_keys)170 void LinkText::MouseHere(const GG::Pt& pt, GG::Flags<GG::ModKey> mod_keys)
171 { TextLinker::MouseHere_(pt, mod_keys); }
172
MouseLeave()173 void LinkText::MouseLeave()
174 { TextLinker::MouseLeave_(); }
175
SizeMove(const GG::Pt & ul,const GG::Pt & lr)176 void LinkText::SizeMove(const GG::Pt& ul, const GG::Pt& lr) {
177 bool resized = Size() != (lr-ul);
178 GG::TextControl::SizeMove(ul, lr);
179 if (resized)
180 LocateLinks();
181 }
182
183 ///////////////////////////////////////
184 // LinkDecorator
185 ///////////////////////////////////////
186
Decorate(const std::string & target,const std::string & content) const187 std::string LinkDecorator::Decorate(const std::string& target, const std::string& content) const{
188 return GG::RgbaTag(ClientUI::DefaultLinkColor()) + content + LINK_FORMAT_CLOSE;
189 }
190
DecorateRollover(const std::string & target,const std::string & content) const191 std::string LinkDecorator::DecorateRollover(const std::string& target, const std::string& content) const{
192 return GG::RgbaTag(ClientUI::RolloverLinkColor()) + content + LINK_FORMAT_CLOSE;
193 }
194
CastStringToInt(const std::string & str)195 int LinkDecorator::CastStringToInt(const std::string& str) {
196 std::stringstream ss;
197 ss << str;
198 int retval = -1;
199 ss >> retval;
200
201 if (ss.eof())
202 return retval;
203
204 return -1;
205 }
206
Decorate(const std::string & object_id_str,const std::string & content) const207 std::string ColorByOwner::Decorate(const std::string& object_id_str, const std::string& content) const {
208 GG::Clr color = ClientUI::DefaultLinkColor();
209 const Empire* empire = nullptr;
210 // get object indicated by object_id, and then get object's owner, if any
211 int object_id = CastStringToInt(object_id_str);
212 auto object = Objects().get(object_id);
213 if (object && !object->Unowned())
214 empire = GetEmpire(object->Owner());
215 if (empire)
216 color = empire->Color();
217 return GG::RgbaTag(color) + content + "</rgba>";
218 }
219
Decorate(const std::string & path_type,const std::string & content) const220 std::string PathTypeDecorator::Decorate(const std::string& path_type, const std::string& content) const {
221 return LinkDecorator::Decorate(path_type, BrowsePathLinkText(content));
222 }
223
DecorateRollover(const std::string & path_type,const std::string & content) const224 std::string PathTypeDecorator::DecorateRollover(const std::string& path_type, const std::string& content) const {
225 return LinkDecorator::DecorateRollover(path_type, BrowsePathLinkText(content));
226 }
227
228
229 ///////////////////////////////////////
230 // TextLinker::Link
231 ///////////////////////////////////////
232 struct TextLinker::Link {
233 std::string type; ///< contents of type field of link tag (eg "planet" in <planet 3>)
234 std::string data; ///< contents of data field of link tag (eg "3" in <planet 3>)
235 std::vector<GG::Rect> rects; ///< the rectangles in which this link falls, in window coordinates (some links may span more than one line)
236 std::pair<int, int> text_posn; ///< the index of the first (.first) and last + 1 (.second) characters in the raw link text
237 std::pair<int, int> real_text_posn; ///< the index of the first and last + 1 characters in the current (potentially decorated) content string
238 };
239
240
241 ///////////////////////////////////////
242 // TextLinker
243 ///////////////////////////////////////
TextLinker()244 TextLinker::TextLinker() :
245 m_links(),
246 m_rollover_link(-1)
247 {
248 RegisterLinkTags();
249 }
250
~TextLinker()251 TextLinker::~TextLinker()
252 {}
253
SetDecorator(const std::string & link_type,LinkDecorator * decorator)254 void TextLinker::SetDecorator(const std::string& link_type, LinkDecorator* decorator) {
255 m_decorators[link_type] = std::shared_ptr<LinkDecorator>(decorator);
256 MarkLinks();
257 }
258
LinkDefaultFormatTag(const Link & link,const std::string & content) const259 std::string TextLinker::LinkDefaultFormatTag(const Link& link, const std::string& content) const {
260 const LinkDecorator* decorator = &DEFAULT_DECORATOR;
261
262 auto it = m_decorators.find(link.type);
263 if (it != m_decorators.end()){
264 decorator = it->second.get();
265 }
266
267 return decorator->Decorate(link.data, content);
268 }
269
LinkRolloverFormatTag(const Link & link,const std::string & content) const270 std::string TextLinker::LinkRolloverFormatTag(const Link& link, const std::string& content) const {
271 const LinkDecorator* decorator = &DEFAULT_DECORATOR;
272
273 auto it = m_decorators.find(link.type);
274 if (it != m_decorators.end())
275 decorator = it->second.get();
276
277 return decorator->DecorateRollover(link.data, content);
278 }
279
Render_()280 void TextLinker::Render_() {
281 if (!RENDER_DEBUGGING_LINK_RECTS)
282 return;
283
284 // draw yellow box around whole text block
285 GG::Rect bounds(TextUpperLeft(), TextLowerRight());
286 FlatRectangle(bounds.ul, bounds.lr, GG::CLR_ZERO, GG::CLR_YELLOW, 1);
287
288 // draw red box around individual linkified bits of text within block
289 for (const Link& link : m_links) {
290 for (const GG::Rect& rect : link.rects) {
291 GG::Rect r = TextUpperLeft() + rect;
292 FlatRectangle(r.ul, r.lr, GG::CLR_ZERO, GG::CLR_RED, 1);
293 }
294 }
295 }
296
LClick_(const GG::Pt & pt,GG::Flags<GG::ModKey> mod_keys)297 void TextLinker::LClick_(const GG::Pt& pt, GG::Flags<GG::ModKey> mod_keys) {
298 int sel_link = GetLinkUnderPt(pt);
299 if (sel_link == -1)
300 return;
301 if (sel_link < 0 || sel_link >= static_cast<int>(m_links.size())) {
302 ErrorLogger() << "TextLinker::LClick_ found out of bounds sel_link!";
303 return;
304 }
305
306 const std::string LINK_TYPE = m_links[sel_link].type;
307 const std::string DATA = m_links[sel_link].data;
308
309 LinkClickedSignal(LINK_TYPE, DATA);
310 }
311
RClick_(const GG::Pt & pt,GG::Flags<GG::ModKey> mod_keys)312 void TextLinker::RClick_(const GG::Pt& pt, GG::Flags<GG::ModKey> mod_keys) {
313 int sel_link = GetLinkUnderPt(pt);
314 if (sel_link == -1)
315 return;
316 if (sel_link < 0 || sel_link >= static_cast<int>(m_links.size())) {
317 ErrorLogger() << "TextLinker::RClick_ found out of bounds sel_link!";
318 return;
319 }
320
321 const std::string& LINK_TYPE = m_links[sel_link].type;
322 const std::string& DATA = m_links[sel_link].data;
323
324 LinkRightClickedSignal(LINK_TYPE, DATA);
325 }
326
LDoubleClick_(const GG::Pt & pt,GG::Flags<GG::ModKey> mod_keys)327 void TextLinker::LDoubleClick_(const GG::Pt& pt, GG::Flags<GG::ModKey> mod_keys) {
328 int sel_link = GetLinkUnderPt(pt);
329 if (sel_link == -1)
330 return;
331 if (sel_link < 0 || sel_link >= static_cast<int>(m_links.size())) {
332 ErrorLogger() << "TextLinker::DoubleClick_ found out of bounds sel_link!";
333 return;
334 }
335
336 const std::string& LINK_TYPE = m_links[sel_link].type;
337 const std::string& DATA = m_links[sel_link].data;
338
339 LinkDoubleClickedSignal(LINK_TYPE, DATA);
340 }
341
MouseHere_(const GG::Pt & pt,GG::Flags<GG::ModKey> mod_keys)342 void TextLinker::MouseHere_(const GG::Pt& pt, GG::Flags<GG::ModKey> mod_keys) {
343 int rollover_link = GetLinkUnderPt(pt);
344 if (rollover_link != m_rollover_link) {
345 m_rollover_link = rollover_link;
346 MarkLinks();
347 }
348 }
349
MouseLeave_()350 void TextLinker::MouseLeave_() {
351 m_rollover_link = -1;
352 MarkLinks();
353 }
354
FindLinks()355 void TextLinker::FindLinks() {
356 m_links.clear();
357
358 GG::Y y_posn(0); // y-coordinate of the top of the current line
359 Link link;
360
361 // control text needs to be updated so that the line data is calculated from
362 // the raw text and not the marked text set in MarkText().
363 SetLinkedText(RawText());
364
365 for (const auto& curr_line : GetLineData()) {
366 for (const auto& curr_char : curr_line.char_data) {
367 for (const auto& tag : curr_char.tags) {
368 if (tag->tag_name == VarText::PLANET_ID_TAG ||
369 tag->tag_name == VarText::SYSTEM_ID_TAG ||
370 tag->tag_name == VarText::SHIP_ID_TAG ||
371 tag->tag_name == VarText::FLEET_ID_TAG ||
372 tag->tag_name == VarText::BUILDING_ID_TAG ||
373 tag->tag_name == VarText::FIELD_ID_TAG ||
374 tag->tag_name == VarText::COMBAT_ID_TAG ||
375 tag->tag_name == VarText::EMPIRE_ID_TAG ||
376 tag->tag_name == VarText::DESIGN_ID_TAG ||
377 tag->tag_name == VarText::PREDEFINED_DESIGN_TAG ||
378 tag->tag_name == VarText::TECH_TAG ||
379 tag->tag_name == VarText::BUILDING_TYPE_TAG ||
380 tag->tag_name == VarText::SPECIAL_TAG ||
381 tag->tag_name == VarText::SHIP_HULL_TAG ||
382 tag->tag_name == VarText::SHIP_PART_TAG ||
383 tag->tag_name == VarText::SPECIES_TAG ||
384 tag->tag_name == VarText::FIELD_TYPE_TAG ||
385 tag->tag_name == VarText::METER_TYPE_TAG ||
386 tag->tag_name == TextLinker::ENCYCLOPEDIA_TAG ||
387 tag->tag_name == TextLinker::GRAPH_TAG ||
388 tag->tag_name == TextLinker::URL_TAG ||
389 tag->tag_name == TextLinker::BROWSE_PATH_TAG)
390 {
391 link.type = tag->tag_name;
392 if (tag->close_tag) {
393 link.text_posn.second = Value(curr_char.string_index);
394 m_links.push_back(link);
395 link = Link();
396 } else {
397 if (!tag->params.empty()) {
398 if (tag->tag_name == TextLinker::BROWSE_PATH_TAG) {
399 auto all_param_str(AllParamsAsString(tag->params));
400 link.data = ResolveNestedPathTypes(all_param_str);
401 // BROWSE_PATH_TAG requires a PathType within param
402 if (link.data == all_param_str) {
403 ErrorLogger() << "Invalid param \"" << link.data << "\" for tag "
404 << TextLinker::BROWSE_PATH_TAG;
405 link.data = PathTypeToString(PATH_INVALID);
406 }
407 } else {
408 link.data = tag->params[0];
409 }
410 } else {
411 link.data.clear();
412 }
413 link.text_posn.first = Value(curr_char.string_index);
414 for (auto& itag : curr_char.tags) {
415 link.text_posn.first -= Value(itag->StringSize());
416 }
417 }
418 // Before decoration, the real positions are the same as the raw ones
419 link.real_text_posn = link.text_posn;
420 }
421 }
422 }
423 }
424
425 LocateLinks();
426 }
427
LocateLinks()428 void TextLinker::LocateLinks() {
429 if (m_links.empty())
430 return;
431
432 GG::Y y_posn(0); // y-coordinate of the top of the current line
433 const auto& font = GetFont();
434
435 // We assume that links are stored in m_links in the order they appear in the text.
436 // We shall iterate through the text, updating the rectangles of a link whenever we know we are inside it
437 auto current_link = m_links.begin();
438 bool inside_link = false;
439
440 for (const auto& curr_line : GetLineData()) {
441 // if the last line ended without the current tag ending
442 if (inside_link)
443 current_link->rects.push_back(GG::Rect(GG::X0, y_posn, GG::X0,
444 y_posn + font->Height()));
445
446 for (unsigned int i = 0; i < curr_line.char_data.size(); ++i) {
447 // The link text_posn is at the beginning of the tag, whereas
448 // char_data jumps over tags. That is why we cannot test for precise equality
449 if (!inside_link && curr_line.char_data[i].string_index >= current_link->real_text_posn.first &&
450 curr_line.char_data[i].string_index < current_link->real_text_posn.second)
451 {
452 inside_link = true;
453 // Clear out the old rectangles
454 current_link->rects.clear();
455 current_link->rects.push_back(GG::Rect(i ? curr_line.char_data[i - 1].extent : GG::X0,
456 y_posn, GG::X0, y_posn + font->Height()));
457 } else if (inside_link && curr_line.char_data[i].string_index >= current_link->real_text_posn.second) {
458 inside_link = false;
459 current_link->rects.back().lr.x = i ? curr_line.char_data[i - 1].extent : GG::X0;
460 ++current_link;
461 if (current_link == m_links.end())
462 return;
463 }
464 }
465
466 // if a line is ending without the current tag ending
467 if (inside_link)
468 current_link->rects.back().lr.x = curr_line.char_data.empty() ? GG::X0 : curr_line.char_data.back().extent;
469
470 y_posn += font->Lineskip();
471 }
472 }
473
GetLinkUnderPt(const GG::Pt & pt)474 int TextLinker::GetLinkUnderPt(const GG::Pt& pt) {
475 std::vector<Link> links;
476 try {
477 links = m_links;
478 } catch (...) {
479 ErrorLogger() << "exception caught copying links in GetLinkUnderPt";
480 return -1;
481 }
482
483 GG::Pt tex_ul = TextUpperLeft();
484
485 for (unsigned int i = 0; i < links.size(); ++i) {
486 const Link& link = links[i];
487
488 for (const GG::Rect& link_rect : link.rects) {
489 GG::Rect r = tex_ul + link_rect;
490 if (r.Contains(pt))
491 return i;
492 }
493 }
494 return -1; // no link found
495 }
496
MarkLinks()497 void TextLinker::MarkLinks() {
498 const std::string& raw_text = RawText();
499
500 if (m_links.empty()) {
501 SetLinkedText(raw_text);
502 return;
503 }
504
505 int copy_start_index = 0;
506 auto raw_text_start_it = raw_text.begin();
507
508 std::string marked_text;
509
510 // copy text from current copy_start_index up to just before start of next link
511 for (int i = 0; i < static_cast<int>(m_links.size()); ++i) {
512 Link& link = m_links[i];
513 int link_start_index = link.text_posn.first;
514 int link_end_index = link.text_posn.second;
515
516 // copy raw text up to start of first link
517 std::copy(raw_text_start_it + copy_start_index, raw_text_start_it + link_start_index, std::back_inserter(marked_text));
518
519 std::string content = std::string(raw_text_start_it + link_start_index, raw_text_start_it + link_end_index);
520
521 int length_before = marked_text.size();
522 // add link markup open tag
523 if (i == m_rollover_link)
524 marked_text += LinkRolloverFormatTag(link, content);
525 else
526 marked_text += LinkDefaultFormatTag(link, content);
527
528 // update copy point for following text
529 copy_start_index = link_end_index;
530 // update m_links to know the real positions of the links in the decorated text
531 // this makes you able to call LocateLinks without resetting the text.
532 link.real_text_posn.first = length_before;
533 link.real_text_posn.second = marked_text.size();
534 }
535
536 // copy remaining text after last link
537 std::copy(raw_text_start_it + copy_start_index, raw_text.end(), std::back_inserter(marked_text));
538
539 // set underlying UI control text
540 SetLinkedText(marked_text);
541 }
542
543 const LinkDecorator TextLinker::DEFAULT_DECORATOR;
544
LinkTaggedText(const std::string & tag,const std::string & stringtable_entry)545 std::string LinkTaggedText(const std::string& tag, const std::string& stringtable_entry)
546 { return "<" + tag + " " + stringtable_entry + ">" + UserString(stringtable_entry) + "</" + tag + ">"; }
547
LinkTaggedIDText(const std::string & tag,int id,const std::string & text)548 std::string LinkTaggedIDText(const std::string& tag, int id, const std::string& text)
549 { return "<" + tag + " " + std::to_string(id) + ">" + text + "</" + tag + ">"; }
550
LinkTaggedPresetText(const std::string & tag,const std::string & stringtable_entry,const std::string & display_text)551 std::string LinkTaggedPresetText(const std::string& tag, const std::string& stringtable_entry, const std::string& display_text)
552 { return "<" + tag + " " + stringtable_entry + ">" + display_text + "</" + tag + ">"; }
553
554 namespace {
555 static bool link_tags_registered = false;
556 }
557
RegisterLinkTags()558 void RegisterLinkTags() {
559 if (link_tags_registered)
560 return;
561 link_tags_registered = true;
562
563 // need to register the tags that link text uses so GG::Font will know how to (not) render them
564 GG::Font::RegisterKnownTag(VarText::PLANET_ID_TAG);
565 GG::Font::RegisterKnownTag(VarText::SYSTEM_ID_TAG);
566 GG::Font::RegisterKnownTag(VarText::SHIP_ID_TAG);
567 GG::Font::RegisterKnownTag(VarText::FLEET_ID_TAG);
568 GG::Font::RegisterKnownTag(VarText::BUILDING_ID_TAG);
569 GG::Font::RegisterKnownTag(VarText::FIELD_ID_TAG);
570
571 GG::Font::RegisterKnownTag(VarText::COMBAT_ID_TAG);
572
573 GG::Font::RegisterKnownTag(VarText::EMPIRE_ID_TAG);
574 GG::Font::RegisterKnownTag(VarText::DESIGN_ID_TAG);
575 GG::Font::RegisterKnownTag(VarText::PREDEFINED_DESIGN_TAG);
576
577 GG::Font::RegisterKnownTag(VarText::TECH_TAG);
578 GG::Font::RegisterKnownTag(VarText::BUILDING_TYPE_TAG);
579 GG::Font::RegisterKnownTag(VarText::SPECIAL_TAG);
580 GG::Font::RegisterKnownTag(VarText::SHIP_HULL_TAG);
581 GG::Font::RegisterKnownTag(VarText::SHIP_PART_TAG);
582 GG::Font::RegisterKnownTag(VarText::SPECIES_TAG);
583 GG::Font::RegisterKnownTag(VarText::FIELD_TYPE_TAG);
584 GG::Font::RegisterKnownTag(VarText::METER_TYPE_TAG);
585
586 GG::Font::RegisterKnownTag(TextLinker::ENCYCLOPEDIA_TAG);
587 GG::Font::RegisterKnownTag(TextLinker::GRAPH_TAG);
588 GG::Font::RegisterKnownTag(TextLinker::URL_TAG);
589 GG::Font::RegisterKnownTag(TextLinker::BROWSE_PATH_TAG);
590 }
591