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