1 /**************************************************************************** 2 * Copyright 2020 Thomas E. Dickey * 3 * * 4 * Permission is hereby granted, free of charge, to any person obtaining a * 5 * copy of this software and associated documentation files (the * 6 * "Software"), to deal in the Software without restriction, including * 7 * without limitation the rights to use, copy, modify, merge, publish, * 8 * distribute, distribute with modifications, sublicense, and/or sell * 9 * copies of the Software, and to permit persons to whom the Software is * 10 * furnished to do so, subject to the following conditions: * 11 * * 12 * The above copyright notice and this permission notice shall be included * 13 * in all copies or substantial portions of the Software. * 14 * * 15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * 16 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * 17 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * 18 * IN NO EVENT SHALL THE ABOVE COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, * 19 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR * 20 * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR * 21 * THE USE OR OTHER DEALINGS IN THE SOFTWARE. * 22 * * 23 * Except as contained in this notice, the name(s) of the above copyright * 24 * holders shall not be used in advertising or otherwise to promote the * 25 * sale, use or other dealings in this Software without prior written * 26 * authorization. * 27 ****************************************************************************/ 28 /* 29 * $Id: move_field.c,v 1.7 2020/05/09 12:52:00 tom Exp $ 30 * 31 * Demonstrate move_field(). 32 */ 33 34 #include <test.priv.h> 35 36 #if USE_LIBFORM 37 38 #include <edit_field.h> 39 #include <popup_msg.h> 40 41 #ifdef HAVE_NETBSD_FORM_H 42 #define form_field_row(field) (field)->form_row 43 #define form_field_col(field) (field)->form_col 44 #else /* e.g., SVr4, ncurses */ 45 #define form_field_row(field) (field)->frow 46 #define form_field_col(field) (field)->fcol 47 #endif 48 49 #define DO_DEMO CTRL('F') /* actual key for toggling demo-mode */ 50 #define MY_DEMO EDIT_FIELD('f') /* internal request-code */ 51 52 static char empty[] = ""; 53 static FIELD *all_fields[100]; 54 55 /* *INDENT-OFF* */ 56 static struct { 57 int code; 58 int result; 59 const char *help; 60 } commands[] = { 61 { CTRL('A'), REQ_BEG_FIELD, "go to beginning of field" }, 62 { CTRL('D'), REQ_DOWN_FIELD, "move downward to field" }, 63 { CTRL('E'), REQ_END_FIELD, "go to end of field" }, 64 { CTRL('H'), REQ_DEL_PREV, "delete previous character" }, 65 { CTRL('I'), REQ_NEXT_FIELD, "go to next field" }, 66 { CTRL('K'), REQ_CLR_EOF, "clear to end of field" }, 67 { CTRL('N'), REQ_NEXT_FIELD, "go to next field" }, 68 { CTRL('P'), REQ_PREV_FIELD, "go to previous field" }, 69 { CTRL('Q'), MY_QUIT, "exit form" }, 70 { CTRL('U'), REQ_UP_FIELD, "move upward to field" }, 71 { CTRL('W'), REQ_NEXT_WORD, "go to next word" }, 72 { CTRL('X'), REQ_CLR_FIELD, "clear field" }, 73 { CTRL('['), MY_QUIT, "exit form" }, 74 { KEY_F(1), MY_HELP, "show this screen", }, 75 { KEY_BACKSPACE, REQ_DEL_PREV, "delete previous character" }, 76 { KEY_BTAB, REQ_PREV_FIELD, "go to previous field" }, 77 { KEY_DOWN, REQ_DOWN_CHAR, "move down 1 character" }, 78 { KEY_END, REQ_LAST_FIELD, "go to last field" }, 79 { KEY_HOME, REQ_FIRST_FIELD, "go to first field" }, 80 { KEY_LEFT, REQ_LEFT_CHAR, "move left 1 character" }, 81 { KEY_NEXT, REQ_NEXT_FIELD, "go to next field" }, 82 { KEY_PREVIOUS, REQ_PREV_FIELD, "go to previous field" }, 83 { KEY_RIGHT, REQ_RIGHT_CHAR, "move right 1 character" }, 84 { KEY_UP, REQ_UP_CHAR, "move up 1 character" }, 85 { DO_DEMO, MY_DEMO, "move current field with cursor keys" } 86 }; 87 /* *INDENT-ON* */ 88 89 static void 90 my_help_edit_field(void) 91 { 92 int used = 0; 93 unsigned n; 94 char **msgs = typeCalloc(char *, 3 + SIZEOF(commands)); 95 96 msgs[used++] = strdup("Defined form edit/traversal keys:"); 97 for (n = 0; n < SIZEOF(commands); ++n) { 98 char *msg; 99 const char *name; 100 const char *code = keyname(commands[n].code); 101 size_t need = 5; 102 #ifdef NCURSES_VERSION 103 if ((name = form_request_name(commands[n].result)) == 0) 104 #endif 105 name = commands[n].help; 106 need = 5 + strlen(code) + strlen(name); 107 msg = typeMalloc(char, need); 108 _nc_SPRINTF(msg, _nc_SLIMIT(need) "%s -- %s", code, name); 109 msgs[used++] = msg; 110 } 111 msgs[used++] = 112 strdup("Arrow keys move within a field as you would expect."); 113 msgs[used] = 0; 114 popup_msg2(stdscr, msgs); 115 for (n = 0; msgs[n] != 0; ++n) { 116 free(msgs[n]); 117 } 118 free(msgs); 119 } 120 121 static FIELD * 122 make_label(const char *label, int frow, int fcol) 123 { 124 FIELD *f = new_field(1, (int) strlen(label), frow, fcol, 0, 0); 125 126 if (f) { 127 set_field_buffer(f, 0, label); 128 set_field_opts(f, (int) ((unsigned) field_opts(f) & ~O_ACTIVE)); 129 } 130 return (f); 131 } 132 133 static FIELD * 134 make_field(int frow, int fcol, int rows, int cols) 135 { 136 FIELD *f = new_field(rows, cols, frow, fcol, 0, 1); 137 138 if (f) { 139 set_field_back(f, A_UNDERLINE); 140 init_edit_field(f, empty); 141 } 142 return (f); 143 } 144 145 static void 146 erase_form(FORM *f) 147 { 148 WINDOW *w = form_win(f); 149 WINDOW *s = form_sub(f); 150 151 unpost_form(f); 152 werase(w); 153 wrefresh(w); 154 delwin(s); 155 delwin(w); 156 } 157 158 static FieldAttrs * 159 my_field_attrs(FIELD *f) 160 { 161 return (FieldAttrs *) field_userptr(f); 162 } 163 164 static int 165 buffer_length(FIELD *f) 166 { 167 return my_field_attrs(f)->row_lengths[0]; 168 } 169 170 static void 171 set_buffer_length(FIELD *f, int length) 172 { 173 my_field_attrs(f)->row_lengths[0] = length; 174 } 175 176 static int 177 offset_in_field(FORM *form) 178 { 179 FIELD *field = current_field(form); 180 int currow, curcol; 181 182 form_getyx(form, currow, curcol); 183 return curcol + currow * (int) field->dcols; 184 } 185 186 static void 187 inactive_field(FIELD *f) 188 { 189 set_field_back(f, my_field_attrs(f)->background); 190 } 191 192 static int 193 my_edit_field(FORM *form, int *result) 194 { 195 int ch = wgetch(form_win(form)); 196 int status; 197 FIELD *before; 198 unsigned n; 199 int length; 200 int before_row; 201 int before_col; 202 int before_off = offset_in_field(form); 203 204 form_getyx(form, before_row, before_col); 205 before = current_field(form); 206 set_field_back(before, A_NORMAL); 207 if (ch <= KEY_MAX) { 208 set_field_back(before, A_REVERSE); 209 } else if (ch <= MAX_FORM_COMMAND) { 210 inactive_field(before); 211 } 212 213 *result = ch; 214 for (n = 0; n < SIZEOF(commands); ++n) { 215 if (commands[n].code == ch) { 216 *result = commands[n].result; 217 break; 218 } 219 } 220 221 status = form_driver(form, *result); 222 223 if (status == E_OK) { 224 bool modified = TRUE; 225 226 length = buffer_length(before); 227 if (length < before_off) 228 length = before_off; 229 switch (*result) { 230 case REQ_CLR_EOF: 231 length = before_off; 232 break; 233 case REQ_CLR_EOL: 234 if ((int) (before_row + 1) == (int) (before->rows)) 235 length = before_off; 236 break; 237 case REQ_CLR_FIELD: 238 length = 0; 239 break; 240 case REQ_DEL_CHAR: 241 if (length > before_off) 242 --length; 243 break; 244 case REQ_DEL_PREV: 245 if (length > 0) { 246 if (before_col > 0) { 247 --length; 248 } else if (before_row > 0) { 249 length -= (int) before->cols + before_col; 250 } 251 } 252 break; 253 case REQ_NEW_LINE: 254 length += (int) before->cols; 255 break; 256 257 default: 258 modified = (ch < MIN_FORM_COMMAND 259 && isprint(ch)); 260 break; 261 } 262 263 /* 264 * If we do not force a re-validation, then field_buffer 0 will 265 * be lagging by one character. 266 */ 267 if (modified && form_driver(form, REQ_VALIDATION) == E_OK && *result 268 < MIN_FORM_COMMAND) 269 ++length; 270 271 set_buffer_length(before, length); 272 } 273 274 if (current_field(form) != before) 275 inactive_field(before); 276 return status; 277 } 278 279 static FIELD ** 280 copy_fields(FIELD **source, size_t length) 281 { 282 FIELD **target = calloc(length + 1, sizeof(FIELD *)); 283 memcpy(target, source, length * sizeof(FIELD *)); 284 return target; 285 } 286 287 /* display a status message to show what's happening */ 288 static void 289 show_status(FORM *form, FIELD *field) 290 { 291 WINDOW *sub = form_sub(form); 292 int currow, curcol; 293 294 getyx(stdscr, currow, curcol); 295 mvprintw(LINES - 1, 0, 296 "Field at [%d,%d]. Press %s to quit moving.", 297 getbegy(sub) + form_field_row(field), 298 getbegx(sub) + form_field_col(field), 299 keyname(DO_DEMO)); 300 clrtobot(); 301 move(currow, curcol); 302 refresh(); 303 } 304 305 /* 306 * Move the current label+field in response to cursor-keys (or h,j,k,l) until 307 * a control/F is read. 308 */ 309 static void 310 do_demo(FORM *form) 311 { 312 int count = field_count(form); 313 FIELD *my_field = current_field(form); 314 315 if (count > 0 && my_field != NULL) { 316 size_t needed = (size_t) count; 317 FIELD **old_fields = copy_fields(form_fields(form), needed); 318 FIELD **new_fields = copy_fields(form_fields(form), needed); 319 int ch; 320 321 if (old_fields != NULL && new_fields != NULL) { 322 bool found = FALSE; 323 324 /* TODO: move the label too, in parallel with the editing field */ 325 326 /* remove the current field from the newer list */ 327 for (ch = 0; ch <= count; ++ch) { 328 if (found) { 329 new_fields[ch - 1] = new_fields[ch]; 330 } else if (new_fields[ch] == my_field) { 331 found = TRUE; 332 } 333 } 334 335 if (found) { 336 int currow, curcol; 337 338 getyx(stdscr, currow, curcol); 339 340 show_status(form, my_field); 341 ch = '?'; 342 while ((ch = wgetch(form_win(form))) != DO_DEMO) { 343 int field_y = form_field_row(my_field); 344 int field_x = form_field_col(my_field); 345 346 switch (ch) { 347 case 'h': 348 case KEY_LEFT: 349 if (field_x > 0) 350 field_x--; 351 break; 352 case 'j': 353 case KEY_DOWN: 354 field_y++; 355 break; 356 case 'k': 357 case KEY_UP: 358 if (field_y > 0) 359 field_y--; 360 break; 361 case 'l': 362 case KEY_RIGHT: 363 field_x++; 364 break; 365 case CTRL('Q'): 366 case CTRL('['): 367 ch = DO_DEMO; 368 /* FALLTHRU */ 369 case DO_DEMO: 370 break; 371 default: 372 continue; 373 } 374 375 if (ch == DO_DEMO) 376 break; 377 378 /* alter connected fields temporarily to move the field */ 379 unpost_form(form); 380 set_form_fields(form, new_fields); 381 post_form(form); 382 383 /* TODO: update screen position on success */ 384 move_field(my_field, field_y, field_x); 385 386 /* restore the form's list of fields */ 387 unpost_form(form); 388 set_form_fields(form, old_fields); 389 post_form(form); 390 391 show_status(form, my_field); 392 } 393 394 /* cleanup */ 395 move(LINES - 1, 0); 396 clrtobot(); 397 move(currow, curcol); 398 refresh(); 399 } 400 } 401 free(new_fields); 402 } 403 } 404 405 static int 406 my_form_driver(FORM *form, int c) 407 { 408 switch (c) { 409 case MY_QUIT: 410 if (form_driver(form, REQ_VALIDATION) == E_OK) 411 return (TRUE); 412 break; 413 case MY_HELP: 414 my_help_edit_field(); 415 break; 416 case MY_DEMO: 417 do_demo(form); 418 break; 419 default: 420 beep(); 421 break; 422 } 423 return (FALSE); 424 } 425 426 static void 427 demo_forms(void) 428 { 429 FORM *form; 430 int c; 431 unsigned n = 0; 432 const char *fname; 433 434 /* describe the form */ 435 all_fields[n++] = make_label("Sample Form", 0, 15); 436 437 fname = "Last Name"; 438 all_fields[n++] = make_label(fname, 2, 0); 439 all_fields[n++] = make_field(3, 0, 1, 18); 440 set_field_type(all_fields[n - 1], TYPE_ALPHA, 1); 441 442 fname = "First Name"; 443 all_fields[n++] = make_label(fname, 2, 20); 444 all_fields[n++] = make_field(3, 20, 1, 12); 445 set_field_type(all_fields[n - 1], TYPE_ALPHA, 1); 446 447 fname = "Middle Name"; 448 all_fields[n++] = make_label(fname, 2, 34); 449 all_fields[n++] = make_field(3, 34, 1, 12); 450 set_field_type(all_fields[n - 1], TYPE_ALPHA, 1); 451 452 fname = "Comments"; 453 all_fields[n++] = make_label(fname, 5, 0); 454 all_fields[n++] = make_field(6, 0, 4, 46); 455 init_edit_field(all_fields[n - 1], empty); 456 457 all_fields[n] = (FIELD *) 0; 458 459 if ((form = new_form(all_fields)) != 0) { 460 int finished = 0; 461 462 post_form(form); 463 464 while (!finished) { 465 switch (my_edit_field(form, &c)) { 466 case E_OK: 467 break; 468 case E_UNKNOWN_COMMAND: 469 finished = my_form_driver(form, c); 470 break; 471 default: 472 beep(); 473 break; 474 } 475 } 476 477 erase_form(form); 478 479 free_form(form); 480 } 481 for (c = 0; all_fields[c] != 0; c++) { 482 free_edit_field(all_fields[c]); 483 free_field(all_fields[c]); 484 } 485 noraw(); 486 nl(); 487 } 488 489 int 490 main(void) 491 { 492 setlocale(LC_ALL, ""); 493 494 initscr(); 495 cbreak(); 496 noecho(); 497 raw(); 498 nonl(); /* lets us read ^M's */ 499 intrflush(stdscr, FALSE); 500 keypad(stdscr, TRUE); 501 502 if (has_colors()) { 503 start_color(); 504 init_pair(1, COLOR_WHITE, COLOR_BLUE); 505 init_pair(2, COLOR_GREEN, COLOR_BLACK); 506 init_pair(3, COLOR_CYAN, COLOR_BLACK); 507 bkgd((chtype) COLOR_PAIR(1)); 508 refresh(); 509 } 510 511 demo_forms(); 512 513 endwin(); 514 ExitProgram(EXIT_SUCCESS); 515 } 516 517 #else 518 int 519 main(void) 520 { 521 printf("This program requires the curses form library\n"); 522 ExitProgram(EXIT_FAILURE); 523 } 524 #endif 525