1 /* $NetBSD: internals.c,v 1.17 2013/10/18 19:53:59 christos Exp $ */ 2 3 /*- 4 * Copyright (c) 1998-1999 Brett Lymn (blymn@baea.com.au, brett_lymn@yahoo.com.au) 5 * All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 2. The name of the author may not be used to endorse or promote products 13 * derived from this software without specific prior written permission 14 * 15 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 16 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 17 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 18 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 19 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 20 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 21 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 22 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 24 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 * 26 * 27 */ 28 29 #include <sys/cdefs.h> 30 __RCSID("$NetBSD: internals.c,v 1.17 2013/10/18 19:53:59 christos Exp $"); 31 32 #include <menu.h> 33 #include <ctype.h> 34 #include <stdlib.h> 35 #include <string.h> 36 #include "internals.h" 37 38 /* internal function prototypes */ 39 static void 40 _menui_calc_neighbours(MENU *menu, int item_no); 41 static void _menui_redraw_menu(MENU *menu, int old_top_row, int old_cur_item); 42 43 /* 44 * Link all the menu items together to speed up navigation. We need 45 * to calculate the widest item entry, then work out how many columns 46 * of items the window will accommodate and then how many rows there will 47 * be. Once the layout is determined the neighbours of each item is 48 * calculated and the item structures updated. 49 */ 50 int 51 _menui_stitch_items(MENU *menu) 52 { 53 int i, row_major; 54 55 row_major = ((menu->opts & O_ROWMAJOR) == O_ROWMAJOR); 56 57 if (menu->posted == 1) 58 return E_POSTED; 59 if (menu->items == NULL) 60 return E_BAD_ARGUMENT; 61 62 menu->item_rows = menu->item_count / menu->cols; 63 menu->item_cols = menu->cols; 64 if (menu->item_count > (menu->item_rows * menu->item_cols)) 65 menu->item_rows += 1; 66 67 _menui_max_item_size(menu); 68 69 for (i = 0; i < menu->item_count; i++) { 70 /* fill in the row and column value of the item */ 71 if (row_major) { 72 menu->items[i]->row = i / menu->item_cols; 73 menu->items[i]->col = i % menu->item_cols; 74 } else { 75 menu->items[i]->row = i % menu->item_rows; 76 menu->items[i]->col = i / menu->item_rows; 77 } 78 79 _menui_calc_neighbours(menu, i); 80 } 81 82 return E_OK; 83 } 84 85 /* 86 * Calculate the neighbours for an item in menu. 87 */ 88 static void 89 _menui_calc_neighbours(MENU *menu, int item_no) 90 { 91 int neighbour, cycle, row_major, edge; 92 ITEM *item; 93 94 row_major = ((menu->opts & O_ROWMAJOR) == O_ROWMAJOR); 95 cycle = ((menu->opts & O_NONCYCLIC) != O_NONCYCLIC); 96 item = menu->items[item_no]; 97 98 if (menu->item_rows < 2) { 99 if (cycle) { 100 item->up = item; 101 item->down = item; 102 } else { 103 item->up = NULL; 104 item->down = NULL; 105 } 106 } else { 107 108 /* up */ 109 if (menu->item_cols < 2) { 110 if (item_no == 0) { 111 if (cycle) 112 item->up = 113 menu->items[menu->item_count - 1]; 114 else 115 item->up = NULL; 116 } else 117 item->up = menu->items[item_no - 1]; 118 } else { 119 edge = 0; 120 if (row_major) { 121 if (item->row == 0) { 122 neighbour = 123 (menu->item_rows - 1) * menu->item_cols 124 + item->col; 125 if (neighbour >= menu->item_count) 126 neighbour -= menu->item_cols; 127 edge = 1; 128 } else 129 neighbour = item_no - menu->item_cols; 130 } else { 131 if (item->row == 0) { 132 neighbour = menu->item_rows * item->col 133 + menu->item_rows - 1; 134 if (neighbour >= menu->item_count) 135 neighbour = menu->item_count - 1; 136 edge = 1; 137 } else 138 neighbour = item_no - 1; 139 } 140 141 142 item->up = menu->items[neighbour]; 143 if ((!cycle) && (edge == 1)) 144 item->up = NULL; 145 } 146 147 /* Down */ 148 if (menu->item_cols < 2) { 149 if (item_no == (menu->item_count - 1)) { 150 if (cycle) 151 item->down = menu->items[0]; 152 else 153 item->down = NULL; 154 } else 155 item->down = menu->items[item_no + 1]; 156 } else { 157 edge = 0; 158 if (row_major) { 159 if (item->row == menu->item_rows - 1) { 160 neighbour = item->col; 161 edge = 1; 162 } else { 163 neighbour = item_no + menu->item_cols; 164 if (neighbour >= menu->item_count) { 165 neighbour = item->col; 166 edge = 1; 167 } 168 } 169 } else { 170 if (item->row == menu->item_rows - 1) { 171 neighbour = item->col * menu->item_rows; 172 edge = 1; 173 } else { 174 neighbour = item_no + 1; 175 if (neighbour >= menu->item_count) { 176 neighbour = item->col 177 * menu->item_rows; 178 edge = 1; 179 } 180 } 181 } 182 183 item->down = menu->items[neighbour]; 184 if ((!cycle) && (edge == 1)) 185 item->down = NULL; 186 } 187 } 188 189 if (menu->item_cols < 2) { 190 if (cycle) { 191 item->left = item; 192 item->right = item; 193 } else { 194 item->left = NULL; 195 item->right = NULL; 196 } 197 } else { 198 /* left */ 199 if (menu->item_rows < 2) { 200 if (item_no == 0) { 201 if (cycle) 202 item->left = 203 menu->items[menu->item_count - 1]; 204 else 205 item->left = NULL; 206 } else 207 item->left = menu->items[item_no - 1]; 208 } else { 209 edge = 0; 210 if (row_major) { 211 if (item->col == 0) { 212 neighbour = item_no + menu->cols - 1; 213 if (neighbour >= menu->item_count) 214 neighbour = menu->item_count - 1; 215 edge = 1; 216 } else 217 neighbour = item_no - 1; 218 } else { 219 if (item->col == 0) { 220 neighbour = menu->item_rows 221 * (menu->item_cols - 1) + item->row; 222 if (neighbour >= menu->item_count) 223 neighbour -= menu->item_rows; 224 edge = 1; 225 } else 226 neighbour = item_no - menu->item_rows; 227 } 228 229 item->left = menu->items[neighbour]; 230 if ((!cycle) && (edge == 1)) 231 item->left = NULL; 232 } 233 234 /* right */ 235 if (menu->item_rows < 2) { 236 if (item_no == menu->item_count - 1) { 237 if (cycle) 238 item->right = menu->items[0]; 239 else 240 item->right = NULL; 241 } else 242 item->right = menu->items[item_no + 1]; 243 } else { 244 edge = 0; 245 if (row_major) { 246 if (item->col == menu->item_cols - 1) { 247 neighbour = item_no - menu->item_cols 248 + 1; 249 edge = 1; 250 } else if (item_no == menu->item_count - 1) { 251 neighbour = item->row * menu->item_cols; 252 edge = 1; 253 } else 254 neighbour = item_no + 1; 255 } else { 256 if (item->col == menu->item_cols - 1) { 257 neighbour = item->row; 258 edge = 1; 259 } else { 260 neighbour = item_no + menu->item_rows; 261 if (neighbour >= menu->item_count) { 262 neighbour = item->row; 263 edge = 1; 264 } 265 } 266 } 267 268 item->right = menu->items[neighbour]; 269 if ((!cycle) && (edge == 1)) 270 item->right = NULL; 271 } 272 } 273 } 274 275 /* 276 * Goto the item pointed to by item and adjust the menu structure 277 * accordingly. Call the term and init functions if required. 278 */ 279 int 280 _menui_goto_item(MENU *menu, ITEM *item, int new_top_row) 281 { 282 int old_top_row = menu->top_row, old_cur_item = menu->cur_item; 283 284 /* If we get a null then the menu is not cyclic so deny request */ 285 if (item == NULL) 286 return E_REQUEST_DENIED; 287 288 menu->in_init = 1; 289 if (menu->top_row != new_top_row) { 290 if ((menu->posted == 1) && (menu->menu_term != NULL)) 291 menu->menu_term(menu); 292 menu->top_row = new_top_row; 293 294 if ((menu->posted == 1) && (menu->menu_init != NULL)) 295 menu->menu_init(menu); 296 } 297 298 /* this looks like wasted effort but it can happen.... */ 299 if (menu->cur_item != item->index) { 300 301 if ((menu->posted == 1) && (menu->item_term != NULL)) 302 menu->item_term(menu); 303 304 menu->cur_item = item->index; 305 menu->cur_row = item->row; 306 menu->cur_col = item->col; 307 308 if (menu->posted == 1) 309 _menui_redraw_menu(menu, old_top_row, old_cur_item); 310 311 if ((menu->posted == 1) && (menu->item_init != NULL)) 312 menu->item_init(menu); 313 314 } 315 316 menu->in_init = 0; 317 return E_OK; 318 } 319 320 /* 321 * Attempt to match items with the pattern buffer in the direction given 322 * by iterating over the menu items. If a match is found return E_OK 323 * otherwise return E_NO_MATCH 324 */ 325 int 326 _menui_match_items(MENU *menu, int direction, int *item_matched) 327 { 328 int i, caseless; 329 330 caseless = ((menu->opts & O_IGNORECASE) == O_IGNORECASE); 331 332 i = menu->cur_item; 333 if (direction == MATCH_NEXT_FORWARD) { 334 if (++i >= menu->item_count) i = 0; 335 } else if (direction == MATCH_NEXT_REVERSE) { 336 if (--i < 0) i = menu->item_count - 1; 337 } 338 339 340 do { 341 if (menu->items[i]->name.length >= menu->plen) { 342 /* no chance if pattern is longer */ 343 if (caseless) { 344 if (strncasecmp(menu->items[i]->name.string, 345 menu->pattern, 346 (size_t) menu->plen) == 0) { 347 *item_matched = i; 348 menu->match_len = menu->plen; 349 return E_OK; 350 } 351 } else { 352 if (strncmp(menu->items[i]->name.string, 353 menu->pattern, 354 (size_t) menu->plen) == 0) { 355 *item_matched = i; 356 menu->match_len = menu->plen; 357 return E_OK; 358 } 359 } 360 } 361 362 if ((direction == MATCH_FORWARD) || 363 (direction == MATCH_NEXT_FORWARD)) { 364 if (++i >= menu->item_count) i = 0; 365 } else { 366 if (--i <= 0) i = menu->item_count - 1; 367 } 368 } while (i != menu->cur_item); 369 370 menu->match_len = 0; /* match did not succeed - kill the match len. */ 371 return E_NO_MATCH; 372 } 373 374 /* 375 * Attempt to match the pattern buffer against the items. If c is a 376 * printable character then add it to the pattern buffer prior to 377 * performing the match. Direction determines the direction of matching. 378 * If the match is successful update the item_matched variable with the 379 * index of the item that matched the pattern. 380 */ 381 int 382 _menui_match_pattern(MENU *menu, int c, int direction, int *item_matched) 383 { 384 if (menu == NULL) 385 return E_BAD_ARGUMENT; 386 if (menu->items == NULL) 387 return E_BAD_ARGUMENT; 388 if (*menu->items == NULL) 389 return E_BAD_ARGUMENT; 390 391 if (isprint(c)) { 392 /* add char to buffer - first allocate room for it */ 393 if ((menu->pattern = (char *) 394 realloc(menu->pattern, 395 menu->plen + sizeof(char) + 396 ((menu->plen > 0)? 0 : 1))) 397 == NULL) 398 return E_SYSTEM_ERROR; 399 menu->pattern[menu->plen] = c; 400 menu->pattern[++menu->plen] = '\0'; 401 402 /* there is no chance of a match if pattern is longer 403 than all the items */ 404 if (menu->plen >= menu->max_item_width) { 405 menu->pattern[--menu->plen] = '\0'; 406 return E_NO_MATCH; 407 } 408 409 if (_menui_match_items(menu, direction, 410 item_matched) == E_NO_MATCH) { 411 menu->pattern[--menu->plen] = '\0'; 412 return E_NO_MATCH; 413 } else 414 return E_OK; 415 } else { 416 if (_menui_match_items(menu, direction, 417 item_matched) == E_OK) { 418 return E_OK; 419 } else { 420 return E_NO_MATCH; 421 } 422 } 423 } 424 425 /* 426 * Draw an item in the subwindow complete with appropriate highlighting. 427 */ 428 void 429 _menui_draw_item(MENU *menu, int item) 430 { 431 int j, pad_len, mark_len; 432 433 mark_len = max(menu->mark.length, menu->unmark.length); 434 435 wmove(menu->scrwin, 436 menu->items[item]->row - menu->top_row, 437 menu->items[item]->col * (menu->col_width + 1)); 438 439 if (menu->cur_item == item) 440 wattrset(menu->scrwin, menu->fore); 441 if ((menu->items[item]->opts & O_SELECTABLE) != O_SELECTABLE) 442 wattron(menu->scrwin, menu->grey); 443 444 /* deal with the menu mark, if one is set. 445 * We mark the selected items and write blanks for 446 * all others unless the menu unmark string is set in which 447 * case the unmark string is written. 448 */ 449 if ((menu->items[item]->selected == 1) || 450 (((menu->opts & O_ONEVALUE) == O_ONEVALUE) && 451 (menu->cur_item == item))) { 452 if (menu->mark.string != NULL) { 453 for (j = 0; j < menu->mark.length; j++) { 454 waddch(menu->scrwin, 455 menu->mark.string[j]); 456 } 457 } 458 /* blank any length difference between mark & unmark */ 459 for (j = menu->mark.length; j < mark_len; j++) 460 waddch(menu->scrwin, ' '); 461 } else { 462 if (menu->unmark.string != NULL) { 463 for (j = 0; j < menu->unmark.length; j++) { 464 waddch(menu->scrwin, 465 menu->unmark.string[j]); 466 } 467 } 468 /* blank any length difference between mark & unmark */ 469 for (j = menu->unmark.length; j < mark_len; j++) 470 waddch(menu->scrwin, ' '); 471 } 472 473 /* add the menu name */ 474 for (j=0; j < menu->items[item]->name.length; j++) 475 waddch(menu->scrwin, 476 menu->items[item]->name.string[j]); 477 478 pad_len = menu->col_width - menu->items[item]->name.length 479 - mark_len - 1; 480 if ((menu->opts & O_SHOWDESC) == O_SHOWDESC) { 481 pad_len -= menu->items[item]->description.length - 1; 482 for (j = 0; j < pad_len; j++) 483 waddch(menu->scrwin, menu->pad); 484 for (j = 0; j < menu->items[item]->description.length; j++) { 485 waddch(menu->scrwin, 486 menu->items[item]->description.string[j]); 487 } 488 } else { 489 for (j = 0; j < pad_len; j++) 490 waddch(menu->scrwin, ' '); 491 } 492 menu->items[item]->visible = 1; 493 494 /* kill any special attributes... */ 495 wattrset(menu->scrwin, menu->back); 496 497 /* 498 * Fill in the spacing between items, annoying but it looks 499 * odd if the menu items are inverse because the spacings do not 500 * have the same attributes as the items. 501 */ 502 if ((menu->items[item]->col > 0) && 503 (menu->items[item]->col < (menu->item_cols - 1))) { 504 wmove(menu->scrwin, 505 menu->items[item]->row - menu->top_row, 506 menu->items[item]->col * (menu->col_width + 1) - 1); 507 waddch(menu->scrwin, ' '); 508 } 509 510 /* and position the cursor nicely */ 511 pos_menu_cursor(menu); 512 } 513 514 /* 515 * Draw the menu in the subwindow provided. 516 */ 517 int 518 _menui_draw_menu(MENU *menu) 519 { 520 int rowmajor, i, j, k, row = -1, stride; 521 int incr, cur_row, offset, row_count; 522 523 rowmajor = ((menu->opts & O_ROWMAJOR) == O_ROWMAJOR); 524 525 if (rowmajor) { 526 stride = 1; 527 incr = menu->item_cols; 528 } else { 529 stride = menu->item_rows; 530 incr = 1; 531 } 532 row_count = 0; 533 534 for (i = 0; i < menu->item_count; i += incr) { 535 if (menu->items[i]->row == menu->top_row) 536 break; 537 row_count++; 538 for (j = 0; j < menu->item_cols; j++) { 539 offset = j * stride + i; 540 if (offset >= menu->item_count) 541 break; /* done */ 542 menu->items[offset]->visible = 0; 543 } 544 } 545 546 wmove(menu->scrwin, 0, 0); 547 548 menu->col_width = getmaxx(menu->scrwin) / menu->cols; 549 550 for (cur_row = 0; cur_row < menu->rows; cur_row++) { 551 for (j = 0; j < menu->cols; j++) { 552 offset = j * stride + i; 553 if (offset >= menu->item_count) { 554 /* no more items to draw, write background blanks */ 555 wattrset(menu->scrwin, menu->back); 556 if (row < 0) { 557 row = menu->items[menu->item_count - 1]->row; 558 } 559 560 wmove(menu->scrwin, cur_row, 561 j * (menu->col_width + 1)); 562 for (k = 0; k < menu->col_width; k++) 563 waddch(menu->scrwin, ' '); 564 } else { 565 _menui_draw_item(menu, offset); 566 } 567 } 568 569 i += incr; 570 row_count++; 571 } 572 573 if (row_count < menu->item_rows) { 574 for (cur_row = row_count; cur_row < menu->item_rows; cur_row++) { 575 for (j = 0; j < menu->item_cols; j++) { 576 offset = j * stride + i; 577 if (offset >= menu->item_count) 578 break; /* done */ 579 menu->items[offset]->visible = 0; 580 } 581 i += incr; 582 } 583 } 584 585 return E_OK; 586 } 587 588 589 /* 590 * Calculate the widest menu item and stash it in the menu struct. 591 * 592 */ 593 void 594 _menui_max_item_size(MENU *menu) 595 { 596 int i, with_desc, width; 597 598 with_desc = ((menu->opts & O_SHOWDESC) == O_SHOWDESC); 599 600 for (i = 0; i < menu->item_count; i++) { 601 width = menu->items[i]->name.length 602 + max(menu->mark.length, menu->unmark.length); 603 if (with_desc) 604 width += menu->items[i]->description.length + 1; 605 606 menu->max_item_width = max(menu->max_item_width, width); 607 } 608 } 609 610 611 /* 612 * Redraw the menu on the screen. If the current item has changed then 613 * unhighlight the old item and highlight the new one. 614 */ 615 static void 616 _menui_redraw_menu(MENU *menu, int old_top_row, int old_cur_item) 617 { 618 619 if (menu->top_row != old_top_row) { 620 /* top row changed - redo the whole menu 621 * XXXX this could be improved if we had wscrl implemented. 622 623 * XXXX we could scroll the window and just fill in the 624 * XXXX changed lines. 625 */ 626 wclear(menu->scrwin); 627 _menui_draw_menu(menu); 628 } else { 629 if (menu->cur_item != old_cur_item) { 630 /* redo the old item as a normal one. */ 631 _menui_draw_item(menu, old_cur_item); 632 } 633 /* and then redraw the current item */ 634 _menui_draw_item(menu, menu->cur_item); 635 } 636 } 637