xref: /minix3/lib/libmenu/internals.c (revision 84d9c625bfea59e274550651111ae9edfdc40fbd)
1 /*	$NetBSD: internals.c,v 1.17 2013/10/18 19:53:59 christos 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 without 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 <sys/cdefs.h>
30 __RCSID("$NetBSD: internals.c,v 1.17 2013/10/18 19:53:59 christos Exp $");
31 
32 #include <menu.h>
33 #include <ctype.h>
34 #include <stdlib.h>
35 #include <string.h>
36 #include "internals.h"
37 
38 /* internal function prototypes */
39 static void
40 _menui_calc_neighbours(MENU *menu, int item_no);
41 static void _menui_redraw_menu(MENU *menu, int old_top_row, int old_cur_item);
42 
43   /*
44    * Link all the menu items together to speed up navigation.  We need
45    * to calculate the widest item entry, then work out how many columns
46    * of items the window will accommodate and then how many rows there will
47    * be.  Once the layout is determined the neighbours of each item is
48    * calculated and the item structures updated.
49    */
50 int
_menui_stitch_items(MENU * menu)51 _menui_stitch_items(MENU *menu)
52 {
53 	int i, row_major;
54 
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 	menu->item_rows = menu->item_count / menu->cols;
63 	menu->item_cols = menu->cols;
64 	if (menu->item_count > (menu->item_rows * menu->item_cols))
65 		menu->item_rows += 1;
66 
67 	_menui_max_item_size(menu);
68 
69 	for (i = 0; i < menu->item_count; i++) {
70 		  /* fill in the row and column value of the item */
71 		if (row_major) {
72 			menu->items[i]->row = i / menu->item_cols;
73 			menu->items[i]->col = i % menu->item_cols;
74 		} else {
75 			menu->items[i]->row = i % menu->item_rows;
76 			menu->items[i]->col = i / menu->item_rows;
77 		}
78 
79 		_menui_calc_neighbours(menu, i);
80 	}
81 
82 	return E_OK;
83 }
84 
85   /*
86    * Calculate the neighbours for an item in menu.
87    */
88 static void
_menui_calc_neighbours(MENU * menu,int item_no)89 _menui_calc_neighbours(MENU *menu, int item_no)
90 {
91 	int neighbour, cycle, row_major, edge;
92 	ITEM *item;
93 
94 	row_major = ((menu->opts & O_ROWMAJOR) == O_ROWMAJOR);
95 	cycle = ((menu->opts & O_NONCYCLIC) != O_NONCYCLIC);
96 	item = menu->items[item_no];
97 
98 	if (menu->item_rows < 2) {
99 		if (cycle) {
100 			item->up = item;
101 			item->down = item;
102 		} else {
103 			item->up = NULL;
104 			item->down = NULL;
105 		}
106 	} else {
107 
108 		/* up */
109 		if (menu->item_cols < 2) {
110 			if (item_no == 0) {
111 				if (cycle)
112 					item->up =
113 					    menu->items[menu->item_count - 1];
114 				else
115 					item->up = NULL;
116 			} else
117 				item->up = menu->items[item_no - 1];
118 		} else {
119 			edge = 0;
120 			if (row_major) {
121 				if (item->row == 0) {
122 					neighbour =
123 				    	(menu->item_rows - 1) * menu->item_cols
124 						+ item->col;
125 					if (neighbour >= menu->item_count)
126 						neighbour -= menu->item_cols;
127 					edge = 1;
128 				} else
129 					neighbour = item_no - menu->item_cols;
130 			} else {
131 				if (item->row == 0) {
132 					neighbour = menu->item_rows * item->col
133 						+ menu->item_rows - 1;
134 					if (neighbour >= menu->item_count)
135 						neighbour = menu->item_count - 1;
136 					edge = 1;
137 				} else
138 					neighbour = item_no - 1;
139 			}
140 
141 
142 			item->up = menu->items[neighbour];
143 			if ((!cycle) && (edge == 1))
144 				item->up = NULL;
145 		}
146 
147 		/* Down */
148 		if (menu->item_cols < 2) {
149 			if (item_no == (menu->item_count - 1)) {
150 				if (cycle)
151 					item->down = menu->items[0];
152 				else
153 					item->down = NULL;
154 			} else
155 				item->down = menu->items[item_no + 1];
156 		} else {
157 			edge = 0;
158 			if (row_major) {
159 				if (item->row == menu->item_rows - 1) {
160 					neighbour = item->col;
161 					edge = 1;
162 				} else {
163 					neighbour = item_no + menu->item_cols;
164 					if (neighbour >= menu->item_count) {
165 						neighbour = item->col;
166 						edge = 1;
167 					}
168 				}
169 			} else {
170 				if (item->row == menu->item_rows - 1) {
171 					neighbour = item->col * menu->item_rows;
172 					edge = 1;
173 				} else {
174 					neighbour = item_no + 1;
175 					if (neighbour >= menu->item_count) {
176 						neighbour = item->col
177 						    * menu->item_rows;
178 						edge = 1;
179 					}
180 				}
181 			}
182 
183 			item->down = menu->items[neighbour];
184 			if ((!cycle) && (edge == 1))
185 				item->down = NULL;
186 		}
187 	}
188 
189 	if (menu->item_cols < 2) {
190 		if (cycle) {
191 			item->left = item;
192 			item->right = item;
193 		} else {
194 			item->left = NULL;
195 			item->right = NULL;
196 		}
197 	} else {
198 		/* left */
199 		if (menu->item_rows < 2) {
200 			if (item_no == 0) {
201 				if (cycle)
202 					item->left =
203 					    menu->items[menu->item_count - 1];
204 				else
205 					item->left = NULL;
206 			} else
207 				item->left = menu->items[item_no - 1];
208 		} else {
209 			edge = 0;
210 			if (row_major) {
211 				if (item->col == 0) {
212 					neighbour = item_no + menu->cols - 1;
213 					if (neighbour >= menu->item_count)
214 						neighbour = menu->item_count - 1;
215 					edge = 1;
216 				} else
217 					neighbour = item_no - 1;
218 			} else {
219 				if (item->col == 0) {
220 					neighbour = menu->item_rows
221 					    * (menu->item_cols - 1) + item->row;
222 					if (neighbour >= menu->item_count)
223 						neighbour -= menu->item_rows;
224 					edge = 1;
225 				} else
226 					neighbour = item_no - menu->item_rows;
227 			}
228 
229 			item->left = menu->items[neighbour];
230 			if ((!cycle) && (edge == 1))
231 				item->left = NULL;
232 		}
233 
234 		/* right */
235 		if (menu->item_rows < 2) {
236 			if (item_no == menu->item_count - 1) {
237 				if (cycle)
238 					item->right = menu->items[0];
239 				else
240 					item->right = NULL;
241 			} else
242 				item->right = menu->items[item_no + 1];
243 		} else {
244 			edge = 0;
245 			if (row_major) {
246 				if (item->col == menu->item_cols - 1) {
247 					neighbour = item_no - menu->item_cols
248 					    + 1;
249 					edge = 1;
250 				} else if (item_no == menu->item_count - 1) {
251 					neighbour = item->row * menu->item_cols;
252 					edge = 1;
253 				} else
254 					neighbour = item_no + 1;
255 			} else {
256 				if (item->col == menu->item_cols - 1) {
257 					neighbour = item->row;
258 					edge = 1;
259 				} else {
260 					neighbour = item_no + menu->item_rows;
261 					if (neighbour >= menu->item_count) {
262 						neighbour = item->row;
263 						edge = 1;
264 					}
265 				}
266 			}
267 
268 			item->right = menu->items[neighbour];
269 			if ((!cycle) && (edge == 1))
270 				item->right = NULL;
271 		}
272 	}
273 }
274 
275 /*
276  * Goto the item pointed to by item and adjust the menu structure
277  * accordingly.  Call the term and init functions if required.
278  */
279 int
_menui_goto_item(MENU * menu,ITEM * item,int new_top_row)280 _menui_goto_item(MENU *menu, ITEM *item, int new_top_row)
281 {
282 	int old_top_row = menu->top_row, old_cur_item = menu->cur_item;
283 
284 	  /* If we get a null then the menu is not cyclic so deny request */
285 	if (item == NULL)
286 		return E_REQUEST_DENIED;
287 
288 	menu->in_init = 1;
289 	if (menu->top_row != new_top_row) {
290 		if ((menu->posted == 1) && (menu->menu_term != NULL))
291 			menu->menu_term(menu);
292 		menu->top_row = new_top_row;
293 
294 		if ((menu->posted == 1) && (menu->menu_init != NULL))
295 			menu->menu_init(menu);
296 	}
297 
298 	  /* this looks like wasted effort but it can happen.... */
299 	if (menu->cur_item != item->index) {
300 
301 		if ((menu->posted == 1) && (menu->item_term != NULL))
302 			menu->item_term(menu);
303 
304 		menu->cur_item = item->index;
305 		menu->cur_row = item->row;
306 		menu->cur_col = item->col;
307 
308 		if (menu->posted == 1)
309 			_menui_redraw_menu(menu, old_top_row, old_cur_item);
310 
311 		if ((menu->posted == 1) && (menu->item_init != NULL))
312 			menu->item_init(menu);
313 
314 	}
315 
316 	menu->in_init = 0;
317 	return E_OK;
318 }
319 
320 /*
321  * Attempt to match items with the pattern buffer in the direction given
322  * by iterating over the menu items.  If a match is found return E_OK
323  * otherwise return E_NO_MATCH
324  */
325 int
_menui_match_items(MENU * menu,int direction,int * item_matched)326 _menui_match_items(MENU *menu, int direction, int *item_matched)
327 {
328 	int i, caseless;
329 
330 	caseless = ((menu->opts & O_IGNORECASE) == O_IGNORECASE);
331 
332 	i = menu->cur_item;
333 	if (direction == MATCH_NEXT_FORWARD) {
334 		if (++i >= menu->item_count) i = 0;
335 	} else if (direction == MATCH_NEXT_REVERSE) {
336 		if (--i < 0) i = menu->item_count - 1;
337 	}
338 
339 
340 	do {
341 		if (menu->items[i]->name.length >= menu->plen) {
342 			  /* no chance if pattern is longer */
343 			if (caseless) {
344 				if (strncasecmp(menu->items[i]->name.string,
345 						menu->pattern,
346 						(size_t) menu->plen) == 0) {
347 					*item_matched = i;
348 					menu->match_len = menu->plen;
349 					return E_OK;
350 				}
351 			} else {
352 				if (strncmp(menu->items[i]->name.string,
353 					    menu->pattern,
354 					    (size_t) menu->plen) == 0) {
355 					*item_matched = i;
356 					menu->match_len = menu->plen;
357 					return E_OK;
358 				}
359 			}
360 		}
361 
362 		if ((direction == MATCH_FORWARD) ||
363 		    (direction == MATCH_NEXT_FORWARD)) {
364 			if (++i >= menu->item_count) i = 0;
365 		} else {
366 			if (--i <= 0) i = menu->item_count - 1;
367 		}
368 	} while (i != menu->cur_item);
369 
370 	menu->match_len = 0; /* match did not succeed - kill the match len. */
371 	return E_NO_MATCH;
372 }
373 
374 /*
375  * Attempt to match the pattern buffer against the items.  If c is a
376  * printable character then add it to the pattern buffer prior to
377  * performing the match.  Direction determines the direction of matching.
378  * If the match is successful update the item_matched variable with the
379  * index of the item that matched the pattern.
380  */
381 int
_menui_match_pattern(MENU * menu,int c,int direction,int * item_matched)382 _menui_match_pattern(MENU *menu, int c, int direction, int *item_matched)
383 {
384 	if (menu == NULL)
385 		return E_BAD_ARGUMENT;
386 	if (menu->items == NULL)
387 		return E_BAD_ARGUMENT;
388 	if (*menu->items == NULL)
389 		return E_BAD_ARGUMENT;
390 
391 	if (isprint(c)) {
392 		  /* add char to buffer - first allocate room for it */
393 		if ((menu->pattern = (char *)
394 		     realloc(menu->pattern,
395 			     menu->plen + sizeof(char) +
396 			     ((menu->plen > 0)? 0 : 1)))
397 		    == NULL)
398 			return E_SYSTEM_ERROR;
399 		menu->pattern[menu->plen] = c;
400 		menu->pattern[++menu->plen] = '\0';
401 
402 		  /* there is no chance of a match if pattern is longer
403 		     than all the items */
404 		if (menu->plen >= menu->max_item_width) {
405 			menu->pattern[--menu->plen] = '\0';
406 			return E_NO_MATCH;
407 		}
408 
409 		if (_menui_match_items(menu, direction,
410 					item_matched) == E_NO_MATCH) {
411 			menu->pattern[--menu->plen] = '\0';
412 			return E_NO_MATCH;
413 		} else
414 			return E_OK;
415 	} else {
416 		if (_menui_match_items(menu, direction,
417 					item_matched) == E_OK) {
418 			return E_OK;
419 		} else {
420 			return E_NO_MATCH;
421 		}
422 	}
423 }
424 
425 /*
426  * Draw an item in the subwindow complete with appropriate highlighting.
427  */
428 void
_menui_draw_item(MENU * menu,int item)429 _menui_draw_item(MENU *menu, int item)
430 {
431 	int j, pad_len, mark_len;
432 
433 	mark_len = max(menu->mark.length, menu->unmark.length);
434 
435 	wmove(menu->scrwin,
436 	      menu->items[item]->row - menu->top_row,
437 	      menu->items[item]->col * (menu->col_width + 1));
438 
439 	if (menu->cur_item == item)
440 		wattrset(menu->scrwin, menu->fore);
441 	if ((menu->items[item]->opts & O_SELECTABLE) != O_SELECTABLE)
442 		wattron(menu->scrwin, menu->grey);
443 
444 	  /* deal with the menu mark, if  one is set.
445 	   * We mark the selected items and write blanks for
446 	   * all others unless the menu unmark string is set in which
447 	   * case the unmark string is written.
448 	   */
449 	if ((menu->items[item]->selected == 1) ||
450 	    (((menu->opts & O_ONEVALUE) == O_ONEVALUE) &&
451 		(menu->cur_item == item))) {
452 		if (menu->mark.string != NULL) {
453 			for (j = 0; j < menu->mark.length; j++) {
454 				waddch(menu->scrwin,
455 				       menu->mark.string[j]);
456 			}
457 		}
458 		  /* blank any length difference between mark & unmark */
459 		for (j = menu->mark.length; j < mark_len; j++)
460 			waddch(menu->scrwin, ' ');
461 	} else {
462 		if (menu->unmark.string != NULL) {
463 			for (j = 0; j < menu->unmark.length; j++) {
464 				waddch(menu->scrwin,
465 				       menu->unmark.string[j]);
466 			}
467 		}
468 		  /* blank any length difference between mark & unmark */
469 		for (j = menu->unmark.length; j < mark_len; j++)
470 			waddch(menu->scrwin, ' ');
471 	}
472 
473 	  /* add the menu name */
474 	for (j=0; j < menu->items[item]->name.length; j++)
475 		waddch(menu->scrwin,
476 		       menu->items[item]->name.string[j]);
477 
478 	pad_len = menu->col_width - menu->items[item]->name.length
479 		- mark_len - 1;
480 	if ((menu->opts & O_SHOWDESC) == O_SHOWDESC) {
481 		pad_len -= menu->items[item]->description.length - 1;
482 		for (j = 0; j < pad_len; j++)
483 			waddch(menu->scrwin, menu->pad);
484 		for (j = 0; j < menu->items[item]->description.length; j++) {
485 			waddch(menu->scrwin,
486 			       menu->items[item]->description.string[j]);
487 		}
488 	} else {
489 		for (j = 0; j < pad_len; j++)
490 			waddch(menu->scrwin, ' ');
491 	}
492 	menu->items[item]->visible = 1;
493 
494 	  /* kill any special attributes... */
495 	wattrset(menu->scrwin, menu->back);
496 
497 	  /*
498 	   * Fill in the spacing between items, annoying but it looks
499 	   * odd if the menu items are inverse because the spacings do not
500 	   * have the same attributes as the items.
501 	   */
502 	if ((menu->items[item]->col > 0) &&
503 	    (menu->items[item]->col < (menu->item_cols - 1))) {
504 		wmove(menu->scrwin,
505 		      menu->items[item]->row - menu->top_row,
506 		      menu->items[item]->col * (menu->col_width + 1) - 1);
507 		waddch(menu->scrwin, ' ');
508 	}
509 
510 	  /* and position the cursor nicely */
511 	pos_menu_cursor(menu);
512 }
513 
514 /*
515  * Draw the menu in the subwindow provided.
516  */
517 int
_menui_draw_menu(MENU * menu)518 _menui_draw_menu(MENU *menu)
519 {
520 	int rowmajor, i, j, k, row = -1, stride;
521 	int incr, cur_row, offset, row_count;
522 
523 	rowmajor = ((menu->opts & O_ROWMAJOR) == O_ROWMAJOR);
524 
525 	if (rowmajor) {
526 		stride = 1;
527 		incr = menu->item_cols;
528 	} else {
529 		stride = menu->item_rows;
530 		incr = 1;
531 	}
532 	row_count = 0;
533 
534 	for (i = 0;  i < menu->item_count; i += incr) {
535 		if (menu->items[i]->row == menu->top_row)
536 			break;
537 		row_count++;
538 		for (j = 0; j < menu->item_cols; j++) {
539 			offset = j * stride + i;
540 			if (offset >= menu->item_count)
541 				break; /* done */
542 			menu->items[offset]->visible = 0;
543 		}
544 	}
545 
546 	wmove(menu->scrwin, 0, 0);
547 
548 	menu->col_width = getmaxx(menu->scrwin) / menu->cols;
549 
550 	for (cur_row = 0; cur_row < menu->rows; cur_row++) {
551 		for (j = 0; j < menu->cols; j++) {
552 			offset = j * stride + i;
553 			if (offset >= menu->item_count) {
554 			   /* no more items to draw, write background blanks */
555 				wattrset(menu->scrwin, menu->back);
556 				if (row < 0) {
557 					row = menu->items[menu->item_count - 1]->row;
558 				}
559 
560 				wmove(menu->scrwin, cur_row,
561 				      j * (menu->col_width + 1));
562 				for (k = 0; k < menu->col_width; k++)
563 					waddch(menu->scrwin, ' ');
564 			} else {
565 				_menui_draw_item(menu, offset);
566 			}
567 		}
568 
569 		i += incr;
570 		row_count++;
571 	}
572 
573 	if (row_count < menu->item_rows) {
574 		for (cur_row = row_count;  cur_row < menu->item_rows; cur_row++) {
575 			for (j = 0; j < menu->item_cols; j++) {
576 				offset = j * stride + i;
577 				if (offset >= menu->item_count)
578 					break; /* done */
579 				menu->items[offset]->visible = 0;
580 			}
581 			i += incr;
582 		}
583 	}
584 
585 	return E_OK;
586 }
587 
588 
589 /*
590  * Calculate the widest menu item and stash it in the menu struct.
591  *
592  */
593 void
_menui_max_item_size(MENU * menu)594 _menui_max_item_size(MENU *menu)
595 {
596 	int i, with_desc, width;
597 
598 	with_desc = ((menu->opts & O_SHOWDESC) == O_SHOWDESC);
599 
600 	for (i = 0; i < menu->item_count; i++) {
601 		width = menu->items[i]->name.length
602 			+ max(menu->mark.length, menu->unmark.length);
603 		if (with_desc)
604 			width += menu->items[i]->description.length + 1;
605 
606 		menu->max_item_width = max(menu->max_item_width, width);
607 	}
608 }
609 
610 
611 /*
612  * Redraw the menu on the screen.  If the current item has changed then
613  * unhighlight the old item and highlight the new one.
614  */
615 static void
_menui_redraw_menu(MENU * menu,int old_top_row,int old_cur_item)616 _menui_redraw_menu(MENU *menu, int old_top_row, int old_cur_item)
617 {
618 
619 	if (menu->top_row != old_top_row) {
620 		  /* top row changed - redo the whole menu
621 		   * XXXX this could be improved if we had wscrl implemented.
622 
623 		   * XXXX we could scroll the window and just fill in the
624 		   * XXXX changed lines.
625 		   */
626 		wclear(menu->scrwin);
627 		_menui_draw_menu(menu);
628 	} else {
629 		if (menu->cur_item != old_cur_item) {
630 			  /* redo the old item as a normal one. */
631 			_menui_draw_item(menu, old_cur_item);
632 		}
633 		  /* and then redraw the current item */
634 		_menui_draw_item(menu, menu->cur_item);
635 	}
636 }
637