OSDN Git Service

upgrade to 3.6.6
[jnethack/source.git] / win / curses / curswins.c
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 *
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
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
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 *
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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 }