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.6 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 (invent || (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 (invent || (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 (invent || (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 (invent || (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()162 curses_refresh_nethack_windows()
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 ((moves <= 1) && !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()353 curs_destroy_all_wins()
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 = zeroany;
512 curses_add_nhmenu_item(wid, NO_GLYPH, &Id, 0, 0, attr, text, FALSE);
513 } else {
514 waddstr(win, text);
515 wnoutrefresh(win);
516 }
517 }
518
519
520 /* Clear the contents of a window via the given NetHack winid */
521
522 void
curses_clear_nhwin(winid wid)523 curses_clear_nhwin(winid wid)
524 {
525 WINDOW *win = curses_get_nhwin(wid);
526 boolean border = curses_window_has_border(wid);
527
528 if (wid == MAP_WIN) {
529 clearok(win, TRUE); /* Redraw entire screen when refreshed */
530 clear_map();
531 }
532
533 werase(win);
534
535 if (border) {
536 box(win, 0, 0);
537 }
538 }
539
540 /* Change colour of window border to alert player to something */
541 void
curses_alert_win_border(winid wid,boolean onoff)542 curses_alert_win_border(winid wid, boolean onoff)
543 {
544 WINDOW *win = curses_get_nhwin(wid);
545
546 if (!win || !curses_window_has_border(wid))
547 return;
548 if (onoff)
549 curses_toggle_color_attr(win, ALERT_BORDER_COLOR, NONE, ON);
550 box(win, 0, 0);
551 if (onoff)
552 curses_toggle_color_attr(win, ALERT_BORDER_COLOR, NONE, OFF);
553 wnoutrefresh(win);
554 }
555
556
557 void
curses_alert_main_borders(boolean onoff)558 curses_alert_main_borders(boolean onoff)
559 {
560 curses_alert_win_border(MAP_WIN, onoff);
561 curses_alert_win_border(MESSAGE_WIN, onoff);
562 curses_alert_win_border(STATUS_WIN, onoff);
563 curses_alert_win_border(INV_WIN, onoff);
564 }
565
566 /* Return true if given wid is a main NetHack window */
567
568 static boolean
is_main_window(winid wid)569 is_main_window(winid wid)
570 {
571 if (wid == MESSAGE_WIN || wid == MAP_WIN
572 || wid == STATUS_WIN || wid == INV_WIN)
573 return TRUE;
574
575 return FALSE;
576 }
577
578
579 /* Unconditionally write a single character to a window at the given
580 coordinates without a refresh. Currently only used for the map. */
581
582 static void
write_char(WINDOW * win,int x,int y,nethack_char nch)583 write_char(WINDOW * win, int x, int y, nethack_char nch)
584 {
585 curses_toggle_color_attr(win, nch.color, nch.attr, ON);
586 #ifdef PDCURSES
587 mvwaddrawch(win, y, x, nch.ch);
588 #else
589 mvwaddch(win, y, x, nch.ch);
590 #endif
591 curses_toggle_color_attr(win, nch.color, nch.attr, OFF);
592 }
593
594
595 /* Draw the entire visible map onto the screen given the visible map
596 boundaries */
597
598 void
curses_draw_map(int sx,int sy,int ex,int ey)599 curses_draw_map(int sx, int sy, int ex, int ey)
600 {
601 int curx, cury;
602 int bspace = 0;
603
604 #ifdef MAP_SCROLLBARS
605 int sbsx, sbsy, sbex, sbey, count;
606 nethack_char hsb_back, hsb_bar, vsb_back, vsb_bar;
607 #endif
608
609 if (curses_window_has_border(MAP_WIN)) {
610 bspace++;
611 }
612 #ifdef MAP_SCROLLBARS
613 hsb_back.ch = '-';
614 hsb_back.color = SCROLLBAR_BACK_COLOR;
615 hsb_back.attr = A_NORMAL;
616 hsb_bar.ch = '*';
617 hsb_bar.color = SCROLLBAR_COLOR;
618 hsb_bar.attr = A_NORMAL;
619 vsb_back.ch = '|';
620 vsb_back.color = SCROLLBAR_BACK_COLOR;
621 vsb_back.attr = A_NORMAL;
622 vsb_bar.ch = '*';
623 vsb_bar.color = SCROLLBAR_COLOR;
624 vsb_bar.attr = A_NORMAL;
625
626 /* Horizontal scrollbar */
627 if (sx > 0 || ex < (COLNO - 1)) {
628 sbsx = (int) (((long) sx * (long) (ex - sx + 1)) / (long) COLNO);
629 sbex = (int) (((long) ex * (long) (ex - sx + 1)) / (long) COLNO);
630
631 if (sx > 0 && sbsx == 0)
632 ++sbsx;
633 if (ex < ROWNO - 1 && sbex == ROWNO - 1)
634 --sbex;
635
636 for (count = 0; count < sbsx; count++) {
637 write_char(mapwin, count + bspace, ey - sy + 1 + bspace, hsb_back);
638 }
639
640 for (count = sbsx; count <= sbex; count++) {
641 write_char(mapwin, count + bspace, ey - sy + 1 + bspace, hsb_bar);
642 }
643
644 for (count = sbex + 1; count <= (ex - sx); count++) {
645 write_char(mapwin, count + bspace, ey - sy + 1 + bspace, hsb_back);
646 }
647 }
648
649 /* Vertical scrollbar */
650 if (sy > 0 || ey < (ROWNO - 1)) {
651 sbsy = (int) (((long) sy * (long) (ey - sy + 1)) / (long) ROWNO);
652 sbey = (int) (((long) ey * (long) (ey - sy + 1)) / (long) ROWNO);
653
654 if (sy > 0 && sbsy == 0)
655 ++sbsy;
656 if (ey < ROWNO - 1 && sbey == ROWNO - 1)
657 --sbey;
658
659 for (count = 0; count < sbsy; count++) {
660 write_char(mapwin, ex - sx + 1 + bspace, count + bspace, vsb_back);
661 }
662
663 for (count = sbsy; count <= sbey; count++) {
664 write_char(mapwin, ex - sx + 1 + bspace, count + bspace, vsb_bar);
665 }
666
667 for (count = sbey + 1; count <= (ey - sy); count++) {
668 write_char(mapwin, ex - sx + 1 + bspace, count + bspace, vsb_back);
669 }
670 }
671 #endif /* MAP_SCROLLBARS */
672
673 for (curx = sx; curx <= ex; curx++) {
674 for (cury = sy; cury <= ey; cury++) {
675 write_char(mapwin, curx - sx + bspace, cury - sy + bspace,
676 map[cury][curx]);
677 }
678 }
679 }
680
681
682 /* Init map array to blanks */
683
684 static void
clear_map()685 clear_map()
686 {
687 int x, y;
688
689 for (x = 0; x < COLNO; x++) {
690 for (y = 0; y < ROWNO; y++) {
691 map[y][x].ch = ' ';
692 map[y][x].color = NO_COLOR;
693 map[y][x].attr = A_NORMAL;
694 }
695 }
696 }
697
698
699 /* Determine visible boundaries of map, and determine if it needs to be
700 based on the location of the player. */
701
702 boolean
curses_map_borders(int * sx,int * sy,int * ex,int * ey,int ux,int uy)703 curses_map_borders(int *sx, int *sy, int *ex, int *ey, int ux, int uy)
704 {
705 static int width = 0;
706 static int height = 0;
707 static int osx = 0;
708 static int osy = 0;
709 static int oex = 0;
710 static int oey = 0;
711 static int oux = -1;
712 static int ouy = -1;
713
714 if ((oux == -1) || (ouy == -1)) {
715 oux = u.ux;
716 ouy = u.uy;
717 }
718
719 if (ux == -1) {
720 ux = oux;
721 } else {
722 oux = ux;
723 }
724
725 if (uy == -1) {
726 uy = ouy;
727 } else {
728 ouy = uy;
729 }
730
731 curses_get_window_size(MAP_WIN, &height, &width);
732
733 #ifdef MAP_SCROLLBARS
734 if (width < COLNO) {
735 height--; /* room for horizontal scrollbar */
736 }
737
738 if (height < ROWNO) {
739 width--; /* room for vertical scrollbar */
740
741 if (width == COLNO) {
742 height--;
743 }
744 }
745 #endif /* MAP_SCROLLBARS */
746
747 if (width >= COLNO) {
748 *sx = 0;
749 *ex = COLNO - 1;
750 } else {
751 *ex = (width / 2) + ux;
752 *sx = *ex - (width - 1);
753
754 if (*ex >= COLNO) {
755 *sx = COLNO - width;
756 *ex = COLNO - 1;
757 } else if (*sx < 0) {
758 *sx = 0;
759 *ex = width - 1;
760 }
761 }
762
763 if (height >= ROWNO) {
764 *sy = 0;
765 *ey = ROWNO - 1;
766 } else {
767 *ey = (height / 2) + uy;
768 *sy = *ey - (height - 1);
769
770 if (*ey >= ROWNO) {
771 *sy = ROWNO - height;
772 *ey = ROWNO - 1;
773 } else if (*sy < 0) {
774 *sy = 0;
775 *ey = height - 1;
776 }
777 }
778
779 if ((*sx != osx) || (*sy != osy) || (*ex != oex) || (*ey != oey) ||
780 map_clipped) {
781 osx = *sx;
782 osy = *sy;
783 oex = *ex;
784 oey = *ey;
785 return TRUE;
786 }
787
788 return FALSE;
789 }
790