1 /* Listbox widget implementation. */
2 /* $Id: listbox.c,v 1.212 2005/08/02 01:20:45 miciah Exp $ */
12 #include "bfu/dialog.h"
13 #include "bfu/hierbox.h"
14 #include "bfu/listbox.h"
15 #include "config/kbdbind.h"
16 #include "intl/gettext/libintl.h"
17 #include "terminal/draw.h"
18 #include "terminal/mouse.h"
19 #include "terminal/terminal.h"
20 #include "util/color.h"
21 #include "util/conv.h"
22 #include "util/lists.h"
25 #define VERTICAL_LISTBOX_MARGIN 3
28 add_dlg_listbox(struct dialog
*dlg
, int height
, void *box_data
)
30 struct widget
*widget
= &dlg
->widgets
[dlg
->number_of_widgets
++];
32 widget
->type
= WIDGET_LISTBOX
;
33 widget
->data
= box_data
;
35 widget
->info
.listbox
.height
= height
;
39 get_listbox_widget_data(struct widget_data
*widget_data
)
41 assert(widget_data
->widget
->type
== WIDGET_LISTBOX
);
42 return ((struct listbox_data
*) widget_data
->widget
->data
);
45 /* Layout for generic boxes */
47 dlg_format_listbox(struct terminal
*term
, struct widget_data
*widget_data
,
48 int x
, int *y
, int w
, int max_height
, int *rw
,
49 enum format_align align
)
51 int min
, optimal_h
, height
;
53 /* Height bussiness follows: */
55 /* This is only weird heuristic, it could scale well I hope. */
56 optimal_h
= max_height
* 7 / 10 - VERTICAL_LISTBOX_MARGIN
;
57 min
= get_opt_int("ui.dialogs.listbox_min_height");
59 if (max_height
- VERTICAL_LISTBOX_MARGIN
< min
) {
60 /* Big trouble: can't satisfy even the minimum :-(. */
61 height
= max_height
- VERTICAL_LISTBOX_MARGIN
;
62 } else if (optimal_h
< min
) {
68 set_box(&widget_data
->box
, x
, *y
, w
, height
);
76 *|item00->root = NULL ,item10->prev
77 *|item00->child <----->|item10->root
78 *|item00->next <-. |item10->child [<->]
86 *|item02->root = NULL ,item11->prev
87 *|item02->child <----->|item11->root ,item20->prev
88 *`item02->next \ |item11->child <----->|item20->root
89 * | |item11->next <-. |item20->child [<->]
93 * | |item12->child [<->]
97 * `-|item13->root ,item21->prev
98 * |item13->child <----->|item21->root
99 * `item13->next |item21->child [<->]
104 /* Traverse a hierarchic tree from @item by @offset items, calling @fn,
105 * if it is not NULL, on each item traversed (that is, each of the items
106 * that we move _through_; this means from the passed @item up to,
107 * but not including, the returned item).
109 * @offset may be negative to indicate that we should traverse upwards.
111 * Besides the current item, @fn is also passed @d, which is otherwise unused
112 * by traverse_listbox_items_list, and a pointer to @offset, which @fn can set
113 * to 0 to stop traversal or to other values to change the direction in which
114 * or the number of items over which we will traverse.
116 * @fn should return 1 if it freed its passed item, or return 0 otherwise.
118 * If the passed @offset is zero, we set @offset to 1 and traverse thru
119 * the list (down) until either we reach the end or @fn sets @offset to 0. */
120 /* From the box structure, we should use only 'items' here. */
121 struct listbox_item
*
122 traverse_listbox_items_list(struct listbox_item
*item
, struct listbox_data
*box
,
123 int offset
, int follow_visible
,
124 int (*fn
)(struct listbox_item
*, void *, int *),
127 struct listbox_item
*visible_item
= item
;
130 int infinite
= !offset
;
132 if (!item
) return NULL
;
137 while (offset
&& !stop
) {
138 /* We need to cache these. Or what will happen if something
139 * will free us item too early? However, we rely on item
140 * being at least NULL in that case. */
141 /* There must be no orphaned listbox_items. No free()d roots
142 * and no dangling children. */
143 #define item_cache(item) \
145 croot = box->ops->get_root(item); cprev = item->prev; cnext = item->next; \
147 struct listbox_item
*croot
, *cprev
, *cnext
;
151 if (fn
&& (!follow_visible
|| item
->visible
)) {
152 if (fn(item
, d
, &offset
)) {
153 /* We was free()d! Let's try to carry on w/ the
154 * cached coordinates. */
158 infinite
= 0; /* safety (matches) */
164 /* Otherwise we climb back up when last item in root
166 struct listbox_item
*cragsman
= NULL
;
168 /* Direction DOWN. */
170 if (!infinite
) offset
--;
172 if (item
&& !list_empty(item
->child
) && item
->expanded
173 && (!follow_visible
|| item
->visible
)) {
174 /* Descend to children. */
175 item
= item
->child
.next
;
181 && (void *) cnext
== &croot
->child
) {
182 /* Last item in a non-root list, climb to your
184 if (!cragsman
) cragsman
= item
;
189 if (!croot
&& (!cnext
|| (void *) cnext
== box
->items
)) {
190 /* Last item in the root list, quit.. */
193 /* ..and fall back where we were. */
199 /* We're not at the end of anything, go on. */
206 if (!item
|| (follow_visible
&& !item
->visible
)) {
215 if (!infinite
) offset
++;
218 && (void *) cprev
== &croot
->child
) {
219 /* First item in a non-root list, climb to your
226 if (!croot
&& (void *) cprev
== box
->items
) {
227 /* First item in the root list, quit. */
232 /* We're not at the start of anything, go on. */
233 if (!levmove
&& !stop
) {
237 while (item
&& !list_empty(item
->child
)
239 && (!follow_visible
|| item
->visible
)) {
240 /* Descend to children. */
241 item
= item
->child
.prev
;
248 if (!item
|| (follow_visible
&& !item
->visible
)) {
261 calc_dist(struct listbox_item
*item
, void *data_
, int *offset
)
263 int *item_offset
= data_
;
267 else if (*offset
> 0)
273 /* Moves the selected item by [dist] items. If [dist] is out of the current
274 * range, the selected item is moved to the extreme (ie, the top or bottom) */
276 listbox_sel_move(struct widget_data
*widget_data
, int dist
)
278 struct listbox_data
*box
= get_listbox_widget_data(widget_data
);
280 if (list_empty(*box
->items
)) return;
282 if (!box
->top
) box
->top
= box
->items
->next
;
283 if (!box
->sel
) box
->sel
= box
->top
;
285 /* We want to have these visible if possible. */
286 if (box
->top
&& !box
->top
->visible
) {
287 box
->top
= traverse_listbox_items_list(box
->top
, box
,
292 if (!dist
&& !box
->sel
->visible
) dist
= 1;
295 box
->sel
= traverse_listbox_items_list(box
->sel
, box
, dist
, 1,
298 /* box->sel_offset becomes the offset of the new box->sel
302 if (box
->sel_offset
< 0) {
303 /* We must scroll up. */
306 } else if (box
->sel_offset
>= widget_data
->box
.height
) {
307 /* We must scroll down. */
308 box
->sel_offset
= widget_data
->box
.height
- 1;
309 box
->top
= traverse_listbox_items_list(box
->sel
, box
,
310 1 - widget_data
->box
.height
,
316 test_search(struct listbox_item
*item
, void *data_
, int *offset
)
318 struct listbox_context
*listbox_context
= data_
;
320 if (item
!= listbox_context
->item
)
321 listbox_context
->offset
++;
329 listbox_item_offset(struct listbox_data
*box
, struct listbox_item
*item
)
331 struct listbox_context ctx
;
333 memset(&ctx
, 0, sizeof(ctx
));
337 traverse_listbox_items_list(box
->items
->next
, box
, 0, 1, test_search
, &ctx
);
343 listbox_sel(struct widget_data
*widget_data
, struct listbox_item
*item
)
345 struct listbox_data
*box
= get_listbox_widget_data(widget_data
);
347 listbox_sel_move(widget_data
,
348 listbox_item_offset(box
, item
)
349 - listbox_item_offset(box
, box
->sel
));
353 /* Takes care about rendering of each listbox item. */
355 display_listbox_item(struct listbox_item
*item
, void *data_
, int *offset
)
357 struct listbox_context
*data
= data_
;
358 unsigned char *stylename
;
359 int len
; /* Length of the current text field. */
360 struct color_pair
*color
;
361 int depth
= item
->depth
+ 1;
365 stylename
= (item
== data
->box
->sel
) ? "menu.selected"
366 : ((item
->marked
) ? "menu.marked"
369 color
= get_bfu_color(data
->term
, stylename
);
371 y
= data
->widget_data
->box
.y
+ data
->offset
;
372 for (d
= 0; d
< depth
- 1; d
++) {
373 struct listbox_item
*root
= item
;
374 struct listbox_item
*child
= item
;
377 for (i
= depth
- d
; i
; i
--) {
379 if (root
) root
= data
->box
->ops
->get_root(root
);
383 x
= data
->widget_data
->box
.x
+ d
* 5;
384 draw_text(data
->term
, x
, y
, " ", 5, 0, color
);
386 if (root
? root
->child
.prev
== child
387 : data
->box
->items
->prev
== child
)
388 continue; /* We were the last branch. */
390 draw_border_char(data
->term
, x
+ 1, y
, BORDER_SVLINE
, color
);
394 enum border_char str
[5] =
395 { 32, BORDER_SRTEE
, BORDER_SHLINE
, BORDER_SHLINE
, 32 };
398 switch (item
->type
) {
402 struct listbox_item
*root
= data
->box
->ops
->get_root(item
);
405 if (item
== root
->child
.prev
) {
406 str
[1] = BORDER_SDLCORNER
;
409 struct list_head
*p
= data
->box
->items
;
411 if (p
->next
== item
) {
412 str
[1] = BORDER_SULCORNER
;
413 } else if (p
->prev
== item
) {
414 str
[1] = BORDER_SDLCORNER
;
421 str
[1] = (item
->expanded
) ? '-' : '+';
425 INTERNAL("Unknown item type");
429 if (item
->marked
) str
[4] = '*';
431 x
= data
->widget_data
->box
.x
+ (depth
- 1) * 5;
432 for (i
= 0; i
< 5; i
++) {
433 draw_border_char(data
->term
, x
+ i
, y
, str
[i
], color
);
437 x
= data
->widget_data
->box
.x
+ depth
* 5;
439 if (item
->type
== BI_SEPARATOR
) {
441 int width
= data
->widget_data
->box
.width
- depth
* 5;
443 for (i
= 0; i
< width
; i
++) {
444 draw_border_char(data
->term
, x
+ i
, y
, BORDER_SHLINE
, color
);
447 } else if (data
->box
->ops
&& data
->box
->ops
->draw
) {
448 int width
= data
->widget_data
->box
.width
- depth
* 5;
450 data
->box
->ops
->draw(item
, data
, x
, y
, width
);
454 struct listbox_ops
*ops
= data
->box
->ops
;
456 assert(ops
&& ops
->get_info
);
458 text
= ops
->get_text(item
, data
->term
);
462 int_upper_bound(&len
, int_max(0, data
->widget_data
->box
.width
- depth
* 5));
464 draw_text(data
->term
, x
, y
, text
, len
, 0, color
);
469 if (item
== data
->box
->sel
) {
470 x
= data
->widget_data
->box
.x
;
472 /* For blind users: */
473 set_cursor(data
->term
, x
, y
, 1);
474 set_window_ptr(data
->dlg_data
->win
, x
, y
);
482 /* Displays a dialog box */
483 static widget_handler_status_T
484 display_listbox(struct dialog_data
*dlg_data
, struct widget_data
*widget_data
)
486 struct terminal
*term
= dlg_data
->win
->term
;
487 struct listbox_data
*box
= get_listbox_widget_data(widget_data
);
488 struct listbox_context data
;
490 listbox_sel_move(widget_data
, 0);
492 draw_box(term
, &widget_data
->box
, ' ', 0,
493 get_bfu_color(term
, "menu.normal"));
495 memset(&data
, 0, sizeof(data
));
497 data
.widget_data
= widget_data
;
499 data
.dlg_data
= dlg_data
;
501 traverse_listbox_items_list(box
->top
, box
, widget_data
->box
.height
,
502 1, display_listbox_item
, &data
);
504 return EVENT_PROCESSED
;
508 check_old_state(struct listbox_item
*item
, void *info_
, int *offset
)
510 struct listbox_data
*box
= info_
;
512 if (box
->sel
== item
)
514 else if (box
->top
== item
)
517 if (!box
->sel
&& !box
->top
)
523 static widget_handler_status_T
524 init_listbox(struct dialog_data
*dlg_data
, struct widget_data
*widget_data
)
526 struct hierbox_browser
*browser
= dlg_data
->dlg
->udata2
;
527 struct listbox_data
*box
= get_listbox_widget_data(widget_data
);
529 /* Try to restore the position from last time */
530 if (!list_empty(browser
->root
.child
) && browser
->box_data
.items
) {
531 copy_struct(box
, &browser
->box_data
);
533 traverse_listbox_items_list(browser
->root
.child
.next
, box
, 0, 0,
534 check_old_state
, box
);
536 box
->sel
= (!box
->sel
) ? browser
->box_data
.sel
: NULL
;
537 box
->top
= (!box
->top
) ? browser
->box_data
.top
: NULL
;
538 if (!box
->sel
) box
->sel
= box
->top
;
539 if (!box
->top
) box
->top
= box
->sel
;
542 box
->ops
= browser
->ops
;
543 box
->items
= &browser
->root
.child
;
545 add_to_list(browser
->boxes
, box
);
546 return EVENT_PROCESSED
;
549 static widget_handler_status_T
550 mouse_listbox(struct dialog_data
*dlg_data
, struct widget_data
*widget_data
)
553 struct listbox_data
*box
= get_listbox_widget_data(widget_data
);
554 struct term_event
*ev
= dlg_data
->term_event
;
556 if (!list_empty(*box
->items
)) {
557 if (!box
->top
) box
->top
= box
->items
->next
;
558 if (!box
->sel
) box
->sel
= box
->top
;
561 if (check_mouse_action(ev
, B_DOWN
)) {
562 struct widget_data
*dlg_item
= dlg_data
->widgets_data
;
564 switch (get_mouse_button(ev
)) {
566 listbox_sel_move(dlg_item
, 1);
567 display_widget(dlg_data
, dlg_item
);
568 return EVENT_PROCESSED
;
571 listbox_sel_move(dlg_item
, -1);
572 display_widget(dlg_data
, dlg_item
);
573 return EVENT_PROCESSED
;
577 if (check_mouse_wheel(ev
))
578 return EVENT_NOT_PROCESSED
;
580 if (check_mouse_position(ev
, &widget_data
->box
)) {
581 /* Clicked in the box. */
582 int offset
= ev
->info
.mouse
.y
- widget_data
->box
.y
;
584 box
->sel_offset
= offset
;
585 box
->sel
= offset
? traverse_listbox_items_list(box
->top
, box
,
590 if (box
->sel
&& box
->sel
->type
== BI_FOLDER
) {
591 int xdepth
= widget_data
->box
.x
+ box
->sel
->depth
* 5;
592 int x
= ev
->info
.mouse
.x
;
594 if (x
>= xdepth
&& x
<= xdepth
+ 2)
595 box
->sel
->expanded
= !box
->sel
->expanded
;
598 display_widget(dlg_data
, widget_data
);
600 return EVENT_PROCESSED
;
603 #endif /* CONFIG_MOUSE */
605 return EVENT_NOT_PROCESSED
;
608 static widget_handler_status_T
609 kbd_listbox(struct dialog_data
*dlg_data
, struct widget_data
*widget_data
)
611 struct widget_data
*dlg_item
= dlg_data
->widgets_data
;
612 struct term_event
*ev
= dlg_data
->term_event
;
614 /* Not a pure listbox, but you're not supposed to use this outside of
615 * the listbox browser anyway, so what.. */
618 enum menu_action action_id
;
621 action_id
= kbd_action(KEYMAP_MENU
, ev
, NULL
);
624 if (action_id
== ACT_MENU_DOWN
) {
625 listbox_sel_move(dlg_item
, 1);
626 display_widget(dlg_data
, dlg_item
);
628 return EVENT_PROCESSED
;
631 if (action_id
== ACT_MENU_UP
) {
632 listbox_sel_move(dlg_item
, -1);
633 display_widget(dlg_data
, dlg_item
);
635 return EVENT_PROCESSED
;
638 if (action_id
== ACT_MENU_PAGE_DOWN
) {
639 struct listbox_data
*box
;
641 box
= get_listbox_widget_data(dlg_item
);
643 listbox_sel_move(dlg_item
,
644 2 * dlg_item
->box
.height
645 - box
->sel_offset
- 1);
647 display_widget(dlg_data
, dlg_item
);
649 return EVENT_PROCESSED
;
652 if (action_id
== ACT_MENU_PAGE_UP
) {
653 struct listbox_data
*box
;
655 box
= get_listbox_widget_data(dlg_item
);
657 listbox_sel_move(dlg_item
,
658 -dlg_item
->box
.height
661 display_widget(dlg_data
, dlg_item
);
663 return EVENT_PROCESSED
;
666 if (action_id
== ACT_MENU_HOME
) {
667 listbox_sel_move(dlg_item
, -INT_MAX
);
668 display_widget(dlg_data
, dlg_item
);
670 return EVENT_PROCESSED
;
673 if (action_id
== ACT_MENU_END
) {
674 listbox_sel_move(dlg_item
, INT_MAX
);
675 display_widget(dlg_data
, dlg_item
);
677 return EVENT_PROCESSED
;
680 if (action_id
== ACT_MENU_MARK_ITEM
) {
681 struct listbox_data
*box
;
683 box
= get_listbox_widget_data(dlg_item
);
685 box
->sel
->marked
= !box
->sel
->marked
;
686 listbox_sel_move(dlg_item
, 1);
688 display_widget(dlg_data
, dlg_item
);
690 return EVENT_PROCESSED
;
693 if (action_id
== ACT_MENU_DELETE
) {
694 struct listbox_data
*box
;
696 box
= get_listbox_widget_data(dlg_item
);
699 && box
->ops
->can_delete
)
700 push_hierbox_delete_button(dlg_data
,
703 return EVENT_PROCESSED
;
706 /* Selecting a button; most probably ;). */
717 return EVENT_NOT_PROCESSED
;
720 struct widget_ops listbox_ops
= {