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.7 curswins.c */
3 /* Copyright (c) Karl Garrison, 2010. */
4 /* NetHack may be freely redistributed.  See license for details. */
5 
6 #include "curses.h"
7 #include "hack.h"
8 #include "wincurs.h"
9 #include "curswins.h"
10 
11 /* Window handling for curses interface */
12 
13 /* Private declarations */
14 
15 typedef struct nhw {
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 */
24 } nethack_window;
25 
26 typedef struct nhwd {
27     winid nhwid;                /* NetHack window id */
28     struct nhwd *prev_wid;      /* Pointer to previous entry */
29     struct nhwd *next_wid;      /* Pointer to next entry */
30 } nethack_wid;
31 
32 typedef struct nhchar {
33     int ch;                     /* character */
34     int color;                  /* color info for character */
35     int attr;                   /* attributes of character */
36 } nethack_char;
37 
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 */
42 
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);
46 
47 /* Create a window with the specified size and orientation */
48 
49 WINDOW *
curses_create_window(int width,int height,orient orientation)50 curses_create_window(int width, int height, orient orientation)
51 {
52     int mapx = 0, mapy = 0, maph = 0, mapw = 0;
53     int startx = 0;
54     int starty = 0;
55     WINDOW *win;
56     boolean map_border = FALSE;
57     int mapb_offset = 0;
58 
59     if ((orientation == UP) || (orientation == DOWN) ||
60         (orientation == LEFT) || (orientation == RIGHT)) {
61         if (g.invent || (g.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);
65         } else {
66             map_border = TRUE;
67             mapx = 0;
68             mapy = 0;
69             maph = term_rows;
70             mapw = term_cols;
71         }
72     }
73 
74     if (map_border) {
75         mapb_offset = 1;
76     }
77 
78     width += 2;                 /* leave room for bounding box */
79     height += 2;
80 
81     if ((width > term_cols) || (height > term_rows)) {
82         impossible(
83                 "curses_create_window: Terminal too small for dialog window");
84         width = term_cols;
85         height = term_rows;
86     }
87     switch (orientation) {
88     default:
89         impossible("curses_create_window: Bad orientation");
90         /*FALLTHRU*/
91     case CENTER:
92         startx = (term_cols / 2) - (width / 2);
93         starty = (term_rows / 2) - (height / 2);
94         break;
95     case UP:
96         if (g.invent || (g.moves > 1)) {
97             startx = (mapw / 2) - (width / 2) + mapx + mapb_offset;
98         } else {
99             startx = 0;
100         }
101 
102         starty = mapy + mapb_offset;
103         break;
104     case DOWN:
105         if (g.invent || (g.moves > 1)) {
106             startx = (mapw / 2) - (width / 2) + mapx + mapb_offset;
107         } else {
108             startx = 0;
109         }
110 
111         starty = height - mapy - 1 - mapb_offset;
112         break;
113     case LEFT:
114         if (map_border && (width < term_cols))
115             startx = 1;
116         else
117             startx = 0;
118         starty = term_rows - height;
119         break;
120     case RIGHT:
121         if (g.invent || (g.moves > 1)) {
122             startx = (mapw + mapx + (mapb_offset * 2)) - width;
123         } else {
124             startx = term_cols - width;
125         }
126 
127         starty = 0;
128         break;
129     }
130 
131     if (startx < 0) {
132         startx = 0;
133     }
134 
135     if (starty < 0) {
136         starty = 0;
137     }
138 
139     win = newwin(height, width, starty, startx);
140     curses_toggle_color_attr(win, DIALOG_BORDER_COLOR, NONE, ON);
141     box(win, 0, 0);
142     curses_toggle_color_attr(win, DIALOG_BORDER_COLOR, NONE, OFF);
143     return win;
144 }
145 
146 
147 /* Erase and delete curses window, and refresh standard windows */
148 
149 void
curses_destroy_win(WINDOW * win)150 curses_destroy_win(WINDOW *win)
151 {
152     werase(win);
153     wrefresh(win);
154     delwin(win);
155     curses_refresh_nethack_windows();
156 }
157 
158 
159 /* Refresh nethack windows if they exist, or base window if not */
160 
161 void
curses_refresh_nethack_windows(void)162 curses_refresh_nethack_windows(void)
163 {
164     WINDOW *status_window, *message_window, *map_window, *inv_window;
165 
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);
170 
171     if ((g.moves <= 1) && !g.invent) {
172         /* Main windows not yet displayed; refresh base window instead */
173         touchwin(stdscr);
174         refresh();
175     } else {
176         touchwin(status_window);
177         wnoutrefresh(status_window);
178         touchwin(map_window);
179         wnoutrefresh(map_window);
180         touchwin(message_window);
181         wnoutrefresh(message_window);
182         if (inv_window) {
183             touchwin(inv_window);
184             wnoutrefresh(inv_window);
185         }
186         doupdate();
187     }
188 }
189 
190 
191 /* Return curses window pointer for given NetHack winid */
192 
193 WINDOW *
curses_get_nhwin(winid wid)194 curses_get_nhwin(winid wid)
195 {
196     if (!is_main_window(wid)) {
197         impossible("curses_get_nhwin: wid %d out of range. Not a main window.",
198                    wid);
199         return NULL;
200     }
201 
202     return nhwins[wid].curwin;
203 }
204 
205 
206 /* Add curses window pointer and window info to list for given NetHack winid */
207 
208 void
curses_add_nhwin(winid wid,int height,int width,int y,int x,orient orientation,boolean border)209 curses_add_nhwin(winid wid, int height, int width, int y, int x,
210                  orient orientation, boolean border)
211 {
212     WINDOW *win;
213     int real_width = width;
214     int real_height = height;
215 
216     if (!is_main_window(wid)) {
217         impossible("curses_add_nhwin: wid %d out of range. Not a main window.",
218                    wid);
219         return;
220     }
221 
222     nhwins[wid].nhwin = wid;
223     nhwins[wid].border = border;
224     nhwins[wid].width = width;
225     nhwins[wid].height = height;
226     nhwins[wid].x = x;
227     nhwins[wid].y = y;
228     nhwins[wid].orientation = orientation;
229 
230     if (border) {
231         real_width += 2;        /* leave room for bounding box */
232         real_height += 2;
233     }
234 
235     win = newwin(real_height, real_width, y, x);
236 
237     switch (wid) {
238     case MESSAGE_WIN:
239         messagewin = win;
240         break;
241     case STATUS_WIN:
242         statuswin = win;
243         break;
244     case MAP_WIN:
245         mapwin = win;
246 
247         if ((width < COLNO) || (height < ROWNO)) {
248             map_clipped = TRUE;
249         } else {
250             map_clipped = FALSE;
251         }
252 
253         break;
254     }
255 
256     if (border) {
257         box(win, 0, 0);
258     }
259 
260     nhwins[wid].curwin = win;
261 }
262 
263 
264 /* Add wid to list of known window IDs */
265 
266 void
curses_add_wid(winid wid)267 curses_add_wid(winid wid)
268 {
269     nethack_wid *new_wid;
270     nethack_wid *widptr = nhwids;
271 
272     new_wid = (nethack_wid *) alloc((unsigned) sizeof (nethack_wid));
273     new_wid->nhwid = wid;
274 
275     new_wid->next_wid = NULL;
276 
277     if (widptr == NULL) {
278         new_wid->prev_wid = NULL;
279         nhwids = new_wid;
280     } else {
281         while (widptr->next_wid != NULL) {
282             widptr = widptr->next_wid;
283         }
284         new_wid->prev_wid = widptr;
285         widptr->next_wid = new_wid;
286     }
287 }
288 
289 
290 /* refresh a curses window via given nethack winid */
291 
292 void
curses_refresh_nhwin(winid wid)293 curses_refresh_nhwin(winid wid)
294 {
295     wnoutrefresh(curses_get_nhwin(wid));
296     doupdate();
297 }
298 
299 
300 /* Delete curses window via given NetHack winid and remove entry from list */
301 
302 void
curses_del_nhwin(winid wid)303 curses_del_nhwin(winid wid)
304 {
305     if (curses_is_menu(wid) || curses_is_text(wid)) {
306         curses_del_menu(wid, TRUE);
307         return;
308     } else if (wid == INV_WIN) {
309         curses_del_menu(wid, TRUE);
310         /* don't return yet */
311     }
312 
313     if (!is_main_window(wid)) {
314         impossible("curses_del_nhwin: wid %d out of range. Not a main window.",
315                    wid);
316         return;
317     }
318     nhwins[wid].curwin = NULL;
319     nhwins[wid].nhwin = -1;
320 }
321 
322 
323 /* Delete wid from list of known window IDs */
324 
325 void
curses_del_wid(winid wid)326 curses_del_wid(winid wid)
327 {
328     nethack_wid *tmpwid;
329     nethack_wid *widptr;
330 
331     if (curses_is_menu(wid) || curses_is_text(wid)) {
332         curses_del_menu(wid, FALSE);
333     }
334 
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;
339             } else {
340                 nhwids = widptr->next_wid;      /* New head mode, or NULL */
341             }
342             if ((tmpwid = widptr->next_wid) != NULL) {
343                 tmpwid->prev_wid = widptr->prev_wid;
344             }
345             free(widptr);
346             break;
347         }
348     }
349 }
350 
351 /* called by destroy_nhwindows() prior to exit */
352 void
curs_destroy_all_wins(void)353 curs_destroy_all_wins(void)
354 {
355     curses_count_window((char *) 0); /* clean up orphan */
356 
357     while (nhwids)
358         curses_del_wid(nhwids->nhwid);
359 }
360 
361 /* Print a single character in the given window at the given coordinates */
362 
363 void
curses_putch(winid wid,int x,int y,int ch,int color,int attr)364 curses_putch(winid wid, int x, int y, int ch, int color, int attr)
365 {
366     static boolean map_initted = FALSE;
367     int sx, sy, ex, ey;
368     boolean border = curses_window_has_border(wid);
369     nethack_char nch;
370 /*
371     if (wid == STATUS_WIN) {
372         curses_update_stats();
373     }
374 */
375     if (wid != MAP_WIN) {
376         return;
377     }
378 
379     if (!map_initted) {
380         clear_map();
381         map_initted = TRUE;
382     }
383 
384     --x; /* map column [0] is not used; draw column [1] in first screen col */
385     map[y][x].ch = ch;
386     map[y][x].color = color;
387     map[y][x].attr = attr;
388     nch = map[y][x];
389 
390     (void) curses_map_borders(&sx, &sy, &ex, &ey, -1, -1);
391 
392     if ((x >= sx) && (x <= ex) && (y >= sy) && (y <= ey)) {
393         if (border) {
394             x++;
395             y++;
396         }
397 
398         write_char(mapwin, x - sx, y - sy, nch);
399     }
400     /* refresh after every character?
401      * Fair go, mate! Some of us are playing from Australia! */
402     /* wrefresh(mapwin); */
403 }
404 
405 
406 /* Get x, y coordinates of curses window on the physical terminal window */
407 
408 void
curses_get_window_xy(winid wid,int * x,int * y)409 curses_get_window_xy(winid wid, int *x, int *y)
410 {
411     if (!is_main_window(wid)) {
412         impossible(
413               "curses_get_window_xy: wid %d out of range. Not a main window.",
414                    wid);
415         *x = 0;
416         *y = 0;
417         return;
418     }
419 
420     *x = nhwins[wid].x;
421     *y = nhwins[wid].y;
422 }
423 
424 
425 /* Get usable width and height curses window on the physical terminal window */
426 
427 void
curses_get_window_size(winid wid,int * height,int * width)428 curses_get_window_size(winid wid, int *height, int *width)
429 {
430     *height = nhwins[wid].height;
431     *width = nhwins[wid].width;
432 }
433 
434 
435 /* Determine if given window has a visible border */
436 
437 boolean
curses_window_has_border(winid wid)438 curses_window_has_border(winid wid)
439 {
440     return nhwins[wid].border;
441 }
442 
443 
444 /* Determine if window for given winid exists */
445 
446 boolean
curses_window_exists(winid wid)447 curses_window_exists(winid wid)
448 {
449     nethack_wid *widptr;
450 
451     for (widptr = nhwids; widptr; widptr = widptr->next_wid)
452         if (widptr->nhwid == wid)
453             return TRUE;
454 
455     return FALSE;
456 }
457 
458 
459 /* Return the orientation of the specified window */
460 
461 int
curses_get_window_orientation(winid wid)462 curses_get_window_orientation(winid wid)
463 {
464     if (!is_main_window(wid)) {
465         impossible(
466      "curses_get_window_orientation: wid %d out of range. Not a main window.",
467                    wid);
468         return CENTER;
469     }
470 
471     return nhwins[wid].orientation;
472 }
473 
474 
475 /* Output a line of text to specified NetHack window with given coordinates
476    and text attributes */
477 
478 void
curses_puts(winid wid,int attr,const char * text)479 curses_puts(winid wid, int attr, const char *text)
480 {
481     anything Id;
482     WINDOW *win = NULL;
483 
484     if (is_main_window(wid)) {
485         win = curses_get_nhwin(wid);
486     }
487 
488     if (wid == MESSAGE_WIN) {
489         /* if a no-history message is being shown, remove it */
490         if (counting)
491             curses_count_window((char *) 0);
492 
493         curses_message_win_puts(text, FALSE);
494         return;
495     }
496 
497 #if 0
498     if (wid == STATUS_WIN) {
499         curses_update_stats();     /* We will do the write ourselves */
500         return;
501     }
502 #endif
503 
504     if (curses_is_menu(wid) || curses_is_text(wid)) {
505         if (!curses_menu_exists(wid)) {
506             impossible(
507                      "curses_puts: Attempted write to nonexistent window %d!",
508                        wid);
509             return;
510         }
511         Id = cg.zeroany;
512         curses_add_nhmenu_item(wid, &nul_glyphinfo, &Id, 0, 0,
513                                attr, text, MENU_ITEMFLAGS_NONE);
514     } else {
515         waddstr(win, text);
516         wnoutrefresh(win);
517     }
518 }
519 
520 
521 /* Clear the contents of a window via the given NetHack winid */
522 
523 void
curses_clear_nhwin(winid wid)524 curses_clear_nhwin(winid wid)
525 {
526     WINDOW *win = curses_get_nhwin(wid);
527     boolean border = curses_window_has_border(wid);
528 
529     if (wid == MAP_WIN) {
530         clearok(win, TRUE);     /* Redraw entire screen when refreshed */
531         clear_map();
532     }
533 
534     werase(win);
535 
536     if (border) {
537         box(win, 0, 0);
538     }
539 }
540 
541 /* Change colour of window border to alert player to something */
542 void
curses_alert_win_border(winid wid,boolean onoff)543 curses_alert_win_border(winid wid, boolean onoff)
544 {
545     WINDOW *win = curses_get_nhwin(wid);
546 
547     if (!win || !curses_window_has_border(wid))
548         return;
549     if (onoff)
550         curses_toggle_color_attr(win, ALERT_BORDER_COLOR, NONE, ON);
551     box(win, 0, 0);
552     if (onoff)
553         curses_toggle_color_attr(win, ALERT_BORDER_COLOR, NONE, OFF);
554     wnoutrefresh(win);
555 }
556 
557 
558 void
curses_alert_main_borders(boolean onoff)559 curses_alert_main_borders(boolean onoff)
560 {
561     curses_alert_win_border(MAP_WIN, onoff);
562     curses_alert_win_border(MESSAGE_WIN, onoff);
563     curses_alert_win_border(STATUS_WIN, onoff);
564     curses_alert_win_border(INV_WIN, onoff);
565 }
566 
567 /* Return true if given wid is a main NetHack window */
568 
569 static boolean
is_main_window(winid wid)570 is_main_window(winid wid)
571 {
572     if (wid == MESSAGE_WIN || wid == MAP_WIN
573         || wid == STATUS_WIN || wid == INV_WIN)
574         return TRUE;
575 
576     return FALSE;
577 }
578 
579 
580 /* Unconditionally write a single character to a window at the given
581 coordinates without a refresh.  Currently only used for the map. */
582 
583 static void
write_char(WINDOW * win,int x,int y,nethack_char nch)584 write_char(WINDOW * win, int x, int y, nethack_char nch)
585 {
586     curses_toggle_color_attr(win, nch.color, nch.attr, ON);
587 #ifdef PDCURSES
588     mvwaddrawch(win, y, x, nch.ch);
589 #else
590     mvwaddch(win, y, x, nch.ch);
591 #endif
592     curses_toggle_color_attr(win, nch.color, nch.attr, OFF);
593 }
594 
595 
596 /* Draw the entire visible map onto the screen given the visible map
597 boundaries */
598 
599 void
curses_draw_map(int sx,int sy,int ex,int ey)600 curses_draw_map(int sx, int sy, int ex, int ey)
601 {
602     int curx, cury;
603     int bspace = 0;
604 
605 #ifdef MAP_SCROLLBARS
606     int sbsx, sbsy, sbex, sbey, count;
607     nethack_char hsb_back, hsb_bar, vsb_back, vsb_bar;
608 #endif
609 
610     if (curses_window_has_border(MAP_WIN)) {
611         bspace++;
612     }
613 #ifdef MAP_SCROLLBARS
614     hsb_back.ch = '-';
615     hsb_back.color = SCROLLBAR_BACK_COLOR;
616     hsb_back.attr = A_NORMAL;
617     hsb_bar.ch = '*';
618     hsb_bar.color = SCROLLBAR_COLOR;
619     hsb_bar.attr = A_NORMAL;
620     vsb_back.ch = '|';
621     vsb_back.color = SCROLLBAR_BACK_COLOR;
622     vsb_back.attr = A_NORMAL;
623     vsb_bar.ch = '*';
624     vsb_bar.color = SCROLLBAR_COLOR;
625     vsb_bar.attr = A_NORMAL;
626 
627     /* Horizontal scrollbar */
628     if (sx > 0 || ex < (COLNO - 1)) {
629          sbsx = (int) (((long) sx * (long) (ex - sx + 1)) / (long) COLNO);
630          sbex = (int) (((long) ex * (long) (ex - sx + 1)) / (long) COLNO);
631 
632         if (sx > 0 && sbsx == 0)
633             ++sbsx;
634         if (ex < ROWNO - 1 && sbex == ROWNO - 1)
635             --sbex;
636 
637         for (count = 0; count < sbsx; count++) {
638             write_char(mapwin, count + bspace, ey - sy + 1 + bspace, hsb_back);
639         }
640 
641         for (count = sbsx; count <= sbex; count++) {
642             write_char(mapwin, count + bspace, ey - sy + 1 + bspace, hsb_bar);
643         }
644 
645         for (count = sbex + 1; count <= (ex - sx); count++) {
646             write_char(mapwin, count + bspace, ey - sy + 1 + bspace, hsb_back);
647         }
648     }
649 
650     /* Vertical scrollbar */
651     if (sy > 0 || ey < (ROWNO - 1)) {
652         sbsy = (int) (((long) sy * (long) (ey - sy + 1)) / (long) ROWNO);
653         sbey = (int) (((long) ey * (long) (ey - sy + 1)) / (long) ROWNO);
654 
655         if (sy > 0 && sbsy == 0)
656             ++sbsy;
657         if (ey < ROWNO - 1 && sbey == ROWNO - 1)
658             --sbey;
659 
660         for (count = 0; count < sbsy; count++) {
661             write_char(mapwin, ex - sx + 1 + bspace, count + bspace, vsb_back);
662         }
663 
664         for (count = sbsy; count <= sbey; count++) {
665             write_char(mapwin, ex - sx + 1 + bspace, count + bspace, vsb_bar);
666         }
667 
668         for (count = sbey + 1; count <= (ey - sy); count++) {
669             write_char(mapwin, ex - sx + 1 + bspace, count + bspace, vsb_back);
670         }
671     }
672 #endif /* MAP_SCROLLBARS */
673 
674     for (curx = sx; curx <= ex; curx++) {
675         for (cury = sy; cury <= ey; cury++) {
676             write_char(mapwin, curx - sx + bspace, cury - sy + bspace,
677                        map[cury][curx]);
678         }
679     }
680 }
681 
682 
683 /* Init map array to blanks */
684 
685 static void
clear_map(void)686 clear_map(void)
687 {
688     int x, y;
689 
690     for (x = 0; x < COLNO; x++) {
691         for (y = 0; y < ROWNO; y++) {
692             map[y][x].ch = ' ';
693             map[y][x].color = NO_COLOR;
694             map[y][x].attr = A_NORMAL;
695         }
696     }
697 }
698 
699 
700 /* Determine visible boundaries of map, and determine if it needs to be
701 based on the location of the player. */
702 
703 boolean
curses_map_borders(int * sx,int * sy,int * ex,int * ey,int ux,int uy)704 curses_map_borders(int *sx, int *sy, int *ex, int *ey, int ux, int uy)
705 {
706     static int width = 0;
707     static int height = 0;
708     static int osx = 0;
709     static int osy = 0;
710     static int oex = 0;
711     static int oey = 0;
712     static int oux = -1;
713     static int ouy = -1;
714 
715     if ((oux == -1) || (ouy == -1)) {
716         oux = u.ux;
717         ouy = u.uy;
718     }
719 
720     if (ux == -1) {
721         ux = oux;
722     } else {
723         oux = ux;
724     }
725 
726     if (uy == -1) {
727         uy = ouy;
728     } else {
729         ouy = uy;
730     }
731 
732     curses_get_window_size(MAP_WIN, &height, &width);
733 
734 #ifdef MAP_SCROLLBARS
735     if (width < COLNO) {
736         height--;               /* room for horizontal scrollbar */
737     }
738 
739     if (height < ROWNO) {
740         width--;                /* room for vertical scrollbar */
741 
742         if (width == COLNO) {
743             height--;
744         }
745     }
746 #endif /* MAP_SCROLLBARS */
747 
748     if (width >= COLNO) {
749         *sx = 0;
750         *ex = COLNO - 1;
751     } else {
752         *ex = (width / 2) + ux;
753         *sx = *ex - (width - 1);
754 
755         if (*ex >= COLNO) {
756             *sx = COLNO - width;
757             *ex = COLNO - 1;
758         } else if (*sx < 0) {
759             *sx = 0;
760             *ex = width - 1;
761         }
762     }
763 
764     if (height >= ROWNO) {
765         *sy = 0;
766         *ey = ROWNO - 1;
767     } else {
768         *ey = (height / 2) + uy;
769         *sy = *ey - (height - 1);
770 
771         if (*ey >= ROWNO) {
772             *sy = ROWNO - height;
773             *ey = ROWNO - 1;
774         } else if (*sy < 0) {
775             *sy = 0;
776             *ey = height - 1;
777         }
778     }
779 
780     if ((*sx != osx) || (*sy != osy) || (*ex != oex) || (*ey != oey) ||
781         map_clipped) {
782         osx = *sx;
783         osy = *sy;
784         oex = *ex;
785         oey = *ey;
786         return TRUE;
787     }
788 
789     return FALSE;
790 }
791