4 * Copyright (c) Tuomo Valkonen 1999-2006.
6 * Ion is free software; you can redistribute it and/or modify it under
7 * the terms of the GNU Lesser General Public License as published by
8 * the Free Software Foundation; either version 2.1 of the License, or
9 * (at your option) any later version.
15 #include <libtu/minmax.h>
16 #include <libtu/objp.h>
17 #include <libtu/obj.h>
18 #include <libmainloop/defer.h>
19 #include <libmainloop/signal.h>
21 #include <ioncore/common.h>
22 #include <ioncore/window.h>
23 #include <ioncore/global.h>
24 #include <ioncore/regbind.h>
25 #include <ioncore/strings.h>
26 #include <ioncore/pointer.h>
27 #include <ioncore/focus.h>
28 #include <ioncore/event.h>
29 #include <ioncore/xwindow.h>
30 #include <ioncore/names.h>
35 #define MENU_WIN(MENU) ((MENU)->win.win)
41 static bool extl_table_getis(ExtlTab tab, int i, const char *s, char c,
47 if(!extl_table_geti_t(tab, i, &sub))
49 ret=extl_table_get(sub, 's', c, s, p);
50 extl_unref_table(sub);
58 /*{{{ Drawing routines */
61 static void get_outer_geom(WMenu *menu, WRectangle *geom)
65 geom->w=REGION_GEOM(menu).w;
66 geom->h=REGION_GEOM(menu).h;
70 static void get_inner_geom(WMenu *menu, WRectangle *geom)
74 get_outer_geom(menu, geom);
76 if(menu->brush!=NULL){
77 grbrush_get_border_widths(menu->brush, &bdw);
80 geom->w-=bdw.left+bdw.right;
81 geom->h-=bdw.top+bdw.bottom;
82 geom->w=maxof(0, geom->w);
83 geom->h=maxof(0, geom->h);
88 static void menu_draw_entry(WMenu *menu, int i, const WRectangle *igeom,
94 static const char *attrs[]={
95 "active-selected-normal",
96 "active-selected-submenu",
97 "active-unselected-normal",
98 "active-unselected-submenu",
99 "inactive-selected-normal",
100 "inactive-selected-submenu",
101 "inactive-unselected-normal",
102 "inactive-unselected-submenu",
105 if(menu->entry_brush==NULL)
109 geom.h=menu->entry_h;
110 geom.y+=(i-menu->first_entry)*(menu->entry_h+menu->entry_spacing);
112 a=((REGION_IS_ACTIVE(menu) ? 0 : 4)
113 |(menu->selected_entry==i ? 0 : 2)
114 |(menu->entries[i].flags&WMENUENTRY_SUBMENU ? 1 : 0));
116 grbrush_begin(menu->entry_brush, &geom, GRBRUSH_AMEND);
118 grbrush_draw_textbox(menu->entry_brush, &geom, menu->entries[i].title,
121 grbrush_end(menu->entry_brush);
125 void menu_draw_entries(WMenu *menu, bool complete)
130 if(menu->entry_brush==NULL)
133 get_inner_geom(menu, &igeom);
135 mx=menu->first_entry+menu->vis_entries;
136 mx=(mx < menu->n_entries ? mx : menu->n_entries);
138 for(i=menu->first_entry; i<mx; i++)
139 menu_draw_entry(menu, i, &igeom, complete);
143 void menu_draw(WMenu *menu, bool complete)
146 const char *substyle=(REGION_IS_ACTIVE(menu) ? "active" : "inactive");
148 if(menu->brush==NULL)
151 get_outer_geom(menu, &geom);
153 grbrush_begin(menu->brush, &geom,
154 (complete ? 0 : GRBRUSH_NO_CLEAR_OK));
156 grbrush_draw_border(menu->brush, &geom, substyle);
158 menu_draw_entries(menu, FALSE);
160 grbrush_end(menu->brush);
170 static void menu_calc_size(WMenu *menu, bool maxexact,
172 int *w_ret, int *h_ret)
174 GrBorderWidths bdw, e_bdw;
177 int nath, bdh, maxew=menu->max_entry_w;
179 grbrush_get_border_widths(menu->brush, &bdw);
180 grbrush_get_border_widths(menu->entry_brush, &e_bdw);
182 if(maxexact || maxew>maxw-(int)bdw.left-(int)bdw.right){
183 maxew=maxw-bdw.left-bdw.right;
186 *w_ret=maxew+bdw.left+bdw.right;
189 bdh=bdw.top+bdw.bottom;
191 if(menu->n_entries==0){
192 *h_ret=(maxexact ? maxh : bdh);
196 int vis=(maxh-bdh+e_bdw.spacing)/(e_bdw.spacing+menu->entry_h);
197 if(vis>menu->n_entries){
200 }else if(menu->selected_entry>=0){
201 if(menu->selected_entry<menu->first_entry)
202 menu->first_entry=menu->selected_entry;
203 else if(menu->selected_entry>=menu->first_entry+vis)
204 menu->first_entry=menu->selected_entry-vis+1;
208 menu->vis_entries=vis;
212 *h_ret=vis*menu->entry_h+(vis-1)*e_bdw.spacing+bdh;
215 /* Calculate new shortened entry names */
216 maxew-=e_bdw.left+e_bdw.right;
218 for(i=0; i<menu->n_entries; i++){
219 if(menu->entries[i].title){
220 free(menu->entries[i].title);
221 menu->entries[i].title=NULL;
226 if(extl_table_getis(menu->tab, i+1, "name", 's', &str)){
227 menu->entries[i].title=grbrush_make_label(menu->entry_brush,
235 void calc_size(WMenu *menu, int *w, int *h)
237 if(menu->pmenu_mode){
238 menu_calc_size(menu, FALSE, INT_MAX, INT_MAX, w, h);
240 menu_calc_size(menu, !(menu->last_fp.mode®ION_FIT_BOUNDS),
241 menu->last_fp.g.w, menu->last_fp.g.h, w, h);
246 /* Return offset from bottom-left corner of containing mplex or top-right
247 * corner of parent menu for the respective corner of menu.
249 static void get_placement_offs(WMenu *menu, int *xoff, int *yoff)
256 if(menu->brush!=NULL){
257 grbrush_get_border_widths(menu->brush, &bdw);
262 if(menu->entry_brush!=NULL){
263 grbrush_get_border_widths(menu->entry_brush, &bdw);
270 #define MINIMUM_Y_VISIBILITY 20
271 #define POINTER_OFFSET 5
273 static void menu_firstfit(WMenu *menu, bool submenu, const WRectangle *refg)
277 calc_size(menu, &(geom.w), &(geom.h));
279 if(!(menu->last_fp.mode®ION_FIT_BOUNDS)){
280 geom.x=menu->last_fp.g.x;
281 geom.y=menu->last_fp.g.y;
282 }else if(menu->pmenu_mode){
287 const WRectangle *maxg =
288 ®ION_GEOM(REGION_PARENT((WRegion*)menu));
291 geom.y+=POINTER_OFFSET;
293 if(geom.y+MINIMUM_Y_VISIBILITY>maxg->y+maxg->h){
294 geom.y=maxg->y+maxg->h-MINIMUM_Y_VISIBILITY;
295 geom.x=refg->x+POINTER_OFFSET;
296 if(geom.x+geom.w>maxg->x+maxg->w)
297 geom.x=refg->x-geom.w-POINTER_OFFSET;
301 else if(geom.x+geom.w>maxg->x+maxg->w)
302 geom.x=maxg->x+maxg->w-geom.w;
306 const WRectangle *maxg=&(menu->last_fp.g);
308 int l, r, t, b, xoff, yoff;
310 get_placement_offs(menu, &xoff, &yoff);
312 r=refg->x+refg->w+xoff;
314 b=refg->y+refg->h-yoff;
316 geom.x=maxof(l, r-geom.w);
317 if(geom.x+geom.w>maxg->x+maxg->w)
320 geom.y=minof(b-geom.h, t);
325 geom.y=maxg->y+maxg->h-geom.h;
329 window_do_fitrep(&menu->win, NULL, &geom);
333 static void menu_do_refit(WMenu *menu, WWindow *par, const WFitParams *oldfp)
337 calc_size(menu, &(geom.w), &(geom.h));
339 if(!(menu->last_fp.mode®ION_FIT_BOUNDS)){
340 geom.x=menu->last_fp.g.x;
341 geom.y=menu->last_fp.g.y;
342 }else if(menu->pmenu_mode){
343 geom.x=REGION_GEOM(menu).x;
344 geom.y=REGION_GEOM(menu).y;
346 const WRectangle *maxg=&(menu->last_fp.g);
347 int xdiff=REGION_GEOM(menu).x-oldfp->g.x;
348 int ydiff=(REGION_GEOM(menu).y+REGION_GEOM(menu).h
349 -(oldfp->g.y+oldfp->g.h));
350 geom.x=maxof(0, minof(maxg->x+xdiff, maxg->x+maxg->w-geom.w));
351 geom.y=maxof(0, minof(maxg->y+maxg->h+ydiff, maxg->y+maxg->h)-geom.h);
354 window_do_fitrep(&menu->win, par, &geom);
358 bool menu_fitrep(WMenu *menu, WWindow *par, const WFitParams *fp)
362 if(par!=NULL && !region_same_rootwin((WRegion*)par, (WRegion*)menu))
367 menu_do_refit(menu, par, &oldfp);
369 if(menu->submenu!=NULL && !menu->pmenu_mode)
370 region_fitrep((WRegion*)(menu->submenu), par, fp);
379 /*{{{ Brush update */
382 static void calc_entry_dimens(WMenu *menu)
384 int i, n=extl_table_get_n(menu->tab);
391 if(extl_table_gets_s(menu->tab, title, &str)){
392 maxw=grbrush_get_text_width(title_brush, str, strlen(str));
398 if(extl_table_getis(menu->tab, i, "name", 's', &str)){
399 int w=grbrush_get_text_width(menu->entry_brush,
407 grbrush_get_border_widths(menu->entry_brush, &bdw);
408 grbrush_get_font_extents(menu->entry_brush, &fnte);
410 menu->max_entry_w=maxw+bdw.left+bdw.right;
411 menu->entry_h=fnte.max_height+bdw.top+bdw.bottom;
412 menu->entry_spacing=bdw.spacing;
416 static bool menu_init_gr(WMenu *menu, WRootWin *rootwin, Window win)
418 GrBrush *brush, *entry_brush;
420 const char *style=(menu->big_mode
424 : "input-menu-normal"));
426 const char *entry_style=(menu->big_mode
427 ? "tab-menuentry-big"
429 ? "tab-menuentry-pmenu"
430 : "tab-menuentry-normal"));
432 brush=gr_get_brush(win, rootwin, style);
437 entry_brush=grbrush_get_slave(brush, rootwin, entry_style);
439 if(entry_brush==NULL){
440 grbrush_release(brush);
444 if(menu->entry_brush!=NULL)
445 grbrush_release(menu->entry_brush);
446 if(menu->brush!=NULL)
447 grbrush_release(menu->brush);
450 menu->entry_brush=entry_brush;
452 calc_entry_dimens(menu);
458 void menu_updategr(WMenu *menu)
460 if(!menu_init_gr(menu, region_rootwin_of((WRegion*)menu),
465 menu_do_refit(menu, NULL, &(menu->last_fp));
467 region_updategr_default((WRegion*)menu);
469 window_draw((WWindow*)menu, TRUE);
473 static void menu_release_gr(WMenu *menu)
475 if(menu->entry_brush!=NULL){
476 grbrush_release(menu->entry_brush);
477 menu->entry_brush=NULL;
479 if(menu->brush!=NULL){
480 grbrush_release(menu->brush);
492 static WMenuEntry *preprocess_menu(ExtlTab tab, int *n_entries)
499 n=extl_table_get_n(tab);
505 entries=ALLOC_N(WMenuEntry, n);
510 /* Initialise entries and check submenus */
512 entries[i-1].title=NULL;
513 entries[i-1].flags=0;
514 if(extl_table_getis(tab, i, "submenu_fn", 'f', &fn)){
515 entries[i-1].flags|=WMENUENTRY_SUBMENU;
517 }else if(extl_table_getis(tab, i, "submenu", 't', &sub)){
518 entries[i-1].flags|=WMENUENTRY_SUBMENU;
519 extl_unref_table(sub);
528 bool menu_init(WMenu *menu, WWindow *par, const WFitParams *fp,
529 const WMenuCreateParams *params)
534 menu->entries=preprocess_menu(params->tab, &(menu->n_entries));
536 if(menu->entries==NULL){
537 warn(TR("Empty menu."));
541 menu->tab=extl_ref_table(params->tab);
542 menu->handler=extl_ref_fn(params->handler);
543 menu->pmenu_mode=params->pmenu_mode;
544 menu->big_mode=params->big_mode;
548 if(params->pmenu_mode)
549 menu->selected_entry=-1;
551 menu->selected_entry=params->initial-1;
552 if(menu->selected_entry<0)
553 menu->selected_entry=0;
554 if(params->initial > menu->n_entries)
555 menu->selected_entry=0;
561 menu->entry_brush=NULL;
562 menu->entry_spacing=0;
563 menu->vis_entries=menu->n_entries;
566 menu->typeahead=NULL;
571 if(!window_init((WWindow*)menu, par, fp))
576 if(!menu_init_gr(menu, region_rootwin_of((WRegion*)par), win))
579 menu_firstfit(menu, params->submenu_mode, &(params->refg));
581 window_select_input(&(menu->win), IONCORE_EVENTMASK_NORMAL);
583 region_add_bindmap((WRegion*)menu, mod_menu_menu_bindmap);
585 region_register((WRegion*)menu);
590 window_deinit((WWindow*)menu);
592 extl_unref_table(menu->tab);
593 extl_unref_fn(menu->handler);
599 WMenu *create_menu(WWindow *par, const WFitParams *fp,
600 const WMenuCreateParams *params)
602 CREATEOBJ_IMPL(WMenu, menu, (p, par, fp, params));
607 void menu_deinit(WMenu *menu)
611 menu_typeahead_clear(menu);
613 if(menu->submenu!=NULL)
614 destroy_obj((Obj*)menu->submenu);
616 extl_unref_table(menu->tab);
617 extl_unref_fn(menu->handler);
619 for(i=0; i<menu->n_entries; i++)
620 free(menu->entries[i].title);
623 menu_release_gr(menu);
624 window_deinit((WWindow*)menu);
634 static void menu_inactivated(WMenu *menu)
636 window_draw((WWindow*)menu, FALSE);
640 static void menu_activated(WMenu *menu)
642 window_draw((WWindow*)menu, FALSE);
652 static WMenu *menu_head(WMenu *menu)
654 WMenu *m=REGION_MANAGER_CHK(menu, WMenu);
655 return (m==NULL ? menu : menu_head(m));
659 static WMenu *menu_tail(WMenu *menu)
661 return (menu->submenu==NULL ? menu : menu_tail(menu->submenu));
665 static void menu_managed_remove(WMenu *menu, WRegion *sub)
667 bool mcf=region_may_control_focus((WRegion*)menu);
669 if(sub!=(WRegion*)menu->submenu)
674 region_unset_manager(sub, (WRegion*)menu);
677 region_do_set_focus((WRegion*)menu, FALSE);
681 int get_sub_y_off(WMenu *menu, int n)
683 /* top + sum of above entries and spacings - top_of_sub */
684 return (menu->entry_h+menu->entry_spacing)*(n-menu->first_entry);
688 static void show_sub(WMenu *menu, int n)
691 WMenuCreateParams fnp;
695 par=REGION_PARENT(menu);
702 fnp.pmenu_mode=menu->pmenu_mode;
703 fnp.big_mode=menu->big_mode;
704 fnp.submenu_mode=TRUE;
706 if(menu->pmenu_mode){
707 fnp.refg.x=REGION_GEOM(menu).x+REGION_GEOM(menu).w;
708 fnp.refg.y=REGION_GEOM(menu).y+get_sub_y_off(menu, n);
712 fnp.refg=REGION_GEOM(menu);
715 fnp.tab=extl_table_none();
718 if(extl_table_getis(menu->tab, n+1, "submenu_fn", 'f', &fn)){
720 extl_call(fn, NULL, "t", &(fnp.tab));
721 extl_unprotect(NULL);
724 extl_table_getis(menu->tab, n+1, "submenu", 't', &(fnp.tab));
726 if(fnp.tab==extl_table_none())
730 fnp.handler=extl_ref_fn(menu->handler);
735 if(extl_table_getis(menu->tab, n+1, "initial", 'f', &fn)){
737 extl_call(fn, NULL, "i", &(fnp.initial));
738 extl_unprotect(NULL);
741 extl_table_getis(menu->tab, n+1, "initial", 'i', &(fnp.initial));
745 submenu=create_menu(par, &fp, &fnp);
750 menu->submenu=submenu;
751 region_set_manager((WRegion*)submenu, (WRegion*)menu);
753 region_restack((WRegion*)submenu, MENU_WIN(menu), Above);
754 region_map((WRegion*)submenu);
756 if(!menu->pmenu_mode && region_may_control_focus((WRegion*)menu))
757 region_do_set_focus((WRegion*)submenu, FALSE);
761 static void menu_do_set_focus(WMenu *menu, bool warp)
763 if(menu->submenu!=NULL)
764 region_do_set_focus((WRegion*)menu->submenu, warp);
766 window_do_set_focus((WWindow*)menu, warp);
770 void menu_restack(WMenu *menu, Window other, int mode)
772 xwindow_restack(MENU_WIN(menu), other, mode);
773 if(menu->submenu!=NULL)
774 region_restack((WRegion*)(menu->submenu), MENU_WIN(menu), Above);
778 void menu_stacking(WMenu *menu, Window *bottomret, Window *topret)
782 if(menu->submenu!=NULL)
783 region_stacking((WRegion*)(menu->submenu), bottomret, topret);
785 *bottomret=MENU_WIN(menu);
787 *topret=MENU_WIN(menu);
798 static void menu_do_select_nth(WMenu *menu, int n)
800 int oldn=menu->selected_entry;
806 if(menu->submenu!=NULL)
807 destroy_obj((Obj*)menu->submenu);
809 assert(menu->submenu==NULL);
811 menu->selected_entry=n;
814 if(n<menu->first_entry){
817 }else if(n>=menu->first_entry+menu->vis_entries){
818 menu->first_entry=n-menu->vis_entries+1;
822 if(menu->entries[n].flags&WMENUENTRY_SUBMENU &&
829 menu_draw_entries(menu, TRUE);
831 /* redraw new and old selected entry */
833 get_inner_geom(menu, &igeom);
837 menu_draw_entry(menu, oldn, &igeom, TRUE);
839 menu_draw_entry(menu, n, &igeom, TRUE);
845 * Select \var{n}:th entry in menu.
848 void menu_select_nth(WMenu *menu, int n)
852 if(n>=menu->n_entries)
855 menu_typeahead_clear(menu);
856 menu_do_select_nth(menu, n);
861 * Select previous entry in menu.
864 void menu_select_prev(WMenu *menu)
866 menu_select_nth(menu, (menu->selected_entry<=0
868 : menu->selected_entry-1));
873 * Select next entry in menu.
876 void menu_select_next(WMenu *menu)
878 menu_select_nth(menu, (menu->selected_entry+1)%menu->n_entries);
882 static void menu_do_finish(WMenu *menu)
887 WMenu *head=menu_head(menu);
889 handler=menu->handler;
890 menu->handler=extl_fn_none();
892 ok=extl_table_geti_t(menu->tab, menu->selected_entry+1, &tab);
894 if(region_manager_allows_destroying((WRegion*)head))
895 destroy_obj((Obj*)head);
896 else if(head->submenu!=NULL)
897 destroy_obj((Obj*)head->submenu);
900 extl_call(handler, "t", NULL, tab);
902 extl_unref_fn(handler);
903 extl_unref_table(tab);
908 * If selected entry is a submenu, display that.
909 * Otherwise destroy the menu and call handler for selected entry.
912 void menu_finish(WMenu *menu)
914 menu_typeahead_clear(menu);
916 if(!menu->pmenu_mode && menu->selected_entry>=0 &&
917 menu->entries[menu->selected_entry].flags&WMENUENTRY_SUBMENU){
918 show_sub(menu, menu->selected_entry);
922 mainloop_defer_action((Obj*)menu, (WDeferredAction*)menu_do_finish);
928 * Close \var{menu} not calling any possible finish handlers.
931 void menu_cancel(WMenu *menu)
933 if(region_manager_allows_destroying((WRegion*)menu))
934 mainloop_defer_destroy((Obj*)menu);
943 static int scroll_time=20;
944 static int scroll_amount=3;
945 static WTimer *scroll_timer=NULL;
948 static void reset_scroll_timer()
950 if(scroll_timer!=NULL){
951 destroy_obj((Obj*)scroll_timer);
958 * Set module basic settings. The parameter table may contain the
961 * \begin{tabularx}{\linewidth}{lX}
962 * \tabhead{Field & Description}
963 * \var{scroll_amount} & Number of pixels to scroll at a time
964 * pointer-controlled menus when one extends
965 * beyond a border of the screen and the pointer
966 * touches that border. \\
967 * \var{scroll_delay} & Time between such scrolling events in
972 void mod_menu_set(ExtlTab tab)
976 if(extl_table_gets_i(tab, "scroll_amount", &a))
977 scroll_amount=maxof(0, a);
978 if(extl_table_gets_i(tab, "scroll_delay", &t))
979 scroll_time=maxof(0, t);
984 * Get module basic settings. For details, see \fnref{mod_menu.set}.
988 ExtlTab mod_menu_get()
990 ExtlTab tab=extl_create_table();
991 extl_table_sets_i(tab, "scroll_amount", scroll_amount);
992 extl_table_sets_i(tab, "scroll_delay", scroll_time);
1005 static int calc_diff(const WRectangle *mg, const WRectangle *pg, int d)
1009 return mg->x+mg->w-pg->w;
1011 return mg->y+mg->h-pg->h;
1021 static int scrolld_subs(WMenu *menu, int d)
1024 WRegion *p=REGION_PARENT_REG(menu);
1025 const WRectangle *pg;
1033 diff=maxof(diff, calc_diff(®ION_GEOM(menu), pg, d));
1037 return minof(maxof(0, diff), scroll_amount);
1041 static void menu_select_entry_at(WMenu *menu, int px, int py);
1044 static void do_scroll(WMenu *menu, int xd, int yd)
1049 xwindow_pointer_pos(region_root_of((WRegion*)menu), &px, &py);
1052 g=REGION_GEOM(menu);
1056 window_do_fitrep((WWindow*)menu, NULL, &g);
1058 menu_select_entry_at(menu, px, py);
1065 static void scroll_left(WTimer *timer, WMenu *menu)
1068 do_scroll(menu, -scrolld_subs(menu, D_LEFT), 0);
1069 if(scrolld_subs(menu, D_LEFT)>0){
1070 timer_set(timer, scroll_time, (WTimerHandler*)scroll_left,
1078 static void scroll_up(WTimer *timer, WMenu *menu)
1081 do_scroll(menu, 0, -scrolld_subs(menu, D_UP));
1082 if(scrolld_subs(menu, D_UP)>0){
1083 timer_set(timer, scroll_time, (WTimerHandler*)scroll_up,
1092 static void scroll_right(WTimer *timer, WMenu *menu)
1095 do_scroll(menu, scrolld_subs(menu, D_RIGHT), 0);
1096 if(scrolld_subs(menu, D_RIGHT)>0){
1097 timer_set(timer, scroll_time, (WTimerHandler*)scroll_right,
1105 static void scroll_down(WTimer *timer, WMenu *menu)
1108 do_scroll(menu, 0, scrolld_subs(menu, D_DOWN));
1109 if(scrolld_subs(menu, D_DOWN)>0){
1110 timer_set(timer, scroll_time, (WTimerHandler*)scroll_down,
1118 static void end_scroll(WMenu *menu)
1120 reset_scroll_timer();
1123 #define SCROLL_OFFSET 10
1125 static void check_scroll(WMenu *menu, int x, int y)
1127 WRegion *parent=REGION_PARENT_REG(menu);
1129 WTimerHandler *fn=NULL;
1131 if(!menu->pmenu_mode)
1139 region_rootpos(parent, &rx, &ry);
1143 if(x<=SCROLL_OFFSET){
1144 fn=(WTimerHandler*)scroll_right;
1145 }else if(y<=SCROLL_OFFSET){
1146 fn=(WTimerHandler*)scroll_down;
1147 }else if(x>=REGION_GEOM(parent).w-SCROLL_OFFSET){
1148 fn=(WTimerHandler*)scroll_left;
1149 }else if(y>=REGION_GEOM(parent).h-SCROLL_OFFSET){
1150 fn=(WTimerHandler*)scroll_up;
1158 if(scroll_timer!=NULL){
1159 if(scroll_timer->handler==(WTimerHandler*)fn &&
1160 timer_is_set(scroll_timer)){
1164 scroll_timer=create_timer();
1165 if(scroll_timer==NULL)
1169 fn(scroll_timer, (Obj*)menu_head(menu));
1176 /*{{{ Pointer handlers */
1179 int menu_entry_at_root(WMenu *menu, int root_x, int root_y)
1181 int rx, ry, x, y, entry;
1183 region_rootpos((WRegion*)menu, &rx, &ry);
1185 get_inner_geom(menu, &ig);
1190 if(x<0 || x>=ig.w || y<0 || y>=ig.h)
1193 entry=y/(menu->entry_h+menu->entry_spacing);
1194 if(entry<0 || entry>=menu->vis_entries)
1196 return entry+menu->first_entry;
1200 int menu_entry_at_root_tree(WMenu *menu, int root_x, int root_y,
1205 menu=menu_tail(menu);
1209 if(!menu->pmenu_mode)
1210 return menu_entry_at_root(menu, root_x, root_y);
1213 entry=menu_entry_at_root(menu, root_x, root_y);
1218 menu=REGION_MANAGER_CHK(menu, WMenu);
1225 static void menu_select_entry_at(WMenu *menu, int px, int py)
1227 int entry=menu_entry_at_root_tree(menu, px, py, &menu);
1229 menu_do_select_nth(menu, entry);
1233 void menu_release(WMenu *menu, XButtonEvent *ev)
1235 int entry=menu_entry_at_root_tree(menu, ev->x_root, ev->y_root, &menu);
1238 menu_select_nth(menu, entry);
1240 }else if(menu->pmenu_mode){
1241 menu_cancel(menu_head(menu));
1246 void menu_motion(WMenu *menu, XMotionEvent *ev, int dx, int dy)
1248 menu_select_entry_at(menu, ev->x_root, ev->y_root);
1249 check_scroll(menu, ev->x_root, ev->y_root);
1253 void menu_button(WMenu *menu, XButtonEvent *ev)
1255 int entry=menu_entry_at_root_tree(menu, ev->x_root, ev->y_root, &menu);
1257 menu_select_nth(menu, entry);
1261 int menu_press(WMenu *menu, XButtonEvent *ev, WRegion **reg_ret)
1263 menu_button(menu, ev);
1264 menu=menu_head(menu);
1265 ioncore_set_drag_handlers((WRegion*)menu,
1267 (WMotionHandler*)menu_motion,
1268 (WButtonHandler*)menu_release,
1278 /*{{{ Typeahead find */
1281 static void menu_insstr(WMenu *menu, const char *buf, size_t n)
1283 size_t oldlen=(menu->typeahead==NULL ? 0 : strlen(menu->typeahead));
1284 char *newta=(char*)malloc(oldlen+n+1);
1292 memcpy(newta, menu->typeahead, oldlen);
1294 memcpy(newta+oldlen, buf, n);
1295 newta[oldlen+n]='\0';
1298 while(*newta!='\0'){
1300 entry=menu->selected_entry;
1302 if(menu->entries[entry].title!=NULL){
1303 size_t l=strlen(menu->entries[entry].title);
1304 if(libtu_strcasestr(menu->entries[entry].title, newta)){
1309 entry=(entry+1)%menu->n_entries;
1310 }while(entry!=menu->selected_entry);
1312 menu_do_select_nth(menu, entry);
1318 if(newta_orig!=newta){
1323 char *p=scopy(newta);
1328 if(menu->typeahead!=NULL)
1329 free(menu->typeahead);
1330 menu->typeahead=newta;
1335 * Clear typeahead buffer.
1338 void menu_typeahead_clear(WMenu *menu)
1340 if(menu->typeahead!=NULL){
1341 free(menu->typeahead);
1342 menu->typeahead=NULL;
1350 /*{{{ Dynamic function table and class implementation */
1353 static DynFunTab menu_dynfuntab[]={
1354 {(DynFun*)region_fitrep, (DynFun*)menu_fitrep},
1355 {region_updategr, menu_updategr},
1356 {window_draw, menu_draw},
1357 {(DynFun*)window_press, (DynFun*)menu_press},
1358 {region_managed_remove, menu_managed_remove},
1359 {region_do_set_focus, menu_do_set_focus},
1360 {region_activated, menu_activated},
1361 {region_inactivated, menu_inactivated},
1362 {window_insstr, menu_insstr},
1363 {region_restack, menu_restack},
1364 {region_stacking, menu_stacking},
1370 IMPLCLASS(WMenu, WWindow, menu_deinit, menu_dynfuntab);