Freeciv21
Develop your civilization from humble roots to a global empire
featured_text.cpp
Go to the documentation of this file.
1 /*__ ___ ***************************************
2 / \ / \ Copyright (c) 1996-2020 Freeciv21 and Freeciv
3 \_ \ / __/ contributors. This file is part of Freeciv21.
4  _\ \ / /__ Freeciv21 is free software: you can redistribute it
5  \___ \____/ __/ and/or modify it under the terms of the GNU General
6  \_ _/ Public License as published by the Free Software
7  | @ @ \_ Foundation, either version 3 of the License,
8  | or (at your option) any later version.
9  _/ /\ You should have received a copy of the GNU
10  /o) (o/\ \_ General Public License along with Freeciv21.
11  \_____/ / If not, see https://www.gnu.org/licenses/.
12  \____/ ********************************************************/
13 #include <cstdarg>
14 #include <cstring>
15 
16 // utility
17 #include "fcintl.h"
18 #include "log.h"
19 #include "shared.h"
20 #include "support.h"
21 
22 // common
23 #include "city.h"
24 #include "combat.h"
25 #include "game.h"
26 #include "map.h"
27 #include "tile.h"
28 #include "unit.h"
29 
30 #include "featured_text.h"
31 
32 #define SEQ_START '['
33 #define SEQ_STOP ']'
34 #define SEQ_END '/'
35 
36 #define MAX_LEN_STR 32
37 #define log_featured_text qDebug
38 
39 #define text_tag_list_rev_iterate(tags, ptag) \
40  TYPED_LIST_ITERATE_REV(struct text_tag, tags, ptag)
41 #define text_tag_list_rev_iterate_end LIST_ITERATE_REV_END
42 
43 // The text_tag structure. See documentation in featured_text.h.
44 struct text_tag {
45  enum text_tag_type type; // The type of the tag.
46  ft_offset_t start_offset; // The start offset (in bytes).
47  ft_offset_t stop_offset; // The stop offset (in bytes).
48  union {
49  struct { // TTT_COLOR only.
50  char foreground[MAX_LEN_STR]; // foreground color name.
51  char background[MAX_LEN_STR]; // background color name.
52  } color;
53  struct { // TTT_LINK only.
54  enum text_link_type type; // The target type of the link.
55  int id; // The id of linked object.
56  char name[MAX_LEN_STR]; // A string to indentify the link.
57  } link;
58  };
59 };
60 
62  ST_START, // e.g. [sequence].
63  ST_STOP, /* e.g. [/sequence]. */
64  ST_SINGLE /* e.g. [sequence/]. */
65 };
66 
67 // Predefined colors.
68 const struct ft_color ftc_any = FT_COLOR(nullptr, nullptr);
69 
70 const struct ft_color ftc_warning = FT_COLOR("#FF0000", nullptr);
71 const struct ft_color ftc_log = FT_COLOR("#7F7F7F", nullptr);
72 const struct ft_color ftc_server = FT_COLOR("#8B0000", nullptr);
73 const struct ft_color ftc_client = FT_COLOR("#EF7F00", nullptr);
74 const struct ft_color ftc_editor = FT_COLOR("#0000FF", nullptr);
75 const struct ft_color ftc_command = FT_COLOR("#006400", nullptr);
76 struct ft_color ftc_changed = FT_COLOR("#FF0000", nullptr);
77 const struct ft_color ftc_server_prompt = FT_COLOR("#FF0000", "#BEBEBE");
78 const struct ft_color ftc_player_lost = FT_COLOR("#FFFFFF", "#000000");
79 const struct ft_color ftc_game_start = FT_COLOR("#00FF00", "#115511");
80 
81 const struct ft_color ftc_chat_public = FT_COLOR("#00008B", nullptr);
82 const struct ft_color ftc_chat_ally = FT_COLOR("#551166", nullptr);
83 const struct ft_color ftc_chat_private = FT_COLOR("#A020F0", nullptr);
84 const struct ft_color ftc_chat_luaconsole = FT_COLOR("#006400", nullptr);
85 
86 const struct ft_color ftc_vote_public = FT_COLOR("#FFFFFF", "#AA0000");
87 const struct ft_color ftc_vote_team = FT_COLOR("#FFFFFF", "#5555CC");
88 const struct ft_color ftc_vote_passed = FT_COLOR("#006400", "#AAFFAA");
89 const struct ft_color ftc_vote_failed = FT_COLOR("#8B0000", "#FFAAAA");
90 const struct ft_color ftc_vote_yes = FT_COLOR("#000000", "#C8FFD5");
91 const struct ft_color ftc_vote_no = FT_COLOR("#000000", "#FFD2D2");
92 const struct ft_color ftc_vote_abstain = FT_COLOR("#000000", "#E8E8E8");
93 
94 const struct ft_color ftc_luaconsole_input = FT_COLOR("#2B008B", nullptr);
95 const struct ft_color ftc_luaconsole_error = FT_COLOR("#FF0000", nullptr);
96 const struct ft_color ftc_luaconsole_warn = FT_COLOR("#CF2020", nullptr);
97 const struct ft_color ftc_luaconsole_normal = FT_COLOR("#006400", nullptr);
98 const struct ft_color ftc_luaconsole_verbose = FT_COLOR("#B8B8B8", nullptr);
99 
104 static const char *text_tag_type_name(enum text_tag_type type)
105 {
106  switch (type) {
107  case TTT_BOLD:
108  return "bold";
109  case TTT_ITALIC:
110  return "italic";
111  case TTT_STRIKE:
112  return "strike";
113  case TTT_UNDERLINE:
114  return "underline";
115  case TTT_COLOR:
116  return "color";
117  case TTT_LINK:
118  return "link";
119  case TTT_INVALID:
120  return nullptr;
121  };
122  // Don't handle the default case to be warned if a new value was added.
123  return nullptr;
124 }
125 
130 static const char *text_tag_type_short_name(enum text_tag_type type)
131 {
132  switch (type) {
133  case TTT_BOLD:
134  return "b";
135  case TTT_ITALIC:
136  return "i";
137  case TTT_STRIKE:
138  return "s";
139  case TTT_UNDERLINE:
140  return "u";
141  case TTT_COLOR:
142  return "c";
143  case TTT_LINK:
144  return "l";
145  case TTT_INVALID:
146  return nullptr;
147  };
148  // Don't handle the default case to be warned if a new value was added.
149  return nullptr;
150 }
151 
155 static const char *text_link_type_name(enum text_link_type type)
156 {
157  switch (type) {
158  case TLT_CITY:
159  return "city";
160  case TLT_TILE:
161  return "tile";
162  case TLT_UNIT:
163  return "unit";
164  case TLT_INVALID:
165  fc_assert_ret_val(type != TLT_INVALID, nullptr);
166  };
167  // Don't handle the default case to be warned if a new value was added.
168  return nullptr;
169 }
170 
175 static bool find_option(const char *buf_in, const char *option,
176  char *buf_out, size_t write_len)
177 {
178  size_t option_len = qstrlen(option);
179  const char *buf_in_ptr = buf_in;
180  int len = qstrlen(buf_in);
181 
182  while (*buf_in != '\0' && (buf_in - buf_in_ptr) < len) {
183  while (QChar::isSpace(*buf_in) && *buf_in != '\0') {
184  buf_in++;
185  }
186 
187  if (0 == fc_strncasecmp(buf_in, option, option_len)) {
188  buf_in += option_len; // This is this one.
189 
190  while ((QChar::isSpace(*buf_in) || *buf_in == '=')
191  && *buf_in != '\0') {
192  buf_in++;
193  }
194  if (*buf_in == '"') { // Quote case.
195  const char *end = strchr(++buf_in, '"');
196 
197  if (!end) {
198  return false;
199  }
200  if (end - buf_in + 1 > 0) {
201  fc_strlcpy(buf_out, buf_in, MIN(end - buf_in + 1, write_len));
202  } else {
203  *buf_out = '\0';
204  }
205  return true;
206  } else {
207  while (QChar::isLetterOrNumber(*buf_in) && write_len > 1) {
208  *buf_out++ = *buf_in++;
209  write_len--;
210  }
211  *buf_out = '\0';
212  return true;
213  }
214  }
215  buf_in++;
216  }
217  return false;
218 }
219 
224 static bool text_tag_init_from_sequence(struct text_tag *ptag,
225  enum text_tag_type type,
226  ft_offset_t start_offset,
227  QString &qsequence)
228 {
229  ptag->type = type;
230  ptag->start_offset = start_offset;
232 
233  QByteArray ba = qsequence.toLocal8Bit();
234  const char *sequence = ba.constData();
235 
236  switch (type) {
237  case TTT_BOLD:
238  case TTT_ITALIC:
239  case TTT_STRIKE:
240  case TTT_UNDERLINE:
241  return true;
242  case TTT_COLOR:
243  if (!find_option(sequence, "foreground", ptag->color.foreground,
244  sizeof(ptag->color.foreground))
245  && !find_option(sequence, "fg", ptag->color.foreground,
246  sizeof(ptag->color.foreground))) {
247  ptag->color.foreground[0] = '\0';
248  }
249  if (!find_option(sequence, "background", ptag->color.background,
250  sizeof(ptag->color.background))
251  && !find_option(sequence, "bg", ptag->color.background,
252  sizeof(ptag->color.background))) {
253  ptag->color.background[0] = '\0';
254  }
255  return true;
256  break;
257  case TTT_LINK: {
258  char buf[64];
259  const char *name;
260  int i;
261 
262  if (!find_option(sequence, "target", buf, sizeof(buf))
263  && !find_option(sequence, "tgt", buf, sizeof(buf))) {
264  log_featured_text("text_tag_init_from_sequence(): "
265  "target link type not set.");
266  return false;
267  }
268 
269  ptag->link.type = TLT_INVALID;
270  for (i = 0; (name = text_link_type_name(text_link_type(i))); i++) {
271  if (0 == fc_strncasecmp(buf, name, qstrlen(name))) {
272  ptag->link.type = text_link_type(i);
273  break;
274  }
275  }
276  if (ptag->link.type == TLT_INVALID) {
277  log_featured_text("text_tag_init_from_sequence(): "
278  "target link type not supported (\"%s\").",
279  buf);
280  return false;
281  }
282 
283  switch (ptag->link.type) {
284  case TLT_CITY:
285  if (!find_option(sequence, "id", buf, sizeof(buf))) {
286  log_featured_text("text_tag_init_from_sequence(): "
287  "city link without id.");
288  return false;
289  }
290  if (!str_to_int(buf, &ptag->link.id)) {
291  log_featured_text("text_tag_init_from_sequence(): "
292  "city link without valid id (\"%s\").",
293  buf);
294  return false;
295  }
296 
297  if (!find_option(sequence, "name", ptag->link.name,
298  sizeof(ptag->link.name))) {
299  // Set something as name.
300  fc_snprintf(ptag->link.name, sizeof(ptag->link.name), "CITY_ID%d",
301  ptag->link.id);
302  }
303  return true;
304  case TLT_TILE:
305  struct tile *ptile;
306  int x, y;
307 
308  if (!find_option(sequence, "x", buf, sizeof(buf))) {
309  log_featured_text("text_tag_init_from_sequence(): "
310  "tile link without x coordinate.");
311  return false;
312  }
313  if (!str_to_int(buf, &x)) {
314  log_featured_text("text_tag_init_from_sequence(): "
315  "tile link without valid x coordinate "
316  "(\"%s\").",
317  buf);
318  return false;
319  }
320 
321  if (!find_option(sequence, "y", buf, sizeof(buf))) {
322  log_featured_text("text_tag_init_from_sequence(): "
323  "tile link without y coordinate.");
324  return false;
325  }
326  if (!str_to_int(buf, &y)) {
327  log_featured_text("text_tag_init_from_sequence(): "
328  "tile link without valid y coordinate "
329  "(\"%s\").",
330  buf);
331  return false;
332  }
333 
334  ptile = map_pos_to_tile(&(wld.map), x, y);
335  if (!ptile) {
336  log_featured_text("text_tag_init_from_sequence(): "
337  "(%d, %d) are not valid coordinates "
338  "in this game.",
339  x, y);
340  return false;
341  }
342  ptag->link.id = tile_index(ptile);
343  fc_snprintf(ptag->link.name, sizeof(ptag->link.name), "(%d, %d)",
344  TILE_XY(ptile));
345  return true;
346  case TLT_UNIT:
347  if (!find_option(sequence, "id", buf, sizeof(buf))) {
348  log_featured_text("text_tag_init_from_sequence(): "
349  "unit link without id.");
350  return false;
351  }
352  if (!str_to_int(buf, &ptag->link.id)) {
353  log_featured_text("text_tag_init_from_sequence(): "
354  "unit link without valid id (\"%s\").",
355  buf);
356  return false;
357  }
358 
359  if (!find_option(sequence, "name", ptag->link.name,
360  sizeof(ptag->link.name))) {
361  // Set something as name.
362  fc_snprintf(ptag->link.name, sizeof(ptag->link.name), "UNIT_ID%d",
363  ptag->link.id);
364  }
365  return true;
366  case TLT_INVALID:
367  fc_assert_ret_val(ptag->link.type != TLT_INVALID, false);
368  break;
369  };
370  } break;
371  case TTT_INVALID:
372  fc_assert(type != TTT_INVALID); // return below
373  };
374  return false;
375 }
376 
394 static bool text_tag_initv(struct text_tag *ptag, enum text_tag_type type,
395  ft_offset_t start_offset, ft_offset_t stop_offset,
396  va_list args)
397 {
398  ptag->type = type;
399  ptag->start_offset = start_offset;
400  ptag->stop_offset = stop_offset;
401 
402  switch (type) {
403  case TTT_BOLD:
404  case TTT_ITALIC:
405  case TTT_STRIKE:
406  case TTT_UNDERLINE:
407  return true;
408  case TTT_COLOR: {
409  const struct ft_color color = va_arg(args, struct ft_color);
410 
411  if ((nullptr == color.foreground || '\0' == color.foreground[0])
412  && (nullptr == color.background || '\0' == color.background[0])) {
413  return false; // No color at all.
414  }
415 
416  if (nullptr != color.foreground && '\0' != color.foreground[0]) {
417  sz_strlcpy(ptag->color.foreground, color.foreground);
418  } else {
419  ptag->color.foreground[0] = '\0';
420  }
421 
422  if (nullptr != color.background && '\0' != color.background[0]) {
423  sz_strlcpy(ptag->color.background, color.background);
424  } else {
425  ptag->color.background[0] = '\0';
426  }
427  }
428  return true;
429  break;
430  case TTT_LINK: {
431  ptag->link.type = text_link_type(va_arg(args, int));
432  switch (ptag->link.type) {
433  case TLT_CITY: {
434  struct city *pcity = va_arg(args, struct city *);
435 
436  if (!pcity) {
437  return false;
438  }
439  ptag->link.id = pcity->id;
440  sz_strlcpy(ptag->link.name, city_name_get(pcity));
441  }
442  return true;
443  case TLT_TILE: {
444  struct tile *ptile = va_arg(args, struct tile *);
445 
446  if (!ptile) {
447  return false;
448  }
449  ptag->link.id = tile_index(ptile);
450  fc_snprintf(ptag->link.name, sizeof(ptag->link.name), "(%d, %d)",
451  TILE_XY(ptile));
452  }
453  return true;
454  case TLT_UNIT: {
455  struct unit *punit = va_arg(args, struct unit *);
456 
457  if (!punit) {
458  return false;
459  }
460  ptag->link.id = punit->id;
461  sz_strlcpy(ptag->link.name, unit_name_translation(punit));
462  }
463  return true;
464  case TLT_INVALID:
465  fc_assert_ret_val(ptag->link.type != TLT_INVALID, false);
466  };
467  } break;
468  case TTT_INVALID:
469  fc_assert(type != TTT_INVALID); // return below
470  };
471  return false;
472 }
473 
477 static size_t text_tag_start_sequence(const struct text_tag *ptag, char *buf,
478  size_t len)
479 {
480  switch (ptag->type) {
481  case TTT_BOLD:
482  case TTT_ITALIC:
483  case TTT_STRIKE:
484  case TTT_UNDERLINE:
485  return fc_snprintf(buf, len, "%c%s%c", SEQ_START,
487  case TTT_COLOR: {
488  size_t ret = fc_snprintf(buf, len, "%c%s", SEQ_START,
490 
491  if (ptag->color.foreground[0] != '\0') {
492  ret += fc_snprintf(buf + ret, len - ret, " fg=\"%s\"",
493  ptag->color.foreground);
494  }
495  if (ptag->color.background[0] != '\0') {
496  ret += fc_snprintf(buf + ret, len - ret, " bg=\"%s\"",
497  ptag->color.background);
498  }
499  return ret + fc_snprintf(buf + ret, len - ret, "%c", SEQ_STOP);
500  } break;
501  case TTT_LINK: {
502  size_t ret = fc_snprintf(buf, len, "%c%s tgt=\"%s\"", SEQ_START,
504  text_link_type_name(ptag->link.type));
505 
506  switch (ptag->link.type) {
507  case TLT_CITY: {
508  struct city *pcity = game_city_by_number(ptag->link.id);
509 
510  if (pcity) {
511  ret += fc_snprintf(buf + ret, len - ret, " id=%d name=\"%s\"",
512  pcity->id, city_name_get(pcity));
513  } else {
514  ret += fc_snprintf(buf + ret, len - ret, " id=%d", ptag->link.id);
515  }
516  } break;
517  case TLT_TILE: {
518  struct tile *ptile = index_to_tile(&(wld.map), ptag->link.id);
519 
520  if (ptile) {
521  ret +=
522  fc_snprintf(buf + ret, len - ret, " x=%d y=%d", TILE_XY(ptile));
523  } else {
524  ret += fc_snprintf(buf + ret, len - ret, " id=%d", ptag->link.id);
525  }
526  } break;
527  case TLT_UNIT: {
528  struct unit *punit = game_unit_by_number(ptag->link.id);
529 
530  if (punit) {
531  ret += fc_snprintf(buf + ret, len - ret, " id=%d name=\"%s\"",
532  punit->id, unit_name_translation(punit));
533  } else {
534  ret += fc_snprintf(buf + ret, len - ret, " id=%d", ptag->link.id);
535  }
536  } break;
537  case TLT_INVALID:
538  fc_assert_ret_val(ptag->link.type != TLT_INVALID, 0);
539  };
540 
541  if (ptag->stop_offset == ptag->start_offset) {
542  /* This is a single sequence like [link ... /]. */
543  ret += fc_snprintf(buf + ret, len - ret, "%c", SEQ_END);
544  }
545 
546  return ret + fc_snprintf(buf + ret, len - ret, "%c", SEQ_STOP);
547  }
548  case TTT_INVALID:
549  fc_assert(ptag->type != TTT_INVALID); // return below
550  };
551  return 0;
552 }
553 
557 static size_t text_tag_stop_sequence(const struct text_tag *ptag, char *buf,
558  size_t len)
559 {
560  if (ptag->type == TTT_LINK && ptag->stop_offset == ptag->start_offset) {
561  // Should be already finished.
562  return 0;
563  }
564 
565  return fc_snprintf(buf, len, "%c%c%s%c", SEQ_START, SEQ_END,
567 }
568 
572 static size_t text_tag_replace_text(const struct text_tag *ptag, char *buf,
573  size_t len, bool replace_link_text)
574 {
575  if (ptag->type != TTT_LINK) {
576  return 0;
577  }
578 
579  if (replace_link_text) {
580  // The client might check if this should be updated or translated.
581  switch (ptag->link.type) {
582  case TLT_CITY: {
583  struct city *pcity = game_city_by_number(ptag->link.id);
584 
585  /* Note that if city_tile(pcity) is nullptr, then it is probably an
586  * invisible city (see client/packhand.c). Then, we don't
587  * use the current city name which is usually not complete,
588  * a dumb string using the city id. */
589  if (nullptr != pcity && nullptr != city_tile(pcity)) {
590  return fc_snprintf(buf, len, "%s", city_name_get(pcity));
591  }
592  } break;
593  case TLT_TILE:
594  break;
595  case TLT_UNIT: {
596  struct unit *punit = game_unit_by_number(ptag->link.id);
597 
598  if (punit) {
599  return fc_snprintf(buf, len, "%s", unit_name_translation(punit));
600  }
601  } break;
602  case TLT_INVALID:
603  fc_assert_ret_val(ptag->link.type != TLT_INVALID, false);
604  };
605  }
606 
607  if (ptag->link.type == TLT_UNIT) {
608  // Attempt to translate the link name (it should be a unit type name).
609  return fc_snprintf(buf, len, "%s", _(ptag->link.name));
610  } else {
611  return fc_snprintf(buf, len, "%s", ptag->link.name);
612  }
613 }
614 
632 struct text_tag *text_tag_new(enum text_tag_type tag_type,
635 {
636  auto *ptag = new text_tag;
637  va_list args;
638  bool ok;
639 
640  va_start(args, stop_offset);
641  ok = text_tag_initv(ptag, tag_type, start_offset, stop_offset, args);
642  va_end(args);
643 
644  if (ok) {
645  return ptag;
646  } else {
647  delete ptag;
648  return nullptr;
649  }
650 }
651 
656 struct text_tag *text_tag_copy(const struct text_tag *ptag)
657 {
658  if (!ptag) {
659  return nullptr;
660  }
661 
662  auto *pnew_tag = new text_tag;
663  *pnew_tag = *ptag;
664 
665  return pnew_tag;
666 }
667 
671 void text_tag_destroy(struct text_tag *ptag) { delete ptag; }
672 
676 enum text_tag_type text_tag_type(const struct text_tag *ptag)
677 {
678  return ptag->type;
679 }
680 
685 {
686  return ptag->start_offset;
687 }
688 
693 {
694  return ptag->stop_offset;
695 }
696 
701 QString text_tag_color_foreground(const struct text_tag *ptag)
702 {
703  if (ptag->type != TTT_COLOR) {
704  qCritical("text_tag_color_foreground(): incompatible tag type.");
705  return QStringLiteral();
706  }
707 
708  return ptag->color.foreground;
709 }
710 
715 QString text_tag_color_background(const struct text_tag *ptag)
716 {
717  if (ptag->type != TTT_COLOR) {
718  qCritical("text_tag_color_background(): incompatible tag type.");
719  return QStringLiteral();
720  }
721 
722  return ptag->color.background;
723 }
724 
729 enum text_link_type text_tag_link_type(const struct text_tag *ptag)
730 {
731  if (ptag->type != TTT_LINK) {
732  qCritical("text_tag_link_type(): incompatible tag type.");
733  return TLT_INVALID;
734  }
735 
736  return ptag->link.type;
737 }
738 
744 int text_tag_link_id(const struct text_tag *ptag)
745 {
746  if (ptag->type != TTT_LINK) {
747  qCritical("text_tag_link_id(): incompatible tag type.");
748  return TTT_INVALID;
749  }
750 
751  return ptag->link.id;
752 }
753 
758 static size_t extract_sequence_text(const char *featured_text, QString &buf,
759  size_t len, enum sequence_type *seq_type,
760  enum text_tag_type *type)
761 {
762  const char *buf_in = featured_text;
763  const char *stop = strchr(buf_in, SEQ_STOP);
764  const char *end = stop;
765  const char *name;
766  size_t type_len;
767  size_t name_len;
768  int i;
769 
770  if (!stop) {
771  return 0; // Not valid.
772  }
773 
774  // Check sequence type.
775  for (buf_in++; QChar::isSpace(*buf_in); buf_in++) {
776  ;
777  }
778 
779  if (*buf_in == SEQ_END) {
780  *seq_type = ST_STOP;
781  buf_in++;
782  } else {
783  for (end--; QChar::isSpace(*end); end--) {
784  ;
785  }
786 
787  if (*end == SEQ_END) {
788  *seq_type = ST_SINGLE;
789 
790  for (end--; QChar::isSpace(*end); end--) {
791  ;
792  }
793  } else {
794  *seq_type = ST_START;
795  }
796  }
797 
798  while (QChar::isSpace(*buf_in)) {
799  buf_in++;
800  }
801 
802  // Check the length of the type name.
803  for (name = buf_in; name < stop; name++) {
804  if (!QChar::isLetter(*name)) {
805  break;
806  }
807  }
808  type_len = name - buf_in;
809 
810  *type = TTT_INVALID;
811  for (i = 0;
812  (name = text_tag_type_name(static_cast<enum text_tag_type>(i)));
813  i++) {
814  name_len = qstrlen(name);
815  if (name_len == type_len
816  && 0 == fc_strncasecmp(name, buf_in, name_len)) {
817  buf_in += name_len;
818  *type = static_cast<enum text_tag_type>(i);
819  break;
820  }
821  }
822  if (*type == TTT_INVALID) {
823  // Try with short names.
824  for (i = 0; (name = text_tag_type_short_name(
825  static_cast<enum text_tag_type>(i)));
826  i++) {
827  name_len = qstrlen(name);
828  if (name_len == type_len
829  && 0 == fc_strncasecmp(name, buf_in, name_len)) {
830  buf_in += name_len;
831  *type = static_cast<enum text_tag_type>(i);
832  break;
833  }
834  }
835  if (*type == TTT_INVALID) {
836  return 0; // Not valid.
837  }
838  }
839 
840  while (QChar::isSpace(*buf_in)) {
841  buf_in++;
842  }
843 
844  if (end - buf_in + 2 > 0) {
845  buf = QString(buf_in).left(MIN(end - buf_in + 2, len));
846  } else {
847  buf = QLatin1String("");
848  }
849  return stop - featured_text + 1;
850 }
851 
861 size_t featured_text_to_plain_text(const char *featured_text,
862  char *plain_text, size_t plain_text_len,
863  struct text_tag_list **tags,
864  bool replace_link_text)
865 {
866  const char *text_in = featured_text;
867  char *text_out = plain_text;
868  size_t text_out_len = plain_text_len;
869 
870  if (tags) {
871  *tags = text_tag_list_new();
872  }
873 
874  while (*text_in != '\0' && text_out_len > 1) {
875  if (SEQ_START == *text_in) {
876  // Escape sequence...
877  QString buf;
878  enum sequence_type seq_type;
879  enum text_tag_type type;
880  size_t len = extract_sequence_text(text_in, buf, text_out_len,
881  &seq_type, &type);
882 
883  if (len > 0) {
884  // Looks a valid sequence.
885  text_in += len;
886  switch (seq_type) {
887  case ST_START:
888  if (tags) {
889  // Create a new tag.
890  text_tag *ptag = new text_tag;
891 
893  text_out - plain_text, buf)) {
894  text_tag_list_append(*tags, ptag);
895  } else {
896  text_tag_destroy(ptag);
897  log_featured_text("Couldn't create a text tag with \"%s\".",
898  qUtf8Printable(buf));
899  }
900  }
901  break;
902  case ST_STOP:
903  if (tags) {
904  // Set the stop offset.
905  struct text_tag *ptag = nullptr;
906 
907  // Look up on reversed order.
908  text_tag_list_rev_iterate(*tags, piter)
909  {
910  if (piter->type == type
911  && piter->stop_offset == FT_OFFSET_UNSET) {
912  ptag = piter;
913  break;
914  }
915  }
917 
918  if (ptag) {
919  ptag->stop_offset = text_out - plain_text;
920  } else {
921  log_featured_text("Extra text tag end for \"%s\".",
923  }
924  }
925  break;
926  case ST_SINGLE: {
927  // In this case, we replace the sequence by some text.
928  struct text_tag tag;
929 
930  if (!text_tag_init_from_sequence(&tag, type, text_out - plain_text,
931  buf)) {
932  log_featured_text("Couldn't create a text tag with \"%s\".",
933  qUtf8Printable(buf));
934  } else {
935  len = text_tag_replace_text(&tag, text_out, text_out_len,
936  replace_link_text);
937  text_out += len;
938  text_out_len -= len;
939  if (tags) {
940  // Set it in the list.
941  auto *ptag = new text_tag;
942 
943  *ptag = tag;
944  ptag->stop_offset = text_out - plain_text;
945  text_tag_list_append(*tags, ptag);
946  }
947  }
948  } break;
949  };
950  } else {
951  *text_out++ = *text_in++;
952  text_out_len--;
953  }
954  } else {
955  *text_out++ = *text_in++;
956  text_out_len--;
957  }
958  }
959  *text_out = '\0';
960 
961  return plain_text_len - text_out_len;
962 }
963 
982 size_t featured_text_apply_tag(const char *text_source, char *featured_text,
983  size_t featured_text_len,
984  enum text_tag_type tag_type,
987 {
988  struct text_tag tag;
989  size_t len, total_len = 0;
990  va_list args;
991 
992  if (start_offset == FT_OFFSET_UNSET || start_offset > qstrlen(text_source)
994  log_featured_text("featured_text_apply_tag(): invalid offsets.");
995  return 0;
996  }
997 
998  va_start(args, stop_offset);
999  if (!text_tag_initv(&tag, tag_type, start_offset, stop_offset, args)) {
1000  va_end(args);
1001  return 0;
1002  }
1003  va_end(args);
1004 
1005  if (start_offset > 0) {
1006  // First part: before the sequence.
1007  len = 0;
1008  while (len < start_offset && *text_source != '\0'
1009  && featured_text_len > 1) {
1010  *featured_text++ = *text_source++;
1011  featured_text_len--;
1012  len++;
1013  }
1014  total_len += len;
1015  }
1016 
1017  // Start sequence.
1018  len = text_tag_start_sequence(&tag, featured_text, featured_text_len);
1019  total_len += len;
1020  featured_text += len;
1021  featured_text_len -= len;
1022 
1023  // Second part: between the sequences.
1024  len = start_offset;
1025  while (len < stop_offset && *text_source != '\0'
1026  && featured_text_len > 1) {
1027  *featured_text++ = *text_source++;
1028  featured_text_len--;
1029  len++;
1030  }
1031  total_len += len;
1032 
1033  // Stop sequence.
1034  len = text_tag_stop_sequence(&tag, featured_text, featured_text_len);
1035  total_len += len;
1036  featured_text += len;
1037  featured_text_len -= len;
1038 
1039  // Third part: after the sequence.
1040  while (*text_source != '\0' && featured_text_len > 1) {
1041  *featured_text++ = *text_source++;
1042  featured_text_len--;
1043  total_len++;
1044  }
1045  *featured_text = '\0';
1046 
1047  return total_len;
1048 }
1049 
1055 const char *city_link(const struct city *pcity)
1056 {
1057  static char buf[MAX_LEN_LINK];
1058 
1059  fc_snprintf(buf, sizeof(buf), "%c%s tgt=\"%s\" id=%d name=\"%s\" %c%c",
1061  text_link_type_name(TLT_CITY), pcity->id, city_name_get(pcity),
1062  SEQ_END, SEQ_STOP);
1063  return buf;
1064 }
1065 
1072 const char *city_tile_link(const struct city *pcity)
1073 {
1074  static char buf[MAX_LEN_LINK];
1076 
1077  fc_snprintf(buf, sizeof(buf), "%c%s tgt=\"%s\" x=%d y=%d%c%s%c%c%s%c",
1079  TILE_XY(city_tile(pcity)), SEQ_STOP, city_name_get(pcity),
1081  return buf;
1082 }
1083 
1089 const char *tile_link(const struct tile *ptile)
1090 {
1091  static char buf[MAX_LEN_LINK];
1092 
1093  fc_snprintf(buf, sizeof(buf), "%c%s tgt=\"%s\" x=%d y=%d %c%c", SEQ_START,
1096  SEQ_STOP);
1097  return buf;
1098 }
1099 
1105 const char *unit_link(const struct unit *punit)
1106 {
1107  static char buf[MAX_LEN_LINK];
1108 
1109  fc_snprintf(buf, sizeof(buf), "%c%s tgt=\"%s\" id=%d name=\"%s\" %c%c",
1111  text_link_type_name(TLT_UNIT), punit->id,
1113  return buf;
1114 }
1115 
1122 const char *unit_tile_link(const struct unit *punit)
1123 {
1124  static char buf[MAX_LEN_LINK];
1126 
1127  fc_snprintf(buf, sizeof(buf), "%c%s tgt=\"%s\" x=%d y=%d%c%s%c%c%s%c",
1129  TILE_XY(unit_tile(punit)), SEQ_STOP,
1131  SEQ_STOP);
1132  return buf;
1133 }
1134 
1142 const char *unit_veteran_level_and_bonus(const unit *punit)
1143 {
1144  static char buf[MAX_LEN_LINK];
1145 
1146  if (!punit) {
1147  buf[0] = '\0'; /* If no unit, return empty string */
1148  return buf;
1149  }
1150 
1151  const veteran_level *vlevel =
1152  utype_veteran_level(unit_type_get(punit), punit->veteran);
1153  snprintf(buf, sizeof(buf), _("v%d %d%%"), punit->veteran,
1154  vlevel->power_fact);
1155  return buf;
1156 }
1157 
1163 const char *unit_veteran_level_string(const struct unit *punit)
1164 {
1165  static char buf[MAX_LEN_LINK];
1166  const struct veteran_level *vlevel;
1167 
1168  if (!punit) {
1169  buf[0] = '\0'; /* If no unit, return empty string */
1170  return buf;
1171  }
1172 
1173  vlevel = utype_veteran_level(unit_type_get(punit), punit->veteran);
1174  fc_snprintf(buf, sizeof(buf), "%s", name_translation_get(&vlevel->name));
1175  return buf;
1176 }
1177 
1183 const char *unit_achieved_rank_string(const struct unit *punit)
1184 {
1185  static char buf[MAX_LEN_LINK];
1186 
1187  fc_snprintf(buf, sizeof(buf),
1188  /* TRANS: " and achieved the rank of <veteran level>";
1189  * preserve leading space */
1190  _(", promoted to %s"), unit_veteran_level_string(punit));
1191  return buf;
1192 }
1193 
1199 const char *unit_tired_attack_string(const struct unit *punit)
1200 {
1201  static char buf[MAX_LEN_LINK];
1202 
1203  if (is_tired_attack(punit->moves_left)) {
1204  fc_snprintf(buf, sizeof(buf),
1205  /* TRANS: tired; note trailing space */
1206  _("tired "));
1207  } else {
1208  buf[0] = '\0';
1209  }
1210  return buf;
1211 }
1212 
1220 const char *unit_firepower_if_not_one(int firepower)
1221 {
1222  static char buf[MAX_LEN_LINK];
1223 
1224  if (firepower == 1) {
1225  buf[0] = '\0';
1226  } else {
1227  fc_snprintf(buf, sizeof(buf),
1228  /* TRANS: FP = Firepower of a unit; note trailing space */
1229  _("FP:%d "), firepower);
1230  }
1231  return buf;
1232 }
struct tile * city_tile(const struct city *pcity)
Return the tile location of the city.
Definition: city.cpp:1095
const char * city_name_get(const struct city *pcity)
Return the name of the city.
Definition: city.cpp:1077
bool is_tired_attack(int moves_left)
Returns if the attack is going to be a tired attack.
Definition: combat.cpp:872
char * tag_name
Definition: events.cpp:64
#define _(String)
Definition: fcintl.h:50
struct text_tag * text_tag_copy(const struct text_tag *ptag)
This function returns a new pointer to a text_tag which is similar to the 'ptag' argument.
static const char * text_tag_type_name(enum text_tag_type type)
Return the long name of the text tag type.
const struct ft_color ftc_chat_private
size_t featured_text_apply_tag(const char *text_source, char *featured_text, size_t featured_text_len, enum text_tag_type tag_type, ft_offset_t start_offset, ft_offset_t stop_offset,...)
Apply a tag to a text.
const char * unit_firepower_if_not_one(int firepower)
Get string of unit's firepower text, i.e.
const char * unit_veteran_level_and_bonus(const unit *punit)
Get a text of a unit's numerical veteran level and the bonus percentage it confers.
static const char * text_link_type_name(enum text_link_type type)
Return the name of the text tag link target type.
static bool find_option(const char *buf_in, const char *option, char *buf_out, size_t write_len)
Find inside a sequence the string associated to a particular option name.
const char * city_tile_link(const struct city *pcity)
Get a text link to a city tile (make a clickable link to a tile with the city name as text).
const struct ft_color ftc_vote_abstain
enum text_link_type text_tag_link_type(const struct text_tag *ptag)
Return the link target type suggested by this text tag.
const struct ft_color ftc_log
const struct ft_color ftc_player_lost
size_t featured_text_to_plain_text(const char *featured_text, char *plain_text, size_t plain_text_len, struct text_tag_list **tags, bool replace_link_text)
Separate the text from the text features.
const struct ft_color ftc_luaconsole_verbose
#define text_tag_list_rev_iterate(tags, ptag)
#define SEQ_STOP
const char * tile_link(const struct tile *ptile)
Get a text link to a tile.
struct text_tag * text_tag_new(enum text_tag_type tag_type, ft_offset_t start_offset, ft_offset_t stop_offset,...)
Returns a new text_tag or nullptr on error.
#define SEQ_START
static bool text_tag_init_from_sequence(struct text_tag *ptag, enum text_tag_type type, ft_offset_t start_offset, QString &qsequence)
Initialize a text_tag structure from a string sequence.
const struct ft_color ftc_command
QString text_tag_color_background(const struct text_tag *ptag)
Return the background color suggested by this text tag.
const char * unit_tired_attack_string(const struct unit *punit)
Get string of unit's attack would be a tired attack or not.
QString text_tag_color_foreground(const struct text_tag *ptag)
Return the foreground color suggested by this text tag.
const char * unit_tile_link(const struct unit *punit)
Get a text link to a unit tile (make a clickable link to a tile with the unit type name as text).
ft_offset_t text_tag_stop_offset(const struct text_tag *ptag)
Return the stop offset (in bytes) of this text tag.
static bool text_tag_initv(struct text_tag *ptag, enum text_tag_type type, ft_offset_t start_offset, ft_offset_t stop_offset, va_list args)
Initialize a text_tag structure from a va_list.
const char * unit_veteran_level_string(const struct unit *punit)
Get a text of a unit's vet level.
static size_t extract_sequence_text(const char *featured_text, QString &buf, size_t len, enum sequence_type *seq_type, enum text_tag_type *type)
Extract a sequence from a string.
const struct ft_color ftc_server
const struct ft_color ftc_vote_failed
void text_tag_destroy(struct text_tag *ptag)
Free a text_tag structure.
const struct ft_color ftc_vote_yes
static size_t text_tag_start_sequence(const struct text_tag *ptag, char *buf, size_t len)
Print in a string the start sequence of the tag.
const struct ft_color ftc_luaconsole_error
const struct ft_color ftc_warning
enum text_tag_type text_tag_type(const struct text_tag *ptag)
Return the type of this text tag.
#define text_tag_list_rev_iterate_end
static const char * text_tag_type_short_name(enum text_tag_type type)
Return the name abbreviation of the text tag type.
const struct ft_color ftc_client
const struct ft_color ftc_luaconsole_normal
const struct ft_color ftc_editor
const char * unit_achieved_rank_string(const struct unit *punit)
Get string of when unit gets upgraded to new veteran level.
static size_t text_tag_replace_text(const struct text_tag *ptag, char *buf, size_t len, bool replace_link_text)
When the sequence looks like [sequence/] then we insert a string instead.
const struct ft_color ftc_any
const struct ft_color ftc_vote_no
const char * city_link(const struct city *pcity)
Get a text link to a city.
const struct ft_color ftc_vote_passed
#define MAX_LEN_STR
#define log_featured_text
static size_t text_tag_stop_sequence(const struct text_tag *ptag, char *buf, size_t len)
Print in a string the stop sequence of the tag.
const struct ft_color ftc_luaconsole_warn
int text_tag_link_id(const struct text_tag *ptag)
Return the link target id suggested by this text tag (city id, tile index or unit id).
const struct ft_color ftc_chat_luaconsole
ft_offset_t text_tag_start_offset(const struct text_tag *ptag)
Return the start offset (in bytes) of this text tag.
const struct ft_color ftc_vote_team
const char * unit_link(const struct unit *punit)
Get a text link to an unit.
const struct ft_color ftc_game_start
const struct ft_color ftc_server_prompt
struct ft_color ftc_changed
const struct ft_color ftc_vote_public
sequence_type
@ ST_STOP
@ ST_SINGLE
@ ST_START
const struct ft_color ftc_luaconsole_input
#define SEQ_END
const struct ft_color ftc_chat_public
const struct ft_color ftc_chat_ally
#define FT_OFFSET_UNSET
Definition: featured_text.h:95
#define MAX_LEN_LINK
#define text_tag_list_new()
int ft_offset_t
Definition: featured_text.h:93
text_link_type
@ TLT_INVALID
@ TLT_TILE
@ TLT_UNIT
@ TLT_CITY
text_tag_type
@ TTT_LINK
@ TTT_BOLD
@ TTT_ITALIC
@ TTT_INVALID
@ TTT_STRIKE
@ TTT_COLOR
@ TTT_UNDERLINE
#define FT_COLOR(fg, bg)
struct unit * game_unit_by_number(int id)
Find unit out of all units in game: now uses fast idex method, instead of looking through all units o...
Definition: game.cpp:112
struct world wld
Definition: game.cpp:48
struct city * game_city_by_number(int id)
Often used function to get a city pointer from a city ID.
Definition: game.cpp:103
const char * name
Definition: inputfile.cpp:118
#define fc_assert(condition)
Definition: log.h:89
#define fc_assert_ret_val(condition, val)
Definition: log.h:114
struct tile * map_pos_to_tile(const struct civ_map *nmap, int map_x, int map_y)
Return the tile for the given cartesian (map) position.
Definition: map.cpp:391
struct tile * index_to_tile(const struct civ_map *imap, int mindex)
Return the tile for the given index position.
Definition: map.cpp:429
static const char * name_translation_get(const struct name_translation *ptrans)
int len
Definition: packhand.cpp:127
bool str_to_int(const char *str, int *pint)
Convert 'str' to it's int reprentation if possible.
Definition: shared.cpp:384
#define MIN(x, y)
Definition: shared.h:49
Definition: city.h:291
int id
Definition: city.h:296
const char * background
const char * foreground
The base class for options.
Definition: options.cpp:209
struct text_tag::@23::@26 link
char background[MAX_LEN_STR]
char name[MAX_LEN_STR]
struct text_tag::@23::@25 color
ft_offset_t stop_offset
ft_offset_t start_offset
enum text_tag_type type
char foreground[MAX_LEN_STR]
Definition: tile.h:42
Definition: unit.h:134
int moves_left
Definition: unit.h:147
int id
Definition: unit.h:141
int veteran
Definition: unit.h:149
int power_fact
Definition: unittype.h:453
struct name_translation name
Definition: unittype.h:452
struct civ_map map
Definition: world_object.h:21
int fc_snprintf(char *str, size_t n, const char *format,...)
See also fc_utf8_snprintf_trunc(), fc_utf8_snprintf_rep().
Definition: support.cpp:537
size_t fc_strlcpy(char *dest, const char *src, size_t n)
fc_strlcpy() provides utf-8 version of (non-standard) function strlcpy() It is intended as more user-...
Definition: support.cpp:412
int fc_strncasecmp(const char *str0, const char *str1, size_t n)
Compare strings like strncmp(), but ignoring case.
Definition: support.cpp:100
#define sz_strlcpy(dest, src)
Definition: support.h:140
#define tile_index(_pt_)
Definition: tile.h:70
#define TILE_XY(ptile)
Definition: tile.h:36
#define unit_tile(_pu)
Definition: unit.h:371
const struct unit_type * unit_type_get(const struct unit *punit)
Return the unit type for this unit.
Definition: unittype.cpp:114
const char * unit_name_translation(const struct unit *punit)
Return the (translated) name of the unit.
Definition: unittype.cpp:1265
const struct veteran_level * utype_veteran_level(const struct unit_type *punittype, int level)
Return veteran level properties of given unit in given veterancy level.
Definition: unittype.cpp:2224