source: fmtext.c @ ab9bf01

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