1 /* $NetBSD: internals.c,v 1.15 2012/06/27 11:53:36 blymn 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.15 2012/06/27 11:53:36 blymn 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 if (menu->mark.string != NULL) { 451 for (j = 0; j < menu->mark.length; j++) { 452 waddch(menu->scrwin, 453 menu->mark.string[j]); 454 } 455 } 456 /* blank any length difference between mark & unmark */ 457 for (j = menu->mark.length; j < mark_len; j++) 458 waddch(menu->scrwin, ' '); 459 } else { 460 if (menu->unmark.string != NULL) { 461 for (j = 0; j < menu->unmark.length; j++) { 462 waddch(menu->scrwin, 463 menu->unmark.string[j]); 464 } 465 } 466 /* blank any length difference between mark & unmark */ 467 for (j = menu->unmark.length; j < mark_len; j++) 468 waddch(menu->scrwin, ' '); 469 } 470 471 /* add the menu name */ 472 for (j=0; j < menu->items[item]->name.length; j++) 473 waddch(menu->scrwin, 474 menu->items[item]->name.string[j]); 475 476 pad_len = menu->col_width - menu->items[item]->name.length 477 - mark_len - 1; 478 if ((menu->opts & O_SHOWDESC) == O_SHOWDESC) { 479 pad_len -= menu->items[item]->description.length - 1; 480 for (j = 0; j < pad_len; j++) 481 waddch(menu->scrwin, menu->pad); 482 for (j = 0; j < menu->items[item]->description.length; j++) { 483 waddch(menu->scrwin, 484 menu->items[item]->description.string[j]); 485 } 486 } else { 487 for (j = 0; j < pad_len; j++) 488 waddch(menu->scrwin, ' '); 489 } 490 menu->items[item]->visible = 1; 491 492 /* kill any special attributes... */ 493 wattrset(menu->scrwin, menu->back); 494 495 /* 496 * Fill in the spacing between items, annoying but it looks 497 * odd if the menu items are inverse because the spacings do not 498 * have the same attributes as the items. 499 */ 500 if ((menu->items[item]->col > 0) && 501 (menu->items[item]->col < (menu->item_cols - 1))) { 502 wmove(menu->scrwin, 503 menu->items[item]->row - menu->top_row, 504 menu->items[item]->col * (menu->col_width + 1) - 1); 505 waddch(menu->scrwin, ' '); 506 } 507 508 /* and position the cursor nicely */ 509 pos_menu_cursor(menu); 510 } 511 512 /* 513 * Draw the menu in the subwindow provided. 514 */ 515 int 516 _menui_draw_menu(MENU *menu) 517 { 518 int rowmajor, i, j, k, row = -1, col = -1, stride; 519 int incr, cur_row, offset, row_count; 520 521 rowmajor = ((menu->opts & O_ROWMAJOR) == O_ROWMAJOR); 522 523 if (rowmajor) { 524 stride = 1; 525 incr = menu->item_cols; 526 } else { 527 stride = menu->item_rows; 528 incr = 1; 529 } 530 row_count = 0; 531 532 for (i = 0; i < menu->item_count; i += incr) { 533 if (menu->items[i]->row == menu->top_row) 534 break; 535 row_count++; 536 for (j = 0; j < menu->item_cols; j++) { 537 offset = j * stride + i; 538 if (offset >= menu->item_count) 539 break; /* done */ 540 menu->items[offset]->visible = 0; 541 } 542 } 543 544 wmove(menu->scrwin, 0, 0); 545 546 menu->col_width = getmaxx(menu->scrwin) / menu->cols; 547 548 for (cur_row = 0; cur_row < menu->rows; cur_row++) { 549 for (j = 0; j < menu->cols; j++) { 550 offset = j * stride + i; 551 if (offset >= menu->item_count) { 552 /* no more items to draw, write background blanks */ 553 wattrset(menu->scrwin, menu->back); 554 if (row < 0) { 555 row = menu->items[menu->item_count - 1]->row; 556 col = menu->items[menu->item_count - 1]->col; 557 } 558 559 wmove(menu->scrwin, cur_row, 560 j * (menu->col_width + 1)); 561 for (k = 0; k < menu->col_width; k++) 562 waddch(menu->scrwin, ' '); 563 } else { 564 _menui_draw_item(menu, offset); 565 } 566 } 567 568 i += incr; 569 row_count++; 570 } 571 572 if (row_count < menu->item_rows) { 573 for (cur_row = row_count; cur_row < menu->item_rows; cur_row++) { 574 for (j = 0; j < menu->item_cols; j++) { 575 offset = j * stride + i; 576 if (offset >= menu->item_count) 577 break; /* done */ 578 menu->items[offset]->visible = 0; 579 } 580 i += incr; 581 } 582 } 583 584 return E_OK; 585 } 586 587 588 /* 589 * Calculate the widest menu item and stash it in the menu struct. 590 * 591 */ 592 void 593 _menui_max_item_size(MENU *menu) 594 { 595 int i, with_desc, width; 596 597 with_desc = ((menu->opts & O_SHOWDESC) == O_SHOWDESC); 598 599 for (i = 0; i < menu->item_count; i++) { 600 width = menu->items[i]->name.length 601 + max(menu->mark.length, menu->unmark.length); 602 if (with_desc) 603 width += menu->items[i]->description.length + 1; 604 605 menu->max_item_width = max(menu->max_item_width, width); 606 } 607 } 608 609 610 /* 611 * Redraw the menu on the screen. If the current item has changed then 612 * unhighlight the old item and highlight the new one. 613 */ 614 static void 615 _menui_redraw_menu(MENU *menu, int old_top_row, int old_cur_item) 616 { 617 618 if (menu->top_row != old_top_row) { 619 /* top row changed - redo the whole menu 620 * XXXX this could be improved if we had wscrl implemented. 621 622 * XXXX we could scroll the window and just fill in the 623 * XXXX changed lines. 624 */ 625 wclear(menu->scrwin); 626 _menui_draw_menu(menu); 627 } else { 628 if (menu->cur_item != old_cur_item) { 629 /* redo the old item as a normal one. */ 630 _menui_draw_item(menu, old_cur_item); 631 } 632 /* and then redraw the current item */ 633 _menui_draw_item(menu, menu->cur_item); 634 } 635 } 636