Newer versions of libextl and libtu
[notion/jeffpc.git] / mod_menu / menu.c
blob478661d2e7d36bb5e6af1f4df1b16a5f626c9c11
1 /*
2 * ion/mod_menu/menu.c
4 * Copyright (c) Tuomo Valkonen 1999-2009.
6 * See the included file LICENSE for details.
7 */
9 #include <string.h>
10 #include <limits.h>
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>
32 #include "menu.h"
33 #include "main.h"
36 #define MENU_WIN(MENU) ((MENU)->win.win)
39 /*{{{ Helpers */
42 static bool extl_table_getis(ExtlTab tab, int i, const char *s, char c,
43 void *p)
45 ExtlTab sub;
46 bool ret;
48 if(!extl_table_geti_t(tab, i, &sub))
49 return FALSE;
50 ret=extl_table_get(sub, 's', c, s, p);
51 extl_unref_table(sub);
52 return ret;
56 /*}}}*/
59 /*{{{ Drawing routines */
62 static void get_outer_geom(WMenu *menu, WRectangle *geom)
64 geom->x=0;
65 geom->y=0;
66 geom->w=REGION_GEOM(menu).w;
67 geom->h=REGION_GEOM(menu).h;
71 static void get_inner_geom(WMenu *menu, WRectangle *geom)
73 GrBorderWidths bdw;
75 get_outer_geom(menu, geom);
77 if(menu->brush!=NULL){
78 grbrush_get_border_widths(menu->brush, &bdw);
79 geom->x+=bdw.left;
80 geom->y+=bdw.top;
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);
89 GR_DEFATTR(active);
90 GR_DEFATTR(inactive);
91 GR_DEFATTR(selected);
92 GR_DEFATTR(unselected);
93 GR_DEFATTR(normal);
94 GR_DEFATTR(submenu);
97 static void init_attr()
99 GR_ALLOCATTR_BEGIN;
100 GR_ALLOCATTR(active);
101 GR_ALLOCATTR(inactive);
102 GR_ALLOCATTR(selected);
103 GR_ALLOCATTR(unselected);
104 GR_ALLOCATTR(normal);
105 GR_ALLOCATTR(submenu);
106 GR_ALLOCATTR_END;
110 static void menu_draw_entry(WMenu *menu, int i, const WRectangle *igeom,
111 bool complete)
113 WRectangle geom;
114 GrAttr sa, aa;
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)
120 return;
122 geom=*igeom;
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,
134 complete);
136 grbrush_end(menu->entry_brush);
140 void menu_draw_entries(WMenu *menu, bool complete)
142 WRectangle igeom;
143 int i, mx;
145 if(menu->entry_brush==NULL)
146 return;
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));
161 WRectangle geom;
163 if(menu->brush==NULL)
164 return;
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);
181 /*}}}*/
184 /*{{{ Resize */
187 static void menu_calc_size(WMenu *menu, bool maxexact,
188 int maxw, int maxh,
189 int *w_ret, int *h_ret)
191 GrBorderWidths bdw, e_bdw;
192 char *str;
193 int i;
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;
201 *w_ret=maxw;
202 }else{
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);
210 menu->first_entry=0;
211 menu->vis_entries=0;
212 }else{
213 int vis=(maxh-bdh+e_bdw.spacing)/(e_bdw.spacing+menu->entry_h);
214 if(vis>menu->n_entries){
215 vis=menu->n_entries;
216 menu->first_entry=0;
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;
223 if(vis<=0)
224 vis=1;
225 menu->vis_entries=vis;
226 if(maxexact)
227 *h_ret=maxh;
228 else
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;
240 if(maxew<=0)
241 continue;
243 if(extl_table_getis(menu->tab, i+1, "name", 's', &str)){
244 menu->entries[i].title=grbrush_make_label(menu->entry_brush,
245 str, maxew);
246 free(str);
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);
256 }else{
257 menu_calc_size(menu, !(menu->last_fp.mode&REGION_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)
268 GrBorderWidths bdw;
270 *xoff=0;
271 *yoff=0;
273 if(menu->brush!=NULL){
274 grbrush_get_border_widths(menu->brush, &bdw);
275 *xoff+=bdw.right;
276 *yoff+=bdw.top;
279 if(menu->entry_brush!=NULL){
280 grbrush_get_border_widths(menu->entry_brush, &bdw);
281 *xoff+=bdw.right;
282 *yoff+=bdw.top;
287 #define MINIMUM_Y_VISIBILITY 20
288 #define POINTER_OFFSET 5
290 static void menu_firstfit(WMenu *menu, bool submenu, const WRectangle *refg)
292 WRectangle geom;
294 calc_size(menu, &(geom.w), &(geom.h));
296 if(!(menu->last_fp.mode&REGION_FIT_BOUNDS)){
297 geom.x=menu->last_fp.g.x;
298 geom.y=menu->last_fp.g.y;
299 }else if(menu->pmenu_mode){
300 geom.x=refg->x;
301 geom.y=refg->y;
303 if(!submenu){
304 const WRectangle *maxg =
305 &REGION_GEOM(REGION_PARENT((WRegion*)menu));
307 geom.x-=geom.w/2;
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;
315 }else{
316 if(geom.x<0)
317 geom.x=0;
318 else if(geom.x+geom.w>maxg->x+maxg->w)
319 geom.x=maxg->x+maxg->w-geom.w;
322 }else{
323 const WRectangle *maxg=&(menu->last_fp.g);
324 if(submenu){
325 int l, r, t, b, xoff, yoff;
327 get_placement_offs(menu, &xoff, &yoff);
328 l=refg->x+xoff;
329 r=refg->x+refg->w+xoff;
330 t=refg->y-yoff;
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)
335 geom.x=maxg->x;
337 geom.y=minof(b-geom.h, t);
338 if(geom.y<maxg->y)
339 geom.y=maxg->y;
340 }else{
341 geom.x=maxg->x;
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)
352 WRectangle geom;
354 calc_size(menu, &(geom.w), &(geom.h));
356 if(!(menu->last_fp.mode&REGION_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;
362 }else{
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)
377 WFitParams oldfp;
379 if(par!=NULL && !region_same_rootwin((WRegion*)par, (WRegion*)menu))
380 return FALSE;
382 oldfp=menu->last_fp;
383 menu->last_fp=*fp;
384 menu_do_refit(menu, par, &oldfp);
386 if(menu->submenu!=NULL && !menu->pmenu_mode)
387 region_fitrep((WRegion*)(menu->submenu), par, fp);
389 return TRUE;
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){
400 GrBorderWidths bdw;
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;
413 /*}}}*/
416 /*{{{ Brush update */
419 static void calc_entry_dimens(WMenu *menu)
421 int i, n=extl_table_get_n(menu->tab);
422 GrFontExtents fnte;
423 GrBorderWidths bdw;
424 int maxw=0;
425 char *str;
427 #if 0
428 if(extl_table_gets_s(menu->tab, title, &str)){
429 maxw=grbrush_get_text_width(title_brush, str, strlen(str));
430 free(str);
432 #endif
434 for(i=1; i<=n; i++){
435 if(extl_table_getis(menu->tab, i, "name", 's', &str)){
436 int w=grbrush_get_text_width(menu->entry_brush,
437 str, strlen(str));
438 if(w>maxw)
439 maxw=w;
440 free(str);
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 char *st;
457 const char *style=(menu->big_mode
458 ? "input-menu-big"
459 : (menu->pmenu_mode
460 ? "input-menu-pmenu"
461 : "input-menu-normal"));
463 const char *entry_style=(menu->big_mode
464 ? "tab-menuentry-big"
465 : (menu->pmenu_mode
466 ? "tab-menuentry-pmenu"
467 : "tab-menuentry-normal"));
469 brush=gr_get_brush(win, rootwin, style);
471 if(brush==NULL)
472 return FALSE;
474 entry_brush=grbrush_get_slave(brush, rootwin, entry_style);
476 if(entry_brush==NULL){
477 grbrush_release(brush);
478 return FALSE;
481 if(menu->entry_brush!=NULL)
482 grbrush_release(menu->entry_brush);
483 if(menu->brush!=NULL)
484 grbrush_release(menu->brush);
486 menu->brush=brush;
487 menu->entry_brush=entry_brush;
489 calc_entry_dimens(menu);
491 return TRUE;
495 void menu_updategr(WMenu *menu)
497 if(!menu_init_gr(menu, region_rootwin_of((WRegion*)menu),
498 MENU_WIN(menu))){
499 return;
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);
518 menu->brush=NULL;
523 /*}}}*/
526 /*{{{ Init/deinit */
529 static WMenuEntry *preprocess_menu(ExtlTab tab, int *n_entries)
531 WMenuEntry *entries;
532 ExtlTab entry;
533 int i, n;
535 n=extl_table_get_n(tab);
536 *n_entries=n;
538 if(n<=0)
539 return NULL;
541 entries=ALLOC_N(WMenuEntry, n);
543 if(entries==NULL)
544 return NULL;
546 init_attr();
548 /* Initialise entries and check submenus */
549 for(i=1; i<=n; i++){
550 WMenuEntry *ent=&entries[i-1];
552 ent->title=NULL;
553 ent->flags=0;
555 gr_stylespec_init(&ent->attr);
557 if(extl_table_geti_t(tab, i, &entry)){
558 char *attr;
559 ExtlTab sub;
560 ExtlFn fn;
562 if(extl_table_gets_s(entry, "attr", &attr)){
563 gr_stylespec_load_(&ent->attr, attr, TRUE);
564 free(attr);
567 if(extl_table_gets_f(entry, "submenu_fn", &fn)){
568 ent->flags|=WMENUENTRY_SUBMENU;
569 extl_unref_fn(fn);
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);
582 return entries;
586 static void deinit_entries(WMenu *menu);
589 bool menu_init(WMenu *menu, WWindow *par, const WFitParams *fp,
590 const WMenuCreateParams *params)
592 Window win;
593 int i;
595 menu->entries=preprocess_menu(params->tab, &(menu->n_entries));
597 if(menu->entries==NULL){
598 warn(TR("Empty menu."));
599 return FALSE;
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;*/
608 menu->last_fp=*fp;
610 if(params->pmenu_mode){
611 menu->selected_entry=-1;
612 }else{
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;
620 menu->max_entry_w=0;
621 menu->entry_h=0;
622 menu->brush=NULL;
623 menu->entry_brush=NULL;
624 menu->entry_spacing=0;
625 menu->vis_entries=menu->n_entries;
626 menu->first_entry=0;
627 menu->submenu=NULL;
628 menu->typeahead=NULL;
630 menu->gm_kcb=0;
631 menu->gm_state=0;
633 if(!window_init((WWindow*)menu, par, fp, "WMenu"))
634 goto fail;
636 win=menu->win.win;
638 if(!menu_init_gr(menu, region_rootwin_of((WRegion*)par), win))
639 goto fail2;
641 init_attr();
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);
651 return TRUE;
653 fail2:
654 window_deinit((WWindow*)menu);
655 fail:
656 extl_unref_table(menu->tab);
657 extl_unref_fn(menu->handler);
658 deinit_entries(menu);
659 return FALSE;
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)
672 int i;
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);
680 free(menu->entries);
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);
705 /*}}}*/
708 /*{{{ Focus */
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);
723 /*}}}*/
726 /*{{{ Submenus */
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)
747 return;
749 menu->submenu=NULL;
751 region_unset_manager(sub, (WRegion*)menu);
753 if(mcf)
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)
767 WFitParams fp;
768 WMenuCreateParams fnp;
769 WMenu *submenu;
770 WWindow *par;
772 par=REGION_PARENT(menu);
774 if(par==NULL)
775 return;
777 fp=menu->last_fp;
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);
786 fnp.refg.w=0;
787 fnp.refg.h=0;
788 }else{
789 fnp.refg=REGION_GEOM(menu);
792 fnp.tab=extl_table_none();
794 ExtlFn fn;
795 if(extl_table_getis(menu->tab, n+1, "submenu_fn", 'f', &fn)){
796 extl_protect(NULL);
797 extl_call(fn, NULL, "t", &(fnp.tab));
798 extl_unprotect(NULL);
799 extl_unref_fn(fn);
800 }else{
801 extl_table_getis(menu->tab, n+1, "submenu", 't', &(fnp.tab));
803 if(fnp.tab==extl_table_none())
804 return;
807 fnp.handler=extl_ref_fn(menu->handler);
809 fnp.initial=0;
811 ExtlFn fn;
812 if(extl_table_getis(menu->tab, n+1, "initial", 'f', &fn)){
813 extl_protect(NULL);
814 extl_call(fn, NULL, "i", &(fnp.initial));
815 extl_unprotect(NULL);
816 extl_unref_fn(fn);
817 }else{
818 extl_table_getis(menu->tab, n+1, "initial", 'i', &(fnp.initial));
822 submenu=create_menu(par, &fp, &fnp);
824 if(submenu==NULL)
825 return;
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);
842 else
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)
857 *topret=None;
859 if(menu->submenu!=NULL)
860 region_stacking((WRegion*)(menu->submenu), bottomret, topret);
862 *bottomret=MENU_WIN(menu);
863 if(*topret==None)
864 *topret=MENU_WIN(menu);
869 /*}}}*/
872 /*{{{ Exports */
875 static void menu_do_select_nth(WMenu *menu, int n)
877 int oldn=menu->selected_entry;
878 bool drawfull=FALSE;
880 if(oldn==n)
881 return;
883 if(menu->submenu!=NULL)
884 destroy_obj((Obj*)menu->submenu);
886 assert(menu->submenu==NULL);
888 menu->selected_entry=n;
890 if(n>=0){
891 if(n<menu->first_entry){
892 menu->first_entry=n;
893 drawfull=TRUE;
894 }else if(n>=menu->first_entry+menu->vis_entries){
895 menu->first_entry=n-menu->vis_entries+1;
896 drawfull=TRUE;
899 if(menu->entries[n].flags&WMENUENTRY_SUBMENU &&
900 menu->pmenu_mode){
901 show_sub(menu, n);
905 if(drawfull){
906 menu_draw_entries(menu, TRUE);
907 }else{
908 /* redraw new and old selected entry */
909 WRectangle igeom;
910 get_inner_geom(menu, &igeom);
912 /* !!!BEGIN!!! */
913 if(oldn!=-1)
914 menu_draw_entry(menu, oldn, &igeom, TRUE);
915 if(n!=-1)
916 menu_draw_entry(menu, n, &igeom, TRUE);
921 /*EXTL_DOC
922 * Select \var{n}:th entry in menu.
924 EXTL_EXPORT_MEMBER
925 void menu_select_nth(WMenu *menu, int n)
927 if(n<0)
928 n=0;
929 if(n>=menu->n_entries)
930 n=menu->n_entries-1;
932 menu_typeahead_clear(menu);
933 menu_do_select_nth(menu, n);
937 /*EXTL_DOC
938 * Select previous entry in menu.
940 EXTL_EXPORT_MEMBER
941 void menu_select_prev(WMenu *menu)
943 menu_select_nth(menu, (menu->selected_entry<=0
944 ? menu->n_entries-1
945 : menu->selected_entry-1));
949 /*EXTL_DOC
950 * Select next entry in menu.
952 EXTL_EXPORT_MEMBER
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)
961 ExtlFn handler;
962 ExtlTab tab;
963 bool ok;
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);
976 if(ok)
977 extl_call(handler, "t", NULL, tab);
979 extl_unref_fn(handler);
980 extl_unref_table(tab);
984 /*EXTL_DOC
985 * If selected entry is a submenu, display that.
986 * Otherwise destroy the menu and call handler for selected entry.
988 EXTL_EXPORT_MEMBER
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);
996 return;
999 mainloop_defer_action((Obj*)menu, (WDeferredAction*)menu_do_finish);
1004 /*EXTL_DOC
1005 * Close \var{menu} not calling any possible finish handlers.
1007 EXTL_EXPORT_MEMBER
1008 void menu_cancel(WMenu *menu)
1010 region_defer_rqdispose((WRegion*)menu);
1014 /*}}}*/
1017 /*{{{ Scroll */
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);
1028 scroll_timer=NULL;
1033 /*EXTL_DOC
1034 * Set module basic settings. The parameter table may contain the
1035 * following fields:
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
1044 * milliseconds.
1045 * \end{tabularx}
1047 EXTL_EXPORT
1048 void mod_menu_set(ExtlTab tab)
1050 int a, t;
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);
1059 /*EXTL_DOC
1060 * Get module basic settings. For details, see \fnref{mod_menu.set}.
1062 EXTL_SAFE
1063 EXTL_EXPORT
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);
1069 return tab;
1073 enum{
1074 D_LEFT,
1075 D_RIGHT,
1076 D_DOWN,
1077 D_UP
1081 static int calc_diff(const WRectangle *mg, const WRectangle *pg, int d)
1083 switch(d){
1084 case D_LEFT:
1085 return mg->x+mg->w-pg->w;
1086 case D_UP:
1087 return mg->y+mg->h-pg->h;
1088 case D_RIGHT:
1089 return -mg->x;
1090 case D_DOWN:
1091 return -mg->y;
1093 return 0;
1097 static int scrolld_subs(WMenu *menu, int d)
1099 int diff=0;
1100 WRegion *p=REGION_PARENT_REG(menu);
1101 const WRectangle *pg;
1103 if(p==NULL)
1104 return 0;
1106 pg=&REGION_GEOM(p);
1108 while(menu!=NULL){
1109 diff=maxof(diff, calc_diff(&REGION_GEOM(menu), pg, d));
1110 menu=menu->submenu;
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)
1122 WRectangle g;
1123 int px=-1, py=-1;
1125 xwindow_pointer_pos(region_root_of((WRegion*)menu), &px, &py);
1127 while(menu!=NULL){
1128 g=REGION_GEOM(menu);
1129 g.x+=xd;
1130 g.y+=yd;
1132 window_do_fitrep((WWindow*)menu, NULL, &g);
1134 menu_select_entry_at(menu, px, py);
1136 menu=menu->submenu;
1141 static void scroll_left(WTimer *timer, WMenu *menu)
1143 if(menu!=NULL){
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,
1147 (Obj*)menu);
1148 return;
1154 static void scroll_up(WTimer *timer, WMenu *menu)
1156 if(menu!=NULL){
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,
1160 (Obj*)menu);
1162 return;
1168 static void scroll_right(WTimer *timer, WMenu *menu)
1170 if(menu!=NULL){
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,
1174 (Obj*)menu);
1175 return;
1181 static void scroll_down(WTimer *timer, WMenu *menu)
1183 if(menu!=NULL){
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,
1187 (Obj*)menu);
1188 return;
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);
1204 int rx, ry;
1205 WTimerHandler *fn=NULL;
1207 if(!menu->pmenu_mode)
1208 return;
1210 if(parent==NULL){
1211 end_scroll(menu);
1212 return;
1215 region_rootpos(parent, &rx, &ry);
1216 x-=rx;
1217 y-=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;
1227 }else{
1228 end_scroll(menu);
1229 return;
1232 assert(fn!=NULL);
1234 if(scroll_timer!=NULL){
1235 if(scroll_timer->handler==(WTimerHandler*)fn &&
1236 timer_is_set(scroll_timer)){
1237 return;
1239 }else{
1240 scroll_timer=create_timer();
1241 if(scroll_timer==NULL)
1242 return;
1245 fn(scroll_timer, (Obj*)menu_head(menu));
1249 /*}}}*/
1252 /*{{{ Pointer handlers */
1255 int menu_entry_at_root(WMenu *menu, int root_x, int root_y)
1257 int rx, ry, x, y, entry;
1258 WRectangle ig;
1259 region_rootpos((WRegion*)menu, &rx, &ry);
1261 get_inner_geom(menu, &ig);
1263 x=root_x-rx-ig.x;
1264 y=root_y-ry-ig.y;
1266 if(x<0 || x>=ig.w || y<0 || y>=ig.h)
1267 return -1;
1269 entry=y/(menu->entry_h+menu->entry_spacing);
1270 if(entry<0 || entry>=menu->vis_entries)
1271 return -1;
1272 return entry+menu->first_entry;
1276 int menu_entry_at_root_tree(WMenu *menu, int root_x, int root_y,
1277 WMenu **realmenu)
1279 int entry=-1;
1281 menu=menu_tail(menu);
1283 *realmenu=menu;
1285 if(!menu->pmenu_mode)
1286 return menu_entry_at_root(menu, root_x, root_y);
1288 while(menu!=NULL){
1289 entry=menu_entry_at_root(menu, root_x, root_y);
1290 if(entry>=0){
1291 *realmenu=menu;
1292 break;
1294 menu=REGION_MANAGER_CHK(menu, WMenu);
1297 return entry;
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);
1304 if(entry>=0)
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);
1312 end_scroll(menu);
1313 if(entry>=0){
1314 menu_select_nth(menu, entry);
1315 menu_finish(menu);
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);
1332 if(entry>=0)
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,
1342 NULL,
1343 (WMotionHandler*)menu_motion,
1344 (WButtonHandler*)menu_release,
1345 NULL,
1346 NULL);
1347 return 0;
1351 /*}}}*/
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);
1361 char *newta_orig;
1362 int entry;
1364 if(newta==NULL)
1365 return;
1367 if(oldlen!=0)
1368 memcpy(newta, menu->typeahead, oldlen);
1369 if(n!=0)
1370 memcpy(newta+oldlen, buf, n);
1371 newta[oldlen+n]='\0';
1372 newta_orig=newta;
1374 while(*newta!='\0'){
1375 bool found=FALSE;
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)){
1381 found=TRUE;
1382 break;
1385 entry=(entry+1)%menu->n_entries;
1386 }while(entry!=menu->selected_entry);
1387 if(found){
1388 menu_do_select_nth(menu, entry);
1389 break;
1391 newta++;
1394 if(newta_orig!=newta){
1395 if(*newta=='\0'){
1396 free(newta_orig);
1397 newta=NULL;
1398 }else{
1399 char *p=scopy(newta);
1400 free(newta_orig);
1401 newta=p;
1404 if(menu->typeahead!=NULL)
1405 free(menu->typeahead);
1406 menu->typeahead=newta;
1410 /*EXTL_DOC
1411 * Clear typeahead buffer.
1413 EXTL_EXPORT_MEMBER
1414 void menu_typeahead_clear(WMenu *menu)
1416 if(menu->typeahead!=NULL){
1417 free(menu->typeahead);
1418 menu->typeahead=NULL;
1423 /*}}}*/
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},
1442 END_DYNFUNTAB
1446 EXTL_EXPORT
1447 IMPLCLASS(WMenu, WWindow, menu_deinit, menu_dynfuntab);
1450 /*}}}*/