1 /* $NetBSD: internals.c,v 1.17 2013/10/18 19:53:59 christos 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.17 2013/10/18 19:53:59 christos Exp $");
36 #include "internals.h"
38 /* internal function prototypes */
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
);
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.
51 _menui_stitch_items(MENU
*menu
)
55 row_major
= ((menu
->opts
& O_ROWMAJOR
) == O_ROWMAJOR
);
57 if (menu
->posted
== 1)
59 if (menu
->items
== NULL
)
60 return E_BAD_ARGUMENT
;
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
))
67 _menui_max_item_size(menu
);
69 for (i
= 0; i
< menu
->item_count
; i
++) {
70 /* fill in the row and column value of the item */
72 menu
->items
[i
]->row
= i
/ menu
->item_cols
;
73 menu
->items
[i
]->col
= i
% menu
->item_cols
;
75 menu
->items
[i
]->row
= i
% menu
->item_rows
;
76 menu
->items
[i
]->col
= i
/ menu
->item_rows
;
79 _menui_calc_neighbours(menu
, i
);
86 * Calculate the neighbours for an item in menu.
89 _menui_calc_neighbours(MENU
*menu
, int item_no
)
91 int neighbour
, cycle
, row_major
, edge
;
94 row_major
= ((menu
->opts
& O_ROWMAJOR
) == O_ROWMAJOR
);
95 cycle
= ((menu
->opts
& O_NONCYCLIC
) != O_NONCYCLIC
);
96 item
= menu
->items
[item_no
];
98 if (menu
->item_rows
< 2) {
109 if (menu
->item_cols
< 2) {
113 menu
->items
[menu
->item_count
- 1];
117 item
->up
= menu
->items
[item_no
- 1];
121 if (item
->row
== 0) {
123 (menu
->item_rows
- 1) * menu
->item_cols
125 if (neighbour
>= menu
->item_count
)
126 neighbour
-= menu
->item_cols
;
129 neighbour
= item_no
- menu
->item_cols
;
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;
138 neighbour
= item_no
- 1;
142 item
->up
= menu
->items
[neighbour
];
143 if ((!cycle
) && (edge
== 1))
148 if (menu
->item_cols
< 2) {
149 if (item_no
== (menu
->item_count
- 1)) {
151 item
->down
= menu
->items
[0];
155 item
->down
= menu
->items
[item_no
+ 1];
159 if (item
->row
== menu
->item_rows
- 1) {
160 neighbour
= item
->col
;
163 neighbour
= item_no
+ menu
->item_cols
;
164 if (neighbour
>= menu
->item_count
) {
165 neighbour
= item
->col
;
170 if (item
->row
== menu
->item_rows
- 1) {
171 neighbour
= item
->col
* menu
->item_rows
;
174 neighbour
= item_no
+ 1;
175 if (neighbour
>= menu
->item_count
) {
176 neighbour
= item
->col
183 item
->down
= menu
->items
[neighbour
];
184 if ((!cycle
) && (edge
== 1))
189 if (menu
->item_cols
< 2) {
199 if (menu
->item_rows
< 2) {
203 menu
->items
[menu
->item_count
- 1];
207 item
->left
= menu
->items
[item_no
- 1];
211 if (item
->col
== 0) {
212 neighbour
= item_no
+ menu
->cols
- 1;
213 if (neighbour
>= menu
->item_count
)
214 neighbour
= menu
->item_count
- 1;
217 neighbour
= item_no
- 1;
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
;
226 neighbour
= item_no
- menu
->item_rows
;
229 item
->left
= menu
->items
[neighbour
];
230 if ((!cycle
) && (edge
== 1))
235 if (menu
->item_rows
< 2) {
236 if (item_no
== menu
->item_count
- 1) {
238 item
->right
= menu
->items
[0];
242 item
->right
= menu
->items
[item_no
+ 1];
246 if (item
->col
== menu
->item_cols
- 1) {
247 neighbour
= item_no
- menu
->item_cols
250 } else if (item_no
== menu
->item_count
- 1) {
251 neighbour
= item
->row
* menu
->item_cols
;
254 neighbour
= item_no
+ 1;
256 if (item
->col
== menu
->item_cols
- 1) {
257 neighbour
= item
->row
;
260 neighbour
= item_no
+ menu
->item_rows
;
261 if (neighbour
>= menu
->item_count
) {
262 neighbour
= item
->row
;
268 item
->right
= menu
->items
[neighbour
];
269 if ((!cycle
) && (edge
== 1))
276 * Goto the item pointed to by item and adjust the menu structure
277 * accordingly. Call the term and init functions if required.
280 _menui_goto_item(MENU
*menu
, ITEM
*item
, int new_top_row
)
282 int old_top_row
= menu
->top_row
, old_cur_item
= menu
->cur_item
;
284 /* If we get a null then the menu is not cyclic so deny request */
286 return E_REQUEST_DENIED
;
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
;
294 if ((menu
->posted
== 1) && (menu
->menu_init
!= NULL
))
295 menu
->menu_init(menu
);
298 /* this looks like wasted effort but it can happen.... */
299 if (menu
->cur_item
!= item
->index
) {
301 if ((menu
->posted
== 1) && (menu
->item_term
!= NULL
))
302 menu
->item_term(menu
);
304 menu
->cur_item
= item
->index
;
305 menu
->cur_row
= item
->row
;
306 menu
->cur_col
= item
->col
;
308 if (menu
->posted
== 1)
309 _menui_redraw_menu(menu
, old_top_row
, old_cur_item
);
311 if ((menu
->posted
== 1) && (menu
->item_init
!= NULL
))
312 menu
->item_init(menu
);
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
326 _menui_match_items(MENU
*menu
, int direction
, int *item_matched
)
330 caseless
= ((menu
->opts
& O_IGNORECASE
) == O_IGNORECASE
);
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;
341 if (menu
->items
[i
]->name
.length
>= menu
->plen
) {
342 /* no chance if pattern is longer */
344 if (strncasecmp(menu
->items
[i
]->name
.string
,
346 (size_t) menu
->plen
) == 0) {
348 menu
->match_len
= menu
->plen
;
352 if (strncmp(menu
->items
[i
]->name
.string
,
354 (size_t) menu
->plen
) == 0) {
356 menu
->match_len
= menu
->plen
;
362 if ((direction
== MATCH_FORWARD
) ||
363 (direction
== MATCH_NEXT_FORWARD
)) {
364 if (++i
>= menu
->item_count
) i
= 0;
366 if (--i
<= 0) i
= menu
->item_count
- 1;
368 } while (i
!= menu
->cur_item
);
370 menu
->match_len
= 0; /* match did not succeed - kill the match len. */
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.
382 _menui_match_pattern(MENU
*menu
, int c
, int direction
, int *item_matched
)
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
;
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)))
398 return E_SYSTEM_ERROR
;
399 menu
->pattern
[menu
->plen
] = c
;
400 menu
->pattern
[++menu
->plen
] = '\0';
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';
409 if (_menui_match_items(menu
, direction
,
410 item_matched
) == E_NO_MATCH
) {
411 menu
->pattern
[--menu
->plen
] = '\0';
416 if (_menui_match_items(menu
, direction
,
417 item_matched
) == E_OK
) {
426 * Draw an item in the subwindow complete with appropriate highlighting.
429 _menui_draw_item(MENU
*menu
, int item
)
431 int j
, pad_len
, mark_len
;
433 mark_len
= max(menu
->mark
.length
, menu
->unmark
.length
);
436 menu
->items
[item
]->row
- menu
->top_row
,
437 menu
->items
[item
]->col
* (menu
->col_width
+ 1));
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
);
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.
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
++) {
455 menu
->mark
.string
[j
]);
458 /* blank any length difference between mark & unmark */
459 for (j
= menu
->mark
.length
; j
< mark_len
; j
++)
460 waddch(menu
->scrwin
, ' ');
462 if (menu
->unmark
.string
!= NULL
) {
463 for (j
= 0; j
< menu
->unmark
.length
; j
++) {
465 menu
->unmark
.string
[j
]);
468 /* blank any length difference between mark & unmark */
469 for (j
= menu
->unmark
.length
; j
< mark_len
; j
++)
470 waddch(menu
->scrwin
, ' ');
473 /* add the menu name */
474 for (j
=0; j
< menu
->items
[item
]->name
.length
; j
++)
476 menu
->items
[item
]->name
.string
[j
]);
478 pad_len
= menu
->col_width
- menu
->items
[item
]->name
.length
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
++) {
486 menu
->items
[item
]->description
.string
[j
]);
489 for (j
= 0; j
< pad_len
; j
++)
490 waddch(menu
->scrwin
, ' ');
492 menu
->items
[item
]->visible
= 1;
494 /* kill any special attributes... */
495 wattrset(menu
->scrwin
, menu
->back
);
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.
502 if ((menu
->items
[item
]->col
> 0) &&
503 (menu
->items
[item
]->col
< (menu
->item_cols
- 1))) {
505 menu
->items
[item
]->row
- menu
->top_row
,
506 menu
->items
[item
]->col
* (menu
->col_width
+ 1) - 1);
507 waddch(menu
->scrwin
, ' ');
510 /* and position the cursor nicely */
511 pos_menu_cursor(menu
);
515 * Draw the menu in the subwindow provided.
518 _menui_draw_menu(MENU
*menu
)
520 int rowmajor
, i
, j
, k
, row
= -1, stride
;
521 int incr
, cur_row
, offset
, row_count
;
523 rowmajor
= ((menu
->opts
& O_ROWMAJOR
) == O_ROWMAJOR
);
527 incr
= menu
->item_cols
;
529 stride
= menu
->item_rows
;
534 for (i
= 0; i
< menu
->item_count
; i
+= incr
) {
535 if (menu
->items
[i
]->row
== menu
->top_row
)
538 for (j
= 0; j
< menu
->item_cols
; j
++) {
539 offset
= j
* stride
+ i
;
540 if (offset
>= menu
->item_count
)
542 menu
->items
[offset
]->visible
= 0;
546 wmove(menu
->scrwin
, 0, 0);
548 menu
->col_width
= getmaxx(menu
->scrwin
) / menu
->cols
;
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
);
557 row
= menu
->items
[menu
->item_count
- 1]->row
;
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
, ' ');
565 _menui_draw_item(menu
, offset
);
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
)
579 menu
->items
[offset
]->visible
= 0;
590 * Calculate the widest menu item and stash it in the menu struct.
594 _menui_max_item_size(MENU
*menu
)
596 int i
, with_desc
, width
;
598 with_desc
= ((menu
->opts
& O_SHOWDESC
) == O_SHOWDESC
);
600 for (i
= 0; i
< menu
->item_count
; i
++) {
601 width
= menu
->items
[i
]->name
.length
602 + max(menu
->mark
.length
, menu
->unmark
.length
);
604 width
+= menu
->items
[i
]->description
.length
+ 1;
606 menu
->max_item_width
= max(menu
->max_item_width
, width
);
612 * Redraw the menu on the screen. If the current item has changed then
613 * unhighlight the old item and highlight the new one.
616 _menui_redraw_menu(MENU
*menu
, int old_top_row
, int old_cur_item
)
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.
623 * XXXX we could scroll the window and just fill in the
624 * XXXX changed lines.
626 wclear(menu
->scrwin
);
627 _menui_draw_menu(menu
);
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
);
633 /* and then redraw the current item */
634 _menui_draw_item(menu
, menu
->cur_item
);