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