1 /* 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" -*-*/
2 /* NetHack 3.6 curswins.c */
3 /* Copyright (c) Karl Garrison, 2010. */
4 /* NetHack may be freely redistributed. See license for details. */
11 /* Window handling for curses interface */
13 /* Private declarations */
16 winid nhwin; /* NetHack window id */
17 WINDOW *curwin; /* Curses window pointer */
18 int width; /* Usable width not counting border */
19 int height; /* Usable height not counting border */
20 int x; /* start of window on terminal (left) */
21 int y; /* start of window on termial (top) */
22 int orientation; /* Placement of window relative to map */
23 boolean border; /* Whether window has a visible border */
27 winid nhwid; /* NetHack window id */
28 struct nhwd *prev_wid; /* Pointer to previous entry */
29 struct nhwd *next_wid; /* Pointer to next entry */
32 typedef struct nhchar {
33 int ch; /* character */
34 int color; /* color info for character */
35 int attr; /* attributes of character */
38 static boolean map_clipped; /* Map window smaller than 80x21 */
39 static nethack_window nhwins[NHWIN_MAX]; /* NetHack window array */
40 static nethack_char map[ROWNO][COLNO]; /* Map window contents */
41 static nethack_wid *nhwids = NULL; /* NetHack wid array */
43 static boolean is_main_window(winid wid);
44 static void write_char(WINDOW * win, int x, int y, nethack_char ch);
45 static void clear_map(void);
47 /* Create a window with the specified size and orientation */
50 curses_create_window(int width, int height, orient orientation)
52 int mapx = 0, mapy = 0, maph = 0, mapw = 0;
56 boolean map_border = FALSE;
59 if ((orientation == UP) || (orientation == DOWN) ||
60 (orientation == LEFT) || (orientation == RIGHT)) {
61 if (invent || (moves > 1)) {
62 map_border = curses_window_has_border(MAP_WIN);
63 curses_get_window_xy(MAP_WIN, &mapx, &mapy);
64 curses_get_window_size(MAP_WIN, &maph, &mapw);
78 width += 2; /* leave room for bounding box */
81 if ((width > term_cols) || (height > term_rows)) {
83 "curses_create_window: Terminal too small for dialog window");
87 switch (orientation) {
89 impossible("curses_create_window: Bad orientation");
90 /* fall through to centre */
92 startx = (term_cols / 2) - (width / 2);
93 starty = (term_rows / 2) - (height / 2);
96 if (invent || (moves > 1)) {
97 startx = (mapw / 2) - (width / 2) + mapx + mapb_offset;
102 starty = mapy + mapb_offset;
105 if (invent || (moves > 1)) {
106 startx = (mapw / 2) - (width / 2) + mapx + mapb_offset;
111 starty = height - mapy - 1 - mapb_offset;
114 if (map_border && (width < term_cols))
118 starty = term_rows - height;
121 if (invent || (moves > 1)) {
122 startx = (mapw + mapx + (mapb_offset * 2)) - width;
124 startx = term_cols - width;
139 win = newwin(height, width, starty, startx);
140 curses_toggle_color_attr(win, DIALOG_BORDER_COLOR, NONE, ON);
142 curses_toggle_color_attr(win, DIALOG_BORDER_COLOR, NONE, OFF);
147 /* Erase and delete curses window, and refresh standard windows */
150 curses_destroy_win(WINDOW *win)
155 curses_refresh_nethack_windows();
159 /* Refresh nethack windows if they exist, or base window if not */
162 curses_refresh_nethack_windows()
164 WINDOW *status_window, *message_window, *map_window, *inv_window;
166 status_window = curses_get_nhwin(STATUS_WIN);
167 message_window = curses_get_nhwin(MESSAGE_WIN);
168 map_window = curses_get_nhwin(MAP_WIN);
169 inv_window = curses_get_nhwin(INV_WIN);
171 if ((moves <= 1) && !invent) {
172 /* Main windows not yet displayed; refresh base window instead */
176 touchwin(status_window);
177 wnoutrefresh(status_window);
178 touchwin(map_window);
179 wnoutrefresh(map_window);
180 touchwin(message_window);
181 wnoutrefresh(message_window);
183 touchwin(inv_window);
184 wnoutrefresh(inv_window);
191 /* Return curses window pointer for given NetHack winid */
194 curses_get_nhwin(winid wid)
196 if (!is_main_window(wid)) {
197 impossible("curses_get_nhwin: wid %d out of range. Not a main window.",
202 return nhwins[wid].curwin;
206 /* Add curses window pointer and window info to list for given NetHack winid */
209 curses_add_nhwin(winid wid, int height, int width, int y, int x,
210 orient orientation, boolean border)
213 int real_width = width;
214 int real_height = height;
216 if (!is_main_window(wid)) {
217 impossible("curses_add_nhwin: wid %d out of range. Not a main window.",
222 nhwins[wid].nhwin = wid;
223 nhwins[wid].border = border;
224 nhwins[wid].width = width;
225 nhwins[wid].height = height;
228 nhwins[wid].orientation = orientation;
231 real_width += 2; /* leave room for bounding box */
235 win = newwin(real_height, real_width, y, x);
247 if ((width < COLNO) || (height < ROWNO)) {
260 nhwins[wid].curwin = win;
264 /* Add wid to list of known window IDs */
267 curses_add_wid(winid wid)
269 nethack_wid *new_wid;
270 nethack_wid *widptr = nhwids;
272 new_wid = (nethack_wid *) alloc((unsigned) sizeof (nethack_wid));
273 new_wid->nhwid = wid;
275 new_wid->next_wid = NULL;
277 if (widptr == NULL) {
278 new_wid->prev_wid = NULL;
281 while (widptr->next_wid != NULL) {
282 widptr = widptr->next_wid;
284 new_wid->prev_wid = widptr;
285 widptr->next_wid = new_wid;
290 /* refresh a curses window via given nethack winid */
293 curses_refresh_nhwin(winid wid)
295 wnoutrefresh(curses_get_nhwin(wid));
300 /* Delete curses window via given NetHack winid and remove entry from list */
303 curses_del_nhwin(winid wid)
305 if (curses_is_menu(wid) || curses_is_text(wid)) {
306 curses_del_menu(wid, TRUE);
308 } else if (wid == INV_WIN) {
309 curses_del_menu(wid, TRUE);
310 /* don't return yet */
313 if (!is_main_window(wid)) {
314 impossible("curses_del_nhwin: wid %d out of range. Not a main window.",
318 nhwins[wid].curwin = NULL;
319 nhwins[wid].nhwin = -1;
323 /* Delete wid from list of known window IDs */
326 curses_del_wid(winid wid)
331 if (curses_is_menu(wid) || curses_is_text(wid)) {
332 curses_del_menu(wid, FALSE);
335 for (widptr = nhwids; widptr; widptr = widptr->next_wid) {
336 if (widptr->nhwid == wid) {
337 if ((tmpwid = widptr->prev_wid) != NULL) {
338 tmpwid->next_wid = widptr->next_wid;
340 nhwids = widptr->next_wid; /* New head mode, or NULL */
342 if ((tmpwid = widptr->next_wid) != NULL) {
343 tmpwid->prev_wid = widptr->prev_wid;
351 /* called by destroy_nhwindows() prior to exit */
353 curs_destroy_all_wins()
356 curses_del_wid(nhwids->nhwid);
359 /* Print a single character in the given window at the given coordinates */
362 curses_putch(winid wid, int x, int y, int ch, int color, int attr)
365 boolean border = curses_window_has_border(wid);
367 static boolean map_initted = FALSE;
369 if (wid == STATUS_WIN) {
370 curses_update_stats();
373 if (wid != MAP_WIN) {
383 map[y][x].color = color;
384 map[y][x].attr = attr;
387 (void) curses_map_borders(&sx, &sy, &ex, &ey, -1, -1);
389 if ((x >= sx) && (x <= ex) && (y >= sy) && (y <= ey)) {
395 write_char(mapwin, x - sx, y - sy, nch);
397 /* refresh after every character?
398 * Fair go, mate! Some of us are playing from Australia! */
399 /* wrefresh(mapwin); */
403 /* Get x, y coordinates of curses window on the physical terminal window */
406 curses_get_window_xy(winid wid, int *x, int *y)
408 if (!is_main_window(wid)) {
410 "curses_get_window_xy: wid %d out of range. Not a main window.",
422 /* Get usable width and height curses window on the physical terminal window */
425 curses_get_window_size(winid wid, int *height, int *width)
427 *height = nhwins[wid].height;
428 *width = nhwins[wid].width;
432 /* Determine if given window has a visible border */
435 curses_window_has_border(winid wid)
437 return nhwins[wid].border;
441 /* Determine if window for given winid exists */
444 curses_window_exists(winid wid)
448 for (widptr = nhwids; widptr; widptr = widptr->next_wid)
449 if (widptr->nhwid == wid)
456 /* Return the orientation of the specified window */
459 curses_get_window_orientation(winid wid)
461 if (!is_main_window(wid)) {
463 "curses_get_window_orientation: wid %d out of range. Not a main window.",
468 return nhwins[wid].orientation;
472 /* Output a line of text to specified NetHack window with given coordinates
473 and text attributes */
476 curses_puts(winid wid, int attr, const char *text)
481 if (is_main_window(wid)) {
482 win = curses_get_nhwin(wid);
485 if (wid == MESSAGE_WIN) {
486 curses_message_win_puts(text, FALSE);
491 if (wid == STATUS_WIN) {
492 curses_update_stats(); /* We will do the write ourselves */
497 if (curses_is_menu(wid) || curses_is_text(wid)) {
498 if (!curses_menu_exists(wid)) {
500 "curses_puts: Attempted write to nonexistant window %d!",
505 curses_add_nhmenu_item(wid, NO_GLYPH, &Id, 0, 0, attr, text, FALSE);
513 /* Clear the contents of a window via the given NetHack winid */
516 curses_clear_nhwin(winid wid)
518 WINDOW *win = curses_get_nhwin(wid);
519 boolean border = curses_window_has_border(wid);
521 if (wid == MAP_WIN) {
522 clearok(win, TRUE); /* Redraw entire screen when refreshed */
533 /* Change colour of window border to alert player to something */
535 curses_alert_win_border(winid wid, boolean onoff)
537 WINDOW *win = curses_get_nhwin(wid);
539 if (!win || !curses_window_has_border(wid))
542 curses_toggle_color_attr(win, ALERT_BORDER_COLOR, NONE, ON);
545 curses_toggle_color_attr(win, ALERT_BORDER_COLOR, NONE, OFF);
551 curses_alert_main_borders(boolean onoff)
553 curses_alert_win_border(MAP_WIN, onoff);
554 curses_alert_win_border(MESSAGE_WIN, onoff);
555 curses_alert_win_border(STATUS_WIN, onoff);
556 curses_alert_win_border(INV_WIN, onoff);
559 /* Return true if given wid is a main NetHack window */
562 is_main_window(winid wid)
564 if (wid == MESSAGE_WIN || wid == MAP_WIN
565 || wid == STATUS_WIN || wid == INV_WIN)
572 /* Unconditionally write a single character to a window at the given
573 coordinates without a refresh. Currently only used for the map. */
576 write_char(WINDOW * win, int x, int y, nethack_char nch)
578 curses_toggle_color_attr(win, nch.color, nch.attr, ON);
580 mvwaddrawch(win, y, x, nch.ch);
582 mvwaddch(win, y, x, nch.ch);
584 curses_toggle_color_attr(win, nch.color, nch.attr, OFF);
588 /* Draw the entire visible map onto the screen given the visible map
592 curses_draw_map(int sx, int sy, int ex, int ey)
597 #ifdef MAP_SCROLLBARS
598 int sbsx, sbsy, sbex, sbey, count;
599 nethack_char hsb_back, hsb_bar, vsb_back, vsb_bar;
602 if (curses_window_has_border(MAP_WIN)) {
605 #ifdef MAP_SCROLLBARS
607 hsb_back.color = SCROLLBAR_BACK_COLOR;
608 hsb_back.attr = A_NORMAL;
610 hsb_bar.color = SCROLLBAR_COLOR;
611 hsb_bar.attr = A_NORMAL;
613 vsb_back.color = SCROLLBAR_BACK_COLOR;
614 vsb_back.attr = A_NORMAL;
616 vsb_bar.color = SCROLLBAR_COLOR;
617 vsb_bar.attr = A_NORMAL;
619 /* Horizontal scrollbar */
620 if ((sx > 0) || (ex < (COLNO - 1))) {
621 sbsx = (sx * ((long) (ex - sx + 1) / COLNO));
622 sbex = (ex * ((long) (ex - sx + 1) / COLNO));
624 for (count = 0; count < sbsx; count++) {
625 write_char(mapwin, count + bspace, ey - sy + 1 + bspace, hsb_back);
628 for (count = sbsx; count <= sbex; count++) {
629 write_char(mapwin, count + bspace, ey - sy + 1 + bspace, hsb_bar);
632 for (count = sbex + 1; count <= (ex - sx); count++) {
633 write_char(mapwin, count + bspace, ey - sy + 1 + bspace, hsb_back);
637 /* Vertical scrollbar */
638 if ((sy > 0) || (ey < (ROWNO - 1))) {
639 sbsy = (sy * ((long) (ey - sy + 1) / ROWNO));
640 sbey = (ey * ((long) (ey - sy + 1) / ROWNO));
642 for (count = 0; count < sbsy; count++) {
643 write_char(mapwin, ex - sx + 1 + bspace, count + bspace, vsb_back);
646 for (count = sbsy; count <= sbey; count++) {
647 write_char(mapwin, ex - sx + 1 + bspace, count + bspace, vsb_bar);
650 for (count = sbey + 1; count <= (ey - sy); count++) {
651 write_char(mapwin, ex - sx + 1 + bspace, count + bspace, vsb_back);
654 #endif /* MAP_SCROLLBARS */
656 for (curx = sx; curx <= ex; curx++) {
657 for (cury = sy; cury <= ey; cury++) {
658 write_char(mapwin, curx - sx + bspace, cury - sy + bspace,
665 /* Init map array to blanks */
672 for (x = 0; x < COLNO; x++) {
673 for (y = 0; y < ROWNO; y++) {
675 map[y][x].color = NO_COLOR;
676 map[y][x].attr = A_NORMAL;
682 /* Determine visible boundaries of map, and determine if it needs to be
683 based on the location of the player. */
686 curses_map_borders(int *sx, int *sy, int *ex, int *ey, int ux, int uy)
688 static int width = 0;
689 static int height = 0;
697 if ((oux == -1) || (ouy == -1)) {
714 curses_get_window_size(MAP_WIN, &height, &width);
716 #ifdef MAP_SCROLLBARS
718 height--; /* room for horizontal scrollbar */
721 if (height < ROWNO) {
722 width--; /* room for vertical scrollbar */
724 if (width == COLNO) {
728 #endif /* MAP_SCROLLBARS */
730 if (width >= COLNO) {
734 *ex = (width / 2) + ux;
735 *sx = *ex - (width - 1);
740 } else if (*sx < 0) {
746 if (height >= ROWNO) {
750 *ey = (height / 2) + uy;
751 *sy = *ey - (height - 1);
754 *sy = ROWNO - height;
756 } else if (*sy < 0) {
762 if ((*sx != osx) || (*sy != osy) || (*ex != oex) || (*ey != oey) ||