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