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 nath
, 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
;
457 const char *style
=(menu
->big_mode
461 : "input-menu-normal"));
463 const char *entry_style
=(menu
->big_mode
464 ? "tab-menuentry-big"
466 ? "tab-menuentry-pmenu"
467 : "tab-menuentry-normal"));
469 brush
=gr_get_brush(win
, rootwin
, style
);
474 entry_brush
=grbrush_get_slave(brush
, rootwin
, entry_style
);
476 if(entry_brush
==NULL
){
477 grbrush_release(brush
);
481 if(menu
->entry_brush
!=NULL
)
482 grbrush_release(menu
->entry_brush
);
483 if(menu
->brush
!=NULL
)
484 grbrush_release(menu
->brush
);
487 menu
->entry_brush
=entry_brush
;
489 calc_entry_dimens(menu
);
495 void menu_updategr(WMenu
*menu
)
497 if(!menu_init_gr(menu
, region_rootwin_of((WRegion
*)menu
),
502 menu_do_refit(menu
, NULL
, &(menu
->last_fp
));
504 region_updategr_default((WRegion
*)menu
);
506 window_draw((WWindow
*)menu
, TRUE
);
510 static void menu_release_gr(WMenu
*menu
)
512 if(menu
->entry_brush
!=NULL
){
513 grbrush_release(menu
->entry_brush
);
514 menu
->entry_brush
=NULL
;
516 if(menu
->brush
!=NULL
){
517 grbrush_release(menu
->brush
);
529 static WMenuEntry
*preprocess_menu(ExtlTab tab
, int *n_entries
)
535 n
=extl_table_get_n(tab
);
541 entries
=ALLOC_N(WMenuEntry
, n
);
548 /* Initialise entries and check submenus */
550 WMenuEntry
*ent
=&entries
[i
-1];
555 gr_stylespec_init(&ent
->attr
);
557 if(extl_table_geti_t(tab
, i
, &entry
)){
562 if(extl_table_gets_s(entry
, "attr", &attr
)){
563 gr_stylespec_load_(&ent
->attr
, attr
, TRUE
);
567 if(extl_table_gets_f(entry
, "submenu_fn", &fn
)){
568 ent
->flags
|=WMENUENTRY_SUBMENU
;
570 }else if(extl_table_gets_t(entry
, "submenu", &sub
)){
571 ent
->flags
|=WMENUENTRY_SUBMENU
;
572 extl_unref_table(sub
);
575 if(ent
->flags
&WMENUENTRY_SUBMENU
)
576 gr_stylespec_set(&ent
->attr
, GR_ATTR(submenu
));
578 extl_unref_table(entry
);
586 static void deinit_entries(WMenu
*menu
);
589 bool menu_init(WMenu
*menu
, WWindow
*par
, const WFitParams
*fp
,
590 const WMenuCreateParams
*params
)
595 menu
->entries
=preprocess_menu(params
->tab
, &(menu
->n_entries
));
597 if(menu
->entries
==NULL
){
598 warn(TR("Empty menu."));
602 menu
->tab
=extl_ref_table(params
->tab
);
603 menu
->handler
=extl_ref_fn(params
->handler
);
604 menu
->pmenu_mode
=params
->pmenu_mode
;
605 menu
->big_mode
=params
->big_mode
;
606 /*menu->cycle_bindmap=NULL;*/
610 if(params
->pmenu_mode
){
611 menu
->selected_entry
=-1;
613 menu
->selected_entry
=params
->initial
-1;
614 if(menu
->selected_entry
<0)
615 menu
->selected_entry
=0;
616 if(params
->initial
> menu
->n_entries
)
617 menu
->selected_entry
=0;
623 menu
->entry_brush
=NULL
;
624 menu
->entry_spacing
=0;
625 menu
->vis_entries
=menu
->n_entries
;
628 menu
->typeahead
=NULL
;
633 if(!window_init((WWindow
*)menu
, par
, fp
, "WMenu"))
638 if(!menu_init_gr(menu
, region_rootwin_of((WRegion
*)par
), win
))
643 menu_firstfit(menu
, params
->submenu_mode
, &(params
->refg
));
645 window_select_input(&(menu
->win
), IONCORE_EVENTMASK_NORMAL
);
647 region_add_bindmap((WRegion
*)menu
, mod_menu_menu_bindmap
);
649 region_register((WRegion
*)menu
);
654 window_deinit((WWindow
*)menu
);
656 extl_unref_table(menu
->tab
);
657 extl_unref_fn(menu
->handler
);
658 deinit_entries(menu
);
663 WMenu
*create_menu(WWindow
*par
, const WFitParams
*fp
,
664 const WMenuCreateParams
*params
)
666 CREATEOBJ_IMPL(WMenu
, menu
, (p
, par
, fp
, params
));
670 static void deinit_entries(WMenu
*menu
)
674 for(i
=0; i
<menu
->n_entries
; i
++){
675 gr_stylespec_unalloc(&menu
->entries
[i
].attr
);
676 if(menu
->entries
[i
].title
!=NULL
)
677 free(menu
->entries
[i
].title
);
684 void menu_deinit(WMenu
*menu
)
686 menu_typeahead_clear(menu
);
688 if(menu
->submenu
!=NULL
)
689 destroy_obj((Obj
*)menu
->submenu
);
691 /*if(menu->cycle_bindmap!=NULL)
692 bindmap_destroy(menu->cycle_bindmap);*/
694 extl_unref_table(menu
->tab
);
695 extl_unref_fn(menu
->handler
);
697 deinit_entries(menu
);
699 menu_release_gr(menu
);
701 window_deinit((WWindow
*)menu
);
711 static void menu_inactivated(WMenu
*menu
)
713 window_draw((WWindow
*)menu
, FALSE
);
717 static void menu_activated(WMenu
*menu
)
719 window_draw((WWindow
*)menu
, FALSE
);
729 static WMenu
*menu_head(WMenu
*menu
)
731 WMenu
*m
=REGION_MANAGER_CHK(menu
, WMenu
);
732 return (m
==NULL
? menu
: menu_head(m
));
736 static WMenu
*menu_tail(WMenu
*menu
)
738 return (menu
->submenu
==NULL
? menu
: menu_tail(menu
->submenu
));
742 static void menu_managed_remove(WMenu
*menu
, WRegion
*sub
)
744 bool mcf
=region_may_control_focus((WRegion
*)menu
);
746 if(sub
!=(WRegion
*)menu
->submenu
)
751 region_unset_manager(sub
, (WRegion
*)menu
);
754 region_do_set_focus((WRegion
*)menu
, FALSE
);
758 int get_sub_y_off(WMenu
*menu
, int n
)
760 /* top + sum of above entries and spacings - top_of_sub */
761 return (menu
->entry_h
+menu
->entry_spacing
)*(n
-menu
->first_entry
);
765 static void show_sub(WMenu
*menu
, int n
)
768 WMenuCreateParams fnp
;
772 par
=REGION_PARENT(menu
);
779 fnp
.pmenu_mode
=menu
->pmenu_mode
;
780 fnp
.big_mode
=menu
->big_mode
;
781 fnp
.submenu_mode
=TRUE
;
783 if(menu
->pmenu_mode
){
784 fnp
.refg
.x
=REGION_GEOM(menu
).x
+REGION_GEOM(menu
).w
;
785 fnp
.refg
.y
=REGION_GEOM(menu
).y
+get_sub_y_off(menu
, n
);
789 fnp
.refg
=REGION_GEOM(menu
);
792 fnp
.tab
=extl_table_none();
795 if(extl_table_getis(menu
->tab
, n
+1, "submenu_fn", 'f', &fn
)){
797 extl_call(fn
, NULL
, "t", &(fnp
.tab
));
798 extl_unprotect(NULL
);
801 extl_table_getis(menu
->tab
, n
+1, "submenu", 't', &(fnp
.tab
));
803 if(fnp
.tab
==extl_table_none())
807 fnp
.handler
=extl_ref_fn(menu
->handler
);
812 if(extl_table_getis(menu
->tab
, n
+1, "initial", 'f', &fn
)){
814 extl_call(fn
, NULL
, "i", &(fnp
.initial
));
815 extl_unprotect(NULL
);
818 extl_table_getis(menu
->tab
, n
+1, "initial", 'i', &(fnp
.initial
));
822 submenu
=create_menu(par
, &fp
, &fnp
);
827 menu
->submenu
=submenu
;
828 region_set_manager((WRegion
*)submenu
, (WRegion
*)menu
);
830 region_restack((WRegion
*)submenu
, MENU_WIN(menu
), Above
);
831 region_map((WRegion
*)submenu
);
833 if(!menu
->pmenu_mode
&& region_may_control_focus((WRegion
*)menu
))
834 region_do_set_focus((WRegion
*)submenu
, FALSE
);
838 static void menu_do_set_focus(WMenu
*menu
, bool warp
)
840 if(menu
->submenu
!=NULL
)
841 region_do_set_focus((WRegion
*)menu
->submenu
, warp
);
843 window_do_set_focus((WWindow
*)menu
, warp
);
847 void menu_restack(WMenu
*menu
, Window other
, int mode
)
849 xwindow_restack(MENU_WIN(menu
), other
, mode
);
850 if(menu
->submenu
!=NULL
)
851 region_restack((WRegion
*)(menu
->submenu
), MENU_WIN(menu
), Above
);
855 void menu_stacking(WMenu
*menu
, Window
*bottomret
, Window
*topret
)
859 if(menu
->submenu
!=NULL
)
860 region_stacking((WRegion
*)(menu
->submenu
), bottomret
, topret
);
862 *bottomret
=MENU_WIN(menu
);
864 *topret
=MENU_WIN(menu
);
875 static void menu_do_select_nth(WMenu
*menu
, int n
)
877 int oldn
=menu
->selected_entry
;
883 if(menu
->submenu
!=NULL
)
884 destroy_obj((Obj
*)menu
->submenu
);
886 assert(menu
->submenu
==NULL
);
888 menu
->selected_entry
=n
;
891 if(n
<menu
->first_entry
){
894 }else if(n
>=menu
->first_entry
+menu
->vis_entries
){
895 menu
->first_entry
=n
-menu
->vis_entries
+1;
899 if(menu
->entries
[n
].flags
&WMENUENTRY_SUBMENU
&&
906 menu_draw_entries(menu
, TRUE
);
908 /* redraw new and old selected entry */
910 get_inner_geom(menu
, &igeom
);
914 menu_draw_entry(menu
, oldn
, &igeom
, TRUE
);
916 menu_draw_entry(menu
, n
, &igeom
, TRUE
);
922 * Select \var{n}:th entry in menu.
925 void menu_select_nth(WMenu
*menu
, int n
)
929 if(n
>=menu
->n_entries
)
932 menu_typeahead_clear(menu
);
933 menu_do_select_nth(menu
, n
);
938 * Select previous entry in menu.
941 void menu_select_prev(WMenu
*menu
)
943 menu_select_nth(menu
, (menu
->selected_entry
<=0
945 : menu
->selected_entry
-1));
950 * Select next entry in menu.
953 void menu_select_next(WMenu
*menu
)
955 menu_select_nth(menu
, (menu
->selected_entry
+1)%menu
->n_entries
);
959 static void menu_do_finish(WMenu
*menu
)
964 WMenu
*head
=menu_head(menu
);
966 handler
=menu
->handler
;
967 menu
->handler
=extl_fn_none();
969 ok
=extl_table_geti_t(menu
->tab
, menu
->selected_entry
+1, &tab
);
971 if(!region_rqdispose((WRegion
*)head
)){
972 if(head
->submenu
!=NULL
)
973 destroy_obj((Obj
*)head
->submenu
);
977 extl_call(handler
, "t", NULL
, tab
);
979 extl_unref_fn(handler
);
980 extl_unref_table(tab
);
985 * If selected entry is a submenu, display that.
986 * Otherwise destroy the menu and call handler for selected entry.
989 void menu_finish(WMenu
*menu
)
991 menu_typeahead_clear(menu
);
993 if(!menu
->pmenu_mode
&& menu
->selected_entry
>=0 &&
994 menu
->entries
[menu
->selected_entry
].flags
&WMENUENTRY_SUBMENU
){
995 show_sub(menu
, menu
->selected_entry
);
999 mainloop_defer_action((Obj
*)menu
, (WDeferredAction
*)menu_do_finish
);
1005 * Close \var{menu} not calling any possible finish handlers.
1008 void menu_cancel(WMenu
*menu
)
1010 region_defer_rqdispose((WRegion
*)menu
);
1019 static int scroll_time
=20;
1020 static int scroll_amount
=3;
1021 static WTimer
*scroll_timer
=NULL
;
1024 static void reset_scroll_timer()
1026 if(scroll_timer
!=NULL
){
1027 destroy_obj((Obj
*)scroll_timer
);
1034 * Set module basic settings. The parameter table may contain the
1037 * \begin{tabularx}{\linewidth}{lX}
1038 * \tabhead{Field & Description}
1039 * \var{scroll_amount} & Number of pixels to scroll at a time in
1040 * pointer-controlled menus when one extends
1041 * beyond a border of the screen and the pointer
1042 * touches that border. \\
1043 * \var{scroll_delay} & Time between such scrolling events in
1048 void mod_menu_set(ExtlTab tab
)
1052 if(extl_table_gets_i(tab
, "scroll_amount", &a
))
1053 scroll_amount
=maxof(0, a
);
1054 if(extl_table_gets_i(tab
, "scroll_delay", &t
))
1055 scroll_time
=maxof(0, t
);
1060 * Get module basic settings. For details, see \fnref{mod_menu.set}.
1064 ExtlTab
mod_menu_get()
1066 ExtlTab tab
=extl_create_table();
1067 extl_table_sets_i(tab
, "scroll_amount", scroll_amount
);
1068 extl_table_sets_i(tab
, "scroll_delay", scroll_time
);
1081 static int calc_diff(const WRectangle
*mg
, const WRectangle
*pg
, int d
)
1085 return mg
->x
+mg
->w
-pg
->w
;
1087 return mg
->y
+mg
->h
-pg
->h
;
1097 static int scrolld_subs(WMenu
*menu
, int d
)
1100 WRegion
*p
=REGION_PARENT_REG(menu
);
1101 const WRectangle
*pg
;
1109 diff
=maxof(diff
, calc_diff(®ION_GEOM(menu
), pg
, d
));
1113 return minof(maxof(0, diff
), scroll_amount
);
1117 static void menu_select_entry_at(WMenu
*menu
, int px
, int py
);
1120 static void do_scroll(WMenu
*menu
, int xd
, int yd
)
1125 xwindow_pointer_pos(region_root_of((WRegion
*)menu
), &px
, &py
);
1128 g
=REGION_GEOM(menu
);
1132 window_do_fitrep((WWindow
*)menu
, NULL
, &g
);
1134 menu_select_entry_at(menu
, px
, py
);
1141 static void scroll_left(WTimer
*timer
, WMenu
*menu
)
1144 do_scroll(menu
, -scrolld_subs(menu
, D_LEFT
), 0);
1145 if(scrolld_subs(menu
, D_LEFT
)>0){
1146 timer_set(timer
, scroll_time
, (WTimerHandler
*)scroll_left
,
1154 static void scroll_up(WTimer
*timer
, WMenu
*menu
)
1157 do_scroll(menu
, 0, -scrolld_subs(menu
, D_UP
));
1158 if(scrolld_subs(menu
, D_UP
)>0){
1159 timer_set(timer
, scroll_time
, (WTimerHandler
*)scroll_up
,
1168 static void scroll_right(WTimer
*timer
, WMenu
*menu
)
1171 do_scroll(menu
, scrolld_subs(menu
, D_RIGHT
), 0);
1172 if(scrolld_subs(menu
, D_RIGHT
)>0){
1173 timer_set(timer
, scroll_time
, (WTimerHandler
*)scroll_right
,
1181 static void scroll_down(WTimer
*timer
, WMenu
*menu
)
1184 do_scroll(menu
, 0, scrolld_subs(menu
, D_DOWN
));
1185 if(scrolld_subs(menu
, D_DOWN
)>0){
1186 timer_set(timer
, scroll_time
, (WTimerHandler
*)scroll_down
,
1194 static void end_scroll(WMenu
*menu
)
1196 reset_scroll_timer();
1199 #define SCROLL_OFFSET 10
1201 static void check_scroll(WMenu
*menu
, int x
, int y
)
1203 WRegion
*parent
=REGION_PARENT_REG(menu
);
1205 WTimerHandler
*fn
=NULL
;
1207 if(!menu
->pmenu_mode
)
1215 region_rootpos(parent
, &rx
, &ry
);
1219 if(x
<=SCROLL_OFFSET
){
1220 fn
=(WTimerHandler
*)scroll_right
;
1221 }else if(y
<=SCROLL_OFFSET
){
1222 fn
=(WTimerHandler
*)scroll_down
;
1223 }else if(x
>=REGION_GEOM(parent
).w
-SCROLL_OFFSET
){
1224 fn
=(WTimerHandler
*)scroll_left
;
1225 }else if(y
>=REGION_GEOM(parent
).h
-SCROLL_OFFSET
){
1226 fn
=(WTimerHandler
*)scroll_up
;
1234 if(scroll_timer
!=NULL
){
1235 if(scroll_timer
->handler
==(WTimerHandler
*)fn
&&
1236 timer_is_set(scroll_timer
)){
1240 scroll_timer
=create_timer();
1241 if(scroll_timer
==NULL
)
1245 fn(scroll_timer
, (Obj
*)menu_head(menu
));
1252 /*{{{ Pointer handlers */
1255 int menu_entry_at_root(WMenu
*menu
, int root_x
, int root_y
)
1257 int rx
, ry
, x
, y
, entry
;
1259 region_rootpos((WRegion
*)menu
, &rx
, &ry
);
1261 get_inner_geom(menu
, &ig
);
1266 if(x
<0 || x
>=ig
.w
|| y
<0 || y
>=ig
.h
)
1269 entry
=y
/(menu
->entry_h
+menu
->entry_spacing
);
1270 if(entry
<0 || entry
>=menu
->vis_entries
)
1272 return entry
+menu
->first_entry
;
1276 int menu_entry_at_root_tree(WMenu
*menu
, int root_x
, int root_y
,
1281 menu
=menu_tail(menu
);
1285 if(!menu
->pmenu_mode
)
1286 return menu_entry_at_root(menu
, root_x
, root_y
);
1289 entry
=menu_entry_at_root(menu
, root_x
, root_y
);
1294 menu
=REGION_MANAGER_CHK(menu
, WMenu
);
1301 static void menu_select_entry_at(WMenu
*menu
, int px
, int py
)
1303 int entry
=menu_entry_at_root_tree(menu
, px
, py
, &menu
);
1305 menu_do_select_nth(menu
, entry
);
1309 void menu_release(WMenu
*menu
, XButtonEvent
*ev
)
1311 int entry
=menu_entry_at_root_tree(menu
, ev
->x_root
, ev
->y_root
, &menu
);
1314 menu_select_nth(menu
, entry
);
1316 }else if(menu
->pmenu_mode
){
1317 menu_cancel(menu_head(menu
));
1322 void menu_motion(WMenu
*menu
, XMotionEvent
*ev
, int dx
, int dy
)
1324 menu_select_entry_at(menu
, ev
->x_root
, ev
->y_root
);
1325 check_scroll(menu
, ev
->x_root
, ev
->y_root
);
1329 void menu_button(WMenu
*menu
, XButtonEvent
*ev
)
1331 int entry
=menu_entry_at_root_tree(menu
, ev
->x_root
, ev
->y_root
, &menu
);
1333 menu_select_nth(menu
, entry
);
1337 int menu_press(WMenu
*menu
, XButtonEvent
*ev
, WRegion
**reg_ret
)
1339 menu_button(menu
, ev
);
1340 menu
=menu_head(menu
);
1341 ioncore_set_drag_handlers((WRegion
*)menu
,
1343 (WMotionHandler
*)menu_motion
,
1344 (WButtonHandler
*)menu_release
,
1354 /*{{{ Typeahead find */
1357 static void menu_insstr(WMenu
*menu
, const char *buf
, size_t n
)
1359 size_t oldlen
=(menu
->typeahead
==NULL
? 0 : strlen(menu
->typeahead
));
1360 char *newta
=(char*)malloc(oldlen
+n
+1);
1368 memcpy(newta
, menu
->typeahead
, oldlen
);
1370 memcpy(newta
+oldlen
, buf
, n
);
1371 newta
[oldlen
+n
]='\0';
1374 while(*newta
!='\0'){
1376 entry
=menu
->selected_entry
;
1378 if(menu
->entries
[entry
].title
!=NULL
){
1379 size_t l
=strlen(menu
->entries
[entry
].title
);
1380 if(libtu_strcasestr(menu
->entries
[entry
].title
, newta
)){
1385 entry
=(entry
+1)%menu
->n_entries
;
1386 }while(entry
!=menu
->selected_entry
);
1388 menu_do_select_nth(menu
, entry
);
1394 if(newta_orig
!=newta
){
1399 char *p
=scopy(newta
);
1404 if(menu
->typeahead
!=NULL
)
1405 free(menu
->typeahead
);
1406 menu
->typeahead
=newta
;
1411 * Clear typeahead buffer.
1414 void menu_typeahead_clear(WMenu
*menu
)
1416 if(menu
->typeahead
!=NULL
){
1417 free(menu
->typeahead
);
1418 menu
->typeahead
=NULL
;
1426 /*{{{ Dynamic function table and class implementation */
1429 static DynFunTab menu_dynfuntab
[]={
1430 {(DynFun
*)region_fitrep
, (DynFun
*)menu_fitrep
},
1431 {region_updategr
, menu_updategr
},
1432 {window_draw
, menu_draw
},
1433 {(DynFun
*)window_press
, (DynFun
*)menu_press
},
1434 {region_managed_remove
, menu_managed_remove
},
1435 {region_do_set_focus
, menu_do_set_focus
},
1436 {region_activated
, menu_activated
},
1437 {region_inactivated
, menu_inactivated
},
1438 {window_insstr
, menu_insstr
},
1439 {region_restack
, menu_restack
},
1440 {region_stacking
, menu_stacking
},
1441 {region_size_hints
, menu_size_hints
},
1447 IMPLCLASS(WMenu
, WWindow
, menu_deinit
, menu_dynfuntab
);