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