I've noticed many instances of the game pausing and not being sure why, then pressing <space> and having it resume. The curses interface had a tendency to put its equivalent of the --More-- prompt, >>, somewhere where that wasn't visible, either off the right hand edge (possibly) or underneath the window borders if those were enabled. Especially the very last one it issues prior to exit. (An extra one compared to tty behavior.) This ended up being a pretty substantial overhaul of message window handling. I wouldn't be surprised if it has off-by-one errors which happen to be paired up and cancel each other out. ">>" is still drawn in orange if guicolor is on, now in inverse video when that is off. If it happens to be drawn at the same screen location in consecutive instances, the first ">" will toggle between blink and not blink so that there'll be no doubt as to whether the keypress registered when dismissing it (moot if the text preceding it is different but there's no attempt to be smart enough to check that, just screen placement).
846 lines
26 KiB
C
846 lines
26 KiB
C
/* vim:set cin ft=c sw=4 sts=4 ts=8 et ai cino=Ls\:0t0(0 : -*- mode:c;fill-column:80;tab-width:8;c-basic-offset:4;indent-tabs-mode:nil;c-file-style:"k&r" -*-*/
|
|
/* NetHack 3.6 cursmesg.c */
|
|
/* Copyright (c) Karl Garrison, 2010. */
|
|
/* NetHack may be freely redistributed. See license for details. */
|
|
|
|
#include "curses.h"
|
|
#include "hack.h"
|
|
#include "wincurs.h"
|
|
#include "cursmesg.h"
|
|
#include <ctype.h>
|
|
|
|
/*
|
|
* Note: references to "More>>" mean ">>", the curses rendition of "--More--".
|
|
*/
|
|
|
|
/* player can type ESC at More>> prompt to avoid seeing more messages
|
|
for the current move; but hero might get more than one move per turn,
|
|
so the input routines need to be able to cancel this */
|
|
long curs_mesg_suppress_turn = -1;
|
|
|
|
/* Message window routines for curses interface */
|
|
|
|
/* Private declatations */
|
|
|
|
typedef struct nhpm {
|
|
char *str; /* Message text */
|
|
long turn; /* Turn number for message */
|
|
struct nhpm *prev_mesg; /* Pointer to previous message */
|
|
struct nhpm *next_mesg; /* Pointer to next message */
|
|
} nhprev_mesg;
|
|
|
|
static void scroll_window(winid wid);
|
|
static void unscroll_window(winid wid);
|
|
static void directional_scroll(winid wid, int nlines);
|
|
static void mesg_add_line(const char *mline);
|
|
static nhprev_mesg *get_msg_line(boolean reverse, int mindex);
|
|
|
|
static int turn_lines = 0;
|
|
static int mx = 0;
|
|
static int my = 0; /* message window text location */
|
|
static nhprev_mesg *first_mesg = NULL;
|
|
static nhprev_mesg *last_mesg = NULL;
|
|
static int max_messages;
|
|
static int num_messages = 0;
|
|
static int last_messages = 0;
|
|
|
|
/* Write string to the message window. Attributes set by calling function. */
|
|
|
|
void
|
|
curses_message_win_puts(const char *message, boolean recursed)
|
|
{
|
|
int height, width, border_space, linespace;
|
|
char *tmpstr;
|
|
WINDOW *win = curses_get_nhwin(MESSAGE_WIN);
|
|
boolean bold, border = curses_window_has_border(MESSAGE_WIN);
|
|
int message_length = (int) strlen(message);
|
|
|
|
#if 0
|
|
/*
|
|
* This was useful when curses used genl_putmsghistory() but is not
|
|
* needed now that it has its own curses_putmsghistory() which is
|
|
* capable of putting something into the ^P recall history without
|
|
* displaying it at the same time.
|
|
*/
|
|
if (strncmp("Count:", message, 6) == 0) {
|
|
curses_count_window(message);
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
if (curs_mesg_suppress_turn == moves) {
|
|
return; /* user has typed ESC to avoid seeing remaining messages. */
|
|
}
|
|
|
|
curses_get_window_size(MESSAGE_WIN, &height, &width);
|
|
border_space = (border ? 1 : 0);
|
|
if (mx < border_space)
|
|
mx = border_space;
|
|
if (my < border_space)
|
|
my = border_space;
|
|
|
|
if (strcmp(message, "#") == 0) { /* Extended command or Count: */
|
|
if ((strcmp(toplines, "#") != 0)
|
|
/* Bottom of message window */
|
|
&& (my >= (height - 1 + border_space)) && (height != 1)) {
|
|
scroll_window(MESSAGE_WIN);
|
|
mx = width;
|
|
my--;
|
|
strcpy(toplines, message);
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (!recursed) {
|
|
strcpy(toplines, message);
|
|
mesg_add_line(message);
|
|
}
|
|
|
|
linespace = width - 3 - (mx - border_space);
|
|
|
|
if (linespace < message_length) {
|
|
if (my - border_space >= height - 1) {
|
|
/* bottom of message win */
|
|
if (++turn_lines >= height) { /* || height == 1) */
|
|
/* Pause until key is hit - Esc suppresses any further
|
|
messages that turn */
|
|
if (curses_more() == '\033') {
|
|
curs_mesg_suppress_turn = moves;
|
|
return;
|
|
}
|
|
/* turn_lines reset to 0 by more()->block()->got_input() */
|
|
} else {
|
|
scroll_window(MESSAGE_WIN);
|
|
}
|
|
} else {
|
|
if (mx != border_space) {
|
|
my++;
|
|
mx = border_space;
|
|
++turn_lines;
|
|
}
|
|
}
|
|
}
|
|
|
|
bold = (height > 1 && !last_messages);
|
|
if (bold)
|
|
curses_toggle_color_attr(win, NONE, A_BOLD, ON);
|
|
|
|
/* will this message fit as-is or do we need to split it? */
|
|
if (mx == border_space && message_length > width - 2) {
|
|
/* split needed */
|
|
tmpstr = curses_break_str(message, (width - 2), 1);
|
|
mvwprintw(win, my, mx, "%s", tmpstr), mx += (int) strlen(tmpstr);
|
|
/* one space to separate first part of message from rest [is this
|
|
actually useful?] */
|
|
if (mx < width - 2)
|
|
++mx;
|
|
free(tmpstr);
|
|
if (bold)
|
|
curses_toggle_color_attr(win, NONE, A_BOLD, OFF);
|
|
tmpstr = curses_str_remainder(message, (width - 2), 1);
|
|
curses_message_win_puts(tmpstr, TRUE);
|
|
free(tmpstr);
|
|
} else {
|
|
mvwprintw(win, my, mx, "%s", message), mx += message_length;
|
|
/* two spaces to separate this message from next one if they happen
|
|
to fit on the same line; (FIXME: it would be better if this was
|
|
done at start of next message rather than end of this one since
|
|
it impacts placement of "More>>") */
|
|
if (mx < width - 2) {
|
|
if (++mx < width - 2)
|
|
++mx;
|
|
}
|
|
if (bold)
|
|
curses_toggle_color_attr(win, NONE, A_BOLD, OFF);
|
|
}
|
|
wrefresh(win);
|
|
}
|
|
|
|
void
|
|
curses_got_input(void)
|
|
{
|
|
/* if messages are being suppressed, reenable them */
|
|
curs_mesg_suppress_turn = -1;
|
|
|
|
/* misleadingly named; represents number of lines delivered since
|
|
player was sure to have had a chance to read them; if player
|
|
has just given input then there aren't any such lines right;
|
|
that includes responding to More>> even though it stays same turn */
|
|
turn_lines = 0;
|
|
}
|
|
|
|
int
|
|
curses_block(boolean noscroll) /* noscroll - blocking because of msgtype
|
|
* = stop/alert else blocking because
|
|
* window is full, so need to scroll after */
|
|
{
|
|
static const char resp[] = " \r\n\033"; /* space, enter, esc */
|
|
static int prev_x = -1, prev_y = -1, blink = 0;
|
|
int height, width, moreattr, oldcrsr, ret = 0,
|
|
brdroffset = curses_window_has_border(MESSAGE_WIN) ? 1 : 0;
|
|
WINDOW *win = curses_get_nhwin(MESSAGE_WIN);
|
|
|
|
curses_get_window_size(MESSAGE_WIN, &height, &width);
|
|
if (mx - brdroffset > width - 3) { /* -3: room for ">>_" */
|
|
if (my - brdroffset < height - 1)
|
|
++my, mx = brdroffset;
|
|
else
|
|
mx = width - 3 + brdroffset;
|
|
}
|
|
/* if ">>" (--More--) is being rendered at the same spot as before,
|
|
toggle attributes so that the first '>' starts blinking if it wasn't
|
|
or stops blinking if it was */
|
|
if (mx == prev_x && my == prev_y) {
|
|
blink = 1 - blink;
|
|
} else {
|
|
prev_x = mx, prev_y = my;
|
|
blink = 0;
|
|
}
|
|
moreattr = !iflags.wc2_guicolor ? A_REVERSE : NONE;
|
|
curses_toggle_color_attr(win, MORECOLOR, moreattr, ON);
|
|
if (blink) {
|
|
wattron(win, A_BLINK);
|
|
mvwprintw(win, my, mx, ">"), mx += 1;
|
|
wattroff(win, A_BLINK);
|
|
waddstr(win, ">"), mx += 1;
|
|
} else {
|
|
mvwprintw(win, my, mx, ">>"), mx += 2;
|
|
}
|
|
curses_toggle_color_attr(win, MORECOLOR, moreattr, OFF);
|
|
wrefresh(win);
|
|
|
|
/* cancel mesg suppression; all messages will have had chance to be read */
|
|
curses_got_input();
|
|
|
|
oldcrsr = curs_set(1);
|
|
do {
|
|
ret = wgetch(win);
|
|
if (ret == ERR || ret == '\0')
|
|
ret = '\n';
|
|
/* msgtype=stop should require space/enter rather than any key,
|
|
as we want to prevent YASD from direction keys. */
|
|
} while (!index(resp, (char) ret));
|
|
if (oldcrsr >= 0)
|
|
(void) curs_set(oldcrsr);
|
|
|
|
if (height == 1) {
|
|
curses_clear_unhighlight_message_window();
|
|
} else {
|
|
mx -= 2, mvwprintw(win, my, mx, " "); /* back up and blank out ">>" */
|
|
if (!noscroll) {
|
|
scroll_window(MESSAGE_WIN);
|
|
}
|
|
wrefresh(win);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
int
|
|
curses_more()
|
|
{
|
|
return curses_block(FALSE);
|
|
}
|
|
|
|
|
|
/* Clear the message window if one line; otherwise unhighlight old messages */
|
|
|
|
void
|
|
curses_clear_unhighlight_message_window()
|
|
{
|
|
int mh, mw, count,
|
|
brdroffset = curses_window_has_border(MESSAGE_WIN) ? 1 : 0;
|
|
WINDOW *win = curses_get_nhwin(MESSAGE_WIN);
|
|
|
|
turn_lines = 0;
|
|
curses_get_window_size(MESSAGE_WIN, &mh, &mw);
|
|
|
|
if (mh == 1) {
|
|
curses_clear_nhwin(MESSAGE_WIN);
|
|
mx = my = brdroffset;
|
|
} else {
|
|
mx = mw + brdroffset; /* Force new line on new turn */
|
|
|
|
for (count = 0; count < mh; count++)
|
|
mvwchgat(win, count + brdroffset, brdroffset,
|
|
mw, COLOR_PAIR(8), A_NORMAL, NULL);
|
|
wnoutrefresh(win);
|
|
}
|
|
wmove(win, my, mx);
|
|
}
|
|
|
|
|
|
/* Reset message window cursor to starting position, and display most
|
|
recent messages. */
|
|
|
|
void
|
|
curses_last_messages()
|
|
{
|
|
boolean border = curses_window_has_border(MESSAGE_WIN);
|
|
nhprev_mesg *mesg;
|
|
int i, j, height, width;
|
|
|
|
curses_get_window_size(MESSAGE_WIN, &height, &width);
|
|
|
|
if (border)
|
|
mx = my = 1;
|
|
else
|
|
mx = my = 0;
|
|
|
|
++last_messages;
|
|
for (j = 0, i = num_messages - 1; i > 0 && j < height; --i, ++j) {
|
|
mesg = get_msg_line(TRUE, i);
|
|
if (mesg && mesg->str && *mesg->str)
|
|
curses_message_win_puts(mesg->str, TRUE);
|
|
}
|
|
curses_message_win_puts(toplines, TRUE);
|
|
--last_messages;
|
|
}
|
|
|
|
|
|
/* Initialize list for message history */
|
|
|
|
void
|
|
curses_init_mesg_history()
|
|
{
|
|
max_messages = iflags.msg_history;
|
|
|
|
if (max_messages < 1) {
|
|
max_messages = 1;
|
|
}
|
|
|
|
if (max_messages > MESG_HISTORY_MAX) {
|
|
max_messages = MESG_HISTORY_MAX;
|
|
}
|
|
}
|
|
|
|
/* Delete message history at game end. */
|
|
|
|
void
|
|
curses_teardown_messages(void)
|
|
{
|
|
nhprev_mesg *current_mesg;
|
|
|
|
while ((current_mesg = first_mesg) != 0) {
|
|
first_mesg = current_mesg->next_mesg;
|
|
free(current_mesg->str);
|
|
free(current_mesg);
|
|
}
|
|
last_mesg = (nhprev_mesg *) 0;
|
|
num_messages = 0;
|
|
}
|
|
|
|
/* Display previous message window messages in reverse chron order */
|
|
|
|
void
|
|
curses_prev_mesg()
|
|
{
|
|
int count;
|
|
winid wid;
|
|
long turn = 0;
|
|
anything Id;
|
|
nhprev_mesg *mesg;
|
|
menu_item *selected = NULL;
|
|
boolean do_lifo = (iflags.prevmsg_window != 'f');
|
|
|
|
wid = curses_get_wid(NHW_MENU);
|
|
curses_create_nhmenu(wid);
|
|
Id = zeroany;
|
|
|
|
for (count = 0; count < num_messages; ++count) {
|
|
mesg = get_msg_line(do_lifo, count);
|
|
if (turn != mesg->turn && count != 0) {
|
|
curses_add_menu(wid, NO_GLYPH, &Id, 0, 0, A_NORMAL, "---", FALSE);
|
|
}
|
|
curses_add_menu(wid, NO_GLYPH, &Id, 0, 0, A_NORMAL, mesg->str, FALSE);
|
|
turn = mesg->turn;
|
|
}
|
|
if (!count)
|
|
curses_add_menu(wid, NO_GLYPH, &Id, 0, 0, A_NORMAL,
|
|
"[No past messages available.]", FALSE);
|
|
|
|
curses_end_menu(wid, "");
|
|
if (!do_lifo)
|
|
curs_menu_set_bottom_heavy(wid);
|
|
curses_select_menu(wid, PICK_NONE, &selected);
|
|
curses_del_wid(wid);
|
|
}
|
|
|
|
|
|
/* Display at the bottom of the message window without remembering the
|
|
line for ^P recall. Used for putstr(WIN_MESSAGE,ATR_NOHISTORY,text)
|
|
which core currently uses for 'Count: 123' and dolook's autodescribe.
|
|
popup_dialog is not currently implemented for this function. */
|
|
|
|
void
|
|
curses_count_window(const char *count_text)
|
|
{
|
|
static WINDOW *countwin = NULL;
|
|
int startx, starty, winx, winy;
|
|
int messageh, messagew;
|
|
|
|
if (!count_text) {
|
|
if (countwin)
|
|
delwin(countwin), countwin = NULL;
|
|
counting = FALSE;
|
|
return;
|
|
}
|
|
counting = TRUE;
|
|
|
|
curses_get_window_xy(MESSAGE_WIN, &winx, &winy);
|
|
curses_get_window_size(MESSAGE_WIN, &messageh, &messagew);
|
|
|
|
if (curses_window_has_border(MESSAGE_WIN)) {
|
|
winx++;
|
|
winy++;
|
|
}
|
|
|
|
winy += messageh - 1;
|
|
|
|
#ifdef PDCURSES
|
|
if (countwin)
|
|
curses_destroy_win(countwin), countwin = NULL;
|
|
#endif /* PDCURSES */
|
|
/* this used to specify a width of 25; that was adequate for 'Count: 123'
|
|
but not for dolook's autodescribe when it refers to a named monster */
|
|
if (!countwin)
|
|
countwin = newwin(1, messagew, winy, winx);
|
|
startx = 0;
|
|
starty = 0;
|
|
|
|
mvwprintw(countwin, starty, startx, "%s", count_text);
|
|
wrefresh(countwin);
|
|
}
|
|
|
|
/* Gets a "line" (buffer) of input. */
|
|
void
|
|
curses_message_win_getline(const char *prompt, char *answer, int buffer)
|
|
{
|
|
int height, width; /* of window */
|
|
char *tmpbuf, *p_answer; /* combined prompt + answer */
|
|
int nlines, maxlines, i; /* prompt + answer */
|
|
int promptline;
|
|
int promptx;
|
|
char **linestarts; /* pointers to start of each line */
|
|
char *tmpstr; /* for free() */
|
|
int maxy, maxx; /* linewrap / scroll */
|
|
int ch;
|
|
WINDOW *win = curses_get_nhwin(MESSAGE_WIN);
|
|
int border_space = 0;
|
|
int len = 0; /* of answer string */
|
|
boolean border = curses_window_has_border(MESSAGE_WIN);
|
|
|
|
*answer = '\0';
|
|
orig_cursor = curs_set(0);
|
|
|
|
curses_get_window_size(MESSAGE_WIN, &height, &width);
|
|
if (border) {
|
|
height -= 2, width -= 2;
|
|
border_space = 1;
|
|
if (mx < 1)
|
|
mx = 1;
|
|
if (my < 1)
|
|
my = 1;
|
|
}
|
|
maxy = height - 1 + border_space;
|
|
maxx = width - 1 + border_space;
|
|
|
|
tmpbuf = (char *) alloc((unsigned) ((int) strlen(prompt) + buffer + 2));
|
|
maxlines = buffer / width * 2;
|
|
Strcpy(tmpbuf, prompt);
|
|
Strcat(tmpbuf, " ");
|
|
nlines = curses_num_lines(tmpbuf, width);
|
|
maxlines += nlines * 2;
|
|
linestarts = (char **) alloc((unsigned) (maxlines * sizeof (char *)));
|
|
p_answer = tmpbuf + strlen(tmpbuf);
|
|
linestarts[0] = tmpbuf;
|
|
|
|
if (mx > border_space) { /* newline */
|
|
if (my >= maxy)
|
|
scroll_window(MESSAGE_WIN);
|
|
else
|
|
my++;
|
|
mx = border_space;
|
|
}
|
|
|
|
curses_toggle_color_attr(win, NONE, A_BOLD, ON);
|
|
|
|
for (i = 0; i < nlines - 1; i++) {
|
|
tmpstr = curses_break_str(linestarts[i], width - 1, 1);
|
|
linestarts[i + 1] = linestarts[i] + (int) strlen(tmpstr);
|
|
if (*linestarts[i + 1] == ' ')
|
|
linestarts[i + 1]++;
|
|
mvwaddstr(win, my, mx, tmpstr);
|
|
free(tmpstr);
|
|
if (++my >= maxy) {
|
|
scroll_window(MESSAGE_WIN);
|
|
my--;
|
|
}
|
|
}
|
|
mvwaddstr(win, my, mx, linestarts[nlines - 1]);
|
|
mx = promptx = (int) strlen(linestarts[nlines - 1]) + border_space;
|
|
promptline = nlines - 1;
|
|
|
|
while (1) {
|
|
mx = (int) strlen(linestarts[nlines - 1]) + border_space;
|
|
if (mx > maxx) {
|
|
if (nlines < maxlines) {
|
|
tmpstr = curses_break_str(linestarts[nlines - 1],
|
|
width - 1, 1);
|
|
mx = (int) strlen(tmpstr) + border_space;
|
|
mvwprintw(win, my, mx, "%*c", maxx - mx + 1, ' ');
|
|
if (++my > maxy) {
|
|
scroll_window(MESSAGE_WIN);
|
|
my--;
|
|
}
|
|
mx = border_space;
|
|
linestarts[nlines] = linestarts[nlines - 1]
|
|
+ (int) strlen(tmpstr);
|
|
if (*linestarts[nlines] == ' ')
|
|
linestarts[nlines]++;
|
|
mvwaddstr(win, my, mx, linestarts[nlines]);
|
|
mx = (int) strlen(linestarts[nlines]) + border_space;
|
|
nlines++;
|
|
free(tmpstr);
|
|
} else {
|
|
p_answer[--len] = '\0';
|
|
mvwaddch(win, my, --mx, ' ');
|
|
}
|
|
}
|
|
wmove(win, my, mx);
|
|
curs_set(1);
|
|
wrefresh(win);
|
|
curses_got_input(); /* despite its name, before rathre than after... */
|
|
#ifdef PDCURSES
|
|
ch = wgetch(win);
|
|
#else
|
|
ch = getch();
|
|
#endif
|
|
#if 0 /* [erase_char (delete one character) and kill_char (delete all
|
|
* characters) are from tty and not currently set up for curses] */
|
|
if (ch == erase_char) {
|
|
ch = '\177'; /* match switch-case below */
|
|
|
|
/* honor kill_char if it's ^U or similar, but not if it's '@' */
|
|
} else if (ch == kill_char && (ch < ' ' || ch >= '\177')) { /*ASCII*/
|
|
if (len == 0) /* nothing to kill; just start over */
|
|
continue;
|
|
ch = '\033'; /* get rid of all current input, then start over */
|
|
}
|
|
#endif
|
|
curs_set(0);
|
|
switch (ch) {
|
|
case '\033': /* DOESCAPE */
|
|
/* if there isn't any input yet, return ESC */
|
|
if (len == 0) {
|
|
Strcpy(answer, "\033");
|
|
return;
|
|
}
|
|
/* otherwise, discard current input and start over;
|
|
first need to blank it from the screen */
|
|
while (nlines - 1 > promptline) {
|
|
if (nlines-- > height) {
|
|
unscroll_window(MESSAGE_WIN);
|
|
tmpstr = curses_break_str(linestarts[nlines - height],
|
|
width - 1, 1);
|
|
mvwaddstr(win, border_space, border_space, tmpstr);
|
|
free(tmpstr);
|
|
} else {
|
|
mx = border_space;
|
|
mvwprintw(win, my, mx, "%*c", maxx - mx, ' ');
|
|
my--;
|
|
}
|
|
}
|
|
mx = promptx;
|
|
mvwprintw(win, my, mx, "%*c", maxx - mx, ' ');
|
|
*p_answer = '\0';
|
|
len = 0;
|
|
break;
|
|
case ERR: /* should not happen */
|
|
*answer = '\0';
|
|
free(tmpbuf);
|
|
free(linestarts);
|
|
curs_set(orig_cursor);
|
|
curses_toggle_color_attr(win, NONE, A_BOLD, OFF);
|
|
return;
|
|
case '\r':
|
|
case '\n':
|
|
free(linestarts);
|
|
(void) strncpy(answer, p_answer, buffer);
|
|
answer[buffer - 1] = '\0';
|
|
Strcpy(toplines, tmpbuf);
|
|
mesg_add_line(tmpbuf);
|
|
free(tmpbuf);
|
|
curs_set(orig_cursor);
|
|
curses_toggle_color_attr(win, NONE, A_BOLD, OFF);
|
|
if (++my > maxy) {
|
|
scroll_window(MESSAGE_WIN);
|
|
my--;
|
|
}
|
|
mx = border_space;
|
|
return;
|
|
case '\177': /* DEL/Rubout */
|
|
case KEY_DC: /* delete-character */
|
|
case '\b': /* ^H (Backspace: '\011') */
|
|
case KEY_BACKSPACE:
|
|
if (len < 1) {
|
|
len = 1;
|
|
mx = promptx;
|
|
}
|
|
p_answer[--len] = '\0';
|
|
mvwaddch(win, my, --mx, ' ');
|
|
/* try to unwrap back to the previous line if there is one */
|
|
if (nlines > 1 && (int) strlen(linestarts[nlines - 2]) < width) {
|
|
mvwaddstr(win, my - 1, border_space, linestarts[nlines - 2]);
|
|
if (nlines-- > height) {
|
|
unscroll_window(MESSAGE_WIN);
|
|
tmpstr = curses_break_str(linestarts[nlines - height],
|
|
width - 1, 1);
|
|
mvwaddstr(win, border_space, border_space, tmpstr);
|
|
free(tmpstr);
|
|
} else {
|
|
/* clean up the leftovers on the next line,
|
|
if we didn't scroll it away */
|
|
mvwprintw(win, my--, border_space, "%*c",
|
|
(int) strlen(linestarts[nlines]), ' ');
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
p_answer[len++] = ch;
|
|
if (len >= buffer)
|
|
len = buffer - 1;
|
|
else
|
|
mvwaddch(win, my, mx, ch);
|
|
p_answer[len] = '\0';
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Scroll lines upward in given window, or clear window if only one line. */
|
|
static void
|
|
scroll_window(winid wid)
|
|
{
|
|
directional_scroll(wid, 1);
|
|
}
|
|
|
|
static void
|
|
unscroll_window(winid wid)
|
|
{
|
|
directional_scroll(wid, -1);
|
|
}
|
|
|
|
static void
|
|
directional_scroll(winid wid, int nlines)
|
|
{
|
|
int wh, ww, s_top, s_bottom;
|
|
boolean border = curses_window_has_border(wid);
|
|
WINDOW *win = curses_get_nhwin(wid);
|
|
|
|
curses_get_window_size(wid, &wh, &ww);
|
|
if (wh == 1) {
|
|
curses_clear_nhwin(wid);
|
|
return;
|
|
}
|
|
if (border) {
|
|
s_top = 1;
|
|
s_bottom = wh;
|
|
} else {
|
|
s_top = 0;
|
|
s_bottom = wh - 1;
|
|
}
|
|
scrollok(win, TRUE);
|
|
wsetscrreg(win, s_top, s_bottom);
|
|
wscrl(win, nlines);
|
|
scrollok(win, FALSE);
|
|
if (wid == MESSAGE_WIN) {
|
|
if (border)
|
|
mx = 1;
|
|
else
|
|
mx = 0;
|
|
}
|
|
if (border) {
|
|
box(win, 0, 0);
|
|
}
|
|
wrefresh(win);
|
|
}
|
|
|
|
|
|
/* Add given line to message history */
|
|
|
|
static void
|
|
mesg_add_line(const char *mline)
|
|
{
|
|
nhprev_mesg *current_mesg;
|
|
|
|
/*
|
|
* Messages are kept in a doubly linked list, with head 'first_mesg',
|
|
* tail 'last_mesg', and a maximum capacity of 'max_messages'.
|
|
*/
|
|
if (num_messages < max_messages) {
|
|
/* create a new list element */
|
|
current_mesg = (nhprev_mesg *) alloc((unsigned) sizeof (nhprev_mesg));
|
|
current_mesg->str = dupstr(mline);
|
|
} else {
|
|
/* instead of discarding list element being forced out, reuse it */
|
|
current_mesg = first_mesg;
|
|
/* whenever new 'mline' is shorter, extra allocation size of the
|
|
original element will be frittered away, until eventually we'll
|
|
discard this 'str' and dupstr() a replacement; we could easily
|
|
track the allocation size but don't really need to do so */
|
|
if (strlen(mline) <= strlen(current_mesg->str)) {
|
|
Strcpy(current_mesg->str, mline);
|
|
} else {
|
|
free((genericptr_t) current_mesg->str);
|
|
current_mesg->str = dupstr(mline);
|
|
}
|
|
}
|
|
current_mesg->turn = moves;
|
|
|
|
if (num_messages == 0) {
|
|
/* very first message; set up head */
|
|
first_mesg = current_mesg;
|
|
} else {
|
|
/* not first message; tail exists */
|
|
last_mesg->next_mesg = current_mesg;
|
|
}
|
|
current_mesg->prev_mesg = last_mesg;
|
|
last_mesg = current_mesg; /* new tail */
|
|
|
|
if (num_messages < max_messages) {
|
|
/* wasn't at capacity yet */
|
|
++num_messages;
|
|
} else {
|
|
/* at capacity; old head is being removed */
|
|
first_mesg = first_mesg->next_mesg; /* new head */
|
|
first_mesg->prev_mesg = NULL; /* head has no prev_mesg */
|
|
}
|
|
/* since 'current_mesg' might be reusing 'first_mesg' and has now
|
|
become 'last_mesg', this update must be after head replacement */
|
|
last_mesg->next_mesg = NULL; /* tail has no next_mesg */
|
|
}
|
|
|
|
|
|
/* Returns specified line from message history, or NULL if out of bounds */
|
|
|
|
static nhprev_mesg *
|
|
get_msg_line(boolean reverse, int mindex)
|
|
{
|
|
int count;
|
|
nhprev_mesg *current_mesg;
|
|
|
|
if (reverse) {
|
|
current_mesg = last_mesg;
|
|
for (count = 0; count < mindex; count++) {
|
|
if (!current_mesg)
|
|
break;
|
|
current_mesg = current_mesg->prev_mesg;
|
|
}
|
|
} else {
|
|
current_mesg = first_mesg;
|
|
for (count = 0; count < mindex; count++) {
|
|
if (!current_mesg)
|
|
break;
|
|
current_mesg = current_mesg->next_mesg;
|
|
}
|
|
}
|
|
return current_mesg;
|
|
}
|
|
|
|
/* save/restore code retrieves one ^P message at a time during save and
|
|
puts it into save file; if any new messages are added to the list while
|
|
that is taking place, the results are likely to be scrambled */
|
|
char *
|
|
curses_getmsghistory(init)
|
|
boolean init;
|
|
{
|
|
static int nxtidx;
|
|
nhprev_mesg *mesg;
|
|
|
|
if (init)
|
|
nxtidx = 0;
|
|
else
|
|
++nxtidx;
|
|
|
|
if (nxtidx < num_messages) {
|
|
/* we could encode mesg->turn with the text of the message,
|
|
but then that text might need to be truncated, and more
|
|
significantly, restoring the save file with another
|
|
interface wouldn't know how find and decode or remove it;
|
|
likewise, restoring another interface's save file with
|
|
curses wouldn't find the expected turn info;
|
|
so, we live without that */
|
|
mesg = get_msg_line(FALSE, nxtidx);
|
|
} else
|
|
mesg = (nhprev_mesg *) 0;
|
|
|
|
return mesg ? mesg->str : (char *) 0;
|
|
}
|
|
|
|
/*
|
|
* This is called by the core savefile restore routines.
|
|
* Each time we are called, we stuff the string into our message
|
|
* history recall buffer. The core will send the oldest message
|
|
* first (actually it sends them in the order they exist in the
|
|
* save file, but that is supposed to be the oldest first).
|
|
* These messages get pushed behind any which have been issued
|
|
* during this session since they come from a previous session
|
|
* and logically precede anything (like "Restoring save file...")
|
|
* that's happened now.
|
|
*
|
|
* Called with a null pointer to finish up restoration.
|
|
*
|
|
* It's also called by the quest pager code when a block message
|
|
* has a one-line summary specified. We put that line directly
|
|
* into message history for ^P recall without having displayed it.
|
|
*/
|
|
void
|
|
curses_putmsghistory(msg, restoring_msghist)
|
|
const char *msg;
|
|
boolean restoring_msghist;
|
|
{
|
|
static boolean initd = FALSE;
|
|
static int stash_count;
|
|
static nhprev_mesg *stash_head = 0;
|
|
|
|
if (restoring_msghist && !initd) {
|
|
/* hide any messages we've gathered since starting current session
|
|
so that the ^P data will start out empty as we add ones being
|
|
restored from save file; we'll put these back after that's done */
|
|
stash_count = num_messages, num_messages = 0;
|
|
stash_head = first_mesg, first_mesg = (nhprev_mesg *) 0;
|
|
last_mesg = (nhprev_mesg *) 0; /* no need to remember the tail */
|
|
initd = TRUE;
|
|
}
|
|
|
|
if (msg) {
|
|
mesg_add_line(msg);
|
|
/* treat all saved and restored messages as turn #1 */
|
|
last_mesg->turn = 1L;
|
|
} else if (stash_count) {
|
|
nhprev_mesg *mesg;
|
|
long mesg_turn;
|
|
|
|
/* put any messages generated during the beginning of the current
|
|
session back; they logically follow any from the previous
|
|
session's save file */
|
|
while (stash_count > 0) {
|
|
/* we could manipulate the linked list directly but treating
|
|
stashed messages as newly occurring ones is much simpler;
|
|
we ignore the backlinks because the list is destroyed as it
|
|
gets processed hence there can't be any other traversals */
|
|
mesg = stash_head;
|
|
stash_head = mesg->next_mesg;
|
|
--stash_count;
|
|
mesg_turn = mesg->turn;
|
|
mesg_add_line(mesg->str);
|
|
/* added line became new tail */
|
|
last_mesg->turn = mesg_turn;
|
|
free((genericptr_t) mesg->str);
|
|
free((genericptr_t) mesg);
|
|
}
|
|
initd = FALSE; /* reset */
|
|
}
|
|
}
|
|
|
|
/*cursmesg.c*/
|