source: fmtext.c @ c268c9e

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