Home | History | Annotate | Line # | Download | only in libmenu
internals.c revision 1.17
      1 /*	$NetBSD: internals.c,v 1.17 2013/10/18 19:53:59 christos 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.17 2013/10/18 19:53:59 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);
     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 	    (((menu->opts & O_ONEVALUE) == O_ONEVALUE) &&
    451 		(menu->cur_item == item))) {
    452 		if (menu->mark.string != NULL) {
    453 			for (j = 0; j < menu->mark.length; j++) {
    454 				waddch(menu->scrwin,
    455 				       menu->mark.string[j]);
    456 			}
    457 		}
    458 		  /* blank any length difference between mark & unmark */
    459 		for (j = menu->mark.length; j < mark_len; j++)
    460 			waddch(menu->scrwin, ' ');
    461 	} else {
    462 		if (menu->unmark.string != NULL) {
    463 			for (j = 0; j < menu->unmark.length; j++) {
    464 				waddch(menu->scrwin,
    465 				       menu->unmark.string[j]);
    466 			}
    467 		}
    468 		  /* blank any length difference between mark & unmark */
    469 		for (j = menu->unmark.length; j < mark_len; j++)
    470 			waddch(menu->scrwin, ' ');
    471 	}
    472 
    473 	  /* add the menu name */
    474 	for (j=0; j < menu->items[item]->name.length; j++)
    475 		waddch(menu->scrwin,
    476 		       menu->items[item]->name.string[j]);
    477 
    478 	pad_len = menu->col_width - menu->items[item]->name.length
    479 		- mark_len - 1;
    480 	if ((menu->opts & O_SHOWDESC) == O_SHOWDESC) {
    481 		pad_len -= menu->items[item]->description.length - 1;
    482 		for (j = 0; j < pad_len; j++)
    483 			waddch(menu->scrwin, menu->pad);
    484 		for (j = 0; j < menu->items[item]->description.length; j++) {
    485 			waddch(menu->scrwin,
    486 			       menu->items[item]->description.string[j]);
    487 		}
    488 	} else {
    489 		for (j = 0; j < pad_len; j++)
    490 			waddch(menu->scrwin, ' ');
    491 	}
    492 	menu->items[item]->visible = 1;
    493 
    494 	  /* kill any special attributes... */
    495 	wattrset(menu->scrwin, menu->back);
    496 
    497 	  /*
    498 	   * Fill in the spacing between items, annoying but it looks
    499 	   * odd if the menu items are inverse because the spacings do not
    500 	   * have the same attributes as the items.
    501 	   */
    502 	if ((menu->items[item]->col > 0) &&
    503 	    (menu->items[item]->col < (menu->item_cols - 1))) {
    504 		wmove(menu->scrwin,
    505 		      menu->items[item]->row - menu->top_row,
    506 		      menu->items[item]->col * (menu->col_width + 1) - 1);
    507 		waddch(menu->scrwin, ' ');
    508 	}
    509 
    510 	  /* and position the cursor nicely */
    511 	pos_menu_cursor(menu);
    512 }
    513 
    514 /*
    515  * Draw the menu in the subwindow provided.
    516  */
    517 int
    518 _menui_draw_menu(MENU *menu)
    519 {
    520 	int rowmajor, i, j, k, row = -1, stride;
    521 	int incr, cur_row, offset, row_count;
    522 
    523 	rowmajor = ((menu->opts & O_ROWMAJOR) == O_ROWMAJOR);
    524 
    525 	if (rowmajor) {
    526 		stride = 1;
    527 		incr = menu->item_cols;
    528 	} else {
    529 		stride = menu->item_rows;
    530 		incr = 1;
    531 	}
    532 	row_count = 0;
    533 
    534 	for (i = 0;  i < menu->item_count; i += incr) {
    535 		if (menu->items[i]->row == menu->top_row)
    536 			break;
    537 		row_count++;
    538 		for (j = 0; j < menu->item_cols; j++) {
    539 			offset = j * stride + i;
    540 			if (offset >= menu->item_count)
    541 				break; /* done */
    542 			menu->items[offset]->visible = 0;
    543 		}
    544 	}
    545 
    546 	wmove(menu->scrwin, 0, 0);
    547 
    548 	menu->col_width = getmaxx(menu->scrwin) / menu->cols;
    549 
    550 	for (cur_row = 0; cur_row < menu->rows; cur_row++) {
    551 		for (j = 0; j < menu->cols; j++) {
    552 			offset = j * stride + i;
    553 			if (offset >= menu->item_count) {
    554 			   /* no more items to draw, write background blanks */
    555 				wattrset(menu->scrwin, menu->back);
    556 				if (row < 0) {
    557 					row = menu->items[menu->item_count - 1]->row;
    558 				}
    559 
    560 				wmove(menu->scrwin, cur_row,
    561 				      j * (menu->col_width + 1));
    562 				for (k = 0; k < menu->col_width; k++)
    563 					waddch(menu->scrwin, ' ');
    564 			} else {
    565 				_menui_draw_item(menu, offset);
    566 			}
    567 		}
    568 
    569 		i += incr;
    570 		row_count++;
    571 	}
    572 
    573 	if (row_count < menu->item_rows) {
    574 		for (cur_row = row_count;  cur_row < menu->item_rows; cur_row++) {
    575 			for (j = 0; j < menu->item_cols; j++) {
    576 				offset = j * stride + i;
    577 				if (offset >= menu->item_count)
    578 					break; /* done */
    579 				menu->items[offset]->visible = 0;
    580 			}
    581 			i += incr;
    582 		}
    583 	}
    584 
    585 	return E_OK;
    586 }
    587 
    588 
    589 /*
    590  * Calculate the widest menu item and stash it in the menu struct.
    591  *
    592  */
    593 void
    594 _menui_max_item_size(MENU *menu)
    595 {
    596 	int i, with_desc, width;
    597 
    598 	with_desc = ((menu->opts & O_SHOWDESC) == O_SHOWDESC);
    599 
    600 	for (i = 0; i < menu->item_count; i++) {
    601 		width = menu->items[i]->name.length
    602 			+ max(menu->mark.length, menu->unmark.length);
    603 		if (with_desc)
    604 			width += menu->items[i]->description.length + 1;
    605 
    606 		menu->max_item_width = max(menu->max_item_width, width);
    607 	}
    608 }
    609 
    610 
    611 /*
    612  * Redraw the menu on the screen.  If the current item has changed then
    613  * unhighlight the old item and highlight the new one.
    614  */
    615 static void
    616 _menui_redraw_menu(MENU *menu, int old_top_row, int old_cur_item)
    617 {
    618 
    619 	if (menu->top_row != old_top_row) {
    620 		  /* top row changed - redo the whole menu
    621 		   * XXXX this could be improved if we had wscrl implemented.
    622 
    623 		   * XXXX we could scroll the window and just fill in the
    624 		   * XXXX changed lines.
    625 		   */
    626 		wclear(menu->scrwin);
    627 		_menui_draw_menu(menu);
    628 	} else {
    629 		if (menu->cur_item != old_cur_item) {
    630 			  /* redo the old item as a normal one. */
    631 			_menui_draw_item(menu, old_cur_item);
    632 		}
    633 		  /* and then redraw the current item */
    634 		_menui_draw_item(menu, menu->cur_item);
    635 	}
    636 }
    637