637 lines
16 KiB
C
637 lines
16 KiB
C
/* $NetBSD: internals.c,v 1.17 2013/10/18 19:53:59 christos 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.17 2013/10/18 19:53:59 christos 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) ||
|
|
(((menu->opts & O_ONEVALUE) == O_ONEVALUE) &&
|
|
(menu->cur_item == item))) {
|
|
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, 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;
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|