4 * Copyright (c) Tuomo Valkonen 1999-2009.
6 * See the included file LICENSE for details.
12 #include <libtu/minmax.h>
13 #include <libtu/objp.h>
14 #include <libtu/obj.h>
15 #include <libmainloop/defer.h>
16 #include <libmainloop/signal.h>
18 #include <ioncore/common.h>
19 #include <ioncore/window.h>
20 #include <ioncore/global.h>
21 #include <ioncore/regbind.h>
22 #include <ioncore/strings.h>
23 #include <ioncore/pointer.h>
24 #include <ioncore/focus.h>
25 #include <ioncore/event.h>
26 #include <ioncore/xwindow.h>
27 #include <ioncore/names.h>
28 #include <ioncore/gr.h>
29 #include <ioncore/gr-util.h>
30 #include <ioncore/sizehint.h>
31 #include <ioncore/resize.h>
36 #define MENU_WIN(MENU) ((MENU)->win.win)
42 static bool extl_table_getis(ExtlTab tab
, int i
, const char *s
, char c
,
48 if(!extl_table_geti_t(tab
, i
, &sub
))
50 ret
=extl_table_get(sub
, 's', c
, s
, p
);
51 extl_unref_table(sub
);
59 /*{{{ Drawing routines */
62 static void get_outer_geom(WMenu
*menu
, WRectangle
*geom
)
66 geom
->w
=REGION_GEOM(menu
).w
;
67 geom
->h
=REGION_GEOM(menu
).h
;
71 static void get_inner_geom(WMenu
*menu
, WRectangle
*geom
)
75 get_outer_geom(menu
, geom
);
77 if(menu
->brush
!=NULL
){
78 grbrush_get_border_widths(menu
->brush
, &bdw
);
81 geom
->w
-=bdw
.left
+bdw
.right
;
82 geom
->h
-=bdw
.top
+bdw
.bottom
;
83 geom
->w
=MAXOF(0, geom
->w
);
84 geom
->h
=MAXOF(0, geom
->h
);
92 GR_DEFATTR(unselected
);
97 static void init_attr()
100 GR_ALLOCATTR(active
);
101 GR_ALLOCATTR(inactive
);
102 GR_ALLOCATTR(selected
);
103 GR_ALLOCATTR(unselected
);
104 GR_ALLOCATTR(normal
);
105 GR_ALLOCATTR(submenu
);
110 static void menu_draw_entry(WMenu
*menu
, int i
, const WRectangle
*igeom
,
116 aa
=(REGION_IS_ACTIVE(menu
) ? GR_ATTR(active
) : GR_ATTR(inactive
));
117 sa
=(menu
->selected_entry
==i
? GR_ATTR(selected
) : GR_ATTR(unselected
));
119 if(menu
->entry_brush
==NULL
)
123 geom
.h
=menu
->entry_h
;
124 geom
.y
+=(i
-menu
->first_entry
)*(menu
->entry_h
+menu
->entry_spacing
);
126 grbrush_begin(menu
->entry_brush
, &geom
, GRBRUSH_AMEND
|GRBRUSH_KEEP_ATTR
);
128 grbrush_init_attr(menu
->entry_brush
, &menu
->entries
[i
].attr
);
130 grbrush_set_attr(menu
->entry_brush
, aa
);
131 grbrush_set_attr(menu
->entry_brush
, sa
);
133 grbrush_draw_textbox(menu
->entry_brush
, &geom
, menu
->entries
[i
].title
,
136 grbrush_end(menu
->entry_brush
);
140 void menu_draw_entries(WMenu
*menu
, bool complete
)
145 if(menu
->entry_brush
==NULL
)
148 get_inner_geom(menu
, &igeom
);
150 mx
=menu
->first_entry
+menu
->vis_entries
;
151 mx
=(mx
< menu
->n_entries
? mx
: menu
->n_entries
);
153 for(i
=menu
->first_entry
; i
<mx
; i
++)
154 menu_draw_entry(menu
, i
, &igeom
, complete
);
158 void menu_draw(WMenu
*menu
, bool complete
)
160 GrAttr aa
=(REGION_IS_ACTIVE(menu
) ? GR_ATTR(active
) : GR_ATTR(inactive
));
163 if(menu
->brush
==NULL
)
166 get_outer_geom(menu
, &geom
);
168 grbrush_begin(menu
->brush
, &geom
,
169 (complete
? 0 : GRBRUSH_NO_CLEAR_OK
));
171 grbrush_set_attr(menu
->brush
, aa
);
173 grbrush_draw_border(menu
->brush
, &geom
);
175 menu_draw_entries(menu
, FALSE
);
177 grbrush_end(menu
->brush
);
187 static void menu_calc_size(WMenu
*menu
, bool maxexact
,
189 int *w_ret
, int *h_ret
)
191 GrBorderWidths bdw
, e_bdw
;
194 int bdh
, maxew
=menu
->max_entry_w
;
196 grbrush_get_border_widths(menu
->brush
, &bdw
);
197 grbrush_get_border_widths(menu
->entry_brush
, &e_bdw
);
199 if(maxexact
|| maxew
>maxw
-(int)bdw
.left
-(int)bdw
.right
){
200 maxew
=maxw
-bdw
.left
-bdw
.right
;
203 *w_ret
=maxew
+bdw
.left
+bdw
.right
;
206 bdh
=bdw
.top
+bdw
.bottom
;
208 if(menu
->n_entries
==0){
209 *h_ret
=(maxexact
? maxh
: bdh
);
213 int vis
=(maxh
-bdh
+e_bdw
.spacing
)/(e_bdw
.spacing
+menu
->entry_h
);
214 if(vis
>menu
->n_entries
){
217 }else if(menu
->selected_entry
>=0){
218 if(menu
->selected_entry
<menu
->first_entry
)
219 menu
->first_entry
=menu
->selected_entry
;
220 else if(menu
->selected_entry
>=menu
->first_entry
+vis
)
221 menu
->first_entry
=menu
->selected_entry
-vis
+1;
225 menu
->vis_entries
=vis
;
229 *h_ret
=vis
*menu
->entry_h
+(vis
-1)*e_bdw
.spacing
+bdh
;
232 /* Calculate new shortened entry names */
233 maxew
-=e_bdw
.left
+e_bdw
.right
;
235 for(i
=0; i
<menu
->n_entries
; i
++){
236 if(menu
->entries
[i
].title
){
237 free(menu
->entries
[i
].title
);
238 menu
->entries
[i
].title
=NULL
;
243 if(extl_table_getis(menu
->tab
, i
+1, "name", 's', &str
)){
244 menu
->entries
[i
].title
=grbrush_make_label(menu
->entry_brush
,
252 void calc_size(WMenu
*menu
, int *w
, int *h
)
254 if(menu
->pmenu_mode
){
255 menu_calc_size(menu
, FALSE
, INT_MAX
, INT_MAX
, w
, h
);
257 menu_calc_size(menu
, !(menu
->last_fp
.mode
®ION_FIT_BOUNDS
),
258 menu
->last_fp
.g
.w
, menu
->last_fp
.g
.h
, w
, h
);
263 /* Return offset from bottom-left corner of containing mplex or top-right
264 * corner of parent menu for the respective corner of menu.
266 static void get_placement_offs(WMenu
*menu
, int *xoff
, int *yoff
)
273 if(menu
->brush
!=NULL
){
274 grbrush_get_border_widths(menu
->brush
, &bdw
);
279 if(menu
->entry_brush
!=NULL
){
280 grbrush_get_border_widths(menu
->entry_brush
, &bdw
);
287 #define MINIMUM_Y_VISIBILITY 20
288 #define POINTER_OFFSET 5
290 static void menu_firstfit(WMenu
*menu
, bool submenu
, const WRectangle
*refg
)
294 calc_size(menu
, &(geom
.w
), &(geom
.h
));
296 if(!(menu
->last_fp
.mode
®ION_FIT_BOUNDS
)){
297 geom
.x
=menu
->last_fp
.g
.x
;
298 geom
.y
=menu
->last_fp
.g
.y
;
299 }else if(menu
->pmenu_mode
){
304 const WRectangle
*maxg
=
305 ®ION_GEOM(REGION_PARENT((WRegion
*)menu
));
308 geom
.y
+=POINTER_OFFSET
;
310 if(geom
.y
+MINIMUM_Y_VISIBILITY
>maxg
->y
+maxg
->h
){
311 geom
.y
=maxg
->y
+maxg
->h
-MINIMUM_Y_VISIBILITY
;
312 geom
.x
=refg
->x
+POINTER_OFFSET
;
313 if(geom
.x
+geom
.w
>maxg
->x
+maxg
->w
)
314 geom
.x
=refg
->x
-geom
.w
-POINTER_OFFSET
;
318 else if(geom
.x
+geom
.w
>maxg
->x
+maxg
->w
)
319 geom
.x
=maxg
->x
+maxg
->w
-geom
.w
;
323 const WRectangle
*maxg
=&(menu
->last_fp
.g
);
325 int l
, r
, t
, b
, xoff
, yoff
;
327 get_placement_offs(menu
, &xoff
, &yoff
);
329 r
=refg
->x
+refg
->w
+xoff
;
331 b
=refg
->y
+refg
->h
-yoff
;
333 geom
.x
=MAXOF(l
, r
-geom
.w
);
334 if(geom
.x
+geom
.w
>maxg
->x
+maxg
->w
)
337 geom
.y
=MINOF(b
-geom
.h
, t
);
342 geom
.y
=maxg
->y
+maxg
->h
-geom
.h
;
346 window_do_fitrep(&menu
->win
, NULL
, &geom
);
350 static void menu_do_refit(WMenu
*menu
, WWindow
*par
, const WFitParams
*oldfp
)
354 calc_size(menu
, &(geom
.w
), &(geom
.h
));
356 if(!(menu
->last_fp
.mode
®ION_FIT_BOUNDS
)){
357 geom
.x
=menu
->last_fp
.g
.x
;
358 geom
.y
=menu
->last_fp
.g
.y
;
359 }else if(menu
->pmenu_mode
){
360 geom
.x
=REGION_GEOM(menu
).x
;
361 geom
.y
=REGION_GEOM(menu
).y
;
363 const WRectangle
*maxg
=&(menu
->last_fp
.g
);
364 int xdiff
=REGION_GEOM(menu
).x
-oldfp
->g
.x
;
365 int ydiff
=(REGION_GEOM(menu
).y
+REGION_GEOM(menu
).h
366 -(oldfp
->g
.y
+oldfp
->g
.h
));
367 geom
.x
=MAXOF(0, MINOF(maxg
->x
+xdiff
, maxg
->x
+maxg
->w
-geom
.w
));
368 geom
.y
=MAXOF(0, MINOF(maxg
->y
+maxg
->h
+ydiff
, maxg
->y
+maxg
->h
)-geom
.h
);
371 window_do_fitrep(&menu
->win
, par
, &geom
);
375 bool menu_fitrep(WMenu
*menu
, WWindow
*par
, const WFitParams
*fp
)
379 if(par
!=NULL
&& !region_same_rootwin((WRegion
*)par
, (WRegion
*)menu
))
384 menu_do_refit(menu
, par
, &oldfp
);
386 if(menu
->submenu
!=NULL
&& !menu
->pmenu_mode
)
387 region_fitrep((WRegion
*)(menu
->submenu
), par
, fp
);
393 void menu_size_hints(WMenu
*menu
, WSizeHints
*hints_ret
)
395 int n
=menu
->n_entries
;
396 int w
=menu
->max_entry_w
;
397 int h
=menu
->entry_h
*n
+ menu
->entry_spacing
*MAXOF(0, n
-1);
399 if(menu
->brush
!=NULL
){
401 grbrush_get_border_widths(menu
->brush
, &bdw
);
403 w
+=bdw
.left
+bdw
.right
;
404 h
+=bdw
.top
+bdw
.bottom
;
407 hints_ret
->min_set
=TRUE
;
408 hints_ret
->min_width
=w
;
409 hints_ret
->min_height
=h
;
416 /*{{{ Brush update */
419 static void calc_entry_dimens(WMenu
*menu
)
421 int i
, n
=extl_table_get_n(menu
->tab
);
428 if(extl_table_gets_s(menu
->tab
, title
, &str
)){
429 maxw
=grbrush_get_text_width(title_brush
, str
, strlen(str
));
435 if(extl_table_getis(menu
->tab
, i
, "name", 's', &str
)){
436 int w
=grbrush_get_text_width(menu
->entry_brush
,
444 grbrush_get_border_widths(menu
->entry_brush
, &bdw
);
445 grbrush_get_font_extents(menu
->entry_brush
, &fnte
);
447 menu
->max_entry_w
=maxw
+bdw
.left
+bdw
.right
;
448 menu
->entry_h
=fnte
.max_height
+bdw
.top
+bdw
.bottom
;
449 menu
->entry_spacing
=bdw
.spacing
;
453 static bool menu_init_gr(WMenu
*menu
, WRootWin
*rootwin
, Window win
)
455 GrBrush
*brush
, *entry_brush
;
456 const char *style
=(menu
->big_mode
460 : "input-menu-normal"));
462 const char *entry_style
=(menu
->big_mode
463 ? "tab-menuentry-big"
465 ? "tab-menuentry-pmenu"
466 : "tab-menuentry-normal"));
468 brush
=gr_get_brush(win
, rootwin
, style
);
473 entry_brush
=grbrush_get_slave(brush
, rootwin
, entry_style
);
475 if(entry_brush
==NULL
){
476 grbrush_release(brush
);
480 if(menu
->entry_brush
!=NULL
)
481 grbrush_release(menu
->entry_brush
);
482 if(menu
->brush
!=NULL
)
483 grbrush_release(menu
->brush
);
486 menu
->entry_brush
=entry_brush
;
488 calc_entry_dimens(menu
);
494 void menu_updategr(WMenu
*menu
)
496 if(!menu_init_gr(menu
, region_rootwin_of((WRegion
*)menu
),
501 menu_do_refit(menu
, NULL
, &(menu
->last_fp
));
503 region_updategr_default((WRegion
*)menu
);
505 window_draw((WWindow
*)menu
, TRUE
);
509 static void menu_release_gr(WMenu
*menu
)
511 if(menu
->entry_brush
!=NULL
){
512 grbrush_release(menu
->entry_brush
);
513 menu
->entry_brush
=NULL
;
515 if(menu
->brush
!=NULL
){
516 grbrush_release(menu
->brush
);
528 static WMenuEntry
*preprocess_menu(ExtlTab tab
, int *n_entries
)
534 n
=extl_table_get_n(tab
);
540 entries
=ALLOC_N(WMenuEntry
, n
);
547 /* Initialise entries and check submenus */
549 WMenuEntry
*ent
=&entries
[i
-1];
554 gr_stylespec_init(&ent
->attr
);
556 if(extl_table_geti_t(tab
, i
, &entry
)){
561 if(extl_table_gets_s(entry
, "attr", &attr
)){
562 gr_stylespec_load_(&ent
->attr
, attr
, TRUE
);
566 if(extl_table_gets_f(entry
, "submenu_fn", &fn
)){
567 ent
->flags
|=WMENUENTRY_SUBMENU
;
569 }else if(extl_table_gets_t(entry
, "submenu", &sub
)){
570 ent
->flags
|=WMENUENTRY_SUBMENU
;
571 extl_unref_table(sub
);
574 if(ent
->flags
&WMENUENTRY_SUBMENU
)
575 gr_stylespec_set(&ent
->attr
, GR_ATTR(submenu
));
577 extl_unref_table(entry
);
585 static void deinit_entries(WMenu
*menu
);
588 bool menu_init(WMenu
*menu
, WWindow
*par
, const WFitParams
*fp
,
589 const WMenuCreateParams
*params
)
593 menu
->entries
=preprocess_menu(params
->tab
, &(menu
->n_entries
));
595 if(menu
->entries
==NULL
){
596 warn(TR("Empty menu."));
600 menu
->tab
=extl_ref_table(params
->tab
);
601 menu
->handler
=extl_ref_fn(params
->handler
);
602 menu
->pmenu_mode
=params
->pmenu_mode
;
603 menu
->big_mode
=params
->big_mode
;
604 /*menu->cycle_bindmap=NULL;*/
608 if(params
->pmenu_mode
){
609 menu
->selected_entry
=-1;
611 menu
->selected_entry
=params
->initial
-1;
612 if(menu
->selected_entry
<0)
613 menu
->selected_entry
=0;
614 if(params
->initial
> menu
->n_entries
)
615 menu
->selected_entry
=0;
621 menu
->entry_brush
=NULL
;
622 menu
->entry_spacing
=0;
623 menu
->vis_entries
=menu
->n_entries
;
626 menu
->typeahead
=NULL
;
631 if(!window_init((WWindow
*)menu
, par
, fp
, "WMenu"))
636 if(!menu_init_gr(menu
, region_rootwin_of((WRegion
*)par
), win
))
641 menu_firstfit(menu
, params
->submenu_mode
, &(params
->refg
));
643 window_select_input(&(menu
->win
), IONCORE_EVENTMASK_NORMAL
);
645 region_add_bindmap((WRegion
*)menu
, mod_menu_menu_bindmap
);
647 region_register((WRegion
*)menu
);
652 window_deinit((WWindow
*)menu
);
654 extl_unref_table(menu
->tab
);
655 extl_unref_fn(menu
->handler
);
656 deinit_entries(menu
);
661 WMenu
*create_menu(WWindow
*par
, const WFitParams
*fp
,
662 const WMenuCreateParams
*params
)
664 CREATEOBJ_IMPL(WMenu
, menu
, (p
, par
, fp
, params
));
668 static void deinit_entries(WMenu
*menu
)
672 for(i
=0; i
<menu
->n_entries
; i
++){
673 gr_stylespec_unalloc(&menu
->entries
[i
].attr
);
674 if(menu
->entries
[i
].title
!=NULL
)
675 free(menu
->entries
[i
].title
);
682 void menu_deinit(WMenu
*menu
)
684 menu_typeahead_clear(menu
);
686 if(menu
->submenu
!=NULL
)
687 destroy_obj((Obj
*)menu
->submenu
);
689 /*if(menu->cycle_bindmap!=NULL)
690 bindmap_destroy(menu->cycle_bindmap);*/
692 extl_unref_table(menu
->tab
);
693 extl_unref_fn(menu
->handler
);
695 deinit_entries(menu
);
697 menu_release_gr(menu
);
699 window_deinit((WWindow
*)menu
);
709 static void menu_inactivated(WMenu
*menu
)
711 window_draw((WWindow
*)menu
, FALSE
);
715 static void menu_activated(WMenu
*menu
)
717 window_draw((WWindow
*)menu
, FALSE
);
727 static WMenu
*menu_head(WMenu
*menu
)
729 WMenu
*m
=REGION_MANAGER_CHK(menu
, WMenu
);
730 return (m
==NULL
? menu
: menu_head(m
));
734 static WMenu
*menu_tail(WMenu
*menu
)
736 return (menu
->submenu
==NULL
? menu
: menu_tail(menu
->submenu
));
740 static void menu_managed_remove(WMenu
*menu
, WRegion
*sub
)
742 bool mcf
=region_may_control_focus((WRegion
*)menu
);
744 if(sub
!=(WRegion
*)menu
->submenu
)
749 region_unset_manager(sub
, (WRegion
*)menu
);
752 region_do_set_focus((WRegion
*)menu
, FALSE
);
756 int get_sub_y_off(WMenu
*menu
, int n
)
758 /* top + sum of above entries and spacings - top_of_sub */
759 return (menu
->entry_h
+menu
->entry_spacing
)*(n
-menu
->first_entry
);
763 static void show_sub(WMenu
*menu
, int n
)
766 WMenuCreateParams fnp
;
770 par
=REGION_PARENT(menu
);
777 fnp
.pmenu_mode
=menu
->pmenu_mode
;
778 fnp
.big_mode
=menu
->big_mode
;
779 fnp
.submenu_mode
=TRUE
;
781 if(menu
->pmenu_mode
){
782 fnp
.refg
.x
=REGION_GEOM(menu
).x
+REGION_GEOM(menu
).w
;
783 fnp
.refg
.y
=REGION_GEOM(menu
).y
+get_sub_y_off(menu
, n
);
787 fnp
.refg
=REGION_GEOM(menu
);
790 fnp
.tab
=extl_table_none();
793 if(extl_table_getis(menu
->tab
, n
+1, "submenu_fn", 'f', &fn
)){
795 extl_call(fn
, NULL
, "t", &(fnp
.tab
));
796 extl_unprotect(NULL
);
799 extl_table_getis(menu
->tab
, n
+1, "submenu", 't', &(fnp
.tab
));
801 if(fnp
.tab
==extl_table_none())
805 fnp
.handler
=extl_ref_fn(menu
->handler
);
810 if(extl_table_getis(menu
->tab
, n
+1, "initial", 'f', &fn
)){
812 extl_call(fn
, NULL
, "i", &(fnp
.initial
));
813 extl_unprotect(NULL
);
816 extl_table_getis(menu
->tab
, n
+1, "initial", 'i', &(fnp
.initial
));
820 submenu
=create_menu(par
, &fp
, &fnp
);
825 menu
->submenu
=submenu
;
826 region_set_manager((WRegion
*)submenu
, (WRegion
*)menu
);
828 region_restack((WRegion
*)submenu
, MENU_WIN(menu
), Above
);
829 region_map((WRegion
*)submenu
);
831 if(!menu
->pmenu_mode
&& region_may_control_focus((WRegion
*)menu
))
832 region_do_set_focus((WRegion
*)submenu
, FALSE
);
836 static void menu_do_set_focus(WMenu
*menu
, bool warp
)
838 if(menu
->submenu
!=NULL
)
839 region_do_set_focus((WRegion
*)menu
->submenu
, warp
);
841 window_do_set_focus((WWindow
*)menu
, warp
);
845 void menu_restack(WMenu
*menu
, Window other
, int mode
)
847 xwindow_restack(MENU_WIN(menu
), other
, mode
);
848 if(menu
->submenu
!=NULL
)
849 region_restack((WRegion
*)(menu
->submenu
), MENU_WIN(menu
), Above
);
853 void menu_stacking(WMenu
*menu
, Window
*bottomret
, Window
*topret
)
857 if(menu
->submenu
!=NULL
)
858 region_stacking((WRegion
*)(menu
->submenu
), bottomret
, topret
);
860 *bottomret
=MENU_WIN(menu
);
862 *topret
=MENU_WIN(menu
);
873 static void menu_do_select_nth(WMenu
*menu
, int n
)
875 int oldn
=menu
->selected_entry
;
881 if(menu
->submenu
!=NULL
)
882 destroy_obj((Obj
*)menu
->submenu
);
884 assert(menu
->submenu
==NULL
);
886 menu
->selected_entry
=n
;
889 if(n
<menu
->first_entry
){
892 }else if(n
>=menu
->first_entry
+menu
->vis_entries
){
893 menu
->first_entry
=n
-menu
->vis_entries
+1;
897 if(menu
->entries
[n
].flags
&WMENUENTRY_SUBMENU
&&
904 menu_draw_entries(menu
, TRUE
);
906 /* redraw new and old selected entry */
908 get_inner_geom(menu
, &igeom
);
912 menu_draw_entry(menu
, oldn
, &igeom
, TRUE
);
914 menu_draw_entry(menu
, n
, &igeom
, TRUE
);
920 * Select \var{n}:th entry in menu.
923 void menu_select_nth(WMenu
*menu
, int n
)
927 if(n
>=menu
->n_entries
)
930 menu_typeahead_clear(menu
);
931 menu_do_select_nth(menu
, n
);
936 * Select previous entry in menu.
939 void menu_select_prev(WMenu
*menu
)
941 menu_select_nth(menu
, (menu
->selected_entry
<=0
943 : menu
->selected_entry
-1));
948 * Select next entry in menu.
951 void menu_select_next(WMenu
*menu
)
953 menu_select_nth(menu
, (menu
->selected_entry
+1)%menu
->n_entries
);
957 static void menu_do_finish(WMenu
*menu
)
962 WMenu
*head
=menu_head(menu
);
964 handler
=menu
->handler
;
965 menu
->handler
=extl_fn_none();
967 ok
=extl_table_geti_t(menu
->tab
, menu
->selected_entry
+1, &tab
);
969 if(!region_rqdispose((WRegion
*)head
)){
970 if(head
->submenu
!=NULL
)
971 destroy_obj((Obj
*)head
->submenu
);
975 extl_call(handler
, "t", NULL
, tab
);
977 extl_unref_fn(handler
);
978 extl_unref_table(tab
);
983 * If selected entry is a submenu, display that.
984 * Otherwise destroy the menu and call handler for selected entry.
987 void menu_finish(WMenu
*menu
)
989 menu_typeahead_clear(menu
);
991 if(!menu
->pmenu_mode
&& menu
->selected_entry
>=0 &&
992 menu
->entries
[menu
->selected_entry
].flags
&WMENUENTRY_SUBMENU
){
993 show_sub(menu
, menu
->selected_entry
);
997 mainloop_defer_action((Obj
*)menu
, (WDeferredAction
*)menu_do_finish
);
1003 * Close \var{menu} not calling any possible finish handlers.
1006 void menu_cancel(WMenu
*menu
)
1008 region_defer_rqdispose((WRegion
*)menu
);
1017 static int scroll_time
=20;
1018 static int scroll_amount
=3;
1019 static WTimer
*scroll_timer
=NULL
;
1022 static void reset_scroll_timer()
1024 if(scroll_timer
!=NULL
){
1025 destroy_obj((Obj
*)scroll_timer
);
1032 * Set module basic settings. The parameter table may contain the
1035 * \begin{tabularx}{\linewidth}{lX}
1036 * \tabhead{Field & Description}
1037 * \var{scroll_amount} & Number of pixels to scroll at a time in
1038 * pointer-controlled menus when one extends
1039 * beyond a border of the screen and the pointer
1040 * touches that border. \\
1041 * \var{scroll_delay} & Time between such scrolling events in
1046 void mod_menu_set(ExtlTab tab
)
1050 if(extl_table_gets_i(tab
, "scroll_amount", &a
))
1051 scroll_amount
=MAXOF(0, a
);
1052 if(extl_table_gets_i(tab
, "scroll_delay", &t
))
1053 scroll_time
=MAXOF(0, t
);
1058 * Get module basic settings. For details, see \fnref{mod_menu.set}.
1062 ExtlTab
mod_menu_get()
1064 ExtlTab tab
=extl_create_table();
1065 extl_table_sets_i(tab
, "scroll_amount", scroll_amount
);
1066 extl_table_sets_i(tab
, "scroll_delay", scroll_time
);
1079 static int calc_diff(const WRectangle
*mg
, const WRectangle
*pg
, int d
)
1083 return mg
->x
+mg
->w
-pg
->w
;
1085 return mg
->y
+mg
->h
-pg
->h
;
1095 static int scrolld_subs(WMenu
*menu
, int d
)
1098 WRegion
*p
=REGION_PARENT_REG(menu
);
1099 const WRectangle
*pg
;
1107 int new_diff
= calc_diff(®ION_GEOM(menu
), pg
, d
);
1108 diff
=MAXOF(diff
, new_diff
);
1112 return MINOF(MAXOF(0, diff
), scroll_amount
);
1116 static void menu_select_entry_at(WMenu
*menu
, int px
, int py
);
1119 static void do_scroll(WMenu
*menu
, int xd
, int yd
)
1124 xwindow_pointer_pos(region_root_of((WRegion
*)menu
), &px
, &py
);
1127 g
=REGION_GEOM(menu
);
1131 window_do_fitrep((WWindow
*)menu
, NULL
, &g
);
1133 menu_select_entry_at(menu
, px
, py
);
1140 static void scroll_left(WTimer
*timer
, WMenu
*menu
)
1143 do_scroll(menu
, -scrolld_subs(menu
, D_LEFT
), 0);
1144 if(scrolld_subs(menu
, D_LEFT
)>0){
1145 timer_set(timer
, scroll_time
, (WTimerHandler
*)scroll_left
,
1153 static void scroll_up(WTimer
*timer
, WMenu
*menu
)
1156 do_scroll(menu
, 0, -scrolld_subs(menu
, D_UP
));
1157 if(scrolld_subs(menu
, D_UP
)>0){
1158 timer_set(timer
, scroll_time
, (WTimerHandler
*)scroll_up
,
1167 static void scroll_right(WTimer
*timer
, WMenu
*menu
)
1170 do_scroll(menu
, scrolld_subs(menu
, D_RIGHT
), 0);
1171 if(scrolld_subs(menu
, D_RIGHT
)>0){
1172 timer_set(timer
, scroll_time
, (WTimerHandler
*)scroll_right
,
1180 static void scroll_down(WTimer
*timer
, WMenu
*menu
)
1183 do_scroll(menu
, 0, scrolld_subs(menu
, D_DOWN
));
1184 if(scrolld_subs(menu
, D_DOWN
)>0){
1185 timer_set(timer
, scroll_time
, (WTimerHandler
*)scroll_down
,
1193 static void end_scroll(WMenu
*menu
)
1195 reset_scroll_timer();
1198 #define SCROLL_OFFSET 10
1200 static void check_scroll(WMenu
*menu
, int x
, int y
)
1202 WRegion
*parent
=REGION_PARENT_REG(menu
);
1204 WTimerHandler
*fn
=NULL
;
1206 if(!menu
->pmenu_mode
)
1214 region_rootpos(parent
, &rx
, &ry
);
1218 if(x
<=SCROLL_OFFSET
){
1219 fn
=(WTimerHandler
*)scroll_right
;
1220 }else if(y
<=SCROLL_OFFSET
){
1221 fn
=(WTimerHandler
*)scroll_down
;
1222 }else if(x
>=REGION_GEOM(parent
).w
-SCROLL_OFFSET
){
1223 fn
=(WTimerHandler
*)scroll_left
;
1224 }else if(y
>=REGION_GEOM(parent
).h
-SCROLL_OFFSET
){
1225 fn
=(WTimerHandler
*)scroll_up
;
1233 if(scroll_timer
!=NULL
){
1234 if(scroll_timer
->handler
==(WTimerHandler
*)fn
&&
1235 timer_is_set(scroll_timer
)){
1239 scroll_timer
=create_timer();
1240 if(scroll_timer
==NULL
)
1244 fn(scroll_timer
, (Obj
*)menu_head(menu
));
1251 /*{{{ Pointer handlers */
1254 int menu_entry_at_root(WMenu
*menu
, int root_x
, int root_y
)
1256 int rx
, ry
, x
, y
, entry
;
1258 region_rootpos((WRegion
*)menu
, &rx
, &ry
);
1260 get_inner_geom(menu
, &ig
);
1265 if(x
<0 || x
>=ig
.w
|| y
<0 || y
>=ig
.h
)
1268 entry
=y
/(menu
->entry_h
+menu
->entry_spacing
);
1269 if(entry
<0 || entry
>=menu
->vis_entries
)
1271 return entry
+menu
->first_entry
;
1275 int menu_entry_at_root_tree(WMenu
*menu
, int root_x
, int root_y
,
1280 menu
=menu_tail(menu
);
1284 if(!menu
->pmenu_mode
)
1285 return menu_entry_at_root(menu
, root_x
, root_y
);
1288 entry
=menu_entry_at_root(menu
, root_x
, root_y
);
1293 menu
=REGION_MANAGER_CHK(menu
, WMenu
);
1300 static void menu_select_entry_at(WMenu
*menu
, int px
, int py
)
1302 int entry
=menu_entry_at_root_tree(menu
, px
, py
, &menu
);
1304 menu_do_select_nth(menu
, entry
);
1308 void menu_release(WMenu
*menu
, XButtonEvent
*ev
)
1310 int entry
=menu_entry_at_root_tree(menu
, ev
->x_root
, ev
->y_root
, &menu
);
1313 menu_select_nth(menu
, entry
);
1315 }else if(menu
->pmenu_mode
){
1316 menu_cancel(menu_head(menu
));
1321 void menu_motion(WMenu
*menu
, XMotionEvent
*ev
, int UNUSED(dx
), int UNUSED(dy
))
1323 menu_select_entry_at(menu
, ev
->x_root
, ev
->y_root
);
1324 check_scroll(menu
, ev
->x_root
, ev
->y_root
);
1328 void menu_button(WMenu
*menu
, XButtonEvent
*ev
)
1330 int entry
=menu_entry_at_root_tree(menu
, ev
->x_root
, ev
->y_root
, &menu
);
1332 menu_select_nth(menu
, entry
);
1336 int menu_press(WMenu
*menu
, XButtonEvent
*ev
, WRegion
**UNUSED(reg_ret
))
1338 menu_button(menu
, ev
);
1339 menu
=menu_head(menu
);
1340 ioncore_set_drag_handlers((WRegion
*)menu
,
1342 (WMotionHandler
*)menu_motion
,
1343 (WButtonHandler
*)menu_release
,
1353 /*{{{ Typeahead find */
1356 static void menu_insstr(WMenu
*menu
, const char *buf
, size_t n
)
1358 size_t oldlen
=(menu
->typeahead
==NULL
? 0 : strlen(menu
->typeahead
));
1359 char *newta
=(char*)malloc(oldlen
+n
+1);
1367 memcpy(newta
, menu
->typeahead
, oldlen
);
1369 memcpy(newta
+oldlen
, buf
, n
);
1370 newta
[oldlen
+n
]='\0';
1373 while(*newta
!='\0'){
1375 entry
=menu
->selected_entry
;
1377 if(menu
->entries
[entry
].title
!=NULL
){
1378 if(libtu_strcasestr(menu
->entries
[entry
].title
, newta
)){
1383 entry
=(entry
+1)%menu
->n_entries
;
1384 }while(entry
!=menu
->selected_entry
);
1386 menu_do_select_nth(menu
, entry
);
1392 if(newta_orig
!=newta
){
1397 char *p
=scopy(newta
);
1402 if(menu
->typeahead
!=NULL
)
1403 free(menu
->typeahead
);
1404 menu
->typeahead
=newta
;
1409 * Clear typeahead buffer.
1412 void menu_typeahead_clear(WMenu
*menu
)
1414 if(menu
->typeahead
!=NULL
){
1415 free(menu
->typeahead
);
1416 menu
->typeahead
=NULL
;
1424 /*{{{ Dynamic function table and class implementation */
1427 static DynFunTab menu_dynfuntab
[]={
1428 {(DynFun
*)region_fitrep
, (DynFun
*)menu_fitrep
},
1429 {region_updategr
, menu_updategr
},
1430 {window_draw
, menu_draw
},
1431 {(DynFun
*)window_press
, (DynFun
*)menu_press
},
1432 {region_managed_remove
, menu_managed_remove
},
1433 {region_do_set_focus
, menu_do_set_focus
},
1434 {region_activated
, menu_activated
},
1435 {region_inactivated
, menu_inactivated
},
1436 {window_insstr
, menu_insstr
},
1437 {region_restack
, menu_restack
},
1438 {region_stacking
, menu_stacking
},
1439 {region_size_hints
, menu_size_hints
},
1445 IMPLCLASS(WMenu
, WWindow
, menu_deinit
, menu_dynfuntab
);