/* Sarien - A Sierra AGI resource interpreter engine * Copyright (C) 1999-2001 Stuart George and Claudio Matsuoka * * $Id: text.c,v 1.62 2001/11/06 01:07:38 cmatsuoka Exp $ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; see docs/COPYING for further details. */ #include #include #include #include #include "sarien.h" #include "agi.h" #include "sprite.h" /* for commit_both() */ #include "graphics.h" #include "keyboard.h" #include "text.h" static void print_text2 (int l, char *msg, int foff, int xoff, int yoff, int len, int fg, int bg) { int x1, y1; int maxx, minx, ofoff; int update; /* Note: Must be unsigned to use cyrillic characters!! */ unsigned char *m; /* kludge! */ update = 1; if (l == 2) { update = l = 0; } /* FR: strings with len == 1 were not printed */ if (len == 1) { put_text_character (l, xoff + foff, yoff, *msg, fg, bg); maxx = 1; minx = 0; ofoff = foff; y1 = 0; /* Check this */ } else { maxx = 0; minx = GFX_WIDTH; ofoff = foff; for (m = msg, x1 = y1 = 0; *m; m++) { if (*m >= 0x20 || *m == 1 || *m == 2 || *m == 3) { /* FIXME */ if((x1!=(len-1) || x1==39) && ((y1*CHAR_LINES)+yoff <= (GFX_HEIGHT - CHAR_LINES))) { put_text_character (l, (x1 * CHAR_COLS) + xoff + foff, (y1 * CHAR_LINES) + yoff, *m, fg, bg); if (x1>maxx) maxx=x1; if (x1= 40) len = 32; xoff = x * CHAR_COLS; yoff = y * CHAR_LINES; len--; m = msg = word_wrap_string (agi_sprintf (p), &len); for (lin = 1; *m; m++) { if (*m == '\n') lin++; } if (lin * CHAR_LINES > GFX_HEIGHT) lin = (GFX_HEIGHT / CHAR_LINES); w = (len + 2) * CHAR_COLS; h = (lin + 2) * CHAR_LINES; if (xoff < 0) xoff = (GFX_WIDTH - w) / 2; else xoff -= CHAR_COLS; if (yoff < 0) yoff = (GFX_HEIGHT - 2 * CHAR_LINES - h) / 2; game.window.active = TRUE; game.window.x1 = xoff; game.window.y1 = yoff; game.window.x2 = xoff + w - 1; game.window.y2 = yoff + h - 1; game.window.buffer = malloc (w * h); _D (_D_WARN "x1=%d, y1=%d, x2=%d, y2=%d", game.window.x1, game.window.y1, game.window.x2, game.window.y2); save_block (game.window.x1, game.window.y1, game.window.x2, game.window.y2, game.window.buffer); draw_box (game.window.x1, game.window.y1, game.window.x2, game.window.y2, MSG_BOX_COLOUR, MSG_BOX_LINE, 2); print_text2 (2, msg, 0, CHAR_COLS + xoff, CHAR_LINES + yoff, len + 1, MSG_BOX_TEXT, MSG_BOX_COLOUR); free (msg); do_update (); } static void erase_textbox () { if (!game.window.active) { _D ("no window active"); return; } _D (_D_WARN "x1=%d, y1=%d, x2=%d, y2=%d", game.window.x1, game.window.y1, game.window.x2, game.window.y2); restore_block (game.window.x1, game.window.y1, game.window.x2, game.window.y2, game.window.buffer); free (game.window.buffer); game.window.active = FALSE; do_update (); } /* * Public functions */ void draw_text (char *msg, int f, int x, int y, int len, int fg, int bg) { print_text2 (0, agi_sprintf (msg), f, x, y, len, fg, bg); } /** * Print text in the Sarien screen. */ void print_text (char *msg, int f, int x, int y, int len, int fg, int bg) { f *= CHAR_COLS; x *= CHAR_COLS; y *= CHAR_LINES; _D (_D_WARN "%s, %d, %d, %d, %d, %d, %d", msg, f, x, y, len, fg, bg); print_text2 (0, agi_sprintf (msg), f, x, y, len, fg, bg); } /** * Print text in the Sarien console. */ void print_text_console (char *msg, int x, int y, int len, int fg, int bg) { x *= CHAR_COLS; y *= 10; print_text2 (1, msg, 0, x, y, len, fg, bg); } /** * Wrap text line to the specified width. */ char* word_wrap_string (char *mesg, int *len) { /* If the message has a long word (longer than 31 character) then * loop in line 239 (for (; *v != ' '; v--, c--);) can wrap * around 0 and write large number in c. This causes returned * length to be negative (!) and eventually crashes in calling * code. The fix is simple -- remove unsigned in maxc, c, l * declaration. --Vasyl */ char *msg, *v, *e; int maxc, c, l = *len; v = msg = strdup (mesg); e = msg + strlen (msg); maxc = 0; while (42) { /* _D ("[%s], %d", msg, maxc); */ if (strchr (v, ' ') == NULL && strlen (v) > l) { _D (_D_CRIT "Word too long in message"); l = strlen (v); } while ((c = strcspn (v, "\n")) <= l) { if (c > maxc) maxc = c; if ((v += c + 1) >= e) goto end; } c = l; if ((v += l) >= e) break; /* The same line that caused that bug I mentioned * should also do another check: * for (; *v != ' ' && *v != '\n'; v--, c--); * While this does not matter in most cases, in the case of * long words it caused extra \n inserted in the line * preceding long word. This one is definitely non-critical; * one might argue that the function is not supposed to deal * with long words. BTW, that condition at the beginning of * the while loop that checks word length does not make much * sense -- it verifies the length of the first word but for * the rest it does something odd. Overall, even with these * changes the function is still not completely robust. * --Vasyl */ if (*v != ' ') for (; *v != ' ' && *v != '\n'; v--, c--); if (c > maxc) maxc = c; *v++ = '\n'; } end: *len = maxc; return msg; } /** * Remove existing window, if any. */ void close_window () { _D (_D_WARN "close window"); erase_both (); erase_textbox (); /* remove window, if any */ blit_both (); commit_both (); /* redraw sprites */ game.has_window = FALSE; } /** * Display a message box. * This function displays the specified message in a text box * centered in the screen and waits until a key is pressed. * @param p The text to be displayed */ int message_box (char *s) { int k; erase_both (); blit_textbox (s, -1, -1, -1); blit_both (); k = wait_key (); _D (_D_WARN "wait_key returned %02x", k); close_window (); return k; } /** * Display a message box with buttons. * This function displays the specified message in a text box * centered in the screen and waits until a button is pressed. * @param p The text to be displayed * @param b NULL-terminated list of button labels */ int selection_box (char *m, char **b) { int x, y, i, s; int key, active = 0; int rc = -1; erase_both (); blit_textbox (m, -1, -1, -1); x = game.window.x1 + 5 * CHAR_COLS / 2; y = game.window.y2 - 5 * CHAR_LINES / 2; s = game.window.x2 - game.window.x1 + 1 - 5 * CHAR_COLS; _D ("s = %d", s); /* Get space between buttons */ for (i = 0; b[i]; i++) { s -= CHAR_COLS * strlen (b[i]); } if (i > 1) { _D ("s / %d = %d", i - 1, s / (i - 1)); s /= (i - 1); } else { x += s / 2; } blit_both (); /* clear key queue */ while (keypress ()) { get_key (); } _D (_D_WARN "waiting..."); while (42) { int xx = x; for (i = 0; b[i]; i++) { draw_button (xx, y, b[i], i == active, 0); xx += CHAR_COLS * strlen (b[i]) + s; } poll_timer (); /* msdos driver -> does nothing */ key = do_poll_keyboard (); if (!console_keyhandler (key)) { switch (key) { case KEY_ENTER: rc = active; goto press; case KEY_ESCAPE: rc = -1; goto getout; #ifdef USE_MOUSE case BUTTON_LEFT: { int xx = x; for (i = 0; b[i]; i++) { if (test_button (xx, y, b[i])) { rc = active = i; goto press; } xx += CHAR_COLS * strlen (b[i]) + s; } break; } #endif case 0x09: /* Tab */ _D ("Focus change"); active++; active %= i; break; } } console_cycle (); } press: _D (_D_WARN "Button pressed: %d", rc); getout: close_window (); return rc; } /** * */ int print (char *p, int lin, int col, int len) { assert (p != NULL); blit_textbox (p, lin, col, len); if (getflag (F_output_mode)) { /* non-blocking window */ setflag (F_output_mode, FALSE); return 1; } /* blocking */ if (game.vars[V_window_reset] == 0) { int k; setvar (V_key, 0); k = wait_key(); close_window (); return k; } /* timed window */ _D (_D_WARN "f15==0, v21==%d => timed", getvar (21)); game.msg_box_ticks = getvar (V_window_reset) * 10; setvar (V_key, 0); do { main_cycle (); if (game.keypress == KEY_ENTER) { _D (_D_WARN "KEY_ENTER"); setvar (V_window_reset, 0); game.keypress = 0; break; } } while (game.msg_box_ticks > 0); setvar (V_window_reset, 0); close_window (); return 0; } void print_status (char *message, ...) { char x[42]; va_list args; va_start (args, message); #ifdef HAVE_VSNPRINTF vsnprintf (x, 41, message, args); #else vsprintf (x, message, args); #endif va_end (args); _D (_D_WARN "fg=%d, bg=%d", STATUS_FG, STATUS_BG); print_text (x, 0, 0, game.line_status, 40, STATUS_FG, STATUS_BG); } /** * Formats AGI string. * @param s string containing the format specifier * @param n logic number */ #define MAX_LEN 768 char *agi_sprintf (char *s) { static char x[MAX_LEN], y[MAX_LEN]; char z[16], *p; int xx, xy; _D ("logic %d, '%s'", game.lognum, s); /* turn a AGI string into a real string */ p = x; for (*p = xx = xy = 0; *s; ) { switch (*s) { case '\\': s++; continue; case '%': s++; switch (*s++) { case 'v': xx = atoi(s); while (*s >= '0' && *s <= '9') s++; sprintf (z, "%03i", getvar(xx)); xy=99; if(*s=='|') { s++; xy = atoi (s); while (*s >= '0' && *s <= '9') s++; } xx=0; if(xy==99) { /* remove all leading 0' */ /* dont remove the 3rd zero if 000 */ while(z[xx]=='0' && xx<2) xx++; } else xx=3-xy; strcat(p, z + xx); break; case '0': strcat(p, object_name (atol(s)-1)); break; case 'g': strcat(p, game.logics[0].texts[atol(s)-1]); break; case 'w': strcat(p, game.ego_words[atol(s)-1].word); break; case 's': strcat(p, game.strings[atol(s)]); break; case 'm': { int n = game.lognum, m = atoi(s) - 1; if (game.logics[n].num_texts > m) strcat(p,game.logics[n].texts[m]); break; } } while (*s >= '0' && *s <= '9') s++; while (*p) p++; break; default: assert (p < x + MAX_LEN); *p++ = *s++; *p = 0; break; } } p = x; if (strchr (x, '%') != NULL) { strcpy (y, x); p = agi_sprintf (y); } return p; } /** * */ void write_status () { char x[64]; #ifdef USE_CONSOLE if (debug.statusline) { print_status ("%3d(%03d) %3d,%3d(%3d,%3d) ", getvar (0), getvar (1), game.view_table[0].x_pos, game.view_table[0].y_pos, WIN_TO_PIC_X(mouse.x), WIN_TO_PIC_Y(mouse.y)); return; } #endif if (!game.status_line) { clear_lines (0, 0, 0); flush_lines (0, 0); return; } sprintf (x, " Score:%i of %-3i", game.vars[V_score], game.vars[V_max_score]); print_status ("%-17s Sound:%s ", x, getflag (F_sound_on) ? "on " : "off"); } /** * Clear text lines in the screen. * @param l1 start line * @param l2 end line * @param c color */ void clear_lines (int l1, int l2, int c) { /* do we need to adjust for +8 on topline? * inc for endline so it matches the correct num * ie, from 22 to 24 is 3 lines, not 2 lines. */ /* if (c) c = 15; */ l1 *= CHAR_LINES; l2 *= CHAR_LINES; l2 += CHAR_LINES - 1; draw_rectangle (0, l1, GFX_WIDTH - 1, l2, c); } /** * */ void flush_lines (int l1, int l2) { l1 *= CHAR_LINES; l2 *= CHAR_LINES; l2 += CHAR_LINES - 1; flush_block (0, l1, GFX_WIDTH - 1, l2); } /* end: text.c */