[BACK]Return to internals.c CVS log [TXT][DIR] Up to [cvs.NetBSD.org] / src / lib / libmenu

File: [cvs.NetBSD.org] / src / lib / libmenu / internals.c (download)

Revision 1.13.42.1, Thu Aug 9 06:46:37 2012 UTC (7 years, 9 months ago) by jdc
Branch: netbsd-6
CVS Tags: netbsd-6-1-RELEASE, netbsd-6-1-RC4, netbsd-6-1-RC3, netbsd-6-1-RC2, netbsd-6-1-RC1, netbsd-6-1-5-RELEASE, netbsd-6-1-4-RELEASE, netbsd-6-1-3-RELEASE, netbsd-6-1-2-RELEASE, netbsd-6-1-1-RELEASE, netbsd-6-1, netbsd-6-0-RELEASE, netbsd-6-0-RC2, netbsd-6-0-RC1, netbsd-6-0-6-RELEASE, netbsd-6-0-5-RELEASE, netbsd-6-0-4-RELEASE, netbsd-6-0-3-RELEASE, netbsd-6-0-2-RELEASE, netbsd-6-0-1-RELEASE, netbsd-6-0, matt-nb6-plus-nbase, matt-nb6-plus-base, matt-nb6-plus
Changes since 1.13: +242 -187 lines

Pull up revisions:
  src/lib/libcurses/get_wch.c revision 1.10
  src/lib/libmenu/internals.c revisions 1.14,1.15
