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     nethack_wid *widptr = nhwids;
471 
472     while (widptr != NULL)
473     {
474         if (widptr->nhwid == wid)
475         {
476             return TRUE;
477 	}
478 
479 	widptr = widptr->next_wid;
480     }
481 
482     return FALSE;
483 }
484 
485 
486 /* Return the orientation of the specified window */
487 
curses_get_window_orientation(winid wid)488 int curses_get_window_orientation(winid wid)
489 {
490     if (!is_main_window(wid))
491     {
492         panic("curses_get_window_orientation: wid out of range. Not a main window.");
493     }
494 
495     return nhwins[wid].orientation;
496 }
497 
498 
499 /* Output a line of text to specified NetHack window with given coordinates
500 and text attributes */
501 
curses_puts(winid wid,int attr,const char * text)502 void curses_puts(winid wid, int attr, const char *text)
503 {
504     anything *identifier;
505     WINDOW *win = NULL;
506 
507     if (is_main_window(wid))
508     {
509         win = curses_get_nhwin(wid);
510     }
511 
512     if (wid == MESSAGE_WIN)
513     {
514         curses_message_win_puts(text, FALSE);
515         return;
516     }
517 
518     if (wid == STATUS_WIN)
519     {
520         curses_update_stats(FALSE);  /* We will do the write ourselves */
521         return;
522     }
523 
524     if (curses_is_menu(wid) || curses_is_text(wid))
525     {
526         if (!curses_menu_exists(wid))
527         {
528             panic("curses_puts: Attempted write to nonexistant window!");
529         }
530         identifier = malloc(sizeof(anything));
531         identifier->a_void = NULL;
532         curses_add_nhmenu_item(wid, identifier, 0, 0, attr, text,
533          FALSE);
534     }
535     else
536     {
537         waddstr(win, text);
538         wrefresh(win);
539     }
540 }
541 
542 
543 /* Clear the contents of a window via the given NetHack winid */
544 
curses_clear_nhwin(winid wid)545 void curses_clear_nhwin(winid wid)
546 {
547     WINDOW *win = curses_get_nhwin(wid);
548     boolean border = curses_window_has_border(wid);
549 
550     if (wid == MAP_WIN)
551     {
552         clearok(win, TRUE); /* Redraw entire screen when refreshed */
553         clear_map();
554     }
555 
556     werase(win);
557 
558     if (border)
559     {
560         box(win, 0, 0);
561     }
562 }
563 
564 
565 /* Return true if given wid is a main NetHack window */
566 
is_main_window(winid wid)567 static boolean is_main_window(winid wid)
568 {
569     if ((wid == MESSAGE_WIN) || (wid == MAP_WIN) ||
570      (wid == STATUS_WIN))
571     {
572         return TRUE;
573     }
574     else
575     {
576         return FALSE;
577     }
578 }
579 
580 
581 /* Unconditionally write a single character to a window at the given
582 coordinates without a refresh.  Currently only used for the map. */
583 
write_char(WINDOW * win,int x,int y,nethack_char nch)584 static void 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 
curses_draw_map(int sx,int sy,int ex,int ey)599 void curses_draw_map(int sx, int sy, int ex, int ey)
600 {
601     int curx, cury;
602     int bspace = 0;
603 #ifdef MAP_SCROLLBARS
604     int sbsx, sbsy, sbex, sbey, count;
605     nethack_char hsb_back, hsb_bar, vsb_back, vsb_bar;
606 #endif
607 
608     if (curses_window_has_border(MAP_WIN))
609     {
610         bspace++;
611     }
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     {
630         sbsx = (sx * ((float)(ex - sx + 1) / COLNO));
631         sbex = (ex * ((float)(ex - sx + 1) / COLNO));
632 
633         for (count = 0; count < sbsx; count++)
634         {
635             write_char(mapwin, count + bspace,
636              ey - sy + 1 + bspace, hsb_back);
637         }
638 
639         for (count = sbsx; count <= sbex; count++)
640         {
641             write_char(mapwin, count + bspace,
642              ey - sy + 1 + bspace, hsb_bar);
643         }
644 
645         for (count = sbex + 1; count <= (ex - sx); count++)
646         {
647             write_char(mapwin, count + bspace,
648              ey - sy + 1 + bspace, hsb_back);
649         }
650     }
651 
652     /* Vertical scrollbar */
653     if ((sy > 0) || (ey < (ROWNO - 1)))
654     {
655         sbsy = (sy * ((float)(ey - sy + 1) / ROWNO));
656         sbey = (ey * ((float)(ey - sy + 1) / ROWNO));
657 
658         for (count = 0; count < sbsy; count++)
659         {
660             write_char(mapwin, ex - sx + 1 + bspace, count + bspace,
661              vsb_back);
662         }
663 
664         for (count = sbsy; count <= sbey; count++)
665         {
666             write_char(mapwin, ex - sx + 1 + bspace, count + bspace,
667              vsb_bar);
668         }
669 
670         for (count = sbey + 1; count <= (ey - sy); count++)
671         {
672             write_char(mapwin, ex - sx + 1 + bspace, count + bspace,
673              vsb_back);
674         }
675     }
676 #endif  /* MAP_SCROLLBARS */
677 
678     for (curx = sx; curx <= ex; curx++)
679     {
680         for (cury = sy; cury <= ey; cury++)
681         {
682             write_char(mapwin, curx - sx + bspace, cury - sy + bspace,
683              map[cury][curx]);
684         }
685     }
686 }
687 
688 
689 /* Init map array to blanks */
690 
clear_map()691 static void clear_map()
692 {
693     int x, y;
694 
695     for (x = 0; x < COLNO; x++)
696     {
697         for (y = 0; y < ROWNO; y++)
698         {
699             map[y][x].ch = ' ';
700             map[y][x].color = NO_COLOR;
701             map[y][x].attr = A_NORMAL;
702         }
703     }
704 }
705 
706 
707 /* Determine visible boundaries of map, and determine if it needs to be
708 based on the location of the player. */
709 
curses_map_borders(int * sx,int * sy,int * ex,int * ey,int ux,int uy)710 boolean curses_map_borders(int *sx, int *sy, int *ex, int *ey, int ux,
711  int uy)
712 {
713     static int width = 0;
714     static int height = 0;
715     static int osx = 0;
716     static int osy = 0;
717     static int oex = 0;
718     static int oey = 0;
719     static int oux = -1;
720     static int ouy = -1;
721 
722     if ((oux == -1) || (ouy == -1))
723     {
724         oux = u.ux;
725         ouy = u.uy;
726     }
727 
728     if (ux == -1)
729     {
730         ux = oux;
731     }
732     else
733     {
734         oux = ux;
735     }
736 
737     if (uy == -1)
738     {
739         uy = ouy;
740     }
741     else
742     {
743         ouy = uy;
744     }
745 
746     curses_get_window_size(MAP_WIN, &height, &width);
747 
748 #ifdef MAP_SCROLLBARS
749     if (width < COLNO)
750     {
751         height--;  /* room for horizontal scrollbar */
752     }
753 
754     if (height < ROWNO)
755     {
756         width--;  /* room for vertical scrollbar */
757 
758         if (width == COLNO)
759         {
760             height--;
761         }
762     }
763 #endif  /* MAP_SCROLLBARS */
764 
765     if (width >= COLNO)
766     {
767         *sx = 0;
768         *ex = COLNO - 1;
769     }
770     else
771     {
772         *ex = (width / 2) + ux;
773         *sx = *ex - (width - 1);
774 
775         if (*ex >= COLNO)
776         {
777             *sx = COLNO - width;
778             *ex = COLNO - 1;
779         }
780         else if (*sx < 0)
781         {
782             *sx = 0;
783             *ex = width - 1;
784         }
785     }
786 
787     if (height >= ROWNO)
788     {
789         *sy = 0;
790         *ey = ROWNO - 1;
791     }
792     else
793     {
794         *ey = (height / 2) + uy;
795         *sy = *ey - (height - 1);
796 
797         if (*ey >= ROWNO)
798         {
799             *sy = ROWNO - height;
800             *ey = ROWNO - 1;
801         }
802         else if (*sy < 0)
803         {
804             *sy = 0;
805             *ey = height - 1;
806         }
807     }
808 
809     if ((*sx != osx) || (*sy != osy) || (*ex != oex) || (*ey != oey) ||
810      map_clipped)
811     {
812         osx = *sx;
813         osy = *sy;
814         oex = *ex;
815         oey = *ey;
816         return TRUE;
817     }
818 
819     return FALSE;
820 }
821