Expand PMF_FN_* macros.
[netbsd-mini2440.git] / lib / libmenu / internals.c
blob8928ecf50e82b0cabc7191525fff85aaff28cd7b
1 /* $NetBSD: internals.c,v 1.12 2003/03/09 01:08:48 lukem Exp $ */
3 /*-
4 * Copyright (c) 1998-1999 Brett Lymn (blymn@baea.com.au, brett_lymn@yahoo.com.au)
5 * All rights reserved.
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
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.
29 #include <sys/cdefs.h>
30 __RCSID("$NetBSD: internals.c,v 1.12 2003/03/09 01:08:48 lukem Exp $");
32 #include <menu.h>
33 #include <ctype.h>
34 #include <stdlib.h>
35 #include <string.h>
36 #include "internals.h"
38 /* internal function prototypes */
39 static void
40 _menui_calc_neighbours(MENU *menu, int item_no, int cycle, int item_rows,
41 int item_cols, ITEM **next, ITEM **prev,
42 ITEM **major_next, ITEM **major_prev);
43 static void _menui_redraw_menu(MENU *menu, int old_top_row, int old_cur_item);
46 * Link all the menu items together to speed up navigation. We need
47 * to calculate the widest item entry, then work out how many columns
48 * of items the window will accommodate and then how many rows there will
49 * be. Once the layout is determined the neighbours of each item is
50 * calculated and the item structures updated.
52 int
53 _menui_stitch_items(MENU *menu)
55 int i, cycle, row_major;
57 cycle = ((menu->opts & O_NONCYCLIC) != O_NONCYCLIC);
58 row_major = ((menu->opts & O_ROWMAJOR) == O_ROWMAJOR);
60 if (menu->posted == 1)
61 return E_POSTED;
62 if (menu->items == NULL)
63 return E_BAD_ARGUMENT;
65 if (row_major) {
66 menu->item_rows = menu->item_count / menu->cols;
67 menu->item_cols = menu->cols;
68 if (menu->item_count > (menu->item_rows * menu->item_cols))
69 menu->item_rows += 1;
70 } else {
71 menu->item_cols = menu->item_count / menu->rows;
72 menu->item_rows = menu->rows;
73 if (menu->item_count > (menu->item_rows * menu->item_cols))
74 menu->item_cols += 1;
78 _menui_max_item_size(menu);
80 for (i = 0; i < menu->item_count; i++) {
81 /* Calculate the neighbours. The ugliness here deals with
82 * the differing menu layout styles. The layout affects
83 * the neighbour calculation so we change the arguments
84 * around depending on the layout style.
86 _menui_calc_neighbours(menu, i, cycle,
87 (row_major) ? menu->item_rows
88 : menu->item_cols,
89 (row_major) ? menu->item_cols
90 : menu->item_rows,
91 (row_major) ? &menu->items[i]->right
92 : &menu->items[i]->down,
93 (row_major) ? &menu->items[i]->left
94 : &menu->items[i]->up,
95 (row_major) ? &menu->items[i]->down
96 : &menu->items[i]->right,
97 (row_major) ? &menu->items[i]->up
98 : &menu->items[i]->left);
100 /* fill in the row and column value of the item */
101 if (row_major) {
102 menu->items[i]->row = i / menu->item_cols;
103 menu->items[i]->col = i % menu->item_cols;
104 } else {
105 menu->items[i]->row = i % menu->item_rows;
106 menu->items[i]->col = i / menu->item_rows;
110 return E_OK;
114 * Calculate the neighbours for an item in menu. This routine deliberately
115 * does not refer to up/down/left/right as these concepts depend on the menu
116 * layout style (row major or not). By arranging the arguments in the right
117 * order the caller can generate the neighbours for either menu layout style.
119 static void
120 _menui_calc_neighbours(MENU *menu, int item_no, int cycle, int item_rows,
121 int item_cols, ITEM **next, ITEM **prev,
122 ITEM **major_next, ITEM **major_prev)
124 int neighbour;
126 if (item_rows < 2) {
127 if (cycle) {
128 *major_next = menu->items[item_no];
129 *major_prev = menu->items[item_no];
130 } else {
131 *major_next = NULL;
132 *major_prev = NULL;
134 } else {
135 neighbour = item_no + item_cols;
136 if (neighbour >= menu->item_count) {
137 if (cycle) {
138 if (item_rows == 2) {
139 neighbour = item_no - item_cols;
140 if (neighbour < 0)
141 neighbour = item_no;
142 *major_next = menu->items[neighbour];
143 } else {
144 *major_next =
145 menu->items[item_no % item_cols];
147 } else
148 *major_next = NULL;
149 } else
150 *major_next = menu->items[neighbour];
153 neighbour = item_no - item_cols;
154 if (neighbour < 0) {
155 if (cycle) {
156 if (item_rows == 2) {
157 neighbour = item_no + item_cols;
158 if (neighbour >= menu->item_count)
159 neighbour = item_no;
160 *major_prev = menu->items[neighbour];
161 } else {
162 neighbour = item_no +
163 (item_rows - 1) * item_cols;
165 if (neighbour >= menu->item_count)
166 neighbour = item_no +
167 (item_rows - 2)
168 * item_cols;
170 *major_prev = menu->items[neighbour];
172 } else
173 *major_prev = NULL;
174 } else
175 *major_prev = menu->items[neighbour];
178 if ((item_no % item_cols) == 0) {
179 if (cycle) {
180 if (item_cols < 2) {
181 *prev = menu->items[item_no];
182 } else {
183 neighbour = item_no + item_cols - 1;
184 if (neighbour >= menu->item_count) {
185 if (item_cols == 2) {
186 *prev = menu->items[item_no];
187 } else {
188 *prev = menu->items[menu->item_count - 1];
190 } else
191 *prev = menu->items[neighbour];
193 } else
194 *prev = NULL;
195 } else
196 *prev = menu->items[item_no - 1];
198 if ((item_no % item_cols) == (item_cols - 1)) {
199 if (cycle) {
200 if (item_cols < 2) {
201 *next = menu->items[item_no];
202 } else {
203 neighbour = item_no - item_cols + 1;
204 if (neighbour >= menu->item_count) {
205 if (item_cols == 2) {
206 *next = menu->items[item_no];
207 } else {
208 neighbour = item_cols * item_no / item_cols;
210 *next = menu->items[neighbour];
212 } else
213 *next = menu->items[neighbour];
215 } else
216 *next = NULL;
217 } else {
218 neighbour = item_no + 1;
219 if (neighbour >= menu->item_count) {
220 if (cycle) {
221 neighbour = item_cols * (item_rows - 1);
222 *next = menu->items[neighbour];
223 } else
224 *next = NULL;
225 } else
226 *next = menu->items[neighbour];
231 * Goto the item pointed to by item and adjust the menu structure
232 * accordingly. Call the term and init functions if required.
235 _menui_goto_item(MENU *menu, ITEM *item, int new_top_row)
237 int old_top_row = menu->top_row, old_cur_item = menu->cur_item;
239 /* If we get a null then the menu is not cyclic so deny request */
240 if (item == NULL)
241 return E_REQUEST_DENIED;
243 menu->in_init = 1;
244 if (menu->top_row != new_top_row) {
245 if ((menu->posted == 1) && (menu->menu_term != NULL))
246 menu->menu_term(menu);
247 menu->top_row = new_top_row;
249 if ((menu->posted == 1) && (menu->menu_init != NULL))
250 menu->menu_init(menu);
253 /* this looks like wasted effort but it can happen.... */
254 if (menu->cur_item != item->index) {
256 if ((menu->posted == 1) && (menu->item_term != NULL))
257 menu->item_term(menu);
259 menu->cur_item = item->index;
260 menu->cur_row = item->row;
261 menu->cur_col = item->col;
263 if (menu->posted == 1)
264 _menui_redraw_menu(menu, old_top_row, old_cur_item);
266 if ((menu->posted == 1) && (menu->item_init != NULL))
267 menu->item_init(menu);
271 menu->in_init = 0;
272 return E_OK;
276 * Attempt to match items with the pattern buffer in the direction given
277 * by iterating over the menu items. If a match is found return E_OK
278 * otherwise return E_NO_MATCH
281 _menui_match_items(MENU *menu, int direction, int *item_matched)
283 int i, caseless;
285 caseless = ((menu->opts & O_IGNORECASE) == O_IGNORECASE);
287 i = menu->cur_item;
288 if (direction == MATCH_NEXT_FORWARD) {
289 if (++i >= menu->item_count) i = 0;
290 } else if (direction == MATCH_NEXT_REVERSE) {
291 if (--i < 0) i = menu->item_count - 1;
295 do {
296 if (menu->items[i]->name.length >= menu->plen) {
297 /* no chance if pattern is longer */
298 if (caseless) {
299 if (strncasecmp(menu->items[i]->name.string,
300 menu->pattern,
301 (size_t) menu->plen) == 0) {
302 *item_matched = i;
303 menu->match_len = menu->plen;
304 return E_OK;
306 } else {
307 if (strncmp(menu->items[i]->name.string,
308 menu->pattern,
309 (size_t) menu->plen) == 0) {
310 *item_matched = i;
311 menu->match_len = menu->plen;
312 return E_OK;
317 if ((direction == MATCH_FORWARD) ||
318 (direction == MATCH_NEXT_FORWARD)) {
319 if (++i >= menu->item_count) i = 0;
320 } else {
321 if (--i <= 0) i = menu->item_count - 1;
323 } while (i != menu->cur_item);
325 menu->match_len = 0; /* match did not succeed - kill the match len. */
326 return E_NO_MATCH;
330 * Attempt to match the pattern buffer against the items. If c is a
331 * printable character then add it to the pattern buffer prior to
332 * performing the match. Direction determines the direction of matching.
333 * If the match is successful update the item_matched variable with the
334 * index of the item that matched the pattern.
337 _menui_match_pattern(MENU *menu, int c, int direction, int *item_matched)
339 if (menu == NULL)
340 return E_BAD_ARGUMENT;
341 if (menu->items == NULL)
342 return E_BAD_ARGUMENT;
343 if (*menu->items == NULL)
344 return E_BAD_ARGUMENT;
346 if (isprint(c)) {
347 /* add char to buffer - first allocate room for it */
348 if ((menu->pattern = (char *)
349 realloc(menu->pattern,
350 menu->plen + sizeof(char) +
351 ((menu->plen > 0)? 0 : 1)))
352 == NULL)
353 return E_SYSTEM_ERROR;
354 menu->pattern[menu->plen] = c;
355 menu->pattern[++menu->plen] = '\0';
357 /* there is no chance of a match if pattern is longer
358 than all the items */
359 if (menu->plen >= menu->max_item_width) {
360 menu->pattern[--menu->plen] = '\0';
361 return E_NO_MATCH;
364 if (_menui_match_items(menu, direction,
365 item_matched) == E_NO_MATCH) {
366 menu->pattern[--menu->plen] = '\0';
367 return E_NO_MATCH;
368 } else
369 return E_OK;
370 } else {
371 if (_menui_match_items(menu, direction,
372 item_matched) == E_OK) {
373 return E_OK;
374 } else {
375 return E_NO_MATCH;
381 * Draw an item in the subwindow complete with appropriate highlighting.
383 void
384 _menui_draw_item(MENU *menu, int item)
386 int j, pad_len, mark_len;
388 mark_len = max(menu->mark.length, menu->unmark.length);
390 wmove(menu->scrwin,
391 menu->items[item]->row - menu->top_row,
392 menu->items[item]->col * (menu->col_width + 1));
394 if (menu->cur_item == item)
395 wattrset(menu->scrwin, menu->fore);
396 if ((menu->items[item]->opts & O_SELECTABLE) != O_SELECTABLE)
397 wattron(menu->scrwin, menu->grey);
399 /* deal with the menu mark, if one is set.
400 * We mark the selected items and write blanks for
401 * all others unless the menu unmark string is set in which
402 * case the unmark string is written.
404 if (menu->items[item]->selected == 1) {
405 if (menu->mark.string != NULL) {
406 for (j = 0; j < menu->mark.length; j++) {
407 waddch(menu->scrwin,
408 menu->mark.string[j]);
411 /* blank any length difference between mark & unmark */
412 for (j = menu->mark.length; j < mark_len; j++)
413 waddch(menu->scrwin, ' ');
414 } else {
415 if (menu->unmark.string != NULL) {
416 for (j = 0; j < menu->unmark.length; j++) {
417 waddch(menu->scrwin,
418 menu->unmark.string[j]);
421 /* blank any length difference between mark & unmark */
422 for (j = menu->unmark.length; j < mark_len; j++)
423 waddch(menu->scrwin, ' ');
426 /* add the menu name */
427 for (j=0; j < menu->items[item]->name.length; j++)
428 waddch(menu->scrwin,
429 menu->items[item]->name.string[j]);
431 pad_len = menu->col_width - menu->items[item]->name.length
432 - mark_len - 1;
433 if ((menu->opts & O_SHOWDESC) == O_SHOWDESC) {
434 pad_len -= menu->items[item]->description.length - 1;
435 for (j = 0; j < pad_len; j++)
436 waddch(menu->scrwin, menu->pad);
437 for (j = 0; j < menu->items[item]->description.length; j++) {
438 waddch(menu->scrwin,
439 menu->items[item]->description.string[j]);
441 } else {
442 for (j = 0; j < pad_len; j++)
443 waddch(menu->scrwin, ' ');
445 menu->items[item]->visible = 1;
447 /* kill any special attributes... */
448 wattrset(menu->scrwin, menu->back);
451 * Fill in the spacing between items, annoying but it looks
452 * odd if the menu items are inverse because the spacings do not
453 * have the same attributes as the items.
455 if ((menu->items[item]->col > 0) &&
456 (menu->items[item]->col < (menu->item_cols - 1))) {
457 wmove(menu->scrwin,
458 menu->items[item]->row - menu->top_row,
459 menu->items[item]->col * (menu->col_width + 1) - 1);
460 waddch(menu->scrwin, ' ');
463 /* and position the cursor nicely */
464 pos_menu_cursor(menu);
468 * Draw the menu in the subwindow provided.
471 _menui_draw_menu(MENU *menu)
473 int rowmajor, i, j, max_items, last_item, row = -1, col = -1;
475 rowmajor = ((menu->opts & O_ROWMAJOR) == O_ROWMAJOR);
477 for (i = 0; i < menu->item_count; i++) {
478 if (menu->items[i]->row == menu->top_row)
479 break;
480 menu->items[i]->visible = 0;
483 wmove(menu->scrwin, 0, 0);
485 menu->col_width = getmaxx(menu->scrwin) / menu->cols;
487 max_items = menu->rows * menu->cols;
488 last_item = ((max_items + i) > menu->item_count) ? menu->item_count :
489 (max_items + i);
491 for (; i < last_item; i++) {
492 if (i > menu->item_count) {
493 /* no more items to draw, write background blanks */
494 wattrset(menu->scrwin, menu->back);
495 if (row < 0) {
496 row = menu->items[menu->item_count - 1]->row;
497 col = menu->items[menu->item_count - 1]->col;
500 if (rowmajor) {
501 col++;
502 if (col > menu->cols) {
503 col = 0;
504 row++;
506 } else {
507 row++;
508 if (row > menu->rows) {
509 row = 0;
510 col++;
513 wmove(menu->scrwin, row,
514 col * (menu->col_width + 1));
515 for (j = 0; j < menu->col_width; j++)
516 waddch(menu->scrwin, ' ');
517 } else {
518 _menui_draw_item(menu, i);
524 if (last_item < menu->item_count) {
525 for (j = last_item; j < menu->item_count; j++)
526 menu->items[j]->visible = 0;
529 return E_OK;
534 * Calculate the widest menu item and stash it in the menu struct.
537 void
538 _menui_max_item_size(MENU *menu)
540 int i, with_desc, width;
542 with_desc = ((menu->opts & O_SHOWDESC) == O_SHOWDESC);
544 for (i = 0; i < menu->item_count; i++) {
545 width = menu->items[i]->name.length
546 + max(menu->mark.length, menu->unmark.length);
547 if (with_desc)
548 width += menu->items[i]->description.length + 1;
550 menu->max_item_width = max(menu->max_item_width, width);
556 * Redraw the menu on the screen. If the current item has changed then
557 * unhighlight the old item and highlight the new one.
559 static void
560 _menui_redraw_menu(MENU *menu, int old_top_row, int old_cur_item)
563 if (menu->top_row != old_top_row) {
564 /* top row changed - redo the whole menu
565 * XXXX this could be improved if we had wscrl implemented.
567 * XXXX we could scroll the window and just fill in the
568 * XXXX changed lines.
570 wclear(menu->scrwin);
571 _menui_draw_menu(menu);
572 } else {
573 if (menu->cur_item != old_cur_item) {
574 /* redo the old item as a normal one. */
575 _menui_draw_item(menu, old_cur_item);
577 /* and then redraw the current item */
578 _menui_draw_item(menu, menu->cur_item);