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