]> git.decadent.org.uk Git - ion3.git/blob - mod_menu/menu.c
Imported Upstream version 20090110
[ion3.git] / mod_menu / menu.c
1 /*
2  * ion/mod_menu/menu.c
3  *
4  * Copyright (c) Tuomo Valkonen 1999-2009. 
5  *
6  * See the included file LICENSE for details.
7  */
8
9 #include <string.h>
10 #include <limits.h>
11
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>
17
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"
34
35
36 #define MENU_WIN(MENU) ((MENU)->win.win)
37
38
39 /*{{{ Helpers */
40
41
42 static bool extl_table_getis(ExtlTab tab, int i, const char *s, char c,
43                              void *p)
44 {
45     ExtlTab sub;
46     bool ret;
47     
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;
53 }
54
55
56 /*}}}*/
57
58
59 /*{{{ Drawing routines */
60
61
62 static void get_outer_geom(WMenu *menu, WRectangle *geom)
63 {
64     geom->x=0;
65     geom->y=0;
66     geom->w=REGION_GEOM(menu).w;
67     geom->h=REGION_GEOM(menu).h;
68 }
69
70
71 static void get_inner_geom(WMenu *menu, WRectangle *geom)
72 {
73     GrBorderWidths bdw;
74     
75     get_outer_geom(menu, geom);
76     
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);
85     }
86 }
87
88
89 GR_DEFATTR(active);
90 GR_DEFATTR(inactive);
91 GR_DEFATTR(selected);
92 GR_DEFATTR(unselected);
93 GR_DEFATTR(normal);
94 GR_DEFATTR(submenu);
95
96
97 static void init_attr()
98 {
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;
107 }
108
109
110 static void menu_draw_entry(WMenu *menu, int i, const WRectangle *igeom,
111                             bool complete)
112 {
113     WRectangle geom;
114     GrAttr sa, aa;
115
116     aa=(REGION_IS_ACTIVE(menu) ? GR_ATTR(active) : GR_ATTR(inactive));
117     sa=(menu->selected_entry==i ? GR_ATTR(selected) : GR_ATTR(unselected));
118     
119     if(menu->entry_brush==NULL)
120         return;
121     
122     geom=*igeom;
123     geom.h=menu->entry_h;
124     geom.y+=(i-menu->first_entry)*(menu->entry_h+menu->entry_spacing);
125     
126     grbrush_begin(menu->entry_brush, &geom, GRBRUSH_AMEND|GRBRUSH_KEEP_ATTR);
127     
128     grbrush_init_attr(menu->entry_brush, &menu->entries[i].attr);
129     
130     grbrush_set_attr(menu->entry_brush, aa);
131     grbrush_set_attr(menu->entry_brush, sa);
132
133     grbrush_draw_textbox(menu->entry_brush, &geom, menu->entries[i].title, 
134                          complete);
135     
136     grbrush_end(menu->entry_brush);
137 }
138
139     
140 void menu_draw_entries(WMenu *menu, bool complete)
141 {
142     WRectangle igeom;
143     int i, mx;
144     
145     if(menu->entry_brush==NULL)
146         return;
147         
148     get_inner_geom(menu, &igeom);
149     
150     mx=menu->first_entry+menu->vis_entries;
151     mx=(mx < menu->n_entries ? mx : menu->n_entries);
152     
153     for(i=menu->first_entry; i<mx; i++)
154         menu_draw_entry(menu, i, &igeom, complete);
155 }
156
157
158 void menu_draw(WMenu *menu, bool complete)
159 {
160     GrAttr aa=(REGION_IS_ACTIVE(menu) ? GR_ATTR(active) : GR_ATTR(inactive));
161     WRectangle geom;
162     
163     if(menu->brush==NULL)
164         return;
165     
166     get_outer_geom(menu, &geom);
167     
168     grbrush_begin(menu->brush, &geom, 
169                   (complete ? 0 : GRBRUSH_NO_CLEAR_OK));
170     
171     grbrush_set_attr(menu->brush, aa);
172     
173     grbrush_draw_border(menu->brush, &geom);
174     
175     menu_draw_entries(menu, FALSE);
176     
177     grbrush_end(menu->brush);
178 }
179
180
181 /*}}}*/
182
183
184 /*{{{ Resize */
185
186
187 static void menu_calc_size(WMenu *menu, bool maxexact, 
188                            int maxw, int maxh, 
189                            int *w_ret, int *h_ret)
190 {
191     GrBorderWidths bdw, e_bdw;
192     char *str;
193     int i;
194     int nath, bdh, maxew=menu->max_entry_w;
195     
196     grbrush_get_border_widths(menu->brush, &bdw);
197     grbrush_get_border_widths(menu->entry_brush, &e_bdw);
198     
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;
204     }
205     
206     bdh=bdw.top+bdw.bottom;
207     
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;
222         }
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;
230     }
231
232     /* Calculate new shortened entry names */
233     maxew-=e_bdw.left+e_bdw.right;
234     
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;
239         }
240         if(maxew<=0)
241             continue;
242         
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);
247         }
248     }
249 }
250
251
252 void calc_size(WMenu *menu, int *w, int *h)
253 {
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);
259     }
260 }
261     
262
263 /* Return offset from bottom-left corner of containing mplex or top-right
264  * corner of  parent menu for the respective corner of menu.
265  */
266 static void get_placement_offs(WMenu *menu, int *xoff, int *yoff)
267 {
268     GrBorderWidths bdw;
269     
270     *xoff=0; 
271     *yoff=0;
272     
273     if(menu->brush!=NULL){
274         grbrush_get_border_widths(menu->brush, &bdw);
275         *xoff+=bdw.right;
276         *yoff+=bdw.top;
277     }
278     
279     if(menu->entry_brush!=NULL){
280         grbrush_get_border_widths(menu->entry_brush, &bdw);
281         *xoff+=bdw.right;
282         *yoff+=bdw.top;
283     }
284 }
285     
286     
287 #define MINIMUM_Y_VISIBILITY 20
288 #define POINTER_OFFSET 5
289
290 static void menu_firstfit(WMenu *menu, bool submenu, const WRectangle *refg)
291 {
292     WRectangle geom;
293     
294     calc_size(menu, &(geom.w), &(geom.h));
295     
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;
302         
303         if(!submenu){
304             const WRectangle *maxg = 
305                 &REGION_GEOM(REGION_PARENT((WRegion*)menu));
306             
307             geom.x-=geom.w/2;
308             geom.y+=POINTER_OFFSET;
309
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;
320             }
321         }
322     }else{
323         const WRectangle *maxg=&(menu->last_fp.g);
324         if(submenu){
325             int l, r, t, b, xoff, yoff;
326             
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;
332             
333             geom.x=maxof(l, r-geom.w);
334             if(geom.x+geom.w>maxg->x+maxg->w)
335                 geom.x=maxg->x;
336
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;
343         }
344     }
345     
346     window_do_fitrep(&menu->win, NULL, &geom);
347 }
348
349
350 static void menu_do_refit(WMenu *menu, WWindow *par, const WFitParams *oldfp)
351 {
352     WRectangle geom;
353     
354     calc_size(menu, &(geom.w), &(geom.h));
355     
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);
369     }
370     
371     window_do_fitrep(&menu->win, par, &geom);
372 }
373
374
375 bool menu_fitrep(WMenu *menu, WWindow *par, const WFitParams *fp)
376 {
377     WFitParams oldfp;
378     
379     if(par!=NULL && !region_same_rootwin((WRegion*)par, (WRegion*)menu))
380         return FALSE;
381     
382     oldfp=menu->last_fp;
383     menu->last_fp=*fp;
384     menu_do_refit(menu, par, &oldfp);
385     
386     if(menu->submenu!=NULL && !menu->pmenu_mode)
387         region_fitrep((WRegion*)(menu->submenu), par, fp);
388     
389     return TRUE;
390 }
391
392
393 void menu_size_hints(WMenu *menu, WSizeHints *hints_ret)
394 {
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);
398
399     if(menu->brush!=NULL){
400         GrBorderWidths bdw;
401         grbrush_get_border_widths(menu->brush, &bdw);
402         
403         w+=bdw.left+bdw.right;
404         h+=bdw.top+bdw.bottom;
405     }
406     
407     hints_ret->min_set=TRUE;
408     hints_ret->min_width=w;
409     hints_ret->min_height=h;
410 }
411
412
413 /*}}}*/
414
415
416 /*{{{ Brush update */
417
418
419 static void calc_entry_dimens(WMenu *menu)
420 {
421     int i, n=extl_table_get_n(menu->tab);
422     GrFontExtents fnte;
423     GrBorderWidths bdw;
424     int maxw=0;
425     char *str;
426     
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);
431     }
432 #endif
433     
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);
441         }
442     }
443     
444     grbrush_get_border_widths(menu->entry_brush, &bdw);
445     grbrush_get_font_extents(menu->entry_brush, &fnte);
446     
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;
450 }
451
452
453 static bool menu_init_gr(WMenu *menu, WRootWin *rootwin, Window win)
454 {
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"));
462
463     const char *entry_style=(menu->big_mode
464                                    ? "tab-menuentry-big"
465                                    : (menu->pmenu_mode
466                                       ? "tab-menuentry-pmenu"
467                                       : "tab-menuentry-normal"));
468     
469     brush=gr_get_brush(win, rootwin, style);
470     
471     if(brush==NULL)
472         return FALSE;
473
474     entry_brush=grbrush_get_slave(brush, rootwin, entry_style);
475     
476     if(entry_brush==NULL){
477         grbrush_release(brush);
478         return FALSE;
479     }
480     
481     if(menu->entry_brush!=NULL)
482         grbrush_release(menu->entry_brush);
483     if(menu->brush!=NULL)
484         grbrush_release(menu->brush);
485
486     menu->brush=brush;
487     menu->entry_brush=entry_brush;
488     
489     calc_entry_dimens(menu);
490     
491     return TRUE;
492 }
493
494
495 void menu_updategr(WMenu *menu)
496 {
497     if(!menu_init_gr(menu, region_rootwin_of((WRegion*)menu),
498                      MENU_WIN(menu))){
499         return;
500     }
501     
502     menu_do_refit(menu, NULL, &(menu->last_fp));
503     
504     region_updategr_default((WRegion*)menu);
505     
506     window_draw((WWindow*)menu, TRUE);
507 }
508
509
510 static void menu_release_gr(WMenu *menu)
511 {
512     if(menu->entry_brush!=NULL){
513         grbrush_release(menu->entry_brush);
514         menu->entry_brush=NULL;
515     }
516     if(menu->brush!=NULL){
517         grbrush_release(menu->brush);
518         menu->brush=NULL;
519     }
520 }
521
522
523 /*}}}*/
524
525
526 /*{{{ Init/deinit */
527
528
529 static WMenuEntry *preprocess_menu(ExtlTab tab, int *n_entries)
530 {
531     WMenuEntry *entries;
532     ExtlTab entry;
533     int i, n;
534     
535     n=extl_table_get_n(tab);
536     *n_entries=n;
537     
538     if(n<=0)
539         return NULL;
540
541     entries=ALLOC_N(WMenuEntry, n);  
542     
543     if(entries==NULL)
544         return NULL;
545         
546     init_attr();
547     
548     /* Initialise entries and check submenus */
549     for(i=1; i<=n; i++){
550         WMenuEntry *ent=&entries[i-1];
551         
552         ent->title=NULL;
553         ent->flags=0;
554         
555         gr_stylespec_init(&ent->attr);
556         
557         if(extl_table_geti_t(tab, i, &entry)){
558             char *attr;
559             ExtlTab sub;
560             ExtlFn fn;
561             
562             if(extl_table_gets_s(entry, "attr", &attr)){
563                 gr_stylespec_load_(&ent->attr, attr, TRUE);
564                 free(attr);
565             }
566             
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);
573             }
574             
575             if(ent->flags&WMENUENTRY_SUBMENU)
576                 gr_stylespec_set(&ent->attr, GR_ATTR(submenu));
577             
578             extl_unref_table(entry);
579         }
580     }
581     
582     return entries;
583 }
584
585
586 static void deinit_entries(WMenu *menu);
587
588
589 bool menu_init(WMenu *menu, WWindow *par, const WFitParams *fp,
590                const WMenuCreateParams *params)
591 {
592     Window win;
593     int i;
594     
595     menu->entries=preprocess_menu(params->tab, &(menu->n_entries));
596     
597     if(menu->entries==NULL){
598         warn(TR("Empty menu."));
599         return FALSE;
600     }
601
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;*/
607     
608     menu->last_fp=*fp;
609     
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;
618     }
619     
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;
629     
630     menu->gm_kcb=0;
631     menu->gm_state=0;
632     
633     if(!window_init((WWindow*)menu, par, fp))
634         goto fail;
635
636     win=menu->win.win;
637     
638     if(!menu_init_gr(menu, region_rootwin_of((WRegion*)par), win))
639         goto fail2;
640         
641     init_attr();
642     
643     menu_firstfit(menu, params->submenu_mode, &(params->refg));
644     
645     window_select_input(&(menu->win), IONCORE_EVENTMASK_NORMAL);
646     
647     region_add_bindmap((WRegion*)menu, mod_menu_menu_bindmap);
648     
649     region_register((WRegion*)menu);
650     
651     return TRUE;
652
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;
660 }
661
662
663 WMenu *create_menu(WWindow *par, const WFitParams *fp, 
664                    const WMenuCreateParams *params)
665 {
666     CREATEOBJ_IMPL(WMenu, menu, (p, par, fp, params));
667 }
668
669
670 static void deinit_entries(WMenu *menu)
671 {
672     int i;
673     
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);
678     }
679     
680     free(menu->entries);
681 }
682
683
684 void menu_deinit(WMenu *menu)
685 {
686     menu_typeahead_clear(menu);
687     
688     if(menu->submenu!=NULL)
689         destroy_obj((Obj*)menu->submenu);
690     
691     /*if(menu->cycle_bindmap!=NULL)
692         bindmap_destroy(menu->cycle_bindmap);*/
693
694     extl_unref_table(menu->tab);
695     extl_unref_fn(menu->handler);
696     
697     deinit_entries(menu);
698     
699     menu_release_gr(menu);
700     
701     window_deinit((WWindow*)menu);
702 }
703
704
705 /*}}}*/
706
707
708 /*{{{ Focus  */
709
710
711 static void menu_inactivated(WMenu *menu)
712 {
713     window_draw((WWindow*)menu, FALSE);
714 }
715
716
717 static void menu_activated(WMenu *menu)
718 {
719     window_draw((WWindow*)menu, FALSE);
720 }
721
722
723 /*}}}*/
724
725
726 /*{{{ Submenus */
727
728
729 static WMenu *menu_head(WMenu *menu)
730 {
731     WMenu *m=REGION_MANAGER_CHK(menu, WMenu);
732     return (m==NULL ? menu : menu_head(m));
733 }
734
735
736 static WMenu *menu_tail(WMenu *menu)
737 {
738     return (menu->submenu==NULL ? menu : menu_tail(menu->submenu));
739 }
740
741
742 static void menu_managed_remove(WMenu *menu, WRegion *sub)
743 {
744     bool mcf=region_may_control_focus((WRegion*)menu);
745     
746     if(sub!=(WRegion*)menu->submenu)
747         return;
748     
749     menu->submenu=NULL;
750
751     region_unset_manager(sub, (WRegion*)menu);
752     
753     if(mcf)
754         region_do_set_focus((WRegion*)menu, FALSE);
755 }
756
757
758 int get_sub_y_off(WMenu *menu, int n)
759 {
760     /* top + sum of above entries and spacings - top_of_sub */
761     return (menu->entry_h+menu->entry_spacing)*(n-menu->first_entry);
762 }
763
764
765 static void show_sub(WMenu *menu, int n)
766 {
767     WFitParams fp;
768     WMenuCreateParams fnp;
769     WMenu *submenu;
770     WWindow *par;
771     
772     par=REGION_PARENT(menu);
773     
774     if(par==NULL)
775         return;
776     
777     fp=menu->last_fp;
778
779     fnp.pmenu_mode=menu->pmenu_mode;
780     fnp.big_mode=menu->big_mode;
781     fnp.submenu_mode=TRUE;
782     
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);
790     }
791
792     fnp.tab=extl_table_none();
793     {
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));
802         }
803         if(fnp.tab==extl_table_none())
804             return;
805     }
806     
807     fnp.handler=extl_ref_fn(menu->handler);
808     
809     fnp.initial=0;
810     {
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));
819         }
820     }
821
822     submenu=create_menu(par, &fp, &fnp);
823     
824     if(submenu==NULL)
825         return;
826     
827     menu->submenu=submenu;
828     region_set_manager((WRegion*)submenu, (WRegion*)menu);
829         
830     region_restack((WRegion*)submenu, MENU_WIN(menu), Above);
831     region_map((WRegion*)submenu);
832     
833     if(!menu->pmenu_mode && region_may_control_focus((WRegion*)menu))
834         region_do_set_focus((WRegion*)submenu, FALSE);
835 }
836
837
838 static void menu_do_set_focus(WMenu *menu, bool warp)
839 {
840     if(menu->submenu!=NULL)
841         region_do_set_focus((WRegion*)menu->submenu, warp);
842     else
843         window_do_set_focus((WWindow*)menu, warp);
844 }
845
846
847 void menu_restack(WMenu *menu, Window other, int mode)
848 {
849     xwindow_restack(MENU_WIN(menu), other, mode);
850     if(menu->submenu!=NULL)
851         region_restack((WRegion*)(menu->submenu), MENU_WIN(menu), Above);
852 }
853
854
855 void menu_stacking(WMenu *menu, Window *bottomret, Window *topret)
856 {
857     *topret=None;
858     
859     if(menu->submenu!=NULL)
860         region_stacking((WRegion*)(menu->submenu), bottomret, topret);
861     
862     *bottomret=MENU_WIN(menu);
863     if(*topret==None)
864         *topret=MENU_WIN(menu);
865
866 }
867
868     
869 /*}}}*/
870
871
872 /*{{{ Exports */
873
874
875 static void menu_do_select_nth(WMenu *menu, int n)
876 {
877     int oldn=menu->selected_entry;
878     bool drawfull=FALSE;
879     
880     if(oldn==n)
881         return;
882     
883     if(menu->submenu!=NULL)
884         destroy_obj((Obj*)menu->submenu);
885     
886     assert(menu->submenu==NULL);
887
888     menu->selected_entry=n;
889
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;
897         }
898         
899         if(menu->entries[n].flags&WMENUENTRY_SUBMENU &&
900            menu->pmenu_mode){
901             show_sub(menu, n);
902         }
903     }
904     
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);
911
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);
917     }
918 }
919
920
921 /*EXTL_DOC
922  * Select \var{n}:th entry in menu.
923  */
924 EXTL_EXPORT_MEMBER
925 void menu_select_nth(WMenu *menu, int n)
926 {
927     if(n<0)
928         n=0;
929     if(n>=menu->n_entries)
930         n=menu->n_entries-1;
931
932     menu_typeahead_clear(menu);
933     menu_do_select_nth(menu, n);
934 }
935
936
937 /*EXTL_DOC
938  * Select previous entry in menu.
939  */
940 EXTL_EXPORT_MEMBER
941 void menu_select_prev(WMenu *menu)
942 {
943     menu_select_nth(menu, (menu->selected_entry<=0 
944                            ? menu->n_entries-1
945                            : menu->selected_entry-1));
946 }
947
948
949 /*EXTL_DOC
950  * Select next entry in menu.
951  */
952 EXTL_EXPORT_MEMBER
953 void menu_select_next(WMenu *menu)
954 {
955     menu_select_nth(menu, (menu->selected_entry+1)%menu->n_entries);
956 }
957
958
959 static void menu_do_finish(WMenu *menu)
960 {
961     ExtlFn handler;
962     ExtlTab tab;
963     bool ok;
964     WMenu *head=menu_head(menu);
965     
966     handler=menu->handler;
967     menu->handler=extl_fn_none();
968     
969     ok=extl_table_geti_t(menu->tab, menu->selected_entry+1, &tab);
970     
971     if(!region_rqdispose((WRegion*)head)){
972         if(head->submenu!=NULL)
973             destroy_obj((Obj*)head->submenu);
974     }
975     
976     if(ok)
977         extl_call(handler, "t", NULL, tab);
978
979     extl_unref_fn(handler);
980     extl_unref_table(tab);
981 }
982
983
984 /*EXTL_DOC
985  * If selected entry is a submenu, display that.
986  * Otherwise destroy the menu and call handler for selected entry.
987  */
988 EXTL_EXPORT_MEMBER
989 void menu_finish(WMenu *menu)
990 {
991     menu_typeahead_clear(menu);
992
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;
997     }
998     
999     mainloop_defer_action((Obj*)menu, (WDeferredAction*)menu_do_finish);
1000 }
1001
1002
1003
1004 /*EXTL_DOC
1005  * Close \var{menu} not calling any possible finish handlers.
1006  */
1007 EXTL_EXPORT_MEMBER
1008 void menu_cancel(WMenu *menu)
1009 {
1010     region_defer_rqdispose((WRegion*)menu);
1011 }
1012
1013
1014 /*}}}*/
1015
1016
1017 /*{{{ Scroll */
1018
1019 static int scroll_time=20;
1020 static int scroll_amount=3;
1021 static WTimer *scroll_timer=NULL;
1022
1023
1024 static void reset_scroll_timer()
1025 {
1026     if(scroll_timer!=NULL){
1027         destroy_obj((Obj*)scroll_timer);
1028         scroll_timer=NULL;
1029     }
1030 }
1031
1032
1033 /*EXTL_DOC
1034  * Set module basic settings. The parameter table may contain the
1035  * following fields:
1036  * 
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}
1046  */
1047 EXTL_EXPORT
1048 void mod_menu_set(ExtlTab tab)
1049 {
1050     int a, t;
1051     
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);
1056 }
1057
1058
1059 /*EXTL_DOC
1060  * Get module basic settings. For details, see \fnref{mod_menu.set}.
1061  */
1062 EXTL_SAFE
1063 EXTL_EXPORT
1064 ExtlTab mod_menu_get()
1065 {
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;
1070 }
1071
1072
1073 enum{
1074     D_LEFT,
1075     D_RIGHT,
1076     D_DOWN,
1077     D_UP
1078 };
1079
1080
1081 static int calc_diff(const WRectangle *mg, const WRectangle *pg, int d)
1082 {
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;
1092     }
1093     return 0;
1094 }
1095
1096
1097 static int scrolld_subs(WMenu *menu, int d)
1098 {
1099     int diff=0;
1100     WRegion *p=REGION_PARENT_REG(menu);
1101     const WRectangle *pg;
1102     
1103     if(p==NULL)
1104         return 0;
1105
1106     pg=&REGION_GEOM(p);
1107     
1108     while(menu!=NULL){
1109         diff=maxof(diff, calc_diff(&REGION_GEOM(menu), pg, d));
1110         menu=menu->submenu;
1111     }
1112     
1113     return minof(maxof(0, diff), scroll_amount);
1114 }
1115
1116
1117 static void menu_select_entry_at(WMenu *menu, int px, int py);
1118
1119
1120 static void do_scroll(WMenu *menu, int xd, int yd)
1121 {
1122     WRectangle g;
1123     int px=-1, py=-1;
1124     
1125     xwindow_pointer_pos(region_root_of((WRegion*)menu), &px, &py);
1126
1127     while(menu!=NULL){
1128         g=REGION_GEOM(menu);
1129         g.x+=xd;
1130         g.y+=yd;
1131         
1132         window_do_fitrep((WWindow*)menu, NULL, &g);
1133         
1134         menu_select_entry_at(menu, px, py);
1135
1136         menu=menu->submenu;
1137     }
1138 }
1139
1140
1141 static void scroll_left(WTimer *timer, WMenu *menu)
1142 {
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;
1149         }
1150     }
1151 }
1152
1153
1154 static void scroll_up(WTimer *timer, WMenu *menu)
1155 {
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);
1161
1162             return;
1163         }
1164     }
1165 }
1166
1167
1168 static void scroll_right(WTimer *timer, WMenu *menu)
1169 {
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;
1176         }
1177     }
1178 }
1179
1180
1181 static void scroll_down(WTimer *timer, WMenu *menu)
1182 {
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;
1189         }
1190     }
1191 }
1192
1193
1194 static void end_scroll(WMenu *menu)
1195 {
1196     reset_scroll_timer();
1197 }
1198
1199 #define SCROLL_OFFSET 10
1200
1201 static void check_scroll(WMenu *menu, int x, int y)
1202 {
1203     WRegion *parent=REGION_PARENT_REG(menu);
1204     int rx, ry;
1205     WTimerHandler *fn=NULL;
1206
1207     if(!menu->pmenu_mode)
1208         return;
1209     
1210     if(parent==NULL){
1211         end_scroll(menu);
1212         return;
1213     }
1214
1215     region_rootpos(parent, &rx, &ry);
1216     x-=rx;
1217     y-=ry;
1218     
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;
1230     }
1231     
1232     assert(fn!=NULL);
1233     
1234     if(scroll_timer!=NULL){
1235         if(scroll_timer->handler==(WTimerHandler*)fn &&
1236            timer_is_set(scroll_timer)){
1237             return;
1238         }
1239     }else{
1240         scroll_timer=create_timer();
1241         if(scroll_timer==NULL)
1242             return;
1243     }
1244     
1245     fn(scroll_timer, (Obj*)menu_head(menu));
1246 }
1247
1248
1249 /*}}}*/
1250
1251
1252 /*{{{ Pointer handlers */
1253
1254
1255 int menu_entry_at_root(WMenu *menu, int root_x, int root_y)
1256 {
1257     int rx, ry, x, y, entry;
1258     WRectangle ig;
1259     region_rootpos((WRegion*)menu, &rx, &ry);
1260     
1261     get_inner_geom(menu, &ig);
1262     
1263     x=root_x-rx-ig.x;
1264     y=root_y-ry-ig.y;
1265     
1266     if(x<0 || x>=ig.w || y<0  || y>=ig.h)
1267         return -1;
1268     
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;
1273 }
1274
1275
1276 int menu_entry_at_root_tree(WMenu *menu, int root_x, int root_y, 
1277                             WMenu **realmenu)
1278 {
1279     int entry=-1;
1280     
1281     menu=menu_tail(menu);
1282     
1283     *realmenu=menu;
1284     
1285     if(!menu->pmenu_mode)
1286         return menu_entry_at_root(menu, root_x, root_y);
1287     
1288     while(menu!=NULL){
1289         entry=menu_entry_at_root(menu, root_x, root_y);
1290         if(entry>=0){
1291             *realmenu=menu;
1292             break;
1293         }
1294         menu=REGION_MANAGER_CHK(menu, WMenu);
1295     }
1296     
1297     return entry;
1298 }
1299
1300
1301 static void menu_select_entry_at(WMenu *menu, int px, int py)
1302 {
1303     int entry=menu_entry_at_root_tree(menu, px, py, &menu);
1304     if(entry>=0)
1305         menu_do_select_nth(menu, entry);
1306 }
1307
1308
1309 void menu_release(WMenu *menu, XButtonEvent *ev)
1310 {
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));
1318     }
1319 }
1320
1321
1322 void menu_motion(WMenu *menu, XMotionEvent *ev, int dx, int dy)
1323 {
1324     menu_select_entry_at(menu, ev->x_root, ev->y_root);
1325     check_scroll(menu, ev->x_root, ev->y_root);
1326 }
1327
1328
1329 void menu_button(WMenu *menu, XButtonEvent *ev)
1330 {
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);
1334 }
1335
1336
1337 int menu_press(WMenu *menu, XButtonEvent *ev, WRegion **reg_ret)
1338 {
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;
1348 }
1349
1350
1351 /*}}}*/
1352
1353
1354 /*{{{ Typeahead find */
1355
1356
1357 static void menu_insstr(WMenu *menu, const char *buf, size_t n)
1358 {
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;
1363     
1364     if(newta==NULL)
1365         return;
1366     
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;
1373     
1374     while(*newta!='\0'){
1375         bool found=FALSE;
1376         entry=menu->selected_entry;
1377         do{
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;
1383                 }
1384             }
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;
1390         }
1391         newta++;
1392     }
1393     
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;
1402         }
1403     }
1404     if(menu->typeahead!=NULL)
1405         free(menu->typeahead);
1406     menu->typeahead=newta;
1407 }
1408
1409
1410 /*EXTL_DOC
1411  * Clear typeahead buffer.
1412  */
1413 EXTL_EXPORT_MEMBER
1414 void menu_typeahead_clear(WMenu *menu)
1415 {
1416     if(menu->typeahead!=NULL){
1417         free(menu->typeahead);
1418         menu->typeahead=NULL;
1419     }
1420 }
1421
1422
1423 /*}}}*/
1424
1425
1426 /*{{{ Dynamic function table and class implementation */
1427
1428
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
1443 };
1444
1445
1446 EXTL_EXPORT
1447 IMPLCLASS(WMenu, WWindow, menu_deinit, menu_dynfuntab);
1448
1449     
1450 /*}}}*/
1451
1452