OSDN Git Service

upgrade to 3.6.2
[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         /* fall through to centre */
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     while (nhwids)
356         curses_del_wid(nhwids->nhwid);
357 }
358
359 /* Print a single character in the given window at the given coordinates */
360
361 void
362 curses_putch(winid wid, int x, int y, int ch, int color, int attr)
363 {
364     int sx, sy, ex, ey;
365     boolean border = curses_window_has_border(wid);
366     nethack_char nch;
367     static boolean map_initted = FALSE;
368 /*
369     if (wid == STATUS_WIN) {
370         curses_update_stats();
371     }
372 */
373     if (wid != MAP_WIN) {
374         return;
375     }
376
377     if (!map_initted) {
378         clear_map();
379         map_initted = TRUE;
380     }
381
382     map[y][x].ch = ch;
383     map[y][x].color = color;
384     map[y][x].attr = attr;
385     nch = map[y][x];
386
387     (void) curses_map_borders(&sx, &sy, &ex, &ey, -1, -1);
388
389     if ((x >= sx) && (x <= ex) && (y >= sy) && (y <= ey)) {
390         if (border) {
391             x++;
392             y++;
393         }
394
395         write_char(mapwin, x - sx, y - sy, nch);
396     }
397     /* refresh after every character?
398      * Fair go, mate! Some of us are playing from Australia! */
399     /* wrefresh(mapwin); */
400 }
401
402
403 /* Get x, y coordinates of curses window on the physical terminal window */
404
405 void
406 curses_get_window_xy(winid wid, int *x, int *y)
407 {
408     if (!is_main_window(wid)) {
409         impossible(
410               "curses_get_window_xy: wid %d out of range. Not a main window.",
411                    wid);
412         *x = 0;
413         *y = 0;
414         return;
415     }
416
417     *x = nhwins[wid].x;
418     *y = nhwins[wid].y;
419 }
420
421
422 /* Get usable width and height curses window on the physical terminal window */
423
424 void
425 curses_get_window_size(winid wid, int *height, int *width)
426 {
427     *height = nhwins[wid].height;
428     *width = nhwins[wid].width;
429 }
430
431
432 /* Determine if given window has a visible border */
433
434 boolean
435 curses_window_has_border(winid wid)
436 {
437     return nhwins[wid].border;
438 }
439
440
441 /* Determine if window for given winid exists */
442
443 boolean
444 curses_window_exists(winid wid)
445 {
446     nethack_wid *widptr;
447
448     for (widptr = nhwids; widptr; widptr = widptr->next_wid)
449         if (widptr->nhwid == wid)
450             return TRUE;
451
452     return FALSE;
453 }
454
455
456 /* Return the orientation of the specified window */
457
458 int
459 curses_get_window_orientation(winid wid)
460 {
461     if (!is_main_window(wid)) {
462         impossible(
463      "curses_get_window_orientation: wid %d out of range. Not a main window.",
464                    wid);
465         return CENTER;
466     }
467
468     return nhwins[wid].orientation;
469 }
470
471
472 /* Output a line of text to specified NetHack window with given coordinates
473    and text attributes */
474
475 void
476 curses_puts(winid wid, int attr, const char *text)
477 {
478     anything Id;
479     WINDOW *win = NULL;
480
481     if (is_main_window(wid)) {
482         win = curses_get_nhwin(wid);
483     }
484
485     if (wid == MESSAGE_WIN) {
486         curses_message_win_puts(text, FALSE);
487         return;
488     }
489
490 #if 0
491     if (wid == STATUS_WIN) {
492         curses_update_stats();     /* We will do the write ourselves */
493         return;
494     }
495 #endif
496
497     if (curses_is_menu(wid) || curses_is_text(wid)) {
498         if (!curses_menu_exists(wid)) {
499             impossible(
500                      "curses_puts: Attempted write to nonexistant window %d!",
501                        wid);
502             return;
503         }
504         Id = zeroany;
505         curses_add_nhmenu_item(wid, NO_GLYPH, &Id, 0, 0, attr, text, FALSE);
506     } else {
507         waddstr(win, text);
508         wnoutrefresh(win);
509     }
510 }
511
512
513 /* Clear the contents of a window via the given NetHack winid */
514
515 void
516 curses_clear_nhwin(winid wid)
517 {
518     WINDOW *win = curses_get_nhwin(wid);
519     boolean border = curses_window_has_border(wid);
520
521     if (wid == MAP_WIN) {
522         clearok(win, TRUE);     /* Redraw entire screen when refreshed */
523         clear_map();
524     }
525
526     werase(win);
527
528     if (border) {
529         box(win, 0, 0);
530     }
531 }
532
533 /* Change colour of window border to alert player to something */
534 void
535 curses_alert_win_border(winid wid, boolean onoff)
536 {
537     WINDOW *win = curses_get_nhwin(wid);
538
539     if (!win || !curses_window_has_border(wid))
540         return;
541     if (onoff)
542         curses_toggle_color_attr(win, ALERT_BORDER_COLOR, NONE, ON);
543     box(win, 0, 0);
544     if (onoff)
545         curses_toggle_color_attr(win, ALERT_BORDER_COLOR, NONE, OFF);
546     wnoutrefresh(win);
547 }
548
549
550 void
551 curses_alert_main_borders(boolean onoff)
552 {
553     curses_alert_win_border(MAP_WIN, onoff);
554     curses_alert_win_border(MESSAGE_WIN, onoff);
555     curses_alert_win_border(STATUS_WIN, onoff);
556     curses_alert_win_border(INV_WIN, onoff);
557 }
558
559 /* Return true if given wid is a main NetHack window */
560
561 static boolean
562 is_main_window(winid wid)
563 {
564     if (wid == MESSAGE_WIN || wid == MAP_WIN
565         || wid == STATUS_WIN || wid == INV_WIN)
566         return TRUE;
567
568     return FALSE;
569 }
570
571
572 /* Unconditionally write a single character to a window at the given
573 coordinates without a refresh.  Currently only used for the map. */
574
575 static void
576 write_char(WINDOW * win, int x, int y, nethack_char nch)
577 {
578     curses_toggle_color_attr(win, nch.color, nch.attr, ON);
579 #ifdef PDCURSES
580     mvwaddrawch(win, y, x, nch.ch);
581 #else
582     mvwaddch(win, y, x, nch.ch);
583 #endif
584     curses_toggle_color_attr(win, nch.color, nch.attr, OFF);
585 }
586
587
588 /* Draw the entire visible map onto the screen given the visible map
589 boundaries */
590
591 void
592 curses_draw_map(int sx, int sy, int ex, int ey)
593 {
594     int curx, cury;
595     int bspace = 0;
596
597 #ifdef MAP_SCROLLBARS
598     int sbsx, sbsy, sbex, sbey, count;
599     nethack_char hsb_back, hsb_bar, vsb_back, vsb_bar;
600 #endif
601
602     if (curses_window_has_border(MAP_WIN)) {
603         bspace++;
604     }
605 #ifdef MAP_SCROLLBARS
606     hsb_back.ch = '-';
607     hsb_back.color = SCROLLBAR_BACK_COLOR;
608     hsb_back.attr = A_NORMAL;
609     hsb_bar.ch = '*';
610     hsb_bar.color = SCROLLBAR_COLOR;
611     hsb_bar.attr = A_NORMAL;
612     vsb_back.ch = '|';
613     vsb_back.color = SCROLLBAR_BACK_COLOR;
614     vsb_back.attr = A_NORMAL;
615     vsb_bar.ch = '*';
616     vsb_bar.color = SCROLLBAR_COLOR;
617     vsb_bar.attr = A_NORMAL;
618
619     /* Horizontal scrollbar */
620     if ((sx > 0) || (ex < (COLNO - 1))) {
621         sbsx = (sx * ((long) (ex - sx + 1) / COLNO));
622         sbex = (ex * ((long) (ex - sx + 1) / COLNO));
623
624         for (count = 0; count < sbsx; count++) {
625             write_char(mapwin, count + bspace, ey - sy + 1 + bspace, hsb_back);
626         }
627
628         for (count = sbsx; count <= sbex; count++) {
629             write_char(mapwin, count + bspace, ey - sy + 1 + bspace, hsb_bar);
630         }
631
632         for (count = sbex + 1; count <= (ex - sx); count++) {
633             write_char(mapwin, count + bspace, ey - sy + 1 + bspace, hsb_back);
634         }
635     }
636
637     /* Vertical scrollbar */
638     if ((sy > 0) || (ey < (ROWNO - 1))) {
639         sbsy = (sy * ((long) (ey - sy + 1) / ROWNO));
640         sbey = (ey * ((long) (ey - sy + 1) / ROWNO));
641
642         for (count = 0; count < sbsy; count++) {
643             write_char(mapwin, ex - sx + 1 + bspace, count + bspace, vsb_back);
644         }
645
646         for (count = sbsy; count <= sbey; count++) {
647             write_char(mapwin, ex - sx + 1 + bspace, count + bspace, vsb_bar);
648         }
649
650         for (count = sbey + 1; count <= (ey - sy); count++) {
651             write_char(mapwin, ex - sx + 1 + bspace, count + bspace, vsb_back);
652         }
653     }
654 #endif /* MAP_SCROLLBARS */
655
656     for (curx = sx; curx <= ex; curx++) {
657         for (cury = sy; cury <= ey; cury++) {
658             write_char(mapwin, curx - sx + bspace, cury - sy + bspace,
659                        map[cury][curx]);
660         }
661     }
662 }
663
664
665 /* Init map array to blanks */
666
667 static void
668 clear_map()
669 {
670     int x, y;
671
672     for (x = 0; x < COLNO; x++) {
673         for (y = 0; y < ROWNO; y++) {
674             map[y][x].ch = ' ';
675             map[y][x].color = NO_COLOR;
676             map[y][x].attr = A_NORMAL;
677         }
678     }
679 }
680
681
682 /* Determine visible boundaries of map, and determine if it needs to be
683 based on the location of the player. */
684
685 boolean
686 curses_map_borders(int *sx, int *sy, int *ex, int *ey, int ux, int uy)
687 {
688     static int width = 0;
689     static int height = 0;
690     static int osx = 0;
691     static int osy = 0;
692     static int oex = 0;
693     static int oey = 0;
694     static int oux = -1;
695     static int ouy = -1;
696
697     if ((oux == -1) || (ouy == -1)) {
698         oux = u.ux;
699         ouy = u.uy;
700     }
701
702     if (ux == -1) {
703         ux = oux;
704     } else {
705         oux = ux;
706     }
707
708     if (uy == -1) {
709         uy = ouy;
710     } else {
711         ouy = uy;
712     }
713
714     curses_get_window_size(MAP_WIN, &height, &width);
715
716 #ifdef MAP_SCROLLBARS
717     if (width < COLNO) {
718         height--;               /* room for horizontal scrollbar */
719     }
720
721     if (height < ROWNO) {
722         width--;                /* room for vertical scrollbar */
723
724         if (width == COLNO) {
725             height--;
726         }
727     }
728 #endif /* MAP_SCROLLBARS */
729
730     if (width >= COLNO) {
731         *sx = 0;
732         *ex = COLNO - 1;
733     } else {
734         *ex = (width / 2) + ux;
735         *sx = *ex - (width - 1);
736
737         if (*ex >= COLNO) {
738             *sx = COLNO - width;
739             *ex = COLNO - 1;
740         } else if (*sx < 0) {
741             *sx = 0;
742             *ex = width - 1;
743         }
744     }
745
746     if (height >= ROWNO) {
747         *sy = 0;
748         *ey = ROWNO - 1;
749     } else {
750         *ey = (height / 2) + uy;
751         *sy = *ey - (height - 1);
752
753         if (*ey >= ROWNO) {
754             *sy = ROWNO - height;
755             *ey = ROWNO - 1;
756         } else if (*sy < 0) {
757             *sy = 0;
758             *ey = height - 1;
759         }
760     }
761
762     if ((*sx != osx) || (*sy != osy) || (*ex != oex) || (*ey != oey) ||
763         map_clipped) {
764         osx = *sx;
765         osy = *sy;
766         oex = *ex;
767         oey = *ey;
768         return TRUE;
769     }
770
771     return FALSE;
772 }