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 "libaura/mem.h"
53 
54 #include "libdfui/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 }
989