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