/*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2021 Alfonso Sabato Siciliano * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include #include #ifdef PORTNCURSES #include #else #include #endif #include #include #include #include "bsddialog.h" #include "bsddialog_progressview.h" #include "lib_util.h" #include "bsddialog_theme.h" #define BARMARGIN 3 #define MINBARWIDTH 10 #define MINWIDTH (VBORDERS + MINBARWIDTH + BARMARGIN * 2) #define MINHEIGHT 7 /* without text */ /* "Bar": gauge - mixedgauge - rangebox - pause - progressview */ bool bsddialog_interruptprogview; bool bsddialog_abortprogview; int bsddialog_total_progview; extern struct bsddialog_theme t; static void draw_perc_bar(WINDOW *win, int y, int x, int size, int perc, bool withlabel, int label) { char labelstr[128]; int i, blue_x, color; blue_x = (int)((perc*(size))/100); wmove(win, y, x); for (i = 0; i < size; i++) { color = (i <= blue_x) ? t.bar.f_color : t.bar.color; wattron(win, color); waddch(win, ' '); wattroff(win, color); } if (withlabel) sprintf(labelstr, "%d", label); else sprintf(labelstr, "%3d%%", perc); wmove(win, y, x + size/2 - 2); for (i=0; i < (int) strlen(labelstr); i++) { color = (blue_x + 1 <= size/2 - (int)strlen(labelstr)/2 + i ) ? t.bar.color : t.bar.f_color; wattron(win, color); waddch(win, labelstr[i]); wattroff(win, color); } } static int bar_autosize(struct bsddialog_conf *conf, int rows, int cols, int *h, int *w, char *text, struct buttons *bs) { int maxword, maxline, nlines, buttonswidth; if (get_text_properties(conf, text, &maxword, &maxline, &nlines) != 0) return BSDDIALOG_ERROR; buttonswidth = 0; if (bs != NULL) { /* gauge has not buttons */ buttonswidth= bs->nbuttons * bs->sizebutton; if (bs->nbuttons > 0) buttonswidth += (bs->nbuttons-1) * t.button.space; } if (cols == BSDDIALOG_AUTOSIZE) { *w = VBORDERS; /* buttons size */ *w += buttonswidth; /* bar size */ *w = MAX(*w, MINWIDTH); /* text size*/ *w = MAX((int)(maxline + VBORDERS + t.text.hmargin * 2), *w); /* conf.auto_minwidth */ *w = MAX(*w, (int)conf->auto_minwidth); /* avoid terminal overflow */ *w = MIN(*w, widget_max_width(conf)); } if (rows == BSDDIALOG_AUTOSIZE) { *h = MINHEIGHT; if (maxword > 0) *h += 1; /* conf.auto_minheight */ *h = MAX(*h, (int)conf->auto_minheight); /* avoid terminal overflow */ *h = MIN(*h, widget_max_height(conf)); } return (0); } static int bar_checksize(char *text, int rows, int cols, struct buttons *bs) { int minheight, minwidth; minwidth = 0; if (bs != NULL) { /* gauge has not buttons */ minwidth = bs->nbuttons * bs->sizebutton; if (bs->nbuttons > 0) minwidth += (bs->nbuttons-1) * t.button.space; } minwidth = MAX(minwidth + VBORDERS, MINBARWIDTH); if (cols< minwidth) RETURN_ERROR("Few cols for this widget"); minheight = MINHEIGHT + ((text != NULL && strlen(text) > 0) ? 1 : 0); if (rows < minheight) RETURN_ERROR("Few rows for this mixedgauge"); return 0; } int bsddialog_gauge(struct bsddialog_conf *conf, char* text, int rows, int cols, unsigned int perc) { WINDOW *widget, *textpad, *bar, *shadow; char input[2048], ntext[2048], *pntext; int y, x, h, w, htextpad; bool mainloop; if (set_widget_size(conf, rows, cols, &h, &w) != 0) return BSDDIALOG_ERROR; if (bar_autosize(conf, rows, cols, &h, &w, text, NULL) != 0) return BSDDIALOG_ERROR; if (bar_checksize(text, h, w, NULL) != 0) return BSDDIALOG_ERROR; if (set_widget_position(conf, &y, &x, h, w) != 0) return BSDDIALOG_ERROR; if (new_widget_withtextpad(conf, &shadow, &widget, y, x, h, w, RAISED, &textpad, &htextpad, text, false) != 0) return BSDDIALOG_ERROR; bar = new_boxed_window(conf, y+h-4, x+3, 3, w-6, RAISED); mainloop = true; while (mainloop) { wrefresh(widget); prefresh(textpad, 0, 0, y+1, x+1+t.text.hmargin, y+h-4, x+w-1-t.text.hmargin); draw_perc_bar(bar, 1, 1, w-8, perc, false, -1 /*unused*/); wrefresh(bar); while (true) { scanf("%s", input); if (strcmp(input,"EOF") == 0) { mainloop = false; break; } if (strcmp(input,"XXX") == 0) break; } scanf("%d", &perc); perc = perc < 0 ? 0 : perc; perc = perc > 100 ? 100 : perc; htextpad = 1; wclear(textpad); pntext = &ntext[0]; ntext[0] = '\0'; while (true) { scanf("%s", input); if (strcmp(input,"EOF") == 0) { mainloop = false; break; } if (strcmp(input,"XXX") == 0) break; pntext[0] = ' '; pntext++; strcpy(pntext, input); pntext += strlen(input); } print_textpad(conf, textpad, &htextpad, w-2-t.text.hmargin*2, ntext); } delwin(bar); end_widget_withtextpad(conf, widget, h, w, textpad, shadow); return BSDDIALOG_OK; } /* Mixedgauge */ static int mixedgauge(struct bsddialog_conf *conf, char* text, int rows, int cols, unsigned int mainperc, unsigned int nminibars, char **minilabels, int *minipercs, bool color) { WINDOW *widget, *textpad, *bar, *shadow; int i, output, miniperc, y, x, h, w, max_minbarlen; int maxword, maxline, nlines, htextpad, ypad; int colorperc, red, green; char states[12][14] = { " Succeeded ", /* 0 */ " Failed ", /* 1 */ " Passed ", /* 2 */ " Completed ", /* 3 */ " Checked ", /* 4 */ " Done ", /* 5 */ " Skipped ", /* 6 */ " In Progress ", /* 7 */ "(blank) ", /* 8 */ " N/A ", /* 9 */ " Pending ", /* 10 */ " UNKNOWN ", /* 10+ */ }; red = bsddialog_color(BSDDIALOG_WHITE,BSDDIALOG_RED, BSDDIALOG_BOLD); green = bsddialog_color(BSDDIALOG_WHITE,BSDDIALOG_GREEN,BSDDIALOG_BOLD); max_minbarlen = 0; for (i=0; i < (int)nminibars; i++) max_minbarlen = MAX(max_minbarlen, (int)strlen(minilabels[i])); max_minbarlen += 3 + 16 /* seps + [...] or mainbar */; if (set_widget_size(conf, rows, cols, &h, &w) != 0) return BSDDIALOG_ERROR; /* mixedgauge autosize */ if (get_text_properties(conf, text, &maxword, &maxline, &nlines) != 0) return BSDDIALOG_ERROR; if (cols == BSDDIALOG_AUTOSIZE) { w = max_minbarlen + HBORDERS; w = MAX(w, maxline + 4); w = MAX(w, (int)conf->auto_minwidth); w = MIN(w, widget_max_width(conf) - 1); } if (rows == BSDDIALOG_AUTOSIZE) { h = 5; /* borders + mainbar */ h += nminibars; h += (strlen(text) > 0 ? 3 : 0); h = MAX(h, (int)conf->auto_minheight); h = MIN(h, widget_max_height(conf) -1); } /* mixedgauge checksize */ if (w < max_minbarlen + 2) RETURN_ERROR("Few cols for this mixedgauge"); if (h < 5 + (int)nminibars + (strlen(text) > 0 ? 1 : 0)) RETURN_ERROR("Few rows for this mixedgauge"); if (set_widget_position(conf, &y, &x, h, w) != 0) return BSDDIALOG_ERROR; output = new_widget_withtextpad(conf, &shadow, &widget, y, x, h, w, RAISED, &textpad, &htextpad, text, false); if (output == BSDDIALOG_ERROR) return output; /* mini bars */ for (i=0; i < (int)nminibars; i++) { miniperc = minipercs[i]; if (miniperc == 8) continue; /* label */ if (color && (miniperc == 7 || miniperc < 0)) wattron(widget, A_BOLD); mvwaddstr(widget, i+1, 2, minilabels[i]); wattroff(widget, A_BOLD); /* perc */ if (miniperc > 10) mvwaddstr(widget, i+1, w-2-15, states[11]); else if (miniperc >= 0 && miniperc <= 10) { mvwaddstr(widget, i+1, w-2-15, "[ ]"); if (color && miniperc == 1) /* Failed */ colorperc = red; if (color && miniperc == 5) /* Done */ colorperc = green; if (color && (miniperc == 1 || miniperc == 5)) wattron(widget, colorperc); mvwaddstr(widget, i+1, 1+w-2-15, states[miniperc]); if (color && (miniperc == 1 || miniperc == 5)) wattroff(widget, colorperc); } else { /* miniperc < 0 */ miniperc = abs(miniperc); mvwaddstr(widget, i+1, w-2-15, "[ ]"); draw_perc_bar(widget, i+1, 1+w-2-15, 13, miniperc, false, -1 /*unused*/); } } wrefresh(widget); ypad = y + h - 5 - htextpad; ypad = ypad < y+(int)nminibars ? y+nminibars : ypad; prefresh(textpad, 0, 0, ypad, x+2, y+h-4, x+w-2); /* main bar */ bar = new_boxed_window(conf, y+h -4, x+3, 3, w-6, RAISED); draw_perc_bar(bar, 1, 1, w-8, mainperc, false, -1 /*unused*/); wattron(bar, t.bar.color); mvwaddstr(bar, 0, 2, "Overall Progress"); wattroff(bar, t.bar.color); wrefresh(bar); /* getch(); port ncurses shows nothing */ delwin(bar); end_widget_withtextpad(conf, widget, h, w, textpad, shadow); return BSDDIALOG_OK; } int bsddialog_mixedgauge(struct bsddialog_conf *conf, char* text, int rows, int cols, unsigned int mainperc, unsigned int nminibars, char **minilabels, int *minipercs) { int output; output = mixedgauge(conf, text, rows, cols, mainperc, nminibars, minilabels, minipercs, false); return (output); } int bsddialog_progressview (struct bsddialog_conf *conf, char * text, int rows, int cols, struct bsddialog_progviewconf *pvconf, unsigned int nminibar, struct bsddialog_fileminibar *minibar) { int perc, output; int *minipercs; unsigned int i; char **minilabels; unsigned int mainperc, totaltodo; time_t tstart, told, tnew, refresh; bool update; float readforsec; if ((minilabels = calloc(nminibar, sizeof(char*))) == NULL) RETURN_ERROR("Cannot allocate memory for minilabels\n"); if ((minipercs = calloc(nminibar, sizeof(int))) == NULL) RETURN_ERROR("Cannot allocate memory for minipercs\n"); totaltodo = 0; for(i=0; irefresh == 0 ? 0 : pvconf->refresh - 1; output = BSDDIALOG_OK; i = 0; update = true; time(&told); tstart = told; while (!(bsddialog_interruptprogview || bsddialog_abortprogview)) { if (bsddialog_total_progview == 0 || totaltodo == 0) mainperc = 0; else mainperc = (bsddialog_total_progview * 100) / totaltodo; time(&tnew); if (update || tnew > told + refresh) { output = mixedgauge(conf, text, rows, cols, mainperc, nminibar, minilabels, minipercs, true); if (output == BSDDIALOG_ERROR) return BSDDIALOG_ERROR; move(LINES-1, 2); clrtoeol(); readforsec = ((tnew - tstart) == 0) ? 0 : bsddialog_total_progview / (float)(tnew - tstart); printw(pvconf->fmtbottomstr, bsddialog_total_progview, readforsec); refresh(); time(&told); update = false; } if (i >= nminibar) break; if (minibar[i].status == 1) /* Failed*/ break; perc = pvconf->callback(&minibar[i]); if (minibar[i].status == 5) {/* ||prec >= 100) Done */ minipercs[i] = 5; update = true; i++; } else if (minibar[i].status == 1 || perc < 0) { /* Failed */ minipercs[i] = 1; update = true; } else if (perc == 0) minipercs[i] = 7; /* In progress */ else /* perc > 0 */ minipercs[i] = -(perc); } free(minilabels); free(minipercs); return (output); } int bsddialog_rangebox(struct bsddialog_conf *conf, char* text, int rows, int cols, int min, int max, int *value) { WINDOW *widget, *textpad, *bar, *shadow; int i, y, x, h, w, htextpad; bool loop, buttupdate, barupdate; int input, currvalue, output, sizebar, bigchange, positions; float perc; struct buttons bs; if (value == NULL) RETURN_ERROR("*value cannot be NULL"); if (min >= max) RETURN_ERROR("min >= max"); currvalue = *value; positions = max - min + 1; get_buttons(conf, &bs, BUTTONLABEL(ok_label), BUTTONLABEL(extra_label), BUTTONLABEL(cancel_label), BUTTONLABEL(help_label)); if (set_widget_size(conf, rows, cols, &h, &w) != 0) return BSDDIALOG_ERROR; if (bar_autosize(conf, rows, cols, &h, &w, text, &bs) != 0) return BSDDIALOG_ERROR; if (bar_checksize(text, h, w, &bs) != 0) return BSDDIALOG_ERROR; if (set_widget_position(conf, &y, &x, h, w) != 0) return BSDDIALOG_ERROR; if (new_widget_withtextpad(conf, &shadow, &widget, y, x, h, w, RAISED, &textpad, &htextpad, text, true) != 0) return BSDDIALOG_ERROR; prefresh(textpad, 0, 0, y+1, x+1+t.text.hmargin, y+h-7, x+w-1-t.text.hmargin); sizebar = w - HBORDERS - 2 - BARMARGIN * 2; bigchange = MAX(1, sizebar/10); bar = new_boxed_window(conf, y + h - 6, x + 1 + BARMARGIN, 3, sizebar + 2, RAISED); loop = buttupdate = barupdate = true; while(loop) { if (buttupdate) { draw_buttons(widget, h-2, w, bs, true); wrefresh(widget); buttupdate = false; } if (barupdate) { perc = ((float)(currvalue - min)*100) / (positions-1); draw_perc_bar(bar, 1, 1, sizebar, perc, true, currvalue); barupdate = false; wrefresh(bar); } input = getch(); switch(input) { case KEY_ENTER: case 10: /* Enter */ output = bs.value[bs.curr]; *value = currvalue; loop = false; break; case 27: /* Esc */ output = BSDDIALOG_ESC; loop = false; break; case '\t': /* TAB */ bs.curr = (bs.curr + 1) % bs.nbuttons; buttupdate = true; break; case KEY_LEFT: if (bs.curr > 0) { bs.curr--; buttupdate = true; } break; case KEY_RIGHT: if (bs.curr < (int) bs.nbuttons - 1) { bs.curr++; buttupdate = true; } break; case KEY_HOME: currvalue = max; barupdate = true; break; case KEY_END: currvalue = min; barupdate = true; break; case KEY_NPAGE: currvalue -= bigchange; if (currvalue < min) currvalue = min; barupdate = true; break; case KEY_PPAGE: currvalue += bigchange; if (currvalue > max) currvalue = max; barupdate = true; break; case KEY_UP: if (currvalue < max) { currvalue++; barupdate = true; } break; case KEY_DOWN: if (currvalue > min) { currvalue--; barupdate = true; } break; case KEY_F(1): if (conf->f1_file == NULL && conf->f1_message == NULL) break; if (f1help(conf) != 0) return BSDDIALOG_ERROR; /* No break! the terminal size can change */ case KEY_RESIZE: hide_widget(y, x, h, w,conf->shadow); /* * Unnecessary, but, when the columns decrease the * following "refresh" seem not work */ refresh(); if (set_widget_size(conf, rows, cols, &h, &w) != 0) return BSDDIALOG_ERROR; if (bar_autosize(conf, rows, cols, &h, &w, text, &bs) != 0) return BSDDIALOG_ERROR; if (bar_checksize(text, h, w, &bs) != 0) return BSDDIALOG_ERROR; if (set_widget_position(conf, &y, &x, h, w) != 0) return BSDDIALOG_ERROR; wclear(shadow); mvwin(shadow, y + t.shadow.h, x + t.shadow.w); wresize(shadow, h, w); wclear(widget); mvwin(widget, y, x); wresize(widget, h, w); htextpad = 1; wclear(textpad); wresize(textpad, 1, w - HBORDERS - t.text.hmargin * 2); sizebar = w - HBORDERS - 2 - BARMARGIN * 2; bigchange = MAX(1, sizebar/10); wclear(bar); mvwin(bar, y + h - 6, x + 1 + BARMARGIN); wresize(bar, 3, sizebar + 2); if(update_widget_withtextpad(conf, shadow, widget, h, w, RAISED, textpad, &htextpad, text, true) != 0) return BSDDIALOG_ERROR; prefresh(textpad, 0, 0, y+1, x+1+t.text.hmargin, y+h-7, x+w-1-t.text.hmargin); draw_borders(conf, bar, 3, sizebar + 2, RAISED); barupdate = true; buttupdate = true; break; default: for (i = 0; i < (int) bs.nbuttons; i++) if (tolower(input) == tolower((bs.label[i])[0])) { output = bs.value[i]; loop = false; } } } delwin(bar); end_widget_withtextpad(conf, widget, h, w, textpad, shadow); return output; } int bsddialog_pause(struct bsddialog_conf *conf, char* text, int rows, int cols, unsigned int sec) { WINDOW *widget, *textpad, *bar, *shadow; int i, output, y, x, h, w, htextpad; bool loop, buttupdate, barupdate; int input, tout, sizebar; float perc; struct buttons bs; get_buttons(conf, &bs, BUTTONLABEL(ok_label), BUTTONLABEL(extra_label), BUTTONLABEL(cancel_label), BUTTONLABEL(help_label)); if (set_widget_size(conf, rows, cols, &h, &w) != 0) return BSDDIALOG_ERROR; if (bar_autosize(conf, rows, cols, &h, &w, text, &bs) != 0) return BSDDIALOG_ERROR; if (bar_checksize(text, h, w, &bs) != 0) return BSDDIALOG_ERROR; if (set_widget_position(conf, &y, &x, h, w) != 0) return BSDDIALOG_ERROR; if (new_widget_withtextpad(conf, &shadow, &widget, y, x, h, w, RAISED, &textpad, &htextpad, text, true) != 0) return BSDDIALOG_ERROR; prefresh(textpad, 0, 0, y+1, x+1+t.text.hmargin, y+h-7, x+w-1-t.text.hmargin); sizebar = w - HBORDERS - 2 - BARMARGIN * 2; bar = new_boxed_window(conf, y + h - 6, x + 1 + BARMARGIN, 3, sizebar + 2, RAISED); tout = sec; nodelay(stdscr, TRUE); timeout(1000); loop = buttupdate = barupdate = true; while(loop) { if (barupdate) { perc = (float)tout * 100 / sec; draw_perc_bar(bar, 1, 1, sizebar, perc, true, tout); barupdate = false; wrefresh(bar); } if (buttupdate) { draw_buttons(widget, h-2, w, bs, true); wrefresh(widget); buttupdate = false; } input = getch(); if(input < 0) { /* timeout */ tout--; if (tout < 0) { output = BSDDIALOG_TIMEOUT; break; } else { barupdate = true; continue; } } switch(input) { case KEY_ENTER: case 10: /* Enter */ output = bs.value[bs.curr]; loop = false; break; case 27: /* Esc */ output = BSDDIALOG_ESC; loop = false; break; case '\t': /* TAB */ bs.curr = (bs.curr + 1) % bs.nbuttons; buttupdate = true; break; case KEY_LEFT: if (bs.curr > 0) { bs.curr--; buttupdate = true; } break; case KEY_RIGHT: if (bs.curr < (int) bs.nbuttons - 1) { bs.curr++; buttupdate = true; } break; case KEY_F(1): if (conf->f1_file == NULL && conf->f1_message == NULL) break; if (f1help(conf) != 0) return BSDDIALOG_ERROR; /* No break! the terminal size can change */ case KEY_RESIZE: hide_widget(y, x, h, w,conf->shadow); /* * Unnecessary, but, when the columns decrease the * following "refresh" seem not work */ refresh(); if (set_widget_size(conf, rows, cols, &h, &w) != 0) return BSDDIALOG_ERROR; if (bar_autosize(conf, rows, cols, &h, &w, text, &bs) != 0) return BSDDIALOG_ERROR; if (bar_checksize(text, h, w, &bs) != 0) return BSDDIALOG_ERROR; if (set_widget_position(conf, &y, &x, h, w) != 0) return BSDDIALOG_ERROR; wclear(shadow); mvwin(shadow, y + t.shadow.h, x + t.shadow.w); wresize(shadow, h, w); wclear(widget); mvwin(widget, y, x); wresize(widget, h, w); htextpad = 1; wclear(textpad); wresize(textpad, 1, w - HBORDERS - t.text.hmargin * 2); sizebar = w - HBORDERS - 2 - BARMARGIN * 2; wclear(bar); mvwin(bar, y + h - 6, x + 1 + BARMARGIN); wresize(bar, 3, sizebar + 2); if(update_widget_withtextpad(conf, shadow, widget, h, w, RAISED, textpad, &htextpad, text, true) != 0) return BSDDIALOG_ERROR; prefresh(textpad, 0, 0, y+1, x+1+t.text.hmargin, y+h-7, x+w-1-t.text.hmargin); draw_borders(conf, bar, 3, sizebar + 2, RAISED); barupdate = true; buttupdate = true; break; default: for (i = 0; i < (int) bs.nbuttons; i++) if (tolower(input) == tolower((bs.label[i])[0])) { output = bs.value[i]; loop = false; } } } nodelay(stdscr, FALSE); delwin(bar); end_widget_withtextpad(conf, widget, h, w, textpad, shadow); return output; }