1 /*- 2 * SPDX-License-Identifier: BSD-2-Clause 3 * 4 * Copyright (c) 2021-2023 Alfonso Sabato Siciliano 5 * 6 * Redistribution and use in source and binary forms, with or without 7 * modification, are permitted provided that the following conditions 8 * are met: 9 * 1. Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * 2. Redistributions in binary form must reproduce the above copyright 12 * notice, this list of conditions and the following disclaimer in the 13 * documentation and/or other materials provided with the distribution. 14 * 15 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 16 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 19 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 21 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 22 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 23 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 24 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 25 * SUCH DAMAGE. 26 */ 27 28 #include <curses.h> 29 30 #include "bsddialog.h" 31 #include "bsddialog_theme.h" 32 #include "lib_util.h" 33 34 struct scrolltext { 35 WINDOW *pad; 36 int ypad; 37 int xpad; 38 int ys; 39 int ye; 40 int xs; 41 int xe; 42 int hpad; 43 int wpad; 44 int margin; /* 2 with multicolumn char, 0 otherwise */ 45 int printrows; /* d.h - BORDERS - HBUTTONS */ 46 }; 47 48 static void updateborders(struct dialog *d, struct scrolltext *st) 49 { 50 chtype arrowch, borderch; 51 52 if (d->conf->no_lines) 53 borderch = ' '; 54 else if (d->conf->ascii_lines) 55 borderch = '|'; 56 else 57 borderch = ACS_VLINE; 58 59 if (st->xpad > 0) { 60 arrowch = d->conf->ascii_lines ? '<' : ACS_LARROW; 61 arrowch |= t.dialog.arrowcolor; 62 } else { 63 arrowch = borderch; 64 arrowch |= t.dialog.lineraisecolor; 65 } 66 mvwvline(d->widget, (d->h / 2) - 2, 0, arrowch, 4); 67 68 if (st->xpad + d->w - 2 - st->margin < st->wpad) { 69 arrowch = d->conf->ascii_lines ? '>' : ACS_RARROW; 70 arrowch |= t.dialog.arrowcolor; 71 } else { 72 arrowch = borderch; 73 arrowch |= t.dialog.linelowercolor; 74 } 75 mvwvline(d->widget, (d->h / 2) - 2, d->w - 1, arrowch, 4); 76 77 if (st->hpad > d->h - 4) { 78 wattron(d->widget, t.dialog.arrowcolor); 79 mvwprintw(d->widget, d->h - 3, d->w - 6, 80 "%3d%%", 100 * (st->ypad + d->h - 4) / st->hpad); 81 wattroff(d->widget, t.dialog.arrowcolor); 82 } 83 } 84 85 static int textbox_size_position(struct dialog *d, struct scrolltext *st) 86 { 87 int minw; 88 89 if (set_widget_size(d->conf, d->rows, d->cols, &d->h, &d->w) != 0) 90 return (BSDDIALOG_ERROR); 91 if (set_widget_autosize(d->conf, d->rows, d->cols, &d->h, &d->w, 92 d->text, NULL, &d->bs, st->hpad, st->wpad + st->margin) != 0) 93 return (BSDDIALOG_ERROR); 94 minw = (st->wpad > 0) ? 2 /*multicolumn char*/ + st->margin : 0 ; 95 if (widget_checksize(d->h, d->w, &d->bs, MIN(st->hpad, 1), minw) != 0) 96 return (BSDDIALOG_ERROR); 97 if (set_widget_position(d->conf, &d->y, &d->x, d->h, d->w) != 0) 98 return (BSDDIALOG_ERROR); 99 100 return (0); 101 } 102 103 static int textbox_draw(struct dialog *d, struct scrolltext *st) 104 { 105 if (d->built) { 106 hide_dialog(d); 107 refresh(); /* Important for decreasing screen */ 108 } 109 if (textbox_size_position(d, st) != 0) 110 return (BSDDIALOG_ERROR); 111 if (draw_dialog(d) != 0) 112 return (BSDDIALOG_ERROR); 113 if (d->built) 114 refresh(); /* Important to fix grey lines expanding screen */ 115 116 st->ys = d->y + 1; 117 st->xs = (st->margin == 0) ? d->x + 1 : d->x + 2; 118 st->ye = st->ys + d->h - 5; 119 st->xe = st->xs + d->w - 3 - st->margin; 120 st->ypad = st->xpad = 0; 121 st->printrows = d->h-4; 122 123 return (0); 124 } 125 126 /* API */ 127 int 128 bsddialog_textbox(struct bsddialog_conf *conf, const char *file, int rows, 129 int cols) 130 { 131 bool loop, has_multicol_ch; 132 int i, retval; 133 unsigned int defaulttablen, linecols; 134 wint_t input; 135 char buf[BUFSIZ]; 136 FILE *fp; 137 struct scrolltext st; 138 struct dialog d; 139 140 if (file == NULL) 141 RETURN_ERROR("*file is NULL"); 142 if ((fp = fopen(file, "r")) == NULL) 143 RETURN_FMTERROR("Cannot open file \"%s\"", file); 144 145 if (prepare_dialog(conf, "" /* fake */, rows, cols, &d) != 0) 146 return (BSDDIALOG_ERROR); 147 set_buttons(&d, true, "EXIT", NULL); 148 149 defaulttablen = TABSIZE; 150 if (conf->text.tablen > 0) 151 set_tabsize(conf->text.tablen); 152 st.hpad = 1; 153 st.wpad = 1; 154 st.pad = newpad(st.hpad, st.wpad); 155 wbkgd(st.pad, t.dialog.color); 156 st.margin = 0; 157 i = 0; 158 while (fgets(buf, BUFSIZ, fp) != NULL) { 159 if (str_props(buf, &linecols, &has_multicol_ch) != 0) 160 continue; 161 if ((int)linecols > st.wpad) { 162 st.wpad = linecols; 163 wresize(st.pad, st.hpad, st.wpad); 164 } 165 if (i > st.hpad-1) { 166 st.hpad++; 167 wresize(st.pad, st.hpad, st.wpad); 168 } 169 mvwaddstr(st.pad, i, 0, buf); 170 i++; 171 if (has_multicol_ch) 172 st.margin = 2; 173 } 174 fclose(fp); 175 set_tabsize(defaulttablen); /* reset because it is curses global */ 176 177 if (textbox_draw(&d, &st) != 0) 178 return (BSDDIALOG_ERROR); 179 180 loop = true; 181 while (loop) { 182 updateborders(&d, &st); 183 /* 184 * Overflow multicolumn charchter right border: 185 * wnoutrefresh(widget); 186 * pnoutrefresh(pad, ypad, xpad, ys, xs, ye, xe); 187 * doupdate(); 188 */ 189 wrefresh(d.widget); 190 prefresh(st.pad, st.ypad, st.xpad, st.ys, st.xs, st.ye, st.xe); 191 if (get_wch(&input) == ERR) 192 continue; 193 if (shortcut_buttons(input, &d.bs)) { 194 DRAW_BUTTONS(d); 195 doupdate(); 196 retval = BUTTONVALUE(d.bs); 197 break; /* loop */ 198 } 199 switch(input) { 200 case KEY_ENTER: 201 case 10: /* Enter */ 202 retval = BSDDIALOG_OK; 203 loop = false; 204 break; 205 case 27: /* Esc */ 206 if (conf->key.enable_esc) { 207 retval = BSDDIALOG_ESC; 208 loop = false; 209 } 210 break; 211 case '\t': /* TAB */ 212 d.bs.curr = (d.bs.curr + 1) % d.bs.nbuttons; 213 DRAW_BUTTONS(d); 214 break; 215 case KEY_HOME: 216 st.ypad = 0; 217 break; 218 case KEY_END: 219 st.ypad = MAX(st.hpad - st.printrows, 0); 220 break; 221 case KEY_PPAGE: 222 st.ypad = MAX(st.ypad - st.printrows, 0); 223 break; 224 case KEY_NPAGE: 225 st.ypad += st.printrows; 226 if (st.ypad + st.printrows > st.hpad) 227 st.ypad = st.hpad - st.printrows; 228 break; 229 case '0': 230 st.xpad = 0; 231 break; 232 case KEY_LEFT: 233 case 'h': 234 st.xpad = MAX(st.xpad - 1, 0); 235 break; 236 case KEY_RIGHT: 237 case 'l': 238 if (st.xpad + d.w - 2 - st.margin < st.wpad) 239 st.xpad++; 240 break; 241 case KEY_UP: 242 case 'k': 243 st.ypad = MAX(st.ypad - 1, 0); 244 break; 245 case KEY_DOWN: 246 case'j': 247 if (st.ypad + st.printrows <= st.hpad -1) 248 st.ypad++; 249 break; 250 case KEY_F(1): 251 if (conf->key.f1_file == NULL && 252 conf->key.f1_message == NULL) 253 break; 254 if (f1help_dialog(conf) != 0) 255 return (BSDDIALOG_ERROR); 256 if (textbox_draw(&d, &st) != 0) 257 return (BSDDIALOG_ERROR); 258 break; 259 case KEY_RESIZE: 260 if (textbox_draw(&d, &st) != 0) 261 return (BSDDIALOG_ERROR); 262 break; 263 } 264 } 265 266 delwin(st.pad); 267 end_dialog(&d); 268 269 return (retval); 270 } 271