Home | History | Annotate | Line # | Download | only in libmenu
internals.c revision 1.13.42.1
      1 /*	$NetBSD: internals.c,v 1.13.42.1 2012/08/09 06:46:37 jdc Exp $	*/
      2 
      3 /*-
      4  * Copyright (c) 1998-1999 Brett Lymn (blymn (at) baea.com.au, brett_lymn (at) 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.42.1 2012/08/09 06:46:37 jdc 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