source: fmtext.c @ b343c2c

release-1.10release-1.8release-1.9
Last change on this file since b343c2c was d427f08, checked in by Nelson Elhage <nelhage@mit.edu>, 13 years ago
Use G_GNUC_WARN_UNUSED_RESULT Have gcc warn us when we ignore the result of a function that requires the caller to free the result, or an initilization function that can fail. This might help (slightly) with preventing leaks and segfaults. Additionally changed some functions that should never fail to not return values. (The owl_list_* functions changed only fail if list->size < 0, which we assume is not the case elsewhere.)
  • Property mode set to 100644
File size: 25.9 KB
Line 
1#include "owl.h"
2#include <stdlib.h>
3#include <string.h>
4
5/* initialize an fmtext with no data */
6void owl_fmtext_init_null(owl_fmtext *f)
7{
8  f->buff = g_string_new("");
9}
10
11/* Clear the data from an fmtext, but don't deallocate memory. This
12   fmtext can then be appended to again. */
13void owl_fmtext_clear(owl_fmtext *f)
14{
15  g_string_truncate(f->buff, 0);
16}
17
18int owl_fmtext_is_format_char(gunichar c)
19{
20  if ((c & ~OWL_FMTEXT_UC_ATTR_MASK) == OWL_FMTEXT_UC_ATTR) return 1;
21  if ((c & ~(OWL_FMTEXT_UC_ALLCOLOR_MASK)) == OWL_FMTEXT_UC_COLOR_BASE) return 1;
22  return 0;
23}
24/* append text to the end of 'f' with attribute 'attr' and color
25 * 'color'
26 */
27void owl_fmtext_append_attr(owl_fmtext *f, const char *text, char attr, short fgcolor, short bgcolor)
28{
29  int a = 0, fg = 0, bg = 0;
30 
31  if (attr != OWL_FMTEXT_ATTR_NONE) a=1;
32  if (fgcolor != OWL_COLOR_DEFAULT) fg=1;
33  if (bgcolor != OWL_COLOR_DEFAULT) bg=1;
34
35  /* Set attributes */
36  if (a)
37    g_string_append_unichar(f->buff, OWL_FMTEXT_UC_ATTR | attr);
38  if (fg)
39    g_string_append_unichar(f->buff, OWL_FMTEXT_UC_FGCOLOR | fgcolor);
40  if (bg)
41    g_string_append_unichar(f->buff, OWL_FMTEXT_UC_BGCOLOR | bgcolor);
42
43  g_string_append(f->buff, text);
44
45  /* Reset attributes */
46  if (bg) g_string_append_unichar(f->buff, OWL_FMTEXT_UC_BGDEFAULT);
47  if (fg) g_string_append_unichar(f->buff, OWL_FMTEXT_UC_FGDEFAULT);
48  if (a)  g_string_append_unichar(f->buff, OWL_FMTEXT_UC_ATTR | OWL_FMTEXT_UC_ATTR);
49}
50
51/* Append normal, uncolored text 'text' to 'f' */
52void owl_fmtext_append_normal(owl_fmtext *f, const char *text)
53{
54  owl_fmtext_append_attr(f, text, OWL_FMTEXT_ATTR_NONE, OWL_COLOR_DEFAULT, OWL_COLOR_DEFAULT);
55}
56
57/* Append normal, uncolored text specified by format string to 'f' */
58void G_GNUC_PRINTF(2, 3) owl_fmtext_appendf_normal(owl_fmtext *f, const char *fmt, ...)
59{
60  va_list ap;
61  char *buff;
62
63  va_start(ap, fmt);
64  buff = g_strdup_vprintf(fmt, ap);
65  va_end(ap);
66  if (!buff)
67    return;
68  owl_fmtext_append_attr(f, buff, OWL_FMTEXT_ATTR_NONE, OWL_COLOR_DEFAULT, OWL_COLOR_DEFAULT);
69  g_free(buff);
70}
71
72/* Append normal text 'text' to 'f' with color 'color' */
73void owl_fmtext_append_normal_color(owl_fmtext *f, const char *text, int fgcolor, int bgcolor)
74{
75  owl_fmtext_append_attr(f, text, OWL_FMTEXT_ATTR_NONE, fgcolor, bgcolor);
76}
77
78/* Append bold text 'text' to 'f' */
79void owl_fmtext_append_bold(owl_fmtext *f, const char *text)
80{
81  owl_fmtext_append_attr(f, text, OWL_FMTEXT_ATTR_BOLD, OWL_COLOR_DEFAULT, OWL_COLOR_DEFAULT);
82}
83
84/* Append reverse video text 'text' to 'f' */
85void owl_fmtext_append_reverse(owl_fmtext *f, const char *text)
86{
87  owl_fmtext_append_attr(f, text, OWL_FMTEXT_ATTR_REVERSE, OWL_COLOR_DEFAULT, OWL_COLOR_DEFAULT);
88}
89
90/* Append reversed and bold, uncolored text 'text' to 'f' */
91void owl_fmtext_append_reversebold(owl_fmtext *f, const char *text)
92{
93  owl_fmtext_append_attr(f, text, OWL_FMTEXT_ATTR_REVERSE | OWL_FMTEXT_ATTR_BOLD, OWL_COLOR_DEFAULT, OWL_COLOR_DEFAULT);
94}
95
96/* Internal function. Parse attrbute character. */
97static void _owl_fmtext_update_attributes(gunichar c, char *attr, short *fgcolor, short *bgcolor)
98{
99  if ((c & OWL_FMTEXT_UC_ATTR) == OWL_FMTEXT_UC_ATTR) {
100    *attr = c & OWL_FMTEXT_UC_ATTR_MASK;
101  }
102  else if ((c & OWL_FMTEXT_UC_COLOR_BASE) == OWL_FMTEXT_UC_COLOR_BASE) {
103    if ((c & OWL_FMTEXT_UC_BGCOLOR) == OWL_FMTEXT_UC_BGCOLOR) {
104      *bgcolor = (c == OWL_FMTEXT_UC_BGDEFAULT
105                  ? OWL_COLOR_DEFAULT
106                  : c & OWL_FMTEXT_UC_COLOR_MASK);
107    }
108    else if ((c & OWL_FMTEXT_UC_FGCOLOR) == OWL_FMTEXT_UC_FGCOLOR) {
109      *fgcolor = (c == OWL_FMTEXT_UC_FGDEFAULT
110                  ? OWL_COLOR_DEFAULT
111                  : c & OWL_FMTEXT_UC_COLOR_MASK);
112    }
113  }
114}
115
116/* Internal function. Scan for attribute characters. */
117static void _owl_fmtext_scan_attributes(const owl_fmtext *f, int start, char *attr, short *fgcolor, short *bgcolor)
118{
119  const char *p;
120  p = strchr(f->buff->str, OWL_FMTEXT_UC_STARTBYTE_UTF8);
121  while (p && p < f->buff->str + start) {
122    _owl_fmtext_update_attributes(g_utf8_get_char(p), attr, fgcolor, bgcolor);
123    p = strchr(p+1, OWL_FMTEXT_UC_STARTBYTE_UTF8);
124  }
125} 
126
127/* Internal function.  Append text from 'in' between index 'start'
128 * inclusive and 'stop' exclusive, to the end of 'f'. This function
129 * works with bytes.
130 */
131static void _owl_fmtext_append_fmtext(owl_fmtext *f, const owl_fmtext *in, int start, int stop)
132{
133  char attr = 0;
134  short fgcolor = OWL_COLOR_DEFAULT;
135  short bgcolor = OWL_COLOR_DEFAULT;
136
137  _owl_fmtext_scan_attributes(in, start, &attr, &fgcolor, &bgcolor);
138
139  if (attr != OWL_FMTEXT_ATTR_NONE)
140    g_string_append_unichar(f->buff, OWL_FMTEXT_UC_ATTR | attr);
141  if (fgcolor != OWL_COLOR_DEFAULT)
142    g_string_append_unichar(f->buff, OWL_FMTEXT_UC_FGCOLOR | fgcolor);
143  if (bgcolor != OWL_COLOR_DEFAULT)
144    g_string_append_unichar(f->buff, OWL_FMTEXT_UC_BGCOLOR | bgcolor);
145
146  g_string_append_len(f->buff, in->buff->str+start, stop-start);
147
148  /* Reset attributes */
149  g_string_append_unichar(f->buff, OWL_FMTEXT_UC_BGDEFAULT);
150  g_string_append_unichar(f->buff, OWL_FMTEXT_UC_FGDEFAULT);
151  g_string_append_unichar(f->buff, OWL_FMTEXT_UC_ATTR | OWL_FMTEXT_UC_ATTR);
152}
153
154/* append fmtext 'in' to 'f' */
155void owl_fmtext_append_fmtext(owl_fmtext *f, const owl_fmtext *in)
156{
157  _owl_fmtext_append_fmtext(f, in, 0, in->buff->len);
158
159}
160
161/* Append 'nspaces' number of spaces to the end of 'f' */
162void owl_fmtext_append_spaces(owl_fmtext *f, int nspaces)
163{
164  int i;
165  for (i=0; i<nspaces; i++) {
166    owl_fmtext_append_normal(f, " ");
167  }
168}
169
170/* Return a plain version of the fmtext.  Caller is responsible for
171 * freeing the return
172 */
173char G_GNUC_WARN_UNUSED_RESULT *owl_fmtext_print_plain(const owl_fmtext *f)
174{
175  return owl_strip_format_chars(f->buff->str);
176}
177
178static void _owl_fmtext_wattrset(WINDOW *w, int attrs)
179{
180  wattrset(w, A_NORMAL);
181  if (attrs & OWL_FMTEXT_ATTR_BOLD) wattron(w, A_BOLD);
182  if (attrs & OWL_FMTEXT_ATTR_REVERSE) wattron(w, A_REVERSE);
183  if (attrs & OWL_FMTEXT_ATTR_UNDERLINE) wattron(w, A_UNDERLINE);
184}
185
186static void _owl_fmtext_wcolor_set(WINDOW *w, short pair)
187{
188  if (has_colors()) {
189      wcolor_set(w,pair,NULL);
190      wbkgdset(w, COLOR_PAIR(pair));
191  }
192}
193
194/* add the formatted text to the curses window 'w'.  The window 'w'
195 * must already be initiatlized with curses
196 */
197static void _owl_fmtext_curs_waddstr(const owl_fmtext *f, WINDOW *w, int do_search, char default_attrs, short default_fgcolor, short default_bgcolor)
198{
199  /* char *tmpbuff; */
200  /* int position, trans1, trans2, trans3, len, lastsame; */
201  char *s, *p;
202  char attr;
203  short fg, bg, pair = 0;
204 
205  if (w==NULL) {
206    owl_function_debugmsg("Hit a null window in owl_fmtext_curs_waddstr.");
207    return;
208  }
209
210  s = f->buff->str;
211  /* Set default attributes. */
212  attr = default_attrs;
213  fg = default_fgcolor;
214  bg = default_bgcolor;
215  _owl_fmtext_wattrset(w, attr);
216  pair = owl_fmtext_get_colorpair(fg, bg);
217  _owl_fmtext_wcolor_set(w, pair);
218
219  /* Find next possible format character. */
220  p = strchr(s, OWL_FMTEXT_UC_STARTBYTE_UTF8);
221  while(p) {
222    if (owl_fmtext_is_format_char(g_utf8_get_char(p))) {
223      /* Deal with all text from last insert to here. */
224      char tmp;
225   
226      tmp = p[0];
227      p[0] = '\0';
228      if (do_search && owl_global_is_search_active(&g)) {
229        /* Search is active, so highlight search results. */
230        int start, end;
231        while (owl_regex_compare(owl_global_get_search_re(&g), s, &start, &end) == 0) {
232          /* Prevent an infinite loop matching the empty string. */
233          if (end == 0)
234            break;
235
236          /* Found search string, highlight it. */
237
238          waddnstr(w, s, start);
239
240          _owl_fmtext_wattrset(w, attr ^ OWL_FMTEXT_ATTR_REVERSE);
241          _owl_fmtext_wcolor_set(w, pair);
242         
243          waddnstr(w, s + start, end - start);
244
245          _owl_fmtext_wattrset(w, attr);
246          _owl_fmtext_wcolor_set(w, pair);
247
248          s += end;
249        }
250      }
251      /* Deal with remaining part of string. */
252      waddstr(w, s);
253      p[0] = tmp;
254
255      /* Deal with new attributes. Process all consecutive formatting
256       * characters, and then apply defaults where relevant. */
257      while (owl_fmtext_is_format_char(g_utf8_get_char(p))) {
258        _owl_fmtext_update_attributes(g_utf8_get_char(p), &attr, &fg, &bg);
259        p = g_utf8_next_char(p);
260      }
261      attr |= default_attrs;
262      if (fg == OWL_COLOR_DEFAULT) fg = default_fgcolor;
263      if (bg == OWL_COLOR_DEFAULT) bg = default_bgcolor;
264      _owl_fmtext_wattrset(w, attr);
265      pair = owl_fmtext_get_colorpair(fg, bg);
266      _owl_fmtext_wcolor_set(w, pair);
267
268      /* Advance to next non-formatting character. */
269      s = p;
270      p = strchr(s, OWL_FMTEXT_UC_STARTBYTE_UTF8);
271    }
272    else {
273      p = strchr(p+1, OWL_FMTEXT_UC_STARTBYTE_UTF8);
274    }
275  }
276  if (s) {
277    waddstr(w, s);
278  }
279  wbkgdset(w, 0);
280}
281
282void owl_fmtext_curs_waddstr(const owl_fmtext *f, WINDOW *w, char default_attrs, short default_fgcolor, short default_bgcolor)
283{
284  _owl_fmtext_curs_waddstr(f, w, 1, default_attrs, default_fgcolor, default_bgcolor);
285}
286
287void owl_fmtext_curs_waddstr_without_search(const owl_fmtext *f, WINDOW *w, char default_attrs, short default_fgcolor, short default_bgcolor)
288{
289  _owl_fmtext_curs_waddstr(f, w, 0, default_attrs, default_fgcolor, default_bgcolor);
290}
291
292/* Expands tabs. Tabs are expanded as if given an initial indent of start. */
293void owl_fmtext_expand_tabs(const owl_fmtext *in, owl_fmtext *out, int start) {
294  int col = start, numcopied = 0;
295  char *ptr;
296
297  for (ptr = in->buff->str;
298       ptr < in->buff->str + in->buff->len;
299       ptr = g_utf8_next_char(ptr)) {
300    gunichar c = g_utf8_get_char(ptr);
301    int chwidth;
302    if (c == '\t') {
303      /* Copy up to this tab */
304      _owl_fmtext_append_fmtext(out, in, numcopied, ptr - in->buff->str);
305      /* and then copy spaces for the tab. */
306      chwidth = OWL_TAB_WIDTH - (col % OWL_TAB_WIDTH);
307      owl_fmtext_append_spaces(out, chwidth);
308      col += chwidth;
309      numcopied = g_utf8_next_char(ptr) - in->buff->str;
310    } else {
311      /* Just update col. We'll append later. */
312      if (c == '\n') {
313        col = start;
314      } else if (!owl_fmtext_is_format_char(c)) {
315        col += mk_wcwidth(c);
316      }
317    }
318  }
319  /* Append anything we've missed. */
320  if (numcopied < in->buff->len)
321    _owl_fmtext_append_fmtext(out, in, numcopied, in->buff->len);
322}
323
324/* start with line 'aline' (where the first line is 0) and print
325 * 'lines' number of lines into 'out'
326 */
327int owl_fmtext_truncate_lines(const owl_fmtext *in, int aline, int lines, owl_fmtext *out)
328{
329  const char *ptr1, *ptr2;
330  int i, offset;
331 
332  /* find the starting line */
333  ptr1 = in->buff->str;
334  for (i = 0; i < aline; i++) {
335    ptr1 = strchr(ptr1, '\n');
336    if (!ptr1) return(-1);
337    ptr1++;
338  }
339 
340  /* ptr1 now holds the starting point */
341
342  /* copy in the next 'lines' lines */
343  if (lines < 1) return(-1);
344
345  for (i = 0; i < lines; i++) {
346    offset = ptr1 - in->buff->str;
347    ptr2 = strchr(ptr1, '\n');
348    if (!ptr2) {
349      /* Copy to the end of the buffer. */
350      _owl_fmtext_append_fmtext(out, in, offset, in->buff->len);
351      return(-1);
352    }
353    /* Copy up to, and including, the new line. */
354    _owl_fmtext_append_fmtext(out, in, offset, (ptr2 - ptr1) + offset + 1);
355    ptr1 = ptr2 + 1;
356  }
357  return(0);
358}
359
360/* Implementation of owl_fmtext_truncate_cols. Does not support tabs in input. */
361void _owl_fmtext_truncate_cols_internal(const owl_fmtext *in, int acol, int bcol, owl_fmtext *out)
362{
363  const char *ptr_s, *ptr_e, *ptr_c, *last;
364  int col, st, padding, chwidth;
365
366  last = in->buff->str + in->buff->len - 1;
367  ptr_s = in->buff->str;
368  while (ptr_s <= last) {
369    ptr_e=strchr(ptr_s, '\n');
370    if (!ptr_e) {
371      /* but this shouldn't happen if we end in a \n */
372      break;
373    }
374   
375    if (ptr_e == ptr_s) {
376      owl_fmtext_append_normal(out, "\n");
377      ++ptr_s;
378      continue;
379    }
380
381    col = 0;
382    st = 0;
383    padding = 0;
384    chwidth = 0;
385    ptr_c = ptr_s;
386    while(ptr_c < ptr_e) {
387      gunichar c = g_utf8_get_char(ptr_c);
388      if (!owl_fmtext_is_format_char(c)) {
389        chwidth = mk_wcwidth(c);
390        if (col + chwidth > bcol) break;
391       
392        if (col >= acol) {
393          if (st == 0) {
394            ptr_s = ptr_c;
395            padding = col - acol;
396            ++st;
397          }
398        }
399        col += chwidth;
400        chwidth = 0;
401      }
402      ptr_c = g_utf8_next_char(ptr_c);
403    }
404    if (st) {
405      /* lead padding */
406      owl_fmtext_append_spaces(out, padding);
407      if (ptr_c == ptr_e) {
408        /* We made it to the newline. Append up to, and including it. */
409        _owl_fmtext_append_fmtext(out, in, ptr_s - in->buff->str, ptr_c - in->buff->str + 1);
410      }
411      else if (chwidth > 1) {
412        /* Last char is wide, truncate. */
413        _owl_fmtext_append_fmtext(out, in, ptr_s - in->buff->str, ptr_c - in->buff->str);
414        owl_fmtext_append_normal(out, "\n");
415      }
416      else {
417        /* Last char fits perfectly, We stop at the next char to make
418         * sure we get it all. */
419        ptr_c = g_utf8_next_char(ptr_c);
420        _owl_fmtext_append_fmtext(out, in, ptr_s - in->buff->str, ptr_c - in->buff->str);
421      }
422    }
423    else {
424      owl_fmtext_append_normal(out, "\n");
425    }
426    ptr_s = g_utf8_next_char(ptr_e);
427  }
428}
429
430/* Truncate the message so that each line begins at column 'acol' and
431 * ends at 'bcol' or sooner.  The first column is number 0.  The new
432 * message is placed in 'out'.  The message is expected to end in a
433 * new line for now.
434 *
435 * NOTE: This needs to be modified to deal with backing up if we find
436 * a SPACING COMBINING MARK at the end of a line. If that happens, we
437 * should back up to the last non-mark character and stop there.
438 *
439 * NOTE: If a line ends at bcol, we omit the newline. This is so printing
440 * to ncurses works.
441 */
442void owl_fmtext_truncate_cols(const owl_fmtext *in, int acol, int bcol, owl_fmtext *out)
443{
444  owl_fmtext notabs;
445
446  /* _owl_fmtext_truncate_cols_internal cannot handle tabs. */
447  if (strchr(in->buff->str, '\t')) {
448    owl_fmtext_init_null(&notabs);
449    owl_fmtext_expand_tabs(in, &notabs, 0);
450    _owl_fmtext_truncate_cols_internal(&notabs, acol, bcol, out);
451    owl_fmtext_cleanup(&notabs);
452  } else {
453    _owl_fmtext_truncate_cols_internal(in, acol, bcol, out);
454  }
455}
456
457/* Return the number of lines in 'f' */
458int owl_fmtext_num_lines(const owl_fmtext *f)
459{
460  int lines, i;
461  char *lastbreak, *p;
462
463  lines=0;
464  lastbreak = f->buff->str;
465  for (i = 0; i < f->buff->len; i++) {
466    if (f->buff->str[i]=='\n') {
467      lastbreak = f->buff->str + i;
468      lines++;
469    }
470  }
471
472  /* Check if there's a trailing line; formatting characters don't count. */
473  for (p = g_utf8_next_char(lastbreak);
474       p < f->buff->str + f->buff->len;
475       p = g_utf8_next_char(p)) {
476    if (!owl_fmtext_is_format_char(g_utf8_get_char(p))) {
477      lines++;
478      break;
479    }
480  }
481
482  return(lines);
483}
484
485/* Returns the line number, starting at 0, of the character which
486 * contains the byte at 'offset'. Note that a trailing newline is part
487 * of the line it ends. Also, while a trailing line of formatting
488 * characters does not contribute to owl_fmtext_num_lines, those
489 * characters are considered on a new line. */
490int owl_fmtext_line_number(const owl_fmtext *f, int offset)
491{
492  int i, lineno = 0;
493  if (offset >= f->buff->len)
494    offset = f->buff->len - 1;
495  for (i = 0; i < offset; i++) {
496    if (f->buff->str[i] == '\n')
497      lineno++;
498  }
499  return lineno;
500}
501
502/* Searches for line 'lineno' in 'f'. The returned range, [start,
503 * end), forms a half-open interval for the extent of the line. */
504void owl_fmtext_line_extents(const owl_fmtext *f, int lineno, int *o_start, int *o_end)
505{
506  int start, end;
507  char *newline;
508  for (start = 0; lineno > 0 && start < f->buff->len; start++) {
509    if (f->buff->str[start] == '\n')
510      lineno--;
511  }
512  newline = strchr(f->buff->str + start, '\n');
513  /* Include the newline, if it is there. */
514  end = newline ? newline - f->buff->str + 1 : f->buff->len;
515  if (o_start) *o_start = start;
516  if (o_end) *o_end = end;
517}
518
519const char *owl_fmtext_get_text(const owl_fmtext *f)
520{
521  return f->buff->str;
522}
523
524int owl_fmtext_num_bytes(const owl_fmtext *f)
525{
526  return f->buff->len;
527}
528
529/* Make a copy of the fmtext 'src' into 'dst' */
530void owl_fmtext_copy(owl_fmtext *dst, const owl_fmtext *src)
531{
532  dst->buff = g_string_new(src->buff->str);
533}
534
535/* Search 'f' for the regex 're' for matches starting at
536 * 'start'. Returns the offset of the first match, -1 if not
537 * found. This is a case-insensitive search.
538 */
539int owl_fmtext_search(const owl_fmtext *f, const owl_regex *re, int start)
540{
541  int offset;
542  if (start > f->buff->len ||
543      owl_regex_compare(re, f->buff->str + start, &offset, NULL) != 0)
544    return -1;
545  return offset + start;
546}
547
548
549/* Append the text 'text' to 'f' and interpret the zephyr style
550 * formatting syntax to set appropriate attributes.
551 */
552void owl_fmtext_append_ztext(owl_fmtext *f, const char *text)
553{
554  int stacksize, curattrs, curcolor;
555  const char *ptr, *txtptr, *tmpptr;
556  char *buff;
557  int attrstack[32], chrstack[32], colorstack[32];
558
559  curattrs=OWL_FMTEXT_ATTR_NONE;
560  curcolor=OWL_COLOR_DEFAULT;
561  stacksize=0;
562  txtptr=text;
563  while (1) {
564    ptr=strpbrk(txtptr, "@{[<()>]}");
565    if (!ptr) {
566      /* add all the rest of the text and exit */
567      owl_fmtext_append_attr(f, txtptr, curattrs, curcolor, OWL_COLOR_DEFAULT);
568      return;
569    } else if (ptr[0]=='@') {
570      /* add the text up to this point then deal with the stack */
571      buff=g_new(char, ptr-txtptr+20);
572      strncpy(buff, txtptr, ptr-txtptr);
573      buff[ptr-txtptr]='\0';
574      owl_fmtext_append_attr(f, buff, curattrs, curcolor, OWL_COLOR_DEFAULT);
575      g_free(buff);
576
577      /* update pointer to point at the @ */
578      txtptr=ptr;
579
580      /* now the stack */
581
582      /* if we've hit our max stack depth, print the @ and move on */
583      if (stacksize==32) {
584        owl_fmtext_append_attr(f, "@", curattrs, curcolor, OWL_COLOR_DEFAULT);
585        txtptr++;
586        continue;
587      }
588
589      /* if it's an @@, print an @ and continue */
590      if (txtptr[1]=='@') {
591        owl_fmtext_append_attr(f, "@", curattrs, curcolor, OWL_COLOR_DEFAULT);
592        txtptr+=2;
593        continue;
594      }
595       
596      /* if there's no opener, print the @ and continue */
597      tmpptr=strpbrk(txtptr, "(<[{ ");
598      if (!tmpptr || tmpptr[0]==' ') {
599        owl_fmtext_append_attr(f, "@", curattrs, curcolor, OWL_COLOR_DEFAULT);
600        txtptr++;
601        continue;
602      }
603
604      /* check what command we've got, push it on the stack, start
605         using it, and continue ... unless it's a color command */
606      buff=g_new(char, tmpptr-ptr+20);
607      strncpy(buff, ptr, tmpptr-ptr);
608      buff[tmpptr-ptr]='\0';
609      if (!strcasecmp(buff, "@bold")) {
610        attrstack[stacksize]=OWL_FMTEXT_ATTR_BOLD;
611        chrstack[stacksize]=tmpptr[0];
612        colorstack[stacksize]=curcolor;
613        stacksize++;
614        curattrs|=OWL_FMTEXT_ATTR_BOLD;
615        txtptr+=6;
616        g_free(buff);
617        continue;
618      } else if (!strcasecmp(buff, "@b")) {
619        attrstack[stacksize]=OWL_FMTEXT_ATTR_BOLD;
620        chrstack[stacksize]=tmpptr[0];
621        colorstack[stacksize]=curcolor;
622        stacksize++;
623        curattrs|=OWL_FMTEXT_ATTR_BOLD;
624        txtptr+=3;
625        g_free(buff);
626        continue;
627      } else if (!strcasecmp(buff, "@i")) {
628        attrstack[stacksize]=OWL_FMTEXT_ATTR_UNDERLINE;
629        chrstack[stacksize]=tmpptr[0];
630        colorstack[stacksize]=curcolor;
631        stacksize++;
632        curattrs|=OWL_FMTEXT_ATTR_UNDERLINE;
633        txtptr+=3;
634        g_free(buff);
635        continue;
636      } else if (!strcasecmp(buff, "@italic")) {
637        attrstack[stacksize]=OWL_FMTEXT_ATTR_UNDERLINE;
638        chrstack[stacksize]=tmpptr[0];
639        colorstack[stacksize]=curcolor;
640        stacksize++;
641        curattrs|=OWL_FMTEXT_ATTR_UNDERLINE;
642        txtptr+=8;
643        g_free(buff);
644        continue;
645      } else if (!strcasecmp(buff, "@")) {
646        attrstack[stacksize]=OWL_FMTEXT_ATTR_NONE;
647        chrstack[stacksize]=tmpptr[0];
648        colorstack[stacksize]=curcolor;
649        stacksize++;
650        txtptr+=2;
651        g_free(buff);
652        continue;
653
654        /* if it's a color read the color, set the current color and
655           continue */
656      } else if (!strcasecmp(buff, "@color") 
657                 && owl_global_is_colorztext(&g)) {
658        g_free(buff);
659        txtptr+=7;
660        tmpptr=strpbrk(txtptr, "@{[<()>]}");
661        if (tmpptr &&
662            ((txtptr[-1]=='(' && tmpptr[0]==')') ||
663             (txtptr[-1]=='<' && tmpptr[0]=='>') ||
664             (txtptr[-1]=='[' && tmpptr[0]==']') ||
665             (txtptr[-1]=='{' && tmpptr[0]=='}'))) {
666
667          /* grab the color name */
668          buff=g_new(char, tmpptr-txtptr+20);
669          strncpy(buff, txtptr, tmpptr-txtptr);
670          buff[tmpptr-txtptr]='\0';
671
672          /* set it as the current color */
673          curcolor=owl_util_string_to_color(buff);
674          if (curcolor == OWL_COLOR_INVALID)
675              curcolor = OWL_COLOR_DEFAULT;
676          g_free(buff);
677          txtptr=tmpptr+1;
678          continue;
679
680        } else {
681
682        }
683
684      } else {
685        /* if we didn't understand it, we'll print it.  This is different from zwgc
686         * but zwgc seems to be smarter about some screw cases than I am
687         */
688        owl_fmtext_append_attr(f, "@", curattrs, curcolor, OWL_COLOR_DEFAULT);
689        txtptr++;
690        continue;
691      }
692
693    } else if (ptr[0]=='}' || ptr[0]==']' || ptr[0]==')' || ptr[0]=='>') {
694      /* add the text up to this point first */
695      buff=g_new(char, ptr-txtptr+20);
696      strncpy(buff, txtptr, ptr-txtptr);
697      buff[ptr-txtptr]='\0';
698      owl_fmtext_append_attr(f, buff, curattrs, curcolor, OWL_COLOR_DEFAULT);
699      g_free(buff);
700
701      /* now deal with the closer */
702      txtptr=ptr;
703
704      /* first, if the stack is empty we must bail (just print and go) */
705      if (stacksize==0) {
706        buff=g_new(char, 5);
707        buff[0]=ptr[0];
708        buff[1]='\0';
709        owl_fmtext_append_attr(f, buff, curattrs, curcolor, OWL_COLOR_DEFAULT);
710        g_free(buff);
711        txtptr++;
712        continue;
713      }
714
715      /* if the closing char is what's on the stack, turn off the
716         attribue and pop the stack */
717      if ((ptr[0]==')' && chrstack[stacksize-1]=='(') ||
718          (ptr[0]=='>' && chrstack[stacksize-1]=='<') ||
719          (ptr[0]==']' && chrstack[stacksize-1]=='[') ||
720          (ptr[0]=='}' && chrstack[stacksize-1]=='{')) {
721        int i;
722        stacksize--;
723        curattrs=OWL_FMTEXT_ATTR_NONE;
724        curcolor = colorstack[stacksize];
725        for (i=0; i<stacksize; i++) {
726          curattrs|=attrstack[i];
727        }
728        txtptr+=1;
729        continue;
730      } else {
731        /* otherwise print and continue */
732        buff=g_new(char, 5);
733        buff[0]=ptr[0];
734        buff[1]='\0';
735        owl_fmtext_append_attr(f, buff, curattrs, curcolor, OWL_COLOR_DEFAULT);
736        g_free(buff);
737        txtptr++;
738        continue;
739      }
740    } else {
741      /* we've found an unattached opener, print everything and move on */
742      buff=g_new(char, ptr-txtptr+20);
743      strncpy(buff, txtptr, ptr-txtptr+1);
744      buff[ptr-txtptr+1]='\0';
745      owl_fmtext_append_attr(f, buff, curattrs, curcolor, OWL_COLOR_DEFAULT);
746      g_free(buff);
747      txtptr=ptr+1;
748      continue;
749    }
750  }
751}
752
753/* requires that the list values are strings or NULL.
754 * joins the elements together with join_with.
755 * If format_fn is specified, passes it the list element value
756 * and it will return a string which this needs to free. */
757void owl_fmtext_append_list(owl_fmtext *f, const owl_list *l, const char *join_with, char *(format_fn)(const char *))
758{
759  int i, size;
760  const char *elem;
761  char *text;
762
763  size = owl_list_get_size(l);
764  for (i=0; i<size; i++) {
765    elem = owl_list_get_element(l,i);
766    if (elem && format_fn) {
767      text = format_fn(elem);
768      if (text) {
769        owl_fmtext_append_normal(f, text);
770        g_free(text);
771      }
772    } else if (elem) {
773      owl_fmtext_append_normal(f, elem);
774    }
775    if ((i < size-1) && join_with) {
776      owl_fmtext_append_normal(f, join_with);
777    }
778  }
779}
780
781/* Free all memory allocated by the object */
782void owl_fmtext_cleanup(owl_fmtext *f)
783{
784  if (f->buff) g_string_free(f->buff, true);
785  f->buff = NULL;
786}
787
788/*** Color Pair manager ***/
789void owl_fmtext_init_colorpair_mgr(owl_colorpair_mgr *cpmgr)
790{
791  /* This could be a bitarray if we wanted to save memory. */
792  short i;
793  /* The test is <= because we allocate COLORS+1 entries. */
794  cpmgr->pairs = g_new(short *, COLORS + 1);
795  for(i = 0; i <= COLORS; i++) {
796    cpmgr->pairs[i] = g_new(short, COLORS + 1);
797  }
798  owl_fmtext_reset_colorpairs(cpmgr);
799}
800
801/* Reset used list */
802void owl_fmtext_reset_colorpairs(owl_colorpair_mgr *cpmgr)
803{
804  short i, j;
805
806  cpmgr->overflow = false;
807  cpmgr->next = 8;
808  /* The test is <= because we allocated COLORS+1 entries. */
809  for(i = 0; i <= COLORS; i++) {
810    for(j = 0; j <= COLORS; j++) {
811      cpmgr->pairs[i][j] = -1;
812    }
813  }
814  if (has_colors()) {
815    for(i = 0; i < 8; i++) {
816      short fg, bg;
817      if (i >= COLORS) continue;
818      pair_content(i, &fg, &bg);
819      cpmgr->pairs[fg+1][bg+1] = i;
820    }
821  }
822}
823
824/* Assign pairs by request */
825short owl_fmtext_get_colorpair(int fg, int bg)
826{
827  owl_colorpair_mgr *cpmgr;
828  short pair;
829
830  if (!has_colors())
831    return 0;
832
833  /* Sanity (Bounds) Check */
834  if (fg > COLORS || fg < OWL_COLOR_DEFAULT) fg = OWL_COLOR_DEFAULT;
835  if (bg > COLORS || bg < OWL_COLOR_DEFAULT) bg = OWL_COLOR_DEFAULT;
836           
837#ifdef HAVE_USE_DEFAULT_COLORS
838  if (fg == OWL_COLOR_DEFAULT) fg = -1;
839#else
840  if (fg == OWL_COLOR_DEFAULT) fg = 0;
841  if (bg == OWL_COLOR_DEFAULT) bg = 0;
842#endif
843
844  /* looking for a pair we already set up for this draw. */
845  cpmgr = owl_global_get_colorpair_mgr(&g);
846  pair = cpmgr->pairs[fg+1][bg+1];
847  if (!(pair != -1 && pair < cpmgr->next)) {
848    /* If we didn't find a pair, search for a free one to assign. */
849    pair = (cpmgr->next < owl_util_get_colorpairs()) ? cpmgr->next : -1;
850    if (pair != -1) {
851      /* We found a free pair, initialize it. */
852      init_pair(pair, fg, bg);
853      cpmgr->pairs[fg+1][bg+1] = pair;
854      cpmgr->next++;
855    }
856    else if (bg != OWL_COLOR_DEFAULT) {
857      /* We still don't have a pair, drop the background color. Too bad. */
858      owl_function_debugmsg("colorpairs: color shortage - dropping background color.");
859      cpmgr->overflow = true;
860      pair = owl_fmtext_get_colorpair(fg, OWL_COLOR_DEFAULT);
861    }
862    else {
863      /* We still don't have a pair, defaults all around. */
864      owl_function_debugmsg("colorpairs: color shortage - dropping foreground and background color.");
865      cpmgr->overflow = true;
866      pair = 0;
867    }
868  }
869  return pair;
870}
Note: See TracBrowser for help on using the repository browser.