(requested by blymn in ticket #459).

* Size argument for memset when clearing cbuf was wrong, cbuf is an array
  of char not int so memset was stomping memory past the end of the array.
  Use sizeof properly to correctly determine the amount of memory to clear.

Extraneous whitespace removal.

* Corrected menu drawing when O_ROWMAJOR is not set
* Corrected menu item neighbour calculation so it works when O_ROWMAJOR
  is set and unset.  This corrects item navigation which was previously
  broken when O_ROWMAJOR was not set.

This resolves lib/46620.

/*	$NetBSD: internals.c,v 1.13.42.1 2012/08/09 06:46:37 jdc Exp $	*/

/*-
 * Copyright (c) 1998-1999 Brett Lymn (blymn@baea.com.au, brett_lymn@yahoo.com.au)
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 *
 */

#include <sys/cdefs.h>
__RCSID("$NetBSD: internals.c,v 1.13.42.1 2012/08/09 06:46:37 jdc Exp $");

#include <menu.h>
#include <ctype.h>
#include <stdlib.h>
#include <string.h>
#include "internals.h"

/* internal function prototypes */
static void
_menui_calc_neighbours(MENU *menu, int item_no);
static void _menui_redraw_menu(MENU *menu, int old_top_row, int old_cur_item);

  /*
   * Link all the menu items together to speed up navigation.  We need
   * to calculate the widest item entry, then work out how many columns
   * of items the window will accommodate and then how many rows there will
   * be.  Once the layout is determined the neighbours of each item is
   * calculated and the item structures updated.
   */
int
_menui_stitch_items(MENU *menu)
{
	int i, row_major;

	row_major = ((menu->opts & O_ROWMAJOR) == O_ROWMAJOR);

	if (menu->posted == 1)
		return E_POSTED;
	if (menu->items == NULL)
		return E_BAD_ARGUMENT;

	menu->item_rows = menu->item_count / menu->cols;
	menu->item_cols = menu->cols;
	if (menu->item_count > (menu->item_rows * menu->item_cols))
		menu->item_rows += 1;

	_menui_max_item_size(menu);

	for (i = 0; i < menu->item_count; i++) {
		  /* fill in the row and column value of the item */
		if (row_major) {
			menu->items[i]->row = i / menu->item_cols;
			menu->items[i]->col = i % menu->item_cols;
		} else {
			menu->items[i]->row = i % menu->item_rows;
			menu->items[i]->col = i / menu->item_rows;
		}

		_menui_calc_neighbours(menu, i);
	}

	return E_OK;
}

  /*
   * Calculate the neighbours for an item in menu.
   */
static void
_menui_calc_neighbours(MENU *menu, int item_no)
{
	int neighbour, cycle, row_major, edge;
	ITEM *item;

	row_major = ((menu->opts & O_ROWMAJOR) == O_ROWMAJOR);
	cycle = ((menu->opts & O_NONCYCLIC) != O_NONCYCLIC);
	item = menu->items[item_no];

	if (menu->item_rows < 2) {
		if (cycle) {
			item->up = item;
			item->down = item;
		} else {
			item->up = NULL;
			item->down = NULL;
		}
	} else {

		/* up */
		if (menu->item_cols < 2) {
			if (item_no == 0) {
				if (cycle)
					item->up =
					    menu->items[menu->item_count - 1];
				else
					item->up = NULL;
			} else
				item->up = menu->items[item_no - 1];
		} else {
			edge = 0;
			if (row_major) {
				if (item->row == 0) {
					neighbour =
				    	(menu->item_rows - 1) * menu->item_cols
						+ item->col;
					if (neighbour >= menu->item_count)
						neighbour -= menu->item_cols;
					edge = 1;
				} else
					neighbour = item_no - menu->item_cols;
			} else {
				if (item->row == 0) {
					neighbour = menu->item_rows * item->col
						+ menu->item_rows - 1;
					if (neighbour >= menu->item_count)
						neighbour = menu->item_count - 1;
					edge = 1;
				} else
					neighbour = item_no - 1;
			}


			item->up = menu->items[neighbour];
			if ((!cycle) && (edge == 1))
				item->up = NULL;
		}

		/* Down */
		if (menu->item_cols < 2) {
			if (item_no == (menu->item_count - 1)) {
				if (cycle)
					item->down = menu->items[0];
				else
					item->down = NULL;
			} else
				item->down = menu->items[item_no + 1];
		} else {
			edge = 0;
			if (row_major) {
				if (item->row == menu->item_rows - 1) {
					neighbour = item->col;
					edge = 1;
				} else {
					neighbour = item_no + menu->item_cols;
					if (neighbour >= menu->item_count) {
						neighbour = item->col;
						edge = 1;
					}
				}
			} else {
				if (item->row == menu->item_rows - 1) {
					neighbour = item->col * menu->item_rows;
					edge = 1;
				} else {
					neighbour = item_no + 1;
					if (neighbour >= menu->item_count) {
						neighbour = item->col
						    * menu->item_rows;
						edge = 1;
					}
				}
			}

			item->down = menu->items[neighbour];
			if ((!cycle) && (edge == 1))
				item->down = NULL;
		}
	}

	if (menu->item_cols < 2) {
		if (cycle) {
			item->left = item;
			item->right = item;
		} else {
			item->left = NULL;
			item->right = NULL;
		}
	} else {
		/* left */
		if (menu->item_rows < 2) {
			if (item_no == 0) {
				if (cycle)
					item->left =
					    menu->items[menu->item_count - 1];
				else
					item->left = NULL;
			} else
				item->left = menu->items[item_no - 1];
		} else {
			edge = 0;
			if (row_major) {
				if (item->col == 0) {
					neighbour = item_no + menu->cols - 1;
					if (neighbour >= menu->item_count)
						neighbour = menu->item_count - 1;
					edge = 1;
				} else
					neighbour = item_no - 1;
			} else {
				if (item->col == 0) {
					neighbour = menu->item_rows
					    * (menu->item_cols - 1) + item->row;
					if (neighbour >= menu->item_count)
						neighbour -= menu->item_rows;
					edge = 1;
				} else
					neighbour = item_no - menu->item_rows;
			}

			item->left = menu->items[neighbour];
			if ((!cycle) && (edge == 1))
				item->left = NULL;
		}

		/* right */
		if (menu->item_rows < 2) {
			if (item_no == menu->item_count - 1) {
				if (cycle)
					item->right = menu->items[0];
				else
					item->right = NULL;
			} else
				item->right = menu->items[item_no + 1];
		} else {
			edge = 0;
			if (row_major) {
				if (item->col == menu->item_cols - 1) {
					neighbour = item_no - menu->item_cols
					    + 1;
					edge = 1;
				} else if (item_no == menu->item_count - 1) {
					neighbour = item->row * menu->item_cols;
					edge = 1;
				} else
					neighbour = item_no + 1;
			} else {
				if (item->col == menu->item_cols - 1) {
					neighbour = item->row;
					edge = 1;
				} else {
					neighbour = item_no + menu->item_rows;
					if (neighbour >= menu->item_count) {
						neighbour = item->row;
						edge = 1;
					}
				}
			}

			item->right = menu->items[neighbour];
			if ((!cycle) && (edge == 1))
				item->right = NULL;
		}
	}
}

/*
 * Goto the item pointed to by item and adjust the menu structure
 * accordingly.  Call the term and init functions if required.
 */
int
_menui_goto_item(MENU *menu, ITEM *item, int new_top_row)
{
	int old_top_row = menu->top_row, old_cur_item = menu->cur_item;

	  /* If we get a null then the menu is not cyclic so deny request */
	if (item == NULL)
		return E_REQUEST_DENIED;

	menu->in_init = 1;
	if (menu->top_row != new_top_row) {
		if ((menu->posted == 1) && (menu->menu_term != NULL))
			menu->menu_term(menu);
		menu->top_row = new_top_row;

		if ((menu->posted == 1) && (menu->menu_init != NULL))
			menu->menu_init(menu);
	}

	  /* this looks like wasted effort but it can happen.... */
	if (menu->cur_item != item->index) {

		if ((menu->posted == 1) && (menu->item_term != NULL))
			menu->item_term(menu);

		menu->cur_item = item->index;
		menu->cur_row = item->row;
		menu->cur_col = item->col;

		if (menu->posted == 1)
			_menui_redraw_menu(menu, old_top_row, old_cur_item);

		if ((menu->posted == 1) && (menu->item_init != NULL))
			menu->item_init(menu);

	}

	menu->in_init = 0;
	return E_OK;
}

/*
 * Attempt to match items with the pattern buffer in the direction given
 * by iterating over the menu items.  If a match is found return E_OK
 * otherwise return E_NO_MATCH
 */
int
_menui_match_items(MENU *menu, int direction, int *item_matched)
{
	int i, caseless;

	caseless = ((menu->opts & O_IGNORECASE) == O_IGNORECASE);

	i = menu->cur_item;
	if (direction == MATCH_NEXT_FORWARD) {
		if (++i >= menu->item_count) i = 0;
	} else if (direction == MATCH_NEXT_REVERSE) {
		if (--i < 0) i = menu->item_count - 1;
	}


	do {
		if (menu->items[i]->name.length >= menu->plen) {
			  /* no chance if pattern is longer */
			if (caseless) {
				if (strncasecmp(menu->items[i]->name.string,
						menu->pattern,
						(size_t) menu->plen) == 0) {
					*item_matched = i;
					menu->match_len = menu->plen;
					return E_OK;
				}
			} else {
				if (strncmp(menu->items[i]->name.string,
					    menu->pattern,
					    (size_t) menu->plen) == 0) {
					*item_matched = i;
					menu->match_len = menu->plen;
					return E_OK;
				}
			}
		}

		if ((direction == MATCH_FORWARD) ||
		    (direction == MATCH_NEXT_FORWARD)) {
			if (++i >= menu->item_count) i = 0;
		} else {
			if (--i <= 0) i = menu->item_count - 1;
		}
	} while (i != menu->cur_item);

	menu->match_len = 0; /* match did not succeed - kill the match len. */
	return E_NO_MATCH;
}

/*
 * Attempt to match the pattern buffer against the items.  If c is a
 * printable character then add it to the pattern buffer prior to
 * performing the match.  Direction determines the direction of matching.
 * If the match is successful update the item_matched variable with the
 * index of the item that matched the pattern.
 */
int
_menui_match_pattern(MENU *menu, int c, int direction, int *item_matched)
{
	if (menu == NULL)
		return E_BAD_ARGUMENT;
	if (menu->items == NULL)
		return E_BAD_ARGUMENT;
	if (*menu->items == NULL)
		return E_BAD_ARGUMENT;

	if (isprint(c)) {
		  /* add char to buffer - first allocate room for it */
		if ((menu->pattern = (char *)
		     realloc(menu->pattern,
			     menu->plen + sizeof(char) +
			     ((menu->plen > 0)? 0 : 1)))
		    == NULL)
			return E_SYSTEM_ERROR;
		menu->pattern[menu->plen] = c;
		menu->pattern[++menu->plen] = '\0';

		  /* there is no chance of a match if pattern is longer
		     than all the items */
		if (menu->plen >= menu->max_item_width) {
			menu->pattern[--menu->plen] = '\0';
			return E_NO_MATCH;
		}

		if (_menui_match_items(menu, direction,
					item_matched) == E_NO_MATCH) {
			menu->pattern[--menu->plen] = '\0';
			return E_NO_MATCH;
		} else
			return E_OK;
	} else {
		if (_menui_match_items(menu, direction,
					item_matched) == E_OK) {
			return E_OK;
		} else {
			return E_NO_MATCH;
		}
	}
}

/*
 * Draw an item in the subwindow complete with appropriate highlighting.
 */
void
_menui_draw_item(MENU *menu, int item)
{
	int j, pad_len, mark_len;

	mark_len = max(menu->mark.length, menu->unmark.length);

	wmove(menu->scrwin,
	      menu->items[item]->row - menu->top_row,
	      menu->items[item]->col * (menu->col_width + 1));

	if (menu->cur_item == item)
		wattrset(menu->scrwin, menu->fore);
	if ((menu->items[item]->opts & O_SELECTABLE) != O_SELECTABLE)
		wattron(menu->scrwin, menu->grey);

	  /* deal with the menu mark, if  one is set.
	   * We mark the selected items and write blanks for
	   * all others unless the menu unmark string is set in which
	   * case the unmark string is written.
	   */
	if (menu->items[item]->selected == 1) {
		if (menu->mark.string != NULL) {
			for (j = 0; j < menu->mark.length; j++) {
				waddch(menu->scrwin,
				       menu->mark.string[j]);
			}
		}
		  /* blank any length difference between mark & unmark */
		for (j = menu->mark.length; j < mark_len; j++)
			waddch(menu->scrwin, ' ');
	} else {
		if (menu->unmark.string != NULL) {
			for (j = 0; j < menu->unmark.length; j++) {
				waddch(menu->scrwin,
				       menu->unmark.string[j]);
			}
		}
		  /* blank any length difference between mark & unmark */
		for (j = menu->unmark.length; j < mark_len; j++)
			waddch(menu->scrwin, ' ');
	}

	  /* add the menu name */
	for (j=0; j < menu->items[item]->name.length; j++)
		waddch(menu->scrwin,
		       menu->items[item]->name.string[j]);

	pad_len = menu->col_width - menu->items[item]->name.length
		- mark_len - 1;
	if ((menu->opts & O_SHOWDESC) == O_SHOWDESC) {
		pad_len -= menu->items[item]->description.length - 1;
		for (j = 0; j < pad_len; j++)
			waddch(menu->scrwin, menu->pad);
		for (j = 0; j < menu->items[item]->description.length; j++) {
			waddch(menu->scrwin,
			       menu->items[item]->description.string[j]);
		}
	} else {
		for (j = 0; j < pad_len; j++)
			waddch(menu->scrwin, ' ');
	}
	menu->items[item]->visible = 1;

	  /* kill any special attributes... */
	wattrset(menu->scrwin, menu->back);

	  /*
	   * Fill in the spacing between items, annoying but it looks
	   * odd if the menu items are inverse because the spacings do not
	   * have the same attributes as the items.
	   */
	if ((menu->items[item]->col > 0) &&
	    (menu->items[item]->col < (menu->item_cols - 1))) {
		wmove(menu->scrwin,
		      menu->items[item]->row - menu->top_row,
		      menu->items[item]->col * (menu->col_width + 1) - 1);
		waddch(menu->scrwin, ' ');
	}

	  /* and position the cursor nicely */
	pos_menu_cursor(menu);
}

/*
 * Draw the menu in the subwindow provided.
 */
int
_menui_draw_menu(MENU *menu)
{
	int rowmajor, i, j, k, row = -1, col = -1, stride;
	int incr, cur_row, offset, row_count;

	rowmajor = ((menu->opts & O_ROWMAJOR) == O_ROWMAJOR);

	if (rowmajor) {
		stride = 1;
		incr = menu->item_cols;
	} else {
		stride = menu->item_rows;
		incr = 1;
	}
	row_count = 0;

	for (i = 0;  i < menu->item_count; i += incr) {
		if (menu->items[i]->row == menu->top_row)
			break;
		row_count++;
		for (j = 0; j < menu->item_cols; j++) {
			offset = j * stride + i;
			if (offset >= menu->item_count)
				break; /* done */
			menu->items[offset]->visible = 0;
		}
	}

	wmove(menu->scrwin, 0, 0);

	menu->col_width = getmaxx(menu->scrwin) / menu->cols;

	for (cur_row = 0; cur_row < menu->rows; cur_row++) {
		for (j = 0; j < menu->cols; j++) {
			offset = j * stride + i;
			if (offset >= menu->item_count) {
			   /* no more items to draw, write background blanks */
				wattrset(menu->scrwin, menu->back);
				if (row < 0) {
					row = menu->items[menu->item_count - 1]->row;
					col = menu->items[menu->item_count - 1]->col;
				}

				wmove(menu->scrwin, cur_row,
				      j * (menu->col_width + 1));
				for (k = 0; k < menu->col_width; k++)
					waddch(menu->scrwin, ' ');
			} else {
				_menui_draw_item(menu, offset);
			}
		}

		i += incr;
		row_count++;
	}

	if (row_count < menu->item_rows) {
		for (cur_row = row_count;  cur_row < menu->item_rows; cur_row++) {
			for (j = 0; j < menu->item_cols; j++) {
				offset = j * stride + i;
				if (offset >= menu->item_count)
					break; /* done */
				menu->items[offset]->visible = 0;
			}
			i += incr;
		}
	}

	return E_OK;
}


/*
 * Calculate the widest menu item and stash it in the menu struct.
 *
 */
void
_menui_max_item_size(MENU *menu)
{
	int i, with_desc, width;

	with_desc = ((menu->opts & O_SHOWDESC) == O_SHOWDESC);

	for (i = 0; i < menu->item_count; i++) {
		width = menu->items[i]->name.length
			+ max(menu->mark.length, menu->unmark.length);
		if (with_desc)
			width += menu->items[i]->description.length + 1;

		menu->max_item_width = max(menu->max_item_width, width);
	}
}


/*
 * Redraw the menu on the screen.  If the current item has changed then
 * unhighlight the old item and highlight the new one.
 */
static void
_menui_redraw_menu(MENU *menu, int old_top_row, int old_cur_item)
{

	if (menu->top_row != old_top_row) {
		  /* top row changed - redo the whole menu
		   * XXXX this could be improved if we had wscrl implemented.

		   * XXXX we could scroll the window and just fill in the
		   * XXXX changed lines.
		   */
		wclear(menu->scrwin);
		_menui_draw_menu(menu);
	} else {
		if (menu->cur_item != old_cur_item) {
			  /* redo the old item as a normal one. */
			_menui_draw_item(menu, old_cur_item);
		}
		  /* and then redraw the current item */
		_menui_draw_item(menu, menu->cur_item);
	}
}