1 /********************************* tui.c ************************************/
2 /*
3 * 'textual user interface'
4 *
5 * $Id: tui.c,v 1.34 2008/07/14 12:35:23 wmcbrine Exp $
6 *
7 * Author : P.J. Kunst <[email protected]>
8 * Date : 25-02-93
9 */
10
11 #include <ctype.h>
12 #include <curses.h>
13 #include <stdio.h>
14 #include <stdlib.h>
15 #include <string.h>
16 #include <time.h>
17 #include "tui.h"
18
19 void statusmsg(char *);
20 int waitforkey(void);
21 void rmerror(void);
22
23 #if defined(__unix) && !defined(__DJGPP__)
24 #include <unistd.h>
25 #endif
26
27 #ifdef A_COLOR
28 # define TITLECOLOR 1 /* color pair indices */
29 # define MAINMENUCOLOR (2 | A_BOLD)
30 # define MAINMENUREVCOLOR (3 | A_BOLD | A_REVERSE)
31 # define SUBMENUCOLOR (4 | A_BOLD)
32 # define SUBMENUREVCOLOR (5 | A_BOLD | A_REVERSE)
33 # define BODYCOLOR 6
34 # define STATUSCOLOR (7 | A_BOLD)
35 # define INPUTBOXCOLOR 8
36 # define EDITBOXCOLOR (9 | A_BOLD | A_REVERSE)
37 #else
38 # define TITLECOLOR 0 /* color pair indices */
39 # define MAINMENUCOLOR (A_BOLD)
40 # define MAINMENUREVCOLOR (A_BOLD | A_REVERSE)
41 # define SUBMENUCOLOR (A_BOLD)
42 # define SUBMENUREVCOLOR (A_BOLD | A_REVERSE)
43 # define BODYCOLOR 0
44 # define STATUSCOLOR (A_BOLD)
45 # define INPUTBOXCOLOR 0
46 # define EDITBOXCOLOR (A_BOLD | A_REVERSE)
47 #endif
48
49 #define th 1 /* title window height */
50 #define mh 1 /* main menu height */
51 #define sh 2 /* status window height */
52 #define bh (LINES - th - mh - sh) /* body window height */
53 #define bw COLS /* body window width */
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
wordchar(void)64 static char wordchar(void)
65 {
66 return 0x17; /* ^W */
67 }
68 #endif
69
padstr(char * s,int length)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
prepad(char * s,int length)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
rmline(WINDOW * win,int nr)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
initcolor(void)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_CYAN);
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
setcolor(WINDOW * win,chtype color)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
colorbox(WINDOW * win,chtype color,int hasbox)136 static void colorbox(WINDOW *win, chtype color, int hasbox)
137 {
138 int maxy;
139 #ifndef PDCURSES
140 int 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
idle(void)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, " %.2d-%.2d-%.4d %.2d:%.2d:%.2d",
178 tp->tm_mday, tp->tm_mon + 1, tp->tm_year + 1900,
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
menudim(menu * mp,int * lines,int * columns)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
setmenupos(int y,int x)196 static void setmenupos(int y, int x)
197 {
198 nexty = y;
199 nextx = x;
200 }
201
getmenupos(int * y,int * x)202 static void getmenupos(int *y, int *x)
203 {
204 *y = nexty;
205 *x = nextx;
206 }
207
hotkey(const char * s)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
repaintmenu(WINDOW * wmenu,menu * mp)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
repaintmainmenu(int width,menu * mp)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
mainhelp(void)243 static 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
mainmenu(menu * mp)252 static 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
cleanup(void)347 static 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 /******************************* EXTERNAL **********************************/
362
clsbody(void)363 void clsbody(void)
364 {
365 werase(wbody);
366 wmove(wbody, 0, 0);
367 }
368
bodylen(void)369 int bodylen(void)
370 {
371 #ifdef PDCURSES
372 return getmaxy(wbody);
373 #else
374 int maxy, maxx;
375
376 getmaxyx(wbody, maxy, maxx);
377 return maxy;
378 #endif
379 }
380
bodywin(void)381 WINDOW *bodywin(void)
382 {
383 return wbody;
384 }
385
rmerror(void)386 void rmerror(void)
387 {
388 rmline(wstat, 0);
389 }
390
rmstatus(void)391 void rmstatus(void)
392 {
393 rmline(wstat, 1);
394 }
395
titlemsg(char * msg)396 void titlemsg(char *msg)
397 {
398 mvwaddstr(wtitl, 0, 2, padstr(msg, bw - 3));
399 wrefresh(wtitl);
400 }
401
bodymsg(char * msg)402 void bodymsg(char *msg)
403 {
404 waddstr(wbody, msg);
405 wrefresh(wbody);
406 }
407
errormsg(char * msg)408 void errormsg(char *msg)
409 {
410 beep();
411 mvwaddstr(wstat, 0, 2, padstr(msg, bw - 3));
412 wrefresh(wstat);
413 }
414
statusmsg(char * msg)415 void statusmsg(char *msg)
416 {
417 mvwaddstr(wstat, 1, 2, padstr(msg, bw - 3));
418 wrefresh(wstat);
419 }
420
keypressed(void)421 bool keypressed(void)
422 {
423 ch = wgetch(wbody);
424
425 return ch != ERR;
426 }
427
getkey(void)428 int getkey(void)
429 {
430 int c = ch;
431
432 ch = ERR;
433 #ifdef ALT_X
434 quit = (c == ALT_X); /* PC only ! */
435 #endif
436 return c;
437 }
438
waitforkey(void)439 int waitforkey(void)
440 {
441 do idle(); while (!keypressed());
442 return getkey();
443 }
444
DoExit(void)445 void DoExit(void) /* terminate program */
446 {
447 quit = TRUE;
448 }
449
domenu(menu * mp)450 void domenu(menu *mp)
451 {
452 int y, x, nitems, barlen, mheight, mw, old = -1, cur = 0, cur0;
453 bool stop = FALSE;
454 WINDOW *wmenu;
455
456 curs_set(0);
457 getmenupos(&y, &x);
458 menudim(mp, &nitems, &barlen);
459 mheight = nitems + 2;
460 mw = barlen + 2;
461 wmenu = newwin(mheight, mw, y, x);
462 colorbox(wmenu, SUBMENUCOLOR, 1);
463 repaintmenu(wmenu, mp);
464
465 key = ERR;
466
467 while (!stop && !quit)
468 {
469 if (cur != old)
470 {
471 if (old != -1)
472 mvwaddstr(wmenu, old + 1, 1,
473 prepad(padstr(mp[old].name, barlen - 1), 1));
474
475 setcolor(wmenu, SUBMENUREVCOLOR);
476 mvwaddstr(wmenu, cur + 1, 1,
477 prepad(padstr(mp[cur].name, barlen - 1), 1));
478
479 setcolor(wmenu, SUBMENUCOLOR);
480 statusmsg(mp[cur].desc);
481
482 old = cur;
483 wrefresh(wmenu);
484 }
485
486 switch (key = ((key != ERR) ? key : waitforkey()))
487 {
488 case '\n': /* menu item selected */
489 touchwin(wbody);
490 wrefresh(wbody);
491 setmenupos(y + 1, x + 1);
492 rmerror();
493
494 key = ERR;
495 curs_set(1);
496 (mp[cur].func)(); /* perform function */
497 curs_set(0);
498
499 repaintmenu(wmenu, mp);
500
501 old = -1;
502 break;
503
504 case KEY_UP:
505 cur = (cur + nitems - 1) % nitems;
506 key = ERR;
507 break;
508
509 case KEY_DOWN:
510 cur = (cur + 1) % nitems;
511 key = ERR;
512 break;
513
514 case KEY_ESC:
515 case KEY_LEFT:
516 case KEY_RIGHT:
517 if (key == KEY_ESC)
518 key = ERR; /* return to prev submenu */
519
520 stop = TRUE;
521 break;
522
523 default:
524 cur0 = cur;
525
526 do
527 {
528 cur = (cur + 1) % nitems;
529
530 } while ((cur != cur0) &&
531 (hotkey(mp[cur].name) != toupper((int)key)));
532
533 key = (hotkey(mp[cur].name) == toupper((int)key)) ? '\n' : ERR;
534 }
535
536 }
537
538 rmerror();
539 delwin(wmenu);
540 touchwin(wbody);
541 wrefresh(wbody);
542 }
543
startmenu(menu * mp,char * mtitle)544 void startmenu(menu *mp, char *mtitle)
545 {
546 initscr();
547 incurses = TRUE;
548 initcolor();
549
550 wtitl = subwin(stdscr, th, bw, 0, 0);
551 wmain = subwin(stdscr, mh, bw, th, 0);
552 wbody = subwin(stdscr, bh, bw, th + mh, 0);
553 wstat = subwin(stdscr, sh, bw, th + mh + bh, 0);
554
555 colorbox(wtitl, TITLECOLOR, 0);
556 colorbox(wmain, MAINMENUCOLOR, 0);
557 colorbox(wbody, BODYCOLOR, 0);
558 colorbox(wstat, STATUSCOLOR, 0);
559
560 if (mtitle)
561 titlemsg(mtitle);
562
563 cbreak(); /* direct input (no newline required)... */
564 noecho(); /* ... without echoing */
565 curs_set(0); /* hide cursor (if possible) */
566 nodelay(wbody, TRUE); /* don't wait for input... */
567 halfdelay(10); /* ...well, no more than a second, anyway */
568 keypad(wbody, TRUE); /* enable cursor keys */
569 scrollok(wbody, TRUE); /* enable scrolling in main window */
570
571 leaveok(stdscr, TRUE);
572 leaveok(wtitl, TRUE);
573 leaveok(wmain, TRUE);
574 leaveok(wstat, TRUE);
575
576 mainmenu(mp);
577
578 cleanup();
579 }
580
repainteditbox(WINDOW * win,int x,char * buf)581 static void repainteditbox(WINDOW *win, int x, char *buf)
582 {
583 #ifndef PDCURSES
584 int maxy;
585 #endif
586 int maxx;
587
588 #ifdef PDCURSES
589 maxx = getmaxx(win);
590 #else
591 getmaxyx(win, maxy, maxx);
592 #endif
593 werase(win);
594 mvwprintw(win, 0, 0, "%s", padstr(buf, maxx));
595 wmove(win, 0, x);
596 wrefresh(win);
597 }
598
599 /*
600
601 weditstr() - edit string
602
603 Description:
604 The initial value of 'str' with a maximum length of 'field' - 1,
605 which is supplied by the calling routine, is editted. The user's
606 erase (^H), kill (^U) and delete word (^W) chars are interpreted.
607 The PC insert or Tab keys toggle between insert and edit mode.
608 Escape aborts the edit session, leaving 'str' unchanged.
609 Enter, Up or Down Arrow are used to accept the changes to 'str'.
610 NOTE: editstr(), mveditstr(), and mvweditstr() are macros.
611
612 Return Value:
613 Returns the input terminating character on success (Escape,
614 Enter, Up or Down Arrow) and ERR on error.
615
616 Errors:
617 It is an error to call this function with a NULL window pointer.
618 The length of the initial 'str' must not exceed 'field' - 1.
619
620 */
621
weditstr(WINDOW * win,char * buf,int field)622 int weditstr(WINDOW *win, char *buf, int field)
623 {
624 char org[MAXSTRLEN], *tp, *bp = buf;
625 bool defdisp = TRUE, stop = FALSE, insert = FALSE;
626 int cury, curx, begy, begx, oldattr;
627 WINDOW *wedit;
628 int c = 0;
629
630 if ((field >= MAXSTRLEN) || (buf == NULL) ||
631 ((int)strlen(buf) > field - 1))
632 return ERR;
633
634 strcpy(org, buf); /* save original */
635
636 wrefresh(win);
637 getyx(win, cury, curx);
638 getbegyx(win, begy, begx);
639
640 wedit = subwin(win, 1, field, begy + cury, begx + curx);
641 oldattr = wedit->_attrs;
642 colorbox(wedit, EDITBOXCOLOR, 0);
643
644 keypad(wedit, TRUE);
645 curs_set(1);
646
647 while (!stop)
648 {
649 idle();
650 repainteditbox(wedit, bp - buf, buf);
651
652 switch (c = wgetch(wedit))
653 {
654 case ERR:
655 break;
656
657 case KEY_ESC:
658 strcpy(buf, org); /* restore original */
659 stop = TRUE;
660 break;
661
662 case '\n':
663 case KEY_UP:
664 case KEY_DOWN:
665 stop = TRUE;
666 break;
667
668 case KEY_LEFT:
669 if (bp > buf)
670 bp--;
671 break;
672
673 case KEY_RIGHT:
674 defdisp = FALSE;
675 if (bp - buf < (int)strlen(buf))
676 bp++;
677 break;
678
679 case '\t': /* TAB -- because insert
680 is broken on HPUX */
681 case KEY_IC: /* enter insert mode */
682 case KEY_EIC: /* exit insert mode */
683 defdisp = FALSE;
684 insert = !insert;
685
686 curs_set(insert ? 2 : 1);
687 break;
688
689 default:
690 if (c == erasechar()) /* backspace, ^H */
691 {
692 if (bp > buf)
693 {
694 memmove((void *)(bp - 1), (const void *)bp, strlen(bp) + 1);
695 bp--;
696 }
697 }
698 else if (c == killchar()) /* ^U */
699 {
700 bp = buf;
701 *bp = '\0';
702 }
703 else if (c == wordchar()) /* ^W */
704 {
705 tp = bp;
706
707 while ((bp > buf) && (*(bp - 1) == ' '))
708 bp--;
709 while ((bp > buf) && (*(bp - 1) != ' '))
710 bp--;
711
712 memmove((void *)bp, (const void *)tp, strlen(tp) + 1);
713 }
714 else if (isprint(c))
715 {
716 if (defdisp)
717 {
718 bp = buf;
719 *bp = '\0';
720 defdisp = FALSE;
721 }
722
723 if (insert)
724 {
725 if ((int)strlen(buf) < field - 1)
726 {
727 memmove((void *)(bp + 1), (const void *)bp,
728 strlen(bp) + 1);
729
730 *bp++ = c;
731 }
732 }
733 else if (bp - buf < field - 1)
734 {
735 /* append new string terminator */
736
737 if (!*bp)
738 bp[1] = '\0';
739
740 *bp++ = c;
741 }
742 }
743 }
744 }
745
746 curs_set(0);
747
748 wattrset(wedit, oldattr);
749 repainteditbox(wedit, bp - buf, buf);
750 delwin(wedit);
751
752 return c;
753 }
754
winputbox(WINDOW * win,int nlines,int ncols)755 WINDOW *winputbox(WINDOW *win, int nlines, int ncols)
756 {
757 WINDOW *winp;
758 int cury, curx, begy, begx;
759
760 getyx(win, cury, curx);
761 getbegyx(win, begy, begx);
762
763 winp = newwin(nlines, ncols, begy + cury, begx + curx);
764 colorbox(winp, INPUTBOXCOLOR, 1);
765
766 return winp;
767 }
768
getstrings(char * desc[],char * buf[],int field)769 int getstrings(char *desc[], char *buf[], int field)
770 {
771 WINDOW *winput;
772 int oldy, oldx, maxy, maxx, nlines, ncols, i, n, l, mmax = 0;
773 int c = 0;
774 bool stop = FALSE;
775
776 for (n = 0; desc[n]; n++)
777 if ((l = strlen(desc[n])) > mmax)
778 mmax = l;
779
780 nlines = n + 2; ncols = mmax + field + 4;
781 getyx(wbody, oldy, oldx);
782 getmaxyx(wbody, maxy, maxx);
783
784 winput = mvwinputbox(wbody, (maxy - nlines) / 2, (maxx - ncols) / 2,
785 nlines, ncols);
786
787 for (i = 0; i < n; i++)
788 mvwprintw(winput, i + 1, 2, "%s", desc[i]);
789
790 i = 0;
791
792 while (!stop)
793 {
794 switch (c = mvweditstr(winput, i+1, mmax+3, buf[i], field))
795 {
796 case KEY_ESC:
797 stop = TRUE;
798 break;
799
800 case KEY_UP:
801 i = (i + n - 1) % n;
802 break;
803
804 case '\n':
805 case '\t':
806 case KEY_DOWN:
807 if (++i == n)
808 stop = TRUE; /* all passed? */
809 }
810 }
811
812 delwin(winput);
813 touchwin(wbody);
814 wmove(wbody, oldy, oldx);
815 wrefresh(wbody);
816
817 return c;
818 }
819