Rulesave saves trade.type and trade.bonus correctly.
[freeciv.git] / common / featured_text.c
blobdf5943b82efaa4bea23d46ed73c45fe22da59700
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_normal = FT_COLOR("#006400", NULL);
101 const struct ft_color ftc_luaconsole_verbose = FT_COLOR("#B8B8B8", NULL);
102 const struct ft_color ftc_luaconsole_debug = FT_COLOR("#B87676", NULL);
104 /**************************************************************************
105 Return the long name of the text tag type.
106 See also text_tag_type_short_name().
107 **************************************************************************/
108 static const char *text_tag_type_name(enum text_tag_type type)
110 switch (type) {
111 case TTT_BOLD:
112 return "bold";
113 case TTT_ITALIC:
114 return "italic";
115 case TTT_STRIKE:
116 return "strike";
117 case TTT_UNDERLINE:
118 return "underline";
119 case TTT_COLOR:
120 return "color";
121 case TTT_LINK:
122 return "link";
124 /* Don't handle the default case to be warned if a new value was added. */
125 return NULL;
128 /**************************************************************************
129 Return the name abbreviation of the text tag type.
130 See also text_tag_type_name().
131 **************************************************************************/
132 static const char *text_tag_type_short_name(enum text_tag_type type)
134 switch (type) {
135 case TTT_BOLD:
136 return "b";
137 case TTT_ITALIC:
138 return "i";
139 case TTT_STRIKE:
140 return "s";
141 case TTT_UNDERLINE:
142 return "u";
143 case TTT_COLOR:
144 return "c";
145 case TTT_LINK:
146 return "l";
148 /* Don't handle the default case to be warned if a new value was added. */
149 return NULL;
152 /**************************************************************************
153 Return the name of the text tag link target type.
154 **************************************************************************/
155 static const char *text_link_type_name(enum text_link_type type)
157 switch (type) {
158 case TLT_CITY:
159 return "city";
160 case TLT_TILE:
161 return "tile";
162 case TLT_UNIT:
163 return "unit";
165 /* Don't handle the default case to be warned if a new value was added. */
166 return NULL;
169 /**************************************************************************
170 Find inside a sequence the string associated to a particular option name.
171 Returns TRUE on success.
172 **************************************************************************/
173 static bool find_option(const char *buf_in, const char *option,
174 char *buf_out, size_t write_len)
176 size_t option_len = strlen(option);
178 while (*buf_in != '\0') {
179 while (fc_isspace(*buf_in) && *buf_in != '\0') {
180 buf_in++;
183 if (0 == strncasecmp(buf_in, option, option_len)) {
184 /* This is this one. */
185 buf_in += option_len;
187 while ((fc_isspace(*buf_in) || *buf_in == '=') && *buf_in != '\0') {
188 buf_in++;
190 if (*buf_in == '"') {
191 /* Quote case. */
192 const char *end = strchr(++buf_in, '"');
194 if (!end) {
195 return FALSE;
197 if (end - buf_in + 1 > 0) {
198 fc_strlcpy(buf_out, buf_in, MIN(end - buf_in + 1, write_len));
199 } else {
200 *buf_out = '\0';
202 return TRUE;
203 } else {
204 while (fc_isalnum(*buf_in) && write_len > 1) {
205 *buf_out++ = *buf_in++;
206 write_len--;
208 *buf_out = '\0';
209 return TRUE;
212 buf_in++;
215 return FALSE;
218 /**************************************************************************
219 Initialize a text_tag structure from a string sequence.
220 Returns TRUE on success.
221 **************************************************************************/
222 static bool text_tag_init_from_sequence(struct text_tag *ptag,
223 enum text_tag_type type,
224 ft_offset_t start_offset,
225 const char *sequence)
227 ptag->type = type;
228 ptag->start_offset = start_offset;
229 ptag->stop_offset = FT_OFFSET_UNSET;
231 switch (type) {
232 case TTT_BOLD:
233 case TTT_ITALIC:
234 case TTT_STRIKE:
235 case TTT_UNDERLINE:
236 return TRUE;
237 case TTT_COLOR:
239 if (!find_option(sequence, "foreground", ptag->color.foreground,
240 sizeof(ptag->color.foreground))
241 && !find_option(sequence, "fg", ptag->color.foreground,
242 sizeof(ptag->color.foreground))) {
243 ptag->color.foreground[0] = '\0';
245 if (!find_option(sequence, "background", ptag->color.background,
246 sizeof(ptag->color.background))
247 && !find_option(sequence, "bg", ptag->color.background,
248 sizeof(ptag->color.background))) {
249 ptag->color.background[0] = '\0';
252 return TRUE;
253 case TTT_LINK:
255 char buf[64];
256 const char *name;
257 int i;
259 if (!find_option(sequence, "target", buf, sizeof(buf))
260 && !find_option(sequence, "tgt", buf, sizeof(buf))) {
261 log_featured_text("text_tag_init_from_sequence(): "
262 "target link type not set.");
263 return FALSE;
266 ptag->link.type = -1;
267 for (i = 0; (name = text_link_type_name(i)); i++) {
268 if (0 == fc_strncasecmp(buf, name, strlen(name))) {
269 ptag->link.type = i;
270 break;
273 if (ptag->link.type == -1) {
274 log_featured_text("text_tag_init_from_sequence(): "
275 "target link type not supported (\"%s\").", buf);
276 return FALSE;
279 switch (ptag->link.type) {
280 case TLT_CITY:
282 if (!find_option(sequence, "id", buf, sizeof(buf))) {
283 log_featured_text("text_tag_init_from_sequence(): "
284 "city link without id.");
285 return FALSE;
287 if (!str_to_int(buf, &ptag->link.id)) {
288 log_featured_text("text_tag_init_from_sequence(): "
289 "city link without valid id (\"%s\").", buf);
290 return FALSE;
293 if (!find_option(sequence, "name", ptag->link.name,
294 sizeof(ptag->link.name))) {
295 /* Set something as name. */
296 fc_snprintf(ptag->link.name, sizeof(ptag->link.name),
297 "CITY_ID%d", ptag->link.id);
300 return TRUE;
301 case TLT_TILE:
303 struct tile *ptile;
304 int x, y;
306 if (!find_option(sequence, "x", buf, sizeof(buf))) {
307 log_featured_text("text_tag_init_from_sequence(): "
308 "tile link without x coordinate.");
309 return FALSE;
311 if (!str_to_int(buf, &x)) {
312 log_featured_text("text_tag_init_from_sequence(): "
313 "tile link without valid x coordinate "
314 "(\"%s\").", buf);
315 return FALSE;
318 if (!find_option(sequence, "y", buf, sizeof(buf))) {
319 log_featured_text("text_tag_init_from_sequence(): "
320 "tile link without y coordinate.");
321 return FALSE;
323 if (!str_to_int(buf, &y)) {
324 log_featured_text("text_tag_init_from_sequence(): "
325 "tile link without valid y coordinate "
326 "(\"%s\").", buf);
327 return FALSE;
330 ptile = map_pos_to_tile(x, y);
331 if (!ptile) {
332 log_featured_text("text_tag_init_from_sequence(): "
333 "(%d, %d) are not valid coordinates "
334 "in this game.", x, y);
335 return FALSE;
337 ptag->link.id = tile_index(ptile);
338 fc_snprintf(ptag->link.name, sizeof(ptag->link.name),
339 "(%d, %d)", TILE_XY(ptile));
341 return TRUE;
342 case TLT_UNIT:
344 if (!find_option(sequence, "id", buf, sizeof(buf))) {
345 log_featured_text("text_tag_init_from_sequence(): "
346 "unit link without id.");
347 return FALSE;
349 if (!str_to_int(buf, &ptag->link.id)) {
350 log_featured_text("text_tag_init_from_sequence(): "
351 "unit link without valid id (\"%s\").", buf);
352 return FALSE;
355 if (!find_option(sequence, "name", ptag->link.name,
356 sizeof(ptag->link.name))) {
357 /* Set something as name. */
358 fc_snprintf(ptag->link.name, sizeof(ptag->link.name),
359 "UNIT_ID%d", ptag->link.id);
362 return TRUE;
366 return FALSE;
369 /**************************************************************************
370 Initialize a text_tag structure from a va_list.
372 What's should be in the va_list:
373 - If the text tag type is TTT_BOLD, TTT_ITALIC, TTT_STRIKE or
374 TTT_UNDERLINE, there shouldn't be any extra argument.
375 - If the text tag type is TTT_COLOR, then there should be 1 argument of
376 type 'struct ft_color'.
377 - If the text tag type is TTT_LINK, then there should be 2 extra arguments.
378 The first is type 'enum text_link_type' and will determine the type of the
379 following argument:
380 - If the link type is TLT_CITY, last argument is typed 'struct city *'.
381 - If the link type is TLT_TILE, last argument is typed 'struct tile *'.
382 - If the link type is TLT_UNIT, last argument is typed 'struct unit *'.
384 Returns TRUE on success.
385 **************************************************************************/
386 static bool text_tag_initv(struct text_tag *ptag, enum text_tag_type type,
387 ft_offset_t start_offset, ft_offset_t stop_offset,
388 va_list args)
390 ptag->type = type;
391 ptag->start_offset = start_offset;
392 ptag->stop_offset = stop_offset;
394 switch (type) {
395 case TTT_BOLD:
396 case TTT_ITALIC:
397 case TTT_STRIKE:
398 case TTT_UNDERLINE:
399 return TRUE;
400 case TTT_COLOR:
402 const struct ft_color color = va_arg(args, struct ft_color);
404 if ((NULL == color.foreground || '\0' == color.foreground[0])
405 && (NULL == color.background || '\0' == color.background[0])) {
406 return FALSE; /* No color at all. */
409 if (NULL != color.foreground && '\0' != color.foreground[0]) {
410 sz_strlcpy(ptag->color.foreground, color.foreground);
411 } else {
412 ptag->color.foreground[0] = '\0';
415 if (NULL != color.background && '\0' != color.background[0]) {
416 sz_strlcpy(ptag->color.background, color.background);
417 } else {
418 ptag->color.background[0] = '\0';
421 return TRUE;
422 case TTT_LINK:
424 ptag->link.type = va_arg(args, enum text_link_type);
425 switch (ptag->link.type) {
426 case TLT_CITY:
428 struct city *pcity = va_arg(args, struct city *);
430 if (!pcity) {
431 return FALSE;
433 ptag->link.id = pcity->id;
434 sz_strlcpy(ptag->link.name, city_name_get(pcity));
436 return TRUE;
437 case TLT_TILE:
439 struct tile *ptile = va_arg(args, struct tile *);
441 if (!ptile) {
442 return FALSE;
444 ptag->link.id = tile_index(ptile);
445 fc_snprintf(ptag->link.name, sizeof(ptag->link.name),
446 "(%d, %d)", TILE_XY(ptile));
448 return TRUE;
449 case TLT_UNIT:
451 struct unit *punit = va_arg(args, struct unit *);
453 if (!punit) {
454 return FALSE;
456 ptag->link.id = punit->id;
457 sz_strlcpy(ptag->link.name, unit_name_translation(punit));
459 return TRUE;
463 return FALSE;
466 /**************************************************************************
467 Print in a string the start sequence of the tag.
468 **************************************************************************/
469 static size_t text_tag_start_sequence(const struct text_tag *ptag,
470 char *buf, size_t len)
472 switch (ptag->type) {
473 case TTT_BOLD:
474 case TTT_ITALIC:
475 case TTT_STRIKE:
476 case TTT_UNDERLINE:
477 return fc_snprintf(buf, len, "%c%s%c", SEQ_START,
478 text_tag_type_short_name(ptag->type), SEQ_STOP);
479 case TTT_COLOR:
481 size_t ret = fc_snprintf(buf, len, "%c%s", SEQ_START,
482 text_tag_type_short_name(ptag->type));
484 if (ptag->color.foreground[0] != '\0') {
485 ret += fc_snprintf(buf + ret, len - ret, " fg=\"%s\"",
486 ptag->color.foreground);
488 if (ptag->color.background[0] != '\0') {
489 ret += fc_snprintf(buf + ret, len - ret, " bg=\"%s\"",
490 ptag->color.background);
492 return ret + fc_snprintf(buf + ret, len - ret, "%c", SEQ_STOP);
494 case TTT_LINK:
496 size_t ret = fc_snprintf(buf, len, "%c%s tgt=\"%s\"", SEQ_START,
497 text_tag_type_short_name(ptag->type),
498 text_link_type_name(ptag->link.type));
500 switch (ptag->link.type) {
501 case TLT_CITY:
503 struct city *pcity = game_city_by_number(ptag->link.id);
505 if (pcity) {
506 ret += fc_snprintf(buf + ret, len - ret,
507 " id=%d name=\"%s\"",
508 pcity->id, city_name_get(pcity));
509 } else {
510 ret += fc_snprintf(buf + ret, len - ret,
511 " id=%d", ptag->link.id);
514 break;
515 case TLT_TILE:
517 struct tile *ptile = index_to_tile(ptag->link.id);
519 if (ptile) {
520 ret += fc_snprintf(buf + ret, len - ret,
521 " x=%d y=%d", TILE_XY(ptile));
522 } else {
523 ret += fc_snprintf(buf + ret, len - ret,
524 " id=%d", ptag->link.id);
527 break;
528 case TLT_UNIT:
530 struct unit *punit = game_unit_by_number(ptag->link.id);
532 if (punit) {
533 ret += fc_snprintf(buf + ret, len - ret,
534 " id=%d name=\"%s\"",
535 punit->id, unit_name_translation(punit));
536 } else {
537 ret += fc_snprintf(buf + ret, len - ret,
538 " id=%d", ptag->link.id);
541 break;
544 if (ptag->stop_offset == ptag->start_offset) {
545 /* This is a single sequence like [link ... /]. */
546 ret += fc_snprintf(buf + ret, len - ret, "%c", SEQ_END);
549 return ret + fc_snprintf(buf + ret, len - ret, "%c", SEQ_STOP);
552 return 0;
555 /**************************************************************************
556 Print in a string the stop sequence of the tag.
557 **************************************************************************/
558 static size_t text_tag_stop_sequence(const struct text_tag *ptag,
559 char *buf, size_t len)
561 if (ptag->type == TTT_LINK && ptag->stop_offset == ptag->start_offset) {
562 /* Should be already finished. */
563 return 0;
566 return fc_snprintf(buf, len, "%c%c%s%c", SEQ_START, SEQ_END,
567 text_tag_type_short_name(ptag->type), SEQ_STOP);
570 /**************************************************************************
571 When the sequence looks like [sequence/] then we insert a string instead.
572 **************************************************************************/
573 static size_t text_tag_replace_text(const struct text_tag *ptag,
574 char *buf, size_t len,
575 bool replace_link_text)
577 if (ptag->type != TTT_LINK) {
578 return 0;
581 if (replace_link_text) {
582 /* The client might check if this should be updated or translated. */
583 switch (ptag->link.type) {
584 case TLT_CITY:
586 struct city *pcity = game_city_by_number(ptag->link.id);
588 /* Note that if city_tile(pcity) is NULL, then it is probably an
589 * invisible city (see client/packhand.c). Then, we don't
590 * use the current city name which is usually not complete,
591 * a dumb string using the city id. */
592 if (NULL != pcity && NULL != city_tile(pcity)) {
593 return fc_snprintf(buf, len, "%s", city_name_get(pcity));
596 break;
597 case TLT_TILE:
598 break;
599 case TLT_UNIT:
601 struct unit *punit = game_unit_by_number(ptag->link.id);
603 if (punit) {
604 return fc_snprintf(buf, len, "%s", unit_name_translation(punit));
607 break;
611 if (ptag->link.type == TLT_UNIT) {
612 /* Attempt to translate the link name (it should be a unit type name). */
613 return fc_snprintf(buf, len, "%s", _(ptag->link.name));
614 } else {
615 return fc_snprintf(buf, len, "%s", ptag->link.name);
619 /**************************************************************************
620 Returns a new text_tag or NULL on error.
622 Prototype:
623 - If tag_type is TTT_BOLD, TTT_ITALIC, TTT_STRIKE or TTT_UNDERLINE, there
624 shouldn't be any extra argument.
625 - If tag_type is TTT_COLOR:
626 struct text_tag *text_tag_new(..., const struct ft_color color);
627 - If tag_type is TTT_LINK and you want a city link:
628 struct text_tag *text_tag_new(..., TLT_CITY, struct city *pcity);
629 - If tag_type is TTT_LINK and you want a tile link:
630 struct text_tag *text_tag_new(..., TLT_TILE, struct tile *ptile);
631 - If tag_type is TTT_LINK and you want an unit link:
632 struct text_tag *text_tag_new(..., TLT_UNIT, struct unit *punit);
634 See also comment for text_tag_initv().
635 **************************************************************************/
636 struct text_tag *text_tag_new(enum text_tag_type tag_type,
637 ft_offset_t start_offset,
638 ft_offset_t stop_offset,
639 ...)
641 struct text_tag *ptag = fc_malloc(sizeof(struct text_tag));
642 va_list args;
643 bool ok;
645 va_start(args, stop_offset);
646 ok = text_tag_initv(ptag, tag_type, start_offset, stop_offset, args);
647 va_end(args);
649 if (ok) {
650 return ptag;
651 } else {
652 free(ptag);
653 return NULL;
657 /**************************************************************************
658 This function returns a new pointer to a text_tag which is similar
659 to the 'ptag' argument.
660 **************************************************************************/
661 struct text_tag *text_tag_copy(const struct text_tag *ptag)
663 struct text_tag *pnew_tag;
665 if (!ptag) {
666 return NULL;
669 pnew_tag = fc_malloc(sizeof(struct text_tag));
670 *pnew_tag = *ptag;
672 return pnew_tag;
675 /**************************************************************************
676 Free a text_tag structure.
677 **************************************************************************/
678 void text_tag_destroy(struct text_tag *ptag)
680 free(ptag);
683 /**************************************************************************
684 Return the type of this text tag.
685 **************************************************************************/
686 enum text_tag_type text_tag_type(const struct text_tag *ptag)
688 return ptag->type;
691 /**************************************************************************
692 Return the start offset (in bytes) of this text tag.
693 **************************************************************************/
694 ft_offset_t text_tag_start_offset(const struct text_tag *ptag)
696 return ptag->start_offset;
699 /**************************************************************************
700 Return the stop offset (in bytes) of this text tag.
701 **************************************************************************/
702 ft_offset_t text_tag_stop_offset(const struct text_tag *ptag)
704 return ptag->stop_offset;
707 /**************************************************************************
708 Return the foreground color suggested by this text tag. This requires
709 the tag type to be TTT_COLOR. Returns NULL on error, "" if unset.
710 **************************************************************************/
711 const char *text_tag_color_foreground(const struct text_tag *ptag)
713 if (ptag->type != TTT_COLOR) {
714 log_error("text_tag_color_foreground(): incompatible tag type.");
715 return NULL;
718 return ptag->color.foreground;
721 /**************************************************************************
722 Return the background color suggested by this text tag. This requires
723 the tag type to be TTT_COLOR. Returns NULL on error, "" if unset.
724 **************************************************************************/
725 const char *text_tag_color_background(const struct text_tag *ptag)
727 if (ptag->type != TTT_COLOR) {
728 log_error("text_tag_color_background(): incompatible tag type.");
729 return NULL;
732 return ptag->color.background;
735 /**************************************************************************
736 Return the link target type suggested by this text tag. This requires
737 the tag type to be TTT_LINK. Returns -1 on error.
738 **************************************************************************/
739 enum text_link_type text_tag_link_type(const struct text_tag *ptag)
741 if (ptag->type != TTT_LINK) {
742 log_error("text_tag_link_type(): incompatible tag type.");
743 return -1;
746 return ptag->link.type;
749 /**************************************************************************
750 Return the link target id suggested by this text tag (city id,
751 tile index or unit id). This requires the tag type to be TTT_LINK.
752 Returns -1 on error.
753 **************************************************************************/
754 int text_tag_link_id(const struct text_tag *ptag)
756 if (ptag->type != TTT_LINK) {
757 log_error("text_tag_link_id(): incompatible tag type.");
758 return -1;
761 return ptag->link.id;
764 /**************************************************************************
765 Extract a sequence from a string. Also, determine the type and the text
766 tag type of the sequence. Return 0 on error.
767 **************************************************************************/
768 static size_t extract_sequence_text(const char *featured_text,
769 char *buf, size_t len,
770 enum sequence_type *seq_type,
771 enum text_tag_type *type)
773 const char *buf_in = featured_text;
774 const char *stop = strchr(buf_in, SEQ_STOP);
775 const char *end = stop;
776 const char *name;
777 size_t type_len;
778 size_t name_len;
779 int i;
781 if (!stop) {
782 return 0; /* Not valid. */
785 /* Check sequence type. */
786 for (buf_in++; fc_isspace(*buf_in); buf_in++);
788 if (*buf_in == SEQ_END) {
789 *seq_type = ST_STOP;
790 buf_in++;
791 } else {
792 for (end--; fc_isspace(*end); end--);
794 if (*end == SEQ_END) {
795 *seq_type = ST_SINGLE;
797 for (end--; fc_isspace(*end); end--);
798 } else {
799 *seq_type = ST_START;
803 while (fc_isspace(*buf_in)) {
804 buf_in++;
807 /* Check the length of the type name. */
808 for (name = buf_in; name < stop; name++) {
809 if (!fc_isalpha(*name)) {
810 break;
813 type_len = name - buf_in;
815 *type = -1;
816 for (i = 0; (name = text_tag_type_name(i)); i++) {
817 name_len = strlen(name);
818 if (name_len == type_len && 0 == fc_strncasecmp(name, buf_in, name_len)) {
819 buf_in += name_len;
820 *type = i;
821 break;
824 if (*type == -1) {
825 /* Try with short names. */
826 for (i = 0; (name = text_tag_type_short_name(i)); i++) {
827 name_len = strlen(name);
828 if (name_len == type_len
829 && 0 == fc_strncasecmp(name, buf_in, name_len)) {
830 buf_in += name_len;
831 *type = i;
832 break;
835 if (*type == -1) {
836 return 0; /* Not valid. */
840 while (fc_isspace(*buf_in)) {
841 buf_in++;
844 if (end - buf_in + 2 > 0) {
845 fc_strlcpy(buf, buf_in, MIN(end - buf_in + 2, len));
846 } else {
847 buf[0] = '\0';
849 return stop - featured_text + 1;
853 /**************************************************************************
854 Separate the text from the text features. 'tags' can be NULL.
856 When 'replace_link_text' is set, the text used for the signal sequence
857 links will be overwritten. It is used on client side to have updated
858 links in chatline, to communicate when users don't know share the city
859 names, and avoid users making voluntary confusing names when editing
860 links in chatline.
861 **************************************************************************/
862 size_t featured_text_to_plain_text(const char *featured_text,
863 char *plain_text, size_t plain_text_len,
864 struct text_tag_list **tags,
865 bool replace_link_text)
867 const char *text_in = featured_text;
868 char *text_out = plain_text;
869 size_t text_out_len = plain_text_len;
871 if (tags) {
872 *tags = text_tag_list_new();
875 while (*text_in != '\0' && text_out_len > 1) {
876 if (SEQ_START == *text_in) {
877 /* Escape sequence... */
878 char buf[text_out_len];
879 enum sequence_type seq_type;
880 enum text_tag_type type;
881 size_t len = extract_sequence_text(text_in, buf, text_out_len,
882 &seq_type, &type);
884 if (len > 0) {
885 /* Looks a valid sequence. */
886 text_in += len;
887 switch (seq_type) {
888 case ST_START:
889 if (tags) {
890 /* Create a new tag. */
891 struct text_tag *ptag = fc_malloc(sizeof(struct text_tag));
893 if (text_tag_init_from_sequence(ptag, type,
894 text_out - plain_text, buf)) {
895 text_tag_list_append(*tags, ptag);
896 } else {
897 text_tag_destroy(ptag);
898 log_featured_text("Couldn't create a text tag with \"%s\".",
899 buf);
902 break;
903 case ST_STOP:
904 if (tags) {
905 /* Set the stop offset. */
906 struct text_tag *ptag = NULL;
908 /* Look up on reversed order. */
909 text_tag_list_rev_iterate(*tags, piter) {
910 if (piter->type == type
911 && piter->stop_offset == FT_OFFSET_UNSET) {
912 ptag = piter;
913 break;
915 } text_tag_list_rev_iterate_end;
917 if (ptag) {
918 ptag->stop_offset = text_out - plain_text;
919 } else {
920 log_featured_text("Extra text tag end for \"%s\".",
921 text_tag_type_name(type));
924 break;
925 case ST_SINGLE:
927 /* In this case, we replace the sequence by some text. */
928 struct text_tag tag;
930 if (!text_tag_init_from_sequence(&tag, type,
931 text_out - plain_text, buf)) {
932 log_featured_text("Couldn't create a text tag with \"%s\".",
933 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 struct text_tag *ptag = fc_malloc(sizeof(struct text_tag));
943 *ptag = tag;
944 ptag->stop_offset = text_out - plain_text;
945 text_tag_list_append(*tags, ptag);
949 break;
951 } else {
952 *text_out++ = *text_in++;
953 text_out_len--;
955 } else {
956 *text_out++ = *text_in++;
957 text_out_len--;
960 *text_out = '\0';
962 return plain_text_len - text_out_len;
965 /**************************************************************************
966 Apply a tag to a text. This text can already containing escape
967 sequences. Returns 0 on error.
969 Prototype:
970 - If tag_type is TTT_BOLD, TTT_ITALIC, TTT_STRIKE or TTT_UNDERLINE, there
971 shouldn't be any extra argument.
972 - If tag_type is TTT_COLOR:
973 size_t featured_text_apply_tag(..., const struct ft_color color);
974 - If tag_type is TTT_LINK and you want a city link:
975 size_t featured_text_apply_tag(..., TLT_CITY, struct city *pcity);
976 - If tag_type is TTT_LINK and you want a tile link:
977 size_t featured_text_apply_tag(..., TLT_TILE, struct tile *ptile);
978 - If tag_type is TTT_LINK and you want an unit link:
979 size_t featured_text_apply_tag(..., TLT_UNIT, struct unit *punit);
981 See also comment for text_tag_initv().
982 **************************************************************************/
983 size_t featured_text_apply_tag(const char *text_source,
984 char *featured_text, size_t featured_text_len,
985 enum text_tag_type tag_type,
986 ft_offset_t start_offset,
987 ft_offset_t stop_offset,
988 ...)
990 struct text_tag tag;
991 size_t len, total_len = 0;
992 va_list args;
994 if (start_offset == FT_OFFSET_UNSET
995 || start_offset > strlen(text_source)
996 || (stop_offset != FT_OFFSET_UNSET
997 && stop_offset < start_offset)) {
998 log_featured_text("featured_text_apply_tag(): invalid offsets.");
999 return 0;
1002 va_start(args, stop_offset);
1003 if (!text_tag_initv(&tag, tag_type, start_offset, stop_offset, args)) {
1004 va_end(args);
1005 return 0;
1007 va_end(args);
1009 if (start_offset > 0) {
1010 /* First part: before the sequence. */
1011 len = 0;
1012 while (len < start_offset
1013 && *text_source != '\0'
1014 && featured_text_len > 1) {
1015 *featured_text++ = *text_source++;
1016 featured_text_len--;
1017 len++;
1019 total_len += len;
1022 /* Start sequence. */
1023 len = text_tag_start_sequence(&tag, featured_text, featured_text_len);
1024 total_len += len;
1025 featured_text += len;
1026 featured_text_len -= len;
1028 /* Second part: between the sequences. */
1029 len = start_offset;
1030 while (len < stop_offset
1031 && *text_source != '\0'
1032 && featured_text_len > 1) {
1033 *featured_text++ = *text_source++;
1034 featured_text_len--;
1035 len++;
1037 total_len += len;
1039 /* Stop sequence. */
1040 len = text_tag_stop_sequence(&tag, featured_text, featured_text_len);
1041 total_len += len;
1042 featured_text += len;
1043 featured_text_len -= len;
1045 /* Third part: after the sequence. */
1046 while (*text_source != '\0'
1047 && featured_text_len > 1) {
1048 *featured_text++ = *text_source++;
1049 featured_text_len--;
1050 total_len++;
1052 *featured_text = '\0';
1054 return total_len;
1057 /**************************************************************************
1058 Get a text link to a city.
1059 N.B.: The returned string is static, so every call to this function
1060 overwrites the previous.
1061 **************************************************************************/
1062 const char *city_link(const struct city *pcity)
1064 static char buf[MAX_LEN_LINK];
1066 fc_snprintf(buf, sizeof(buf), "%c%s tgt=\"%s\" id=%d name=\"%s\" %c%c",
1067 SEQ_START, text_tag_type_short_name(TTT_LINK),
1068 text_link_type_name(TLT_CITY), pcity->id,
1069 city_name_get(pcity), SEQ_END, SEQ_STOP);
1070 return buf;
1073 /**************************************************************************
1074 Get a text link to a city tile (make a clickable link to a tile with the
1075 city name as text).
1076 N.B.: The returned string is static, so every call to this function
1077 overwrites the previous.
1078 **************************************************************************/
1079 const char *city_tile_link(const struct city *pcity)
1081 static char buf[MAX_LEN_LINK];
1082 const char *tag_name = text_tag_type_short_name(TTT_LINK);
1084 fc_snprintf(buf, sizeof(buf), "%c%s tgt=\"%s\" x=%d y=%d%c%s%c%c%s%c",
1085 SEQ_START, tag_name, text_link_type_name(TLT_TILE),
1086 TILE_XY(city_tile(pcity)), SEQ_STOP, city_name_get(pcity),
1087 SEQ_START, SEQ_END, tag_name, SEQ_STOP);
1088 return buf;
1091 /**************************************************************************
1092 Get a text link to a tile.
1093 N.B.: The returned string is static, so every call to this function
1094 overwrites the previous.
1095 **************************************************************************/
1096 const char *tile_link(const struct tile *ptile)
1098 static char buf[MAX_LEN_LINK];
1100 fc_snprintf(buf, sizeof(buf), "%c%s tgt=\"%s\" x=%d y=%d %c%c",
1101 SEQ_START, text_tag_type_short_name(TTT_LINK),
1102 text_link_type_name(TLT_TILE), TILE_XY(ptile),
1103 SEQ_END, SEQ_STOP);
1104 return buf;
1107 /**************************************************************************
1108 Get a text link to an unit.
1109 N.B.: The returned string is static, so every call to this function
1110 overwrites the previous.
1111 **************************************************************************/
1112 const char *unit_link(const struct unit *punit)
1114 static char buf[MAX_LEN_LINK];
1116 fc_snprintf(buf, sizeof(buf), "%c%s tgt=\"%s\" id=%d name=\"%s\" %c%c",
1117 SEQ_START, text_tag_type_short_name(TTT_LINK),
1118 text_link_type_name(TLT_UNIT), punit->id,
1119 unit_name_translation(punit), SEQ_END, SEQ_STOP);
1120 return buf;
1123 /**************************************************************************
1124 Get a text link to a unit tile (make a clickable link to a tile with the
1125 unit type name as text).
1126 N.B.: The returned string is static, so every call to this function
1127 overwrites the previous.
1128 **************************************************************************/
1129 const char *unit_tile_link(const struct unit *punit)
1131 static char buf[MAX_LEN_LINK];
1132 const char *tag_name = text_tag_type_short_name(TTT_LINK);
1134 fc_snprintf(buf, sizeof(buf), "%c%s tgt=\"%s\" x=%d y=%d%c%s%c%c%s%c",
1135 SEQ_START, tag_name, text_link_type_name(TLT_TILE),
1136 TILE_XY(unit_tile(punit)), SEQ_STOP,
1137 unit_name_translation(punit),
1138 SEQ_START, SEQ_END, tag_name, SEQ_STOP);
1139 return buf;