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");
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()
355 curses_count_window((char *) 0); /* clean up orphan */
358 curses_del_wid(nhwids->nhwid);
361 /* Print a single character in the given window at the given coordinates */
364 curses_putch(winid wid, int x, int y, int ch, int color, int attr)
366 static boolean map_initted = FALSE;
368 boolean border = curses_window_has_border(wid);
371 if (wid == STATUS_WIN) {
372 curses_update_stats();
375 if (wid != MAP_WIN) {
384 --x; /* map column [0] is not used; draw column [1] in first screen col */
386 map[y][x].color = color;
387 map[y][x].attr = attr;
390 (void) curses_map_borders(&sx, &sy, &ex, &ey, -1, -1);
392 if ((x >= sx) && (x <= ex) && (y >= sy) && (y <= ey)) {
398 write_char(mapwin, x - sx, y - sy, nch);
400 /* refresh after every character?
401 * Fair go, mate! Some of us are playing from Australia! */
402 /* wrefresh(mapwin); */
406 /* Get x, y coordinates of curses window on the physical terminal window */
409 curses_get_window_xy(winid wid, int *x, int *y)
411 if (!is_main_window(wid)) {
413 "curses_get_window_xy: wid %d out of range. Not a main window.",
425 /* Get usable width and height curses window on the physical terminal window */
428 curses_get_window_size(winid wid, int *height, int *width)
430 *height = nhwins[wid].height;
431 *width = nhwins[wid].width;
435 /* Determine if given window has a visible border */
438 curses_window_has_border(winid wid)
440 return nhwins[wid].border;
444 /* Determine if window for given winid exists */
447 curses_window_exists(winid wid)
451 for (widptr = nhwids; widptr; widptr = widptr->next_wid)
452 if (widptr->nhwid == wid)
459 /* Return the orientation of the specified window */
462 curses_get_window_orientation(winid wid)
464 if (!is_main_window(wid)) {
466 "curses_get_window_orientation: wid %d out of range. Not a main window.",
471 return nhwins[wid].orientation;
475 /* Output a line of text to specified NetHack window with given coordinates
476 and text attributes */
479 curses_puts(winid wid, int attr, const char *text)
484 if (is_main_window(wid)) {
485 win = curses_get_nhwin(wid);
488 if (wid == MESSAGE_WIN) {
489 /* if a no-history message is being shown, remove it */
491 curses_count_window((char *) 0);
493 curses_message_win_puts(text, FALSE);
498 if (wid == STATUS_WIN) {
499 curses_update_stats(); /* We will do the write ourselves */
504 if (curses_is_menu(wid) || curses_is_text(wid)) {
505 if (!curses_menu_exists(wid)) {
507 "curses_puts: Attempted write to nonexistent window %d!",
512 curses_add_nhmenu_item(wid, NO_GLYPH, &Id, 0, 0, attr, text, FALSE);
520 /* Clear the contents of a window via the given NetHack winid */
523 curses_clear_nhwin(winid wid)
525 WINDOW *win = curses_get_nhwin(wid);
526 boolean border = curses_window_has_border(wid);
528 if (wid == MAP_WIN) {
529 clearok(win, TRUE); /* Redraw entire screen when refreshed */
540 /* Change colour of window border to alert player to something */
542 curses_alert_win_border(winid wid, boolean onoff)
544 WINDOW *win = curses_get_nhwin(wid);
546 if (!win || !curses_window_has_border(wid))
549 curses_toggle_color_attr(win, ALERT_BORDER_COLOR, NONE, ON);
552 curses_toggle_color_attr(win, ALERT_BORDER_COLOR, NONE, OFF);
558 curses_alert_main_borders(boolean onoff)
560 curses_alert_win_border(MAP_WIN, onoff);
561 curses_alert_win_border(MESSAGE_WIN, onoff);
562 curses_alert_win_border(STATUS_WIN, onoff);
563 curses_alert_win_border(INV_WIN, onoff);
566 /* Return true if given wid is a main NetHack window */
569 is_main_window(winid wid)
571 if (wid == MESSAGE_WIN || wid == MAP_WIN
572 || wid == STATUS_WIN || wid == INV_WIN)
579 /* Unconditionally write a single character to a window at the given
580 coordinates without a refresh. Currently only used for the map. */
583 write_char(WINDOW * win, int x, int y, nethack_char nch)
585 curses_toggle_color_attr(win, nch.color, nch.attr, ON);
587 mvwaddrawch(win, y, x, nch.ch);
589 mvwaddch(win, y, x, nch.ch);
591 curses_toggle_color_attr(win, nch.color, nch.attr, OFF);
595 /* Draw the entire visible map onto the screen given the visible map
599 curses_draw_map(int sx, int sy, int ex, int ey)
604 #ifdef MAP_SCROLLBARS
605 int sbsx, sbsy, sbex, sbey, count;
606 nethack_char hsb_back, hsb_bar, vsb_back, vsb_bar;
609 if (curses_window_has_border(MAP_WIN)) {
612 #ifdef MAP_SCROLLBARS
614 hsb_back.color = SCROLLBAR_BACK_COLOR;
615 hsb_back.attr = A_NORMAL;
617 hsb_bar.color = SCROLLBAR_COLOR;
618 hsb_bar.attr = A_NORMAL;
620 vsb_back.color = SCROLLBAR_BACK_COLOR;
621 vsb_back.attr = A_NORMAL;
623 vsb_bar.color = SCROLLBAR_COLOR;
624 vsb_bar.attr = A_NORMAL;
626 /* Horizontal scrollbar */
627 if (sx > 0 || ex < (COLNO - 1)) {
628 sbsx = (int) (((long) sx * (long) (ex - sx + 1)) / (long) COLNO);
629 sbex = (int) (((long) ex * (long) (ex - sx + 1)) / (long) COLNO);
631 if (sx > 0 && sbsx == 0)
633 if (ex < ROWNO - 1 && sbex == ROWNO - 1)
636 for (count = 0; count < sbsx; count++) {
637 write_char(mapwin, count + bspace, ey - sy + 1 + bspace, hsb_back);
640 for (count = sbsx; count <= sbex; count++) {
641 write_char(mapwin, count + bspace, ey - sy + 1 + bspace, hsb_bar);
644 for (count = sbex + 1; count <= (ex - sx); count++) {
645 write_char(mapwin, count + bspace, ey - sy + 1 + bspace, hsb_back);
649 /* Vertical scrollbar */
650 if (sy > 0 || ey < (ROWNO - 1)) {
651 sbsy = (int) (((long) sy * (long) (ey - sy + 1)) / (long) ROWNO);
652 sbey = (int) (((long) ey * (long) (ey - sy + 1)) / (long) ROWNO);
654 if (sy > 0 && sbsy == 0)
656 if (ey < ROWNO - 1 && sbey == ROWNO - 1)
659 for (count = 0; count < sbsy; count++) {
660 write_char(mapwin, ex - sx + 1 + bspace, count + bspace, vsb_back);
663 for (count = sbsy; count <= sbey; count++) {
664 write_char(mapwin, ex - sx + 1 + bspace, count + bspace, vsb_bar);
667 for (count = sbey + 1; count <= (ey - sy); count++) {
668 write_char(mapwin, ex - sx + 1 + bspace, count + bspace, vsb_back);
671 #endif /* MAP_SCROLLBARS */
673 for (curx = sx; curx <= ex; curx++) {
674 for (cury = sy; cury <= ey; cury++) {
675 write_char(mapwin, curx - sx + bspace, cury - sy + bspace,
682 /* Init map array to blanks */
689 for (x = 0; x < COLNO; x++) {
690 for (y = 0; y < ROWNO; y++) {
692 map[y][x].color = NO_COLOR;
693 map[y][x].attr = A_NORMAL;
699 /* Determine visible boundaries of map, and determine if it needs to be
700 based on the location of the player. */
703 curses_map_borders(int *sx, int *sy, int *ex, int *ey, int ux, int uy)
705 static int width = 0;
706 static int height = 0;
714 if ((oux == -1) || (ouy == -1)) {
731 curses_get_window_size(MAP_WIN, &height, &width);
733 #ifdef MAP_SCROLLBARS
735 height--; /* room for horizontal scrollbar */
738 if (height < ROWNO) {
739 width--; /* room for vertical scrollbar */
741 if (width == COLNO) {
745 #endif /* MAP_SCROLLBARS */
747 if (width >= COLNO) {
751 *ex = (width / 2) + ux;
752 *sx = *ex - (width - 1);
757 } else if (*sx < 0) {
763 if (height >= ROWNO) {
767 *ey = (height / 2) + uy;
768 *sy = *ey - (height - 1);
771 *sy = ROWNO - height;
773 } else if (*sy < 0) {
779 if ((*sx != osx) || (*sy != osy) || (*ex != oex) || (*ey != oey) ||