1 /*- 2 * SPDX-License-Identifier: BSD-2-Clause 3 * 4 * Copyright (c) 2021-2024 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; 51 cchar_t borderch; 52 53 if (d->conf->no_lines) 54 setcchar(&borderch, L" ", 0, 0, NULL); 55 else if (d->conf->ascii_lines) 56 setcchar(&borderch, L"|", 0, 0, NULL); 57 else 58 borderch = *WACS_VLINE; 59 60 if (st->xpad > 0) { 61 arrowch = LARROW(d->conf) | t.dialog.arrowcolor; 62 mvwvline(d->widget, (d->h / 2) - 2, 0, arrowch, 4); 63 } else { 64 wattron(d->widget, t.dialog.lineraisecolor); 65 mvwvline_set(d->widget, (d->h / 2) - 2, 0, &borderch, 4); 66 wattroff(d->widget, t.dialog.lineraisecolor); 67 } 68 69 if (st->xpad + d->w - 2 - st->margin < st->wpad) { 70 arrowch = RARROW(d->conf) | t.dialog.arrowcolor; 71 mvwvline(d->widget, (d->h / 2) - 2, d->w - 1, arrowch, 4); 72 } else { 73 wattron(d->widget, t.dialog.linelowercolor); 74 mvwvline_set(d->widget, (d->h / 2) - 2, d->w - 1, &borderch, 4); 75 wattroff(d->widget, t.dialog.linelowercolor); 76 } 77 78 if (st->hpad > d->h - 4) { 79 wattron(d->widget, t.dialog.arrowcolor); 80 mvwprintw(d->widget, d->h - 3, d->w - 6, 81 "%3d%%", 100 * (st->ypad + d->h - 4) / st->hpad); 82 wattroff(d->widget, t.dialog.arrowcolor); 83 } 84 } 85 86 static int textbox_size_position(struct dialog *d, struct scrolltext *st) 87 { 88 int minw; 89 90 if (set_widget_size(d->conf, d->rows, d->cols, &d->h, &d->w) != 0) 91 return (BSDDIALOG_ERROR); 92 if (set_widget_autosize(d->conf, d->rows, d->cols, &d->h, &d->w, 93 d->text, NULL, &d->bs, st->hpad, st->wpad + st->margin) != 0) 94 return (BSDDIALOG_ERROR); 95 minw = (st->wpad > 0) ? 2 /*multicolumn char*/ + st->margin : 0 ; 96 if (widget_checksize(d->h, d->w, &d->bs, MIN(st->hpad, 1), minw) != 0) 97 return (BSDDIALOG_ERROR); 98 if (set_widget_position(d->conf, &d->y, &d->x, d->h, d->w) != 0) 99 return (BSDDIALOG_ERROR); 100 101 return (0); 102 } 103 104 static int textbox_draw(struct dialog *d, struct scrolltext *st) 105 { 106 if (d->built) { 107 hide_dialog(d); 108 refresh(); /* Important for decreasing screen */ 109 } 110 if (textbox_size_position(d, st) != 0) 111 return (BSDDIALOG_ERROR); 112 if (draw_dialog(d) != 0) 113 return (BSDDIALOG_ERROR); 114 if (d->built) 115 refresh(); /* Important to fix grey lines expanding screen */ 116 117 st->ys = d->y + 1; 118 st->xs = (st->margin == 0) ? d->x + 1 : d->x + 2; 119 st->ye = st->ys + d->h - 5; 120 st->xe = st->xs + d->w - 3 - st->margin; 121 st->ypad = st->xpad = 0; 122 st->printrows = d->h-4; 123 124 return (0); 125 } 126 127 /* API */ 128 int 129 bsddialog_textbox(struct bsddialog_conf *conf, const char *file, int rows, 130 int cols) 131 { 132 bool loop, has_multicol_ch; 133 int i, retval; 134 unsigned int defaulttablen, linecols; 135 wint_t input; 136 char buf[BUFSIZ]; 137 FILE *fp; 138 struct scrolltext st; 139 struct dialog d; 140 141 if (file == NULL) 142 RETURN_ERROR("*file is NULL"); 143 if ((fp = fopen(file, "r")) == NULL) 144 RETURN_FMTERROR("Cannot open file \"%s\"", file); 145 146 if (prepare_dialog(conf, "" /* fake */, rows, cols, &d) != 0) 147 return (BSDDIALOG_ERROR); 148 set_buttons(&d, true, "EXIT", NULL); 149 150 defaulttablen = TABSIZE; 151 if (conf->text.tablen > 0) 152 set_tabsize(conf->text.tablen); 153 st.hpad = 1; 154 st.wpad = 1; 155 st.pad = newpad(st.hpad, st.wpad); 156 wbkgd(st.pad, t.dialog.color); 157 st.margin = 0; 158 i = 0; 159 while (fgets(buf, BUFSIZ, fp) != NULL) { 160 if (str_props(buf, &linecols, &has_multicol_ch) != 0) 161 continue; 162 if ((int)linecols > st.wpad) { 163 st.wpad = linecols; 164 wresize(st.pad, st.hpad, st.wpad); 165 } 166 if (i > st.hpad-1) { 167 st.hpad++; 168 wresize(st.pad, st.hpad, st.wpad); 169 } 170 mvwaddstr(st.pad, i, 0, buf); 171 i++; 172 if (has_multicol_ch) 173 st.margin = 2; 174 } 175 fclose(fp); 176 set_tabsize(defaulttablen); /* reset because it is curses global */ 177 178 if (textbox_draw(&d, &st) != 0) 179 return (BSDDIALOG_ERROR); 180 181 loop = true; 182 while (loop) { 183 updateborders(&d, &st); 184 /* 185 * Trick, overflow multicolumn charchter right border: 186 * wnoutrefresh(widget); 187 * pnoutrefresh(pad, ypad, xpad, ys, xs, ye, xe); 188 * doupdate(); 189 */ 190 wrefresh(d.widget); 191 prefresh(st.pad, st.ypad, st.xpad, st.ys, st.xs, st.ye, st.xe); 192 if (get_wch(&input) == ERR) 193 continue; 194 if (shortcut_buttons(input, &d.bs)) { 195 DRAW_BUTTONS(d); 196 doupdate(); 197 retval = BUTTONVALUE(d.bs); 198 break; /* loop */ 199 } 200 switch(input) { 201 case KEY_ENTER: 202 case 10: /* Enter */ 203 retval = BSDDIALOG_OK; 204 loop = false; 205 break; 206 case 27: /* Esc */ 207 if (conf->key.enable_esc) { 208 retval = BSDDIALOG_ESC; 209 loop = false; 210 } 211 break; 212 case '\t': /* TAB */ 213 d.bs.curr = (d.bs.curr + 1) % d.bs.nbuttons; 214 DRAW_BUTTONS(d); 215 break; 216 case KEY_HOME: 217 st.ypad = 0; 218 break; 219 case KEY_END: 220 st.ypad = MAX(st.hpad - st.printrows, 0); 221 break; 222 case KEY_PPAGE: 223 st.ypad = MAX(st.ypad - st.printrows, 0); 224 break; 225 case KEY_NPAGE: 226 st.ypad += st.printrows; 227 if (st.ypad + st.printrows > st.hpad) 228 st.ypad = st.hpad - st.printrows; 229 break; 230 case '0': 231 st.xpad = 0; 232 break; 233 case KEY_LEFT: 234 case 'h': 235 st.xpad = MAX(st.xpad - 1, 0); 236 break; 237 case KEY_RIGHT: 238 case 'l': 239 if (st.xpad + d.w - 2 - st.margin < st.wpad) 240 st.xpad++; 241 break; 242 case KEY_UP: 243 case 'k': 244 st.ypad = MAX(st.ypad - 1, 0); 245 break; 246 case KEY_DOWN: 247 case'j': 248 if (st.ypad + st.printrows <= st.hpad -1) 249 st.ypad++; 250 break; 251 case KEY_F(1): 252 if (conf->key.f1_file == NULL && 253 conf->key.f1_message == NULL) 254 break; 255 if (f1help_dialog(conf) != 0) 256 return (BSDDIALOG_ERROR); 257 if (textbox_draw(&d, &st) != 0) 258 return (BSDDIALOG_ERROR); 259 break; 260 case KEY_CTRL('l'): 261 case KEY_RESIZE: 262 if (textbox_draw(&d, &st) != 0) 263 return (BSDDIALOG_ERROR); 264 break; 265 } 266 } 267 268 delwin(st.pad); 269 end_dialog(&d); 270 271 return (retval); 272 } 273