source: fmtext.c @ 40de7394

release-1.10release-1.9
Last change on this file since 40de7394 was 7abfcf2, checked in by David Benjamin <davidben@mit.edu>, 13 years ago
Use wbkgrndset instead of wbkgdset in _owl_fmtext_wcolor_set This allows color pairs past 255 in an ext-color-enabled build to work. In theory anyway. Turns out ncurses is buggy and setcchar doesn't work, but when the patch is accepted upstream we can update the ncurses in the locker.
  • Property mode set to 100644
File size: 26.1 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 */
173CALLER_OWN char *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  cchar_t background;
189  wchar_t blank[2] = { ' ', 0 };
190  if (has_colors()) {
191      wcolor_set(w, pair, NULL);
192      /* Set the background with wbkgrndset so that we can handle color-pairs
193       * past 256 on ncurses ABI 6 and later. */
194      setcchar(&background, blank, 0, pair, NULL);
195      wbkgrndset(w, &background);
196  }
197}
198
199/* add the formatted text to the curses window 'w'.  The window 'w'
200 * must already be initiatlized with curses
201 */
202static void _owl_fmtext_curs_waddstr(const owl_fmtext *f, WINDOW *w, int do_search, char default_attrs, short default_fgcolor, short default_bgcolor)
203{
204  /* char *tmpbuff; */
205  /* int position, trans1, trans2, trans3, len, lastsame; */
206  char *s, *p;
207  char attr;
208  short fg, bg, pair = 0;
209 
210  if (w==NULL) {
211    owl_function_debugmsg("Hit a null window in owl_fmtext_curs_waddstr.");
212    return;
213  }
214
215  s = f->buff->str;
216  /* Set default attributes. */
217  attr = default_attrs;
218  fg = default_fgcolor;
219  bg = default_bgcolor;
220  _owl_fmtext_wattrset(w, attr);
221  pair = owl_fmtext_get_colorpair(fg, bg);
222  _owl_fmtext_wcolor_set(w, pair);
223
224  /* Find next possible format character. */
225  p = strchr(s, OWL_FMTEXT_UC_STARTBYTE_UTF8);
226  while(p) {
227    if (owl_fmtext_is_format_char(g_utf8_get_char(p))) {
228      /* Deal with all text from last insert to here. */
229      char tmp;
230   
231      tmp = p[0];
232      p[0] = '\0';
233      if (do_search && owl_global_is_search_active(&g)) {
234        /* Search is active, so highlight search results. */
235        int start, end;
236        while (owl_regex_compare(owl_global_get_search_re(&g), s, &start, &end) == 0) {
237          /* Prevent an infinite loop matching the empty string. */
238          if (end == 0)
239            break;
240
241          /* Found search string, highlight it. */
242
243          waddnstr(w, s, start);
244
245          _owl_fmtext_wattrset(w, attr ^ OWL_FMTEXT_ATTR_REVERSE);
246          _owl_fmtext_wcolor_set(w, pair);
247         
248          waddnstr(w, s + start, end - start);
249
250          _owl_fmtext_wattrset(w, attr);
251          _owl_fmtext_wcolor_set(w, pair);
252
253          s += end;
254        }
255      }
256      /* Deal with remaining part of string. */
257      waddstr(w, s);
258      p[0] = tmp;
259
260      /* Deal with new attributes. Process all consecutive formatting
261       * characters, and then apply defaults where relevant. */
262      while (owl_fmtext_is_format_char(g_utf8_get_char(p))) {
263        _owl_fmtext_update_attributes(g_utf8_get_char(p), &attr, &fg, &bg);
264        p = g_utf8_next_char(p);
265      }
266      attr |= default_attrs;
267      if (fg == OWL_COLOR_DEFAULT) fg = default_fgcolor;
268      if (bg == OWL_COLOR_DEFAULT) bg = default_bgcolor;
269      _owl_fmtext_wattrset(w, attr);
270      pair = owl_fmtext_get_colorpair(fg, bg);
271      _owl_fmtext_wcolor_set(w, pair);
272
273      /* Advance to next non-formatting character. */
274      s = p;
275      p = strchr(s, OWL_FMTEXT_UC_STARTBYTE_UTF8);
276    }
277    else {
278      p = strchr(p+1, OWL_FMTEXT_UC_STARTBYTE_UTF8);
279    }
280  }
281  if (s) {
282    waddstr(w, s);
283  }
284  wbkgdset(w, 0);
285}
286
287void owl_fmtext_curs_waddstr(const owl_fmtext *f, WINDOW *w, char default_attrs, short default_fgcolor, short default_bgcolor)
288{
289  _owl_fmtext_curs_waddstr(f, w, 1, default_attrs, default_fgcolor, default_bgcolor);
290}
291
292void owl_fmtext_curs_waddstr_without_search(const owl_fmtext *f, WINDOW *w, char default_attrs, short default_fgcolor, short default_bgcolor)
293{
294  _owl_fmtext_curs_waddstr(f, w, 0, default_attrs, default_fgcolor, default_bgcolor);
295}
296
297/* Expands tabs. Tabs are expanded as if given an initial indent of start. */
298void owl_fmtext_expand_tabs(const owl_fmtext *in, owl_fmtext *out, int start) {
299  int col = start, numcopied = 0;
300  char *ptr;
301
302  for (ptr = in->buff->str;
303       ptr < in->buff->str + in->buff->len;
304       ptr = g_utf8_next_char(ptr)) {
305    gunichar c = g_utf8_get_char(ptr);
306    int chwidth;
307    if (c == '\t') {
308      /* Copy up to this tab */
309      _owl_fmtext_append_fmtext(out, in, numcopied, ptr - in->buff->str);
310      /* and then copy spaces for the tab. */
311      chwidth = OWL_TAB_WIDTH - (col % OWL_TAB_WIDTH);
312      owl_fmtext_append_spaces(out, chwidth);
313      col += chwidth;
314      numcopied = g_utf8_next_char(ptr) - in->buff->str;
315    } else {
316      /* Just update col. We'll append later. */
317      if (c == '\n') {
318        col = start;
319      } else if (!owl_fmtext_is_format_char(c)) {
320        col += mk_wcwidth(c);
321      }
322    }
323  }
324  /* Append anything we've missed. */
325  if (numcopied < in->buff->len)
326    _owl_fmtext_append_fmtext(out, in, numcopied, in->buff->len);
327}
328
329/* start with line 'aline' (where the first line is 0) and print
330 * 'lines' number of lines into 'out'
331 */
332int owl_fmtext_truncate_lines(const owl_fmtext *in, int aline, int lines, owl_fmtext *out)
333{
334  const char *ptr1, *ptr2;
335  int i, offset;
336 
337  /* find the starting line */
338  ptr1 = in->buff->str;
339  for (i = 0; i < aline; i++) {
340    ptr1 = strchr(ptr1, '\n');
341    if (!ptr1) return(-1);
342    ptr1++;
343  }
344 
345  /* ptr1 now holds the starting point */
346
347  /* copy in the next 'lines' lines */
348  if (lines < 1) return(-1);
349
350  for (i = 0; i < lines; i++) {
351    offset = ptr1 - in->buff->str;
352    ptr2 = strchr(ptr1, '\n');
353    if (!ptr2) {
354      /* Copy to the end of the buffer. */
355      _owl_fmtext_append_fmtext(out, in, offset, in->buff->len);
356      return(-1);
357    }
358    /* Copy up to, and including, the new line. */
359    _owl_fmtext_append_fmtext(out, in, offset, (ptr2 - ptr1) + offset + 1);
360    ptr1 = ptr2 + 1;
361  }
362  return(0);
363}
364
365/* Implementation of owl_fmtext_truncate_cols. Does not support tabs in input. */
366void _owl_fmtext_truncate_cols_internal(const owl_fmtext *in, int acol, int bcol, owl_fmtext *out)
367{
368  const char *ptr_s, *ptr_e, *ptr_c, *last;
369  int col, st, padding, chwidth;
370
371  last = in->buff->str + in->buff->len - 1;
372  ptr_s = in->buff->str;
373  while (ptr_s <= last) {
374    ptr_e=strchr(ptr_s, '\n');
375    if (!ptr_e) {
376      /* but this shouldn't happen if we end in a \n */
377      break;
378    }
379   
380    if (ptr_e == ptr_s) {
381      owl_fmtext_append_normal(out, "\n");
382      ++ptr_s;
383      continue;
384    }
385
386    col = 0;
387    st = 0;
388    padding = 0;
389    chwidth = 0;
390    ptr_c = ptr_s;
391    while(ptr_c < ptr_e) {
392      gunichar c = g_utf8_get_char(ptr_c);
393      if (!owl_fmtext_is_format_char(c)) {
394        chwidth = mk_wcwidth(c);
395        if (col + chwidth > bcol) break;
396       
397        if (col >= acol) {
398          if (st == 0) {
399            ptr_s = ptr_c;
400            padding = col - acol;
401            ++st;
402          }
403        }
404        col += chwidth;
405        chwidth = 0;
406      }
407      ptr_c = g_utf8_next_char(ptr_c);
408    }
409    if (st) {
410      /* lead padding */
411      owl_fmtext_append_spaces(out, padding);
412      if (ptr_c == ptr_e) {
413        /* We made it to the newline. Append up to, and including it. */
414        _owl_fmtext_append_fmtext(out, in, ptr_s - in->buff->str, ptr_c - in->buff->str + 1);
415      }
416      else if (chwidth > 1) {
417        /* Last char is wide, truncate. */
418        _owl_fmtext_append_fmtext(out, in, ptr_s - in->buff->str, ptr_c - in->buff->str);
419        owl_fmtext_append_normal(out, "\n");
420      }
421      else {
422        /* Last char fits perfectly, We stop at the next char to make
423         * sure we get it all. */
424        ptr_c = g_utf8_next_char(ptr_c);
425        _owl_fmtext_append_fmtext(out, in, ptr_s - in->buff->str, ptr_c - in->buff->str);
426      }
427    }
428    else {
429      owl_fmtext_append_normal(out, "\n");
430    }
431    ptr_s = g_utf8_next_char(ptr_e);
432  }
433}
434
435/* Truncate the message so that each line begins at column 'acol' and
436 * ends at 'bcol' or sooner.  The first column is number 0.  The new
437 * message is placed in 'out'.  The message is expected to end in a
438 * new line for now.
439 *
440 * NOTE: This needs to be modified to deal with backing up if we find
441 * a SPACING COMBINING MARK at the end of a line. If that happens, we
442 * should back up to the last non-mark character and stop there.
443 *
444 * NOTE: If a line ends at bcol, we omit the newline. This is so printing
445 * to ncurses works.
446 */
447void owl_fmtext_truncate_cols(const owl_fmtext *in, int acol, int bcol, owl_fmtext *out)
448{
449  owl_fmtext notabs;
450
451  /* _owl_fmtext_truncate_cols_internal cannot handle tabs. */
452  if (strchr(in->buff->str, '\t')) {
453    owl_fmtext_init_null(&notabs);
454    owl_fmtext_expand_tabs(in, &notabs, 0);
455    _owl_fmtext_truncate_cols_internal(&notabs, acol, bcol, out);
456    owl_fmtext_cleanup(&notabs);
457  } else {
458    _owl_fmtext_truncate_cols_internal(in, acol, bcol, out);
459  }
460}
461
462/* Return the number of lines in 'f' */
463int owl_fmtext_num_lines(const owl_fmtext *f)
464{
465  int lines, i;
466  char *lastbreak, *p;
467
468  lines=0;
469  lastbreak = f->buff->str;
470  for (i = 0; i < f->buff->len; i++) {
471    if (f->buff->str[i]=='\n') {
472      lastbreak = f->buff->str + i;
473      lines++;
474    }
475  }
476
477  /* Check if there's a trailing line; formatting characters don't count. */
478  for (p = g_utf8_next_char(lastbreak);
479       p < f->buff->str + f->buff->len;
480       p = g_utf8_next_char(p)) {
481    if (!owl_fmtext_is_format_char(g_utf8_get_char(p))) {
482      lines++;
483      break;
484    }
485  }
486
487  return(lines);
488}
489
490/* Returns the line number, starting at 0, of the character which
491 * contains the byte at 'offset'. Note that a trailing newline is part
492 * of the line it ends. Also, while a trailing line of formatting
493 * characters does not contribute to owl_fmtext_num_lines, those
494 * characters are considered on a new line. */
495int owl_fmtext_line_number(const owl_fmtext *f, int offset)
496{
497  int i, lineno = 0;
498  if (offset >= f->buff->len)
499    offset = f->buff->len - 1;
500  for (i = 0; i < offset; i++) {
501    if (f->buff->str[i] == '\n')
502      lineno++;
503  }
504  return lineno;
505}
506
507/* Searches for line 'lineno' in 'f'. The returned range, [start,
508 * end), forms a half-open interval for the extent of the line. */
509void owl_fmtext_line_extents(const owl_fmtext *f, int lineno, int *o_start, int *o_end)
510{
511  int start, end;
512  char *newline;
513  for (start = 0; lineno > 0 && start < f->buff->len; start++) {
514    if (f->buff->str[start] == '\n')
515      lineno--;
516  }
517  newline = strchr(f->buff->str + start, '\n');
518  /* Include the newline, if it is there. */
519  end = newline ? newline - f->buff->str + 1 : f->buff->len;
520  if (o_start) *o_start = start;
521  if (o_end) *o_end = end;
522}
523
524const char *owl_fmtext_get_text(const owl_fmtext *f)
525{
526  return f->buff->str;
527}
528
529int owl_fmtext_num_bytes(const owl_fmtext *f)
530{
531  return f->buff->len;
532}
533
534/* Make a copy of the fmtext 'src' into 'dst' */
535void owl_fmtext_copy(owl_fmtext *dst, const owl_fmtext *src)
536{
537  dst->buff = g_string_new(src->buff->str);
538}
539
540/* Search 'f' for the regex 're' for matches starting at
541 * 'start'. Returns the offset of the first match, -1 if not
542 * found. This is a case-insensitive search.
543 */
544int owl_fmtext_search(const owl_fmtext *f, const owl_regex *re, int start)
545{
546  int offset;
547  if (start > f->buff->len ||
548      owl_regex_compare(re, f->buff->str + start, &offset, NULL) != 0)
549    return -1;
550  return offset + start;
551}
552
553
554/* Append the text 'text' to 'f' and interpret the zephyr style
555 * formatting syntax to set appropriate attributes.
556 */
557void owl_fmtext_append_ztext(owl_fmtext *f, const char *text)
558{
559  int stacksize, curattrs, curcolor;
560  const char *ptr, *txtptr, *tmpptr;
561  char *buff;
562  int attrstack[32], chrstack[32], colorstack[32];
563
564  curattrs=OWL_FMTEXT_ATTR_NONE;
565  curcolor=OWL_COLOR_DEFAULT;
566  stacksize=0;
567  txtptr=text;
568  while (1) {
569    ptr=strpbrk(txtptr, "@{[<()>]}");
570    if (!ptr) {
571      /* add all the rest of the text and exit */
572      owl_fmtext_append_attr(f, txtptr, curattrs, curcolor, OWL_COLOR_DEFAULT);
573      return;
574    } else if (ptr[0]=='@') {
575      /* add the text up to this point then deal with the stack */
576      buff=g_new(char, ptr-txtptr+20);
577      strncpy(buff, txtptr, ptr-txtptr);
578      buff[ptr-txtptr]='\0';
579      owl_fmtext_append_attr(f, buff, curattrs, curcolor, OWL_COLOR_DEFAULT);
580      g_free(buff);
581
582      /* update pointer to point at the @ */
583      txtptr=ptr;
584
585      /* now the stack */
586
587      /* if we've hit our max stack depth, print the @ and move on */
588      if (stacksize==32) {
589        owl_fmtext_append_attr(f, "@", curattrs, curcolor, OWL_COLOR_DEFAULT);
590        txtptr++;
591        continue;
592      }
593
594      /* if it's an @@, print an @ and continue */
595      if (txtptr[1]=='@') {
596        owl_fmtext_append_attr(f, "@", curattrs, curcolor, OWL_COLOR_DEFAULT);
597        txtptr+=2;
598        continue;
599      }
600       
601      /* if there's no opener, print the @ and continue */
602      tmpptr=strpbrk(txtptr, "(<[{ ");
603      if (!tmpptr || tmpptr[0]==' ') {
604        owl_fmtext_append_attr(f, "@", curattrs, curcolor, OWL_COLOR_DEFAULT);
605        txtptr++;
606        continue;
607      }
608
609      /* check what command we've got, push it on the stack, start
610         using it, and continue ... unless it's a color command */
611      buff=g_new(char, tmpptr-ptr+20);
612      strncpy(buff, ptr, tmpptr-ptr);
613      buff[tmpptr-ptr]='\0';
614      if (!strcasecmp(buff, "@bold")) {
615        attrstack[stacksize]=OWL_FMTEXT_ATTR_BOLD;
616        chrstack[stacksize]=tmpptr[0];
617        colorstack[stacksize]=curcolor;
618        stacksize++;
619        curattrs|=OWL_FMTEXT_ATTR_BOLD;
620        txtptr+=6;
621        g_free(buff);
622        continue;
623      } else if (!strcasecmp(buff, "@b")) {
624        attrstack[stacksize]=OWL_FMTEXT_ATTR_BOLD;
625        chrstack[stacksize]=tmpptr[0];
626        colorstack[stacksize]=curcolor;
627        stacksize++;
628        curattrs|=OWL_FMTEXT_ATTR_BOLD;
629        txtptr+=3;
630        g_free(buff);
631        continue;
632      } else if (!strcasecmp(buff, "@i")) {
633        attrstack[stacksize]=OWL_FMTEXT_ATTR_UNDERLINE;
634        chrstack[stacksize]=tmpptr[0];
635        colorstack[stacksize]=curcolor;
636        stacksize++;
637        curattrs|=OWL_FMTEXT_ATTR_UNDERLINE;
638        txtptr+=3;
639        g_free(buff);
640        continue;
641      } else if (!strcasecmp(buff, "@italic")) {
642        attrstack[stacksize]=OWL_FMTEXT_ATTR_UNDERLINE;
643        chrstack[stacksize]=tmpptr[0];
644        colorstack[stacksize]=curcolor;
645        stacksize++;
646        curattrs|=OWL_FMTEXT_ATTR_UNDERLINE;
647        txtptr+=8;
648        g_free(buff);
649        continue;
650      } else if (!strcasecmp(buff, "@")) {
651        attrstack[stacksize]=OWL_FMTEXT_ATTR_NONE;
652        chrstack[stacksize]=tmpptr[0];
653        colorstack[stacksize]=curcolor;
654        stacksize++;
655        txtptr+=2;
656        g_free(buff);
657        continue;
658
659        /* if it's a color read the color, set the current color and
660           continue */
661      } else if (!strcasecmp(buff, "@color") 
662                 && owl_global_is_colorztext(&g)) {
663        g_free(buff);
664        txtptr+=7;
665        tmpptr=strpbrk(txtptr, "@{[<()>]}");
666        if (tmpptr &&
667            ((txtptr[-1]=='(' && tmpptr[0]==')') ||
668             (txtptr[-1]=='<' && tmpptr[0]=='>') ||
669             (txtptr[-1]=='[' && tmpptr[0]==']') ||
670             (txtptr[-1]=='{' && tmpptr[0]=='}'))) {
671
672          /* grab the color name */
673          buff=g_new(char, tmpptr-txtptr+20);
674          strncpy(buff, txtptr, tmpptr-txtptr);
675          buff[tmpptr-txtptr]='\0';
676
677          /* set it as the current color */
678          curcolor=owl_util_string_to_color(buff);
679          if (curcolor == OWL_COLOR_INVALID)
680              curcolor = OWL_COLOR_DEFAULT;
681          g_free(buff);
682          txtptr=tmpptr+1;
683          continue;
684
685        } else {
686
687        }
688
689      } else {
690        /* if we didn't understand it, we'll print it.  This is different from zwgc
691         * but zwgc seems to be smarter about some screw cases than I am
692         */
693        g_free(buff);
694        owl_fmtext_append_attr(f, "@", curattrs, curcolor, OWL_COLOR_DEFAULT);
695        txtptr++;
696        continue;
697      }
698
699    } else if (ptr[0]=='}' || ptr[0]==']' || ptr[0]==')' || ptr[0]=='>') {
700      /* add the text up to this point first */
701      buff=g_new(char, ptr-txtptr+20);
702      strncpy(buff, txtptr, ptr-txtptr);
703      buff[ptr-txtptr]='\0';
704      owl_fmtext_append_attr(f, buff, curattrs, curcolor, OWL_COLOR_DEFAULT);
705      g_free(buff);
706
707      /* now deal with the closer */
708      txtptr=ptr;
709
710      /* first, if the stack is empty we must bail (just print and go) */
711      if (stacksize==0) {
712        buff=g_new(char, 5);
713        buff[0]=ptr[0];
714        buff[1]='\0';
715        owl_fmtext_append_attr(f, buff, curattrs, curcolor, OWL_COLOR_DEFAULT);
716        g_free(buff);
717        txtptr++;
718        continue;
719      }
720
721      /* if the closing char is what's on the stack, turn off the
722         attribue and pop the stack */
723      if ((ptr[0]==')' && chrstack[stacksize-1]=='(') ||
724          (ptr[0]=='>' && chrstack[stacksize-1]=='<') ||
725          (ptr[0]==']' && chrstack[stacksize-1]=='[') ||
726          (ptr[0]=='}' && chrstack[stacksize-1]=='{')) {
727        int i;
728        stacksize--;
729        curattrs=OWL_FMTEXT_ATTR_NONE;
730        curcolor = colorstack[stacksize];
731        for (i=0; i<stacksize; i++) {
732          curattrs|=attrstack[i];
733        }
734        txtptr+=1;
735        continue;
736      } else {
737        /* otherwise print and continue */
738        buff=g_new(char, 5);
739        buff[0]=ptr[0];
740        buff[1]='\0';
741        owl_fmtext_append_attr(f, buff, curattrs, curcolor, OWL_COLOR_DEFAULT);
742        g_free(buff);
743        txtptr++;
744        continue;
745      }
746    } else {
747      /* we've found an unattached opener, print everything and move on */
748      buff=g_new(char, ptr-txtptr+20);
749      strncpy(buff, txtptr, ptr-txtptr+1);
750      buff[ptr-txtptr+1]='\0';
751      owl_fmtext_append_attr(f, buff, curattrs, curcolor, OWL_COLOR_DEFAULT);
752      g_free(buff);
753      txtptr=ptr+1;
754      continue;
755    }
756  }
757}
758
759/* requires that the list values are strings or NULL.
760 * joins the elements together with join_with.
761 * If format_fn is specified, passes it the list element value
762 * and it will return a string which this needs to free. */
763void owl_fmtext_append_list(owl_fmtext *f, const GPtrArray *l, const char *join_with, char *(format_fn)(const char *))
764{
765  int i;
766  const char *elem;
767  char *text;
768
769  for (i = 0; i < l->len; i++) {
770    elem = l->pdata[i];
771    if (elem && format_fn) {
772      text = format_fn(elem);
773      if (text) {
774        owl_fmtext_append_normal(f, text);
775        g_free(text);
776      }
777    } else if (elem) {
778      owl_fmtext_append_normal(f, elem);
779    }
780    if ((i < l->len - 1) && join_with) {
781      owl_fmtext_append_normal(f, join_with);
782    }
783  }
784}
785
786/* Free all memory allocated by the object */
787void owl_fmtext_cleanup(owl_fmtext *f)
788{
789  if (f->buff) g_string_free(f->buff, true);
790  f->buff = NULL;
791}
792
793/*** Color Pair manager ***/
794void owl_fmtext_init_colorpair_mgr(owl_colorpair_mgr *cpmgr)
795{
796  /* This could be a bitarray if we wanted to save memory. */
797  short i;
798  /* The test is <= because we allocate COLORS+1 entries. */
799  cpmgr->pairs = g_new(short *, COLORS + 1);
800  for(i = 0; i <= COLORS; i++) {
801    cpmgr->pairs[i] = g_new(short, COLORS + 1);
802  }
803  owl_fmtext_reset_colorpairs(cpmgr);
804}
805
806/* Reset used list */
807void owl_fmtext_reset_colorpairs(owl_colorpair_mgr *cpmgr)
808{
809  short i, j;
810
811  cpmgr->overflow = false;
812  cpmgr->next = 8;
813  /* The test is <= because we allocated COLORS+1 entries. */
814  for(i = 0; i <= COLORS; i++) {
815    for(j = 0; j <= COLORS; j++) {
816      cpmgr->pairs[i][j] = -1;
817    }
818  }
819  if (has_colors()) {
820    for(i = 0; i < 8; i++) {
821      short fg, bg;
822      if (i >= COLORS) continue;
823      pair_content(i, &fg, &bg);
824      cpmgr->pairs[fg+1][bg+1] = i;
825    }
826  }
827}
828
829/* Assign pairs by request */
830short owl_fmtext_get_colorpair(int fg, int bg)
831{
832  owl_colorpair_mgr *cpmgr;
833  short pair;
834
835  if (!has_colors())
836    return 0;
837
838  /* Sanity (Bounds) Check */
839  if (fg > COLORS || fg < OWL_COLOR_DEFAULT) fg = OWL_COLOR_DEFAULT;
840  if (bg > COLORS || bg < OWL_COLOR_DEFAULT) bg = OWL_COLOR_DEFAULT;
841           
842#ifdef HAVE_USE_DEFAULT_COLORS
843  if (fg == OWL_COLOR_DEFAULT) fg = -1;
844#else
845  if (fg == OWL_COLOR_DEFAULT) fg = 0;
846  if (bg == OWL_COLOR_DEFAULT) bg = 0;
847#endif
848
849  /* looking for a pair we already set up for this draw. */
850  cpmgr = owl_global_get_colorpair_mgr(&g);
851  pair = cpmgr->pairs[fg+1][bg+1];
852  if (!(pair != -1 && pair < cpmgr->next)) {
853    /* If we didn't find a pair, search for a free one to assign. */
854    pair = (cpmgr->next < owl_util_get_colorpairs()) ? cpmgr->next : -1;
855    if (pair != -1) {
856      /* We found a free pair, initialize it. */
857      init_pair(pair, fg, bg);
858      cpmgr->pairs[fg+1][bg+1] = pair;
859      cpmgr->next++;
860    }
861    else if (bg != OWL_COLOR_DEFAULT) {
862      /* We still don't have a pair, drop the background color. Too bad. */
863      owl_function_debugmsg("colorpairs: color shortage - dropping background color.");
864      cpmgr->overflow = true;
865      pair = owl_fmtext_get_colorpair(fg, OWL_COLOR_DEFAULT);
866    }
867    else {
868      /* We still don't have a pair, defaults all around. */
869      owl_function_debugmsg("colorpairs: color shortage - dropping foreground and background color.");
870      cpmgr->overflow = true;
871      pair = 0;
872    }
873  }
874  return pair;
875}
Note: See TracBrowser for help on using the repository browser.