xref: /freebsd/contrib/dialog/progressbox.c (revision c66ec88fed842fbaad62c30d510644ceb7bd2d71)
1 /*
2  *  $Id: progressbox.c,v 1.47 2018/06/21 09:14:47 tom Exp $
3  *
4  *  progressbox.c -- implements the progress box
5  *
6  *  Copyright 2006-2014,2018	Thomas E. Dickey
7  *  Copyright 2005		Valery Reznic
8  *
9  *  This program is free software; you can redistribute it and/or modify
10  *  it under the terms of the GNU Lesser General Public License as
11  *  published by the Free Software Foundation; either version 2.1 of the
12  *  License, or (at your option) any later version.
13  *
14  *  This program is distributed in the hope that it will be useful, but
15  *  WITHOUT ANY WARRANTY; without even the implied warranty of
16  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17  *  Lesser General Public License for more details.
18  *
19  *  You should have received a copy of the GNU Lesser General Public
20  *  License along with this program; if not, write to
21  *	Free Software Foundation, Inc.
22  *	51 Franklin St., Fifth Floor
23  *	Boston, MA 02110, USA.
24  */
25 
26 #include <dialog.h>
27 #include <dlg_keys.h>
28 
29 #ifdef KEY_RESIZE
30 #include <errno.h>
31 #endif
32 
33 #define MIN_HIGH (4)
34 #define MIN_WIDE (10 + 2 * (2 + MARGIN))
35 
36 #ifdef KEY_RESIZE
37 typedef struct _wrote {
38     struct _wrote *link;
39     char *text;
40 } WROTE;
41 #endif
42 
43 typedef struct {
44     DIALOG_CALLBACK obj;
45     WINDOW *text;
46     char *prompt;
47     int high, wide;
48     int old_high, old_wide;
49     char line[MAX_LEN + 1];
50     int is_eof;
51 #ifdef KEY_RESIZE
52     WROTE *wrote;
53 #endif
54 } MY_OBJ;
55 
56 static void
57 free_obj(MY_OBJ * obj)
58 {
59     dlg_del_window(obj->obj.win);
60     free(obj->prompt);
61 #ifdef KEY_RESIZE
62     while (obj->wrote) {
63 	WROTE *wrote = obj->wrote;
64 	obj->wrote = wrote->link;
65 	free(wrote->text);
66 	free(wrote);
67     }
68 #endif
69     free(obj);
70 }
71 
72 static void
73 restart_obj(MY_OBJ * obj)
74 {
75     free(obj->prompt);
76     obj->high = obj->old_high;
77     obj->wide = obj->old_wide;
78     dlg_clear();
79     dlg_del_window(obj->obj.win);
80 }
81 
82 static void
83 start_obj(MY_OBJ * obj, const char *title, const char *cprompt)
84 {
85     int y, x, thigh;
86     int i;
87 
88     obj->prompt = dlg_strclone(cprompt);
89     dlg_tab_correct_str(obj->prompt);
90     dlg_auto_size(title, obj->prompt, &obj->high, &obj->wide, MIN_HIGH, MIN_WIDE);
91 
92     dlg_print_size(obj->high, obj->wide);
93     dlg_ctl_size(obj->high, obj->wide);
94 
95     x = dlg_box_x_ordinate(obj->wide);
96     y = dlg_box_y_ordinate(obj->high);
97     thigh = obj->high - (2 * MARGIN);
98 
99     obj->obj.win = dlg_new_window(obj->high, obj->wide, y, x);
100 
101     dlg_draw_box2(obj->obj.win,
102 		  0, 0,
103 		  obj->high, obj->wide,
104 		  dialog_attr,
105 		  border_attr,
106 		  border2_attr);
107     dlg_draw_title(obj->obj.win, title);
108     dlg_draw_helpline(obj->obj.win, FALSE);
109 
110     if (obj->prompt[0] != '\0') {
111 	int y2, x2;
112 
113 	dlg_attrset(obj->obj.win, dialog_attr);
114 	dlg_print_autowrap(obj->obj.win, obj->prompt, obj->high, obj->wide);
115 	getyx(obj->obj.win, y2, x2);
116 	(void) x2;
117 	++y2;
118 	wmove(obj->obj.win, y2, MARGIN);
119 	for (i = 0; i < getmaxx(obj->obj.win) - 2 * MARGIN; i++)
120 	    (void) waddch(obj->obj.win, dlg_boxchar(ACS_HLINE));
121 	y += y2;
122 	thigh -= y2;
123     }
124 
125     /* Create window for text region, used for scrolling text */
126     obj->text = dlg_sub_window(obj->obj.win,
127 			       thigh,
128 			       obj->wide - (2 * MARGIN),
129 			       y + MARGIN,
130 			       x + MARGIN);
131 
132     (void) wrefresh(obj->obj.win);
133 
134     (void) wmove(obj->obj.win, getmaxy(obj->text), (MARGIN + 1));
135     (void) wnoutrefresh(obj->obj.win);
136 
137     dlg_attr_clear(obj->text, getmaxy(obj->text), getmaxx(obj->text), dialog_attr);
138 }
139 
140 /*
141  * Return current line of text.
142  */
143 static char *
144 get_line(MY_OBJ * obj, int *restart)
145 {
146     FILE *fp = obj->obj.input;
147     int col = 0;
148     int j, tmpint, ch;
149     char *result = obj->line;
150 
151     *restart = 0;
152     for (;;) {
153 	ch = getc(fp);
154 #ifdef KEY_RESIZE
155 	/* SIGWINCH may have interrupted this - try to ignore if resizable */
156 	if (ferror(fp)) {
157 	    switch (errno) {
158 	    case EINTR:
159 		clearerr(fp);
160 		continue;
161 	    default:
162 		break;
163 	    }
164 	}
165 #endif
166 	if (feof(fp) || ferror(fp)) {
167 	    obj->is_eof = 1;
168 	    if (!col) {
169 		result = NULL;
170 	    }
171 	    break;
172 	}
173 	if (ch == '\n')
174 	    break;
175 	if (ch == '\r')
176 	    break;
177 	if (col >= MAX_LEN)
178 	    continue;
179 	if ((ch == TAB) && (dialog_vars.tab_correct)) {
180 	    tmpint = dialog_state.tab_len
181 		- (col % dialog_state.tab_len);
182 	    for (j = 0; j < tmpint; j++) {
183 		if (col < MAX_LEN) {
184 		    obj->line[col] = ' ';
185 		    ++col;
186 		} else {
187 		    break;
188 		}
189 	    }
190 	} else {
191 	    obj->line[col] = (char) ch;
192 	    ++col;
193 	}
194     }
195 
196     obj->line[col] = '\0';
197 
198 #ifdef KEY_RESIZE
199     if (result != NULL) {
200 	WINDOW *win = obj->text;
201 	WROTE *wrote = dlg_calloc(WROTE, 1);
202 
203 	if (wrote != 0) {
204 	    wrote->text = dlg_strclone(obj->line);
205 	    wrote->link = obj->wrote;
206 	    obj->wrote = wrote;
207 	}
208 
209 	nodelay(win, TRUE);
210 	if ((ch = wgetch(win)) == KEY_RESIZE) {
211 	    *restart = 1;
212 	}
213 	nodelay(win, FALSE);
214     }
215 #endif
216     return result;
217 }
218 
219 /*
220  * Print a new line of text.
221  */
222 static void
223 print_line(MY_OBJ * obj, const char *line, int row)
224 {
225     int width = obj->wide - (2 * MARGIN);
226     int limit = MIN((int) strlen(line), width - 2);
227 
228     (void) wmove(obj->text, row, 0);	/* move cursor to correct line */
229     wprintw(obj->text, " %.*s", limit, line);
230     wclrtoeol(obj->text);
231 }
232 
233 #ifdef KEY_RESIZE
234 static int
235 wrote_size(MY_OBJ * obj, int want)
236 {
237     int result = 0;
238     WROTE *wrote = obj->wrote;
239     while (wrote != NULL && want > 0) {
240 	wrote = wrote->link;
241 	want--;
242 	result++;
243     }
244     return result;
245 }
246 
247 static const char *
248 wrote_data(MY_OBJ * obj, int want)
249 {
250     const char *result = NULL;
251     WROTE *wrote = obj->wrote;
252     while (wrote != NULL && want > 0) {
253 	result = wrote->text;
254 	wrote = wrote->link;
255 	want--;
256     }
257     return result;
258 }
259 
260 static int
261 reprint_lines(MY_OBJ * obj, int buttons)
262 {
263     int want = getmaxy(obj->text) - (buttons ? 2 : 0);
264     int have = wrote_size(obj, want);
265     int n;
266     for (n = 0; n < have; ++n) {
267 	print_line(obj, wrote_data(obj, have - n), n);
268     }
269     (void) wrefresh(obj->text);
270     return have;
271 }
272 #endif
273 
274 static int
275 pause_for_ok(MY_OBJ * obj, const char *title, const char *cprompt)
276 {
277     /* *INDENT-OFF* */
278     static DLG_KEYS_BINDING binding[] = {
279 	HELPKEY_BINDINGS,
280 	ENTERKEY_BINDINGS,
281 	TRAVERSE_BINDINGS,
282 	END_KEYS_BINDING
283     };
284     /* *INDENT-ON* */
285 
286     int button;
287     int key = 0, fkey;
288     int result = DLG_EXIT_UNKNOWN;
289     const char **buttons = dlg_ok_label();
290     int check;
291     bool save_nocancel = dialog_vars.nocancel;
292     bool redraw = TRUE;
293 
294     dialog_vars.nocancel = TRUE;
295     button = dlg_default_button();
296 
297 #ifdef KEY_RESIZE
298   restart:
299 #endif
300 
301     dlg_register_window(obj->obj.win, "progressbox", binding);
302     dlg_register_buttons(obj->obj.win, "progressbox", buttons);
303 
304     dlg_draw_bottom_box2(obj->obj.win, border_attr, border2_attr, dialog_attr);
305 
306     while (result == DLG_EXIT_UNKNOWN) {
307 	if (redraw) {
308 	    redraw = FALSE;
309 	    if (button < 0)
310 		button = 0;
311 	    dlg_draw_buttons(obj->obj.win,
312 			     obj->high - 2, 0,
313 			     buttons, button,
314 			     FALSE, obj->wide);
315 	}
316 
317 	key = dlg_mouse_wgetch(obj->obj.win, &fkey);
318 	if (dlg_result_key(key, fkey, &result))
319 	    break;
320 
321 	if (!fkey && (check = dlg_char_to_button(key, buttons)) >= 0) {
322 	    result = dlg_ok_buttoncode(check);
323 	    break;
324 	}
325 
326 	if (fkey) {
327 	    switch (key) {
328 	    case DLGK_FIELD_NEXT:
329 		button = dlg_next_button(buttons, button);
330 		redraw = TRUE;
331 		break;
332 	    case DLGK_FIELD_PREV:
333 		button = dlg_prev_button(buttons, button);
334 		redraw = TRUE;
335 		break;
336 	    case DLGK_ENTER:
337 		result = dlg_ok_buttoncode(button);
338 		break;
339 #ifdef KEY_RESIZE
340 	    case KEY_RESIZE:
341 		dlg_will_resize(obj->obj.win);
342 		restart_obj(obj);
343 		start_obj(obj, title, cprompt);
344 		reprint_lines(obj, TRUE);
345 		redraw = TRUE;
346 		goto restart;
347 #endif
348 	    default:
349 		if (is_DLGK_MOUSE(key)) {
350 		    result = dlg_ok_buttoncode(key - M_EVENT);
351 		    if (result < 0)
352 			result = DLG_EXIT_OK;
353 		} else {
354 		    beep();
355 		}
356 		break;
357 	    }
358 
359 	} else {
360 	    beep();
361 	}
362     }
363     dlg_mouse_free_regions();
364     dlg_unregister_window(obj->obj.win);
365 
366     dialog_vars.nocancel = save_nocancel;
367     return result;
368 }
369 
370 int
371 dlg_progressbox(const char *title,
372 		const char *cprompt,
373 		int height,
374 		int width,
375 		int pauseopt,
376 		FILE *fp)
377 {
378     int i;
379     MY_OBJ *obj;
380     int again = 0;
381     int toprow = 0;
382     int result;
383 
384     DLG_TRACE(("# progressbox args:\n"));
385     DLG_TRACE2S("title", title);
386     DLG_TRACE2S("message", cprompt);
387     DLG_TRACE2N("height", height);
388     DLG_TRACE2N("width", width);
389     DLG_TRACE2N("pause", pauseopt);
390     DLG_TRACE2N("fp", fp ? fileno(fp) : -1);
391 
392     obj = dlg_calloc(MY_OBJ, 1);
393     assert_ptr(obj, "dlg_progressbox");
394     obj->obj.input = fp;
395 
396     obj->high = height;
397     obj->wide = width;
398 
399 #ifdef KEY_RESIZE
400     obj->old_high = height;
401     obj->old_wide = width;
402 
403     curs_set(0);
404   restart:
405 #endif
406 
407     start_obj(obj, title, cprompt);
408 #ifdef KEY_RESIZE
409     if (again) {
410 	toprow = reprint_lines(obj, FALSE);
411     }
412 #endif
413 
414     for (i = toprow; get_line(obj, &again); i++) {
415 #ifdef KEY_RESIZE
416 	if (again) {
417 	    dlg_will_resize(obj->obj.win);
418 	    restart_obj(obj);
419 	    goto restart;
420 	}
421 #endif
422 	if (i < getmaxy(obj->text)) {
423 	    print_line(obj, obj->line, i);
424 	} else {
425 	    scrollok(obj->text, TRUE);
426 	    scroll(obj->text);
427 	    scrollok(obj->text, FALSE);
428 	    print_line(obj, obj->line, getmaxy(obj->text) - 1);
429 	}
430 	(void) wrefresh(obj->text);
431 	if (obj->is_eof)
432 	    break;
433     }
434 
435     dlg_trace_win(obj->obj.win);
436     curs_set(1);
437 
438     if (pauseopt) {
439 	int need = 1 + MARGIN;
440 	int base = getmaxy(obj->text) - need;
441 	if (i >= base) {
442 	    i -= base;
443 	    if (i > need)
444 		i = need;
445 	    if (i > 0) {
446 		scrollok(obj->text, TRUE);
447 	    }
448 	    wscrl(obj->text, i);
449 	}
450 	(void) wrefresh(obj->text);
451 	result = pause_for_ok(obj, title, cprompt);
452     } else {
453 	wrefresh(obj->obj.win);
454 	result = DLG_EXIT_OK;
455     }
456 
457     free_obj(obj);
458 
459     return result;
460 }
461 
462 /*
463  * Display text from a stdin in a scrolling window.
464  */
465 int
466 dialog_progressbox(const char *title, const char *cprompt, int height, int width)
467 {
468     int result;
469     result = dlg_progressbox(title,
470 			     cprompt,
471 			     height,
472 			     width,
473 			     FALSE,
474 			     dialog_state.pipe_input);
475     dialog_state.pipe_input = 0;
476     return result;
477 }
478