1 /* $NetBSD: internals.c,v 1.13 2006/11/24 19:46:58 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.13 2006/11/24 19:46:58 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, int cycle, int item_rows, 41 int item_cols, ITEM **next, ITEM **prev, 42 ITEM **major_next, ITEM **major_prev); 43 static void _menui_redraw_menu(MENU *menu, int old_top_row, int old_cur_item); 44 45 /* 46 * Link all the menu items together to speed up navigation. We need 47 * to calculate the widest item entry, then work out how many columns 48 * of items the window will accommodate and then how many rows there will 49 * be. Once the layout is determined the neighbours of each item is 50 * calculated and the item structures updated. 51 */ 52 int 53 _menui_stitch_items(MENU *menu) 54 { 55 int i, cycle, row_major; 56 57 cycle = ((menu->opts & O_NONCYCLIC) != O_NONCYCLIC); 58 row_major = ((menu->opts & O_ROWMAJOR) == O_ROWMAJOR); 59 60 if (menu->posted == 1) 61 return E_POSTED; 62 if (menu->items == NULL) 63 return E_BAD_ARGUMENT; 64 65 if (row_major) { 66 menu->item_rows = menu->item_count / menu->cols; 67 menu->item_cols = menu->cols; 68 if (menu->item_count > (menu->item_rows * menu->item_cols)) 69 menu->item_rows += 1; 70 } else { 71 menu->item_cols = menu->item_count / menu->rows; 72 menu->item_rows = menu->rows; 73 if (menu->item_count > (menu->item_rows * menu->item_cols)) 74 menu->item_cols += 1; 75 } 76 77 78 _menui_max_item_size(menu); 79 80 for (i = 0; i < menu->item_count; i++) { 81 /* Calculate the neighbours. The ugliness here deals with 82 * the differing menu layout styles. The layout affects 83 * the neighbour calculation so we change the arguments 84 * around depending on the layout style. 85 */ 86 _menui_calc_neighbours(menu, i, cycle, 87 (row_major) ? menu->item_rows 88 : menu->item_cols, 89 (row_major) ? menu->item_cols 90 : menu->item_rows, 91 (row_major) ? &menu->items[i]->right 92 : &menu->items[i]->down, 93 (row_major) ? &menu->items[i]->left 94 : &menu->items[i]->up, 95 (row_major) ? &menu->items[i]->down 96 : &menu->items[i]->right, 97 (row_major) ? &menu->items[i]->up 98 : &menu->items[i]->left); 99 100 /* fill in the row and column value of the item */ 101 if (row_major) { 102 menu->items[i]->row = i / menu->item_cols; 103 menu->items[i]->col = i % menu->item_cols; 104 } else { 105 menu->items[i]->row = i % menu->item_rows; 106 menu->items[i]->col = i / menu->item_rows; 107 } 108 } 109 110 return E_OK; 111 } 112 113 /* 114 * Calculate the neighbours for an item in menu. This routine deliberately 115 * does not refer to up/down/left/right as these concepts depend on the menu 116 * layout style (row major or not). By arranging the arguments in the right 117 * order the caller can generate the neighbours for either menu layout style. 118 */ 119 static void 120 _menui_calc_neighbours(MENU *menu, int item_no, int cycle, int item_rows, 121 int item_cols, ITEM **next, ITEM **prev, 122 ITEM **major_next, ITEM **major_prev) 123 { 124 int neighbour; 125 126 if (item_rows < 2) { 127 if (cycle) { 128 *major_next = menu->items[item_no]; 129 *major_prev = menu->items[item_no]; 130 } else { 131 *major_next = NULL; 132 *major_prev = NULL; 133 } 134 } else { 135 neighbour = item_no + item_cols; 136 if (neighbour >= menu->item_count) { 137 if (cycle) { 138 if (item_rows == 2) { 139 neighbour = item_no - item_cols; 140 if (neighbour < 0) 141 neighbour = item_no; 142 *major_next = menu->items[neighbour]; 143 } else { 144 *major_next = 145 menu->items[item_no % item_cols]; 146 } 147 } else 148 *major_next = NULL; 149 } else 150 *major_next = menu->items[neighbour]; 151 152 153 neighbour = item_no - item_cols; 154 if (neighbour < 0) { 155 if (cycle) { 156 if (item_rows == 2) { 157 neighbour = item_no + item_cols; 158 if (neighbour >= menu->item_count) 159 neighbour = item_no; 160 *major_prev = menu->items[neighbour]; 161 } else { 162 neighbour = item_no + 163 (item_rows - 1) * item_cols; 164 165 if (neighbour >= menu->item_count) 166 neighbour = item_no + 167 (item_rows - 2) 168 * item_cols; 169 170 *major_prev = menu->items[neighbour]; 171 } 172 } else 173 *major_prev = NULL; 174 } else 175 *major_prev = menu->items[neighbour]; 176 } 177 178 if ((item_no % item_cols) == 0) { 179 if (cycle) { 180 if (item_cols < 2) { 181 *prev = menu->items[item_no]; 182 } else { 183 neighbour = item_no + item_cols - 1; 184 if (neighbour >= menu->item_count) { 185 if (item_cols == 2) { 186 *prev = menu->items[item_no]; 187 } else { 188 *prev = menu->items[menu->item_count - 1]; 189 } 190 } else 191 *prev = menu->items[neighbour]; 192 } 193 } else 194 *prev = NULL; 195 } else 196 *prev = menu->items[item_no - 1]; 197 198 if ((item_no % item_cols) == (item_cols - 1)) { 199 if (cycle) { 200 if (item_cols < 2) { 201 *next = menu->items[item_no]; 202 } else { 203 neighbour = item_no - item_cols + 1; 204 if (neighbour >= menu->item_count) { 205 if (item_cols == 2) { 206 *next = menu->items[item_no]; 207 } else { 208 neighbour = item_cols * item_no / item_cols; 209 210 *next = menu->items[neighbour]; 211 } 212 } else 213 *next = menu->items[neighbour]; 214 } 215 } else 216 *next = NULL; 217 } else { 218 neighbour = item_no + 1; 219 if (neighbour >= menu->item_count) { 220 if (cycle) { 221 neighbour = item_cols * (item_rows - 1); 222 *next = menu->items[neighbour]; 223 } else 224 *next = NULL; 225 } else 226 *next = menu->items[neighbour]; 227 } 228 } 229 230 /* 231 * Goto the item pointed to by item and adjust the menu structure 232 * accordingly. Call the term and init functions if required. 233 */ 234 int 235 _menui_goto_item(MENU *menu, ITEM *item, int new_top_row) 236 { 237 int old_top_row = menu->top_row, old_cur_item = menu->cur_item; 238 239 /* If we get a null then the menu is not cyclic so deny request */ 240 if (item == NULL) 241 return E_REQUEST_DENIED; 242 243 menu->in_init = 1; 244 if (menu->top_row != new_top_row) { 245 if ((menu->posted == 1) && (menu->menu_term != NULL)) 246 menu->menu_term(menu); 247 menu->top_row = new_top_row; 248 249 if ((menu->posted == 1) && (menu->menu_init != NULL)) 250 menu->menu_init(menu); 251 } 252 253 /* this looks like wasted effort but it can happen.... */ 254 if (menu->cur_item != item->index) { 255 256 if ((menu->posted == 1) && (menu->item_term != NULL)) 257 menu->item_term(menu); 258 259 menu->cur_item = item->index; 260 menu->cur_row = item->row; 261 menu->cur_col = item->col; 262 263 if (menu->posted == 1) 264 _menui_redraw_menu(menu, old_top_row, old_cur_item); 265 266 if ((menu->posted == 1) && (menu->item_init != NULL)) 267 menu->item_init(menu); 268 269 } 270 271 menu->in_init = 0; 272 return E_OK; 273 } 274 275 /* 276 * Attempt to match items with the pattern buffer in the direction given 277 * by iterating over the menu items. If a match is found return E_OK 278 * otherwise return E_NO_MATCH 279 */ 280 int 281 _menui_match_items(MENU *menu, int direction, int *item_matched) 282 { 283 int i, caseless; 284 285 caseless = ((menu->opts & O_IGNORECASE) == O_IGNORECASE); 286 287 i = menu->cur_item; 288 if (direction == MATCH_NEXT_FORWARD) { 289 if (++i >= menu->item_count) i = 0; 290 } else if (direction == MATCH_NEXT_REVERSE) { 291 if (--i < 0) i = menu->item_count - 1; 292 } 293 294 295 do { 296 if (menu->items[i]->name.length >= menu->plen) { 297 /* no chance if pattern is longer */ 298 if (caseless) { 299 if (strncasecmp(menu->items[i]->name.string, 300 menu->pattern, 301 (size_t) menu->plen) == 0) { 302 *item_matched = i; 303 menu->match_len = menu->plen; 304 return E_OK; 305 } 306 } else { 307 if (strncmp(menu->items[i]->name.string, 308 menu->pattern, 309 (size_t) menu->plen) == 0) { 310 *item_matched = i; 311 menu->match_len = menu->plen; 312 return E_OK; 313 } 314 } 315 } 316 317 if ((direction == MATCH_FORWARD) || 318 (direction == MATCH_NEXT_FORWARD)) { 319 if (++i >= menu->item_count) i = 0; 320 } else { 321 if (--i <= 0) i = menu->item_count - 1; 322 } 323 } while (i != menu->cur_item); 324 325 menu->match_len = 0; /* match did not succeed - kill the match len. */ 326 return E_NO_MATCH; 327 } 328 329 /* 330 * Attempt to match the pattern buffer against the items. If c is a 331 * printable character then add it to the pattern buffer prior to 332 * performing the match. Direction determines the direction of matching. 333 * If the match is successful update the item_matched variable with the 334 * index of the item that matched the pattern. 335 */ 336 int 337 _menui_match_pattern(MENU *menu, int c, int direction, int *item_matched) 338 { 339 if (menu == NULL) 340 return E_BAD_ARGUMENT; 341 if (menu->items == NULL) 342 return E_BAD_ARGUMENT; 343 if (*menu->items == NULL) 344 return E_BAD_ARGUMENT; 345 346 if (isprint(c)) { 347 /* add char to buffer - first allocate room for it */ 348 if ((menu->pattern = (char *) 349 realloc(menu->pattern, 350 menu->plen + sizeof(char) + 351 ((menu->plen > 0)? 0 : 1))) 352 == NULL) 353 return E_SYSTEM_ERROR; 354 menu->pattern[menu->plen] = c; 355 menu->pattern[++menu->plen] = '\0'; 356 357 /* there is no chance of a match if pattern is longer 358 than all the items */ 359 if (menu->plen >= menu->max_item_width) { 360 menu->pattern[--menu->plen] = '\0'; 361 return E_NO_MATCH; 362 } 363 364 if (_menui_match_items(menu, direction, 365 item_matched) == E_NO_MATCH) { 366 menu->pattern[--menu->plen] = '\0'; 367 return E_NO_MATCH; 368 } else 369 return E_OK; 370 } else { 371 if (_menui_match_items(menu, direction, 372 item_matched) == E_OK) { 373 return E_OK; 374 } else { 375 return E_NO_MATCH; 376 } 377 } 378 } 379 380 /* 381 * Draw an item in the subwindow complete with appropriate highlighting. 382 */ 383 void 384 _menui_draw_item(MENU *menu, int item) 385 { 386 int j, pad_len, mark_len; 387 388 mark_len = max(menu->mark.length, menu->unmark.length); 389 390 wmove(menu->scrwin, 391 menu->items[item]->row - menu->top_row, 392 menu->items[item]->col * (menu->col_width + 1)); 393 394 if (menu->cur_item == item) 395 wattrset(menu->scrwin, menu->fore); 396 if ((menu->items[item]->opts & O_SELECTABLE) != O_SELECTABLE) 397 wattron(menu->scrwin, menu->grey); 398 399 /* deal with the menu mark, if one is set. 400 * We mark the selected items and write blanks for 401 * all others unless the menu unmark string is set in which 402 * case the unmark string is written. 403 */ 404 if (menu->items[item]->selected == 1) { 405 if (menu->mark.string != NULL) { 406 for (j = 0; j < menu->mark.length; j++) { 407 waddch(menu->scrwin, 408 menu->mark.string[j]); 409 } 410 } 411 /* blank any length difference between mark & unmark */ 412 for (j = menu->mark.length; j < mark_len; j++) 413 waddch(menu->scrwin, ' '); 414 } else { 415 if (menu->unmark.string != NULL) { 416 for (j = 0; j < menu->unmark.length; j++) { 417 waddch(menu->scrwin, 418 menu->unmark.string[j]); 419 } 420 } 421 /* blank any length difference between mark & unmark */ 422 for (j = menu->unmark.length; j < mark_len; j++) 423 waddch(menu->scrwin, ' '); 424 } 425 426 /* add the menu name */ 427 for (j=0; j < menu->items[item]->name.length; j++) 428 waddch(menu->scrwin, 429 menu->items[item]->name.string[j]); 430 431 pad_len = menu->col_width - menu->items[item]->name.length 432 - mark_len - 1; 433 if ((menu->opts & O_SHOWDESC) == O_SHOWDESC) { 434 pad_len -= menu->items[item]->description.length - 1; 435 for (j = 0; j < pad_len; j++) 436 waddch(menu->scrwin, menu->pad); 437 for (j = 0; j < menu->items[item]->description.length; j++) { 438 waddch(menu->scrwin, 439 menu->items[item]->description.string[j]); 440 } 441 } else { 442 for (j = 0; j < pad_len; j++) 443 waddch(menu->scrwin, ' '); 444 } 445 menu->items[item]->visible = 1; 446 447 /* kill any special attributes... */ 448 wattrset(menu->scrwin, menu->back); 449 450 /* 451 * Fill in the spacing between items, annoying but it looks 452 * odd if the menu items are inverse because the spacings do not 453 * have the same attributes as the items. 454 */ 455 if ((menu->items[item]->col > 0) && 456 (menu->items[item]->col < (menu->item_cols - 1))) { 457 wmove(menu->scrwin, 458 menu->items[item]->row - menu->top_row, 459 menu->items[item]->col * (menu->col_width + 1) - 1); 460 waddch(menu->scrwin, ' '); 461 } 462 463 /* and position the cursor nicely */ 464 pos_menu_cursor(menu); 465 } 466 467 /* 468 * Draw the menu in the subwindow provided. 469 */ 470 int 471 _menui_draw_menu(MENU *menu) 472 { 473 int rowmajor, i, j, max_items, last_item, row = -1, col = -1; 474 475 rowmajor = ((menu->opts & O_ROWMAJOR) == O_ROWMAJOR); 476 477 for (i = 0; i < menu->item_count; i++) { 478 if (menu->items[i]->row == menu->top_row) 479 break; 480 menu->items[i]->visible = 0; 481 } 482 483 wmove(menu->scrwin, 0, 0); 484 485 menu->col_width = getmaxx(menu->scrwin) / menu->cols; 486 487 max_items = menu->rows * menu->cols; 488 last_item = ((max_items + i) > menu->item_count) ? menu->item_count : 489 (max_items + i); 490 491 for (; i < last_item; i++) { 492 if (i > menu->item_count) { 493 /* no more items to draw, write background blanks */ 494 wattrset(menu->scrwin, menu->back); 495 if (row < 0) { 496 row = menu->items[menu->item_count - 1]->row; 497 col = menu->items[menu->item_count - 1]->col; 498 } 499 500 if (rowmajor) { 501 col++; 502 if (col > menu->cols) { 503 col = 0; 504 row++; 505 } 506 } else { 507 row++; 508 if (row > menu->rows) { 509 row = 0; 510 col++; 511 } 512 } 513 wmove(menu->scrwin, row, 514 col * (menu->col_width + 1)); 515 for (j = 0; j < menu->col_width; j++) 516 waddch(menu->scrwin, ' '); 517 } else { 518 _menui_draw_item(menu, i); 519 520 } 521 522 } 523 524 if (last_item < menu->item_count) { 525 for (j = last_item; j < menu->item_count; j++) 526 menu->items[j]->visible = 0; 527 } 528 529 return E_OK; 530 } 531 532 533 /* 534 * Calculate the widest menu item and stash it in the menu struct. 535 * 536 */ 537 void 538 _menui_max_item_size(MENU *menu) 539 { 540 int i, with_desc, width; 541 542 with_desc = ((menu->opts & O_SHOWDESC) == O_SHOWDESC); 543 544 for (i = 0; i < menu->item_count; i++) { 545 width = menu->items[i]->name.length 546 + max(menu->mark.length, menu->unmark.length); 547 if (with_desc) 548 width += menu->items[i]->description.length + 1; 549 550 menu->max_item_width = max(menu->max_item_width, width); 551 } 552 } 553 554 555 /* 556 * Redraw the menu on the screen. If the current item has changed then 557 * unhighlight the old item and highlight the new one. 558 */ 559 static void 560 _menui_redraw_menu(MENU *menu, int old_top_row, int old_cur_item) 561 { 562 563 if (menu->top_row != old_top_row) { 564 /* top row changed - redo the whole menu 565 * XXXX this could be improved if we had wscrl implemented. 566 567 * XXXX we could scroll the window and just fill in the 568 * XXXX changed lines. 569 */ 570 wclear(menu->scrwin); 571 _menui_draw_menu(menu); 572 } else { 573 if (menu->cur_item != old_cur_item) { 574 /* redo the old item as a normal one. */ 575 _menui_draw_item(menu, old_cur_item); 576 } 577 /* and then redraw the current item */ 578 _menui_draw_item(menu, menu->cur_item); 579 } 580 } 581