source: fmtext.c @ 9efa5bd

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