xref: /netbsd-src/lib/libmenu/internals.c (revision 89c5a767f8fc7a4633b2d409966e2becbb98ff92)
1 /*	$NetBSD: internals.c,v 1.5 2000/03/13 22:59:22 soren Exp $	*/
2 
3 /*-
4  * Copyright (c) 1998-1999 Brett Lymn (blymn@baea.com.au, brett_lymn@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 neighbours for either menu layout style.
115    */
116 static void
117 _menui_calc_neighbours(menu, item_no, cycle, item_rows, item_cols, next, prev,
118 			major_next, major_prev)
119 	MENU *menu;
120 	int item_no;
121 	int cycle;
122 	int item_rows;
123 	int item_cols;
124 	ITEM **next;
125 	ITEM **prev;
126 	ITEM **major_next;
127 	ITEM **major_prev;
128 {
129 	int neighbour;
130 
131 	if (item_rows < 2) {
132 		if (cycle) {
133 			*major_next = menu->items[item_no];
134 			*major_prev = menu->items[item_no];
135 		} else {
136 			*major_next = NULL;
137 			*major_prev = NULL;
138 		}
139 	} else {
140 		neighbour = item_no + item_cols;
141 		if (neighbour >= menu->item_count) {
142 			if (cycle) {
143 				if (item_rows == 2) {
144 					neighbour = item_no - item_cols;
145 					if (neighbour < 0)
146 						neighbour = item_no;
147 					*major_next = menu->items[neighbour];
148 				} else {
149 					*major_next =
150 						menu->items[item_no % item_cols];
151 				}
152 			} else
153 				*major_next = NULL;
154 		} else
155 			*major_next = menu->items[neighbour];
156 
157 
158 		neighbour = item_no - item_cols;
159 		if (neighbour < 0) {
160 			if (cycle) {
161 				if (item_rows == 2) {
162 					neighbour = item_no + item_cols;
163 					if (neighbour >= menu->item_count)
164 						neighbour = item_no;
165 					*major_prev = menu->items[neighbour];
166 				} else {
167 					neighbour = item_no +
168 						(item_rows - 1) * item_cols;
169 
170 					if (neighbour >= menu->item_count)
171 						neighbour = item_no +
172 							(item_rows - 2)
173 							* item_cols;
174 
175 					*major_prev = menu->items[neighbour];
176 				}
177 			} else
178 				*major_prev = NULL;
179 		} else
180 			*major_prev = menu->items[neighbour];
181 	}
182 
183 	if ((item_no % item_cols) == 0) {
184 		if (cycle) {
185 			if (item_cols  < 2) {
186 				*prev = menu->items[item_no];
187 			} else {
188 				neighbour = item_no + item_cols - 1;
189 				if (neighbour >= menu->item_count) {
190 					if (item_cols == 2) {
191 						*prev = menu->items[item_no];
192 					} else {
193 						*prev = menu->items[menu->item_count - 1];
194 					}
195 				} else
196 					*prev = menu->items[neighbour];
197 			}
198 		} else
199 			*prev = NULL;
200 	} else
201 		*prev = menu->items[item_no - 1];
202 
203 	if ((item_no % item_cols) == (item_cols - 1)) {
204 		if (cycle) {
205 			if (item_cols  < 2) {
206 				*next = menu->items[item_no];
207 			} else {
208 				neighbour = item_no - item_cols + 1;
209 				if (neighbour >= menu->item_count) {
210 					if (item_cols == 2) {
211 						*next = menu->items[item_no];
212 					} else {
213 						neighbour = item_cols * item_no / item_cols;
214 
215 						*next = menu->items[neighbour];
216 					}
217 				} else
218 					*next = menu->items[neighbour];
219 			}
220 		} else
221 			*next = NULL;
222 	} else {
223 		neighbour = item_no + 1;
224 		if (neighbour >= menu->item_count) {
225 			if (cycle) {
226 				neighbour = item_cols * (item_rows - 1);
227 				*next = menu->items[neighbour];
228 			} else
229 				*next = NULL;
230 		} else
231 			*next = menu->items[neighbour];
232 	}
233 }
234 
235 /*
236  * Goto the item pointed to by item and adjust the menu structure
237  * accordingly.  Call the term and init functions if required.
238  */
239 int
240 _menui_goto_item(menu, item, new_top_row)
241 	MENU *menu;
242 	ITEM *item;
243 	int new_top_row;
244 {
245 	int old_top_row = menu->top_row, old_cur_item = menu->cur_item;
246 
247 	  /* If we get a null then the menu is not cyclic so deny request */
248 	if (item == NULL)
249 		return E_REQUEST_DENIED;
250 
251 	menu->in_init = 1;
252 	if (menu->top_row != new_top_row) {
253 		if ((menu->posted == 1) && (menu->menu_term != NULL))
254 			menu->menu_term(menu);
255 		menu->top_row = new_top_row;
256 
257 		if ((menu->posted == 1) && (menu->menu_init != NULL))
258 			menu->menu_init(menu);
259 	}
260 
261 	  /* this looks like wasted effort but it can happen.... */
262 	if (menu->cur_item != item->index) {
263 
264 		if ((menu->posted == 1) && (menu->item_term != NULL))
265 			menu->item_term(menu);
266 
267 		menu->cur_item = item->index;
268 		menu->cur_row = item->row;
269 		menu->cur_col = item->col;
270 
271 		if (menu->posted == 1)
272 			_menui_redraw_menu(menu, old_top_row, old_cur_item);
273 
274 		if ((menu->posted == 1) && (menu->item_init != NULL))
275 			menu->item_init(menu);
276 
277 	}
278 
279 	menu->in_init = 0;
280 	return E_OK;
281 }
282 
283 /*
284  * Attempt to match items with the pattern buffer in the direction given
285  * by iterating over the menu items.  If a match is found return E_OK
286  * otherwise return E_NO_MATCH
287  */
288 int
289 _menui_match_items(menu, direction, item_matched)
290 	MENU *menu;
291 	int direction;
292 	int *item_matched;
293 {
294 	int i, caseless;
295 
296 	caseless = ((menu->opts & O_IGNORECASE) == O_IGNORECASE);
297 
298 	i = menu->cur_item;
299 	if (direction == MATCH_NEXT_FORWARD) {
300 		if (++i >= menu->item_count) i = 0;
301 	} else if (direction == MATCH_NEXT_REVERSE) {
302 		if (--i < 0) i = menu->item_count - 1;
303 	}
304 
305 
306 	do {
307 		if (menu->items[i]->name.length >= menu->plen) {
308 			  /* no chance if pattern is longer */
309 			if (caseless) {
310 				if (strncasecmp(menu->items[i]->name.string,
311 						menu->pattern,
312 						(size_t) menu->plen) == 0) {
313 					*item_matched = i;
314 					menu->match_len = menu->plen;
315 					return E_OK;
316 				}
317 			} else {
318 				if (strncmp(menu->items[i]->name.string,
319 					    menu->pattern,
320 					    (size_t) menu->plen) == 0) {
321 					*item_matched = i;
322 					menu->match_len = menu->plen;
323 					return E_OK;
324 				}
325 			}
326 		}
327 
328 		if ((direction == MATCH_FORWARD) ||
329 		    (direction == MATCH_NEXT_FORWARD)) {
330 			if (++i >= menu->item_count) i = 0;
331 		} else {
332 			if (--i <= 0) i = menu->item_count - 1;
333 		}
334 	} while (i != menu->cur_item);
335 
336 	menu->match_len = 0; /* match did not succeed - kill the match len. */
337 	return E_NO_MATCH;
338 }
339 
340 /*
341  * Attempt to match the pattern buffer against the items.  If c is a
342  * printable character then add it to the pattern buffer prior to
343  * performing the match.  Direction determines the direction of matching.
344  * If the match is successful update the item_matched variable with the
345  * index of the item that matched the pattern.
346  */
347 int
348 _menui_match_pattern(menu, c, direction, item_matched)
349 	MENU *menu;
350 	char c;
351 	int direction;
352 	int *item_matched;
353 {
354 	if (menu == NULL)
355 		return E_BAD_ARGUMENT;
356 	if (menu->items == NULL)
357 		return E_BAD_ARGUMENT;
358 	if (*menu->items == NULL)
359 		return E_BAD_ARGUMENT;
360 
361 	if (isprint(c)) {
362 		  /* add char to buffer - first allocate room for it */
363 		if ((menu->pattern = (char *)
364 		     realloc(menu->pattern,
365 			     menu->plen + sizeof(char) +
366 			     ((menu->plen > 0)? 0 : 1)))
367 		    == NULL)
368 			return E_SYSTEM_ERROR;
369 		menu->pattern[menu->plen] = c;
370 		menu->pattern[++menu->plen] = '\0';
371 
372 		  /* there is no chance of a match if pattern is longer
373 		     than all the items */
374 		if (menu->plen >= menu->max_item_width) {
375 			menu->pattern[--menu->plen] = '\0';
376 			return E_NO_MATCH;
377 		}
378 
379 		if (_menui_match_items(menu, direction,
380 					item_matched) == E_NO_MATCH) {
381 			menu->pattern[--menu->plen] = '\0';
382 			return E_NO_MATCH;
383 		} else
384 			return E_OK;
385 	} else {
386 		if (_menui_match_items(menu, direction,
387 					item_matched) == E_OK) {
388 			return E_OK;
389 		} else {
390 			return E_NO_MATCH;
391 		}
392 	}
393 }
394 
395 /*
396  * Draw an item in the subwindow complete with appropriate highlighting.
397  */
398 void
399 _menui_draw_item(menu, item)
400 	MENU *menu;
401 	int item;
402 {
403 	int j, pad_len, mark_len;
404 
405 	mark_len = max(menu->mark.length, menu->unmark.length);
406 
407 	wmove(menu->menu_subwin,
408 	      menu->items[item]->row - menu->top_row,
409 	      menu->items[item]->col * (menu->col_width + 1));
410 
411 	if ((menu->cur_item == item) || (menu->items[item]->selected == 1))
412 		wattron(menu->menu_subwin, menu->fore);
413 	if ((menu->items[item]->opts & O_SELECTABLE) != O_SELECTABLE)
414 		wattron(menu->menu_subwin, menu->grey);
415 
416 	  /* deal with the menu mark, if  one is set.
417 	   * We mark the selected items and write blanks for
418 	   * all others unless the menu unmark string is set in which
419 	   * case the unmark string is written.
420 	   */
421 	if (menu->items[item]->selected == 1) {
422 		if (menu->mark.string != NULL) {
423 			for (j = 0; j < menu->mark.length; j++) {
424 				waddch(menu->menu_subwin,
425 				       menu->mark.string[j]);
426 			}
427 		}
428 		  /* blank any length difference between mark & unmark */
429 		for (j = menu->mark.length; j < mark_len; j++)
430 			waddch(menu->menu_subwin, ' ');
431 	} else {
432 		if (menu->unmark.string != NULL) {
433 			for (j = 0; j < menu->unmark.length; j++) {
434 				waddch(menu->menu_subwin,
435 				       menu->unmark.string[j]);
436 			}
437 		}
438 		  /* blank any length difference between mark & unmark */
439 		for (j = menu->unmark.length; j < mark_len; j++)
440 			waddch(menu->menu_subwin, ' ');
441 	}
442 
443 	  /* add the menu name */
444 	for (j=0; j < menu->items[item]->name.length; j++)
445 		waddch(menu->menu_subwin,
446 		       menu->items[item]->name.string[j]);
447 
448 	pad_len = menu->col_width - menu->items[item]->name.length
449 		- mark_len - 1;
450 	if ((menu->opts & O_SHOWDESC) == O_SHOWDESC) {
451 		pad_len -= menu->items[item]->description.length - 1;
452 		for (j = 0; j < pad_len; j++)
453 			waddch(menu->menu_subwin, menu->pad);
454 		for (j = 0; j < menu->items[item]->description.length; j++) {
455 			waddch(menu->menu_subwin,
456 			       menu->items[item]->description.string[j]);
457 		}
458 	} else {
459 		for (j = 0; j < pad_len; j++)
460 			waddch(menu->menu_subwin, ' ');
461 	}
462 	menu->items[item]->visible = 1;
463 	  /* kill any special attributes... */
464 	wattrset(menu->menu_subwin, menu->back);
465 
466 	  /* and position the cursor nicely */
467 	pos_menu_cursor(menu);
468 }
469 
470 /*
471  * Draw the menu in the subwindow provided.
472  */
473 int
474 _menui_draw_menu(menu)
475 	MENU *menu;
476 {
477 	int rowmajor, i, j, max_items, last_item, row = -1, col = -1;
478 
479 	rowmajor = ((menu->opts & O_ROWMAJOR) == O_ROWMAJOR);
480 
481 	for (i = 0;  i < menu->item_count; i++) {
482 		if (menu->items[i]->row == menu->top_row)
483 			break;
484 		menu->items[i]->visible = 0;
485 	}
486 
487 	wmove(menu->menu_subwin, 0, 0);
488 
489 	menu->col_width = getmaxx(menu->menu_subwin) / menu->cols;
490 
491 	  /*if ((menu->opts & O_SHOWDESC) == O_SHOWDESC)
492 	    menu->col_width++;*/
493 
494 	max_items = menu->rows * menu->cols;
495 	last_item = ((max_items + i) > menu->item_count) ? menu->item_count :
496 		(max_items + i);
497 
498 	for (; i < last_item; i++) {
499 		if (i > menu->item_count) {
500 			  /* no more items to draw, write background blanks */
501 			wattron(menu->menu_subwin, menu->back);
502 			if (row < 0) {
503 				row = menu->items[menu->item_count - 1]->row;
504 				col = menu->items[menu->item_count - 1]->col;
505 			}
506 
507 			if (rowmajor) {
508 				col++;
509 				if (col > menu->cols) {
510 					col = 0;
511 					row++;
512 				}
513 			} else {
514 				row++;
515 				if (row > menu->rows) {
516 					row = 0;
517 					col++;
518 				}
519 			}
520 			wmove(menu->menu_subwin, row,
521 			      col * (menu->col_width + 1));
522 			for (j = 0; j < menu->col_width; j++)
523 				waddch(menu->menu_subwin, ' ');
524 		} else {
525 			_menui_draw_item(menu, i);
526 
527 		}
528 
529 	}
530 
531 	if (last_item < menu->item_count) {
532 		for (j = last_item; j < menu->item_count; j++)
533 			menu->items[j]->visible = 0;
534 	}
535 
536 	return E_OK;
537 }
538 
539 
540 /*
541  * Calculate the widest menu item and stash it in the menu struct.
542  *
543  */
544 void
545 _menui_max_item_size(menu)
546 	MENU *menu;
547 {
548 	int i, with_desc, width;
549 
550 	with_desc = ((menu->opts & O_SHOWDESC) == O_SHOWDESC);
551 
552 	for (i = 0; i < menu->item_count; i++) {
553 		width = menu->items[i]->name.length
554 			+ max(menu->mark.length, menu->unmark.length);
555 		if (with_desc)
556 			width += menu->items[i]->description.length + 1;
557 
558 		menu->max_item_width = max(menu->max_item_width, width);
559 	}
560 }
561 
562 
563 /*
564  * Redraw the menu on the screen.  If the current item has changed then
565  * unhighlight the old item and highlight the new one.
566  */
567 static void
568 _menui_redraw_menu(menu, old_top_row, old_cur_item)
569 	MENU *menu;
570 	int old_top_row;
571 	int old_cur_item;
572 {
573 
574 	if (menu->top_row != old_top_row) {
575 		  /* top row changed - redo the whole menu
576 		   * XXXX this could be improved if we had wscrl implemented.
577 
578 		   * XXXX we could scroll the window and just fill in the
579 		   * XXXX changed lines.
580 		   */
581 		wclear(menu->menu_subwin);
582 		_menui_draw_menu(menu);
583 	} else {
584 		if (menu->cur_item != old_cur_item) {
585 			  /* redo the old item as a normal one. */
586 			_menui_draw_item(menu, old_cur_item);
587 		}
588 		  /* and then redraw the current item */
589 		_menui_draw_item(menu, menu->cur_item);
590 	}
591 }
592