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