1 /*- 2 * SPDX-License-Identifier: BSD-2-Clause 3 * 4 * Copyright (c) 2021 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 <sys/param.h> 29 30 #include <ctype.h> 31 #ifdef PORTNCURSES 32 #include <ncurses/ncurses.h> 33 #else 34 #include <ncurses.h> 35 #endif 36 #include <string.h> 37 38 #include "bsddialog.h" 39 #include "lib_util.h" 40 #include "bsddialog_theme.h" 41 42 #define AUTO_WIDTH (COLS / 3U) 43 /* at least 1 line text for its pad building in widget_withtextpad_init() */ 44 #define MIN_HEIGHT (HBORDERS + 2 /*buttons*/ + 1 /*text*/) 45 46 extern struct bsddialog_theme t; 47 48 static int 49 message_autosize(struct bsddialog_conf *conf, int rows, int cols, int *h, 50 int *w, char *text, struct buttons bs) 51 { 52 int maxword, maxline, nlines, line; 53 54 if (get_text_properties(conf, text, &maxword, &maxline, &nlines) != 0) 55 return BSDDIALOG_ERROR; 56 57 if (cols == BSDDIALOG_AUTOSIZE) { 58 *w = VBORDERS; 59 /* buttons size */ 60 *w += bs.nbuttons * bs.sizebutton; 61 *w += bs.nbuttons > 0 ? (bs.nbuttons-1) * t.button.space : 0; 62 /* text size */ 63 line = MIN(maxline + VBORDERS + t.text.hmargin * 2, AUTO_WIDTH); 64 line = MAX(line, (int) (maxword + VBORDERS + t.text.hmargin * 2)); 65 *w = MAX(*w, line); 66 /* conf.auto_minwidth */ 67 *w = MAX(*w, (int)conf->auto_minwidth); 68 /* avoid terminal overflow */ 69 *w = MIN(*w, widget_max_width(conf)); 70 } 71 72 if (rows == BSDDIALOG_AUTOSIZE) { 73 *h = MIN_HEIGHT - 1; 74 if (maxword > 0) 75 *h += MAX(nlines, (int)(*w / GET_ASPECT_RATIO(conf))); 76 *h = MAX(*h, MIN_HEIGHT); 77 /* conf.auto_minheight */ 78 *h = MAX(*h, (int)conf->auto_minheight); 79 /* avoid terminal overflow */ 80 *h = MIN(*h, widget_max_height(conf)); 81 } 82 83 return 0; 84 } 85 86 static int message_checksize(int rows, int cols, struct buttons bs) 87 { 88 int mincols; 89 90 mincols = VBORDERS; 91 mincols += bs.nbuttons * bs.sizebutton; 92 mincols += bs.nbuttons > 0 ? (bs.nbuttons-1) * t.button.space : 0; 93 94 if (cols < mincols) 95 RETURN_ERROR("Few cols, Msgbox and Yesno need at least width "\ 96 "for borders, buttons and spaces between buttons"); 97 98 if (rows < MIN_HEIGHT) 99 RETURN_ERROR("Msgbox and Yesno need at least height 5"); 100 101 return 0; 102 } 103 104 static void 105 buttonsupdate(WINDOW *widget, int h, int w, struct buttons bs) 106 { 107 draw_buttons(widget, h-2, w, bs, true); 108 wnoutrefresh(widget); 109 } 110 111 static void 112 textupdate(WINDOW *widget, int y, int x, int h, int w, WINDOW *textpad, 113 int htextpad, int textrow) 114 { 115 if (htextpad > h - 4) { 116 mvwprintw(widget, h-3, w-6, "%3d%%", 117 100 * (textrow+h-4)/ htextpad); 118 wnoutrefresh(widget); 119 } 120 121 pnoutrefresh(textpad, textrow, 0, y+1, x+2, y+h-4, x+w-2); 122 } 123 124 static int 125 do_dialog(struct bsddialog_conf *conf, char *text, int rows, int cols, 126 struct buttons bs) 127 { 128 WINDOW *widget, *textpad, *shadow; 129 bool loop; 130 int i, y, x, h, w, input, output, htextpad, textrow; 131 132 if (set_widget_size(conf, rows, cols, &h, &w) != 0) 133 return BSDDIALOG_ERROR; 134 if (message_autosize(conf, rows, cols, &h, &w, text, bs) != 0) 135 return BSDDIALOG_ERROR; 136 if (message_checksize(h, w, bs) != 0) 137 return BSDDIALOG_ERROR; 138 if (set_widget_position(conf, &y, &x, h, w) != 0) 139 return BSDDIALOG_ERROR; 140 141 if (new_widget_withtextpad(conf, &shadow, &widget, y, x, h, w, RAISED, 142 &textpad, &htextpad, text, true) != 0) 143 return BSDDIALOG_ERROR; 144 145 textrow = 0; 146 loop = true; 147 buttonsupdate(widget, h, w, bs); 148 textupdate(widget, y, x, h, w, textpad, htextpad, textrow); 149 while(loop) { 150 doupdate(); 151 input = getch(); 152 switch (input) { 153 case 10: /* Enter */ 154 output = bs.value[bs.curr]; 155 loop = false; 156 break; 157 case 27: /* Esc */ 158 output = BSDDIALOG_ESC; 159 loop = false; 160 break; 161 case '\t': /* TAB */ 162 bs.curr = (bs.curr + 1) % bs.nbuttons; 163 buttonsupdate(widget, h, w, bs); 164 break; 165 case KEY_F(1): 166 if (conf->f1_file == NULL && conf->f1_message == NULL) 167 break; 168 if (f1help(conf) != 0) 169 return BSDDIALOG_ERROR; 170 /* No break! the terminal size can change */ 171 case KEY_RESIZE: 172 hide_widget(y, x, h, w,conf->shadow); 173 174 /* 175 * Unnecessary, but, when the columns decrease the 176 * following "refresh" seem not work 177 */ 178 refresh(); 179 180 if (set_widget_size(conf, rows, cols, &h, &w) != 0) 181 return BSDDIALOG_ERROR; 182 if (message_autosize(conf, rows, cols, &h, &w, text, bs) != 0) 183 return BSDDIALOG_ERROR; 184 if (message_checksize(h, w, bs) != 0) 185 return BSDDIALOG_ERROR; 186 if (set_widget_position(conf, &y, &x, h, w) != 0) 187 return BSDDIALOG_ERROR; 188 189 wclear(shadow); 190 mvwin(shadow, y + t.shadow.h, x + t.shadow.w); 191 wresize(shadow, h, w); 192 193 wclear(widget); 194 mvwin(widget, y, x); 195 wresize(widget, h, w); 196 197 htextpad = 1; 198 wclear(textpad); 199 wresize(textpad, 1, w - HBORDERS - t.text.hmargin * 2); 200 201 if(update_widget_withtextpad(conf, shadow, widget, h, w, 202 RAISED, textpad, &htextpad, text, true) != 0) 203 return BSDDIALOG_ERROR; 204 205 buttonsupdate(widget, h, w, bs); 206 textupdate(widget, y, x, h, w, textpad, htextpad, textrow); 207 208 /* Important to fix grey lines expanding screen */ 209 refresh(); 210 break; 211 case KEY_UP: 212 if (textrow == 0) 213 break; 214 textrow--; 215 textupdate(widget, y, x, h, w, textpad, htextpad, textrow); 216 break; 217 case KEY_DOWN: 218 if (textrow + h - 4 >= htextpad) 219 break; 220 textrow++; 221 textupdate(widget, y, x, h, w, textpad, htextpad, textrow); 222 break; 223 case KEY_LEFT: 224 if (bs.curr > 0) { 225 bs.curr--; 226 buttonsupdate(widget, h, w, bs); 227 } 228 break; 229 case KEY_RIGHT: 230 if (bs.curr < (int) bs.nbuttons - 1) { 231 bs.curr++; 232 buttonsupdate(widget, h, w, bs); 233 } 234 break; 235 default: 236 for (i = 0; i < (int) bs.nbuttons; i++) 237 if (tolower(input) == tolower(bs.label[i][0])) { 238 output = bs.value[i]; 239 loop = false; 240 } 241 } 242 } 243 244 end_widget_withtextpad(conf, widget, h, w, textpad, shadow); 245 246 return output; 247 } 248 249 /* API */ 250 int 251 bsddialog_msgbox(struct bsddialog_conf *conf, char* text, int rows, int cols) 252 { 253 struct buttons bs; 254 255 get_buttons(conf, &bs, BUTTONLABEL(ok_label), BUTTONLABEL(extra_label), 256 NULL, BUTTONLABEL(help_label)); 257 258 return (do_dialog(conf, text, rows, cols, bs)); 259 } 260 261 int 262 bsddialog_yesno(struct bsddialog_conf *conf, char* text, int rows, int cols) 263 { 264 struct buttons bs; 265 266 get_buttons(conf, &bs, 267 conf->button.ok_label == NULL ? "Yes" : conf->button.ok_label, 268 BUTTONLABEL(extra_label), 269 conf->button.cancel_label == NULL ? "No" : conf->button.cancel_label, 270 BUTTONLABEL(help_label)); 271 272 return (do_dialog(conf, text, rows, cols, bs)); 273 } 274