/* SCCS Id: @(#)wintty.c 3.3 2000/06/27 */ /* Copyright (c) David Cohrs, 1991 */ /* NetHack may be freely redistributed. See license for details. */ /* * Neither a standard out nor character-based control codes should be * part of the "tty look" windowing implementation. * h+ 930227 */ #include "hack.h" #include "dlb.h" #ifdef SHORT_FILENAMES #include "patchlev.h" #else #include "patchlevel.h" #endif #ifdef TTY_GRAPHICS #ifdef MAC # define MICRO /* The Mac is a MICRO only for this file, not in general! */ # ifdef THINK_C extern void msmsg(const char *,...); # endif #endif #ifndef NO_TERMS #include "tcap.h" #endif #include "wintty.h" #ifdef CLIPPING /* might want SIGWINCH */ # if defined(BSD) || defined(ULTRIX) || defined(AIX_31) || defined(_BULL_SOURCE) #include # endif #endif #define DEBUG extern char mapped_menu_cmds[]; /* from options.c */ /* Interface definition, for windows.c */ struct window_procs tty_procs = { "tty", tty_init_nhwindows, tty_player_selection, tty_askname, tty_get_nh_event, tty_exit_nhwindows, tty_suspend_nhwindows, tty_resume_nhwindows, tty_create_nhwindow, tty_clear_nhwindow, tty_display_nhwindow, tty_destroy_nhwindow, tty_curs, tty_putstr, tty_display_file, tty_start_menu, tty_add_menu, tty_end_menu, tty_select_menu, tty_message_menu, tty_update_inventory, tty_mark_synch, tty_wait_synch, #ifdef CLIPPING tty_cliparound, #endif #ifdef POSITIONBAR tty_update_positionbar, #endif tty_print_glyph, tty_raw_print, tty_raw_print_bold, tty_nhgetch, tty_nh_poskey, tty_nhbell, tty_doprev_message, tty_yn_function, tty_getlin, tty_get_ext_cmd, tty_number_pad, tty_delay_output, #ifdef CHANGE_COLOR /* the Mac uses a palette device */ tty_change_color, #ifdef MAC tty_change_background, set_tty_font_name, #endif tty_get_color_string, #endif /* other defs that really should go away (they're tty specific) */ tty_start_screen, tty_end_screen, genl_outrip }; static int maxwin = 0; /* number of windows in use */ winid BASE_WINDOW; struct WinDesc *wins[MAXWIN]; struct DisplayDesc *ttyDisplay; /* the tty display descriptor */ extern void FDECL(cmov, (int,int)); /* from termcap.c */ extern void FDECL(nocmov, (int,int)); /* from termcap.c */ #if defined(UNIX) || defined(VMS) static char obuf[BUFSIZ]; /* BUFSIZ is defined in stdio.h */ #endif static char winpanicstr[] = "Bad window id %d"; char defmorestr[] = "--More--"; #ifdef CLIPPING # if defined(USE_TILES) && defined(MSDOS) boolean clipping = FALSE; /* clipping on? */ int clipx = 0, clipxmax = 0; # else static boolean clipping = FALSE; /* clipping on? */ static int clipx = 0, clipxmax = 0; # endif static int clipy = 0, clipymax = 0; #endif /* CLIPPING */ #if defined(USE_TILES) && defined(MSDOS) extern void FDECL(adjust_cursor_flags, (struct WinDesc *)); #endif #if defined(ASCIIGRAPH) && !defined(NO_TERMS) boolean GFlag = FALSE; boolean HE_resets_AS; /* see termcap.c */ #endif #ifdef MICRO static char to_continue[] = "to continue"; #define getret() getreturn(to_continue) #else STATIC_DCL void NDECL(getret); #endif STATIC_DCL void FDECL(erase_menu_or_text, (winid, struct WinDesc *, BOOLEAN_P)); STATIC_DCL void FDECL(free_window_info, (struct WinDesc *, BOOLEAN_P)); STATIC_DCL void FDECL(dmore,(struct WinDesc *, const char *)); STATIC_DCL void FDECL(set_item_state, (winid, int, tty_menu_item *)); STATIC_DCL void FDECL(set_all_on_page, (winid,tty_menu_item *,tty_menu_item *)); STATIC_DCL void FDECL(unset_all_on_page, (winid,tty_menu_item *,tty_menu_item *)); STATIC_DCL void FDECL(invert_all_on_page, (winid,tty_menu_item *,tty_menu_item *, CHAR_P)); STATIC_DCL void FDECL(invert_all, (winid,tty_menu_item *,tty_menu_item *, CHAR_P)); STATIC_DCL void FDECL(process_menu_window, (winid,struct WinDesc *)); STATIC_DCL void FDECL(process_text_window, (winid,struct WinDesc *)); STATIC_DCL tty_menu_item *FDECL(reverse, (tty_menu_item *)); STATIC_DCL const char * FDECL(compress_str, (const char *)); STATIC_DCL void FDECL(tty_putsym, (winid, int, int, CHAR_P)); static char *FDECL(copy_of, (const char *)); STATIC_DCL void FDECL(bail, (const char *)); /* __attribute__((noreturn)) */ /* * A string containing all the default commands -- to add to a list * of acceptable inputs. */ static const char default_menu_cmds[] = { MENU_FIRST_PAGE, MENU_LAST_PAGE, MENU_NEXT_PAGE, MENU_PREVIOUS_PAGE, MENU_SELECT_ALL, MENU_UNSELECT_ALL, MENU_INVERT_ALL, MENU_SELECT_PAGE, MENU_UNSELECT_PAGE, MENU_INVERT_PAGE, 0 /* null terminator */ }; /* clean up and quit */ STATIC_OVL void bail(mesg) const char *mesg; { clearlocks(); tty_exit_nhwindows(mesg); terminate(EXIT_SUCCESS); /*NOTREACHED*/ } #if defined(SIGWINCH) && defined(CLIPPING) STATIC_OVL void winch() { int oldLI = LI, oldCO = CO, i; register struct WinDesc *cw; getwindowsz(); if((oldLI != LI || oldCO != CO) && ttyDisplay) { ttyDisplay->rows = LI; ttyDisplay->cols = CO; cw = wins[BASE_WINDOW]; cw->rows = ttyDisplay->rows; cw->cols = ttyDisplay->cols; if(iflags.window_inited) { cw = wins[WIN_MESSAGE]; cw->curx = cw->cury = 0; tty_destroy_nhwindow(WIN_STATUS); WIN_STATUS = tty_create_nhwindow(NHW_STATUS); if(u.ux) { #ifdef CLIPPING if(CO < COLNO || LI < ROWNO+3) { setclipped(); tty_cliparound(u.ux, u.uy); } else { clipping = FALSE; clipx = clipy = 0; } #endif i = ttyDisplay->toplin; ttyDisplay->toplin = 0; docrt(); bot(); ttyDisplay->toplin = i; flush_screen(1); if(i) { addtopl(toplines); } else for(i=WIN_INVEN; i < MAXWIN; i++) if(wins[i] && wins[i]->active) { /* cop-out */ addtopl("Press Return to continue: "); break; } (void) fflush(stdout); if(i < 2) flush_screen(1); } } } } #endif /*ARGSUSED*/ void tty_init_nhwindows(argcp,argv) int* argcp; char** argv; { int wid, hgt; /* * Remember tty modes, to be restored on exit. * * gettty() must be called before tty_startup() * due to ordering of LI/CO settings * tty_startup() must be called before initoptions() * due to ordering of graphics settings */ #if defined(UNIX) || defined(VMS) setbuf(stdout,obuf); #endif gettty(); /* to port dependant tty setup */ tty_startup(&wid, &hgt); setftty(); /* calls start_screen */ /* set up tty descriptor */ ttyDisplay = (struct DisplayDesc*) alloc(sizeof(struct DisplayDesc)); ttyDisplay->toplin = 0; ttyDisplay->rows = hgt; ttyDisplay->cols = wid; ttyDisplay->curx = ttyDisplay->cury = 0; ttyDisplay->inmore = ttyDisplay->inread = ttyDisplay->intr = 0; ttyDisplay->dismiss_more = 0; #ifdef TEXTCOLOR ttyDisplay->color = NO_COLOR; #endif ttyDisplay->attrs = 0; /* set up the default windows */ BASE_WINDOW = tty_create_nhwindow(NHW_BASE); wins[BASE_WINDOW]->active = 1; ttyDisplay->lastwin = WIN_ERR; #if defined(SIGWINCH) && defined(CLIPPING) (void) signal(SIGWINCH, winch); #endif /* add one a space forward menu command alias */ add_menu_cmd_alias(' ', MENU_NEXT_PAGE); tty_clear_nhwindow(BASE_WINDOW); tty_putstr(BASE_WINDOW, 0, ""); tty_putstr(BASE_WINDOW, 0, COPYRIGHT_BANNER_A); tty_putstr(BASE_WINDOW, 0, COPYRIGHT_BANNER_B); tty_putstr(BASE_WINDOW, 0, COPYRIGHT_BANNER_C); tty_putstr(BASE_WINDOW, 0, ""); tty_display_nhwindow(BASE_WINDOW, FALSE); } void tty_player_selection() { int i, k, n; char pick4u = 'n', thisch, lastch = 0; char pbuf[QBUFSZ]; winid win; anything any; menu_item *selected = 0; /* Should we randomly pick for the player? */ if (flags.initrole == ROLE_NONE || flags.initrace == ROLE_NONE || flags.initgend == ROLE_NONE || flags.initalign == ROLE_NONE) { const char *prompt = "Shall I pick a character for you? [ynq] "; int echoline; tty_putstr(BASE_WINDOW, 0, ""); echoline = wins[BASE_WINDOW]->cury; tty_putstr(BASE_WINDOW, 0, prompt); do { pick4u = lowc(readchar()); if (index(quitchars, pick4u)) pick4u = 'y'; } while(!index(ynqchars, pick4u)); if ((int)strlen(prompt) + 1 < CO) { /* Echo choice and move back down line */ tty_putsym(BASE_WINDOW, (int)strlen(prompt)+1, echoline, pick4u); tty_putstr(BASE_WINDOW, 0, ""); } else /* Otherwise it's hard to tell where to echo, and things are * wrapping a bit messily anyway, so (try to) make sure the next * question shows up well and doesn't get wrapped at the * bottom of the window. */ tty_clear_nhwindow(BASE_WINDOW); if (pick4u != 'y' && pick4u != 'n') { give_up: /* Quit */ if (selected) free((genericptr_t) selected); bail((char *)0); /*NOTREACHED*/ return; } } /* Select a role, if necessary */ /* we'll try to be compatible with pre-selected race/gender/alignment, * but may not succeed */ if (flags.initrole < 0) { /* Process the choice */ if (pick4u == 'y' || flags.initrole == ROLE_RANDOM) { /* Pick a random role */ flags.initrole = pick_role(flags.initrace, flags.initgend, flags.initalign); if (flags.initrole < 0) { tty_putstr(BASE_WINDOW, 0, "Incompatible role!"); flags.initrole = randrole(); } } else { /* Prompt for a role */ win = create_nhwindow(NHW_MENU); start_menu(win); any.a_void = 0; /* zero out all bits */ for (i = 0; roles[i].name.m; i++) { if (ok_role(i, flags.initrace, flags.initgend, flags.initalign)) { any.a_int = i+1; /* must be non-zero */ thisch = lowc(roles[i].name.m[0]); if (thisch == lastch) thisch = highc(thisch); add_menu(win, NO_GLYPH, &any, thisch, 0, ATR_NONE, an(roles[i].name.m), MENU_UNSELECTED); lastch = thisch; } } any.a_int = pick_role(flags.initrace, flags.initgend, flags.initalign)+1; if (any.a_int == 0) /* must be non-zero */ any.a_int = randrole()+1; add_menu(win, NO_GLYPH, &any , '*', 0, ATR_NONE, "Random", MENU_UNSELECTED); any.a_int = i+1; /* must be non-zero */ add_menu(win, NO_GLYPH, &any , 'q', 0, ATR_NONE, "Quit", MENU_UNSELECTED); end_menu(win, "Pick a role"); n = select_menu(win, PICK_ONE, &selected); destroy_nhwindow(win); /* Process the choice */ if (n != 1 || selected[0].item.a_int == any.a_int) goto give_up; /* Selected quit */ flags.initrole = selected[0].item.a_int - 1; free((genericptr_t) selected), selected = 0; } } /* Select a race, if necessary */ /* force compatibility with role, try for compatibility with * pre-selected gender/alignment */ if (flags.initrace < 0 || !validrace(flags.initrole, flags.initrace)) { /* pre-selected race not valid */ if (pick4u == 'y' || flags.initrace == ROLE_RANDOM) { flags.initrace = pick_race(flags.initrole, flags.initgend, flags.initalign); if (flags.initrace < 0) { tty_putstr(BASE_WINDOW, 0, "Incompatible race!"); flags.initrace = randrace(flags.initrole); } } else { /* pick4u == 'n' */ /* Count the number of valid races */ n = 0; /* number valid */ k = 0; /* valid race */ for (i = 0; races[i].noun; i++) { if (ok_race(flags.initrole, i, flags.initgend, flags.initalign)) { n++; k = i; } } if (n == 0) { for (i = 0; races[i].noun; i++) { if (validrace(flags.initrole, i)) { n++; k = i; } } } /* Permit the user to pick, if there is more than one */ if (n > 1) { win = create_nhwindow(NHW_MENU); start_menu(win); any.a_void = 0; /* zero out all bits */ for (i = 0; races[i].noun; i++) if (ok_race(flags.initrole, i, flags.initgend, flags.initalign)) { any.a_int = i+1; /* must be non-zero */ add_menu(win, NO_GLYPH, &any, races[i].noun[0], 0, ATR_NONE, races[i].noun, MENU_UNSELECTED); } any.a_int = pick_race(flags.initrole, flags.initgend, flags.initalign)+1; if (any.a_int == 0) /* must be non-zero */ any.a_int = randrace(flags.initrole)+1; add_menu(win, NO_GLYPH, &any , '*', 0, ATR_NONE, "Random", MENU_UNSELECTED); any.a_int = i+1; /* must be non-zero */ add_menu(win, NO_GLYPH, &any , 'q', 0, ATR_NONE, "Quit", MENU_UNSELECTED); Sprintf(pbuf, "Pick the race of your %s", roles[flags.initrole].name.m); end_menu(win, pbuf); n = select_menu(win, PICK_ONE, &selected); destroy_nhwindow(win); if (n != 1 || selected[0].item.a_int == any.a_int) goto give_up; /* Selected quit */ k = selected[0].item.a_int - 1; free((genericptr_t) selected), selected = 0; } flags.initrace = k; } } /* Select a gender, if necessary */ /* force compatibility with role/race, try for compatibility with * pre-selected alignment */ if (flags.initgend < 0 || !validgend(flags.initrole, flags.initrace, flags.initgend)) { /* pre-selected gender not valid */ if (pick4u == 'y' || flags.initgend == ROLE_RANDOM) { flags.initgend = pick_gend(flags.initrole, flags.initrace, flags.initalign); if (flags.initgend < 0) { tty_putstr(BASE_WINDOW, 0, "Incompatible gender!"); flags.initgend = randgend(flags.initrole, flags.initrace); } } else { /* pick4u == 'n' */ /* Count the number of valid genders */ n = 0; /* number valid */ k = 0; /* valid gender */ for (i = 0; i < ROLE_GENDERS; i++) { if (ok_gend(flags.initrole, flags.initrace, i, flags.initalign)) { n++; k = i; } } if (n == 0) { for (i = 0; i < ROLE_GENDERS; i++) { if (validgend(flags.initrole, flags.initrace, i)) { n++; k = i; } } } /* Permit the user to pick, if there is more than one */ if (n > 1) { win = create_nhwindow(NHW_MENU); start_menu(win); any.a_void = 0; /* zero out all bits */ for (i = 0; i < ROLE_GENDERS; i++) if (ok_gend(flags.initrole, flags.initrace, i, flags.initalign)) { any.a_int = i+1; add_menu(win, NO_GLYPH, &any, genders[i].adj[0], 0, ATR_NONE, genders[i].adj, MENU_UNSELECTED); } any.a_int = pick_gend(flags.initrole, flags.initrace, flags.initalign)+1; if (any.a_int == 0) /* must be non-zero */ any.a_int = randgend(flags.initrole, flags.initrace)+1; add_menu(win, NO_GLYPH, &any , '*', 0, ATR_NONE, "Random", MENU_UNSELECTED); any.a_int = i+1; /* must be non-zero */ add_menu(win, NO_GLYPH, &any , 'q', 0, ATR_NONE, "Quit", MENU_UNSELECTED); Sprintf(pbuf, "Pick the gender of your %s %s", races[flags.initrace].adj, roles[flags.initrole].name.m); end_menu(win, pbuf); n = select_menu(win, PICK_ONE, &selected); destroy_nhwindow(win); if (n != 1 || selected[0].item.a_int == any.a_int) goto give_up; /* Selected quit */ k = selected[0].item.a_int - 1; free((genericptr_t) selected), selected = 0; } flags.initgend = k; } } /* Select an alignment, if necessary */ /* force compatibility with role/race/gender */ if (flags.initalign < 0 || !validalign(flags.initrole, flags.initrace, flags.initalign)) { /* pre-selected alignment not valid */ if (pick4u == 'y' || flags.initalign == ROLE_RANDOM) { flags.initalign = pick_align(flags.initrole, flags.initrace, flags.initgend); if (flags.initalign < 0) { tty_putstr(BASE_WINDOW, 0, "Incompatible alignment!"); flags.initalign = randalign(flags.initrole, flags.initrace); } } else { /* pick4u == 'n' */ /* Count the number of valid alignments */ n = 0; /* number valid */ k = 0; /* valid alignment */ for (i = 0; i < ROLE_ALIGNS; i++) { if (ok_align(flags.initrole, flags.initrace, flags.initgend, i)) { n++; k = i; } } if (n == 0) { for (i = 0; i < ROLE_ALIGNS; i++) { if (validalign(flags.initrole, flags.initrace, i)) { n++; k = i; } } } /* Permit the user to pick, if there is more than one */ if (n > 1) { win = create_nhwindow(NHW_MENU); start_menu(win); any.a_void = 0; /* zero out all bits */ for (i = 0; i < ROLE_ALIGNS; i++) if (ok_align(flags.initrole, flags.initrace, flags.initgend, i)) { any.a_int = i+1; add_menu(win, NO_GLYPH, &any, aligns[i].adj[0], 0, ATR_NONE, aligns[i].adj, MENU_UNSELECTED); } any.a_int = pick_align(flags.initrole, flags.initrace, flags.initgend)+1; if (any.a_int == 0) /* must be non-zero */ any.a_int = randalign(flags.initrole, flags.initrace)+1; add_menu(win, NO_GLYPH, &any , '*', 0, ATR_NONE, "Random", MENU_UNSELECTED); any.a_int = i+1; /* must be non-zero */ add_menu(win, NO_GLYPH, &any , 'q', 0, ATR_NONE, "Quit", MENU_UNSELECTED); Sprintf(pbuf, "Pick the alignment of your %s %s %s", genders[flags.initgend].adj, races[flags.initrace].adj, (flags.initgend && roles[flags.initrole].name.f) ? roles[flags.initrole].name.f : roles[flags.initrole].name.m); end_menu(win, pbuf); n = select_menu(win, PICK_ONE, &selected); destroy_nhwindow(win); if (n != 1 || selected[0].item.a_int == any.a_int) goto give_up; /* Selected quit */ k = selected[0].item.a_int - 1; free((genericptr_t) selected), selected = 0; } flags.initalign = k; } } /* Success! */ tty_display_nhwindow(BASE_WINDOW, FALSE); } /* * plname is filled either by an option (-u Player or -uPlayer) or * explicitly (by being the wizard) or by askname. * It may still contain a suffix denoting the role, etc. * Always called after init_nhwindows() and before display_gamewindows(). */ void tty_askname() { static char who_are_you[] = "Who are you? "; register int c, ct, tryct = 0; tty_putstr(BASE_WINDOW, 0, ""); do { if (++tryct > 1) { if (tryct > 10) bail("Giving up after 10 tries.\n"); tty_curs(BASE_WINDOW, 1, wins[BASE_WINDOW]->cury - 1); tty_putstr(BASE_WINDOW, 0, "Enter a name for your character..."); /* erase previous prompt (in case of ESC after partial response) */ tty_curs(BASE_WINDOW, 1, wins[BASE_WINDOW]->cury), cl_end(); } tty_putstr(BASE_WINDOW, 0, who_are_you); tty_curs(BASE_WINDOW, (int)(sizeof who_are_you), wins[BASE_WINDOW]->cury - 1); ct = 0; while((c = tty_nhgetch()) != '\n') { if(c == EOF) error("End of input\n"); if (c == '\033') { ct = 0; break; } /* continue outer loop */ /* some people get confused when their erase char is not ^H */ if (c == '\b' || c == '\177') { if(ct) { ct--; #ifdef MICRO # if defined(WIN32CON) backsp(); /* \b is visible on NT */ # else # if defined(MSDOS) if (iflags.grmode) { backsp(); } else # endif msmsg("\b \b"); # endif #else (void) putchar('\b'); (void) putchar(' '); (void) putchar('\b'); #endif } continue; } #if defined(UNIX) || defined(VMS) if(c != '-' && c != '@') if(c < 'A' || (c > 'Z' && c < 'a') || c > 'z') c = '_'; #endif if (ct < (int)(sizeof plname) - 1) { #if defined(MICRO) # if defined(MSDOS) if (iflags.grmode) { (void) putchar(c); } else # endif msmsg("%c", c); #else (void) putchar(c); #endif plname[ct++] = c; } } plname[ct] = 0; } while (ct == 0); /* move to next line to simulate echo of user's */ tty_curs(BASE_WINDOW, 1, wins[BASE_WINDOW]->cury + 1); } void tty_get_nh_event() { return; } #ifndef MICRO STATIC_OVL void getret() { xputs("\n"); if(flags.standout) standoutbeg(); xputs("Hit "); xputs(iflags.cbreak ? "space" : "return"); xputs(" to continue: "); if(flags.standout) standoutend(); xwaitforspace(" "); } #endif void tty_suspend_nhwindows(str) const char *str; { settty(str); /* calls end_screen, perhaps raw_print */ if (!str) tty_raw_print(""); /* calls fflush(stdout) */ } void tty_resume_nhwindows() { gettty(); setftty(); /* calls start_screen */ docrt(); } void tty_exit_nhwindows(str) const char *str; { winid i; tty_suspend_nhwindows(str); /* Just forget any windows existed, since we're about to exit anyway. * Disable windows to avoid calls to window routines. */ for(i=0; itype = type; newwin->flags = 0; newwin->active = FALSE; newwin->curx = newwin->cury = 0; newwin->morestr = 0; newwin->mlist = (tty_menu_item *) 0; newwin->plist = (tty_menu_item **) 0; newwin->npages = newwin->plist_size = newwin->nitems = newwin->how = 0; switch(type) { case NHW_BASE: /* base window, used for absolute movement on the screen */ newwin->offx = newwin->offy = 0; newwin->rows = ttyDisplay->rows; newwin->cols = ttyDisplay->cols; newwin->maxrow = newwin->maxcol = 0; break; case NHW_MESSAGE: /* message window, 1 line long, very wide, top of screen */ newwin->offx = newwin->offy = 0; /* sanity check */ if(iflags.msg_history < 20) iflags.msg_history = 20; else if(iflags.msg_history > 60) iflags.msg_history = 60; newwin->maxrow = newwin->rows = iflags.msg_history; newwin->maxcol = newwin->cols = 0; break; case NHW_STATUS: /* status window, 2 lines long, full width, bottom of screen */ newwin->offx = 0; #if defined(USE_TILES) && defined(MSDOS) if (iflags.grmode) { newwin->offy = ttyDisplay->rows-2; } else #endif newwin->offy = min((int)ttyDisplay->rows-2, ROWNO+1); newwin->rows = newwin->maxrow = 2; newwin->cols = newwin->maxcol = min(ttyDisplay->cols, COLNO); break; case NHW_MAP: /* map window, ROWNO lines long, full width, below message window */ newwin->offx = 0; newwin->offy = 1; newwin->rows = ROWNO; newwin->cols = COLNO; newwin->maxrow = 0; /* no buffering done -- let gbuf do it */ newwin->maxcol = 0; break; case NHW_MENU: case NHW_TEXT: /* inventory/menu window, variable length, full width, top of screen */ /* help window, the same, different semantics for display, etc */ newwin->offx = newwin->offy = 0; newwin->rows = 0; newwin->cols = ttyDisplay->cols; newwin->maxrow = newwin->maxcol = 0; break; default: panic("Tried to create window type %d\n", (int) type); return WIN_ERR; } for(newid = 0; newidmaxrow) { newwin->data = (char **) alloc(sizeof(char *) * (unsigned)newwin->maxrow); newwin->datlen = (short *) alloc(sizeof(short) * (unsigned)newwin->maxrow); if(newwin->maxcol) { for (i = 0; i < newwin->maxrow; i++) { newwin->data[i] = (char *) alloc((unsigned)newwin->maxcol); newwin->datlen[i] = newwin->maxcol; } } else { for (i = 0; i < newwin->maxrow; i++) { newwin->data[i] = (char *) 0; newwin->datlen[i] = 0; } } if(newwin->type == NHW_MESSAGE) newwin->maxrow = 0; } else { newwin->data = (char **)0; newwin->datlen = (short *)0; } return newid; } STATIC_OVL void erase_menu_or_text(window, cw, clear) winid window; struct WinDesc *cw; boolean clear; { if(cw->offx == 0) if(cw->offy) { tty_curs(window, 1, 0); cl_eos(); } else if (clear) clear_screen(); else docrt(); else docorner((int)cw->offx, cw->maxrow+1); } STATIC_OVL void free_window_info(cw, free_data) struct WinDesc *cw; boolean free_data; { int i; if (cw->data) { if (cw == wins[WIN_MESSAGE] && cw->rows > cw->maxrow) cw->maxrow = cw->rows; /* topl data */ for(i=0; imaxrow; i++) if(cw->data[i]) { free((genericptr_t)cw->data[i]); cw->data[i] = (char *)0; if (cw->datlen) cw->datlen[i] = 0; } if (free_data) { free((genericptr_t)cw->data); cw->data = (char **)0; if (cw->datlen) free((genericptr_t)cw->datlen); cw->datlen = (short *)0; cw->rows = 0; } } cw->maxrow = cw->maxcol = 0; if(cw->mlist) { tty_menu_item *temp; while ((temp = cw->mlist) != 0) { cw->mlist = cw->mlist->next; if (temp->str) free((genericptr_t)temp->str); free((genericptr_t)temp); } } if (cw->plist) { free((genericptr_t)cw->plist); cw->plist = 0; } cw->plist_size = cw->npages = cw->nitems = cw->how = 0; if(cw->morestr) { free((genericptr_t)cw->morestr); cw->morestr = 0; } } void tty_clear_nhwindow(window) winid window; { register struct WinDesc *cw = 0; if(window == WIN_ERR || (cw = wins[window]) == (struct WinDesc *) 0) panic(winpanicstr, window); ttyDisplay->lastwin = window; switch(cw->type) { case NHW_MESSAGE: if(ttyDisplay->toplin) { home(); cl_end(); if(cw->cury) docorner(1, cw->cury+1); ttyDisplay->toplin = 0; } break; case NHW_STATUS: tty_curs(window, 1, 0); cl_end(); tty_curs(window, 1, 1); cl_end(); break; case NHW_MAP: /* cheap -- clear the whole thing and tell nethack to redraw botl */ flags.botlx = 1; /* fall into ... */ case NHW_BASE: clear_screen(); break; case NHW_MENU: case NHW_TEXT: if(cw->active) erase_menu_or_text(window, cw, TRUE); free_window_info(cw, FALSE); break; } cw->curx = cw->cury = 0; } STATIC_OVL void dmore(cw, s) register struct WinDesc *cw; const char *s; /* valid responses */ { const char *prompt = cw->morestr ? cw->morestr : defmorestr; int offset = (cw->type == NHW_TEXT) ? 1 : 2; tty_curs(BASE_WINDOW, (int)ttyDisplay->curx + offset, (int)ttyDisplay->cury); if(flags.standout) standoutbeg(); xputs(prompt); ttyDisplay->curx += strlen(prompt); if(flags.standout) standoutend(); xwaitforspace(s); } STATIC_OVL void set_item_state(window, lineno, item) winid window; int lineno; tty_menu_item *item; { char ch = item->selected ? (item->count == -1L ? '+' : '#') : '-'; tty_curs(window, 4, lineno); term_start_attr(item->attr); (void) putchar(ch); ttyDisplay->curx++; term_end_attr(item->attr); } STATIC_OVL void set_all_on_page(window, page_start, page_end) winid window; tty_menu_item *page_start, *page_end; { tty_menu_item *curr; int n; for (n = 0, curr = page_start; curr != page_end; n++, curr = curr->next) if (curr->identifier.a_void && !curr->selected) { curr->selected = TRUE; set_item_state(window, n, curr); } } STATIC_OVL void unset_all_on_page(window, page_start, page_end) winid window; tty_menu_item *page_start, *page_end; { tty_menu_item *curr; int n; for (n = 0, curr = page_start; curr != page_end; n++, curr = curr->next) if (curr->identifier.a_void && curr->selected) { curr->selected = FALSE; curr->count = -1L; set_item_state(window, n, curr); } } STATIC_OVL void invert_all_on_page(window, page_start, page_end, acc) winid window; tty_menu_item *page_start, *page_end; char acc; /* group accelerator, 0 => all */ { tty_menu_item *curr; int n; for (n = 0, curr = page_start; curr != page_end; n++, curr = curr->next) if (curr->identifier.a_void && (acc == 0 || curr->gselector == acc)) { if (curr->selected) { curr->selected = FALSE; curr->count = -1L; } else curr->selected = TRUE; set_item_state(window, n, curr); } } /* * Invert all entries that match the give group accelerator (or all if * zero). */ STATIC_OVL void invert_all(window, page_start, page_end, acc) winid window; tty_menu_item *page_start, *page_end; char acc; /* group accelerator, 0 => all */ { tty_menu_item *curr; boolean on_curr_page; struct WinDesc *cw = wins[window]; invert_all_on_page(window, page_start, page_end, acc); /* invert the rest */ for (on_curr_page = FALSE, curr = cw->mlist; curr; curr = curr->next) { if (curr == page_start) on_curr_page = TRUE; else if (curr == page_end) on_curr_page = FALSE; if (!on_curr_page && curr->identifier.a_void && (acc == 0 || curr->gselector == acc)) { if (curr->selected) { curr->selected = FALSE; curr->count = -1; } else curr->selected = TRUE; } } } STATIC_OVL void process_menu_window(window, cw) winid window; struct WinDesc *cw; { tty_menu_item *page_start, *page_end, *curr; long count; int n, curr_page, page_lines; boolean finished, counting, reset_count; char *cp, *rp, resp[QBUFSZ], gacc[QBUFSZ], *msave, morestr[QBUFSZ]; curr_page = page_lines = 0; page_start = page_end = 0; msave = cw->morestr; /* save the morestr */ cw->morestr = morestr; counting = FALSE; count = 0L; reset_count = TRUE; finished = FALSE; /* collect group accelerators; for PICK_NONE, they're ignored; for PICK_ONE, only those which match exactly one entry will be accepted; for PICK_ANY, those which match any entry are okay */ gacc[0] = '\0'; if (cw->how != PICK_NONE) { int i, gcnt[128]; #define GSELIDX(c) (c & 127) /* guard against `signed char' */ for (i = 0; i < SIZE(gcnt); i++) gcnt[i] = 0; for (n = 0, curr = cw->mlist; curr; curr = curr->next) if (curr->gselector) ++n, ++gcnt[GSELIDX(curr->gselector)]; if (n > 0) /* at least one group accelerator found */ for (rp = gacc, curr = cw->mlist; curr; curr = curr->next) if (curr->gselector && !index(gacc, curr->gselector) && (cw->how == PICK_ANY || gcnt[GSELIDX(curr->gselector)] == 1)) { *rp++ = curr->gselector; *rp = '\0'; /* re-terminate for index() */ } } /* loop until finished */ while (!finished) { if (reset_count) { counting = FALSE; count = 0; } else reset_count = TRUE; if (!page_start) { /* new page to be displayed */ if (curr_page < 0 || (cw->npages > 0 && curr_page >= cw->npages)) panic("bad menu screen page #%d", curr_page); /* clear screen */ if (!cw->offx) { /* if not corner, do clearscreen */ if(cw->offy) { tty_curs(window, 1, 0); cl_eos(); } else clear_screen(); } rp = resp; if (cw->npages > 0) { /* collect accelerators */ page_start = cw->plist[curr_page]; page_end = cw->plist[curr_page + 1]; for (page_lines = 0, curr = page_start; curr != page_end; page_lines++, curr = curr->next) { if (curr->selector) *rp++ = curr->selector; tty_curs(window, 1, page_lines); if (cw->offx) cl_end(); (void) putchar(' '); ++ttyDisplay->curx; /* * Don't use xputs() because (1) under unix it calls * tputstr() which will interpret a '*' as some kind * of padding information and (2) it calls xputc to * actually output the character. We're faster doing * this. */ term_start_attr(curr->attr); for (n = 0, cp = curr->str; *cp && (int) ++ttyDisplay->curx < (int) ttyDisplay->cols; cp++, n++) if (n == 2 && curr->identifier.a_void != 0 && curr->selected) { if (curr->count == -1L) (void) putchar('+'); /* all selected */ else (void) putchar('#'); /* count selected */ } else (void) putchar(*cp); term_end_attr(curr->attr); } } else { page_start = 0; page_end = 0; page_lines = 0; } *rp = 0; /* corner window - clear extra lines from last page */ if (cw->offx) { for (n = page_lines + 1; n < cw->maxrow; n++) { tty_curs(window, 1, n); cl_end(); } } /* set extra chars.. */ Strcat(resp, default_menu_cmds); Strcat(resp, "0123456789\033\n\r"); /* counts, quit */ Strcat(resp, gacc); /* group accelerators */ Strcat(resp, mapped_menu_cmds); if (cw->npages > 1) Sprintf(cw->morestr, "(%d of %d)", curr_page + 1, (int) cw->npages); else Strcpy(cw->morestr, msave); tty_curs(window, 1, page_lines); cl_end(); dmore(cw, resp); } else { /* just put the cursor back... */ tty_curs(window, (int) strlen(cw->morestr) + 2, page_lines); xwaitforspace(resp); } morc = map_menu_cmd(morc); switch (morc) { case '0': /* special case: '0' is also the default ball class */ if (!counting && index(gacc, morc)) goto group_accel; /* fall through to count the zero */ case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': count = (count * 10L) + (long) (morc - '0'); /* * It is debatable whether we should allow 0 to * start a count. There is no difference if the * item is selected. If not selected, then * "0b" could mean: * * count starting zero: "zero b's" * ignore starting zero: "select b" * * At present I don't know which is better. */ if (count != 0L) { /* ignore leading zeros */ counting = TRUE; reset_count = FALSE; } break; case '\033': /* cancel - from counting or loop */ if (!counting) { /* deselect everything */ for (curr = cw->mlist; curr; curr = curr->next) { curr->selected = FALSE; curr->count = -1L; } cw->flags |= WIN_CANCELLED; finished = TRUE; } /* else only stop count */ break; case '\0': /* finished (commit) */ case '\n': case '\r': /* only finished if we are actually picking something */ if (cw->how != PICK_NONE) { finished = TRUE; break; } /* else fall through */ case MENU_NEXT_PAGE: if (cw->npages > 0 && curr_page != cw->npages - 1) { curr_page++; page_start = 0; } else finished = TRUE; /* questionable behavior */ break; case MENU_PREVIOUS_PAGE: if (cw->npages > 0 && curr_page != 0) { --curr_page; page_start = 0; } break; case MENU_FIRST_PAGE: if (cw->npages > 0 && curr_page != 0) { page_start = 0; curr_page = 0; } break; case MENU_LAST_PAGE: if (cw->npages > 0 && curr_page != cw->npages - 1) { page_start = 0; curr_page = cw->npages - 1; } break; case MENU_SELECT_PAGE: if (cw->how == PICK_ANY) set_all_on_page(window, page_start, page_end); break; case MENU_UNSELECT_PAGE: unset_all_on_page(window, page_start, page_end); break; case MENU_INVERT_PAGE: if (cw->how == PICK_ANY) invert_all_on_page(window, page_start, page_end, 0); break; case MENU_SELECT_ALL: if (cw->how == PICK_ANY) { set_all_on_page(window, page_start, page_end); /* set the rest */ for (curr = cw->mlist; curr; curr = curr->next) if (curr->identifier.a_void && !curr->selected) curr->selected = TRUE; } break; case MENU_UNSELECT_ALL: unset_all_on_page(window, page_start, page_end); /* unset the rest */ for (curr = cw->mlist; curr; curr = curr->next) if (curr->identifier.a_void && curr->selected) { curr->selected = FALSE; curr->count = -1; } break; case MENU_INVERT_ALL: if (cw->how == PICK_ANY) invert_all(window, page_start, page_end, 0); break; default: if (cw->how == PICK_NONE || !index(resp, morc)) { /* unacceptable input received */ tty_nhbell(); break; } else if (index(gacc, morc)) { group_accel: /* group accelerator; for the PICK_ONE case, we know that it matches exactly one item in order to be in gacc[] */ invert_all(window, page_start, page_end, morc); if (cw->how == PICK_ONE) finished = TRUE; break; } /* find, toggle, and possibly update */ for (n = 0, curr = page_start; curr != page_end; n++, curr = curr->next) if (morc == curr->selector) { if (curr->selected) { if (counting && count > 0) { curr->count = count; set_item_state(window, n, curr); } else { /* change state */ curr->selected = FALSE; curr->count = -1L; set_item_state(window, n, curr); } } else { /* !selected */ if (counting && count > 0) { curr->count = count; curr->selected = TRUE; set_item_state(window, n, curr); } else if (!counting) { curr->selected = TRUE; set_item_state(window, n, curr); } /* do nothing counting&&count==0 */ } if (cw->how == PICK_ONE) finished = TRUE; break; /* from `for' loop */ } break; } } /* while */ cw->morestr = msave; } STATIC_OVL void process_text_window(window, cw) winid window; struct WinDesc *cw; { int i, n, attr; register char *cp; for (n = 0, i = 0; i < cw->maxrow; i++) { if (!cw->offx && (n + cw->offy == ttyDisplay->rows - 1)) { tty_curs(window, 1, n); cl_end(); dmore(cw, quitchars); if (morc == '\033') { cw->flags |= WIN_CANCELLED; break; } if (cw->offy) { tty_curs(window, 1, 0); cl_eos(); } else clear_screen(); n = 0; } tty_curs(window, 1, n++); if (cw->offx) cl_end(); if (cw->data[i]) { attr = cw->data[i][0] - 1; if (cw->offx) { (void) putchar(' '); ++ttyDisplay->curx; } term_start_attr(attr); for (cp = &cw->data[i][1]; *cp && (int) ++ttyDisplay->curx < (int) ttyDisplay->cols; cp++) (void) putchar(*cp); term_end_attr(attr); } } if (i == cw->maxrow) { tty_curs(BASE_WINDOW, (int)cw->offx + 1, (cw->type == NHW_TEXT) ? (int) ttyDisplay->rows - 1 : n); cl_end(); dmore(cw, quitchars); if (morc == '\033') cw->flags |= WIN_CANCELLED; } } /*ARGSUSED*/ void tty_display_nhwindow(window, blocking) winid window; boolean blocking; /* with ttys, all windows are blocking */ { register struct WinDesc *cw = 0; if(window == WIN_ERR || (cw = wins[window]) == (struct WinDesc *) 0) panic(winpanicstr, window); if(cw->flags & WIN_CANCELLED) return; ttyDisplay->lastwin = window; ttyDisplay->rawprint = 0; switch(cw->type) { case NHW_MESSAGE: if(ttyDisplay->toplin == 1) { more(); ttyDisplay->toplin = 1; /* more resets this */ tty_clear_nhwindow(window); } else ttyDisplay->toplin = 0; cw->curx = cw->cury = 0; if(!cw->active) iflags.window_inited = TRUE; break; case NHW_MAP: end_glyphout(); if(blocking) { if(!ttyDisplay->toplin) ttyDisplay->toplin = 1; tty_display_nhwindow(WIN_MESSAGE, TRUE); return; } case NHW_BASE: (void) fflush(stdout); break; case NHW_TEXT: cw->maxcol = ttyDisplay->cols; /* force full-screen mode */ /*FALLTHRU*/ case NHW_MENU: cw->active = 1; /* avoid converting to uchar before calculations are finished */ cw->offx = (uchar) (int) max((int) 10, (int) (ttyDisplay->cols - cw->maxcol - 1)); if(cw->type == NHW_MENU) cw->offy = 0; if(ttyDisplay->toplin == 1) tty_display_nhwindow(WIN_MESSAGE, TRUE); if(cw->offx == 10 || cw->maxrow >= (int) ttyDisplay->rows) { cw->offx = 0; if(cw->offy) { tty_curs(window, 1, 0); cl_eos(); } else clear_screen(); ttyDisplay->toplin = 0; } else tty_clear_nhwindow(WIN_MESSAGE); if (cw->data) process_text_window(window, cw); else process_menu_window(window, cw); break; } cw->active = 1; } void tty_dismiss_nhwindow(window) winid window; { register struct WinDesc *cw = 0; if(window == WIN_ERR || (cw = wins[window]) == (struct WinDesc *) 0) panic(winpanicstr, window); switch(cw->type) { case NHW_MESSAGE: if (ttyDisplay->toplin) tty_display_nhwindow(WIN_MESSAGE, TRUE); /*FALLTHRU*/ case NHW_STATUS: case NHW_BASE: case NHW_MAP: /* * these should only get dismissed when the game is going away * or suspending */ tty_curs(BASE_WINDOW, 1, (int)ttyDisplay->rows-1); cw->active = 0; break; case NHW_MENU: case NHW_TEXT: if(cw->active) { if (iflags.window_inited) { /* otherwise dismissing the text endwin after other windows * are dismissed tries to redraw the map and panics. since * the whole reason for dismissing the other windows was to * leave the ending window on the screen, we don't want to * erase it anyway. */ erase_menu_or_text(window, cw, FALSE); } cw->active = 0; } break; } cw->flags = 0; } void tty_destroy_nhwindow(window) winid window; { register struct WinDesc *cw = 0; if(window == WIN_ERR || (cw = wins[window]) == (struct WinDesc *) 0) panic(winpanicstr, window); if(cw->active) tty_dismiss_nhwindow(window); if(cw->type == NHW_MESSAGE) iflags.window_inited = 0; if(cw->type == NHW_MAP) clear_screen(); free_window_info(cw, TRUE); free((genericptr_t)cw); wins[window] = 0; } void tty_curs(window, x, y) winid window; register int x, y; /* not xchar: perhaps xchar is unsigned and curx-x would be unsigned as well */ { struct WinDesc *cw = 0; int cx = ttyDisplay->curx; int cy = ttyDisplay->cury; if(window == WIN_ERR || (cw = wins[window]) == (struct WinDesc *) 0) panic(winpanicstr, window); ttyDisplay->lastwin = window; #if defined(USE_TILES) && defined(MSDOS) adjust_cursor_flags(cw); #endif cw->curx = --x; /* column 0 is never used */ cw->cury = y; #ifdef DEBUG if(x<0 || y<0 || y >= cw->rows || x >= cw->cols) { const char *s = "[unknown type]"; switch(cw->type) { case NHW_MESSAGE: s = "[topl window]"; break; case NHW_STATUS: s = "[status window]"; break; case NHW_MAP: s = "[map window]"; break; case NHW_MENU: s = "[corner window]"; break; case NHW_TEXT: s = "[text window]"; break; case NHW_BASE: s = "[base window]"; break; } impossible("bad curs positioning win %d %s (%d,%d)", window, s, x, y); return; } #endif x += cw->offx; y += cw->offy; #ifdef CLIPPING if(clipping && window == WIN_MAP) { x -= clipx; y -= clipy; } #endif if (y == cy && x == cx) return; if(cw->type == NHW_MAP) end_glyphout(); #ifndef NO_TERMS if(!nh_ND && (cx != x || x <= 3)) { /* Extremely primitive */ cmov(x, y); /* bunker!wtm */ return; } #endif if((cy -= y) < 0) cy = -cy; if((cx -= x) < 0) cx = -cx; if(cy <= 3 && cx <= 3) { nocmov(x, y); #ifndef NO_TERMS } else if ((x <= 3 && cy <= 3) || (!nh_CM && x < cx)) { (void) putchar('\r'); ttyDisplay->curx = 0; nocmov(x, y); } else if (!nh_CM) { nocmov(x, y); #endif } else cmov(x, y); ttyDisplay->curx = x; ttyDisplay->cury = y; } STATIC_OVL void tty_putsym(window, x, y, ch) winid window; int x, y; char ch; { register struct WinDesc *cw = 0; if(window == WIN_ERR || (cw = wins[window]) == (struct WinDesc *) 0) panic(winpanicstr, window); switch(cw->type) { case NHW_STATUS: case NHW_MAP: case NHW_BASE: tty_curs(window, x, y); (void) putchar(ch); ttyDisplay->curx++; cw->curx++; break; case NHW_MESSAGE: case NHW_MENU: case NHW_TEXT: impossible("Can't putsym to window type %d", cw->type); break; } } STATIC_OVL const char* compress_str(str) const char *str; { static char cbuf[BUFSZ]; /* compress in case line too long */ if((int)strlen(str) >= CO) { register const char *bp0 = str; register char *bp1 = cbuf; do { #ifdef CLIPPING if(*bp0 != ' ' || bp0[1] != ' ') #else if(*bp0 != ' ' || bp0[1] != ' ' || bp0[2] != ' ') #endif *bp1++ = *bp0; } while(*bp0++); } else return str; return cbuf; } void tty_putstr(window, attr, str) winid window; int attr; const char *str; { register struct WinDesc *cw = 0; register char *ob; register const char *nb; register int i, j, n0; /* Assume there's a real problem if the window is missing -- * probably a panic message */ if(window == WIN_ERR || (cw = wins[window]) == (struct WinDesc *) 0) { tty_raw_print(str); return; } if(str == (const char*)0 || (cw->flags & WIN_CANCELLED)) return; if(cw->type != NHW_MESSAGE) str = compress_str(str); ttyDisplay->lastwin = window; switch(cw->type) { case NHW_MESSAGE: /* really do this later */ update_topl(str); break; case NHW_STATUS: ob = &cw->data[cw->cury][j = cw->curx]; if(flags.botlx) *ob = 0; if(!cw->cury && (int)strlen(str) >= CO) { /* the characters before "St:" are unnecessary */ nb = index(str, ':'); if(nb && nb > str+2) str = nb - 2; } nb = str; for(i = cw->curx+1, n0 = cw->cols; i < n0; i++, nb++) { if(!*nb) { if(*ob || flags.botlx) { /* last char printed may be in middle of line */ tty_curs(WIN_STATUS, i, cw->cury); cl_end(); } break; } if(*ob != *nb) tty_putsym(WIN_STATUS, i, cw->cury, *nb); if(*ob) ob++; } (void) strncpy(&cw->data[cw->cury][j], str, cw->cols - j - 1); cw->data[cw->cury][cw->cols-1] = '\0'; /* null terminate */ cw->cury = (cw->cury+1) % 2; cw->curx = 0; break; case NHW_MAP: tty_curs(window, cw->curx+1, cw->cury); term_start_attr(attr); while(*str && (int) ttyDisplay->curx < (int) ttyDisplay->cols-1) { (void) putchar(*str); str++; ttyDisplay->curx++; } cw->curx = 0; cw->cury++; term_end_attr(attr); break; case NHW_BASE: tty_curs(window, cw->curx+1, cw->cury); term_start_attr(attr); while (*str) { if ((int) ttyDisplay->curx >= (int) ttyDisplay->cols-1) { cw->curx = 0; cw->cury++; tty_curs(window, cw->curx+1, cw->cury); } (void) putchar(*str); str++; ttyDisplay->curx++; } cw->curx = 0; cw->cury++; term_end_attr(attr); break; case NHW_MENU: case NHW_TEXT: if(cw->type == NHW_TEXT && cw->cury == ttyDisplay->rows-1) { /* not a menu, so save memory and output 1 page at a time */ cw->maxcol = ttyDisplay->cols; /* force full-screen mode */ tty_display_nhwindow(window, TRUE); for(i=0; imaxrow; i++) if(cw->data[i]){ free((genericptr_t)cw->data[i]); cw->data[i] = 0; } cw->maxrow = cw->cury = 0; } /* always grows one at a time, but alloc 12 at a time */ if(cw->cury >= cw->rows) { char **tmp; cw->rows += 12; tmp = (char **) alloc(sizeof(char *) * (unsigned)cw->rows); for(i=0; imaxrow; i++) tmp[i] = cw->data[i]; if(cw->data) free((genericptr_t)cw->data); cw->data = tmp; for(i=cw->maxrow; irows; i++) cw->data[i] = 0; } if(cw->data[cw->cury]) free((genericptr_t)cw->data[cw->cury]); n0 = strlen(str) + 1; ob = cw->data[cw->cury] = (char *)alloc((unsigned)n0 + 1); *ob++ = (char)(attr + 1); /* avoid nuls, for convenience */ Strcpy(ob, str); if(n0 > cw->maxcol) cw->maxcol = n0; if(++cw->cury > cw->maxrow) cw->maxrow = cw->cury; if(n0 > CO) { /* attempt to break the line */ for(i = CO-1; i && str[i] != ' ';) i--; if(i) { cw->data[cw->cury-1][++i] = '\0'; tty_putstr(window, attr, &str[i]); } } break; } } void tty_display_file(fname, complain) const char *fname; boolean complain; { #ifdef DEF_PAGER /* this implies that UNIX is defined */ { /* use external pager; this may give security problems */ register int fd = open(fname, 0); if(fd < 0) { if(complain) pline("Cannot open %s.", fname); else docrt(); return; } if(child(1)) { /* Now that child() does a setuid(getuid()) and a chdir(), we may not be able to open file fname anymore, so make it stdin. */ (void) close(0); if(dup(fd)) { if(complain) raw_printf("Cannot open %s as stdin.", fname); } else { (void) execlp(catmore, "page", (char *)0); if(complain) raw_printf("Cannot exec %s.", catmore); } if(complain) sleep(10); /* want to wait_synch() but stdin is gone */ terminate(EXIT_FAILURE); } (void) close(fd); } #else /* DEF_PAGER */ { dlb *f; char buf[BUFSZ]; char *cr; tty_clear_nhwindow(WIN_MESSAGE); /* JTP_DEBUG printf("Opening [%s].\n", fname); getch(); JTP_DEBUG */ f = dlb_fopen(fname, "r"); if (!f) { if(complain) { home(); tty_mark_synch(); tty_raw_print(""); perror(fname); tty_wait_synch(); pline("Cannot open \"%s\".", fname); } else if(u.ux) docrt(); } else { winid datawin = tty_create_nhwindow(NHW_TEXT); if(complain #ifndef NO_TERMS && nh_CD #endif ) { /* attempt to scroll text below map window if there's room */ wins[datawin]->offy = wins[WIN_STATUS]->offy+3; if((int) wins[datawin]->offy + 12 > (int) ttyDisplay->rows) wins[datawin]->offy = 0; } while (dlb_fgets(buf, BUFSZ, f)) { if ((cr = index(buf, '\n')) != 0) *cr = 0; #ifdef MSDOS if ((cr = index(buf, '\r')) != 0) *cr = 0; #endif if (index(buf, '\t') != 0) (void) tabexpand(buf); tty_putstr(datawin, 0, buf); if(wins[datawin]->flags & WIN_CANCELLED) break; } tty_display_nhwindow(datawin, FALSE); tty_destroy_nhwindow(datawin); (void) dlb_fclose(f); } } #endif /* DEF_PAGER */ } void tty_start_menu(window) winid window; { tty_clear_nhwindow(window); return; } /*ARGSUSED*/ /* * Add a menu item to the beginning of the menu list. This list is reversed * later. */ void tty_add_menu(window, glyph, identifier, ch, gch, attr, str, preselected) winid window; /* window to use, must be of type NHW_MENU */ int glyph; /* glyph to display with item (unused) */ const anything *identifier; /* what to return if selected */ char ch; /* keyboard accelerator (0 = pick our own) */ char gch; /* group accelerator (0 = no group) */ int attr; /* attribute for string (like tty_putstr()) */ const char *str; /* menu string */ boolean preselected; /* item is marked as selected */ { register struct WinDesc *cw = 0; tty_menu_item *item; const char *newstr; char buf[4+BUFSZ]; if (str == (const char*) 0) return; if (window == WIN_ERR || (cw = wins[window]) == (struct WinDesc *) 0 || cw->type != NHW_MENU) panic(winpanicstr, window); cw->nitems++; if (identifier->a_void) { int len = strlen(str); if (len >= BUFSZ) { /* We *think* everything's coming in off at most BUFSZ bufs... */ impossible("Menu item too long (%d).", len); len = BUFSZ - 1; } Sprintf(buf, "%c - ", ch ? ch : '?'); (void) strncpy(buf+4, str, len); buf[4+len] = '\0'; newstr = buf; } else newstr = str; item = (tty_menu_item *) alloc(sizeof(tty_menu_item)); item->identifier = *identifier; item->count = -1L; item->selected = preselected; item->selector = ch; item->gselector = gch; item->attr = attr; item->str = copy_of(newstr); item->next = cw->mlist; cw->mlist = item; } /* Invert the given list, can handle NULL as an input. */ STATIC_OVL tty_menu_item * reverse(curr) tty_menu_item *curr; { tty_menu_item *next, *head = 0; while (curr) { next = curr->next; curr->next = head; head = curr; curr = next; } return head; } /* * End a menu in this window, window must a type NHW_MENU. This routine * processes the string list. We calculate the # of pages, then assign * keyboard accelerators as needed. Finally we decide on the width and * height of the window. */ void tty_end_menu(window, prompt) winid window; /* menu to use */ const char *prompt; /* prompt to for menu */ { struct WinDesc *cw = 0; tty_menu_item *curr; short len; int lmax, n; char menu_ch; if (window == WIN_ERR || (cw = wins[window]) == (struct WinDesc *) 0 || cw->type != NHW_MENU) panic(winpanicstr, window); /* Reverse the list so that items are in correct order. */ cw->mlist = reverse(cw->mlist); /* Put the promt at the beginning of the menu. */ if (prompt) { anything any; any.a_void = 0; /* not selectable */ tty_add_menu(window, NO_GLYPH, &any, 0, 0, ATR_NONE, "", MENU_UNSELECTED); tty_add_menu(window, NO_GLYPH, &any, 0, 0, ATR_NONE, prompt, MENU_UNSELECTED); } lmax = min(52, (int)ttyDisplay->rows - 1); /* # lines per page */ cw->npages = (cw->nitems + (lmax - 1)) / lmax; /* # of pages */ /* make sure page list is large enough */ if (cw->plist_size < cw->npages+1 /*need 1 slot beyond last*/) { if (cw->plist) free((genericptr_t)cw->plist); cw->plist_size = cw->npages + 1; cw->plist = (tty_menu_item **) alloc(cw->plist_size * sizeof(tty_menu_item *)); } cw->cols = 0; /* cols is set when the win is initialized... (why?) */ menu_ch = '?'; /* lint suppression */ for (n = 0, curr = cw->mlist; curr; n++, curr = curr->next) { /* set page boundaries and character accelerators */ if ((n % lmax) == 0) { menu_ch = 'a'; cw->plist[n/lmax] = curr; } if (curr->identifier.a_void && !curr->selector) { curr->str[0] = curr->selector = menu_ch; if (menu_ch++ == 'z') menu_ch = 'A'; } /* cut off any lines that are too long */ len = strlen(curr->str) + 2; /* extra space at beg & end */ if (len > (int)ttyDisplay->cols) { curr->str[ttyDisplay->cols-2] = 0; len = ttyDisplay->cols; } if (len > cw->cols) cw->cols = len; } cw->plist[cw->npages] = 0; /* plist terminator */ /* * If greater than 1 page, morestr is "(x of y) " otherwise, "(end) " */ if (cw->npages > 1) { char buf[QBUFSZ]; /* produce the largest demo string */ Sprintf(buf, "(%d of %d) ", cw->npages, cw->npages); len = strlen(buf); cw->morestr = copy_of(""); } else { cw->morestr = copy_of("(end) "); len = strlen(cw->morestr); } if (len > (int)ttyDisplay->cols) { /* truncate the prompt if its too long for the screen */ if (cw->npages <= 1) /* only str in single page case */ cw->morestr[ttyDisplay->cols] = 0; len = ttyDisplay->cols; } if (len > cw->cols) cw->cols = len; cw->maxcol = cw->cols; /* * The number of lines in the first page plus the morestr will be the * maximum size of the window. */ if (cw->npages > 1) cw->maxrow = cw->rows = lmax + 1; else cw->maxrow = cw->rows = cw->nitems + 1; } int tty_select_menu(window, how, menu_list) winid window; int how; menu_item **menu_list; { register struct WinDesc *cw = 0; tty_menu_item *curr; menu_item *mi; int n, cancelled; if(window == WIN_ERR || (cw = wins[window]) == (struct WinDesc *) 0 || cw->type != NHW_MENU) panic(winpanicstr, window); *menu_list = (menu_item *) 0; cw->how = (short) how; morc = 0; tty_display_nhwindow(window, TRUE); cancelled = !!(cw->flags & WIN_CANCELLED); tty_dismiss_nhwindow(window); /* does not destroy window data */ if (cancelled) { n = -1; } else { for (n = 0, curr = cw->mlist; curr; curr = curr->next) if (curr->selected) n++; } if (n > 0) { *menu_list = (menu_item *) alloc(n * sizeof(menu_item)); for (mi = *menu_list, curr = cw->mlist; curr; curr = curr->next) if (curr->selected) { mi->item = curr->identifier; mi->count = curr->count; mi++; } } return n; } /* special hack for treating top line --More-- as a one item menu */ char tty_message_menu(let, how, mesg) char let; int how; const char *mesg; { /* "menu" without selection; use ordinary pline, no more() */ if (how == PICK_NONE) { pline("%s", mesg); return 0; } ttyDisplay->dismiss_more = let; morc = 0; /* barebones pline(); since we're only supposed to be called after response to a prompt, we'll assume that the display is up to date */ tty_putstr(WIN_MESSAGE, 0, mesg); /* if `mesg' didn't wrap (triggering --More--), force --More-- now */ if (ttyDisplay->toplin == 1) more(); /* normally means skip further messages, but in this case it means cancel the current prompt; any other messages should continue to be output normally */ wins[WIN_MESSAGE]->flags &= ~WIN_CANCELLED; ttyDisplay->dismiss_more = 0; return ((how == PICK_ONE && morc == let) || morc == '\033') ? morc : '\0'; } void tty_update_inventory() { return; } void tty_mark_synch() { (void) fflush(stdout); } void tty_wait_synch() { /* we just need to make sure all windows are synch'd */ if(!ttyDisplay || ttyDisplay->rawprint) { getret(); if(ttyDisplay) ttyDisplay->rawprint = 0; } else { tty_display_nhwindow(WIN_MAP, FALSE); if(ttyDisplay->inmore) { addtopl("--More--"); (void) fflush(stdout); } else if(ttyDisplay->inread) { /* this can only happen if we were reading and got interrupted */ ttyDisplay->toplin = 3; /* do this twice; 1st time gets the Quit? message again */ (void) tty_doprev_message(); (void) tty_doprev_message(); ttyDisplay->intr++; (void) fflush(stdout); } } } void docorner(xmin, ymax) register int xmin, ymax; { register int y; register struct WinDesc *cw = wins[WIN_MAP]; if (u.uswallow) { /* Can be done more efficiently */ swallowed(1); return; } #if defined(SIGWINCH) && defined(CLIPPING) if(ymax > LI) ymax = LI; /* can happen if window gets smaller */ #endif for (y = 0; y < ymax; y++) { tty_curs(BASE_WINDOW, xmin,y); /* move cursor */ cl_end(); /* clear to end of line */ #ifdef CLIPPING if (y<(int) cw->offy || y+clipy > ROWNO) continue; /* only refresh board */ #if defined(USE_TILES) && defined(MSDOS) if (iflags.tile_view) row_refresh((xmin/2)+clipx-((int)cw->offx/2),COLNO-1,y+clipy-(int)cw->offy); else #endif row_refresh(xmin+clipx-(int)cw->offx,COLNO-1,y+clipy-(int)cw->offy); #else if (yoffy || y > ROWNO) continue; /* only refresh board */ row_refresh(xmin-(int)cw->offx,COLNO-1,y-(int)cw->offy); #endif } end_glyphout(); if (ymax >= (int) wins[WIN_STATUS]->offy) { /* we have wrecked the bottom line */ flags.botlx = 1; bot(); } } void end_glyphout() { #if defined(ASCIIGRAPH) && !defined(NO_TERMS) if (GFlag) { GFlag = FALSE; graph_off(); } #endif #ifdef TEXTCOLOR if(ttyDisplay->color != NO_COLOR) { term_end_color(); ttyDisplay->color = NO_COLOR; } #endif } void g_putch(in_ch) int in_ch; { register char ch = (char)in_ch; # if defined(ASCIIGRAPH) && !defined(NO_TERMS) if (iflags.IBMgraphics || iflags.eight_bit_tty) { /* IBM-compatible displays don't need other stuff */ (void) putchar(ch); } else if (ch & 0x80) { if (!GFlag || HE_resets_AS) { graph_on(); GFlag = TRUE; } (void) putchar((ch ^ 0x80)); /* Strip 8th bit */ } else { if (GFlag) { graph_off(); GFlag = FALSE; } (void) putchar(ch); } #else (void) putchar(ch); #endif /* ASCIIGRAPH && !NO_TERMS */ return; } #ifdef CLIPPING void setclipped() { clipping = TRUE; clipx = clipy = 0; clipxmax = CO; clipymax = LI - 3; } void tty_cliparound(x, y) int x, y; { extern boolean restoring; int oldx = clipx, oldy = clipy; if (!clipping) return; if (x < clipx + 5) { clipx = max(0, x - 20); clipxmax = clipx + CO; } else if (x > clipxmax - 5) { clipxmax = min(COLNO, clipxmax + 20); clipx = clipxmax - CO; } if (y < clipy + 2) { clipy = max(0, y - (clipymax - clipy) / 2); clipymax = clipy + (LI - 3); } else if (y > clipymax - 2) { clipymax = min(ROWNO, clipymax + (clipymax - clipy) / 2); clipy = clipymax - (LI - 3); } if (clipx != oldx || clipy != oldy) { if (on_level(&u.uz0, &u.uz) && !restoring) (void) doredraw(); } } #endif /* CLIPPING */ /* * tty_print_glyph * * Print the glyph to the output device. Don't flush the output device. * * Since this is only called from show_glyph(), it is assumed that the * position and glyph are always correct (checked there)! */ void tty_print_glyph(window, x, y, glyph) winid window; xchar x, y; int glyph; { uchar ch; register int offset; boolean is_reverse = FALSE; #ifdef TEXTCOLOR int color; #define zap_color(n) color = iflags.use_color ? zapcolors[n] : NO_COLOR #define cmap_color(n) color = iflags.use_color ? defsyms[n].color : NO_COLOR #define obj_color(n) color = iflags.use_color ? objects[n].oc_color : NO_COLOR #define mon_color(n) color = iflags.use_color ? mons[n].mcolor : NO_COLOR #define invis_color(n) color = NO_COLOR #define pet_color(n) color = iflags.use_color ? mons[n].mcolor : \ /* If no color, try to hilite pets; black */ \ /* should be nh_HI */ \ ((iflags.hilite_pet && has_color(CLR_BLACK)) ? \ CLR_BLACK : NO_COLOR) #define warn_color(n) color = iflags.use_color ? def_warnsyms[n].color : NO_COLOR # if defined(REINCARNATION) && defined(ASCIIGRAPH) # define ROGUE_COLOR # endif #else /* no text color */ #define zap_color(n) #define cmap_color(n) #define obj_color(n) #define mon_color(n) #define invis_color(n) #define pet_color(c) #define warn_color(n) #endif #ifdef ROGUE_COLOR # if defined(USE_TILES) && defined(MSDOS) #define HAS_ROGUE_IBM_GRAPHICS (iflags.IBMgraphics && !iflags.grmode && \ Is_rogue_level(&u.uz)) # else #define HAS_ROGUE_IBM_GRAPHICS (iflags.IBMgraphics && Is_rogue_level(&u.uz)) # endif #endif #ifdef CLIPPING if(clipping) { if(x <= clipx || y < clipy || x >= clipxmax || y >= clipymax) return; } #endif /* * Map the glyph back to a character. * * Warning: For speed, this makes an assumption on the order of * offsets. The order is set in display.h. */ if ((offset = (glyph - GLYPH_WARNING_OFF)) >= 0) { /* a warning flash */ ch = warnsyms[offset]; # ifdef ROGUE_COLOR if (HAS_ROGUE_IBM_GRAPHICS) color = NO_COLOR; else # endif warn_color(offset); } else if ((offset = (glyph - GLYPH_SWALLOW_OFF)) >= 0) { /* swallow */ /* see swallow_to_glyph() in display.c */ ch = (uchar) showsyms[S_sw_tl + (offset & 0x7)]; #ifdef ROGUE_COLOR if (HAS_ROGUE_IBM_GRAPHICS && iflags.use_color) color = NO_COLOR; else #endif mon_color(offset >> 3); } else if ((offset = (glyph - GLYPH_ZAP_OFF)) >= 0) { /* zap beam */ /* see zapdir_to_glyph() in display.c */ ch = showsyms[S_vbeam + (offset & 0x3)]; #ifdef ROGUE_COLOR if (HAS_ROGUE_IBM_GRAPHICS && iflags.use_color) color = NO_COLOR; else #endif zap_color((offset >> 2)); } else if ((offset = (glyph - GLYPH_CMAP_OFF)) >= 0) { /* cmap */ ch = showsyms[offset]; #ifdef ROGUE_COLOR if (HAS_ROGUE_IBM_GRAPHICS && iflags.use_color) { if (offset >= S_vwall && offset <= S_hcdoor) color = CLR_BROWN; else if (offset >= S_arrow_trap && offset <= S_polymorph_trap) color = CLR_MAGENTA; else if (offset == S_corr || offset == S_litcorr) color = CLR_GRAY; else if (offset >= S_room && offset <= S_water) color = CLR_GREEN; else color = NO_COLOR; } else #endif cmap_color(offset); } else if ((offset = (glyph - GLYPH_OBJ_OFF)) >= 0) { /* object */ ch = oc_syms[(int)objects[offset].oc_class]; #ifdef ROGUE_COLOR if (HAS_ROGUE_IBM_GRAPHICS && iflags.use_color) { switch(objects[offset].oc_class) { case GOLD_CLASS: color = CLR_YELLOW; break; case FOOD_CLASS: color = CLR_RED; break; default: color = CLR_BRIGHT_BLUE; break; } } else #endif obj_color(offset); } else if ((offset = (glyph - GLYPH_RIDDEN_OFF)) >= 0) { /* mon ridden */ ch = monsyms[(int)mons[offset].mlet]; #ifdef ROGUE_COLOR if (HAS_ROGUE_IBM_GRAPHICS) /* This currently implies that the hero is here -- monsters */ /* don't ride (yet...). Should we set it to yellow like in */ /* the monster case below? There is no equivalent in rogue. */ color = NO_COLOR; /* no need to check iflags.use_color */ else #endif mon_color(offset); } else if ((offset = (glyph - GLYPH_BODY_OFF)) >= 0) { /* a corpse */ ch = oc_syms[(int)objects[CORPSE].oc_class]; #ifdef ROGUE_COLOR if (HAS_ROGUE_IBM_GRAPHICS && iflags.use_color) color = CLR_RED; else #endif mon_color(offset); } else if ((offset = (glyph - GLYPH_DETECT_OFF)) >= 0) { /* mon detect */ ch = monsyms[(int)mons[offset].mlet]; #ifdef ROGUE_COLOR if (HAS_ROGUE_IBM_GRAPHICS) color = NO_COLOR; /* no need to check iflags.use_color */ else #endif mon_color(offset); /* Disabled for now; anyone want to get reverse video to work? */ /* is_reverse = TRUE; */ } else if ((offset = (glyph - GLYPH_INVIS_OFF)) >= 0) { /* invisible */ ch = DEF_INVISIBLE; #ifdef ROGUE_COLOR if (HAS_ROGUE_IBM_GRAPHICS) color = NO_COLOR; /* no need to check iflags.use_color */ else #endif invis_color(offset); } else if ((offset = (glyph - GLYPH_PET_OFF)) >= 0) { /* a pet */ ch = monsyms[(int)mons[offset].mlet]; #ifdef ROGUE_COLOR if (HAS_ROGUE_IBM_GRAPHICS) color = NO_COLOR; /* no need to check iflags.use_color */ else #endif pet_color(offset); } else { /* a monster */ ch = monsyms[(int)mons[glyph].mlet]; #ifdef ROGUE_COLOR if (HAS_ROGUE_IBM_GRAPHICS && iflags.use_color) { if (x == u.ux && y == u.uy) /* actually player should be yellow-on-gray if in a corridor */ color = CLR_YELLOW; else color = NO_COLOR; } else #endif mon_color(glyph); } /* Move the cursor. */ tty_curs(window, x,y); #ifndef NO_TERMS if (ul_hack && ch == '_') { /* non-destructive underscore */ (void) putchar((char) ' '); backsp(); } #endif #ifdef TEXTCOLOR /* Turn off color if no color defined, or rogue level w/o PC graphics. */ # ifdef REINCARNATION # ifdef ASCIIGRAPH if (!has_color(color) || (Is_rogue_level(&u.uz) && !HAS_ROGUE_IBM_GRAPHICS)) # else if (!has_color(color) || Is_rogue_level(&u.uz)) # endif # else if (!has_color(color)) # endif color = NO_COLOR; if (color != ttyDisplay->color) { if(ttyDisplay->color != NO_COLOR) term_end_color(); ttyDisplay->color = color; if(color != NO_COLOR) term_start_color(color); } #endif /* TEXTCOLOR */ #if defined(USE_TILES) && defined(MSDOS) if (iflags.grmode && iflags.tile_view) xputg(glyph,(int)ch); else #endif if (is_reverse) { /* not currently working */ term_start_attr(ATR_INVERSE); g_putch((int)ch); /* print the character */ term_end_attr(ATR_INVERSE); } else g_putch((int)ch); /* print the character */ wins[window]->curx++; /* one character over */ ttyDisplay->curx++; /* the real cursor moved too */ } void tty_raw_print(str) const char *str; { if(ttyDisplay) ttyDisplay->rawprint++; #ifdef MICRO msmsg("%s\n", str); #else puts(str); (void) fflush(stdout); #endif } void tty_raw_print_bold(str) const char *str; { if(ttyDisplay) ttyDisplay->rawprint++; term_start_raw_bold(); #ifdef MICRO msmsg("%s", str); #else (void) fputs(str, stdout); #endif term_end_raw_bold(); #ifdef MICRO msmsg("\n"); #else puts(""); (void) fflush(stdout); #endif } int tty_nhgetch() { int i; #ifdef UNIX /* kludge alert: Some Unix variants return funny values if getc() * is called, interrupted, and then called again. There * is non-reentrant code in the internal _filbuf() routine, called by * getc(). */ static volatile int nesting = 0; char nestbuf; #endif (void) fflush(stdout); /* Note: if raw_print() and wait_synch() get called to report terminal * initialization problems, then wins[] and ttyDisplay might not be * available yet. Such problems will probably be fatal before we get * here, but validate those pointers just in case... */ if (WIN_MESSAGE != WIN_ERR && wins[WIN_MESSAGE]) wins[WIN_MESSAGE]->flags &= ~WIN_STOP; #ifdef UNIX i = ((++nesting == 1) ? tgetch() : (read(fileno(stdin), (genericptr_t)&nestbuf,1) == 1 ? (int)nestbuf : EOF)); --nesting; #else i = tgetch(); #endif if (!i) i = '\033'; /* map NUL to ESC since nethack doesn't expect NUL */ if (ttyDisplay && ttyDisplay->toplin == 1) ttyDisplay->toplin = 2; return i; } /* * return a key, or 0, in which case a mouse button was pressed * mouse events should be returned as character postitions in the map window. * Since normal tty's don't have mice, just return a key. */ /*ARGSUSED*/ int tty_nh_poskey(x, y, mod) int *x, *y, *mod; { # if defined(WIN32CON) int i; (void) fflush(stdout); /* Note: if raw_print() and wait_synch() get called to report terminal * initialization problems, then wins[] and ttyDisplay might not be * available yet. Such problems will probably be fatal before we get * here, but validate those pointers just in case... */ if (WIN_MESSAGE != WIN_ERR && wins[WIN_MESSAGE]) wins[WIN_MESSAGE]->flags &= ~WIN_STOP; i = ntposkey(x, y, mod); if (!i && mod && *mod == 0) i = '\033'; /* map NUL to ESC since nethack doesn't expect NUL */ if (ttyDisplay && ttyDisplay->toplin == 1) ttyDisplay->toplin = 2; return i; # else return tty_nhgetch(); # endif } void win_tty_init() { # if defined(WIN32CON) nttty_open(); # endif return; } #ifdef POSITIONBAR void tty_update_positionbar(posbar) char *posbar; { # ifdef MSDOS video_update_positionbar(posbar); # endif } #endif /* * Allocate a copy of the given string. If null, return a string of * zero length. * * This is an exact duplicate of copy_of() in X11/winmenu.c. */ static char * copy_of(s) const char *s; { if (!s) s = ""; return strcpy((char *) alloc((unsigned) (strlen(s) + 1)), s); } #endif /* TTY_GRAPHICS */ /*wintty.c*/