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