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