diff --git a/src/intf.c b/src/intf.c index 38d8248..ade44c7 100644 --- a/src/intf.c +++ b/src/intf.c @@ -42,6 +42,29 @@ typedef struct txwin { } txwin_t; +// Declarations for argument processing in prepstr() + +#define MAXFMTARGS 8 // Maximum number of positional arguments + +enum argument_type { + TYPE_NONE, // No type yet assigned + TYPE_INT, // int + TYPE_LONGINT, // long int + TYPE_DOUBLE, // double + TYPE_STRING // const char * +}; + +struct argument { + enum argument_type a_type; + union a { + int a_int; + long int a_longint; + double a_double; + const char *a_string; + } a; +}; + + /************************************************************************ * Global variable definitions * ************************************************************************/ @@ -132,6 +155,40 @@ static void txresize (void); #endif +/* + Function: prepstr_addch - Add a character to the prepstr buffer + Parameters: chbuf - Pointer to chtype pointer in which to store string + chbufsize - Pointer to number of chtype elements in chbuf + attr - Character rendition to use + maxlines - Maximum number of screen lines to use + maxwidth - Maximum width of each line, in chars + line - Pointer to current line number + width - Pointer to current line width + lastspc - Pointer to const char * pointer to last space + widthspc - Pointer to width just before last space + widthbuf - Pointer to buffer to store widths of each line + widthbufsize - Number of int elements in widthbuf + str - Pointer to const char * pointer to string + Returns: int - -1 on error (with errno set), 0 otherwise + + This helper function adds the character **str to **chbuf, using attr as + the character rendition (attributes), incrementing both *str and *chbuf + and decrementing *chbufsize. If a string is too long for the current + line, a previous space in the current line is converted to a new line + (if possible), else a new line is inserted into the current location + (if not on the last line). *line, *width, *lastspc, *widthspc and + widthbuf[] are all updated appropriately. +*/ +static int prepstr_addch (chtype *restrict *restrict chbuf, + int *restrict chbufsize, chtype attr, + int maxlines, int maxwidth, int *restrict line, + int *restrict width, + chtype *restrict *restrict lastspc, + int *restrict widthspc, int *restrict widthbuf, + int widthbufsize, + const char *restrict *restrict str); + + /* Function: txinput_fixup - Copy strings with fixup Parameters: dest - Destination buffer of size BUFSIZE @@ -155,7 +212,8 @@ static void txinput_fixup (char *restrict dest, char *restrict src, * Basic text input/output function definitions * ************************************************************************/ -// These functions are documented in the file "intf.h" +/* These functions are documented either in the file "intf.h" or in the + comments above. */ /***********************************************************************/ @@ -497,6 +555,643 @@ void txresize (void) #endif // HANDLE_RESIZE_EVENTS +/***********************************************************************/ +// prepstr_addch: Add a character to the prepstr buffer + +int prepstr_addch (chtype *restrict *restrict chbuf, int *restrict chbufsize, + chtype attr, int maxlines, int maxwidth, + int *restrict line, int *restrict width, + chtype *restrict *restrict lastspc, int *restrict widthspc, + int *restrict widthbuf, int widthbufsize, + const char *restrict *restrict str) +{ + if (*line < 0) { + // First character in buffer: start line 0 + *line = 0; + } + + if (**str == '\n') { + // Start a new line + + if (*line < maxlines - 1) { + *(*chbuf)++ = '\n'; + (*chbufsize)--; + } + + widthbuf[*line] = *width; + *width = 0; + + *lastspc = NULL; + *widthspc = 0; + + (*line)++; + (*str)++; + } else if (*width == maxwidth) { + // Current line is now too long + + if (! isspace(**str) && *lastspc != NULL && *line < maxlines - 1) { + // Break on the last space in this line + **lastspc = '\n'; + + widthbuf[*line] = *widthspc; + *width -= *widthspc + 1; + + *lastspc = NULL; + *widthspc = 0; + + (*line)++; + } else { + // Insert a new-line character (if not on last line) + if (*line < maxlines - 1) { + *(*chbuf)++ = '\n'; + (*chbufsize)--; + } + + widthbuf[*line] = *width; + *width = 0; + + *lastspc = NULL; + *widthspc = 0; + + (*line)++; + + // Skip any following spaces + while (isspace(**str)) { + if (*(*str)++ == '\n') { + break; + } + } + } + } else { + // Insert an ordinary character into the output string + + if (isspace(**str)) { + *lastspc = *chbuf; + *widthspc = *width; + } + + *(*chbuf)++ = (unsigned char) **str | attr; + (*chbufsize)--; + (*width)++; + (*str)++; + } + + return 0; +} + + +/***********************************************************************/ +// prepstr: Prepare a string for printing to screen + +int prepstr (chtype *restrict chbuf, int chbufsize, chtype attr_norm, + chtype attr_alt1, chtype attr_alt2, int maxlines, int maxwidth, + int *restrict widthbuf, int widthbufsize, + const char *restrict format, ...) +{ + struct argument format_arg[MAXFMTARGS]; + int num_format_args, arg_num; + const char *orig_format; + va_list args; + int line, width; + chtype *lastspc; + int widthspc; + chtype curattr; + int saved_errno; + + + assert(chbuf != NULL); + assert(chbufsize > 0); + assert(maxlines > 0); + assert(maxwidth > 0); + assert(widthbuf != NULL); + assert(widthbufsize >= maxlines); + assert(format != NULL); + + /* Do a preliminary scan through the format parameter to determine + the types of each positional argument (conversion specifier). If + we did not support "%m$"-style specifiers, this would not be + necessary. */ + + orig_format = format; + memset(format_arg, 0, sizeof(format_arg)); + num_format_args = 0; + arg_num = 0; + va_start(args, format); + + while (*format != '\0') { + switch (*format++) { + case '^': + // Switch to a different character rendition + if (*format == '\0') { + goto error_inval; + } else { + format++; + } + break; + + case '%': + // Process a conversion specifier + if (*format == '\0') { + goto error_inval; + } else if (*format == '%') { + format++; + } else { + enum argument_type arg_type = TYPE_NONE; + bool inspec = true; + bool flag_posn = false; + bool flag_long = false; + int count = 0; + + while (inspec && *format != '\0') { + char c = *format++; + switch (c) { + case '0': + // Zero flag, or part of numeric count + if (count == 0) + goto error_inval; + + count *= 10; + break; + + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + // Part of some numeric count + count = count * 10 + (c - '0'); + break; + + case '$': + // Fixed-position argument + if (flag_posn || count == 0) + goto error_inval; + + if (count > MAXFMTARGS) { + errno = E2BIG; + goto error; + } + + flag_posn = true; + arg_num = count - 1; + count = 0; + break; + + case '\'': + // Use locale-specific thousands separator + break; + + case 'l': + // Long length modifier + if (flag_long) + goto error_inval; + + flag_long = true; + break; + + case 'd': + // Insert an integer (int or long int) + arg_type = flag_long ? TYPE_LONGINT : TYPE_INT; + goto handlefmt; + + case 'N': + // Insert a monetary amount (double) + if (flag_long) + goto error_inval; + + arg_type = TYPE_DOUBLE; + goto handlefmt; + + case 's': + // Insert a string (const char *) + if (flag_long) + goto error_inval; + + arg_type = TYPE_STRING; + + handlefmt: + if (arg_num >= MAXFMTARGS) { + errno = E2BIG; + goto error; + } + + if (format_arg[arg_num].a_type == TYPE_NONE) { + format_arg[arg_num].a_type = arg_type; + } else if (format_arg[arg_num].a_type != arg_type) { + goto error_inval; + } + + arg_num++; + num_format_args = MAX(num_format_args, arg_num); + + inspec = false; + break; + + default: + goto error_inval; + } + } + if (inspec) + goto error_inval; + } + break; + + default: + // Process an ordinary character: do nothing for now + ; + } + } + + for (int i = 0; i < num_format_args; i++) { + switch (format_arg[i].a_type) { + case TYPE_INT: + format_arg[i].a.a_int = va_arg(args, int); + break; + + case TYPE_LONGINT: + format_arg[i].a.a_longint = va_arg(args, long int); + break; + + case TYPE_DOUBLE: + format_arg[i].a.a_double = va_arg(args, double); + break; + + case TYPE_STRING: + format_arg[i].a.a_string = va_arg(args, const char *); + break; + + default: + /* Cannot allow unused arguments, as we have no way of + knowing how much space they take (cf. int vs. long long + int). */ + goto error_inval; + } + } + + // Actually process the format parameter string + + format = orig_format; + arg_num = 0; + + curattr = attr_norm; + line = -1; // Current line number (0 = first) + width = 0; // Width of the current line + lastspc = NULL; // Pointer to last space in line + widthspc = 0; // Width of line before last space + + while (*format != '\0' && chbufsize > 1 && line < maxlines) { + switch (*format) { + case '^': + // Switch to a different character rendition + if (*++format == '\0') { + goto error_inval; + } else { + switch (*format) { + case '^': + if (prepstr_addch(&chbuf, &chbufsize, curattr, maxlines, + maxwidth, &line, &width, &lastspc, + &widthspc, widthbuf, widthbufsize, + &format) < 0) { + goto error; + } + break; + + case '{': + curattr = attr_alt1; + format++; + break; + + case '[': + curattr = attr_alt2; + format++; + break; + + case '}': + case ']': + curattr = attr_norm; + format++; + break; + + default: + goto error_inval; + } + } + break; + + case '%': + // Process a conversion specifier + if (*++format == '\0') { + goto error_inval; + } else if (*format == '%') { + if (prepstr_addch(&chbuf, &chbufsize, curattr, maxlines, + maxwidth, &line, &width, &lastspc, &widthspc, + widthbuf, widthbufsize, &format) < 0) { + goto error; + } + } else { + bool inspec = true; + bool flag_posn = false; + bool flag_long = false; + bool flag_thou = false; + int count = 0; + const char *str; + + char *buf = malloc(BUFSIZE); + if (buf == NULL) + err_exit_nomem(); + + while (inspec && *format != '\0') { + char c = *format++; + switch (c) { + case '0': + // Zero flag, or part of numeric count + if (count == 0) { + // Zero flag is not supported + free(buf); + goto error_inval; + } + + count *= 10; + break; + + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + // Part of some numeric count + count = count * 10 + (c - '0'); + break; + + case '$': + // Fixed-position argument + if (flag_posn || count == 0) { + free(buf); + goto error_inval; + } + + if (count > MAXFMTARGS) { + free(buf); + errno = E2BIG; + goto error; + } + + flag_posn = true; + arg_num = count - 1; + count = 0; + break; + + case '\'': + // Use locale-specific thousands separator + if (flag_thou) { + free(buf); + goto error_inval; + } + + flag_thou = true; + break; + + case 'l': + // Long length modifier + if (flag_long) { + free(buf); + goto error_inval; + } + + flag_long = true; + break; + + case 'd': + // Insert an integer (int or long int) into the output + if (count != 0) { + free(buf); + goto error_inval; + } + + if (arg_num >= MAXFMTARGS) { + free(buf); + errno = E2BIG; + goto error; + } + + if (flag_long) { + if (snprintf(buf, BUFSIZE, flag_thou ? "%'ld" : "%ld", + format_arg[arg_num].a.a_longint) < 0) { + saved_errno = errno; + free(buf); + errno = saved_errno; + goto error; + } + } else { + if (snprintf(buf, BUFSIZE, flag_thou ? "%'d" : "%d", + format_arg[arg_num].a.a_int) < 0) { + saved_errno = errno; + free(buf); + errno = saved_errno; + goto error; + } + } + + str = buf; + goto insertstr; + + case 'N': + // Insert a monetary amount (double) into the output + if (count != 0 || flag_thou || flag_long) { + free(buf); + goto error_inval; + } + + if (arg_num >= MAXFMTARGS) { + free(buf); + errno = E2BIG; + goto error; + } + + if (l_strfmon(buf, BUFSIZE, "%n", + format_arg[arg_num].a.a_double) < 0) { + saved_errno = errno; + free(buf); + errno = saved_errno; + goto error; + } + + str = buf; + goto insertstr; + + case 's': + // Insert a string (const char *) into the output + if (count != 0 || flag_thou || flag_long) { + free(buf); + goto error_inval; + } + + if (arg_num >= MAXFMTARGS) { + free(buf); + errno = E2BIG; + goto error; + } + + str = format_arg[arg_num].a.a_string; + + if (str == NULL) { + str = "(null)"; // As per GNU printf() + } + + insertstr: + // Insert the string pointed to by str + while (*str != '\0' && chbufsize > 1 && line < maxlines) { + if (prepstr_addch(&chbuf, &chbufsize, curattr, + maxlines, maxwidth, &line, &width, + &lastspc, &widthspc, widthbuf, + widthbufsize, &str) < 0) { + saved_errno = errno; + free(buf); + errno = saved_errno; + goto error; + } + } + + arg_num++; + inspec = false; + break; + + default: + free(buf); + goto error_inval; + } + } + free(buf); + if (inspec) + goto error_inval; + } + break; + + default: + // Process an ordinary character (including new-line) + if (prepstr_addch(&chbuf, &chbufsize, curattr, maxlines, maxwidth, + &line, &width, &lastspc, &widthspc, widthbuf, + widthbufsize, &format) < 0) { + goto error; + } + } + } + + *chbuf = 0; // Terminating NUL byte + + if (line >= 0 && line < maxlines) { + widthbuf[line] = width; + } else if (line >= maxlines) { + line = maxlines - 1; + } + + va_end(args); + return line + 1; + +error_inval: + errno = EINVAL; + +error: + va_end(args); + return -1; +} + + +/***********************************************************************/ +// pr_left: Print strings in chbuf left-aligned + +int pr_left (WINDOW *win, int y, int x, const chtype *restrict chbuf, + int lines, const int *restrict widthbuf) +{ + assert(win != NULL); + assert(chbuf != NULL); + assert(lines > 0); + assert(widthbuf != NULL); + + wmove(win, y, x); + for ( ; *chbuf != '\0'; chbuf++) { + if (*chbuf == '\n') { + wmove(win, getcury(win) + 1, x); + } else { + waddch(win, *chbuf); + } + } + + return OK; +} + + +/***********************************************************************/ +// pr_center: Print strings in chbuf centered in window + +int pr_center (WINDOW *win, int y, int offset, const chtype *restrict chbuf, + int lines, const int *restrict widthbuf) +{ + int ln = 0; + + + assert(win != NULL); + assert(chbuf != NULL); + assert(lines > 0); + assert(widthbuf != NULL); + + wmove(win, y, (getmaxx(win) - widthbuf[ln]) / 2 + offset); + for ( ; *chbuf != '\0'; chbuf++) { + if (*chbuf == '\n') { + if (ln++ >= lines) { + return ERR; + } else { + wmove(win, getcury(win) + 1, + (getmaxx(win) - widthbuf[ln]) / 2 + offset); + } + } else { + waddch(win, *chbuf); + } + } + + return OK; +} + + +/***********************************************************************/ +// pr_right: Print strings in chbuf right-aligned + +int pr_right (WINDOW *win, int y, int x, const chtype *restrict chbuf, + int lines, const int *restrict widthbuf) +{ + int ln = 0; + + + assert(win != NULL); + assert(chbuf != NULL); + assert(lines > 0); + assert(widthbuf != NULL); + + wmove(win, y, x - widthbuf[ln]); + for ( ; *chbuf != '\0'; chbuf++) { + if (*chbuf == '\n') { + if (ln++ >= lines) { + return ERR; + } else { + wmove(win, getcury(win) + 1, x - widthbuf[ln]); + } + } else { + waddch(win, *chbuf); + } + } + + return OK; +} + + /***********************************************************************/ // attrpr: Print a string with a particular character rendition diff --git a/src/intf.h b/src/intf.h index e60957e..3910c76 100644 --- a/src/intf.h +++ b/src/intf.h @@ -254,6 +254,132 @@ extern int delalltxwin (void); extern int txrefresh (void); +/* + Function: prepstr - Prepare a string for printing to screen + Parameters: chbuf - Pointer to chtype buffer in which to store string + chbufsize - Number of chtype elements in chbuf + attr_norm - Normal character rendition to use + attr_alt1 - First alternate character rendition to use + attr_alt2 - Second alternate character rendition to use + maxlines - Maximum number of screen lines to use + maxwidth - Maximum width of each line, in chars + widthbuf - Pointer to buffer to store widths of each line + widthbufsize - Number of int elements in widthbuf + format - Format string as described below + ... - Arguments for the format string + Returns: int - Number of lines actually used, or -1 on error + + This function converts the format string and following arguments into + chbuf, a chtype buffer that can be used for calls to pr_left(), + pr_center() and pr_right(). At most maxlines lines are used, each with + a maximum width of maxwidth. The actual widths of each resulting line + are stored in widthbuf (which must not be NULL). If maxlines is + greater than 1, lines are wrapped as needed. + + The format string is similar to but more limited than printf(). In + particular, the following conversion specifiers are understood: + + %% - Print the ASCII percent sign (ASCII code U+0025) + + %s - Insert the next parameter as a string + %d - Insert the next parameter as an integer (type int) + %'d - Insert as an int, using the locale's thousands separator + %ld - Insert the next parameter as a long int + %'ld - Insert as a long int, using the locale's thousands separator + %N - Insert the next parameter as a double, using the locale's + national currency format (extension to printf()) + + Instead of using "%" to convert the next parameter, "%m$" can be used + to indicate fixed parameter m (where m is an integer from 1 to 8). For + example, "%4$s" inserts the fourth parameter after "format" as a string + into chbuf. As with printf(), using "%m$" together with ordinary "%" + forms is undefined. If "%m$" is used, no parameter m can be skipped. + + Note that no other flag, field width, precision or length modifier + characters are recognised: if needed, these should be formatted FIRST + with snprintf(), then inserted using %s as appropriate. + + In addition to the conversion specifiers, the following character + rendition flags are understood, where the "^" character is a literal + ASCII circumflex accent: + + ^^ - Print the circumflex accent (ASCII code U+005E) + ^{ - Switch to using attr_alt1 character rendition (alternate mode 1) + ^} - Switch to using attr_norm character rendition + ^[ - Switch to using attr_alt2 character rendition (alternate mode 2) + ^] - Switch to using attr_norm character rendition + + Characters other than these are inserted as literals, except that '\n' + will force the start of a new line. By default, attr_norm is used as + the character rendition (attributes). + + This function returns the actual number of lines used (from 0 to + maxlines), or -1 on error (with errno set to EINVAL for an invalid + format conversion specifier or argument). +*/ +extern int prepstr (chtype *restrict chbuf, int chbufsize, chtype attr_norm, + chtype attr_alt1, chtype attr_alt2, int maxlines, + int maxwidth, int *restrict widthbuf, int widthbufsize, + const char *restrict format, ...); + + +/* + Function: pr_left - Print strings in chbuf left-aligned + Parameters: win - Window to use (should be curwin) + y - Line on which to print first string + x - Starting column number for each line + chbuf - chtype buffer as returned from prepstr() + lines - Number of lines in chbuf (as returned from prepstr()) + widthbuf - Widths of each line (as returned from prepstr()) + Returns: int - Error code OK + + This function takes the strings in the chtype array chbuf and prints + them left-aligned in the window win. Note that wrefresh() is NOT + called. +*/ +extern int pr_left (WINDOW *win, int y, int x, const chtype *restrict chbuf, + int lines, const int *restrict widthbuf); + + +/* + Function: pr_center - Print strings in chbuf centered in window + Parameters: win - Window to use (should be curwin) + y - Line on which to print first string + offset - Column offset to add to position for each line + chbuf - chtype buffer as returned from prepstr() + lines - Number of lines in chbuf (as returned from prepstr()) + widthbuf - Widths of each line (as returned from prepstr()) + Returns: int - ERR if more lines in chbuf[] than lines, else OK + + This function takes the strings in the chtype array chbuf and prints + them centered in the window win, offset by the parameter offset. Note + that wrefresh() is NOT called. ERR is returned if there are more lines + in chbuf[] than are passed in the parameter lines. +*/ +extern int pr_center (WINDOW *win, int y, int offset, + const chtype *restrict chbuf, int lines, + const int *restrict widthbuf); + + +/* + Function: pr_right - Print strings in chbuf right-aligned + Parameters: win - Window to use (should be curwin) + y - Line on which to print first string + x - Ending column number for each line + chbuf - chtype buffer as returned from prepstr() + lines - Number of lines in chbuf (as returned from prepstr()) + widthbuf - Widths of each line (as returned from prepstr()) + Returns: int - ERR if more lines in chbuf[] than lines, else OK + + This function takes the strings in the chtype array chbuf and prints + them right-aligned in the window win, with each line ending at column + x. Note that wrefresh() is NOT called. ERR is returned if there are + more lines in chbuf[] than are passed in the parameter lines. +*/ +extern int pr_right (WINDOW *win, int y, int x, const chtype *restrict chbuf, + int lines, const int *restrict widthbuf); + + /* Function: attrpr - Print a string with a particular character rendition Parameters: win - Window to use (should be curwin)