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