1 /* 2 * $Id: rangebox.c,v 1.32 2020/11/22 23:25:09 tom Exp $ 3 * 4 * rangebox.c -- implements the rangebox dialog 5 * 6 * Copyright 2012-2019,2020 Thomas E. Dickey 7 * 8 * This program is free software; you can redistribute it and/or modify 9 * it under the terms of the GNU Lesser General Public License, version 2.1 10 * as published by the Free Software Foundation. 11 * 12 * This program is distributed in the hope that it will be useful, but 13 * WITHOUT ANY WARRANTY; without even the implied warranty of 14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 * Lesser General Public License for more details. 16 * 17 * You should have received a copy of the GNU Lesser General Public 18 * License along with this program; if not, write to 19 * Free Software Foundation, Inc. 20 * 51 Franklin St., Fifth Floor 21 * Boston, MA 02110, USA. 22 */ 23 24 #include <dlg_internals.h> 25 #include <dlg_keys.h> 26 27 #define ONE_HIGH 1 28 29 #define MIN_HIGH (ONE_HIGH + 1 + (4 * MARGIN)) 30 #define MIN_WIDE (10 + 2 + (2 * MARGIN)) 31 32 typedef struct { 33 /* window in which the value and slider are drawn */ 34 WINDOW *window; 35 int min_value; 36 int max_value; 37 /* position and width of the numeric field */ 38 int value_x; 39 int value_len; 40 int value_col; 41 /* position and width of the slider field */ 42 int slide_x; 43 int slide_y; 44 int slide_len; 45 /* current value drawn */ 46 int current; 47 /* value to add to make slider move by one cell */ 48 int slide_inc; 49 } VALUE; 50 51 static int 52 digits_of(int value) 53 { 54 char temp[80]; 55 sprintf(temp, "%d", value); 56 return (int) strlen(temp); 57 } 58 59 static int 60 digit_of(VALUE * data) 61 { 62 int col = data->value_col; 63 int result = 1; 64 65 while (++col < data->value_len) { 66 result *= 10; 67 } 68 return result; 69 } 70 71 static bool 72 set_digit(VALUE * data, int chr) 73 { 74 bool result = FALSE; 75 char buffer[80]; 76 long check; 77 char *next = 0; 78 79 sprintf(buffer, "%*d", data->value_len, data->current); 80 buffer[data->value_col] = (char) chr; 81 check = strtol(buffer, &next, 10); 82 if (next == 0 || *next == '\0') { 83 if ((check <= (long) data->max_value) && 84 (check >= (long) data->min_value)) { 85 result = TRUE; 86 data->current = (int) check; 87 } 88 } 89 90 return result; 91 } 92 93 /* 94 * This is similar to the gauge code, but differs in the way the number 95 * is displayed, etc. 96 */ 97 static void 98 draw_value(VALUE * data, int value) 99 { 100 if (value != data->current) { 101 WINDOW *win = data->window; 102 int y, x; 103 int n; 104 int ranges = (data->max_value + 1 - data->min_value); 105 int offset = (value - data->min_value); 106 int scaled; 107 108 getyx(win, y, x); 109 110 if (ranges > data->slide_len) { 111 scaled = (offset + data->slide_inc) / data->slide_inc; 112 } else if (ranges < data->slide_len) { 113 scaled = (offset + 1) * data->slide_inc; 114 } else { 115 scaled = offset; 116 } 117 118 dlg_attrset(win, gauge_attr); 119 wmove(win, data->slide_y, data->slide_x); 120 for (n = 0; n < data->slide_len; ++n) { 121 (void) waddch(win, ' '); 122 } 123 wmove(win, data->slide_y, data->value_x); 124 wprintw(win, "%*d", data->value_len, value); 125 if ((gauge_attr & A_REVERSE) != 0) { 126 dlg_attroff(win, A_REVERSE); 127 } else { 128 dlg_attrset(win, A_REVERSE); 129 } 130 wmove(win, data->slide_y, data->slide_x); 131 for (n = 0; n < scaled; ++n) { 132 chtype ch2 = winch(win); 133 if (gauge_attr & A_REVERSE) { 134 ch2 &= ~A_REVERSE; 135 } 136 (void) waddch(win, ch2); 137 } 138 dlg_attrset(win, dialog_attr); 139 140 wmove(win, y, x); 141 data->current = value; 142 143 DLG_TRACE(("# drew %d offset %d scaled %d limit %d inc %d\n", 144 value, 145 offset, 146 scaled, 147 data->slide_len, 148 data->slide_inc)); 149 150 dlg_trace_win(win); 151 } 152 } 153 154 /* 155 * Allow the user to select from a range of values, e.g., using a slider. 156 */ 157 int 158 dialog_rangebox(const char *title, 159 const char *cprompt, 160 int height, 161 int width, 162 int min_value, 163 int max_value, 164 int default_value) 165 { 166 /* *INDENT-OFF* */ 167 static DLG_KEYS_BINDING binding[] = { 168 DLG_KEYS_DATA( DLGK_DELETE_RIGHT,KEY_DC ), 169 HELPKEY_BINDINGS, 170 ENTERKEY_BINDINGS, 171 TOGGLEKEY_BINDINGS, 172 DLG_KEYS_DATA( DLGK_FIELD_NEXT, CHR_NEXT ), 173 DLG_KEYS_DATA( DLGK_FIELD_NEXT, KEY_RIGHT ), 174 DLG_KEYS_DATA( DLGK_FIELD_NEXT, TAB ), 175 DLG_KEYS_DATA( DLGK_FIELD_PREV, CHR_BACKSPACE ), 176 DLG_KEYS_DATA( DLGK_FIELD_PREV, CHR_PREVIOUS ), 177 DLG_KEYS_DATA( DLGK_FIELD_PREV, KEY_BTAB ), 178 DLG_KEYS_DATA( DLGK_FIELD_PREV, KEY_LEFT ), 179 DLG_KEYS_DATA( DLGK_ITEM_FIRST, KEY_HOME), 180 DLG_KEYS_DATA( DLGK_ITEM_LAST, KEY_END), 181 DLG_KEYS_DATA( DLGK_ITEM_LAST, KEY_LL ), 182 DLG_KEYS_DATA( DLGK_ITEM_NEXT, '+'), 183 DLG_KEYS_DATA( DLGK_ITEM_NEXT, KEY_DOWN), 184 DLG_KEYS_DATA( DLGK_ITEM_PREV, '-' ), 185 DLG_KEYS_DATA( DLGK_ITEM_PREV, KEY_UP ), 186 DLG_KEYS_DATA( DLGK_PAGE_NEXT, KEY_NEXT), 187 DLG_KEYS_DATA( DLGK_PAGE_NEXT, KEY_NPAGE), 188 DLG_KEYS_DATA( DLGK_PAGE_PREV, KEY_PPAGE ), 189 DLG_KEYS_DATA( DLGK_PAGE_PREV, KEY_PREVIOUS ), 190 END_KEYS_BINDING 191 }; 192 /* *INDENT-ON* */ 193 194 #ifdef KEY_RESIZE 195 int old_height = height; 196 int old_width = width; 197 #endif 198 VALUE data; 199 int key, fkey; 200 int button; 201 int result = DLG_EXIT_UNKNOWN; 202 WINDOW *dialog; 203 int state = dlg_default_button(); 204 const char **buttons = dlg_ok_labels(); 205 char *prompt; 206 char buffer[MAX_LEN]; 207 int cur_value = default_value; 208 int usable; 209 int ranges; 210 int yorg, xorg; 211 212 DLG_TRACE(("# tailbox args:\n")); 213 DLG_TRACE2S("title", title); 214 DLG_TRACE2S("message", cprompt); 215 DLG_TRACE2N("height", height); 216 DLG_TRACE2N("width", width); 217 DLG_TRACE2N("minval", min_value); 218 DLG_TRACE2N("maxval", max_value); 219 DLG_TRACE2N("default", default_value); 220 221 if (max_value < min_value) 222 max_value = min_value; 223 if (cur_value > max_value) 224 cur_value = max_value; 225 if (cur_value < min_value) 226 cur_value = min_value; 227 228 dlg_does_output(); 229 230 #ifdef KEY_RESIZE 231 retry: 232 #endif 233 234 prompt = dlg_strclone(cprompt); 235 dlg_auto_size(title, prompt, &height, &width, MIN_HIGH, MIN_WIDE); 236 237 dlg_button_layout(buttons, &width); 238 dlg_print_size(height, width); 239 dlg_ctl_size(height, width); 240 241 dialog = dlg_new_window(height, width, 242 yorg = dlg_box_y_ordinate(height), 243 xorg = dlg_box_x_ordinate(width)); 244 245 data.window = dialog; 246 247 data.min_value = min_value; 248 data.max_value = max_value; 249 250 usable = (width - 2 - 4 * MARGIN); 251 ranges = max_value - min_value + 1; 252 253 /* 254 * Center the number after allowing for its maximum number of digits. 255 */ 256 data.value_len = digits_of(max_value); 257 if (digits_of(min_value) > data.value_len) 258 data.value_len = digits_of(min_value); 259 data.value_x = (usable - data.value_len) / 2 + MARGIN; 260 data.value_col = data.value_len - 1; 261 262 /* 263 * The slider is scaled, to try to use the width of the dialog. 264 */ 265 if (ranges > usable) { 266 data.slide_inc = (ranges + usable - 1) / usable; 267 data.slide_len = 1 + ranges / data.slide_inc; 268 } else if (ranges < usable) { 269 data.slide_inc = usable / ranges; 270 data.slide_len = ranges * data.slide_inc; 271 } else { 272 data.slide_inc = 1; 273 data.slide_len = usable; 274 } 275 data.slide_x = (usable - data.slide_len) / 2 + MARGIN + 2; 276 data.slide_y = height - 5; 277 278 data.current = cur_value - 1; 279 280 dlg_register_window(dialog, "rangebox", binding); 281 dlg_register_buttons(dialog, "rangebox", buttons); 282 283 dlg_draw_box2(dialog, 0, 0, height, width, dialog_attr, border_attr, border2_attr); 284 dlg_mouse_setbase(xorg, yorg); 285 dlg_mouse_mkregion(data.slide_y - 1, data.slide_x - 1, 3, usable + 2, 'i'); 286 dlg_draw_box2(dialog, 287 height - 6, data.slide_x - MARGIN, 288 2 + MARGIN, data.slide_len + 2 * MARGIN, 289 dialog_attr, 290 border_attr, 291 border2_attr); 292 dlg_draw_bottom_box2(dialog, border_attr, border2_attr, dialog_attr); 293 dlg_draw_title(dialog, title); 294 dlg_draw_helpline(dialog, FALSE); 295 296 dlg_attrset(dialog, dialog_attr); 297 dlg_print_autowrap(dialog, prompt, height, width); 298 299 dlg_trace_win(dialog); 300 301 while (result == DLG_EXIT_UNKNOWN) { 302 int key2; 303 304 draw_value(&data, cur_value); 305 button = (state < 0) ? 0 : state; 306 dlg_draw_buttons(dialog, height - 2, 0, buttons, button, FALSE, width); 307 if (state < 0) { 308 data.value_col = data.value_len + state; 309 wmove(dialog, data.slide_y, data.value_x + data.value_col); 310 } 311 312 key = dlg_mouse_wgetch(dialog, &fkey); 313 if (dlg_result_key(key, fkey, &result)) { 314 if (!dlg_button_key(result, &button, &key, &fkey)) 315 break; 316 } 317 318 if ((key2 = dlg_char_to_button(key, buttons)) >= 0) { 319 result = key2; 320 } else { 321 /* handle function-keys */ 322 if (fkey) { 323 switch (key) { 324 case DLGK_TOGGLE: 325 case DLGK_ENTER: 326 result = dlg_enter_buttoncode(button); 327 break; 328 case DLGK_LEAVE: 329 result = dlg_ok_buttoncode(button); 330 break; 331 case DLGK_FIELD_PREV: 332 if (state < 0 && state > -data.value_len) { 333 --state; 334 } else { 335 state = dlg_prev_ok_buttonindex(state, -data.value_len); 336 } 337 break; 338 case DLGK_FIELD_NEXT: 339 if (state < 0) { 340 ++state; 341 } else { 342 state = dlg_next_ok_buttonindex(state, -data.value_len); 343 } 344 break; 345 case DLGK_ITEM_FIRST: 346 cur_value = min_value; 347 break; 348 case DLGK_ITEM_LAST: 349 cur_value = max_value; 350 break; 351 case DLGK_ITEM_PREV: 352 if (state < 0) { 353 cur_value -= digit_of(&data); 354 } else { 355 cur_value -= 1; 356 } 357 if (cur_value < min_value) 358 cur_value = min_value; 359 break; 360 case DLGK_ITEM_NEXT: 361 if (state < 0) { 362 cur_value += digit_of(&data); 363 } else { 364 cur_value += 1; 365 } 366 if (cur_value > max_value) 367 cur_value = max_value; 368 break; 369 case DLGK_PAGE_PREV: 370 cur_value -= data.slide_inc; 371 if (cur_value < min_value) 372 cur_value = min_value; 373 break; 374 case DLGK_PAGE_NEXT: 375 cur_value += data.slide_inc; 376 if (cur_value > max_value) 377 cur_value = max_value; 378 break; 379 #ifdef KEY_RESIZE 380 case KEY_RESIZE: 381 dlg_will_resize(dialog); 382 /* reset data */ 383 height = old_height; 384 width = old_width; 385 /* repaint */ 386 free(prompt); 387 _dlg_resize_cleanup(dialog); 388 goto retry; 389 #endif 390 case DLGK_MOUSE('i'): 391 state = -data.value_len; 392 break; 393 default: 394 if (is_DLGK_MOUSE(key)) { 395 result = dlg_ok_buttoncode(key - M_EVENT); 396 if (result < 0) 397 result = DLG_EXIT_OK; 398 } 399 break; 400 } 401 } else if (isdigit(key) && state < 0) { 402 if (set_digit(&data, key)) { 403 cur_value = data.current; 404 data.current--; 405 } 406 } else { 407 beep(); 408 } 409 } 410 } 411 412 sprintf(buffer, "%d", cur_value); 413 dlg_add_result(buffer); 414 AddLastKey(); 415 416 dlg_del_window(dialog); 417 dlg_mouse_free_regions(); 418 free(prompt); 419 420 return result; 421 } 422