512dc4611beb4202e32ee978a83ec17b0d9018e2
[dragonfly.git] / contrib / bsdinstaller-1.1.6 / src / frontends / ncurses / curses_form.c
1 /*
2  * Copyright (c)2004 Cat's Eye Technologies.  All rights reserved.
3  * 
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 
8  *   Redistributions of source code must retain the above copyright
9  *   notice, this list of conditions and the following disclaimer.
10  * 
11  *   Redistributions in binary form must reproduce the above copyright
12  *   notice, this list of conditions and the following disclaimer in
13  *   the documentation and/or other materials provided with the
14  *   distribution.
15  * 
16  *   Neither the name of Cat's Eye Technologies nor the names of its
17  *   contributors may be used to endorse or promote products derived
18  *   from this software without specific prior written permission. 
19  * 
20  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
23  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
24  * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
25  * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
26  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
27  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
29  * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
30  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
31  * OF THE POSSIBILITY OF SUCH DAMAGE. 
32  */
33
34 /*
35  * curses_form.c
36  * $Id: curses_form.c,v 1.13 2005/02/08 22:56:06 cpressey Exp $
37  */
38
39 #include <ctype.h>
40 #include <ncurses.h>
41 #include <panel.h>
42 #include <stdlib.h>
43 #include <string.h>
44
45 #ifdef ENABLE_NLS
46 #include <libintl.h>
47 #define _(String) gettext (String)
48 #else
49 #define _(String) (String)
50 #endif
51
52 #include "aura/mem.h"
53
54 #include "dfui/dump.h"
55
56 #include "curses_form.h"
57 #include "curses_widget.h"
58 #include "curses_util.h"
59
60 /*** FORMS ***/
61
62 /*
63  * Create a new curses form with the given title text.
64  */
65 struct curses_form *
66 curses_form_new(const char *title)
67 {
68         struct curses_form *cf;
69
70         AURA_MALLOC(cf, curses_form);
71
72         cf->win = NULL;
73         cf->pan = NULL;
74
75         cf->widget_head = NULL;
76         cf->widget_tail = NULL;
77         cf->widget_focus = NULL;
78         cf->height = 0;
79         cf->width = strlen(title) + 4;
80         cf->x_offset = 0;
81         cf->y_offset = 0;
82         cf->int_width = 0;
83         cf->int_height = 0;
84         cf->want_x = 0;
85         cf->want_y = 0;
86
87         cf->title = aura_strdup(title);
88
89         cf->userdata = NULL;
90         cf->cleanup = 0;
91
92         cf->help_text = NULL;
93
94         return(cf);
95 }
96
97 /*
98  * Deallocate the memory used for the given curses form and all of the
99  * widgets it contains.  Note that this only frees any data at the form's
100  * userdata pointer IFF cleanup is non-zero.  Also, it does not cause the
101  * screen to be refreshed - call curses_form_refresh(NULL) afterwards to
102  * make the form disappear.
103  */
104 void
105 curses_form_free(struct curses_form *cf)
106 {
107         struct curses_widget *w, *t;
108
109         w = cf->widget_head;
110         while (w != NULL) {
111                 t = w->next;
112                 curses_widget_free(w);
113                 w = t;
114         }
115
116         if (cf->help_text != NULL) {
117                 free(cf->help_text);
118         }
119
120         if (cf->cleanup && cf->userdata != NULL) {
121                 free(cf->userdata);
122         }
123
124         if (cf->win != NULL) {
125                 del_panel(cf->pan);
126                 delwin(cf->win);
127         }
128
129         free(cf->title);
130         AURA_FREE(cf, curses_form);
131 }
132
133 /*
134  * Prepare the widget for being placed in the form.  This implements
135  * automatically positioning the widget and automatically resizing
136  * the form if the widget is too large to fit.
137  */
138 static void
139 curses_form_widget_prepare(struct curses_form *cf, struct curses_widget *w)
140 {
141         /*
142          * Link the widget to the form.
143          */
144         w->form = cf;
145
146         /*
147          * Auto-position the widget to the center of the form,
148          * if requested.
149          */
150         if (w->flags & CURSES_WIDGET_CENTER)
151                 w->x = (cf->width - w->width) / 2;
152
153         /*
154          * If the widget's right edge exceeds the width of
155          * the form, expand the form.
156          */
157         dfui_debug("w->x=%d w->width=%d cf->width=%d : ",
158             w->x, w->width, cf->width);
159         if ((w->x + w->width + 1) > cf->width)
160                 cf->width = w->x + w->width + 1;
161         dfui_debug("new cf->width=%d\n", cf->width);
162 }
163
164 /*
165  * Create a new curses_widget and add it to an existing curses_form.
166  * If the width of the widget is larger than will fit on the form, the
167  * form will be expanded, unless it would be expanded larger than the
168  * screen, in which case the widget is shrunk.
169  */
170 struct curses_widget *
171 curses_form_widget_add(struct curses_form *cf,
172                         unsigned int x, unsigned int y,
173                         unsigned int width, widget_t type,
174                         const char *text, unsigned int size,
175                         unsigned int flags)
176 {
177         struct curses_widget *w;
178
179         w = curses_widget_new(x, y, width, type, text, size, flags);
180         curses_form_widget_prepare(cf, w);
181
182         if (cf->widget_head == NULL) {
183                 cf->widget_head = w;
184         } else {
185                 cf->widget_tail->next = w;
186                 w->prev = cf->widget_tail;
187         }
188         cf->widget_tail = w;
189         
190         return(w);
191 }
192
193 /*
194  * Create a new curses_widget and add it after an existing curses_widget
195  * in an existing curses_form.
196  */
197 struct curses_widget *
198 curses_form_widget_insert_after(struct curses_widget *cw,
199                         unsigned int x, unsigned int y,
200                         unsigned int width, widget_t type,
201                         const char *text, unsigned int size,
202                         unsigned int flags)
203 {
204         struct curses_widget *w;
205
206         w = curses_widget_new(x, y, width, type, text, size, flags);
207         curses_form_widget_prepare(cw->form, w);
208
209         w->prev = cw;
210         w->next = cw->next;
211         if (cw->next == NULL)
212                 cw->form->widget_tail = w;
213         else
214                 cw->next->prev = w;
215         cw->next = w;
216         
217         return(w);
218 }
219
220 /*
221  * Unlink a widget from a form.  Does not deallocate the widget.
222  */
223 void
224 curses_form_widget_remove(struct curses_widget *w)
225 {
226         if (w->prev == NULL)
227                 w->form->widget_head = w->next;
228         else
229                 w->prev->next = w->next;
230
231         if (w->next == NULL)
232                 w->form->widget_tail = w->prev;
233         else
234                 w->next->prev = w->prev;
235
236         w->next = NULL;
237         w->prev = NULL;
238         w->form = NULL;
239 }
240
241 int
242 curses_form_descriptive_labels_add(struct curses_form *cf, const char *text,
243                                    unsigned int x, unsigned int y,
244                                    unsigned int width)
245 {
246         struct curses_widget *w;
247         int done = 0;
248         int pos = 0;
249         char *line;
250
251         line = aura_malloc(width + 1, "descriptive line");
252         while (!done) {
253                 done = extract_wrapped_line(text, line, width, &pos);
254                 dfui_debug("line = `%s', done = %d, width = %d, form width = %d : ",
255                    line, done, width, cf->width);
256                 w = curses_form_widget_add(cf, x, y++, 0,
257                     CURSES_LABEL, line, 0, CURSES_WIDGET_WIDEN);
258                 dfui_debug("now %d\n", cf->width);
259         }
260         free(line);
261         return(y);
262 }
263
264 void
265 curses_form_finalize(struct curses_form *cf)
266 {
267         if (cf->widget_head != NULL) {
268                 cf->widget_focus = cf->widget_head;
269                 curses_form_focus_skip_forward(cf);
270                 cf->want_x = cf->widget_focus->x + cf->widget_focus->width / 2;
271                 cf->want_y = cf->widget_focus->y;
272         } else {
273                 cf->widget_focus = NULL;
274         }
275
276         cf->left = (xmax - cf->width) / 2;
277         cf->top  = (ymax - cf->height) / 2;
278
279         /*
280          * Set the internal width and height.
281          */
282         
283         cf->int_width = cf->width;
284         cf->int_height = cf->height;
285
286         /*
287          * Limit form size to physical screen dimensions.
288          */
289         if (cf->width > (xmax - 2)) {
290                 cf->width = xmax - 2;
291                 cf->left = 1;
292         }
293         if (cf->height > (ymax - 2)) {
294                 cf->height = ymax - 2;
295                 cf->top = 1;
296         }
297         if (cf->top < 1)
298                 cf->top = 1;
299         if (cf->left < 1)
300                 cf->left = 1;
301
302         cf->win = newwin(cf->height + 2, cf->width + 2, cf->top - 1, cf->left - 1);
303         if (cf->win == NULL)
304                 fprintf(stderr, "Could not allocate %dx%d window @ %d,%d\n",
305                     cf->width + 2, cf->height + 2, cf->left - 1, cf->top - 1);
306
307         cf->pan = new_panel(cf->win);
308 }
309
310 /*
311  * Render the given form (and all of the widgets it contains) in the
312  * curses backing store.  Does not cause the form to be displayed.
313  */
314 void
315 curses_form_draw(struct curses_form *cf)
316 {
317         struct curses_widget *w;
318         float sb_factor = 0.0;
319         size_t sb_off = 0, sb_size = 0;
320
321         curses_colors_set(cf->win, CURSES_COLORS_NORMAL);
322         curses_window_blank(cf->win);
323
324         curses_colors_set(cf->win, CURSES_COLORS_BORDER);
325         /* draw_frame(cf->left - 1, cf->top - 1, cf->width + 2, cf->height + 2); */
326         wborder(cf->win, 0, 0, 0, 0, 0, 0, 0, 0);
327
328         /*
329          * If the internal dimensions of the form exceed the physical
330          * dimensions, draw scrollbar(s) as appropriate.
331          */
332         if (cf->int_height > cf->height) {
333                 sb_factor = (float)cf->height / (float)cf->int_height;
334                 sb_size = cf->height * sb_factor;
335                 if (sb_size == 0) sb_size = 1;
336                 sb_off = cf->y_offset * sb_factor;
337                 curses_colors_set(cf->win, CURSES_COLORS_SCROLLAREA);
338                 mvwvline(cf->win, 1, cf->width + 1, ACS_CKBOARD, cf->height);
339                 curses_colors_set(cf->win, CURSES_COLORS_SCROLLBAR);
340                 mvwvline(cf->win, 1 + sb_off, cf->width + 1, ACS_BLOCK, sb_size);
341         }
342
343         if (cf->int_width > cf->width) {
344                 sb_factor = (float)cf->width / (float)cf->int_width;
345                 sb_size = cf->width * sb_factor;
346                 if (sb_size == 0) sb_size = 1;
347                 sb_off = cf->x_offset * sb_factor;
348                 curses_colors_set(cf->win, CURSES_COLORS_SCROLLAREA);
349                 mvwhline(cf->win, cf->height + 1, 1, ACS_CKBOARD, cf->width);
350                 curses_colors_set(cf->win, CURSES_COLORS_SCROLLBAR);
351                 mvwhline(cf->win, cf->height + 1, 1 + sb_off, ACS_BLOCK, sb_size);
352         }
353
354         curses_colors_set(cf->win, CURSES_COLORS_BORDER);
355
356         /*
357          * Render the title bar text.
358          */
359         wmove(cf->win, 0, (cf->width - strlen(cf->title)) / 2 - 1);
360         waddch(cf->win, ACS_RTEE);
361         waddch(cf->win, ' ');
362         curses_colors_set(cf->win, CURSES_COLORS_FORMTITLE);
363         waddstr(cf->win, cf->title);
364         curses_colors_set(cf->win, CURSES_COLORS_BORDER);
365         waddch(cf->win, ' ');
366         waddch(cf->win, ACS_LTEE);
367
368         /*
369          * Render a "how to get help" reminder.
370          */
371         if (cf->help_text != NULL) {
372                 static const char *help_msg = "Press F1 for Help";
373
374                 wmove(cf->win, cf->height + 1,
375                       (cf->width - strlen(help_msg)) / 2 - 1);
376                 waddch(cf->win, ACS_RTEE);
377                 waddch(cf->win, ' ');
378                 curses_colors_set(cf->win, CURSES_COLORS_FORMTITLE);
379                 waddstr(cf->win, help_msg);
380                 curses_colors_set(cf->win, CURSES_COLORS_BORDER);
381                 waddch(cf->win, ' ');
382                 waddch(cf->win, ACS_LTEE);
383         }
384
385         /*
386          * Render the widgets.
387          */
388         for (w = cf->widget_head; w != NULL; w = w->next) {
389                 curses_widget_draw(w);
390         }
391
392         /* to put the cursor there */
393         curses_widget_draw_tooltip(cf->widget_focus);
394         curses_widget_draw(cf->widget_focus);
395 }
396
397 /*
398  * Cause the given form to be displayed (if it was not displayed previously)
399  * or refreshed (if it was previously displayed.)  Passing NULL to this
400  * function causes all visible forms to be refreshed.
401  *
402  * (Implementation note: the argument is actually irrelevant - all visible
403  * forms will be refreshed when any form is displayed or refreshed - but
404  * client code should not rely on this behaviour.)
405  */
406 void
407 curses_form_refresh(struct curses_form *cf __unused)
408 {
409         update_panels();
410         doupdate();
411 }
412
413 void
414 curses_form_focus_skip_forward(struct curses_form *cf)
415 {
416         while (!curses_widget_can_take_focus(cf->widget_focus)) {
417                 cf->widget_focus = cf->widget_focus->next;
418                 if (cf->widget_focus == NULL)
419                         cf->widget_focus = cf->widget_head;
420         }
421         curses_form_widget_ensure_visible(cf->widget_focus);
422 }
423
424 void
425 curses_form_focus_skip_backward(struct curses_form *cf)
426 {
427         while (!curses_widget_can_take_focus(cf->widget_focus)) {
428                 cf->widget_focus = cf->widget_focus->prev;
429                 if (cf->widget_focus == NULL)
430                         cf->widget_focus = cf->widget_tail;
431         }
432         curses_form_widget_ensure_visible(cf->widget_focus);
433 }
434
435 void
436 curses_form_advance(struct curses_form *cf)
437 {
438         struct curses_widget *w;
439
440         w = cf->widget_focus;
441         cf->widget_focus = cf->widget_focus->next;
442         if (cf->widget_focus == NULL)
443                 cf->widget_focus = cf->widget_head;
444         curses_form_focus_skip_forward(cf);
445         cf->want_x = cf->widget_focus->x + cf->widget_focus->width / 2;
446         cf->want_y = cf->widget_focus->y;
447         curses_widget_draw(w);
448         curses_widget_draw_tooltip(cf->widget_focus);
449         curses_widget_draw(cf->widget_focus);
450         curses_form_refresh(cf);
451 #ifdef DEBUG
452         curses_debug_int(cf->widget_focus->user_id);
453 #endif
454 }
455
456 void
457 curses_form_retreat(struct curses_form *cf)
458 {
459         struct curses_widget *w;
460
461         w = cf->widget_focus;
462         cf->widget_focus = cf->widget_focus->prev;
463         if (cf->widget_focus == NULL)
464                 cf->widget_focus = cf->widget_tail;
465         curses_form_focus_skip_backward(cf);
466         cf->want_x = cf->widget_focus->x + cf->widget_focus->width / 2;
467         cf->want_y = cf->widget_focus->y;
468         curses_widget_draw(w);
469         curses_widget_draw_tooltip(cf->widget_focus);
470         curses_widget_draw(cf->widget_focus);
471         curses_form_refresh(cf);
472 #ifdef DEBUG
473         curses_debug_int(cf->widget_focus->user_id);
474 #endif
475 }
476
477 /*
478  * Returns the widget at (x, y) within a form, or NULL if
479  * there is no widget at that location.
480  */
481 struct curses_widget *
482 curses_form_widget_at(struct curses_form *cf, unsigned int x, unsigned int y)
483 {
484         struct curses_widget *w;
485
486         for (w = cf->widget_head; w != NULL; w = w->next) {
487                 if (y == w->y && x >= w->x && x <= (w->x + w->width))
488                         return(w);
489         }
490         
491         return(NULL);
492 }
493
494 /*
495  * Returns the first (focusable) widget on
496  * the topmost row of the form.
497  */
498 int
499 curses_form_widget_first_row(struct curses_form *cf)
500 {
501         struct curses_widget *w;
502
503         for (w = cf->widget_head; w != NULL; w = w->next) {
504                 if (curses_widget_can_take_focus(w))
505                         return(w->y);
506         }
507         
508         return(0);
509 }
510
511 /*
512  * Returns the first (focusable) widget on
513  * the bottommost row of the form.
514  */
515 int
516 curses_form_widget_last_row(struct curses_form *cf)
517 {
518         struct curses_widget *w;
519         unsigned int best_y = 0;
520
521         for (w = cf->widget_head; w != NULL; w = w->next) {
522                 if (curses_widget_can_take_focus(w) && w->y > best_y) {
523                         best_y = w->y;
524                 }
525         }
526         
527         return(best_y);
528 }
529
530 /*
531  * Returns the first (focusable) widget on row y.
532  */
533 struct curses_widget *
534 curses_form_widget_first_on_row(struct curses_form *cf, unsigned int y)
535 {
536         struct curses_widget *w;
537
538         for (w = cf->widget_head; w != NULL; w = w->next) {
539                 if (curses_widget_can_take_focus(w) && y == w->y)
540                         return(w);
541         }
542         
543         return(NULL);
544 }
545
546 /*
547  * Returns the (focusable) widget on row y closest to x.
548  */
549 struct curses_widget *
550 curses_form_widget_closest_on_row(struct curses_form *cf,
551                                   unsigned int x, unsigned int y)
552 {
553         struct curses_widget *w, *best = NULL;
554         int dist, best_dist = 999;
555
556         w = curses_form_widget_first_on_row(cf, y);
557         if (w == NULL)
558                 return(NULL);
559
560         for (best = w; w != NULL && w->y == y; w = w->next) {
561                 if (!curses_widget_can_take_focus(w))
562                         continue;
563                 dist = (w->x + w->width / 2) - x;
564                 if (dist < 0) dist *= -1;
565                 if (dist < best_dist) {
566                         best_dist = dist;
567                         best = w;
568                 }
569         }
570
571         return(best);
572 }
573
574 /*
575  * Returns the number of (focusable) widgets with y values less than
576  * (above) the given widget.
577  */
578 int
579 curses_form_widget_count_above(struct curses_form *cf,
580                                 struct curses_widget *w)
581 {
582         struct curses_widget *lw;
583         int count = 0;
584
585         for (lw = cf->widget_head; lw != NULL; lw = lw->next) {
586                 if (curses_widget_can_take_focus(lw) && lw->y < w->y)
587                         count++;
588         }
589         
590         return(count);
591 }
592
593 /*
594  * Returns the number of (focusable) widgets with y values greater than
595  * (below) the given widget.
596  */
597 int
598 curses_form_widget_count_below(struct curses_form *cf,
599                                 struct curses_widget *w)
600 {
601         struct curses_widget *lw;
602         int count = 0;
603
604         for (lw = cf->widget_head; lw != NULL; lw = lw->next) {
605                 if (curses_widget_can_take_focus(lw) && lw->y > w->y)
606                         count++;
607         }
608         
609         return(count);
610 }
611
612 /*
613  * Move to the next widget whose y is greater than the
614  * current want_y, and whose x is closest to want_x.
615  */
616 void
617 curses_form_advance_row(struct curses_form *cf)
618 {
619         struct curses_widget *w, *c;
620         int wy;
621
622         w = cf->widget_focus;
623         if (curses_form_widget_count_below(cf, w) > 0) {
624                 wy = cf->want_y + 1;
625         } else {
626                 wy = curses_form_widget_first_row(cf);
627         }
628         do {
629                 c = curses_form_widget_closest_on_row(cf,
630                     cf->want_x, wy++);
631         } while (c == NULL);
632
633         cf->widget_focus = c;
634         curses_form_focus_skip_forward(cf);
635         cf->want_y = cf->widget_focus->y;
636         curses_widget_draw(w);
637         curses_widget_draw_tooltip(cf->widget_focus);
638         curses_widget_draw(cf->widget_focus);
639         curses_form_refresh(cf);
640 }
641
642 /*
643  * Move to the next widget whose y is less than the
644  * current want_y, and whose x is closest to want_x.
645  */
646 void
647 curses_form_retreat_row(struct curses_form *cf)
648 {
649         struct curses_widget *w, *c;
650         int wy;
651
652         w = cf->widget_focus;
653         if (curses_form_widget_count_above(cf, w) > 0) {
654                 wy = cf->want_y - 1;
655         } else {
656                 wy = curses_form_widget_last_row(cf);
657         }
658         do {
659                 c = curses_form_widget_closest_on_row(cf,
660                     cf->want_x, wy--);
661         } while (c == NULL);
662
663         cf->widget_focus = c;
664         curses_form_focus_skip_backward(cf);
665         cf->want_y = cf->widget_focus->y;
666         curses_widget_draw(w);
667         curses_widget_draw_tooltip(cf->widget_focus);
668         curses_widget_draw(cf->widget_focus);
669         curses_form_refresh(cf);
670 }
671
672 void
673 curses_form_scroll_to(struct curses_form *cf,
674                       unsigned int x_off, unsigned int y_off)
675 {
676         cf->x_offset = x_off;
677         cf->y_offset = y_off;
678 }
679
680 void
681 curses_form_scroll_delta(struct curses_form *cf, int dx, int dy)
682 {
683         unsigned int x_off, y_off;
684
685         if (dx < 0 && (unsigned int)-dx > cf->x_offset) {
686                 x_off = 0;
687         } else {
688                 x_off = cf->x_offset + dx;
689         }
690         if (x_off > (cf->int_width - cf->width))
691                 x_off = cf->int_width - cf->width;
692
693         if (dy < 0 && (unsigned int)-dy > cf->y_offset) {
694                 y_off = 0;
695         } else {
696                 y_off = cf->y_offset + dy;
697         }
698         if (y_off > (cf->int_height - cf->height))
699                 y_off = cf->int_height - cf->height;
700
701         curses_form_scroll_to(cf, x_off, y_off);
702 }
703
704 static void
705 curses_form_refocus_after_scroll(struct curses_form *cf, int dx, int dy)
706 {
707         struct curses_widget *w;
708
709         w = curses_form_widget_closest_on_row(cf,
710             cf->widget_focus->x + dx, cf->widget_focus->y + dy);
711
712         if (w != NULL) {
713                 cf->widget_focus = w;
714                 cf->want_x = w->x + w->width / 2;
715                 cf->want_y = w->y;
716         }
717 }
718
719 int
720 curses_form_widget_is_visible(struct curses_widget *w)
721 {
722         unsigned int wx, wy;
723
724         wx = w->x + 1 - w->form->x_offset;
725         wy = w->y + 1 - w->form->y_offset;
726
727         if (wy < 1 || wy > w->form->height)
728                 return(0);
729
730         return(1);
731 }
732
733 void
734 curses_form_widget_ensure_visible(struct curses_widget *w)
735 {
736         unsigned int wx, wy;
737         int dx = 0, dy = 0;
738
739         /*
740          * If a textbox's offset is such that we can't see
741          * the cursor inside, adjust it.
742          */
743         if (w->type == CURSES_TEXTBOX) {
744                 if (w->curpos - w->offset >= w->width - 2)
745                         w->offset = w->curpos - (w->width - 3);
746                 if (w->offset > w->curpos)
747                         w->offset = w->curpos;
748         }
749
750         if (curses_form_widget_is_visible(w))
751                 return;
752
753         wx = w->x + 1 - w->form->x_offset;
754         wy = w->y + 1 - w->form->y_offset;
755
756         if (wy < 1)
757                 dy = -1 * (1 - wy);
758         else if (wy > w->form->height)
759                 dy = (wy - w->form->height);
760
761         curses_form_scroll_delta(w->form, dx, dy);
762         curses_form_draw(w->form);
763         curses_form_refresh(w->form);
764 }
765
766 static void
767 curses_form_show_help(const char *text)
768 {
769         struct curses_form *cf;
770         struct curses_widget *w;
771
772         cf = curses_form_new(_("Help"));
773
774         cf->height = curses_form_descriptive_labels_add(cf, text, 1, 1, 72);
775         cf->height += 1;
776         w = curses_form_widget_add(cf, 0, cf->height++, 0,
777             CURSES_BUTTON, _("OK"), 0, CURSES_WIDGET_WIDEN);
778         curses_widget_set_click_cb(w, cb_click_close_form);
779         
780         curses_form_finalize(cf);
781
782         curses_form_draw(cf);
783         curses_form_refresh(cf);
784         curses_form_frob(cf);
785         curses_form_free(cf);
786 }
787
788 #define CTRL(c) (char)(c - 'a' + 1)
789
790 struct curses_widget *
791 curses_form_frob(struct curses_form *cf)
792 {
793         int key;
794
795         flushinp();
796         for (;;) {
797                 key = getch();
798                 switch(key) {
799                 case KEY_DOWN:
800                 case CTRL('n'):
801                         curses_form_advance_row(cf);
802                         break;
803                 case KEY_UP:
804                 case CTRL('p'):
805                         curses_form_retreat_row(cf);
806                         break;
807                 case '\t':
808                         curses_form_advance(cf);
809                         break;
810                 case KEY_RIGHT:
811                 case CTRL('f'):
812                         if (cf->widget_focus->type == CURSES_TEXTBOX) {
813                                 if (!curses_textbox_advance_char(cf->widget_focus))
814                                         curses_form_advance(cf);
815                         } else
816                                 curses_form_advance(cf);
817                         break;
818                 case KEY_LEFT:
819                 case CTRL('b'):
820                         if (cf->widget_focus->type == CURSES_TEXTBOX) {
821                                 if (!curses_textbox_retreat_char(cf->widget_focus))
822                                         curses_form_retreat(cf);
823                         } else
824                                 curses_form_retreat(cf);
825                         break;
826                 case '\n':
827                 case '\r':
828                         if (cf->widget_focus->type == CURSES_TEXTBOX) {
829                                 switch (curses_widget_click(cf->widget_focus)) {
830                                 case -1:
831                                         curses_form_advance(cf);
832                                         break;
833                                 case 0:
834                                         break;
835                                 case 1:
836                                         /* this would be pretty rare */
837                                         return(cf->widget_focus);
838                                 }
839                         } else if (cf->widget_focus->type == CURSES_BUTTON) {
840                                 switch (curses_widget_click(cf->widget_focus)) {
841                                 case -1:
842                                         beep();
843                                         break;
844                                 case 0:
845                                         break;
846                                 case 1:
847                                         return(cf->widget_focus);
848                                 }
849                         } else if (cf->widget_focus->type == CURSES_CHECKBOX) {
850                                 curses_checkbox_toggle(cf->widget_focus);                               
851                         } else {
852                                 beep();
853                         }
854                         break;
855                 case '\b':
856                 case KEY_BACKSPACE:
857                 case 127:               /* why is this not KEY_BACKSPACE on xterm?? */
858                         if (cf->widget_focus->type == CURSES_TEXTBOX) {
859                                 curses_textbox_backspace_char(cf->widget_focus);
860                         } else {
861                                 beep();
862                         }
863                         break;
864                 case KEY_DC:
865                 case CTRL('k'):
866                         if (cf->widget_focus->type == CURSES_TEXTBOX) {
867                                 curses_textbox_delete_char(cf->widget_focus);
868                         } else {
869                                 beep();
870                         }
871                         break;
872                 case KEY_HOME:
873                 case CTRL('a'):
874                         if (cf->widget_focus->type == CURSES_TEXTBOX) {
875                                 curses_textbox_home(cf->widget_focus);
876                         } else {
877                                 beep();
878                         }
879                         break;
880                 case KEY_END:
881                 case CTRL('e'):
882                         if (cf->widget_focus->type == CURSES_TEXTBOX) {
883                                 curses_textbox_end(cf->widget_focus);
884                         } else {
885                                 beep();
886                         }
887                         break;
888                 case KEY_NPAGE:
889                 case CTRL('g'):
890                         curses_form_scroll_delta(cf, 0, cf->height - 1);
891                         curses_form_refocus_after_scroll(cf, 0, cf->height - 1);
892                         curses_form_draw(cf);
893                         curses_form_refresh(cf);
894                         break;
895                 case KEY_PPAGE:
896                 case CTRL('t'):
897                         curses_form_scroll_delta(cf, 0, -1 * (cf->height - 1));
898                         curses_form_refocus_after_scroll(cf, 0, -1 * (cf->height - 1));
899                         curses_form_draw(cf);
900                         curses_form_refresh(cf);
901                         break;
902                 case ' ':
903                         if (cf->widget_focus->type == CURSES_TEXTBOX) {
904                                 /* XXX if non-editable, click it */
905                                 curses_textbox_insert_char(cf->widget_focus, ' ');
906                         } else if (cf->widget_focus->type == CURSES_BUTTON) {
907                                 switch (curses_widget_click(cf->widget_focus)) {
908                                 case -1:
909                                         beep();
910                                         break;
911                                 case 0:
912                                         break;
913                                 case 1:
914                                         return(cf->widget_focus);
915                                 }
916                         } else if (cf->widget_focus->type == CURSES_CHECKBOX) {
917                                 curses_checkbox_toggle(cf->widget_focus);                               
918                         } else {
919                                 beep();
920                         }
921                         break;
922                 case KEY_F(1):          /* why does this not work in xterm??? */
923                 case CTRL('w'):
924                         if (cf->help_text != NULL) {
925                                 curses_form_show_help(cf->help_text);
926                                 curses_form_refresh(cf);
927                         }
928                         break;
929                 case KEY_F(10):
930                 case CTRL('l'):
931                         redrawwin(stdscr);
932                         curses_form_refresh(NULL);
933                         break;
934                 default:
935                         if (isprint(key) && cf->widget_focus->type == CURSES_TEXTBOX) {
936                                 curses_textbox_insert_char(cf->widget_focus, (char)key);
937                         } else {
938                                 struct curses_widget *cw;
939                                 
940                                 for (cw = cf->widget_head; cw != NULL; cw = cw->next) {
941                                         if (toupper(key) == cw->accel) {
942                                                 /*
943                                                  * To just refocus:
944                                                  */
945                                                 /*
946                                                 cf->widget_focus = cw;
947                                                 curses_form_widget_ensure_visible(cw);
948                                                 curses_form_draw(cf);
949                                                 curses_form_refresh(cf);
950                                                 */
951                                                 /*
952                                                  * To actually activate:
953                                                  */
954                                                 switch (curses_widget_click(cw)) {
955                                                 case -1:
956                                                         beep();
957                                                         break;
958                                                 case 0:
959                                                         break;
960                                                 case 1:
961                                                         return(cw);
962                                                 }
963
964                                                 break;
965                                         }
966                                 }
967 #ifdef DEBUG
968                                 curses_debug_key(key);
969 #endif
970                                 beep();
971                         }
972                         break;
973                 }
974         }
975 }
976
977 /*** GENERIC CALLBACKS ***/
978
979 /*
980  * Callback to give to curses_button_set_click_cb, for buttons
981  * that simply close the form they are in when they are clicked.
982  * These usually map to dfui actions.
983  */
984 int
985 cb_click_close_form(struct curses_widget *w __unused)
986 {
987         return(1);
988 }