1 /* $NetBSD: internals.c,v 1.12 2003/03/09 01:08:48 lukem Exp $ */
4 * Copyright (c) 1998-1999 Brett Lymn (blymn@baea.com.au, brett_lymn@yahoo.com.au)
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
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
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.
29 #include <sys/cdefs.h>
30 __RCSID("$NetBSD: internals.c,v 1.12 2003/03/09 01:08:48 lukem Exp $");
36 #include "internals.h"
38 /* internal function prototypes */
40 _menui_calc_neighbours(MENU
*menu
, int item_no
, int cycle
, int item_rows
,
41 int item_cols
, ITEM
**next
, ITEM
**prev
,
42 ITEM
**major_next
, ITEM
**major_prev
);
43 static void _menui_redraw_menu(MENU
*menu
, int old_top_row
, int old_cur_item
);
46 * Link all the menu items together to speed up navigation. We need
47 * to calculate the widest item entry, then work out how many columns
48 * of items the window will accommodate and then how many rows there will
49 * be. Once the layout is determined the neighbours of each item is
50 * calculated and the item structures updated.
53 _menui_stitch_items(MENU
*menu
)
55 int i
, cycle
, row_major
;
57 cycle
= ((menu
->opts
& O_NONCYCLIC
) != O_NONCYCLIC
);
58 row_major
= ((menu
->opts
& O_ROWMAJOR
) == O_ROWMAJOR
);
60 if (menu
->posted
== 1)
62 if (menu
->items
== NULL
)
63 return E_BAD_ARGUMENT
;
66 menu
->item_rows
= menu
->item_count
/ menu
->cols
;
67 menu
->item_cols
= menu
->cols
;
68 if (menu
->item_count
> (menu
->item_rows
* menu
->item_cols
))
71 menu
->item_cols
= menu
->item_count
/ menu
->rows
;
72 menu
->item_rows
= menu
->rows
;
73 if (menu
->item_count
> (menu
->item_rows
* menu
->item_cols
))
78 _menui_max_item_size(menu
);
80 for (i
= 0; i
< menu
->item_count
; i
++) {
81 /* Calculate the neighbours. The ugliness here deals with
82 * the differing menu layout styles. The layout affects
83 * the neighbour calculation so we change the arguments
84 * around depending on the layout style.
86 _menui_calc_neighbours(menu
, i
, cycle
,
87 (row_major
) ? menu
->item_rows
89 (row_major
) ? menu
->item_cols
91 (row_major
) ? &menu
->items
[i
]->right
92 : &menu
->items
[i
]->down
,
93 (row_major
) ? &menu
->items
[i
]->left
94 : &menu
->items
[i
]->up
,
95 (row_major
) ? &menu
->items
[i
]->down
96 : &menu
->items
[i
]->right
,
97 (row_major
) ? &menu
->items
[i
]->up
98 : &menu
->items
[i
]->left
);
100 /* fill in the row and column value of the item */
102 menu
->items
[i
]->row
= i
/ menu
->item_cols
;
103 menu
->items
[i
]->col
= i
% menu
->item_cols
;
105 menu
->items
[i
]->row
= i
% menu
->item_rows
;
106 menu
->items
[i
]->col
= i
/ menu
->item_rows
;
114 * Calculate the neighbours for an item in menu. This routine deliberately
115 * does not refer to up/down/left/right as these concepts depend on the menu
116 * layout style (row major or not). By arranging the arguments in the right
117 * order the caller can generate the neighbours for either menu layout style.
120 _menui_calc_neighbours(MENU
*menu
, int item_no
, int cycle
, int item_rows
,
121 int item_cols
, ITEM
**next
, ITEM
**prev
,
122 ITEM
**major_next
, ITEM
**major_prev
)
128 *major_next
= menu
->items
[item_no
];
129 *major_prev
= menu
->items
[item_no
];
135 neighbour
= item_no
+ item_cols
;
136 if (neighbour
>= menu
->item_count
) {
138 if (item_rows
== 2) {
139 neighbour
= item_no
- item_cols
;
142 *major_next
= menu
->items
[neighbour
];
145 menu
->items
[item_no
% item_cols
];
150 *major_next
= menu
->items
[neighbour
];
153 neighbour
= item_no
- item_cols
;
156 if (item_rows
== 2) {
157 neighbour
= item_no
+ item_cols
;
158 if (neighbour
>= menu
->item_count
)
160 *major_prev
= menu
->items
[neighbour
];
162 neighbour
= item_no
+
163 (item_rows
- 1) * item_cols
;
165 if (neighbour
>= menu
->item_count
)
166 neighbour
= item_no
+
170 *major_prev
= menu
->items
[neighbour
];
175 *major_prev
= menu
->items
[neighbour
];
178 if ((item_no
% item_cols
) == 0) {
181 *prev
= menu
->items
[item_no
];
183 neighbour
= item_no
+ item_cols
- 1;
184 if (neighbour
>= menu
->item_count
) {
185 if (item_cols
== 2) {
186 *prev
= menu
->items
[item_no
];
188 *prev
= menu
->items
[menu
->item_count
- 1];
191 *prev
= menu
->items
[neighbour
];
196 *prev
= menu
->items
[item_no
- 1];
198 if ((item_no
% item_cols
) == (item_cols
- 1)) {
201 *next
= menu
->items
[item_no
];
203 neighbour
= item_no
- item_cols
+ 1;
204 if (neighbour
>= menu
->item_count
) {
205 if (item_cols
== 2) {
206 *next
= menu
->items
[item_no
];
208 neighbour
= item_cols
* item_no
/ item_cols
;
210 *next
= menu
->items
[neighbour
];
213 *next
= menu
->items
[neighbour
];
218 neighbour
= item_no
+ 1;
219 if (neighbour
>= menu
->item_count
) {
221 neighbour
= item_cols
* (item_rows
- 1);
222 *next
= menu
->items
[neighbour
];
226 *next
= menu
->items
[neighbour
];
231 * Goto the item pointed to by item and adjust the menu structure
232 * accordingly. Call the term and init functions if required.
235 _menui_goto_item(MENU
*menu
, ITEM
*item
, int new_top_row
)
237 int old_top_row
= menu
->top_row
, old_cur_item
= menu
->cur_item
;
239 /* If we get a null then the menu is not cyclic so deny request */
241 return E_REQUEST_DENIED
;
244 if (menu
->top_row
!= new_top_row
) {
245 if ((menu
->posted
== 1) && (menu
->menu_term
!= NULL
))
246 menu
->menu_term(menu
);
247 menu
->top_row
= new_top_row
;
249 if ((menu
->posted
== 1) && (menu
->menu_init
!= NULL
))
250 menu
->menu_init(menu
);
253 /* this looks like wasted effort but it can happen.... */
254 if (menu
->cur_item
!= item
->index
) {
256 if ((menu
->posted
== 1) && (menu
->item_term
!= NULL
))
257 menu
->item_term(menu
);
259 menu
->cur_item
= item
->index
;
260 menu
->cur_row
= item
->row
;
261 menu
->cur_col
= item
->col
;
263 if (menu
->posted
== 1)
264 _menui_redraw_menu(menu
, old_top_row
, old_cur_item
);
266 if ((menu
->posted
== 1) && (menu
->item_init
!= NULL
))
267 menu
->item_init(menu
);
276 * Attempt to match items with the pattern buffer in the direction given
277 * by iterating over the menu items. If a match is found return E_OK
278 * otherwise return E_NO_MATCH
281 _menui_match_items(MENU
*menu
, int direction
, int *item_matched
)
285 caseless
= ((menu
->opts
& O_IGNORECASE
) == O_IGNORECASE
);
288 if (direction
== MATCH_NEXT_FORWARD
) {
289 if (++i
>= menu
->item_count
) i
= 0;
290 } else if (direction
== MATCH_NEXT_REVERSE
) {
291 if (--i
< 0) i
= menu
->item_count
- 1;
296 if (menu
->items
[i
]->name
.length
>= menu
->plen
) {
297 /* no chance if pattern is longer */
299 if (strncasecmp(menu
->items
[i
]->name
.string
,
301 (size_t) menu
->plen
) == 0) {
303 menu
->match_len
= menu
->plen
;
307 if (strncmp(menu
->items
[i
]->name
.string
,
309 (size_t) menu
->plen
) == 0) {
311 menu
->match_len
= menu
->plen
;
317 if ((direction
== MATCH_FORWARD
) ||
318 (direction
== MATCH_NEXT_FORWARD
)) {
319 if (++i
>= menu
->item_count
) i
= 0;
321 if (--i
<= 0) i
= menu
->item_count
- 1;
323 } while (i
!= menu
->cur_item
);
325 menu
->match_len
= 0; /* match did not succeed - kill the match len. */
330 * Attempt to match the pattern buffer against the items. If c is a
331 * printable character then add it to the pattern buffer prior to
332 * performing the match. Direction determines the direction of matching.
333 * If the match is successful update the item_matched variable with the
334 * index of the item that matched the pattern.
337 _menui_match_pattern(MENU
*menu
, int c
, int direction
, int *item_matched
)
340 return E_BAD_ARGUMENT
;
341 if (menu
->items
== NULL
)
342 return E_BAD_ARGUMENT
;
343 if (*menu
->items
== NULL
)
344 return E_BAD_ARGUMENT
;
347 /* add char to buffer - first allocate room for it */
348 if ((menu
->pattern
= (char *)
349 realloc(menu
->pattern
,
350 menu
->plen
+ sizeof(char) +
351 ((menu
->plen
> 0)? 0 : 1)))
353 return E_SYSTEM_ERROR
;
354 menu
->pattern
[menu
->plen
] = c
;
355 menu
->pattern
[++menu
->plen
] = '\0';
357 /* there is no chance of a match if pattern is longer
358 than all the items */
359 if (menu
->plen
>= menu
->max_item_width
) {
360 menu
->pattern
[--menu
->plen
] = '\0';
364 if (_menui_match_items(menu
, direction
,
365 item_matched
) == E_NO_MATCH
) {
366 menu
->pattern
[--menu
->plen
] = '\0';
371 if (_menui_match_items(menu
, direction
,
372 item_matched
) == E_OK
) {
381 * Draw an item in the subwindow complete with appropriate highlighting.
384 _menui_draw_item(MENU
*menu
, int item
)
386 int j
, pad_len
, mark_len
;
388 mark_len
= max(menu
->mark
.length
, menu
->unmark
.length
);
391 menu
->items
[item
]->row
- menu
->top_row
,
392 menu
->items
[item
]->col
* (menu
->col_width
+ 1));
394 if (menu
->cur_item
== item
)
395 wattrset(menu
->scrwin
, menu
->fore
);
396 if ((menu
->items
[item
]->opts
& O_SELECTABLE
) != O_SELECTABLE
)
397 wattron(menu
->scrwin
, menu
->grey
);
399 /* deal with the menu mark, if one is set.
400 * We mark the selected items and write blanks for
401 * all others unless the menu unmark string is set in which
402 * case the unmark string is written.
404 if (menu
->items
[item
]->selected
== 1) {
405 if (menu
->mark
.string
!= NULL
) {
406 for (j
= 0; j
< menu
->mark
.length
; j
++) {
408 menu
->mark
.string
[j
]);
411 /* blank any length difference between mark & unmark */
412 for (j
= menu
->mark
.length
; j
< mark_len
; j
++)
413 waddch(menu
->scrwin
, ' ');
415 if (menu
->unmark
.string
!= NULL
) {
416 for (j
= 0; j
< menu
->unmark
.length
; j
++) {
418 menu
->unmark
.string
[j
]);
421 /* blank any length difference between mark & unmark */
422 for (j
= menu
->unmark
.length
; j
< mark_len
; j
++)
423 waddch(menu
->scrwin
, ' ');
426 /* add the menu name */
427 for (j
=0; j
< menu
->items
[item
]->name
.length
; j
++)
429 menu
->items
[item
]->name
.string
[j
]);
431 pad_len
= menu
->col_width
- menu
->items
[item
]->name
.length
433 if ((menu
->opts
& O_SHOWDESC
) == O_SHOWDESC
) {
434 pad_len
-= menu
->items
[item
]->description
.length
- 1;
435 for (j
= 0; j
< pad_len
; j
++)
436 waddch(menu
->scrwin
, menu
->pad
);
437 for (j
= 0; j
< menu
->items
[item
]->description
.length
; j
++) {
439 menu
->items
[item
]->description
.string
[j
]);
442 for (j
= 0; j
< pad_len
; j
++)
443 waddch(menu
->scrwin
, ' ');
445 menu
->items
[item
]->visible
= 1;
447 /* kill any special attributes... */
448 wattrset(menu
->scrwin
, menu
->back
);
451 * Fill in the spacing between items, annoying but it looks
452 * odd if the menu items are inverse because the spacings do not
453 * have the same attributes as the items.
455 if ((menu
->items
[item
]->col
> 0) &&
456 (menu
->items
[item
]->col
< (menu
->item_cols
- 1))) {
458 menu
->items
[item
]->row
- menu
->top_row
,
459 menu
->items
[item
]->col
* (menu
->col_width
+ 1) - 1);
460 waddch(menu
->scrwin
, ' ');
463 /* and position the cursor nicely */
464 pos_menu_cursor(menu
);
468 * Draw the menu in the subwindow provided.
471 _menui_draw_menu(MENU
*menu
)
473 int rowmajor
, i
, j
, max_items
, last_item
, row
= -1, col
= -1;
475 rowmajor
= ((menu
->opts
& O_ROWMAJOR
) == O_ROWMAJOR
);
477 for (i
= 0; i
< menu
->item_count
; i
++) {
478 if (menu
->items
[i
]->row
== menu
->top_row
)
480 menu
->items
[i
]->visible
= 0;
483 wmove(menu
->scrwin
, 0, 0);
485 menu
->col_width
= getmaxx(menu
->scrwin
) / menu
->cols
;
487 max_items
= menu
->rows
* menu
->cols
;
488 last_item
= ((max_items
+ i
) > menu
->item_count
) ? menu
->item_count
:
491 for (; i
< last_item
; i
++) {
492 if (i
> menu
->item_count
) {
493 /* no more items to draw, write background blanks */
494 wattrset(menu
->scrwin
, menu
->back
);
496 row
= menu
->items
[menu
->item_count
- 1]->row
;
497 col
= menu
->items
[menu
->item_count
- 1]->col
;
502 if (col
> menu
->cols
) {
508 if (row
> menu
->rows
) {
513 wmove(menu
->scrwin
, row
,
514 col
* (menu
->col_width
+ 1));
515 for (j
= 0; j
< menu
->col_width
; j
++)
516 waddch(menu
->scrwin
, ' ');
518 _menui_draw_item(menu
, i
);
524 if (last_item
< menu
->item_count
) {
525 for (j
= last_item
; j
< menu
->item_count
; j
++)
526 menu
->items
[j
]->visible
= 0;
534 * Calculate the widest menu item and stash it in the menu struct.
538 _menui_max_item_size(MENU
*menu
)
540 int i
, with_desc
, width
;
542 with_desc
= ((menu
->opts
& O_SHOWDESC
) == O_SHOWDESC
);
544 for (i
= 0; i
< menu
->item_count
; i
++) {
545 width
= menu
->items
[i
]->name
.length
546 + max(menu
->mark
.length
, menu
->unmark
.length
);
548 width
+= menu
->items
[i
]->description
.length
+ 1;
550 menu
->max_item_width
= max(menu
->max_item_width
, width
);
556 * Redraw the menu on the screen. If the current item has changed then
557 * unhighlight the old item and highlight the new one.
560 _menui_redraw_menu(MENU
*menu
, int old_top_row
, int old_cur_item
)
563 if (menu
->top_row
!= old_top_row
) {
564 /* top row changed - redo the whole menu
565 * XXXX this could be improved if we had wscrl implemented.
567 * XXXX we could scroll the window and just fill in the
568 * XXXX changed lines.
570 wclear(menu
->scrwin
);
571 _menui_draw_menu(menu
);
573 if (menu
->cur_item
!= old_cur_item
) {
574 /* redo the old item as a normal one. */
575 _menui_draw_item(menu
, old_cur_item
);
577 /* and then redraw the current item */
578 _menui_draw_item(menu
, menu
->cur_item
);