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