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 *
curses_form_new(const char * title)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
curses_form_free(struct curses_form * cf)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
curses_form_widget_prepare(struct curses_form * cf,struct curses_widget * w)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 *
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)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 *
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)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
curses_form_widget_remove(struct curses_widget * w)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
curses_form_descriptive_labels_add(struct curses_form * cf,const char * text,unsigned int x,unsigned int y,unsigned int width)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 int done = 0;
247 int pos = 0;
248 char *line;
249
250 line = aura_malloc(width + 1, "descriptive line");
251 while (!done) {
252 done = extract_wrapped_line(text, line, width, &pos);
253 dfui_debug("line = `%s', done = %d, width = %d, form width = %d : ",
254 line, done, width, cf->width);
255 curses_form_widget_add(cf, x, y++, 0,
256 CURSES_LABEL, line, 0, CURSES_WIDGET_WIDEN);
257 dfui_debug("now %d\n", cf->width);
258 }
259 free(line);
260 return(y);
261 }
262
263 void
curses_form_finalize(struct curses_form * cf)264 curses_form_finalize(struct curses_form *cf)
265 {
266 if (cf->widget_head != NULL) {
267 cf->widget_focus = cf->widget_head;
268 curses_form_focus_skip_forward(cf);
269 cf->want_x = cf->widget_focus->x + cf->widget_focus->width / 2;
270 cf->want_y = cf->widget_focus->y;
271 } else {
272 cf->widget_focus = NULL;
273 }
274
275 cf->left = (xmax - cf->width) / 2;
276 cf->top = (ymax - cf->height) / 2;
277
278 /*
279 * Set the internal width and height.
280 */
281
282 cf->int_width = cf->width;
283 cf->int_height = cf->height;
284
285 /*
286 * Limit form size to physical screen dimensions.
287 */
288 if (cf->width > (xmax - 2)) {
289 cf->width = xmax - 2;
290 cf->left = 1;
291 }
292 if (cf->height > (ymax - 2)) {
293 cf->height = ymax - 2;
294 cf->top = 1;
295 }
296 if (cf->top < 1)
297 cf->top = 1;
298 if (cf->left < 1)
299 cf->left = 1;
300
301 cf->win = newwin(cf->height + 2, cf->width + 2, cf->top - 1, cf->left - 1);
302 if (cf->win == NULL)
303 fprintf(stderr, "Could not allocate %dx%d window @ %d,%d\n",
304 cf->width + 2, cf->height + 2, cf->left - 1, cf->top - 1);
305
306 cf->pan = new_panel(cf->win);
307 }
308
309 /*
310 * Render the given form (and all of the widgets it contains) in the
311 * curses backing store. Does not cause the form to be displayed.
312 */
313 void
curses_form_draw(struct curses_form * cf)314 curses_form_draw(struct curses_form *cf)
315 {
316 struct curses_widget *w;
317 float sb_factor = 0.0;
318 size_t sb_off = 0, sb_size = 0;
319
320 curses_colors_set(cf->win, CURSES_COLORS_NORMAL);
321 curses_window_blank(cf->win);
322
323 curses_colors_set(cf->win, CURSES_COLORS_BORDER);
324 /* draw_frame(cf->left - 1, cf->top - 1, cf->width + 2, cf->height + 2); */
325 wborder(cf->win, 0, 0, 0, 0, 0, 0, 0, 0);
326
327 /*
328 * If the internal dimensions of the form exceed the physical
329 * dimensions, draw scrollbar(s) as appropriate.
330 */
331 if (cf->int_height > cf->height) {
332 sb_factor = (float)cf->height / (float)cf->int_height;
333 sb_size = cf->height * sb_factor;
334 if (sb_size == 0) sb_size = 1;
335 sb_off = cf->y_offset * sb_factor;
336 curses_colors_set(cf->win, CURSES_COLORS_SCROLLAREA);
337 mvwvline(cf->win, 1, cf->width + 1, ACS_CKBOARD, cf->height);
338 curses_colors_set(cf->win, CURSES_COLORS_SCROLLBAR);
339 mvwvline(cf->win, 1 + sb_off, cf->width + 1, ACS_BLOCK, sb_size);
340 }
341
342 if (cf->int_width > cf->width) {
343 sb_factor = (float)cf->width / (float)cf->int_width;
344 sb_size = cf->width * sb_factor;
345 if (sb_size == 0) sb_size = 1;
346 sb_off = cf->x_offset * sb_factor;
347 curses_colors_set(cf->win, CURSES_COLORS_SCROLLAREA);
348 mvwhline(cf->win, cf->height + 1, 1, ACS_CKBOARD, cf->width);
349 curses_colors_set(cf->win, CURSES_COLORS_SCROLLBAR);
350 mvwhline(cf->win, cf->height + 1, 1 + sb_off, ACS_BLOCK, sb_size);
351 }
352
353 curses_colors_set(cf->win, CURSES_COLORS_BORDER);
354
355 /*
356 * Render the title bar text.
357 */
358 wmove(cf->win, 0, (cf->width - strlen(cf->title)) / 2 - 1);
359 waddch(cf->win, ACS_RTEE);
360 waddch(cf->win, ' ');
361 curses_colors_set(cf->win, CURSES_COLORS_FORMTITLE);
362 waddstr(cf->win, cf->title);
363 curses_colors_set(cf->win, CURSES_COLORS_BORDER);
364 waddch(cf->win, ' ');
365 waddch(cf->win, ACS_LTEE);
366
367 /*
368 * Render a "how to get help" reminder.
369 */
370 if (cf->help_text != NULL) {
371 static const char *help_msg = "Press F1 for Help";
372
373 wmove(cf->win, cf->height + 1,
374 (cf->width - strlen(help_msg)) / 2 - 1);
375 waddch(cf->win, ACS_RTEE);
376 waddch(cf->win, ' ');
377 curses_colors_set(cf->win, CURSES_COLORS_FORMTITLE);
378 waddstr(cf->win, help_msg);
379 curses_colors_set(cf->win, CURSES_COLORS_BORDER);
380 waddch(cf->win, ' ');
381 waddch(cf->win, ACS_LTEE);
382 }
383
384 /*
385 * Render the widgets.
386 */
387 for (w = cf->widget_head; w != NULL; w = w->next) {
388 curses_widget_draw(w);
389 }
390
391 /* to put the cursor there */
392 curses_widget_draw_tooltip(cf->widget_focus);
393 curses_widget_draw(cf->widget_focus);
394 }
395
396 /*
397 * Cause the given form to be displayed (if it was not displayed previously)
398 * or refreshed (if it was previously displayed.) Passing NULL to this
399 * function causes all visible forms to be refreshed.
400 *
401 * (Implementation note: the argument is actually irrelevant - all visible
402 * forms will be refreshed when any form is displayed or refreshed - but
403 * client code should not rely on this behaviour.)
404 */
405 void
curses_form_refresh(struct curses_form * cf __unused)406 curses_form_refresh(struct curses_form *cf __unused)
407 {
408 update_panels();
409 doupdate();
410 }
411
412 void
curses_form_focus_skip_forward(struct curses_form * cf)413 curses_form_focus_skip_forward(struct curses_form *cf)
414 {
415 while (!curses_widget_can_take_focus(cf->widget_focus)) {
416 cf->widget_focus = cf->widget_focus->next;
417 if (cf->widget_focus == NULL)
418 cf->widget_focus = cf->widget_head;
419 }
420 curses_form_widget_ensure_visible(cf->widget_focus);
421 }
422
423 void
curses_form_focus_skip_backward(struct curses_form * cf)424 curses_form_focus_skip_backward(struct curses_form *cf)
425 {
426 while (!curses_widget_can_take_focus(cf->widget_focus)) {
427 cf->widget_focus = cf->widget_focus->prev;
428 if (cf->widget_focus == NULL)
429 cf->widget_focus = cf->widget_tail;
430 }
431 curses_form_widget_ensure_visible(cf->widget_focus);
432 }
433
434 void
curses_form_advance(struct curses_form * cf)435 curses_form_advance(struct curses_form *cf)
436 {
437 struct curses_widget *w;
438
439 w = cf->widget_focus;
440 cf->widget_focus = cf->widget_focus->next;
441 if (cf->widget_focus == NULL)
442 cf->widget_focus = cf->widget_head;
443 curses_form_focus_skip_forward(cf);
444 cf->want_x = cf->widget_focus->x + cf->widget_focus->width / 2;
445 cf->want_y = cf->widget_focus->y;
446 curses_widget_draw(w);
447 curses_widget_draw_tooltip(cf->widget_focus);
448 curses_widget_draw(cf->widget_focus);
449 curses_form_refresh(cf);
450 #ifdef DEBUG
451 curses_debug_int(cf->widget_focus->user_id);
452 #endif
453 }
454
455 void
curses_form_retreat(struct curses_form * cf)456 curses_form_retreat(struct curses_form *cf)
457 {
458 struct curses_widget *w;
459
460 w = cf->widget_focus;
461 cf->widget_focus = cf->widget_focus->prev;
462 if (cf->widget_focus == NULL)
463 cf->widget_focus = cf->widget_tail;
464 curses_form_focus_skip_backward(cf);
465 cf->want_x = cf->widget_focus->x + cf->widget_focus->width / 2;
466 cf->want_y = cf->widget_focus->y;
467 curses_widget_draw(w);
468 curses_widget_draw_tooltip(cf->widget_focus);
469 curses_widget_draw(cf->widget_focus);
470 curses_form_refresh(cf);
471 #ifdef DEBUG
472 curses_debug_int(cf->widget_focus->user_id);
473 #endif
474 }
475
476 /*
477 * Returns the widget at (x, y) within a form, or NULL if
478 * there is no widget at that location.
479 */
480 struct curses_widget *
curses_form_widget_at(struct curses_form * cf,unsigned int x,unsigned int y)481 curses_form_widget_at(struct curses_form *cf, unsigned int x, unsigned int y)
482 {
483 struct curses_widget *w;
484
485 for (w = cf->widget_head; w != NULL; w = w->next) {
486 if (y == w->y && x >= w->x && x <= (w->x + w->width))
487 return(w);
488 }
489
490 return(NULL);
491 }
492
493 /*
494 * Returns the first (focusable) widget on
495 * the topmost row of the form.
496 */
497 int
curses_form_widget_first_row(struct curses_form * cf)498 curses_form_widget_first_row(struct curses_form *cf)
499 {
500 struct curses_widget *w;
501
502 for (w = cf->widget_head; w != NULL; w = w->next) {
503 if (curses_widget_can_take_focus(w))
504 return(w->y);
505 }
506
507 return(0);
508 }
509
510 /*
511 * Returns the first (focusable) widget on
512 * the bottommost row of the form.
513 */
514 int
curses_form_widget_last_row(struct curses_form * cf)515 curses_form_widget_last_row(struct curses_form *cf)
516 {
517 struct curses_widget *w;
518 unsigned int best_y = 0;
519
520 for (w = cf->widget_head; w != NULL; w = w->next) {
521 if (curses_widget_can_take_focus(w) && w->y > best_y) {
522 best_y = w->y;
523 }
524 }
525
526 return(best_y);
527 }
528
529 /*
530 * Returns the first (focusable) widget on row y.
531 */
532 struct curses_widget *
curses_form_widget_first_on_row(struct curses_form * cf,unsigned int y)533 curses_form_widget_first_on_row(struct curses_form *cf, unsigned int y)
534 {
535 struct curses_widget *w;
536
537 for (w = cf->widget_head; w != NULL; w = w->next) {
538 if (curses_widget_can_take_focus(w) && y == w->y)
539 return(w);
540 }
541
542 return(NULL);
543 }
544
545 /*
546 * Returns the (focusable) widget on row y closest to x.
547 */
548 struct curses_widget *
curses_form_widget_closest_on_row(struct curses_form * cf,unsigned int x,unsigned int y)549 curses_form_widget_closest_on_row(struct curses_form *cf,
550 unsigned int x, unsigned int y)
551 {
552 struct curses_widget *w, *best = NULL;
553 int dist, best_dist = 999;
554
555 w = curses_form_widget_first_on_row(cf, y);
556 if (w == NULL)
557 return(NULL);
558
559 for (best = w; w != NULL && w->y == y; w = w->next) {
560 if (!curses_widget_can_take_focus(w))
561 continue;
562 dist = (w->x + w->width / 2) - x;
563 if (dist < 0) dist *= -1;
564 if (dist < best_dist) {
565 best_dist = dist;
566 best = w;
567 }
568 }
569
570 return(best);
571 }
572
573 /*
574 * Returns the number of (focusable) widgets with y values less than
575 * (above) the given widget.
576 */
577 int
curses_form_widget_count_above(struct curses_form * cf,struct curses_widget * w)578 curses_form_widget_count_above(struct curses_form *cf,
579 struct curses_widget *w)
580 {
581 struct curses_widget *lw;
582 int count = 0;
583
584 for (lw = cf->widget_head; lw != NULL; lw = lw->next) {
585 if (curses_widget_can_take_focus(lw) && lw->y < w->y)
586 count++;
587 }
588
589 return(count);
590 }
591
592 /*
593 * Returns the number of (focusable) widgets with y values greater than
594 * (below) the given widget.
595 */
596 int
curses_form_widget_count_below(struct curses_form * cf,struct curses_widget * w)597 curses_form_widget_count_below(struct curses_form *cf,
598 struct curses_widget *w)
599 {
600 struct curses_widget *lw;
601 int count = 0;
602
603 for (lw = cf->widget_head; lw != NULL; lw = lw->next) {
604 if (curses_widget_can_take_focus(lw) && lw->y > w->y)
605 count++;
606 }
607
608 return(count);
609 }
610
611 /*
612 * Move to the next widget whose y is greater than the
613 * current want_y, and whose x is closest to want_x.
614 */
615 void
curses_form_advance_row(struct curses_form * cf)616 curses_form_advance_row(struct curses_form *cf)
617 {
618 struct curses_widget *w, *c;
619 int wy;
620
621 w = cf->widget_focus;
622 if (curses_form_widget_count_below(cf, w) > 0) {
623 wy = cf->want_y + 1;
624 } else {
625 wy = curses_form_widget_first_row(cf);
626 }
627 do {
628 c = curses_form_widget_closest_on_row(cf,
629 cf->want_x, wy++);
630 } while (c == NULL);
631
632 cf->widget_focus = c;
633 curses_form_focus_skip_forward(cf);
634 cf->want_y = cf->widget_focus->y;
635 curses_widget_draw(w);
636 curses_widget_draw_tooltip(cf->widget_focus);
637 curses_widget_draw(cf->widget_focus);
638 curses_form_refresh(cf);
639 }
640
641 /*
642 * Move to the next widget whose y is less than the
643 * current want_y, and whose x is closest to want_x.
644 */
645 void
curses_form_retreat_row(struct curses_form * cf)646 curses_form_retreat_row(struct curses_form *cf)
647 {
648 struct curses_widget *w, *c;
649 int wy;
650
651 w = cf->widget_focus;
652 if (curses_form_widget_count_above(cf, w) > 0) {
653 wy = cf->want_y - 1;
654 } else {
655 wy = curses_form_widget_last_row(cf);
656 }
657 do {
658 c = curses_form_widget_closest_on_row(cf,
659 cf->want_x, wy--);
660 } while (c == NULL);
661
662 cf->widget_focus = c;
663 curses_form_focus_skip_backward(cf);
664 cf->want_y = cf->widget_focus->y;
665 curses_widget_draw(w);
666 curses_widget_draw_tooltip(cf->widget_focus);
667 curses_widget_draw(cf->widget_focus);
668 curses_form_refresh(cf);
669 }
670
671 void
curses_form_scroll_to(struct curses_form * cf,unsigned int x_off,unsigned int y_off)672 curses_form_scroll_to(struct curses_form *cf,
673 unsigned int x_off, unsigned int y_off)
674 {
675 cf->x_offset = x_off;
676 cf->y_offset = y_off;
677 }
678
679 void
curses_form_scroll_delta(struct curses_form * cf,int dx,int dy)680 curses_form_scroll_delta(struct curses_form *cf, int dx, int dy)
681 {
682 unsigned int x_off, y_off;
683
684 if (dx < 0 && (unsigned int)-dx > cf->x_offset) {
685 x_off = 0;
686 } else {
687 x_off = cf->x_offset + dx;
688 }
689 if (x_off > (cf->int_width - cf->width))
690 x_off = cf->int_width - cf->width;
691
692 if (dy < 0 && (unsigned int)-dy > cf->y_offset) {
693 y_off = 0;
694 } else {
695 y_off = cf->y_offset + dy;
696 }
697 if (y_off > (cf->int_height - cf->height))
698 y_off = cf->int_height - cf->height;
699
700 curses_form_scroll_to(cf, x_off, y_off);
701 }
702
703 static void
curses_form_refocus_after_scroll(struct curses_form * cf,int dx,int dy)704 curses_form_refocus_after_scroll(struct curses_form *cf, int dx, int dy)
705 {
706 struct curses_widget *w;
707
708 w = curses_form_widget_closest_on_row(cf,
709 cf->widget_focus->x + dx, cf->widget_focus->y + dy);
710
711 if (w != NULL) {
712 cf->widget_focus = w;
713 cf->want_x = w->x + w->width / 2;
714 cf->want_y = w->y;
715 }
716 }
717
718 int
curses_form_widget_is_visible(struct curses_widget * w)719 curses_form_widget_is_visible(struct curses_widget *w)
720 {
721 unsigned int wy;
722
723 wy = w->y + 1 - w->form->y_offset;
724
725 if (wy < 1 || wy > w->form->height)
726 return(0);
727
728 return(1);
729 }
730
731 void
curses_form_widget_ensure_visible(struct curses_widget * w)732 curses_form_widget_ensure_visible(struct curses_widget *w)
733 {
734 unsigned int wy;
735 int dx = 0, dy = 0;
736
737 /*
738 * If a textbox's offset is such that we can't see
739 * the cursor inside, adjust it.
740 */
741 if (w->type == CURSES_TEXTBOX) {
742 if (w->curpos - w->offset >= w->width - 2)
743 w->offset = w->curpos - (w->width - 3);
744 if (w->offset > w->curpos)
745 w->offset = w->curpos;
746 }
747
748 if (curses_form_widget_is_visible(w))
749 return;
750
751 wy = w->y + 1 - w->form->y_offset;
752
753 if (wy < 1)
754 dy = -1 * (1 - wy);
755 else if (wy > w->form->height)
756 dy = (wy - w->form->height);
757
758 curses_form_scroll_delta(w->form, dx, dy);
759 curses_form_draw(w->form);
760 curses_form_refresh(w->form);
761 }
762
763 static void
curses_form_show_help(const char * text)764 curses_form_show_help(const char *text)
765 {
766 struct curses_form *cf;
767 struct curses_widget *w;
768
769 cf = curses_form_new(_("Help"));
770
771 cf->height = curses_form_descriptive_labels_add(cf, text, 1, 1, 72);
772 cf->height += 1;
773 w = curses_form_widget_add(cf, 0, cf->height++, 0,
774 CURSES_BUTTON, _("OK"), 0, CURSES_WIDGET_WIDEN);
775 curses_widget_set_click_cb(w, cb_click_close_form);
776
777 curses_form_finalize(cf);
778
779 curses_form_draw(cf);
780 curses_form_refresh(cf);
781 curses_form_frob(cf);
782 curses_form_free(cf);
783 }
784
785 #define CTRL(c) (char)(c - 'a' + 1)
786
787 struct curses_widget *
curses_form_frob(struct curses_form * cf)788 curses_form_frob(struct curses_form *cf)
789 {
790 int key;
791
792 flushinp();
793 for (;;) {
794 key = getch();
795 switch(key) {
796 case KEY_DOWN:
797 case CTRL('n'):
798 curses_form_advance_row(cf);
799 break;
800 case KEY_UP:
801 case CTRL('p'):
802 curses_form_retreat_row(cf);
803 break;
804 case '\t':
805 curses_form_advance(cf);
806 break;
807 case KEY_RIGHT:
808 case CTRL('f'):
809 if (cf->widget_focus->type == CURSES_TEXTBOX) {
810 if (!curses_textbox_advance_char(cf->widget_focus))
811 curses_form_advance(cf);
812 } else
813 curses_form_advance(cf);
814 break;
815 case KEY_LEFT:
816 case CTRL('b'):
817 if (cf->widget_focus->type == CURSES_TEXTBOX) {
818 if (!curses_textbox_retreat_char(cf->widget_focus))
819 curses_form_retreat(cf);
820 } else
821 curses_form_retreat(cf);
822 break;
823 case '\n':
824 case '\r':
825 if (cf->widget_focus->type == CURSES_TEXTBOX) {
826 switch (curses_widget_click(cf->widget_focus)) {
827 case -1:
828 curses_form_advance(cf);
829 break;
830 case 0:
831 break;
832 case 1:
833 /* this would be pretty rare */
834 return(cf->widget_focus);
835 }
836 } else if (cf->widget_focus->type == CURSES_BUTTON) {
837 switch (curses_widget_click(cf->widget_focus)) {
838 case -1:
839 beep();
840 break;
841 case 0:
842 break;
843 case 1:
844 return(cf->widget_focus);
845 }
846 } else if (cf->widget_focus->type == CURSES_CHECKBOX) {
847 curses_checkbox_toggle(cf->widget_focus);
848 } else {
849 beep();
850 }
851 break;
852 case '\b':
853 case KEY_BACKSPACE:
854 case 127: /* why is this not KEY_BACKSPACE on xterm?? */
855 if (cf->widget_focus->type == CURSES_TEXTBOX) {
856 curses_textbox_backspace_char(cf->widget_focus);
857 } else {
858 beep();
859 }
860 break;
861 case KEY_DC:
862 case CTRL('k'):
863 if (cf->widget_focus->type == CURSES_TEXTBOX) {
864 curses_textbox_delete_char(cf->widget_focus);
865 } else {
866 beep();
867 }
868 break;
869 case KEY_HOME:
870 case CTRL('a'):
871 if (cf->widget_focus->type == CURSES_TEXTBOX) {
872 curses_textbox_home(cf->widget_focus);
873 } else {
874 beep();
875 }
876 break;
877 case KEY_END:
878 case CTRL('e'):
879 if (cf->widget_focus->type == CURSES_TEXTBOX) {
880 curses_textbox_end(cf->widget_focus);
881 } else {
882 beep();
883 }
884 break;
885 case KEY_NPAGE:
886 case CTRL('g'):
887 curses_form_scroll_delta(cf, 0, cf->height - 1);
888 curses_form_refocus_after_scroll(cf, 0, cf->height - 1);
889 curses_form_draw(cf);
890 curses_form_refresh(cf);
891 break;
892 case KEY_PPAGE:
893 case CTRL('t'):
894 curses_form_scroll_delta(cf, 0, -1 * (cf->height - 1));
895 curses_form_refocus_after_scroll(cf, 0, -1 * (cf->height - 1));
896 curses_form_draw(cf);
897 curses_form_refresh(cf);
898 break;
899 case ' ':
900 if (cf->widget_focus->type == CURSES_TEXTBOX) {
901 /* XXX if non-editable, click it */
902 curses_textbox_insert_char(cf->widget_focus, ' ');
903 } else if (cf->widget_focus->type == CURSES_BUTTON) {
904 switch (curses_widget_click(cf->widget_focus)) {
905 case -1:
906 beep();
907 break;
908 case 0:
909 break;
910 case 1:
911 return(cf->widget_focus);
912 }
913 } else if (cf->widget_focus->type == CURSES_CHECKBOX) {
914 curses_checkbox_toggle(cf->widget_focus);
915 } else {
916 beep();
917 }
918 break;
919 case KEY_F(1): /* why does this not work in xterm??? */
920 case CTRL('w'):
921 if (cf->help_text != NULL) {
922 curses_form_show_help(cf->help_text);
923 curses_form_refresh(cf);
924 }
925 break;
926 case KEY_F(10):
927 case CTRL('l'):
928 redrawwin(stdscr);
929 curses_form_refresh(NULL);
930 break;
931 default:
932 if (isprint(key) && cf->widget_focus->type == CURSES_TEXTBOX) {
933 curses_textbox_insert_char(cf->widget_focus, (char)key);
934 } else {
935 struct curses_widget *cw;
936
937 for (cw = cf->widget_head; cw != NULL; cw = cw->next) {
938 if (toupper(key) == cw->accel) {
939 /*
940 * To just refocus:
941 */
942 /*
943 cf->widget_focus = cw;
944 curses_form_widget_ensure_visible(cw);
945 curses_form_draw(cf);
946 curses_form_refresh(cf);
947 */
948 /*
949 * To actually activate:
950 */
951 switch (curses_widget_click(cw)) {
952 case -1:
953 beep();
954 break;
955 case 0:
956 break;
957 case 1:
958 return(cw);
959 }
960
961 break;
962 }
963 }
964 #ifdef DEBUG
965 curses_debug_key(key);
966 #endif
967 beep();
968 }
969 break;
970 }
971 }
972 }
973
974 /*** GENERIC CALLBACKS ***/
975
976 /*
977 * Callback to give to curses_button_set_click_cb, for buttons
978 * that simply close the form they are in when they are clicked.
979 * These usually map to dfui actions.
980 */
981 int
cb_click_close_form(struct curses_widget * w __unused)982 cb_click_close_form(struct curses_widget *w __unused)
983 {
984 return(1);
985 }
986