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