From the newsgroup: identifying by menu pops up multiple menus in succession if the player picks fewer invent entries than are being granted, but the second and subsequent ones could cover up the message window and hide the feedback from prior ones. If multiple popup menus are needed when identifying, issue --More-- before each menu after the first. The code seemed to be trying to do this already, but it should have used wait_synch() rather than mark_synch(), or perhaps used display_nhwindow(WIN_MESSAGE, TRUE) instead of either one of those. For curses, both mark_synch() and wait_synch() were no-ops. Now they do something. X11's behavior wasn't right either; it seemed to be lagging one message behind (something I had noticed recently and then forgotten about; I still don't remember the context then so don't know whether this fixes that earlier situation).
988 lines
32 KiB
C
988 lines
32 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.7 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>
|
|
|
|
/* defined in sys/<foo>/<foo>tty.c or cursmain.c as last resort;
|
|
set up by curses_init_nhwindows() */
|
|
extern char erase_char, kill_char;
|
|
|
|
/*
|
|
* 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_seq = -1L;
|
|
/* if a message is marked urgent, existing suppression will be overridden
|
|
so that messages resume being shown; this is used in case the urgent
|
|
message triggers More>> for the previous message and the player responds
|
|
with ESC; we need to avoid initiating suppression in that situation */
|
|
boolean curs_mesg_no_suppress = FALSE;
|
|
|
|
/* 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_seq == gh.hero_seq) {
|
|
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(gt.toplines, "#") != 0)
|
|
/* Bottom of message window */
|
|
&& (my >= (height - 1 + border_space)) && (height != 1)) {
|
|
scroll_window(MESSAGE_WIN);
|
|
mx = width;
|
|
my--;
|
|
Strcpy(gt.toplines, message);
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (!recursed) {
|
|
strcpy(gt.toplines, message);
|
|
mesg_add_line(message);
|
|
}
|
|
|
|
/* -2: room for trailing ">>" (if More>> is needed) or leading " "
|
|
(if combining this message with preceding one) */
|
|
linespace = (width - 1) - 2 - (mx - border_space);
|
|
|
|
if (linespace < message_length) {
|
|
if (my - border_space >= height - 1) {
|
|
/* bottom of message win */
|
|
if (++turn_lines > height
|
|
|| (turn_lines == height && mx > border_space)) {
|
|
/* pause until key is hit - ESC suppresses further messages
|
|
this turn unless an urgent message is being delivered */
|
|
if (curses_more() == '\033'
|
|
&& !curs_mesg_no_suppress) {
|
|
curs_mesg_suppress_seq = gh.hero_seq;
|
|
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;
|
|
}
|
|
}
|
|
} else { /* don't need to move to next line */
|
|
/* if we aren't at the start of the line, we're combining multiple
|
|
messages on one line; use 2-space separation */
|
|
if (mx > border_space) {
|
|
waddstr(win, " ");
|
|
mx += 2;
|
|
}
|
|
}
|
|
|
|
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;
|
|
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_seq = -1L;
|
|
|
|
/* 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 now;
|
|
that includes responding to More>> even though it stays same turn */
|
|
turn_lines = 0;
|
|
}
|
|
|
|
int
|
|
curses_got_output(void)
|
|
{
|
|
return turn_lines;
|
|
}
|
|
|
|
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 ? (int) 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 {
|
|
if (iflags.debug_fuzzer)
|
|
ret = '\n';
|
|
else
|
|
ret = curses_read_char();
|
|
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 (!strchr(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(void)
|
|
{
|
|
return curses_block(FALSE);
|
|
}
|
|
|
|
|
|
/* Clear the message window if one line; otherwise unhighlight old messages */
|
|
|
|
void
|
|
curses_clear_unhighlight_message_window(void)
|
|
{
|
|
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(void)
|
|
{
|
|
nhprev_mesg *mesg;
|
|
int i, height, width;
|
|
int border = curses_window_has_border(MESSAGE_WIN) ? 1 : 0;
|
|
WINDOW *win = curses_get_nhwin(MESSAGE_WIN);
|
|
|
|
curses_get_window_size(MESSAGE_WIN, &height, &width);
|
|
werase(win);
|
|
mx = my = border;
|
|
|
|
/*
|
|
* FIXME!
|
|
* This shouldn't be relying on a naive line count to decide where
|
|
* to start and stop because curses_message_win_puts() combines short
|
|
* lines. So we can end up with blank lines at bottom of the message
|
|
* window, missing out on one or more older messages which could have
|
|
* been included at the top. Also long messages might wrap and take
|
|
* more than one line apiece.
|
|
*
|
|
* 3.6.2 showed oldest available N-1 lines (by starting at
|
|
* num_mesages - 1 and working back toward 0 until window height was
|
|
* reached [via index 'j' which is gone now]) plus the latest line
|
|
* (via toplines[]), rather than most recent N (start at height - 1
|
|
* and work way up through 0). So it showed wrong subset of lines
|
|
* even if 'N lines' had been the right way to handle this.
|
|
*/
|
|
++last_messages;
|
|
for (i = min(height, num_messages) - 1; i > 0; --i) {
|
|
mesg = get_msg_line(TRUE, i);
|
|
if (mesg && mesg->str && *mesg->str)
|
|
curses_message_win_puts(mesg->str, TRUE);
|
|
}
|
|
curses_message_win_puts(gt.toplines, TRUE);
|
|
--last_messages;
|
|
|
|
if (border)
|
|
box(win, 0, 0);
|
|
wrefresh(win);
|
|
}
|
|
|
|
|
|
/* Initialize list for message history */
|
|
|
|
void
|
|
curses_init_mesg_history(void)
|
|
{
|
|
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 messages in a popup (via menu so can scroll backwards) */
|
|
|
|
void
|
|
curses_prev_mesg(void)
|
|
{
|
|
int count;
|
|
winid wid;
|
|
long turn = 0;
|
|
anything Id;
|
|
nhprev_mesg *mesg;
|
|
menu_item *selected = NULL;
|
|
boolean do_lifo = (iflags.prevmsg_window != 'f');
|
|
#ifdef DEBUG
|
|
static int showturn = 0; /* 1: show hero_seq value in separators */
|
|
int clr = 0;
|
|
|
|
/*
|
|
* Set DEBUGFILES=MesgTurn in environment or sysconf to decorate
|
|
* separator line between blocks of messages with the turn they
|
|
* were issued.
|
|
*/
|
|
if (!showturn)
|
|
showturn = (wizard && explicitdebug("MesgTurn")) ? 1 : -1;
|
|
#endif
|
|
|
|
wid = curses_get_wid(NHW_MENU);
|
|
curses_create_nhmenu(wid, 0UL);
|
|
Id = cg.zeroany;
|
|
|
|
for (count = 0; count < num_messages; ++count) {
|
|
mesg = get_msg_line(do_lifo, count);
|
|
if (mesg->turn != turn) {
|
|
if (count > 0) { /* skip separator for first line */
|
|
char sepbuf[50];
|
|
|
|
Strcpy(sepbuf, "---");
|
|
#ifdef DEBUG
|
|
if (showturn == 1)
|
|
Sprintf(sepbuf, "- %ld+%ld",
|
|
(mesg->turn >> 3), (mesg->turn & 7L));
|
|
#endif
|
|
curses_add_menu(wid, &nul_glyphinfo, &Id, 0, 0,
|
|
A_NORMAL, clr, sepbuf, MENU_ITEMFLAGS_NONE);
|
|
}
|
|
turn = mesg->turn;
|
|
}
|
|
curses_add_menu(wid, &nul_glyphinfo, &Id, 0, 0,
|
|
A_NORMAL, clr, mesg->str, MENU_ITEMFLAGS_NONE);
|
|
}
|
|
if (!count)
|
|
curses_add_menu(wid, &nul_glyphinfo, &Id, 0, 0,
|
|
A_NORMAL, clr, "[No past messages available.]",
|
|
MENU_ITEMFLAGS_NONE);
|
|
|
|
curses_end_menu(wid, "");
|
|
if (!do_lifo)
|
|
curs_menu_set_bottom_heavy(wid);
|
|
curses_select_menu(wid, PICK_NONE, &selected);
|
|
if (selected) /* should always be null for PICK_NONE but be paranoid */
|
|
free((genericptr_t) 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 winx, winy;
|
|
int messageh, messagew, border;
|
|
|
|
if (!count_text) {
|
|
if (countwin)
|
|
delwin(countwin), countwin = NULL;
|
|
counting = FALSE;
|
|
return;
|
|
}
|
|
|
|
/* position of message window, not current position within message window
|
|
(so <0,0> for align_message:Top but will vary for other alignings) */
|
|
curses_get_window_xy(MESSAGE_WIN, &winx, &winy);
|
|
/* size of message window, with space for borders already subtracted */
|
|
curses_get_window_size(MESSAGE_WIN, &messageh, &messagew);
|
|
|
|
/* decide where to put the one-line counting window */
|
|
border = curses_window_has_border(MESSAGE_WIN) ? 1 : 0;
|
|
winx += border; /* first writeable message column */
|
|
winy += border + (messageh - 1); /* last writable message line */
|
|
|
|
/* if most recent message (probably prompt leading to this instance of
|
|
counting window) is going to be covered up, scroll mesgs up a line */
|
|
if (!counting && my == border + (messageh - 1) && mx > border) {
|
|
scroll_window(MESSAGE_WIN);
|
|
if (messageh > 1) {
|
|
/* handling for next message will behave as if we're currently
|
|
positioned at the end of next to last line of message window */
|
|
my = border + (messageh - 1) - 1;
|
|
mx = border + (messagew - 1); /* (0 + 80 - 1) or (1 + 78 - 1) */
|
|
} else {
|
|
/* for a one-line window, use beginning of only line instead */
|
|
my = mx = border; /* 0 or 1 */
|
|
}
|
|
/* wmove(curses_get_nhwin(MESSAGE_WIN), my, mx); -- not needed */
|
|
}
|
|
/* in case we're being called from clear_nhwindow(MESSAGE_WIN)
|
|
which gets called for every command keystroke; it sends an
|
|
empty string to get the scroll-up-one-line effect above and
|
|
we want to avoid the curses overhead for the operations below... */
|
|
if (!*count_text)
|
|
return;
|
|
|
|
counting = TRUE;
|
|
#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);
|
|
werase(countwin);
|
|
|
|
mvwprintw(countwin, 0, 0, "%s", count_text);
|
|
wrefresh(countwin);
|
|
if (activemenu) {
|
|
touchwin(activemenu);
|
|
wrefresh(activemenu);
|
|
}
|
|
}
|
|
|
|
/* 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;
|
|
int border_space = 0;
|
|
int ltmp, len; /* of answer string */
|
|
boolean border = curses_window_has_border(MESSAGE_WIN);
|
|
WINDOW *win = curses_get_nhwin(MESSAGE_WIN);
|
|
|
|
orig_cursor = curs_set(0);
|
|
|
|
curses_get_window_size(MESSAGE_WIN, &height, &width);
|
|
if (border) {
|
|
/* height -= 2, width -= 2; -- sizes already account for border */
|
|
border_space = 1;
|
|
if (mx < 1)
|
|
mx = 1;
|
|
if (my < 1)
|
|
my = 1;
|
|
}
|
|
maxy = height - 1 + border_space;
|
|
maxx = width - 1 + border_space;
|
|
|
|
/* +2? buffer already includes room for terminator; +1: "prompt answer" */
|
|
tmpbuf = (char *) alloc((unsigned) ((int) strlen(prompt) + buffer + 2));
|
|
maxlines = buffer / width * 2;
|
|
Strcpy(tmpbuf, prompt);
|
|
Strcat(tmpbuf, " ");
|
|
p_answer = tmpbuf + strlen(tmpbuf);
|
|
#ifdef EDIT_GETLIN
|
|
len = (int) strlen(answer);
|
|
if (len >= buffer) {
|
|
len = buffer - 1;
|
|
answer[len] = '\0';
|
|
}
|
|
Strcpy(p_answer, answer);
|
|
#else
|
|
len = 0;
|
|
*answer = '\0';
|
|
#endif
|
|
nlines = curses_num_lines(tmpbuf, width);
|
|
maxlines += nlines * 2;
|
|
linestarts = (char **) alloc((unsigned) (maxlines * sizeof (char *)));
|
|
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--;
|
|
}
|
|
}
|
|
promptline = nlines - 1;
|
|
mvwaddstr(win, my, mx, linestarts[promptline]);
|
|
ltmp = (int) strlen(linestarts[promptline]);
|
|
mx = promptx = ltmp + border_space;
|
|
#ifdef EDIT_GETLIN
|
|
if (len <= ltmp) {
|
|
/* preloaded answer fits on same line as [last line of] prompt */
|
|
promptx -= len;
|
|
} else {
|
|
int ltmp2 = len;
|
|
|
|
/* preloaded answer spans lines so will be trickier to erase
|
|
if that is called for; find where the end of the prompt will
|
|
be without the answer appended */
|
|
while (ltmp2 > 0) {
|
|
if ((ltmp2 -= ltmp) < 0) {
|
|
ltmp = -ltmp2;
|
|
break;
|
|
}
|
|
promptline -= 1;
|
|
ltmp = linestarts[promptline + 1] - linestarts[promptline];
|
|
}
|
|
promptx = ltmp + border_space;
|
|
}
|
|
#endif
|
|
|
|
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 rather than after... */
|
|
#ifdef PDCURSES
|
|
ch = wgetch(win);
|
|
#else
|
|
ch = curses_read_char();
|
|
#endif
|
|
curs_set(0);
|
|
|
|
if (erase_char && ch == (int) (uchar) erase_char) {
|
|
ch = '\177'; /* match switch-case below */
|
|
|
|
/* honor kill_char if it's ^U or similar, but not if it's '@' */
|
|
} else if (kill_char && ch == (int) (uchar) 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 */
|
|
}
|
|
|
|
switch (ch) {
|
|
case ERR: /* should not happen */
|
|
*answer = '\0';
|
|
goto alldone;
|
|
case '\033': /* DOESCAPE */
|
|
/* if there isn't any input yet, return ESC */
|
|
if (len == 0) {
|
|
Strcpy(answer, "\033");
|
|
goto alldone;
|
|
}
|
|
/* 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 '\r':
|
|
case '\n':
|
|
(void) strncpy(answer, p_answer, buffer);
|
|
answer[buffer - 1] = '\0';
|
|
Strcpy(gt.toplines, tmpbuf);
|
|
mesg_add_line(tmpbuf);
|
|
#if 1
|
|
/* position at end of current line so next message will be
|
|
written on next line regardless of whether it could fit here */
|
|
mx = border_space ? (width + 1) : (width - 1);
|
|
wmove(win, my, mx);
|
|
#else /* after various other changes, this resulted in getline()
|
|
* prompt+answer being following by a blank message line */
|
|
if (++my > maxy) {
|
|
scroll_window(MESSAGE_WIN);
|
|
my--;
|
|
}
|
|
mx = border_space;
|
|
#endif /*0*/
|
|
goto alldone;
|
|
case '\177': /* DEL/Rubout */
|
|
case KEY_DC: /* delete-character */
|
|
case '\b': /* ^H (Backspace: '\010') */
|
|
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';
|
|
}
|
|
}
|
|
|
|
alldone:
|
|
free(linestarts);
|
|
free(tmpbuf);
|
|
curses_toggle_color_attr(win, NONE, A_BOLD, OFF);
|
|
curs_set(orig_cursor);
|
|
return;
|
|
}
|
|
|
|
/* 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) {
|
|
mx = border ? 1 : 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);
|
|
current_mesg->next_mesg = current_mesg->prev_mesg = (nhprev_mesg *) 0;
|
|
} 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 = gh.hero_seq;
|
|
|
|
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(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(const char *msg, boolean restoring_msghist)
|
|
{
|
|
/* FIXME: these should be moved to struct instance_globals g */
|
|
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;
|
|
#ifdef DUMPLOG
|
|
/* this suffices; there's no need to scrub g.saved_pline[] pointers */
|
|
gs.saved_pline_index = 0;
|
|
#endif
|
|
}
|
|
|
|
if (msg) {
|
|
mesg_add_line(msg);
|
|
/* treat all saved and restored messages as turn #1;
|
|
however, we aren't only called when restoring history;
|
|
core uses putmsghistory() for other stuff during play
|
|
and those messages should have a normal turn value */
|
|
last_mesg->turn = restoring_msghist ? (1L << 3) : gh.hero_seq;
|
|
#ifdef DUMPLOG
|
|
dumplogmsg(last_mesg->str);
|
|
#endif
|
|
} 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;
|
|
#ifdef DUMPLOG
|
|
dumplogmsg(mesg->str);
|
|
#endif
|
|
free((genericptr_t) mesg->str);
|
|
free((genericptr_t) mesg);
|
|
}
|
|
initd = FALSE; /* reset */
|
|
}
|
|
}
|
|
|
|
/*cursmesg.c*/
|