1/*
2 * Program:	objects.c
3 * Author:	Marc van Kempen
4 * Desc:	Implementation of UI-objects:
5 *		- String input fields
6 *		- List selection
7 *		- Buttons
8 *
9 * Copyright (c) 1995, Marc van Kempen
10 *
11 * All rights reserved.
12 *
13 * This software may be used, modified, copied, distributed, and
14 * sold, in both source and binary form provided that the above
15 * copyright and these terms are retained, verbatim, as the first
16 * lines of this file.  Under no circumstances is the author
17 * responsible for the proper functioning of this software, nor does
18 * the author assume any responsibility for damages incurred with
19 * its use.
20 *
21 */
22
23#include <stdlib.h>
24#include <sys/param.h>
25#include <ncurses.h>
26#include <dialog.h>
27#include "dialog.priv.h"
28#include "ui_objects.h"
29
30#define ESC 27
31
32/***********************************************************************
33 *
34 * Obj routines
35 *
36 ***********************************************************************/
37
38void
39AddObj(ComposeObj **Obj, int objtype, void *obj)
40/*
41 * Desc: Add the object <obj> to the list of objects <Obj>
42 */
43{
44    if (*Obj == NULL) {
45	/* Create the root object */
46	*Obj = (ComposeObj *) malloc( sizeof(ComposeObj) );
47	if (!Obj) {
48	    printf("AddObj: Error malloc'ing ComposeObj\n");
49	    exit(-1);
50	}
51	(*Obj)->objtype = objtype;
52	(*Obj)->obj = obj;
53	(*Obj)->next = NULL;
54	(*Obj)->prev = NULL;
55    } else {
56	ComposeObj	*o = *Obj;
57
58	/* create the next object */
59	while (o->next) o = (ComposeObj *) o->next;
60	o->next = (struct ComposeObj *) malloc( sizeof(ComposeObj) );
61	if (!o->next) {
62	    printf("AddObj: Error malloc'ing o->next\n");
63	    exit(-1);
64	}
65	o->next->objtype = objtype;
66	o->next->obj = obj;
67	o->next->next = NULL;
68	o->next->prev = o;
69    }
70
71    return;
72} /* AddObj() */
73
74void
75FreeObj(ComposeObj *Obj)
76/*
77 * Desc: free the memory occupied by *Obj
78 */
79{
80    ComposeObj	*o = Obj;
81
82    o = Obj;
83    while (o) {
84	o = Obj->next;
85	free(Obj);
86	Obj = o;
87    }
88
89    return;
90} /* FreeObj() */
91
92
93int
94ReadObj(ComposeObj *Obj)
95/*
96 * Desc: navigate through the different objects calling their
97 *	 respective navigation routines as necessary
98 * Pre:  Obj != NULL
99 */
100{
101    ComposeObj		*o;
102    ComposeObj		*last;	 /* the last object in the list */
103    int			ret;	 /* the return value from the selection routine */
104
105    /* find the last object in the list */
106    last = Obj;
107    while (last->next) last = last->next;
108
109    ret = 0;
110    o = Obj;
111    while ((ret != SEL_BUTTON) && (ret != SEL_ESC)) {
112	switch(o->objtype) {
113	case STRINGOBJ:
114	    ret = SelectStringObj((StringObj *) o->obj);
115	    break;
116	case LISTOBJ:
117	    ret = SelectListObj((ListObj *) o->obj);
118	    break;
119	case BUTTONOBJ:
120	    ret = SelectButtonObj((ButtonObj *) o->obj);
121	    break;
122	}
123	switch(ret) {
124	case KEY_DOWN:
125	case SEL_CR:
126	case SEL_TAB:	/* move to the next object in the list */
127	    if (o->next != NULL) {
128		o = o->next;	/* next object */
129	    } else {
130		o = Obj;	/* beginning of the list */
131	    }
132	    break;
133
134	case KEY_UP:
135	case SEL_BACKTAB: /* move to the previous object in the list */
136	    if (o->prev != NULL) {
137		o = o->prev;	/* previous object */
138	    } else {
139		o = last;	/* end of the list */
140	    }
141	    break;
142
143	case KEY_F(1): /* display help_file */
144	case '?':
145	    display_helpfile();
146	    break;
147	}
148    }
149
150    return(ret);
151
152} /* ReadObj() */
153
154
155int
156PollObj(ComposeObj **Obj)
157{
158    ComposeObj		*last;	 /* the last object in the list */
159    ComposeObj		*first;  /* the first object in the list */
160    int			ret;	 /* the return value from the selection routine */
161
162    /* find the last object in the list */
163    last = *Obj;
164    while (last->next) last = last->next;
165
166    /* find the first object in the list */
167    first = *Obj;
168    while (first->prev) first = first->prev;
169
170    ret = 0;
171    switch((*Obj)->objtype) {
172    case STRINGOBJ:
173	ret = SelectStringObj((StringObj *) (*Obj)->obj);
174	break;
175    case LISTOBJ:
176	ret = SelectListObj((ListObj *) (*Obj)->obj);
177	break;
178    case BUTTONOBJ:
179	ret = SelectButtonObj((ButtonObj *) (*Obj)->obj);
180	break;
181    }
182    switch(ret) {
183    case KEY_DOWN:
184    case SEL_CR:
185    case SEL_TAB:		     /* move to the next object in the list */
186	if ((*Obj)->next != NULL) {
187	    *Obj = (*Obj)->next;     /* next object */
188	} else {
189	    *Obj = first;	     /* beginning of the list */
190	}
191	break;
192
193    case KEY_UP:
194    case SEL_BACKTAB: 		     /* move to the previous object in the list */
195	if ((*Obj)->prev != NULL) {
196	    *Obj = (*Obj)->prev;     /* previous object */
197	} else {
198	    *Obj = last;	     /* end of the list */
199	}
200	break;
201    }
202
203    return(ret);
204
205} /* PollObj() */
206
207
208void
209DelObj(ComposeObj *Obj)
210/*
211 * Desc: Free all objects
212 */
213{
214    ComposeObj	*o;
215
216    o = Obj;
217    while (Obj != NULL) {
218	switch(Obj->objtype) {
219	case STRINGOBJ:
220	    DelStringObj((StringObj *) Obj->obj);
221	    break;
222	case LISTOBJ:
223	    DelListObj((ListObj *) Obj->obj);
224	    break;
225	case BUTTONOBJ:
226	    DelButtonObj((ButtonObj *) Obj->obj);
227	    break;
228	}
229	Obj = Obj->next;
230    }
231
232    FreeObj(o);
233} /* DelObj() */
234
235/***********************************************************************
236 *
237 * StringObj routines
238 *
239 ***********************************************************************/
240
241static void
242outstr(WINDOW *win, char *str, int attrs)
243{
244    if (attrs & DITEM_NO_ECHO) {
245	char *cpy;
246	int n = strlen(str);
247
248	cpy = alloca(n + 1);
249	memset(cpy, '*', n);
250	cpy[n] = '\0';
251	waddstr(win, cpy);
252    }
253    else
254	waddstr(win, str);
255}
256
257void
258RefreshStringObj(StringObj *so)
259/*
260 * Desc: redraw the object
261 */
262{
263    char tmp[512];
264
265    wmove(so->win, so->y, so->x+1);
266    wattrset(so->win, dialog_attr);
267    waddstr(so->win, so->title);
268
269    draw_box(so->win, so->y+1, so->x, 3, so->w, dialog_attr, border_attr);
270    wattrset(so->win, item_attr);
271    wmove(so->win, so->y+2, so->x+1);
272    if (strlen(so->s) > so->w-2) {
273	strncpy(tmp, (char *) so->s + strlen(so->s) - so->w + 2, so->w - 1);
274	outstr(so->win, tmp, so->attr_mask);
275    } else {
276	outstr(so->win, so->s, so->attr_mask);
277    }
278
279    return;
280} /* RefreshStringObj() */
281
282StringObj *
283NewStringObj(WINDOW *win, char *title, char *s, int y, int x, int w, int len)
284/*
285 * Desc: Initialize a new stringobj and return a pointer to it.
286 *	 Draw the object on the screen at the specified coordinates
287 */
288{
289    StringObj	*so;
290
291    /* Initialize a new object */
292    so = (StringObj *) malloc( sizeof(StringObj) );
293    if (!so) {
294	printf("NewStringObj: Error malloc'ing StringObj\n");
295	exit(-1);
296    }
297    so->title = (char *) malloc( strlen(title) + 1);
298    if (!so->title) {
299	printf("NewStringObj: Error malloc'ing so->title\n");
300	exit(-1);
301    }
302    strcpy(so->title, title);
303    so->s = s;
304    strcpy(so->s, s);
305    so->x = x;
306    so->y = y;
307    so->w = w;
308    so->len = len;
309    so->win = win;
310    so->attr_mask = DialogInputAttrs;	/* Grossly use a global to avoid changing API */
311
312    /* Draw it on the screen */
313    RefreshStringObj(so);
314
315    return(so);
316} /* NewStringObj() */
317
318int
319SelectStringObj(StringObj *so)
320/*
321 * Desc: get input using the info in <so>
322 */
323{
324    int     	key;
325    char	tmp[so->len+1];
326
327    strcpy(tmp, so->s);
328    key = line_edit(so->win, so->y+2, so->x+1,
329		    so->len, so->w-2, inputbox_attr, TRUE, tmp, so->attr_mask);
330    if ((key == '\n') || (key == '\r') || (key == '\t') || key == (KEY_BTAB) ) {
331	strcpy(so->s, tmp);
332    }
333    RefreshStringObj(so);
334    if (key == ESC) {
335	return(SEL_ESC);
336    }
337    if (key == '\t') {
338	return(SEL_TAB);
339    }
340    if ( (key == KEY_BTAB) || (key == KEY_F(2)) ) {
341	return(SEL_BACKTAB);
342    }
343    if ((key == '\n') || (key == '\r')) {
344	return(SEL_CR);
345    }
346    return(key);
347} /* SelectStringObj() */
348
349
350void
351DelStringObj(StringObj *so)
352/*
353 * Desc: Free the space occupied by <so>
354 */
355{
356   free(so->title);
357   free(so);
358
359   return;
360}
361
362/***********************************************************************
363 *
364 * ListObj routines
365 *
366 ***********************************************************************/
367
368void
369DrawNames(ListObj *lo)
370/*
371 * Desc: Just refresh the names, not the surrounding box and title
372 */
373{
374    int 	i, j, h, x, y;
375    char	tmp[MAXPATHLEN];
376
377    x = lo->x + 1;
378    y = lo->y + 2;
379    h = lo->h - 2;
380    for (i=lo->scroll; i<lo->n && i<lo->scroll+h; i++) {
381	wmove(lo->win, y+i-lo->scroll, x);
382	if (lo->seld[i]) {
383	    wattrset(lo->win, A_BOLD);
384	} else {
385	    wattrset(lo->win, item_attr);
386	}
387	if (strlen(lo->name[i]) > lo->w-2) {
388	    strncpy(tmp, lo->name[i], lo->w-2);
389	    tmp[lo->w - 2] = 0;
390	    waddstr(lo->win, tmp);
391	} else {
392	    waddstr(lo->win, lo->name[i]);
393	    for (j=strlen(lo->name[i]); j<lo->w-2; j++) waddstr(lo->win, " ");
394	}
395    }
396    wattrset(lo->win, item_attr);
397    while (i<lo->scroll+h) {
398	wmove(lo->win, y+i-lo->scroll, x);
399	for (j=0; j<lo->w-2; j++) waddstr(lo->win, " ");
400	i++;
401    }
402
403    return;
404} /* DrawNames() */
405
406void
407RefreshListObj(ListObj *lo)
408/*
409 * Desc: redraw the list object
410 */
411{
412    char 	perc[7];
413
414    /* setup the box */
415    wmove(lo->win, lo->y, lo->x+1);
416    wattrset(lo->win, dialog_attr);
417    waddstr(lo->win, lo->title);
418    draw_box(lo->win, lo->y+1, lo->x, lo->h, lo->w, dialog_attr, border_attr);
419
420    /* draw the names */
421    DrawNames(lo);
422
423    /* Draw % indication */
424    sprintf(perc, "(%3d%%)", MIN(100, (int) (100 * (lo->sel+lo->h-2) / MAX(1, lo->n))));
425    wmove(lo->win, lo->y + lo->h, lo->x + lo->w - 8);
426    wattrset(lo->win, dialog_attr);
427    waddstr(lo->win, perc);
428
429
430    return;
431} /* RefreshListObj() */
432
433ListObj *
434NewListObj(WINDOW *win, char *title, char **list, char *listelt, int y, int x,
435	   int h, int w, int n)
436/*
437 * Desc: create a listobj, draw it on the screen and return a pointer to it.
438 */
439{
440    ListObj	*lo;
441    int		i;
442
443    /* Initialize a new object */
444    lo = (ListObj *) malloc( sizeof(ListObj) );
445    if (!lo) {
446	fprintf(stderr, "NewListObj: Error malloc'ing ListObj\n");
447	exit(-1);
448    }
449    lo->title = (char *) malloc( strlen(title) + 1);
450    if (!lo->title) {
451	fprintf(stderr, "NewListObj: Error malloc'ing lo->title\n");
452	exit(-1);
453    }
454    strcpy(lo->title, title);
455    lo->name = list;
456    if (n>0) {
457        lo->seld = (int *) malloc( n * sizeof(int) );
458        if (!lo->seld) {
459            fprintf(stderr, "NewListObj: Error malloc'ing lo->seld\n");
460            exit(-1);
461        }
462        for (i=0; i<n; i++) {
463            lo->seld[i] = FALSE;
464        }
465    } else {
466        lo->seld = NULL;
467    }
468    lo->y = y;
469    lo->x = x;
470    lo->w = w;
471    lo->h = h;
472    lo->n = n;
473    lo->scroll = 0;
474    lo->sel = 0;
475    lo->elt = listelt;
476    lo->win = win;
477
478    /* Draw the object on the screen */
479    RefreshListObj(lo);
480
481    return(lo);
482} /* NewListObj() */
483
484void
485UpdateListObj(ListObj *lo, char **list, int n)
486/*
487 * Desc: Update the list in the listobject with the provided list
488 * Pre:  lo->name "has been freed"
489 *	 "(A i: 0<=i<lo->n: "lo->name[i] has been freed")"
490 */
491{
492    int i;
493
494    if (lo->seld) {
495	free(lo->seld);
496    }
497
498    /* Rewrite the list in the object */
499    lo->name = list;
500    if (n>0) {
501	lo->seld = (int *) malloc( n * sizeof(int) );
502	if (!lo->seld) {
503	    fprintf(stderr, "UpdateListObj: Error malloc'ing lo->seld\n");
504	    exit(-1);
505	}
506	for (i=0; i<n; i++) {
507	    lo->seld[i] = FALSE;
508	}
509    } else {
510        lo->seld = NULL;
511    }
512    lo->n = n;
513    lo->scroll = 0;
514    lo->sel = 0;
515
516    /* Draw the object on the screen */
517    RefreshListObj(lo);
518
519    return;
520} /* UpdateListObj() */
521
522int
523SelectListObj(ListObj *lo)
524/*
525 * Desc: get a listname (or listnames), TAB to move on, or ESC ESC to exit
526 * Pre:	 lo->n >= 1
527 */
528{
529    int 	key, sel_x, sel_y, quit;
530    char	tmp[MAXPATHLEN];
531    char	perc[4];
532
533    sel_x = lo->x+1;
534    sel_y = lo->y + 2 + lo->sel - lo->scroll;
535
536    if (lo->n == 0) return(SEL_TAB);
537
538    keypad(lo->win, TRUE);
539
540    /* Draw current selection in inverse video */
541    wmove(lo->win, sel_y, sel_x);
542    wattrset(lo->win, item_selected_attr);
543    waddstr(lo->win, lo->name[lo->sel]);
544
545    key = wgetch(lo->win);
546    quit = FALSE;
547    while ((key != '\t') && (key != '\n') && (key != '\r')
548	   && (key != ESC) && (key != KEY_F(1)) && (key != '?') && !quit) {
549	/* first draw current item in normal video */
550	wmove(lo->win, sel_y, sel_x);
551	if (lo->seld[lo->sel]) {
552	    wattrset(lo->win, A_BOLD);
553	} else {
554	    wattrset(lo->win, item_attr);
555	}
556	if (strlen(lo->name[lo->sel]) > lo->w - 2) {
557	    strncpy(tmp, lo->name[lo->sel], lo->w - 2);
558	    tmp[lo->w - 2] = 0;
559	    waddstr(lo->win, tmp);
560	} else {
561	    waddstr(lo->win, lo->name[lo->sel]);
562	}
563
564	switch (key) {
565	case KEY_DOWN:
566	case ctrl('n'):
567	    if (sel_y < lo->y + lo->h-1) {
568		if (lo->sel < lo->n-1) {
569		    sel_y++;
570		    lo->sel++;
571		}
572	    } else {
573		if (lo->sel < lo->n-1) {
574		    lo->sel++;
575		    lo->scroll++;
576		    DrawNames(lo);
577		    wrefresh(lo->win);
578		}
579	    }
580	    break;
581	case KEY_UP:
582	case ctrl('p'):
583	    if (sel_y > lo->y+2) {
584		if (lo->sel > 0) {
585		    sel_y--;
586		    lo->sel--;
587		}
588	    } else {
589		if (lo->sel > 0) {
590		    lo->sel--;
591		    lo->scroll--;
592		    DrawNames(lo);
593		    wrefresh(lo->win);
594		}
595	    }
596	    break;
597	case KEY_HOME:
598	case ctrl('a'):
599	    lo->sel = 0;
600	    lo->scroll = 0;
601	    sel_y = lo->y + 2;
602	    DrawNames(lo);
603	    wrefresh(lo->win);
604	    break;
605	case KEY_END:
606	case ctrl('e'):
607	    if (lo->n < lo->h - 3) {
608		lo->sel = lo->n-1;
609		lo->scroll = 0;
610		sel_y = lo->y + 2 + lo->sel - lo->scroll;
611	    } else {
612		/* more than one page of list */
613		lo->sel = lo->n-1;
614		lo->scroll = lo->n-1 - (lo->h-3);
615		sel_y = lo->y + 2 + lo->sel - lo->scroll;
616		DrawNames(lo);
617		wrefresh(lo->win);
618	    }
619	    break;
620	case KEY_NPAGE:
621	case ctrl('f'):
622	    lo->sel += lo->h - 2;
623	    if (lo->sel >= lo->n) lo->sel = lo->n - 1;
624	    lo->scroll += lo->h - 2;
625	    if (lo->scroll >= lo->n - 1) lo->scroll = lo->n - 1;
626	    if (lo->scroll < 0) lo->scroll = 0;
627	    sel_y = lo->y + 2 + lo->sel - lo->scroll;
628	    DrawNames(lo);
629	    wrefresh(lo->win);
630	    break;
631	case KEY_PPAGE:
632	case ctrl('b'):
633	    lo->sel -= lo->h - 2;
634	    if (lo->sel < 0) lo->sel = 0;
635	    lo->scroll -= lo->h - 2;
636	    if (lo->scroll < 0) lo->scroll = 0;
637	    sel_y = lo->y + 2 + lo->sel - lo->scroll;
638	    DrawNames(lo);
639	    wrefresh(lo->win);
640	    break;
641	default:
642	    quit = TRUE;
643	    break;
644	}
645	/* Draw % indication */
646	sprintf(perc, "(%3d%%)", MIN(100, (int)
647				     (100 * (lo->sel+lo->h - 2) / MAX(1, lo->n))));
648	wmove(lo->win, lo->y + lo->h, lo->x + lo->w - 8);
649	wattrset(lo->win, dialog_attr);
650	waddstr(lo->win, perc);
651
652	/* draw current item in inverse */
653	wmove(lo->win, sel_y, sel_x);
654	wattrset(lo->win, item_selected_attr);
655	if (strlen(lo->name[lo->sel]) > lo->w - 2) {
656	    /* when printing in inverse video show the last characters in the */
657	    /* name that will fit in the window */
658	    strncpy(tmp,
659		    lo->name[lo->sel] + strlen(lo->name[lo->sel]) - (lo->w - 2),
660		    lo->w - 2);
661	    tmp[lo->w - 2] = 0;
662	    waddstr(lo->win, tmp);
663	} else {
664	    waddstr(lo->win, lo->name[lo->sel]);
665	}
666	if (!quit) key = wgetch(lo->win);
667    }
668
669    if (key == ESC) {
670	return(SEL_ESC);
671    }
672    if (key == '\t') {
673	return(SEL_TAB);
674    }
675    if ((key == KEY_BTAB) || (key == ctrl('b'))) {
676	return(SEL_BACKTAB);
677    }
678    if ((key == '\n') || (key == '\r')) {
679	strcpy(lo->elt, lo->name[lo->sel]);
680	return(SEL_CR);
681    }
682    return(key);
683} /* SelectListObj() */
684
685void
686DelListObj(ListObj *lo)
687/*
688 * Desc: Free the space occupied by the listobject
689 */
690{
691    free(lo->title);
692    if (lo->seld != NULL) free(lo->seld);
693    free(lo);
694
695    return;
696} /* DelListObj() */
697
698void
699MarkCurrentListObj(ListObj *lo)
700/*
701 * Desc: mark the current item for the selection list
702 */
703{
704    lo->seld[lo->sel] = !(lo->seld[lo->sel]);
705    DrawNames(lo);
706
707    return;
708} /* MarkCurrentListObj() */
709
710void
711MarkAllListObj(ListObj *lo)
712/*
713 * Desc: mark all items
714 */
715{
716    int i;
717
718    for (i=0; i<lo->n; i++) {
719        lo->seld[i] = TRUE;
720    }
721    DrawNames(lo);
722
723    return;
724} /* MarkAllListObj() */
725
726void
727UnMarkAllListObj(ListObj *lo)
728/*
729 * Desc: unmark all items
730 */
731{
732    int i;
733
734    for (i=0; i<lo->n; i++) {
735        lo->seld[i] = FALSE;
736    }
737    DrawNames(lo);
738
739    return;
740} /* UnMarkAllListObj() */
741
742
743/***********************************************************************
744 *
745 * ButtonObj routines
746 *
747 ***********************************************************************/
748
749
750void
751RefreshButtonObj(ButtonObj *bo)
752/*
753 * Desc: redraw the button
754 */
755{
756    draw_box(bo->win, bo->y, bo->x, 3, bo->w, dialog_attr, border_attr);
757    print_button(bo->win, bo->title, bo->y+1, bo->x+2, FALSE);
758
759    return;
760} /* RefreshButtonObj() */
761
762ButtonObj *
763NewButtonObj(WINDOW *win, char *title, int *pushed, int y, int x)
764/*
765 * Desc: Create a new button object
766 */
767{
768    ButtonObj	*bo;
769
770    bo = (ButtonObj *) malloc( sizeof(ButtonObj) );
771
772    bo->win = win;
773    bo->title = (char *) malloc( strlen(title) + 1);
774    strcpy(bo->title, title);
775    bo->x = x;
776    bo->y = y;
777    bo->w = strlen(title) + 6;
778    bo->h = 3;
779    bo->pushed = pushed;
780
781    RefreshButtonObj(bo);
782
783    return(bo);
784} /* NewButtonObj() */
785
786int
787SelectButtonObj(ButtonObj *bo)
788/*
789 * Desc: Wait for buttonpresses or TAB's to move on, or ESC ESC
790 */
791{
792    int	key;
793
794    print_button(bo->win, bo->title, bo->y+1, bo->x+2, TRUE);
795    wmove(bo->win, bo->y+1, bo->x+(bo->w/2)-1);
796    key = wgetch(bo->win);
797    print_button(bo->win, bo->title, bo->y+1, bo->x+2, FALSE);
798    switch(key) {
799    case '\t':
800	return(SEL_TAB);
801	break;
802    case KEY_BTAB:
803    case ctrl('b'):
804	return(SEL_BACKTAB);
805    case '\n':
806    case '\r':
807	*(bo->pushed) = TRUE;
808	return(SEL_BUTTON);
809	break;
810    case ESC:
811	return(SEL_ESC);
812	break;
813    default:
814	return(key);
815	break;
816    }
817} /* SelectButtonObj() */
818
819void
820DelButtonObj(ButtonObj *bo)
821/*
822 * Desc: Free the space occupied by <bo>
823 */
824{
825    free(bo->title);
826    free(bo);
827
828    return;
829} /* DelButtonObj() */
830