/* $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 __RCSID("$NetBSD: internals.c,v 1.17 2013/10/18 19:53:59 christos Exp $"); #include #include #include #include #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); } }