add more test (3)
[cmore.git] / tui.c
1 /********************************* tui.c ************************************/
2 /*
3 * 'textual user interface'
4 *
5 * Author : P.J. Kunst <kunst@prl.philips.nl>
6 * Date : 1993-02-25
7 */
8
9 #include <ctype.h>
10 #include <curses.h>
11 #include <stdio.h>
12 #include <stdlib.h>
13 #include <string.h>
14 #include <time.h>
15 #include "tui.h"
16
17 void statusmsg(char *);
18 int waitforkey(void);
19 void rmerror(void);
20
21 #if defined(__unix) && !defined(__DJGPP__)
22 #include <unistd.h>
23 #endif
24
25 #ifdef A_COLOR
26 # define TITLECOLOR 1 /* color pair indices */
27 # define MAINMENUCOLOR (2 | A_BOLD)
28 # define MAINMENUREVCOLOR (3 | A_BOLD | A_REVERSE)
29 # define SUBMENUCOLOR (4 | A_BOLD)
30 # define SUBMENUREVCOLOR (5 | A_BOLD | A_REVERSE)
31 # define BODYCOLOR 6
32 # define STATUSCOLOR (7 | A_BOLD)
33 # define INPUTBOXCOLOR 8
34 # define EDITBOXCOLOR (9 | A_BOLD | A_REVERSE)
35 #else
36 # define TITLECOLOR 0 /* color pair indices */
37 # define MAINMENUCOLOR (A_BOLD)
38 # define MAINMENUREVCOLOR (A_BOLD | A_REVERSE)
39 # define SUBMENUCOLOR (A_BOLD)
40 # define SUBMENUREVCOLOR (A_BOLD | A_REVERSE)
41 # define BODYCOLOR 0
42 # define STATUSCOLOR (A_BOLD)
43 # define INPUTBOXCOLOR 0
44 # define EDITBOXCOLOR (A_BOLD | A_REVERSE)
45 #endif
46
47
48 #define th 1 /* title window height */
49 #define mh 1 /* main menu height */
50 #define sh 2 /* status window height */
51 #define bh (LINES - th - mh - sh) /* body window height */
52 #define bw COLS /* body window width */
53
54
55 /******************************* STATIC ************************************/
56
57 static WINDOW *wtitl, *wmain, *wbody, *wstat; /* title, menu, body, status win*/
58 static int nexty, nextx;
59 static int key = ERR, ch = ERR;
60 static bool quit = FALSE;
61 static bool incurses = FALSE;
62
63 #ifndef PDCURSES
64 static char wordchar(void)
65 {
66 return 0x17; /* ^W */
67 }
68 #endif
69
70 static char *padstr(char *s, int length)
71 {
72 static char buf[MAXSTRLEN];
73 char fmt[10];
74
75 sprintf(fmt, (int)strlen(s) > length ? "%%.%ds" : "%%-%ds", length);
76 sprintf(buf, fmt, s);
77
78 return buf;
79 }
80
81 static char *prepad(char *s, int length)
82 {
83 int i;
84 char *p = s;
85
86 if (length > 0)
87 {
88 memmove((void *)(s + length), (const void *)s, strlen(s) + 1);
89
90 for (i = 0; i < length; i++)
91 *p++ = ' ';
92 }
93
94 return s;
95 }
96
97 static void rmline(WINDOW *win, int nr) /* keeps box lines intact */
98 {
99 mvwaddstr(win, nr, 1, padstr(" ", bw - 2));
100 wrefresh(win);
101 }
102
103 static void initcolor(void)
104 {
105 #ifdef A_COLOR
106 if (has_colors())
107 start_color();
108
109 /* foreground, background */
110
111 init_pair(TITLECOLOR & ~A_ATTR, COLOR_BLACK, COLOR_BLUE);
112 init_pair(MAINMENUCOLOR & ~A_ATTR, COLOR_WHITE, COLOR_CYAN);
113 init_pair(MAINMENUREVCOLOR & ~A_ATTR, COLOR_WHITE, COLOR_BLACK);
114 init_pair(SUBMENUCOLOR & ~A_ATTR, COLOR_WHITE, COLOR_CYAN);
115 init_pair(SUBMENUREVCOLOR & ~A_ATTR, COLOR_WHITE, COLOR_BLACK);
116 init_pair(BODYCOLOR & ~A_ATTR, COLOR_WHITE, COLOR_BLUE);
117 init_pair(STATUSCOLOR & ~A_ATTR, COLOR_WHITE, COLOR_CYAN);
118 init_pair(INPUTBOXCOLOR & ~A_ATTR, COLOR_BLACK, COLOR_CYAN);
119 init_pair(EDITBOXCOLOR & ~A_ATTR, COLOR_WHITE, COLOR_BLACK);
120 #endif
121 }
122
123 static void setcolor(WINDOW *win, chtype color)
124 {
125 chtype attr = color & A_ATTR; /* extract Bold, Reverse, Blink bits */
126
127 #ifdef A_COLOR
128 attr &= ~A_REVERSE; /* ignore reverse, use colors instead! */
129 wattrset(win, COLOR_PAIR(color & A_CHARTEXT) | attr);
130 #else
131 attr &= ~A_BOLD; /* ignore bold, gives messy display on HP-UX */
132 wattrset(win, attr);
133 #endif
134 }
135
136 static void colorbox(WINDOW *win, chtype color, int hasbox)
137 {
138 int maxy;
139 #ifndef PDCURSES
140 int __attribute__((unused)) maxx;
141 #endif
142 chtype attr = color & A_ATTR; /* extract Bold, Reverse, Blink bits */
143
144 setcolor(win, color);
145
146 #ifdef A_COLOR
147 if (has_colors())
148 wbkgd(win, COLOR_PAIR(color & A_CHARTEXT) | (attr & ~A_REVERSE));
149 else
150 #endif
151 wbkgd(win, attr);
152
153 werase(win);
154
155 #ifdef PDCURSES
156 maxy = getmaxy(win);
157 #else
158 getmaxyx(win, maxy, maxx);
159 #endif
160 if (hasbox && (maxy > 2))
161 box(win, 0, 0);
162
163 touchwin(win);
164 wrefresh(win);
165 }
166
167 static void idle(void)
168 {
169 char buf[MAXSTRLEN];
170 time_t t;
171 struct tm *tp;
172
173 if (time (&t) == -1)
174 return; /* time not available */
175
176 tp = localtime(&t);
177 sprintf(buf, " %.4d-%.2d-%.2d %.2d:%.2d:%.2d",
178 tp->tm_year + 1900, tp->tm_mon + 1, tp->tm_mday,
179 tp->tm_hour, tp->tm_min, tp->tm_sec);
180
181 mvwaddstr(wtitl, 0, bw - strlen(buf) - 2, buf);
182 wrefresh(wtitl);
183 }
184
185 static void menudim(menu *mp, int *lines, int *columns)
186 {
187 int n, l, mmax = 0;
188
189 for (n = 0; mp->func; n++, mp++)
190 if ((l = strlen(mp->name)) > mmax) mmax = l;
191
192 *lines = n;
193 *columns = mmax + 2;
194 }
195
196 static void setmenupos(int y, int x)
197 {
198 nexty = y;
199 nextx = x;
200 }
201
202 static void getmenupos(int *y, int *x)
203 {
204 *y = nexty;
205 *x = nextx;
206 }
207
208 static int hotkey(const char *s)
209 {
210 int c0 = *s; /* if no upper case found, return first char */
211
212 for (; *s; s++)
213 if (isupper((unsigned char)*s))
214 break;
215
216 return *s ? *s : c0;
217 }
218
219 static void repaintmenu(WINDOW *wmenu, menu *mp)
220 {
221 int i;
222 menu *p = mp;
223
224 for (i = 0; p->func; i++, p++)
225 mvwaddstr(wmenu, i + 1, 2, p->name);
226
227 touchwin(wmenu);
228 wrefresh(wmenu);
229 }
230
231 static void repaintmainmenu(int width, menu *mp)
232 {
233 int i;
234 menu *p = mp;
235
236 for (i = 0; p->func; i++, p++)
237 mvwaddstr(wmain, 0, i * width, prepad(padstr(p->name, width - 1), 1));
238
239 touchwin(wmain);
240 wrefresh(wmain);
241 }
242
243 void mainhelp(void)
244 {
245 #ifdef ALT_X
246 statusmsg("Use arrow keys and Enter to select (Alt-X to quit)");
247 #else
248 statusmsg("Use arrow keys and Enter to select");
249 #endif
250 }
251
252 void mainmenu(menu *mp)
253 {
254 int nitems, barlen, old = -1, cur = 0, c, cur0;
255
256 menudim(mp, &nitems, &barlen);
257 repaintmainmenu(barlen, mp);
258
259 while (!quit)
260 {
261 if (cur != old)
262 {
263 if (old != -1)
264 {
265 mvwaddstr(wmain, 0, old * barlen,
266 prepad(padstr(mp[old].name, barlen - 1), 1));
267
268 statusmsg(mp[cur].desc);
269 }
270 else
271 mainhelp();
272
273 setcolor(wmain, MAINMENUREVCOLOR);
274
275 mvwaddstr(wmain, 0, cur * barlen,
276 prepad(padstr(mp[cur].name, barlen - 1), 1));
277
278 setcolor(wmain, MAINMENUCOLOR);
279 old = cur;
280 wrefresh(wmain);
281 }
282
283 switch (c = (key != ERR ? key : waitforkey()))
284 {
285 case KEY_DOWN:
286 case '\n': /* menu item selected */
287 touchwin(wbody);
288 wrefresh(wbody);
289 rmerror();
290 setmenupos(th + mh, cur * barlen);
291 curs_set(1);
292 (mp[cur].func)(); /* perform function */
293 curs_set(0);
294
295 switch (key)
296 {
297 case KEY_LEFT:
298 cur = (cur + nitems - 1) % nitems;
299 key = '\n';
300 break;
301
302 case KEY_RIGHT:
303 cur = (cur + 1) % nitems;
304 key = '\n';
305 break;
306
307 default:
308 key = ERR;
309 }
310
311 repaintmainmenu(barlen, mp);
312 old = -1;
313 break;
314
315 case KEY_LEFT:
316 cur = (cur + nitems - 1) % nitems;
317 break;
318
319 case KEY_RIGHT:
320 cur = (cur + 1) % nitems;
321 break;
322
323 case KEY_ESC:
324 mainhelp();
325 break;
326
327 default:
328 cur0 = cur;
329
330 do
331 {
332 cur = (cur + 1) % nitems;
333
334 } while ((cur != cur0) && (hotkey(mp[cur].name) != toupper(c)));
335
336 if (hotkey(mp[cur].name) == toupper(c))
337 key = '\n';
338 }
339
340 }
341
342 rmerror();
343 touchwin(wbody);
344 wrefresh(wbody);
345 }
346
347 void cleanup(void) /* cleanup curses settings */
348 {
349 if (incurses)
350 {
351 delwin(wtitl);
352 delwin(wmain);
353 delwin(wbody);
354 delwin(wstat);
355 curs_set(1);
356 endwin();
357 incurses = FALSE;
358 }
359 }
360
361
362 /******************************* EXTERNAL **********************************/
363
364 void clsbody(void)
365 {
366 werase(wbody);
367 wmove(wbody, 0, 0);
368 }
369
370 int bodylen(void)
371 {
372 #ifdef PDCURSES
373 return getmaxy(wbody);
374 #else
375 int maxy, __attribute__((unused)) maxx;
376
377 getmaxyx(wbody, maxy, maxx);
378 return maxy;
379 #endif
380 }
381
382 WINDOW *bodywin(void)
383 {
384 return wbody;
385 }
386
387 void rmerror(void)
388 {
389 rmline(wstat, 0);
390 }
391
392 void rmstatus(void)
393 {
394 rmline(wstat, 1);
395 }
396
397 void titlemsg(char *msg)
398 {
399 mvwaddstr(wtitl, 0, 2, padstr(msg, bw - 3));
400 wrefresh(wtitl);
401 }
402
403 void bodymsg(char *msg)
404 {
405 waddstr(wbody, msg);
406 wrefresh(wbody);
407 }
408
409 void errormsg(char *msg)
410 {
411 beep();
412 mvwaddstr(wstat, 0, 2, padstr(msg, bw - 3));
413 wrefresh(wstat);
414 }
415
416 void statusmsg(char *msg)
417 {
418 mvwaddstr(wstat, 1, 2, padstr(msg, bw - 3));
419 wrefresh(wstat);
420 }
421
422 bool keypressed(void)
423 {
424 ch = wgetch(wbody);
425
426 return ch != ERR;
427 }
428
429 int getkey(void)
430 {
431 int c = ch;
432
433 ch = ERR;
434 #ifdef ALT_X
435 quit = (c == ALT_X); /* PC only ! */
436 #endif
437 return c;
438 }
439
440 int waitforkey(void)
441 {
442 do idle(); while (!keypressed());
443 return getkey();
444 }
445
446 void DoExit(void) /* terminate program */
447 {
448 quit = TRUE;
449 }
450
451 void domenu(menu *mp)
452 {
453 int y, x, nitems, barlen, mheight, mw, old = -1, cur = 0, cur0;
454 bool stop = FALSE;
455 WINDOW *wmenu;
456
457 curs_set(0);
458 getmenupos(&y, &x);
459 menudim(mp, &nitems, &barlen);
460 mheight = nitems + 2;
461 mw = barlen + 2;
462 wmenu = newwin(mheight, mw, y, x);
463 colorbox(wmenu, SUBMENUCOLOR, 1);
464 repaintmenu(wmenu, mp);
465
466 key = ERR;
467
468 while (!stop && !quit)
469 {
470 if (cur != old)
471 {
472 if (old != -1)
473 mvwaddstr(wmenu, old + 1, 1,
474 prepad(padstr(mp[old].name, barlen - 1), 1));
475
476 setcolor(wmenu, SUBMENUREVCOLOR);
477 mvwaddstr(wmenu, cur + 1, 1,
478 prepad(padstr(mp[cur].name, barlen - 1), 1));
479
480 setcolor(wmenu, SUBMENUCOLOR);
481 statusmsg(mp[cur].desc);
482
483 old = cur;
484 wrefresh(wmenu);
485 }
486
487 switch (key = ((key != ERR) ? key : waitforkey()))
488 {
489 case '\n': /* menu item selected */
490 touchwin(wbody);
491 wrefresh(wbody);
492 setmenupos(y + 1, x + 1);
493 rmerror();
494
495 key = ERR;
496 curs_set(1);
497 (mp[cur].func)(); /* perform function */
498 curs_set(0);
499
500 repaintmenu(wmenu, mp);
501
502 old = -1;
503 break;
504
505 case KEY_UP:
506 cur = (cur + nitems - 1) % nitems;
507 key = ERR;
508 break;
509
510 case KEY_DOWN:
511 cur = (cur + 1) % nitems;
512 key = ERR;
513 break;
514
515 case KEY_ESC:
516 case KEY_LEFT:
517 case KEY_RIGHT:
518 if (key == KEY_ESC)
519 key = ERR; /* return to prev submenu */
520
521 stop = TRUE;
522 break;
523
524 default:
525 cur0 = cur;
526
527 do
528 {
529 cur = (cur + 1) % nitems;
530
531 } while ((cur != cur0) &&
532 (hotkey(mp[cur].name) != toupper((int)key)));
533
534 key = (hotkey(mp[cur].name) == toupper((int)key)) ? '\n' : ERR;
535 }
536
537 }
538
539 rmerror();
540 delwin(wmenu);
541 touchwin(wbody);
542 wrefresh(wbody);
543 }
544
545 void startmenu(menu *mp, char *mtitle, FUNC init)
546 {
547 initscr();
548 incurses = TRUE;
549 initcolor();
550
551 wtitl = subwin(stdscr, th, bw, 0, 0);
552 wmain = subwin(stdscr, mh, bw, th, 0);
553 wbody = subwin(stdscr, bh, bw, th + mh, 0);
554 wstat = subwin(stdscr, sh, bw, th + mh + bh, 0);
555
556 colorbox(wtitl, TITLECOLOR, 0);
557 colorbox(wmain, MAINMENUCOLOR, 0);
558 colorbox(wbody, BODYCOLOR, 0);
559 colorbox(wstat, STATUSCOLOR, 0);
560
561 if (mtitle)
562 titlemsg(mtitle);
563
564 cbreak(); /* direct input (no newline required)... */
565 noecho(); /* ... without echoing */
566 curs_set(0); /* hide cursor (if possible) */
567 nodelay(wbody, TRUE); /* don't wait for input... */
568 halfdelay(10); /* ...well, no more than a second, anyway */
569 keypad(wbody, TRUE); /* enable cursor keys */
570 scrollok(wbody, TRUE); /* enable scrolling in main window */
571
572 leaveok(stdscr, TRUE);
573 leaveok(wtitl, TRUE);
574 leaveok(wmain, TRUE);
575 leaveok(wstat, TRUE);
576
577 if (init) {
578 int nitems, barlen;
579 menudim(mp, &nitems, &barlen);
580 repaintmainmenu(barlen, mp);
581 (*init)();
582 }
583 mainmenu(mp);
584
585 cleanup();
586 }
587
588 static void repainteditbox(WINDOW *win, int x, char *buf)
589 {
590 #ifndef PDCURSES
591 int __attribute__((unused)) maxy;
592 #endif
593 int maxx;
594
595 #ifdef PDCURSES
596 maxx = getmaxx(win);
597 #else
598 getmaxyx(win, maxy, maxx);
599 #endif
600 werase(win);
601 mvwprintw(win, 0, 0, "%s", padstr(buf, maxx));
602 wmove(win, 0, x);
603 wrefresh(win);
604 }
605
606 /*
607
608 weditstr() - edit string
609
610 Description:
611 The initial value of 'str' with a maximum length of 'field' - 1,
612 which is supplied by the calling routine, is editted. The user's
613 erase (^H), kill (^U) and delete word (^W) chars are interpreted.
614 The PC insert or Tab keys toggle between insert and edit mode.
615 Escape aborts the edit session, leaving 'str' unchanged.
616 Enter, Up or Down Arrow are used to accept the changes to 'str'.
617 NOTE: editstr(), mveditstr(), and mvweditstr() are macros.
618
619 Return Value:
620 Returns the input terminating character on success (Escape,
621 Enter, Up or Down Arrow) and ERR on error.
622
623 Errors:
624 It is an error to call this function with a NULL window pointer.
625 The length of the initial 'str' must not exceed 'field' - 1.
626
627 */
628
629 int weditstr(WINDOW *win, char *buf, int field)
630 {
631 char org[MAXSTRLEN], *tp, *bp = buf;
632 bool defdisp = TRUE, stop = FALSE, insert = FALSE;
633 int cury, curx, begy, begx;
634 chtype oldattr;
635 WINDOW *wedit;
636 int c = 0;
637
638 if ((field >= MAXSTRLEN) || (buf == NULL) ||
639 ((int)strlen(buf) > field - 1))
640 return ERR;
641
642 strcpy(org, buf); /* save original */
643
644 wrefresh(win);
645 getyx(win, cury, curx);
646 getbegyx(win, begy, begx);
647
648 wedit = subwin(win, 1, field, begy + cury, begx + curx);
649 oldattr = getattrs(wedit);
650 colorbox(wedit, EDITBOXCOLOR, 0);
651
652 keypad(wedit, TRUE);
653 curs_set(1);
654
655 while (!stop)
656 {
657 idle();
658 repainteditbox(wedit, bp - buf, buf);
659
660 switch (c = wgetch(wedit))
661 {
662 case ERR:
663 break;
664
665 case KEY_ESC:
666 strcpy(buf, org); /* restore original */
667 stop = TRUE;
668 break;
669
670 case '\n':
671 case KEY_UP:
672 case KEY_DOWN:
673 stop = TRUE;
674 break;
675
676 case KEY_LEFT:
677 if (bp > buf)
678 bp--;
679 break;
680
681 case KEY_RIGHT:
682 defdisp = FALSE;
683 if (bp - buf < (int)strlen(buf))
684 bp++;
685 break;
686
687 case '\t': /* TAB -- because insert
688 is broken on HPUX */
689 case KEY_IC: /* enter insert mode */
690 case KEY_EIC: /* exit insert mode */
691 defdisp = FALSE;
692 insert = !insert;
693
694 curs_set(insert ? 2 : 1);
695 break;
696
697 default:
698 if (c == erasechar()) /* backspace, ^H */
699 {
700 if (bp > buf)
701 {
702 memmove((void *)(bp - 1), (const void *)bp, strlen(bp) + 1);
703 bp--;
704 }
705 }
706 else if (c == killchar()) /* ^U */
707 {
708 bp = buf;
709 *bp = '\0';
710 }
711 else if (c == wordchar()) /* ^W */
712 {
713 tp = bp;
714
715 while ((bp > buf) && (*(bp - 1) == ' '))
716 bp--;
717 while ((bp > buf) && (*(bp - 1) != ' '))
718 bp--;
719
720 memmove((void *)bp, (const void *)tp, strlen(tp) + 1);
721 }
722 else if (isprint(c))
723 {
724 if (defdisp)
725 {
726 bp = buf;
727 *bp = '\0';
728 defdisp = FALSE;
729 }
730
731 if (insert)
732 {
733 if ((int)strlen(buf) < field - 1)
734 {
735 memmove((void *)(bp + 1), (const void *)bp,
736 strlen(bp) + 1);
737
738 *bp++ = c;
739 }
740 }
741 else if (bp - buf < field - 1)
742 {
743 /* append new string terminator */
744
745 if (!*bp)
746 bp[1] = '\0';
747
748 *bp++ = c;
749 }
750 }
751 }
752 }
753
754 curs_set(0);
755
756 wattrset(wedit, oldattr);
757 repainteditbox(wedit, bp - buf, buf);
758 delwin(wedit);
759
760 return c;
761 }
762
763 WINDOW *winputbox(WINDOW *win, int nlines, int ncols)
764 {
765 WINDOW *winp;
766 int cury, curx, begy, begx;
767
768 getyx(win, cury, curx);
769 getbegyx(win, begy, begx);
770
771 winp = newwin(nlines, ncols, begy + cury, begx + curx);
772 colorbox(winp, INPUTBOXCOLOR, 1);
773
774 return winp;
775 }
776
777 int getstrings(char *desc[], char *buf[], int field)
778 {
779 WINDOW *winput;
780 int oldy, oldx, maxy, maxx, nlines, ncols, i, n, l, mmax = 0;
781 int c = 0;
782 bool stop = FALSE;
783
784 for (n = 0; desc[n]; n++)
785 if ((l = strlen(desc[n])) > mmax)
786 mmax = l;
787
788 nlines = n + 2; ncols = mmax + field + 4;
789 getyx(wbody, oldy, oldx);
790 getmaxyx(wbody, maxy, maxx);
791
792 winput = mvwinputbox(wbody, (maxy - nlines) / 2, (maxx - ncols) / 2,
793 nlines, ncols);
794
795 for (i = 0; i < n; i++)
796 mvwprintw(winput, i + 1, 2, "%s", desc[i]);
797
798 i = 0;
799
800 while (!stop)
801 {
802 switch (c = mvweditstr(winput, i+1, mmax+3, buf[i], field))
803 {
804 case KEY_ESC:
805 stop = TRUE;
806 break;
807
808 case KEY_UP:
809 i = (i + n - 1) % n;
810 break;
811
812 case '\n':
813 case '\t':
814 case KEY_DOWN:
815 if (++i == n)
816 stop = TRUE; /* all passed? */
817 }
818 }
819
820 delwin(winput);
821 touchwin(wbody);
822 wmove(wbody, oldy, oldx);
823 wrefresh(wbody);
824
825 return c;
826 }
827
828 /* vim: set ts=4 sw=4 et: */