Stop sharing requirement_unit_state_ereq().
[freeciv.git] / common / featured_text.c
blobd51f1cef08a799452a5307bc693c3c03c4c88cd8
1 /***********************************************************************
2 Freeciv - Copyright (C) 1996 - A Kjeldberg, L Gregersen, P Unold
3 This program is free software; you can redistribute it and/or modify
4 it under the terms of the GNU General Public License as published by
5 the Free Software Foundation; either version 2, or (at your option)
6 any later version.
8 This program is distributed in the hope that it will be useful,
9 but WITHOUT ANY WARRANTY; without even the implied warranty of
10 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 GNU General Public License for more details.
12 ***********************************************************************/
13 #ifdef HAVE_CONFIG_H
14 #include <fc_config.h>
15 #endif
17 #include <stdarg.h>
18 #include <string.h>
20 /* utility */
21 #include "fcintl.h"
22 #include "log.h"
23 #include "mem.h"
24 #include "shared.h"
25 #include "support.h"
27 /* common */
28 #include "city.h"
29 #include "game.h"
30 #include "map.h"
31 #include "tile.h"
32 #include "unit.h"
34 #include "featured_text.h"
36 #define SEQ_START '['
37 #define SEQ_STOP ']'
38 #define SEQ_END '/'
40 #define MAX_LEN_STR 32
41 #define log_featured_text log_verbose
43 #define text_tag_list_rev_iterate(tags, ptag) \
44 TYPED_LIST_ITERATE_REV(struct text_tag, tags, ptag)
45 #define text_tag_list_rev_iterate_end LIST_ITERATE_REV_END
47 /* The text_tag structure. See documentation in featured_text.h. */
48 struct text_tag {
49 enum text_tag_type type; /* The type of the tag. */
50 ft_offset_t start_offset; /* The start offset (in bytes). */
51 ft_offset_t stop_offset; /* The stop offset (in bytes). */
52 union {
53 struct { /* TTT_COLOR only. */
54 char foreground[MAX_LEN_STR]; /* foreground color name. */
55 char background[MAX_LEN_STR]; /* background color name. */
56 } color;
57 struct { /* TTT_LINK only. */
58 enum text_link_type type; /* The target type of the link. */
59 int id; /* The id of linked object. */
60 char name[MAX_LEN_STR]; /* A string to indentify the link. */
61 } link;
65 enum sequence_type {
66 ST_START, /* e.g. [sequence]. */
67 ST_STOP, /* e.g. [/sequence]. */
68 ST_SINGLE /* e.g. [sequence/]. */
71 /* Predefined colors. */
72 const struct ft_color ftc_any = FT_COLOR(NULL, NULL);
74 const struct ft_color ftc_warning = FT_COLOR("#FF0000", NULL);
75 const struct ft_color ftc_log = FT_COLOR("#7F7F7F", NULL);
76 const struct ft_color ftc_server = FT_COLOR("#8B0000", NULL);
77 const struct ft_color ftc_client = FT_COLOR("#EF7F00", NULL);
78 const struct ft_color ftc_editor = FT_COLOR("#0000FF", NULL);
79 const struct ft_color ftc_command = FT_COLOR("#006400", NULL);
80 struct ft_color ftc_changed = FT_COLOR("#FF0000", NULL);
81 const struct ft_color ftc_server_prompt = FT_COLOR("#FF0000", "#BEBEBE");
82 const struct ft_color ftc_player_lost = FT_COLOR("#FFFFFF", "#000000");
83 const struct ft_color ftc_game_start = FT_COLOR("#00FF00", "#115511");
85 const struct ft_color ftc_chat_public = FT_COLOR("#00008B", NULL);
86 const struct ft_color ftc_chat_ally = FT_COLOR("#551166", NULL);
87 const struct ft_color ftc_chat_private = FT_COLOR("#A020F0", NULL);
88 const struct ft_color ftc_chat_luaconsole = FT_COLOR("#006400", NULL);
90 const struct ft_color ftc_vote_public = FT_COLOR("#FFFFFF", "#AA0000");
91 const struct ft_color ftc_vote_team = FT_COLOR("#FFFFFF", "#5555CC");
92 const struct ft_color ftc_vote_passed = FT_COLOR("#006400", "#AAFFAA");
93 const struct ft_color ftc_vote_failed = FT_COLOR("#8B0000", "#FFAAAA");
94 const struct ft_color ftc_vote_yes = FT_COLOR("#000000", "#C8FFD5");
95 const struct ft_color ftc_vote_no = FT_COLOR("#000000", "#FFD2D2");
96 const struct ft_color ftc_vote_abstain = FT_COLOR("#000000", "#E8E8E8");
98 const struct ft_color ftc_luaconsole_input = FT_COLOR("#2B008B", NULL);
99 const struct ft_color ftc_luaconsole_error = FT_COLOR("#FF0000", NULL);
100 const struct ft_color ftc_luaconsole_warn = FT_COLOR("#CF2020", NULL);
101 const struct ft_color ftc_luaconsole_normal = FT_COLOR("#006400", NULL);
102 const struct ft_color ftc_luaconsole_verbose = FT_COLOR("#B8B8B8", NULL);
103 const struct ft_color ftc_luaconsole_debug = FT_COLOR("#B87676", NULL);
105 /**************************************************************************
106 Return the long name of the text tag type.
107 See also text_tag_type_short_name().
108 **************************************************************************/
109 static const char *text_tag_type_name(enum text_tag_type type)
111 switch (type) {
112 case TTT_BOLD:
113 return "bold";
114 case TTT_ITALIC:
115 return "italic";
116 case TTT_STRIKE:
117 return "strike";
118 case TTT_UNDERLINE:
119 return "underline";
120 case TTT_COLOR:
121 return "color";
122 case TTT_LINK:
123 return "link";
125 /* Don't handle the default case to be warned if a new value was added. */
126 return NULL;
129 /**************************************************************************
130 Return the name abbreviation of the text tag type.
131 See also text_tag_type_name().
132 **************************************************************************/
133 static const char *text_tag_type_short_name(enum text_tag_type type)
135 switch (type) {
136 case TTT_BOLD:
137 return "b";
138 case TTT_ITALIC:
139 return "i";
140 case TTT_STRIKE:
141 return "s";
142 case TTT_UNDERLINE:
143 return "u";
144 case TTT_COLOR:
145 return "c";
146 case TTT_LINK:
147 return "l";
149 /* Don't handle the default case to be warned if a new value was added. */
150 return NULL;
153 /**************************************************************************
154 Return the name of the text tag link target type.
155 **************************************************************************/
156 static const char *text_link_type_name(enum text_link_type type)
158 switch (type) {
159 case TLT_CITY:
160 return "city";
161 case TLT_TILE:
162 return "tile";
163 case TLT_UNIT:
164 return "unit";
166 /* Don't handle the default case to be warned if a new value was added. */
167 return NULL;
170 /**************************************************************************
171 Find inside a sequence the string associated to a particular option name.
172 Returns TRUE on success.
173 **************************************************************************/
174 static bool find_option(const char *buf_in, const char *option,
175 char *buf_out, size_t write_len)
177 size_t option_len = strlen(option);
179 while (*buf_in != '\0') {
180 while (fc_isspace(*buf_in) && *buf_in != '\0') {
181 buf_in++;
184 if (0 == strncasecmp(buf_in, option, option_len)) {
185 /* This is this one. */
186 buf_in += option_len;
188 while ((fc_isspace(*buf_in) || *buf_in == '=') && *buf_in != '\0') {
189 buf_in++;
191 if (*buf_in == '"') {
192 /* Quote case. */
193 const char *end = strchr(++buf_in, '"');
195 if (!end) {
196 return FALSE;
198 if (end - buf_in + 1 > 0) {
199 fc_strlcpy(buf_out, buf_in, MIN(end - buf_in + 1, write_len));
200 } else {
201 *buf_out = '\0';
203 return TRUE;
204 } else {
205 while (fc_isalnum(*buf_in) && write_len > 1) {
206 *buf_out++ = *buf_in++;
207 write_len--;
209 *buf_out = '\0';
210 return TRUE;
213 buf_in++;
216 return FALSE;
219 /**************************************************************************
220 Initialize a text_tag structure from a string sequence.
221 Returns TRUE on success.
222 **************************************************************************/
223 static bool text_tag_init_from_sequence(struct text_tag *ptag,
224 enum text_tag_type type,
225 ft_offset_t start_offset,
226 const char *sequence)
228 ptag->type = type;
229 ptag->start_offset = start_offset;
230 ptag->stop_offset = FT_OFFSET_UNSET;
232 switch (type) {
233 case TTT_BOLD:
234 case TTT_ITALIC:
235 case TTT_STRIKE:
236 case TTT_UNDERLINE:
237 return TRUE;
238 case TTT_COLOR:
240 if (!find_option(sequence, "foreground", ptag->color.foreground,
241 sizeof(ptag->color.foreground))
242 && !find_option(sequence, "fg", ptag->color.foreground,
243 sizeof(ptag->color.foreground))) {
244 ptag->color.foreground[0] = '\0';
246 if (!find_option(sequence, "background", ptag->color.background,
247 sizeof(ptag->color.background))
248 && !find_option(sequence, "bg", ptag->color.background,
249 sizeof(ptag->color.background))) {
250 ptag->color.background[0] = '\0';
253 return TRUE;
254 case TTT_LINK:
256 char buf[64];
257 const char *name;
258 int i;
260 if (!find_option(sequence, "target", buf, sizeof(buf))
261 && !find_option(sequence, "tgt", buf, sizeof(buf))) {
262 log_featured_text("text_tag_init_from_sequence(): "
263 "target link type not set.");
264 return FALSE;
267 ptag->link.type = -1;
268 for (i = 0; (name = text_link_type_name(i)); i++) {
269 if (0 == fc_strncasecmp(buf, name, strlen(name))) {
270 ptag->link.type = i;
271 break;
274 if (ptag->link.type == -1) {
275 log_featured_text("text_tag_init_from_sequence(): "
276 "target link type not supported (\"%s\").", buf);
277 return FALSE;
280 switch (ptag->link.type) {
281 case TLT_CITY:
283 if (!find_option(sequence, "id", buf, sizeof(buf))) {
284 log_featured_text("text_tag_init_from_sequence(): "
285 "city link without id.");
286 return FALSE;
288 if (!str_to_int(buf, &ptag->link.id)) {
289 log_featured_text("text_tag_init_from_sequence(): "
290 "city link without valid id (\"%s\").", buf);
291 return FALSE;
294 if (!find_option(sequence, "name", ptag->link.name,
295 sizeof(ptag->link.name))) {
296 /* Set something as name. */
297 fc_snprintf(ptag->link.name, sizeof(ptag->link.name),
298 "CITY_ID%d", ptag->link.id);
301 return TRUE;
302 case TLT_TILE:
304 struct tile *ptile;
305 int x, y;
307 if (!find_option(sequence, "x", buf, sizeof(buf))) {
308 log_featured_text("text_tag_init_from_sequence(): "
309 "tile link without x coordinate.");
310 return FALSE;
312 if (!str_to_int(buf, &x)) {
313 log_featured_text("text_tag_init_from_sequence(): "
314 "tile link without valid x coordinate "
315 "(\"%s\").", buf);
316 return FALSE;
319 if (!find_option(sequence, "y", buf, sizeof(buf))) {
320 log_featured_text("text_tag_init_from_sequence(): "
321 "tile link without y coordinate.");
322 return FALSE;
324 if (!str_to_int(buf, &y)) {
325 log_featured_text("text_tag_init_from_sequence(): "
326 "tile link without valid y coordinate "
327 "(\"%s\").", buf);
328 return FALSE;
331 ptile = map_pos_to_tile(x, y);
332 if (!ptile) {
333 log_featured_text("text_tag_init_from_sequence(): "
334 "(%d, %d) are not valid coordinates "
335 "in this game.", x, y);
336 return FALSE;
338 ptag->link.id = tile_index(ptile);
339 fc_snprintf(ptag->link.name, sizeof(ptag->link.name),
340 "(%d, %d)", TILE_XY(ptile));
342 return TRUE;
343 case TLT_UNIT:
345 if (!find_option(sequence, "id", buf, sizeof(buf))) {
346 log_featured_text("text_tag_init_from_sequence(): "
347 "unit link without id.");
348 return FALSE;
350 if (!str_to_int(buf, &ptag->link.id)) {
351 log_featured_text("text_tag_init_from_sequence(): "
352 "unit link without valid id (\"%s\").", buf);
353 return FALSE;
356 if (!find_option(sequence, "name", ptag->link.name,
357 sizeof(ptag->link.name))) {
358 /* Set something as name. */
359 fc_snprintf(ptag->link.name, sizeof(ptag->link.name),
360 "UNIT_ID%d", ptag->link.id);
363 return TRUE;
367 return FALSE;
370 /**************************************************************************
371 Initialize a text_tag structure from a va_list.
373 What's should be in the va_list:
374 - If the text tag type is TTT_BOLD, TTT_ITALIC, TTT_STRIKE or
375 TTT_UNDERLINE, there shouldn't be any extra argument.
376 - If the text tag type is TTT_COLOR, then there should be 1 argument of
377 type 'struct ft_color'.
378 - If the text tag type is TTT_LINK, then there should be 2 extra arguments.
379 The first is type 'enum text_link_type' and will determine the type of the
380 following argument:
381 - If the link type is TLT_CITY, last argument is typed 'struct city *'.
382 - If the link type is TLT_TILE, last argument is typed 'struct tile *'.
383 - If the link type is TLT_UNIT, last argument is typed 'struct unit *'.
385 Returns TRUE on success.
386 **************************************************************************/
387 static bool text_tag_initv(struct text_tag *ptag, enum text_tag_type type,
388 ft_offset_t start_offset, ft_offset_t stop_offset,
389 va_list args)
391 ptag->type = type;
392 ptag->start_offset = start_offset;
393 ptag->stop_offset = stop_offset;
395 switch (type) {
396 case TTT_BOLD:
397 case TTT_ITALIC:
398 case TTT_STRIKE:
399 case TTT_UNDERLINE:
400 return TRUE;
401 case TTT_COLOR:
403 const struct ft_color color = va_arg(args, struct ft_color);
405 if ((NULL == color.foreground || '\0' == color.foreground[0])
406 && (NULL == color.background || '\0' == color.background[0])) {
407 return FALSE; /* No color at all. */
410 if (NULL != color.foreground && '\0' != color.foreground[0]) {
411 sz_strlcpy(ptag->color.foreground, color.foreground);
412 } else {
413 ptag->color.foreground[0] = '\0';
416 if (NULL != color.background && '\0' != color.background[0]) {
417 sz_strlcpy(ptag->color.background, color.background);
418 } else {
419 ptag->color.background[0] = '\0';
422 return TRUE;
423 case TTT_LINK:
425 ptag->link.type = va_arg(args, enum text_link_type);
426 switch (ptag->link.type) {
427 case TLT_CITY:
429 struct city *pcity = va_arg(args, struct city *);
431 if (!pcity) {
432 return FALSE;
434 ptag->link.id = pcity->id;
435 sz_strlcpy(ptag->link.name, city_name_get(pcity));
437 return TRUE;
438 case TLT_TILE:
440 struct tile *ptile = va_arg(args, struct tile *);
442 if (!ptile) {
443 return FALSE;
445 ptag->link.id = tile_index(ptile);
446 fc_snprintf(ptag->link.name, sizeof(ptag->link.name),
447 "(%d, %d)", TILE_XY(ptile));
449 return TRUE;
450 case TLT_UNIT:
452 struct unit *punit = va_arg(args, struct unit *);
454 if (!punit) {
455 return FALSE;
457 ptag->link.id = punit->id;
458 sz_strlcpy(ptag->link.name, unit_name_translation(punit));
460 return TRUE;
464 return FALSE;
467 /**************************************************************************
468 Print in a string the start sequence of the tag.
469 **************************************************************************/
470 static size_t text_tag_start_sequence(const struct text_tag *ptag,
471 char *buf, size_t len)
473 switch (ptag->type) {
474 case TTT_BOLD:
475 case TTT_ITALIC:
476 case TTT_STRIKE:
477 case TTT_UNDERLINE:
478 return fc_snprintf(buf, len, "%c%s%c", SEQ_START,
479 text_tag_type_short_name(ptag->type), SEQ_STOP);
480 case TTT_COLOR:
482 size_t ret = fc_snprintf(buf, len, "%c%s", SEQ_START,
483 text_tag_type_short_name(ptag->type));
485 if (ptag->color.foreground[0] != '\0') {
486 ret += fc_snprintf(buf + ret, len - ret, " fg=\"%s\"",
487 ptag->color.foreground);
489 if (ptag->color.background[0] != '\0') {
490 ret += fc_snprintf(buf + ret, len - ret, " bg=\"%s\"",
491 ptag->color.background);
493 return ret + fc_snprintf(buf + ret, len - ret, "%c", SEQ_STOP);
495 case TTT_LINK:
497 size_t ret = fc_snprintf(buf, len, "%c%s tgt=\"%s\"", SEQ_START,
498 text_tag_type_short_name(ptag->type),
499 text_link_type_name(ptag->link.type));
501 switch (ptag->link.type) {
502 case TLT_CITY:
504 struct city *pcity = game_city_by_number(ptag->link.id);
506 if (pcity) {
507 ret += fc_snprintf(buf + ret, len - ret,
508 " id=%d name=\"%s\"",
509 pcity->id, city_name_get(pcity));
510 } else {
511 ret += fc_snprintf(buf + ret, len - ret,
512 " id=%d", ptag->link.id);
515 break;
516 case TLT_TILE:
518 struct tile *ptile = index_to_tile(&(wld.map), ptag->link.id);
520 if (ptile) {
521 ret += fc_snprintf(buf + ret, len - ret,
522 " x=%d y=%d", TILE_XY(ptile));
523 } else {
524 ret += fc_snprintf(buf + ret, len - ret,
525 " id=%d", ptag->link.id);
528 break;
529 case TLT_UNIT:
531 struct unit *punit = game_unit_by_number(ptag->link.id);
533 if (punit) {
534 ret += fc_snprintf(buf + ret, len - ret,
535 " id=%d name=\"%s\"",
536 punit->id, unit_name_translation(punit));
537 } else {
538 ret += fc_snprintf(buf + ret, len - ret,
539 " id=%d", ptag->link.id);
542 break;
545 if (ptag->stop_offset == ptag->start_offset) {
546 /* This is a single sequence like [link ... /]. */
547 ret += fc_snprintf(buf + ret, len - ret, "%c", SEQ_END);
550 return ret + fc_snprintf(buf + ret, len - ret, "%c", SEQ_STOP);
553 return 0;
556 /**************************************************************************
557 Print in a string the stop sequence of the tag.
558 **************************************************************************/
559 static size_t text_tag_stop_sequence(const struct text_tag *ptag,
560 char *buf, size_t len)
562 if (ptag->type == TTT_LINK && ptag->stop_offset == ptag->start_offset) {
563 /* Should be already finished. */
564 return 0;
567 return fc_snprintf(buf, len, "%c%c%s%c", SEQ_START, SEQ_END,
568 text_tag_type_short_name(ptag->type), SEQ_STOP);
571 /**************************************************************************
572 When the sequence looks like [sequence/] then we insert a string instead.
573 **************************************************************************/
574 static size_t text_tag_replace_text(const struct text_tag *ptag,
575 char *buf, size_t len,
576 bool replace_link_text)
578 if (ptag->type != TTT_LINK) {
579 return 0;
582 if (replace_link_text) {
583 /* The client might check if this should be updated or translated. */
584 switch (ptag->link.type) {
585 case TLT_CITY:
587 struct city *pcity = game_city_by_number(ptag->link.id);
589 /* Note that if city_tile(pcity) is NULL, then it is probably an
590 * invisible city (see client/packhand.c). Then, we don't
591 * use the current city name which is usually not complete,
592 * a dumb string using the city id. */
593 if (NULL != pcity && NULL != city_tile(pcity)) {
594 return fc_snprintf(buf, len, "%s", city_name_get(pcity));
597 break;
598 case TLT_TILE:
599 break;
600 case TLT_UNIT:
602 struct unit *punit = game_unit_by_number(ptag->link.id);
604 if (punit) {
605 return fc_snprintf(buf, len, "%s", unit_name_translation(punit));
608 break;
612 if (ptag->link.type == TLT_UNIT) {
613 /* Attempt to translate the link name (it should be a unit type name). */
614 return fc_snprintf(buf, len, "%s", _(ptag->link.name));
615 } else {
616 return fc_snprintf(buf, len, "%s", ptag->link.name);
620 /**************************************************************************
621 Returns a new text_tag or NULL on error.
623 Prototype:
624 - If tag_type is TTT_BOLD, TTT_ITALIC, TTT_STRIKE or TTT_UNDERLINE, there
625 shouldn't be any extra argument.
626 - If tag_type is TTT_COLOR:
627 struct text_tag *text_tag_new(..., const struct ft_color color);
628 - If tag_type is TTT_LINK and you want a city link:
629 struct text_tag *text_tag_new(..., TLT_CITY, struct city *pcity);
630 - If tag_type is TTT_LINK and you want a tile link:
631 struct text_tag *text_tag_new(..., TLT_TILE, struct tile *ptile);
632 - If tag_type is TTT_LINK and you want an unit link:
633 struct text_tag *text_tag_new(..., TLT_UNIT, struct unit *punit);
635 See also comment for text_tag_initv().
636 **************************************************************************/
637 struct text_tag *text_tag_new(enum text_tag_type tag_type,
638 ft_offset_t start_offset,
639 ft_offset_t stop_offset,
640 ...)
642 struct text_tag *ptag = fc_malloc(sizeof(struct text_tag));
643 va_list args;
644 bool ok;
646 va_start(args, stop_offset);
647 ok = text_tag_initv(ptag, tag_type, start_offset, stop_offset, args);
648 va_end(args);
650 if (ok) {
651 return ptag;
652 } else {
653 free(ptag);
654 return NULL;
658 /**************************************************************************
659 This function returns a new pointer to a text_tag which is similar
660 to the 'ptag' argument.
661 **************************************************************************/
662 struct text_tag *text_tag_copy(const struct text_tag *ptag)
664 struct text_tag *pnew_tag;
666 if (!ptag) {
667 return NULL;
670 pnew_tag = fc_malloc(sizeof(struct text_tag));
671 *pnew_tag = *ptag;
673 return pnew_tag;
676 /**************************************************************************
677 Free a text_tag structure.
678 **************************************************************************/
679 void text_tag_destroy(struct text_tag *ptag)
681 free(ptag);
684 /**************************************************************************
685 Return the type of this text tag.
686 **************************************************************************/
687 enum text_tag_type text_tag_type(const struct text_tag *ptag)
689 return ptag->type;
692 /**************************************************************************
693 Return the start offset (in bytes) of this text tag.
694 **************************************************************************/
695 ft_offset_t text_tag_start_offset(const struct text_tag *ptag)
697 return ptag->start_offset;
700 /**************************************************************************
701 Return the stop offset (in bytes) of this text tag.
702 **************************************************************************/
703 ft_offset_t text_tag_stop_offset(const struct text_tag *ptag)
705 return ptag->stop_offset;
708 /**************************************************************************
709 Return the foreground color suggested by this text tag. This requires
710 the tag type to be TTT_COLOR. Returns NULL on error, "" if unset.
711 **************************************************************************/
712 const char *text_tag_color_foreground(const struct text_tag *ptag)
714 if (ptag->type != TTT_COLOR) {
715 log_error("text_tag_color_foreground(): incompatible tag type.");
716 return NULL;
719 return ptag->color.foreground;
722 /**************************************************************************
723 Return the background color suggested by this text tag. This requires
724 the tag type to be TTT_COLOR. Returns NULL on error, "" if unset.
725 **************************************************************************/
726 const char *text_tag_color_background(const struct text_tag *ptag)
728 if (ptag->type != TTT_COLOR) {
729 log_error("text_tag_color_background(): incompatible tag type.");
730 return NULL;
733 return ptag->color.background;
736 /**************************************************************************
737 Return the link target type suggested by this text tag. This requires
738 the tag type to be TTT_LINK. Returns -1 on error.
739 **************************************************************************/
740 enum text_link_type text_tag_link_type(const struct text_tag *ptag)
742 if (ptag->type != TTT_LINK) {
743 log_error("text_tag_link_type(): incompatible tag type.");
744 return -1;
747 return ptag->link.type;
750 /**************************************************************************
751 Return the link target id suggested by this text tag (city id,
752 tile index or unit id). This requires the tag type to be TTT_LINK.
753 Returns -1 on error.
754 **************************************************************************/
755 int text_tag_link_id(const struct text_tag *ptag)
757 if (ptag->type != TTT_LINK) {
758 log_error("text_tag_link_id(): incompatible tag type.");
759 return -1;
762 return ptag->link.id;
765 /**************************************************************************
766 Extract a sequence from a string. Also, determine the type and the text
767 tag type of the sequence. Return 0 on error.
768 **************************************************************************/
769 static size_t extract_sequence_text(const char *featured_text,
770 char *buf, size_t len,
771 enum sequence_type *seq_type,
772 enum text_tag_type *type)
774 const char *buf_in = featured_text;
775 const char *stop = strchr(buf_in, SEQ_STOP);
776 const char *end = stop;
777 const char *name;
778 size_t type_len;
779 size_t name_len;
780 int i;
782 if (!stop) {
783 return 0; /* Not valid. */
786 /* Check sequence type. */
787 for (buf_in++; fc_isspace(*buf_in); buf_in++);
789 if (*buf_in == SEQ_END) {
790 *seq_type = ST_STOP;
791 buf_in++;
792 } else {
793 for (end--; fc_isspace(*end); end--);
795 if (*end == SEQ_END) {
796 *seq_type = ST_SINGLE;
798 for (end--; fc_isspace(*end); end--);
799 } else {
800 *seq_type = ST_START;
804 while (fc_isspace(*buf_in)) {
805 buf_in++;
808 /* Check the length of the type name. */
809 for (name = buf_in; name < stop; name++) {
810 if (!fc_isalpha(*name)) {
811 break;
814 type_len = name - buf_in;
816 *type = -1;
817 for (i = 0; (name = text_tag_type_name(i)); i++) {
818 name_len = strlen(name);
819 if (name_len == type_len && 0 == fc_strncasecmp(name, buf_in, name_len)) {
820 buf_in += name_len;
821 *type = i;
822 break;
825 if (*type == -1) {
826 /* Try with short names. */
827 for (i = 0; (name = text_tag_type_short_name(i)); i++) {
828 name_len = strlen(name);
829 if (name_len == type_len
830 && 0 == fc_strncasecmp(name, buf_in, name_len)) {
831 buf_in += name_len;
832 *type = i;
833 break;
836 if (*type == -1) {
837 return 0; /* Not valid. */
841 while (fc_isspace(*buf_in)) {
842 buf_in++;
845 if (end - buf_in + 2 > 0) {
846 fc_strlcpy(buf, buf_in, MIN(end - buf_in + 2, len));
847 } else {
848 buf[0] = '\0';
850 return stop - featured_text + 1;
854 /**************************************************************************
855 Separate the text from the text features. 'tags' can be NULL.
857 When 'replace_link_text' is set, the text used for the signal sequence
858 links will be overwritten. It is used on client side to have updated
859 links in chatline, to communicate when users don't know share the city
860 names, and avoid users making voluntary confusing names when editing
861 links in chatline.
862 **************************************************************************/
863 size_t featured_text_to_plain_text(const char *featured_text,
864 char *plain_text, size_t plain_text_len,
865 struct text_tag_list **tags,
866 bool replace_link_text)
868 const char *text_in = featured_text;
869 char *text_out = plain_text;
870 size_t text_out_len = plain_text_len;
872 if (tags) {
873 *tags = text_tag_list_new();
876 while (*text_in != '\0' && text_out_len > 1) {
877 if (SEQ_START == *text_in) {
878 /* Escape sequence... */
879 char buf[text_out_len];
880 enum sequence_type seq_type;
881 enum text_tag_type type;
882 size_t len = extract_sequence_text(text_in, buf, text_out_len,
883 &seq_type, &type);
885 if (len > 0) {
886 /* Looks a valid sequence. */
887 text_in += len;
888 switch (seq_type) {
889 case ST_START:
890 if (tags) {
891 /* Create a new tag. */
892 struct text_tag *ptag = fc_malloc(sizeof(struct text_tag));
894 if (text_tag_init_from_sequence(ptag, type,
895 text_out - plain_text, buf)) {
896 text_tag_list_append(*tags, ptag);
897 } else {
898 text_tag_destroy(ptag);
899 log_featured_text("Couldn't create a text tag with \"%s\".",
900 buf);
903 break;
904 case ST_STOP:
905 if (tags) {
906 /* Set the stop offset. */
907 struct text_tag *ptag = NULL;
909 /* Look up on reversed order. */
910 text_tag_list_rev_iterate(*tags, piter) {
911 if (piter->type == type
912 && piter->stop_offset == FT_OFFSET_UNSET) {
913 ptag = piter;
914 break;
916 } text_tag_list_rev_iterate_end;
918 if (ptag) {
919 ptag->stop_offset = text_out - plain_text;
920 } else {
921 log_featured_text("Extra text tag end for \"%s\".",
922 text_tag_type_name(type));
925 break;
926 case ST_SINGLE:
928 /* In this case, we replace the sequence by some text. */
929 struct text_tag tag;
931 if (!text_tag_init_from_sequence(&tag, type,
932 text_out - plain_text, buf)) {
933 log_featured_text("Couldn't create a text tag with \"%s\".",
934 buf);
935 } else {
936 len = text_tag_replace_text(&tag, text_out, text_out_len,
937 replace_link_text);
938 text_out += len;
939 text_out_len -= len;
940 if (tags) {
941 /* Set it in the list. */
942 struct text_tag *ptag = fc_malloc(sizeof(struct text_tag));
944 *ptag = tag;
945 ptag->stop_offset = text_out - plain_text;
946 text_tag_list_append(*tags, ptag);
950 break;
952 } else {
953 *text_out++ = *text_in++;
954 text_out_len--;
956 } else {
957 *text_out++ = *text_in++;
958 text_out_len--;
961 *text_out = '\0';
963 return plain_text_len - text_out_len;
966 /**************************************************************************
967 Apply a tag to a text. This text can already containing escape
968 sequences. Returns 0 on error.
970 Prototype:
971 - If tag_type is TTT_BOLD, TTT_ITALIC, TTT_STRIKE or TTT_UNDERLINE, there
972 shouldn't be any extra argument.
973 - If tag_type is TTT_COLOR:
974 size_t featured_text_apply_tag(..., const struct ft_color color);
975 - If tag_type is TTT_LINK and you want a city link:
976 size_t featured_text_apply_tag(..., TLT_CITY, struct city *pcity);
977 - If tag_type is TTT_LINK and you want a tile link:
978 size_t featured_text_apply_tag(..., TLT_TILE, struct tile *ptile);
979 - If tag_type is TTT_LINK and you want an unit link:
980 size_t featured_text_apply_tag(..., TLT_UNIT, struct unit *punit);
982 See also comment for text_tag_initv().
983 **************************************************************************/
984 size_t featured_text_apply_tag(const char *text_source,
985 char *featured_text, size_t featured_text_len,
986 enum text_tag_type tag_type,
987 ft_offset_t start_offset,
988 ft_offset_t stop_offset,
989 ...)
991 struct text_tag tag;
992 size_t len, total_len = 0;
993 va_list args;
995 if (start_offset == FT_OFFSET_UNSET
996 || start_offset > strlen(text_source)
997 || (stop_offset != FT_OFFSET_UNSET
998 && stop_offset < start_offset)) {
999 log_featured_text("featured_text_apply_tag(): invalid offsets.");
1000 return 0;
1003 va_start(args, stop_offset);
1004 if (!text_tag_initv(&tag, tag_type, start_offset, stop_offset, args)) {
1005 va_end(args);
1006 return 0;
1008 va_end(args);
1010 if (start_offset > 0) {
1011 /* First part: before the sequence. */
1012 len = 0;
1013 while (len < start_offset
1014 && *text_source != '\0'
1015 && featured_text_len > 1) {
1016 *featured_text++ = *text_source++;
1017 featured_text_len--;
1018 len++;
1020 total_len += len;
1023 /* Start sequence. */
1024 len = text_tag_start_sequence(&tag, featured_text, featured_text_len);
1025 total_len += len;
1026 featured_text += len;
1027 featured_text_len -= len;
1029 /* Second part: between the sequences. */
1030 len = start_offset;
1031 while (len < stop_offset
1032 && *text_source != '\0'
1033 && featured_text_len > 1) {
1034 *featured_text++ = *text_source++;
1035 featured_text_len--;
1036 len++;
1038 total_len += len;
1040 /* Stop sequence. */
1041 len = text_tag_stop_sequence(&tag, featured_text, featured_text_len);
1042 total_len += len;
1043 featured_text += len;
1044 featured_text_len -= len;
1046 /* Third part: after the sequence. */
1047 while (*text_source != '\0'
1048 && featured_text_len > 1) {
1049 *featured_text++ = *text_source++;
1050 featured_text_len--;
1051 total_len++;
1053 *featured_text = '\0';
1055 return total_len;
1058 /**************************************************************************
1059 Get a text link to a city.
1060 N.B.: The returned string is static, so every call to this function
1061 overwrites the previous.
1062 **************************************************************************/
1063 const char *city_link(const struct city *pcity)
1065 static char buf[MAX_LEN_LINK];
1067 fc_snprintf(buf, sizeof(buf), "%c%s tgt=\"%s\" id=%d name=\"%s\" %c%c",
1068 SEQ_START, text_tag_type_short_name(TTT_LINK),
1069 text_link_type_name(TLT_CITY), pcity->id,
1070 city_name_get(pcity), SEQ_END, SEQ_STOP);
1071 return buf;
1074 /**************************************************************************
1075 Get a text link to a city tile (make a clickable link to a tile with the
1076 city name as text).
1077 N.B.: The returned string is static, so every call to this function
1078 overwrites the previous.
1079 **************************************************************************/
1080 const char *city_tile_link(const struct city *pcity)
1082 static char buf[MAX_LEN_LINK];
1083 const char *tag_name = text_tag_type_short_name(TTT_LINK);
1085 fc_snprintf(buf, sizeof(buf), "%c%s tgt=\"%s\" x=%d y=%d%c%s%c%c%s%c",
1086 SEQ_START, tag_name, text_link_type_name(TLT_TILE),
1087 TILE_XY(city_tile(pcity)), SEQ_STOP, city_name_get(pcity),
1088 SEQ_START, SEQ_END, tag_name, SEQ_STOP);
1089 return buf;
1092 /**************************************************************************
1093 Get a text link to a tile.
1094 N.B.: The returned string is static, so every call to this function
1095 overwrites the previous.
1096 **************************************************************************/
1097 const char *tile_link(const struct tile *ptile)
1099 static char buf[MAX_LEN_LINK];
1101 fc_snprintf(buf, sizeof(buf), "%c%s tgt=\"%s\" x=%d y=%d %c%c",
1102 SEQ_START, text_tag_type_short_name(TTT_LINK),
1103 text_link_type_name(TLT_TILE), TILE_XY(ptile),
1104 SEQ_END, SEQ_STOP);
1105 return buf;
1108 /**************************************************************************
1109 Get a text link to an unit.
1110 N.B.: The returned string is static, so every call to this function
1111 overwrites the previous.
1112 **************************************************************************/
1113 const char *unit_link(const struct unit *punit)
1115 static char buf[MAX_LEN_LINK];
1117 fc_snprintf(buf, sizeof(buf), "%c%s tgt=\"%s\" id=%d name=\"%s\" %c%c",
1118 SEQ_START, text_tag_type_short_name(TTT_LINK),
1119 text_link_type_name(TLT_UNIT), punit->id,
1120 unit_name_translation(punit), SEQ_END, SEQ_STOP);
1121 return buf;
1124 /**************************************************************************
1125 Get a text link to a unit tile (make a clickable link to a tile with the
1126 unit type name as text).
1127 N.B.: The returned string is static, so every call to this function
1128 overwrites the previous.
1129 **************************************************************************/
1130 const char *unit_tile_link(const struct unit *punit)
1132 static char buf[MAX_LEN_LINK];
1133 const char *tag_name = text_tag_type_short_name(TTT_LINK);
1135 fc_snprintf(buf, sizeof(buf), "%c%s tgt=\"%s\" x=%d y=%d%c%s%c%c%s%c",
1136 SEQ_START, tag_name, text_link_type_name(TLT_TILE),
1137 TILE_XY(unit_tile(punit)), SEQ_STOP,
1138 unit_name_translation(punit),
1139 SEQ_START, SEQ_END, tag_name, SEQ_STOP);
1140 return buf;