scummvm/engines/agi/text.cpp

665 lines
14 KiB
C++
Raw Normal View History

2006-05-23 23:43:52 +00:00
/* ScummVM - Scumm Interpreter
* Copyright (C) 2006 The ScummVM project
*
* Copyright (C) 1999-2003 Sarien Team
*
* 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; either version 2
* of the License, or (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* $URL$
* $Id$
*
*/
#include "agi/agi.h"
#include "agi/sprite.h" /* for commit_both() */
#include "agi/graphics.h"
#include "agi/keyboard.h"
#include "agi/text.h"
namespace Agi {
static void print_text2(int l, const char *msg, int foff, int xoff, int yoff,
2006-05-23 23:43:52 +00:00
int len, int fg, int bg) {
int x1, y1;
int maxx, minx, ofoff;
int update;
/* Note: Must be unsigned to use AGDS 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 = (unsigned char *)msg, x1 = y1 = 0; *m; m++) {
if (*m >= 0x20 || *m == 1 || *m == 2 || *m == 3) {
/* FIXME */
int ypos;
ypos = (y1 * CHAR_LINES) + yoff;
if ((x1 != (len - 1) || x1 == 39) && (ypos <= (GFX_HEIGHT - CHAR_LINES))) {
int xpos;
xpos = (x1 * CHAR_COLS) + xoff + foff;
if (xpos >= GFX_WIDTH)
continue;
put_text_character(l, xpos, ypos, *m, fg, bg);
if (x1 > maxx)
maxx = x1;
if (x1 < minx)
minx = x1;
}
x1++;
/* DF: changed the len-1 to len... */
if (x1 == len && m[1] != '\n')
y1++, x1 = foff = 0;
} else {
y1++;
x1 = foff = 0;
}
}
}
if (l)
return;
if (maxx < minx)
return;
maxx *= CHAR_COLS;
minx *= CHAR_COLS;
if (update) {
schedule_update(foff + xoff + minx, yoff, ofoff + xoff + maxx + CHAR_COLS - 1,
yoff + y1 * CHAR_LINES + CHAR_LINES + 1);
/* Making synchronous text updates reduces CPU load
* when updating status line and input area
*/
do_update();
}
}
/* len is in characters, not pixels!!
*/
static void blit_textbox(const char *p, int y, int x, int len) {
2006-05-23 23:43:52 +00:00
/* if x | y = -1, then centre the box */
int xoff, yoff, lin, h, w;
char *msg, *m;
debugC(3, kDebugLevelText, "x=%d, y=%d, len=%d", x, y, len);
if (game.window.active)
close_window();
if (x == 0 && y == 0 && len == 0)
x = y = -1;
if (len <= 0 || len >= 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++) {
/* Test \r for MacOS 8 */
if (*m == '\n' || *m == '\r')
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 - CHAR_COLS) / 2;
else
xoff -= CHAR_COLS;
if (yoff < 0)
yoff = (GFX_HEIGHT - 3 * CHAR_LINES - h) / 2;
draw_window(xoff, yoff, xoff + w - 1, yoff + h - 1);
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) {
debugC(3, kDebugLevelText, "no window active");
return;
}
debugC(4, kDebugLevelText, "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
*/
/**
* Print text in the AGI engine screen.
*/
void print_text(const char *msg, int f, int x, int y, int len, int fg, int bg) {
2006-05-23 23:43:52 +00:00
f *= CHAR_COLS;
x *= CHAR_COLS;
y *= CHAR_LINES;
debugC(4, kDebugLevelText, "%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 AGI engine console.
*/
void print_text_console(const char *msg, int x, int y, int len, int fg, int bg) {
2006-05-23 23:43:52 +00:00
x *= CHAR_COLS;
y *= 10;
print_text2(1, msg, 0, x, y, len, fg, bg);
}
/**
* Wrap text line to the specified width.
* @param str String to wrap.
* @param len Length of line.
*/
char *word_wrap_string(char *str, 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(str);
e = msg + strlen(msg);
maxc = 0;
while (42) {
debugC(3, kDebugLevelText, "[%s], %d", msg, maxc);
if (strchr(v, ' ') == NULL && (int)strlen(v) > l) {
debugC(1, kDebugLevelText | kDebugLevelMain, "Word too long in message");
l = strlen(v);
}
/* Must include \r for MacOS 8 */
while ((c = strcspn(v, "\n\r")) <= l) {
debugC(3, kDebugLevelText, "c = %d, maxc = %d", c, maxc);
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 != '\r';
v--, c--);
if (c > maxc)
maxc = c;
*v++ = '\n';
}
end:
*len = maxc;
return msg;
}
/**
* Remove existing window, if any.
*/
void close_window() {
debugC(4, kDebugLevelText, "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(const char *s) {
2006-05-23 23:43:52 +00:00
int k;
erase_both();
blit_textbox(s, -1, -1, -1);
blit_both();
k = wait_key();
debugC(4, kDebugLevelText, "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(const char *m, const char **b) {
2006-05-23 23:43:52 +00:00
int x, y, i, s;
int key, active = 0;
int rc = -1;
int bx[5], by[5];
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;
debugC(3, kDebugLevelText, "s = %d", s);
/* Automatically position buttons */
for (i = 0; b[i]; i++) {
s -= CHAR_COLS * strlen(b[i]);
}
if (i > 1) {
debugC(3, kDebugLevelText, "s / %d = %d", i - 1, s / (i - 1));
s /= (i - 1);
} else {
x += s / 2;
}
for (i = 0; b[i]; i++) {
bx[i] = x;
by[i] = y;
x += CHAR_COLS * strlen(b[i]) + s;
}
blit_both();
/* clear key queue */
while (keypress()) {
get_key();
}
debugC(4, kDebugLevelText, "waiting...");
while (42) {
for (i = 0; b[i]; i++)
draw_button(bx[i], by[i], b[i], i == active, 0);
poll_timer(); /* msdos driver -> does nothing */
key = do_poll_keyboard();
switch (key) {
case KEY_ENTER:
rc = active;
goto press;
case KEY_ESCAPE:
rc = -1;
goto getout;
case BUTTON_LEFT:
for (i = 0; b[i]; i++) {
if (test_button(bx[i], by[i], b[i])) {
rc = active = i;
goto press;
2006-05-23 23:43:52 +00:00
}
}
break;
case 0x09: /* Tab */
debugC(3, kDebugLevelText, "Focus change");
active++;
active %= i;
break;
2006-05-23 23:43:52 +00:00
}
}
press:
debugC(4, kDebugLevelText, "Button pressed: %d", rc);
getout:
close_window();
debugC(2, kDebugLevelText, "Result = %d", rc);
return rc;
}
/**
*
*/
int print(char *p, int lin, int col, int len) {
if (p == NULL)
return 0;
debugC(4, kDebugLevelText, "lin = %d, col = %d, len = %d", lin, col, len);
if (col == 0 && lin == 0 && len == 0)
lin = col = -1;
if (len == 0)
len = 30;
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 */
debugC(3, kDebugLevelText, "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) {
debugC(4, kDebugLevelText, "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;
}
/**
*
*/
static void print_status(const char *message, ...) {
2006-05-23 23:43:52 +00:00
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);
debugC(4, kDebugLevelText, "fg=%d, bg=%d", STATUS_FG, STATUS_BG);
print_text(x, 0, 0, game.line_status, 40, STATUS_FG, STATUS_BG);
}
static char *safe_strcat(char *s, const char *t) {
if (t != NULL)
strcat(s, t);
return s;
}
/**
* Formats AGI string.
* This function turns a AGI string into a real string expanding values
* according to the AGI format specifiers.
* @param s string containing the format specifier
* @param n logic number
*/
#define MAX_LEN 768
char *agi_sprintf(const char *s) {
2006-05-23 23:43:52 +00:00
static char y[MAX_LEN];
char x[MAX_LEN];
char z[16], *p;
debugC(3, kDebugLevelText, "logic %d, '%s'", game.lognum, s);
p = x;
for (*p = 0; *s;) {
switch (*s) {
case '\\':
s++;
goto literal;
case '%':
s++;
switch (*s++) {
int i;
case 'v':
i = strtoul(s, NULL, 10);
while (*s >= '0' && *s <= '9')
s++;
sprintf(z, "%015i", getvar(i));
i = 99;
if (*s == '|') {
s++;
i = strtoul(s, NULL, 10);
while (*s >= '0' && *s <= '9')
s++;
}
if (i == 99) {
/* remove all leading 0 */
/* don't remove the 3rd zero if 000 */
for (i = 0;
z[i] == '0' && i < 14; i++);
} else {
i = 15 - i;
}
safe_strcat(p, z + i);
break;
case '0':
i = strtoul(s, NULL, 10) - 1;
safe_strcat(p, object_name(i));
break;
case 'g':
i = strtoul(s, NULL, 10) - 1;
safe_strcat(p, game.logics[0].texts[i]);
break;
case 'w':
i = strtoul(s, NULL, 10) - 1;
safe_strcat(p, game.ego_words[i].word);
break;
case 's':
i = strtoul(s, NULL, 10);
safe_strcat(p, game.strings[i]);
break;
case 'm':
i = strtoul(s, NULL, 10) - 1;
if (game.logics[game.lognum].num_texts > i)
safe_strcat(p, agi_sprintf(game. logics[game.lognum].texts[i]));
break;
}
while (*s >= '0' && *s <= '9')
s++;
while (*p)
p++;
break;
default:
literal:
assert(p < x + MAX_LEN);
*p++ = *s++;
*p = 0;
break;
}
}
strcpy(y, x);
return y;
}
/**
* Write the status line.
*/
void write_status() {
char x[64];
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;
}
if (!game.status_line) {
int l = game.line_status;
clear_lines(l, l, 0);
flush_lines(l, l);
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");
}
/**
* Print user input prompt.
*/
void write_prompt() {
int l, fg, bg, pos;
if (!game.input_enabled || game.input_mode != INPUT_NORMAL)
return;
l = game.line_user_input;
fg = game.color_fg;
bg = game.color_bg;
pos = game.cursor_pos;
debugC(4, kDebugLevelText, "erase line %d", l);
clear_lines(l, l, game.color_bg);
debugC(4, kDebugLevelText, "prompt = '%s'", agi_sprintf(game.strings[0]));
print_text(game.strings[0], 0, 0, l, 1, fg, bg);
print_text((char *)game.input_buffer, 0, 1, l, pos + 1, fg, bg);
print_character(pos + 1, l, game.cursor_char, fg, bg);
flush_lines(l, l);
do_update();
}
/**
* 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.
*/
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);
}
/**
*
*/
void draw_window(int x1, int y1, int x2, int y2) {
game.window.active = true;
game.window.x1 = x1;
game.window.y1 = y1;
game.window.x2 = x2;
game.window.y2 = y2;
game.window.buffer = (uint8 *) malloc((x2 - x1 + 1) * (y2 - y1 + 1));
debugC(4, kDebugLevelText, "x1=%d, y1=%d, x2=%d, y2=%d", x1, y1, x2, y2);
save_block(x1, y1, x2, y2, game.window.buffer);
draw_box(x1, y1, x2, y2, MSG_BOX_COLOUR, MSG_BOX_LINE, 2);
}
} // End of namespace Agi