internals.c revision 1.5 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 (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 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