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