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