1 /* 2 * $Id: buildlist.c,v 1.61 2015/01/25 23:52:45 tom Exp $ 3 * 4 * buildlist.c -- implements the buildlist dialog 5 * 6 * Copyright 2012-2014,2015 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 <dialog.h> 25 #include <dlg_keys.h> 26 27 /* 28 * Visually like menubox, but two columns. 29 */ 30 31 #define sLEFT (-2) 32 #define sRIGHT (-1) 33 34 #define KEY_TOGGLE ' ' 35 #define KEY_LEFTCOL '^' 36 #define KEY_RIGHTCOL '$' 37 38 #define MIN_HIGH (1 + (5 * MARGIN)) 39 40 typedef struct { 41 WINDOW *win; 42 int box_y; 43 int box_x; 44 int top_index; 45 int cur_index; 46 } MY_DATA; 47 48 typedef struct { 49 DIALOG_LISTITEM *items; 50 int base_y; /* base for mouse coordinates */ 51 int base_x; 52 int use_height; /* actual size of column box */ 53 int use_width; 54 int item_no; 55 int check_x; 56 int item_x; 57 MY_DATA list[2]; 58 } ALL_DATA; 59 60 /* 61 * Print list item. The 'selected' parameter is true if 'choice' is the 62 * current item. That one is colored differently from the other items. 63 */ 64 static void 65 print_item(ALL_DATA * data, 66 WINDOW *win, 67 DIALOG_LISTITEM * item, 68 int choice, 69 int selected) 70 { 71 chtype save = dlg_get_attrs(win); 72 int i; 73 bool both = (!dialog_vars.no_tags && !dialog_vars.no_items); 74 bool first = TRUE; 75 int climit = (data->item_x - data->check_x - 1); 76 const char *show = (dialog_vars.no_items 77 ? item->name 78 : item->text); 79 80 /* Clear 'residue' of last item */ 81 (void) wattrset(win, menubox_attr); 82 (void) wmove(win, choice, 0); 83 for (i = 0; i < getmaxx(win); i++) 84 (void) waddch(win, ' '); 85 86 (void) wmove(win, choice, data->check_x); 87 (void) wattrset(win, menubox_attr); 88 89 if (both) { 90 dlg_print_listitem(win, item->name, climit, first, selected); 91 (void) waddch(win, ' '); 92 first = FALSE; 93 } 94 95 (void) wmove(win, choice, data->item_x); 96 climit = (getmaxx(win) - data->item_x + 1); 97 dlg_print_listitem(win, show, climit, first, selected); 98 99 if (selected) { 100 dlg_item_help(item->help); 101 } 102 (void) wattrset(win, save); 103 } 104 105 /* 106 * Prints either the left (unselected) or right (selected) list. 107 */ 108 static void 109 print_1_list(ALL_DATA * data, 110 int choice, 111 int selected) 112 { 113 MY_DATA *moi = data->list + selected; 114 WINDOW *win = moi->win; 115 int i, j; 116 int last = 0; 117 int max_rows = getmaxy(win); 118 119 for (i = j = 0; j < max_rows; i++) { 120 int ii = i + moi->top_index; 121 if (ii < 0) { 122 continue; 123 } else if (ii >= data->item_no) { 124 break; 125 } else if (!(selected ^ (data->items[ii].state != 0))) { 126 print_item(data, 127 win, 128 &data->items[ii], 129 j, ii == choice); 130 last = ++j; 131 } 132 } 133 if (wmove(win, last, 0) != ERR) 134 wclrtobot(win); 135 (void) wnoutrefresh(win); 136 } 137 138 /* 139 * Return the previous item from the list, staying in the same column. If no 140 * further movement is possible, return the same choice as given. 141 */ 142 static int 143 prev_item(ALL_DATA * data, int choice, int selected) 144 { 145 int result = choice; 146 int n; 147 148 for (n = choice - 1; n >= 0; --n) { 149 if ((data->items[n].state != 0) == selected) { 150 result = n; 151 break; 152 } 153 } 154 return result; 155 } 156 157 /* 158 * Return true if the given choice is on the first page in the current column. 159 */ 160 static bool 161 stop_prev(ALL_DATA * data, int choice, int selected) 162 { 163 return (prev_item(data, choice, selected) == choice); 164 } 165 166 static bool 167 check_hotkey(DIALOG_LISTITEM * items, int choice, int selected) 168 { 169 bool result = FALSE; 170 171 if ((items[choice].state != 0) == selected) { 172 if (dlg_match_char(dlg_last_getc(), 173 (dialog_vars.no_tags 174 ? items[choice].text 175 : items[choice].name))) { 176 result = TRUE; 177 } 178 } 179 return result; 180 } 181 182 /* 183 * Return the next item from the list, staying in the same column. If no 184 * further movement is possible, return the same choice as given. 185 */ 186 static int 187 next_item(ALL_DATA * data, int choice, int selected) 188 { 189 int result = choice; 190 int n; 191 192 for (n = choice + 1; n < data->item_no; ++n) { 193 if ((data->items[n].state != 0) == selected) { 194 result = n; 195 break; 196 } 197 } 198 dlg_trace_msg("next_item(%d) ->%d\n", choice, result); 199 return result; 200 } 201 202 /* 203 * Translate a choice from items[] to a row-number in an unbounded column, 204 * starting at zero. 205 */ 206 static int 207 index2row(ALL_DATA * data, int choice, int selected) 208 { 209 int result = -1; 210 int n; 211 for (n = 0; n < data->item_no; ++n) { 212 if ((data->items[n].state != 0) == selected) { 213 ++result; 214 } 215 if (n == choice) 216 break; 217 } 218 return result; 219 } 220 221 /* 222 * Return the first choice from items[] for the given column. 223 */ 224 static int 225 first_item(ALL_DATA * data, int selected) 226 { 227 int result = -1; 228 int n; 229 230 for (n = 0; n < data->item_no; ++n) { 231 if ((data->items[n].state != 0) == selected) { 232 result = n; 233 break; 234 } 235 } 236 return result; 237 } 238 239 /* 240 * Return the last choice from items[] for the given column. 241 */ 242 static int 243 last_item(ALL_DATA * data, int selected) 244 { 245 int result = -1; 246 int n; 247 248 for (n = data->item_no - 1; n >= 0; --n) { 249 if ((data->items[n].state != 0) == selected) { 250 result = n; 251 break; 252 } 253 } 254 return result; 255 } 256 257 /* 258 * Convert a row-number back to an item number, i.e., index into items[]. 259 */ 260 static int 261 row2index(ALL_DATA * data, int row, int selected) 262 { 263 int result = -1; 264 int n; 265 for (n = 0; n < data->item_no; ++n) { 266 if ((data->items[n].state != 0) == selected) { 267 if (row-- <= 0) { 268 result = n; 269 break; 270 } 271 } 272 } 273 return result; 274 } 275 276 static int 277 skip_rows(ALL_DATA * data, int row, int skip, int selected) 278 { 279 int choice = row2index(data, row, selected); 280 int result = row; 281 int n; 282 if (skip > 0) { 283 for (n = choice + 1; n < data->item_no; ++n) { 284 if ((data->items[n].state != 0) == selected) { 285 ++result; 286 if (--skip <= 0) 287 break; 288 } 289 } 290 } else if (skip < 0) { 291 for (n = choice - 1; n >= 0; --n) { 292 if ((data->items[n].state != 0) == selected) { 293 --result; 294 if (++skip >= 0) 295 break; 296 } 297 } 298 } 299 return result; 300 } 301 302 /* 303 * Find the closest item in the given column starting with the given choice. 304 */ 305 static int 306 closest_item(ALL_DATA * data, int choice, int selected) 307 { 308 int prev = choice; 309 int next = choice; 310 int result = choice; 311 int n; 312 313 for (n = choice; n >= 0; --n) { 314 if ((data->items[n].state != 0) == selected) { 315 prev = n; 316 break; 317 } 318 } 319 for (n = choice; n < data->item_no; ++n) { 320 if ((data->items[n].state != 0) == selected) { 321 next = n; 322 break; 323 } 324 } 325 if (prev != choice) { 326 result = prev; 327 if (next != choice) { 328 if ((choice - prev) > (next - choice)) { 329 result = next; 330 } 331 } 332 } else if (next != choice) { 333 result = next; 334 } 335 return result; 336 } 337 338 static void 339 print_both(ALL_DATA * data, 340 int choice) 341 { 342 int selected; 343 int cur_y, cur_x; 344 WINDOW *dialog = wgetparent(data->list[0].win); 345 346 getyx(dialog, cur_y, cur_x); 347 for (selected = 0; selected < 2; ++selected) { 348 MY_DATA *moi = data->list + selected; 349 WINDOW *win = moi->win; 350 int thumb_top = index2row(data, moi->top_index, selected); 351 int thumb_max = index2row(data, -1, selected); 352 int thumb_end = thumb_top + getmaxy(win); 353 354 print_1_list(data, choice, selected); 355 356 dlg_mouse_setcode(selected * KEY_MAX); 357 dlg_draw_scrollbar(dialog, 358 (long) (moi->top_index), 359 (long) (thumb_top), 360 (long) MIN(thumb_end, thumb_max), 361 (long) thumb_max, 362 moi->box_x + data->check_x, 363 moi->box_x + getmaxx(win), 364 moi->box_y, 365 moi->box_y + getmaxy(win) + 1, 366 menubox_border2_attr, 367 menubox_border_attr); 368 } 369 (void) wmove(dialog, cur_y, cur_x); 370 dlg_mouse_setcode(0); 371 } 372 373 static void 374 set_top_item(ALL_DATA * data, int value, int selected) 375 { 376 if (value != data->list[selected].top_index) { 377 dlg_trace_msg("set top of %s column to %d\n", 378 selected ? "right" : "left", 379 value); 380 data->list[selected].top_index = value; 381 } 382 } 383 384 /* 385 * Adjust the top-index as needed to ensure that it and the given item are 386 * visible. 387 */ 388 static void 389 fix_top_item(ALL_DATA * data, int cur_item, int selected) 390 { 391 int top_item = data->list[selected].top_index; 392 int cur_row = index2row(data, cur_item, selected); 393 int top_row = index2row(data, top_item, selected); 394 395 if (cur_row < top_row) { 396 top_item = cur_item; 397 } else if ((cur_row - top_row) > data->use_height) { 398 top_item = row2index(data, cur_row + 1 - data->use_height, selected); 399 } 400 if (cur_row < data->use_height) { 401 top_item = row2index(data, 0, selected); 402 } 403 dlg_trace_msg("fix_top_item(cur_item %d, selected %d) ->top_item %d\n", 404 cur_item, selected, top_item); 405 set_top_item(data, top_item, selected); 406 } 407 408 /* 409 * This is an alternate interface to 'buildlist' which allows the application 410 * to read the list item states back directly without putting them in the 411 * output buffer. 412 */ 413 int 414 dlg_buildlist(const char *title, 415 const char *cprompt, 416 int height, 417 int width, 418 int list_height, 419 int item_no, 420 DIALOG_LISTITEM * items, 421 const char *states, 422 int order_mode, 423 int *current_item) 424 { 425 /* *INDENT-OFF* */ 426 static DLG_KEYS_BINDING binding[] = { 427 HELPKEY_BINDINGS, 428 ENTERKEY_BINDINGS, 429 DLG_KEYS_DATA( DLGK_FIELD_NEXT, KEY_RIGHT ), 430 DLG_KEYS_DATA( DLGK_FIELD_NEXT, TAB ), 431 DLG_KEYS_DATA( DLGK_FIELD_PREV, KEY_BTAB ), 432 DLG_KEYS_DATA( DLGK_FIELD_PREV, KEY_LEFT ), 433 DLG_KEYS_DATA( DLGK_ITEM_FIRST, KEY_HOME ), 434 DLG_KEYS_DATA( DLGK_ITEM_LAST, KEY_END ), 435 DLG_KEYS_DATA( DLGK_ITEM_LAST, KEY_LL ), 436 DLG_KEYS_DATA( DLGK_ITEM_NEXT, '+' ), 437 DLG_KEYS_DATA( DLGK_ITEM_NEXT, KEY_DOWN ), 438 DLG_KEYS_DATA( DLGK_ITEM_NEXT, CHR_NEXT ), 439 DLG_KEYS_DATA( DLGK_ITEM_PREV, '-' ), 440 DLG_KEYS_DATA( DLGK_ITEM_PREV, KEY_UP ), 441 DLG_KEYS_DATA( DLGK_ITEM_PREV, CHR_PREVIOUS ), 442 DLG_KEYS_DATA( DLGK_PAGE_NEXT, KEY_NPAGE ), 443 DLG_KEYS_DATA( DLGK_PAGE_NEXT, DLGK_MOUSE(KEY_NPAGE) ), 444 DLG_KEYS_DATA( DLGK_PAGE_NEXT, DLGK_MOUSE(KEY_NPAGE+KEY_MAX) ), 445 DLG_KEYS_DATA( DLGK_PAGE_PREV, KEY_PPAGE ), 446 DLG_KEYS_DATA( DLGK_PAGE_PREV, DLGK_MOUSE(KEY_PPAGE) ), 447 DLG_KEYS_DATA( DLGK_PAGE_PREV, DLGK_MOUSE(KEY_PPAGE+KEY_MAX) ), 448 DLG_KEYS_DATA( DLGK_GRID_LEFT, KEY_LEFTCOL ), 449 DLG_KEYS_DATA( DLGK_GRID_RIGHT, KEY_RIGHTCOL ), 450 END_KEYS_BINDING 451 }; 452 /* *INDENT-ON* */ 453 454 #ifdef KEY_RESIZE 455 int old_height = height; 456 int old_width = width; 457 #endif 458 ALL_DATA all; 459 MY_DATA *data = all.list; 460 int i, j, k, key2, found, x, y, cur_x, cur_y; 461 int key = 0, fkey; 462 bool save_visit = dialog_state.visit_items; 463 int button; 464 int cur_item; 465 int was_mouse; 466 int name_width, text_width, full_width, list_width; 467 int result = DLG_EXIT_UNKNOWN; 468 int num_states; 469 bool first = TRUE; 470 WINDOW *dialog; 471 char *prompt = dlg_strclone(cprompt); 472 const char **buttons = dlg_ok_labels(); 473 const char *widget_name = "buildlist"; 474 475 (void) order_mode; 476 477 dialog_state.plain_buttons = TRUE; 478 479 /* 480 * Unlike other uses of --visit-items, we have two windows to visit. 481 */ 482 if (dialog_state.visit_cols) 483 dialog_state.visit_cols = 2; 484 485 memset(&all, 0, sizeof(all)); 486 all.items = items; 487 all.item_no = item_no; 488 489 if (dialog_vars.default_item != 0) { 490 cur_item = dlg_default_listitem(items); 491 } else { 492 if ((cur_item = first_item(&all, 0)) < 0) 493 cur_item = first_item(&all, 1); 494 } 495 button = (dialog_state.visit_items 496 ? (items[cur_item].state ? sRIGHT : sLEFT) 497 : dlg_default_button()); 498 499 dlg_does_output(); 500 dlg_tab_correct_str(prompt); 501 502 #ifdef KEY_RESIZE 503 retry: 504 #endif 505 506 all.use_height = list_height; 507 all.use_width = (2 * (dlg_calc_list_width(item_no, items) 508 + 4 509 + 2 * MARGIN) 510 + 1); 511 all.use_width = MAX(26, all.use_width); 512 if (all.use_height == 0) { 513 /* calculate height without items (4) */ 514 dlg_auto_size(title, prompt, &height, &width, MIN_HIGH, all.use_width); 515 dlg_calc_listh(&height, &all.use_height, item_no); 516 } else { 517 dlg_auto_size(title, prompt, 518 &height, &width, 519 MIN_HIGH + all.use_height, all.use_width); 520 } 521 dlg_button_layout(buttons, &width); 522 dlg_print_size(height, width); 523 dlg_ctl_size(height, width); 524 525 /* we need at least two states */ 526 if (states == 0 || strlen(states) < 2) 527 states = " *"; 528 num_states = (int) strlen(states); 529 530 x = dlg_box_x_ordinate(width); 531 y = dlg_box_y_ordinate(height); 532 533 dialog = dlg_new_window(height, width, y, x); 534 dlg_register_window(dialog, widget_name, binding); 535 dlg_register_buttons(dialog, widget_name, buttons); 536 537 dlg_mouse_setbase(all.base_x = x, all.base_y = y); 538 539 dlg_draw_box2(dialog, 0, 0, height, width, dialog_attr, border_attr, border2_attr); 540 dlg_draw_bottom_box2(dialog, border_attr, border2_attr, dialog_attr); 541 dlg_draw_title(dialog, title); 542 543 (void) wattrset(dialog, dialog_attr); 544 dlg_print_autowrap(dialog, prompt, height, width); 545 546 list_width = (width - 6 * MARGIN - 2) / 2; 547 getyx(dialog, cur_y, cur_x); 548 data[0].box_y = cur_y + 1; 549 data[0].box_x = MARGIN + 1; 550 data[1].box_y = cur_y + 1; 551 data[1].box_x = data[0].box_x + 1 + 2 * MARGIN + list_width; 552 553 /* 554 * After displaying the prompt, we know how much space we really have. 555 * Limit the list to avoid overwriting the ok-button. 556 */ 557 if (all.use_height + MIN_HIGH > height - cur_y) 558 all.use_height = height - MIN_HIGH - cur_y; 559 if (all.use_height <= 0) 560 all.use_height = 1; 561 562 for (k = 0; k < 2; ++k) { 563 /* create new window for the list */ 564 data[k].win = dlg_sub_window(dialog, all.use_height, list_width, 565 y + data[k].box_y + 1, 566 x + data[k].box_x + 1); 567 568 /* draw a box around the list items */ 569 dlg_draw_box(dialog, data[k].box_y, data[k].box_x, 570 all.use_height + 2 * MARGIN, 571 list_width + 2 * MARGIN, 572 menubox_border_attr, menubox_border2_attr); 573 } 574 575 text_width = 0; 576 name_width = 0; 577 /* Find length of longest item to center buildlist */ 578 for (i = 0; i < item_no; i++) { 579 text_width = MAX(text_width, dlg_count_columns(items[i].text)); 580 name_width = MAX(name_width, dlg_count_columns(items[i].name)); 581 } 582 583 /* If the name+text is wider than the list is allowed, then truncate 584 * one or both of them. If the name is no wider than 1/4 of the list, 585 * leave it intact. 586 */ 587 all.use_width = (list_width - 6 * MARGIN); 588 if (dialog_vars.no_tags && !dialog_vars.no_items) { 589 full_width = MIN(all.use_width, text_width); 590 } else if (dialog_vars.no_items) { 591 full_width = MIN(all.use_width, name_width); 592 } else { 593 if (text_width >= 0 594 && name_width >= 0 595 && all.use_width > 0 596 && text_width + name_width > all.use_width) { 597 int need = (int) (0.25 * all.use_width); 598 if (name_width > need) { 599 int want = (int) (all.use_width * ((double) name_width) / 600 (text_width + name_width)); 601 name_width = (want > need) ? want : need; 602 } 603 text_width = all.use_width - name_width; 604 } 605 full_width = text_width + name_width; 606 } 607 608 all.check_x = (all.use_width - full_width) / 2; 609 all.item_x = ((dialog_vars.no_tags 610 ? 0 611 : (dialog_vars.no_items 612 ? 0 613 : (name_width + 2))) 614 + all.check_x); 615 616 /* ensure we are scrolled to show the current choice */ 617 j = MIN(all.use_height, item_no); 618 for (i = 0; i < 2; ++i) { 619 int top_item = 0; 620 if ((items[cur_item].state != 0) == i) { 621 top_item = cur_item - j + 1; 622 if (top_item < 0) 623 top_item = 0; 624 set_top_item(&all, top_item, i); 625 } else { 626 set_top_item(&all, 0, i); 627 } 628 } 629 630 /* register the new window, along with its borders */ 631 for (i = 0; i < 2; ++i) { 632 dlg_mouse_mkbigregion(data[i].box_y + 1, 633 data[i].box_x, 634 all.use_height, 635 list_width + 2, 636 2 * KEY_MAX + (i * (1 + all.use_height)), 637 1, 1, 1 /* by lines */ ); 638 } 639 640 dlg_draw_buttons(dialog, height - 2, 0, buttons, button, FALSE, width); 641 642 while (result == DLG_EXIT_UNKNOWN) { 643 int which = (items[cur_item].state != 0); 644 MY_DATA *moi = data + which; 645 int at_top = index2row(&all, moi->top_index, which); 646 int at_end = index2row(&all, -1, which); 647 int at_bot = skip_rows(&all, at_top, all.use_height, which); 648 649 dlg_trace_msg("\t** state %d:%d top %d (%d:%d:%d) %d\n", 650 cur_item, item_no - 1, 651 moi->top_index, 652 at_top, at_bot, at_end, 653 which); 654 655 if (first) { 656 print_both(&all, cur_item); 657 dlg_trace_win(dialog); 658 first = FALSE; 659 } 660 661 if (button < 0) { /* --visit-items */ 662 int cur_row = index2row(&all, cur_item, which); 663 cur_y = (data[which].box_y 664 + cur_row 665 + 1); 666 if (at_top > 0) 667 cur_y -= at_top; 668 cur_x = (data[which].box_x 669 + all.check_x + 1); 670 dlg_trace_msg("\t...visit row %d (%d,%d)\n", cur_row, cur_y, cur_x); 671 wmove(dialog, cur_y, cur_x); 672 } 673 674 key = dlg_mouse_wgetch(dialog, &fkey); 675 if (dlg_result_key(key, fkey, &result)) 676 break; 677 678 was_mouse = (fkey && is_DLGK_MOUSE(key)); 679 if (was_mouse) 680 key -= M_EVENT; 681 682 if (!was_mouse) { 683 ; 684 } else if (key >= 2 * KEY_MAX) { 685 i = (key - 2 * KEY_MAX) % (1 + all.use_height); 686 j = (key - 2 * KEY_MAX) / (1 + all.use_height); 687 k = row2index(&all, i + at_top, j); 688 dlg_trace_msg("MOUSE column %d, row %d ->item %d\n", j, i, k); 689 if (k >= 0 && j < 2) { 690 if (j != which) { 691 /* 692 * Mouse click was in the other column. 693 */ 694 moi = data + j; 695 fix_top_item(&all, k, j); 696 } 697 which = j; 698 at_top = index2row(&all, moi->top_index, which); 699 at_bot = skip_rows(&all, at_top, all.use_height, which); 700 cur_item = k; 701 print_both(&all, cur_item); 702 key = KEY_TOGGLE; /* force the selected item to toggle */ 703 } else { 704 beep(); 705 continue; 706 } 707 fkey = FALSE; 708 } else if (key >= KEY_MIN) { 709 if (key > KEY_MAX) { 710 if (which == 0) { 711 key = KEY_RIGHTCOL; /* switch to right-column */ 712 fkey = FALSE; 713 } else { 714 key -= KEY_MAX; 715 } 716 } else { 717 if (which == 1) { 718 key = KEY_LEFTCOL; /* switch to left-column */ 719 fkey = FALSE; 720 } 721 } 722 key = dlg_lookup_key(dialog, key, &fkey); 723 } 724 725 /* 726 * A space toggles the item status. Normally we put the cursor on 727 * the next available item in the same column. But if there are no 728 * more items in the column, move the cursor to the other column. 729 */ 730 if (key == KEY_TOGGLE) { 731 int new_choice; 732 int new_state = items[cur_item].state + 1; 733 734 if ((new_choice = next_item(&all, cur_item, which)) == cur_item) { 735 new_choice = prev_item(&all, cur_item, which); 736 } 737 dlg_trace_msg("cur_item %d, new_choice:%d\n", cur_item, new_choice); 738 if (new_state >= num_states) 739 new_state = 0; 740 741 items[cur_item].state = new_state; 742 if (cur_item == moi->top_index) { 743 set_top_item(&all, new_choice, which); 744 } 745 746 if (new_choice >= 0) { 747 fix_top_item(&all, cur_item, !which); 748 cur_item = new_choice; 749 } 750 print_both(&all, cur_item); 751 dlg_trace_win(dialog); 752 continue; /* wait for another key press */ 753 } 754 755 /* 756 * Check if key pressed matches first character of any item tag in 757 * list. If there is more than one match, we will cycle through 758 * each one as the same key is pressed repeatedly. 759 */ 760 found = FALSE; 761 if (!fkey) { 762 if (button < 0 || !dialog_state.visit_items) { 763 for (j = cur_item + 1; j < item_no; j++) { 764 if (check_hotkey(items, j, which)) { 765 found = TRUE; 766 i = j; 767 break; 768 } 769 } 770 if (!found) { 771 for (j = 0; j <= cur_item; j++) { 772 if (check_hotkey(items, j, which)) { 773 found = TRUE; 774 i = j; 775 break; 776 } 777 } 778 } 779 if (found) 780 dlg_flush_getc(); 781 } else if ((j = dlg_char_to_button(key, buttons)) >= 0) { 782 button = j; 783 ungetch('\n'); 784 continue; 785 } 786 } 787 788 /* 789 * A single digit (1-9) positions the selection to that line in the 790 * current screen. 791 */ 792 if (!found 793 && (key <= '9') 794 && (key > '0') 795 && (key - '1' < at_bot)) { 796 found = TRUE; 797 i = key - '1'; 798 } 799 800 if (!found && fkey) { 801 switch (key) { 802 case DLGK_FIELD_PREV: 803 if ((button == sRIGHT) && dialog_state.visit_items) { 804 key = DLGK_GRID_LEFT; 805 button = sLEFT; 806 } else { 807 button = dlg_prev_button(buttons, button); 808 dlg_draw_buttons(dialog, height - 2, 0, buttons, button, 809 FALSE, width); 810 if (button == sRIGHT) { 811 key = DLGK_GRID_RIGHT; 812 } else { 813 continue; 814 } 815 } 816 break; 817 case DLGK_FIELD_NEXT: 818 if ((button == sLEFT) && dialog_state.visit_items) { 819 key = DLGK_GRID_RIGHT; 820 button = sRIGHT; 821 } else { 822 button = dlg_next_button(buttons, button); 823 dlg_draw_buttons(dialog, height - 2, 0, buttons, button, 824 FALSE, width); 825 if (button == sLEFT) { 826 key = DLGK_GRID_LEFT; 827 } else { 828 continue; 829 } 830 } 831 break; 832 } 833 } 834 835 if (!found && fkey) { 836 i = cur_item; 837 found = TRUE; 838 switch (key) { 839 case DLGK_GRID_LEFT: 840 i = closest_item(&all, cur_item, 0); 841 fix_top_item(&all, i, 0); 842 break; 843 case DLGK_GRID_RIGHT: 844 i = closest_item(&all, cur_item, 1); 845 fix_top_item(&all, i, 1); 846 break; 847 case DLGK_PAGE_PREV: 848 if (cur_item > moi->top_index) { 849 i = moi->top_index; 850 } else if (moi->top_index != 0) { 851 int temp = at_top; 852 if ((temp -= all.use_height) < 0) 853 temp = 0; 854 i = row2index(&all, temp, which); 855 } 856 break; 857 case DLGK_PAGE_NEXT: 858 if ((at_end - at_bot) < all.use_height) { 859 i = next_item(&all, 860 row2index(&all, at_end, which), 861 which); 862 } else { 863 i = next_item(&all, 864 row2index(&all, at_bot, which), 865 which); 866 at_top = at_bot; 867 set_top_item(&all, 868 next_item(&all, 869 row2index(&all, at_top, which), 870 which), 871 which); 872 at_bot = skip_rows(&all, at_top, all.use_height, which); 873 at_bot = MIN(at_bot, at_end); 874 } 875 break; 876 case DLGK_ITEM_FIRST: 877 i = first_item(&all, which); 878 break; 879 case DLGK_ITEM_LAST: 880 i = last_item(&all, which); 881 break; 882 case DLGK_ITEM_PREV: 883 i = prev_item(&all, cur_item, which); 884 if (stop_prev(&all, cur_item, which)) 885 continue; 886 break; 887 case DLGK_ITEM_NEXT: 888 i = next_item(&all, cur_item, which); 889 break; 890 default: 891 found = FALSE; 892 break; 893 } 894 } 895 896 if (found) { 897 if (i != cur_item) { 898 int now_at = index2row(&all, i, which); 899 int oops = item_no; 900 int old_item; 901 902 dlg_trace_msg("<--CHOICE %d\n", i); 903 dlg_trace_msg("<--topITM %d\n", moi->top_index); 904 dlg_trace_msg("<--now_at %d\n", now_at); 905 dlg_trace_msg("<--at_top %d\n", at_top); 906 dlg_trace_msg("<--at_bot %d\n", at_bot); 907 908 if (now_at >= at_bot) { 909 while (now_at >= at_bot) { 910 if ((at_bot - at_top) >= all.use_height) { 911 set_top_item(&all, 912 next_item(&all, moi->top_index, which), 913 which); 914 } 915 at_top = index2row(&all, moi->top_index, which); 916 at_bot = skip_rows(&all, at_top, all.use_height, which); 917 918 dlg_trace_msg("...at_bot %d (now %d vs %d)\n", 919 at_bot, now_at, at_end); 920 dlg_trace_msg("...topITM %d\n", moi->top_index); 921 dlg_trace_msg("...at_top %d (diff %d)\n", at_top, 922 at_bot - at_top); 923 924 if (at_bot >= at_end) { 925 /* 926 * If we bumped into the end, move the top-item 927 * down by one line so that we can display the 928 * last item in the list. 929 */ 930 if ((at_bot - at_top) > all.use_height) { 931 set_top_item(&all, 932 next_item(&all, moi->top_index, which), 933 which); 934 } else if (at_top > 0 && 935 (at_bot - at_top) >= all.use_height) { 936 set_top_item(&all, 937 next_item(&all, moi->top_index, which), 938 which); 939 } 940 break; 941 } 942 if (--oops < 0) { 943 dlg_trace_msg("OOPS-forward\n"); 944 break; 945 } 946 } 947 } else if (now_at < at_top) { 948 while (now_at < at_top) { 949 old_item = moi->top_index; 950 set_top_item(&all, 951 prev_item(&all, moi->top_index, which), 952 which); 953 at_top = index2row(&all, moi->top_index, which); 954 955 dlg_trace_msg("...at_top %d (now %d)\n", at_top, now_at); 956 dlg_trace_msg("...topITM %d\n", moi->top_index); 957 958 if (moi->top_index >= old_item) 959 break; 960 if (at_top <= now_at) 961 break; 962 if (--oops < 0) { 963 dlg_trace_msg("OOPS-backward\n"); 964 break; 965 } 966 } 967 } 968 dlg_trace_msg("-->now_at %d\n", now_at); 969 cur_item = i; 970 print_both(&all, cur_item); 971 } 972 dlg_trace_win(dialog); 973 continue; /* wait for another key press */ 974 } 975 976 if (fkey) { 977 switch (key) { 978 case DLGK_ENTER: 979 result = dlg_enter_buttoncode(button); 980 break; 981 #ifdef KEY_RESIZE 982 case KEY_RESIZE: 983 /* reset data */ 984 height = old_height; 985 width = old_width; 986 /* repaint */ 987 dlg_clear(); 988 dlg_del_window(dialog); 989 refresh(); 990 dlg_mouse_free_regions(); 991 goto retry; 992 #endif 993 default: 994 if (was_mouse) { 995 if ((key2 = dlg_ok_buttoncode(key)) >= 0) { 996 result = key2; 997 break; 998 } 999 beep(); 1000 } 1001 } 1002 } else { 1003 beep(); 1004 } 1005 } 1006 1007 dialog_state.visit_cols = save_visit; 1008 dlg_del_window(dialog); 1009 dlg_mouse_free_regions(); 1010 free(prompt); 1011 *current_item = cur_item; 1012 return result; 1013 } 1014 1015 /* 1016 * Display a dialog box with a list of options that can be turned on or off 1017 */ 1018 int 1019 dialog_buildlist(const char *title, 1020 const char *cprompt, 1021 int height, 1022 int width, 1023 int list_height, 1024 int item_no, 1025 char **items, 1026 int order_mode) 1027 { 1028 int result; 1029 int i, j; 1030 DIALOG_LISTITEM *listitems; 1031 bool separate_output = dialog_vars.separate_output; 1032 bool show_status = FALSE; 1033 int current = 0; 1034 char *help_result; 1035 1036 listitems = dlg_calloc(DIALOG_LISTITEM, (size_t) item_no + 1); 1037 assert_ptr(listitems, "dialog_buildlist"); 1038 1039 for (i = j = 0; i < item_no; ++i) { 1040 listitems[i].name = items[j++]; 1041 listitems[i].text = (dialog_vars.no_items 1042 ? dlg_strempty() 1043 : items[j++]); 1044 listitems[i].state = !dlg_strcmp(items[j++], "on"); 1045 listitems[i].help = ((dialog_vars.item_help) 1046 ? items[j++] 1047 : dlg_strempty()); 1048 } 1049 dlg_align_columns(&listitems[0].text, (int) sizeof(DIALOG_LISTITEM), item_no); 1050 1051 result = dlg_buildlist(title, 1052 cprompt, 1053 height, 1054 width, 1055 list_height, 1056 item_no, 1057 listitems, 1058 NULL, 1059 order_mode, 1060 ¤t); 1061 1062 switch (result) { 1063 case DLG_EXIT_OK: /* FALLTHRU */ 1064 case DLG_EXIT_EXTRA: 1065 show_status = TRUE; 1066 break; 1067 case DLG_EXIT_HELP: 1068 dlg_add_help_listitem(&result, &help_result, &listitems[current]); 1069 if ((show_status = dialog_vars.help_status)) { 1070 if (separate_output) { 1071 dlg_add_string(help_result); 1072 dlg_add_separator(); 1073 } else { 1074 dlg_add_quoted(help_result); 1075 } 1076 } else { 1077 dlg_add_string(help_result); 1078 } 1079 break; 1080 } 1081 1082 if (show_status) { 1083 for (i = 0; i < item_no; i++) { 1084 if (listitems[i].state) { 1085 if (separate_output) { 1086 dlg_add_string(listitems[i].name); 1087 dlg_add_separator(); 1088 } else { 1089 if (dlg_need_separator()) 1090 dlg_add_separator(); 1091 dlg_add_quoted(listitems[i].name); 1092 } 1093 } 1094 } 1095 dlg_add_last_key(-1); 1096 } 1097 1098 dlg_free_columns(&listitems[0].text, (int) sizeof(DIALOG_LISTITEM), item_no); 1099 free(listitems); 1100 return result; 1101 } 1102