Home | History | Annotate | Line # | Download | only in libmenu
internals.c revision 1.3
      1 /*      $Id: internals.c,v 1.3 1999/12/16 12:08:07 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 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