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