]> git.decadent.org.uk Git - ion3.git/blob - mod_menu/menu.c
[svn-inject] Installing original source of ion3
[ion3.git] / mod_menu / menu.c
1 /*
2  * ion/mod_menu/menu.c
3  *
4  * Copyright (c) Tuomo Valkonen 1999-2006. 
5  *
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.
10  */
11
12 #include <string.h>
13 #include <limits.h>
14
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>
20
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>
31 #include "menu.h"
32 #include "main.h"
33
34
35 #define MENU_WIN(MENU) ((MENU)->win.win)
36
37
38 /*{{{ Helpers */
39
40
41 static bool extl_table_getis(ExtlTab tab, int i, const char *s, char c,
42                              void *p)
43 {
44     ExtlTab sub;
45     bool ret;
46     
47     if(!extl_table_geti_t(tab, i, &sub))
48         return FALSE;
49     ret=extl_table_get(sub, 's', c, s, p);
50     extl_unref_table(sub);
51     return ret;
52 }
53
54
55 /*}}}*/
56
57
58 /*{{{ Drawing routines */
59
60
61 static void get_outer_geom(WMenu *menu, WRectangle *geom)
62 {
63     geom->x=0;
64     geom->y=0;
65     geom->w=REGION_GEOM(menu).w;
66     geom->h=REGION_GEOM(menu).h;
67 }
68
69
70 static void get_inner_geom(WMenu *menu, WRectangle *geom)
71 {
72     GrBorderWidths bdw;
73     
74     get_outer_geom(menu, geom);
75     
76     if(menu->brush!=NULL){
77         grbrush_get_border_widths(menu->brush, &bdw);
78         geom->x+=bdw.left;
79         geom->y+=bdw.top;
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);
84     }
85 }
86
87
88 static void menu_draw_entry(WMenu *menu, int i, const WRectangle *igeom,
89                             bool complete)
90 {
91     WRectangle geom;
92     int a;
93
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",
103     };
104     
105     if(menu->entry_brush==NULL)
106         return;
107     
108     geom=*igeom;
109     geom.h=menu->entry_h;
110     geom.y+=(i-menu->first_entry)*(menu->entry_h+menu->entry_spacing);
111     
112     a=((REGION_IS_ACTIVE(menu) ? 0 : 4)
113        |(menu->selected_entry==i ? 0 : 2)
114        |(menu->entries[i].flags&WMENUENTRY_SUBMENU ? 1 : 0));
115
116     grbrush_begin(menu->entry_brush, &geom, GRBRUSH_AMEND);
117
118     grbrush_draw_textbox(menu->entry_brush, &geom, menu->entries[i].title, 
119                          attrs[a], complete);
120     
121     grbrush_end(menu->entry_brush);
122 }
123
124     
125 void menu_draw_entries(WMenu *menu, bool complete)
126 {
127     WRectangle igeom;
128     int i, mx;
129
130     if(menu->entry_brush==NULL)
131         return;
132     
133     get_inner_geom(menu, &igeom);
134     
135     mx=menu->first_entry+menu->vis_entries;
136     mx=(mx < menu->n_entries ? mx : menu->n_entries);
137     
138     for(i=menu->first_entry; i<mx; i++)
139         menu_draw_entry(menu, i, &igeom, complete);
140 }
141
142
143 void menu_draw(WMenu *menu, bool complete)
144 {
145     WRectangle geom;
146     const char *substyle=(REGION_IS_ACTIVE(menu) ? "active" : "inactive");
147     
148     if(menu->brush==NULL)
149         return;
150     
151     get_outer_geom(menu, &geom);
152     
153     grbrush_begin(menu->brush, &geom, 
154                   (complete ? 0 : GRBRUSH_NO_CLEAR_OK));
155     
156     grbrush_draw_border(menu->brush, &geom, substyle);
157     
158     menu_draw_entries(menu, FALSE);
159     
160     grbrush_end(menu->brush);
161 }
162
163
164 /*}}}*/
165
166
167 /*{{{ Resize */
168
169
170 static void menu_calc_size(WMenu *menu, bool maxexact, 
171                            int maxw, int maxh, 
172                            int *w_ret, int *h_ret)
173 {
174     GrBorderWidths bdw, e_bdw;
175     char *str;
176     int i;
177     int nath, bdh, maxew=menu->max_entry_w;
178     
179     grbrush_get_border_widths(menu->brush, &bdw);
180     grbrush_get_border_widths(menu->entry_brush, &e_bdw);
181     
182     if(maxexact || maxew>maxw-(int)bdw.left-(int)bdw.right){
183         maxew=maxw-bdw.left-bdw.right;
184         *w_ret=maxw;
185     }else{
186         *w_ret=maxew+bdw.left+bdw.right;
187     }
188     
189     bdh=bdw.top+bdw.bottom;
190     
191     if(menu->n_entries==0){
192         *h_ret=(maxexact ? maxh : bdh);
193         menu->first_entry=0;
194         menu->vis_entries=0;
195     }else{
196         int vis=(maxh-bdh+e_bdw.spacing)/(e_bdw.spacing+menu->entry_h);
197         if(vis>menu->n_entries){
198             vis=menu->n_entries;
199             menu->first_entry=0;
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;
205         }
206         if(vis<=0)
207             vis=1;
208         menu->vis_entries=vis;
209         if(maxexact)
210             *h_ret=maxh;
211         else
212             *h_ret=vis*menu->entry_h+(vis-1)*e_bdw.spacing+bdh;
213     }
214
215     /* Calculate new shortened entry names */
216     maxew-=e_bdw.left+e_bdw.right;
217     
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;
222         }
223         if(maxew<=0)
224             continue;
225         
226         if(extl_table_getis(menu->tab, i+1, "name", 's', &str)){
227             menu->entries[i].title=grbrush_make_label(menu->entry_brush, 
228                                                       str, maxew);
229             free(str);
230         }
231     }
232 }
233
234
235 void calc_size(WMenu *menu, int *w, int *h)
236 {
237     if(menu->pmenu_mode){
238         menu_calc_size(menu, FALSE, INT_MAX, INT_MAX, w, h);
239     }else{
240         menu_calc_size(menu, !(menu->last_fp.mode&REGION_FIT_BOUNDS),
241                        menu->last_fp.g.w, menu->last_fp.g.h, w, h);
242     }
243 }
244     
245
246 /* Return offset from bottom-left corner of containing mplex or top-right
247  * corner of  parent menu for the respective corner of menu.
248  */
249 static void get_placement_offs(WMenu *menu, int *xoff, int *yoff)
250 {
251     GrBorderWidths bdw;
252     
253     *xoff=0; 
254     *yoff=0;
255     
256     if(menu->brush!=NULL){
257         grbrush_get_border_widths(menu->brush, &bdw);
258         *xoff+=bdw.right;
259         *yoff+=bdw.top;
260     }
261     
262     if(menu->entry_brush!=NULL){
263         grbrush_get_border_widths(menu->entry_brush, &bdw);
264         *xoff+=bdw.right;
265         *yoff+=bdw.top;
266     }
267 }
268     
269     
270 #define MINIMUM_Y_VISIBILITY 20
271 #define POINTER_OFFSET 5
272
273 static void menu_firstfit(WMenu *menu, bool submenu, const WRectangle *refg)
274 {
275     WRectangle geom;
276     
277     calc_size(menu, &(geom.w), &(geom.h));
278     
279     if(!(menu->last_fp.mode&REGION_FIT_BOUNDS)){
280         geom.x=menu->last_fp.g.x;
281         geom.y=menu->last_fp.g.y;
282     }else if(menu->pmenu_mode){
283         geom.x=refg->x;
284         geom.y=refg->y;
285         
286         if(!submenu){
287             const WRectangle *maxg = 
288                 &REGION_GEOM(REGION_PARENT((WRegion*)menu));
289             
290             geom.x-=geom.w/2;
291             geom.y+=POINTER_OFFSET;
292
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;
298             }else{
299                 if(geom.x<0)
300                     geom.x=0;
301                 else if(geom.x+geom.w>maxg->x+maxg->w)
302                     geom.x=maxg->x+maxg->w-geom.w;
303             }
304         }
305     }else{
306         const WRectangle *maxg=&(menu->last_fp.g);
307         if(submenu){
308             int l, r, t, b, xoff, yoff;
309             
310             get_placement_offs(menu, &xoff, &yoff);
311             l=refg->x+xoff;
312             r=refg->x+refg->w+xoff;
313             t=refg->y-yoff;
314             b=refg->y+refg->h-yoff;
315             
316             geom.x=maxof(l, r-geom.w);
317             if(geom.x+geom.w>maxg->x+maxg->w)
318                 geom.x=maxg->x;
319
320             geom.y=minof(b-geom.h, t);
321             if(geom.y<maxg->y)
322                 geom.y=maxg->y;
323         }else{
324             geom.x=maxg->x;
325             geom.y=maxg->y+maxg->h-geom.h;
326         }
327     }
328     
329     window_do_fitrep(&menu->win, NULL, &geom);
330 }
331
332
333 static void menu_do_refit(WMenu *menu, WWindow *par, const WFitParams *oldfp)
334 {
335     WRectangle geom;
336     
337     calc_size(menu, &(geom.w), &(geom.h));
338     
339     if(!(menu->last_fp.mode&REGION_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;
345     }else{
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);
352     }
353     
354     window_do_fitrep(&menu->win, par, &geom);
355 }
356
357
358 bool menu_fitrep(WMenu *menu, WWindow *par, const WFitParams *fp)
359 {
360     WFitParams oldfp;
361     
362     if(par!=NULL && !region_same_rootwin((WRegion*)par, (WRegion*)menu))
363         return FALSE;
364     
365     oldfp=menu->last_fp;
366     menu->last_fp=*fp;
367     menu_do_refit(menu, par, &oldfp);
368     
369     if(menu->submenu!=NULL && !menu->pmenu_mode)
370         region_fitrep((WRegion*)(menu->submenu), par, fp);
371     
372     return TRUE;
373 }
374
375
376 /*}}}*/
377
378
379 /*{{{ Brush update */
380
381
382 static void calc_entry_dimens(WMenu *menu)
383 {
384     int i, n=extl_table_get_n(menu->tab);
385     GrFontExtents fnte;
386     GrBorderWidths bdw;
387     int maxw=0;
388     char *str;
389     
390 #if 0    
391     if(extl_table_gets_s(menu->tab, title, &str)){
392         maxw=grbrush_get_text_width(title_brush, str, strlen(str));
393         free(str);
394     }
395 #endif
396     
397     for(i=1; i<=n; i++){
398         if(extl_table_getis(menu->tab, i, "name", 's', &str)){
399             int w=grbrush_get_text_width(menu->entry_brush, 
400                                          str, strlen(str));
401             if(w>maxw)
402                 maxw=w;
403             free(str);
404         }
405     }
406     
407     grbrush_get_border_widths(menu->entry_brush, &bdw);
408     grbrush_get_font_extents(menu->entry_brush, &fnte);
409     
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;
413 }
414
415
416 static bool menu_init_gr(WMenu *menu, WRootWin *rootwin, Window win)
417 {
418     GrBrush *brush, *entry_brush;
419     char *st;
420     const char *style=(menu->big_mode 
421                        ? "input-menu-big"
422                        : (menu->pmenu_mode
423                           ? "input-menu-pmenu"
424                           : "input-menu-normal"));
425
426     const char *entry_style=(menu->big_mode
427                                    ? "tab-menuentry-big"
428                                    : (menu->pmenu_mode
429                                       ? "tab-menuentry-pmenu"
430                                       : "tab-menuentry-normal"));
431     
432     brush=gr_get_brush(win, rootwin, style);
433     
434     if(brush==NULL)
435         return FALSE;
436
437     entry_brush=grbrush_get_slave(brush, rootwin, entry_style);
438     
439     if(entry_brush==NULL){
440         grbrush_release(brush);
441         return FALSE;
442     }
443     
444     if(menu->entry_brush!=NULL)
445         grbrush_release(menu->entry_brush);
446     if(menu->brush!=NULL)
447         grbrush_release(menu->brush);
448
449     menu->brush=brush;
450     menu->entry_brush=entry_brush;
451     
452     calc_entry_dimens(menu);
453     
454     return TRUE;
455 }
456
457
458 void menu_updategr(WMenu *menu)
459 {
460     if(!menu_init_gr(menu, region_rootwin_of((WRegion*)menu),
461                      MENU_WIN(menu))){
462         return;
463     }
464     
465     menu_do_refit(menu, NULL, &(menu->last_fp));
466     
467     region_updategr_default((WRegion*)menu);
468     
469     window_draw((WWindow*)menu, TRUE);
470 }
471
472
473 static void menu_release_gr(WMenu *menu)
474 {
475     if(menu->entry_brush!=NULL){
476         grbrush_release(menu->entry_brush);
477         menu->entry_brush=NULL;
478     }
479     if(menu->brush!=NULL){
480         grbrush_release(menu->brush);
481         menu->brush=NULL;
482     }
483 }
484
485
486 /*}}}*/
487
488
489 /*{{{ Init/deinit */
490
491
492 static WMenuEntry *preprocess_menu(ExtlTab tab, int *n_entries)
493 {
494     ExtlTab entry, sub;
495     ExtlFn fn;
496     WMenuEntry *entries;
497     int i, n;
498     
499     n=extl_table_get_n(tab);
500     *n_entries=n;
501     
502     if(n<=0)
503         return NULL;
504
505     entries=ALLOC_N(WMenuEntry, n);  
506     
507     if(entries==NULL)
508         return NULL;
509
510     /* Initialise entries and check submenus */
511     for(i=1; i<=n; i++){
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;
516             extl_unref_fn(fn);
517         }else if(extl_table_getis(tab, i, "submenu", 't', &sub)){
518             entries[i-1].flags|=WMENUENTRY_SUBMENU;
519             extl_unref_table(sub);
520         }
521     }
522     
523     return entries;
524 }
525
526
527
528 bool menu_init(WMenu *menu, WWindow *par, const WFitParams *fp,
529                const WMenuCreateParams *params)
530 {
531     Window win;
532     int i;
533     
534     menu->entries=preprocess_menu(params->tab, &(menu->n_entries));
535     
536     if(menu->entries==NULL){
537         warn(TR("Empty menu."));
538         return FALSE;
539     }
540
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;
545
546     menu->last_fp=*fp;
547     
548     if(params->pmenu_mode)
549         menu->selected_entry=-1;
550     else{
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;
556     }
557     
558     menu->max_entry_w=0;
559     menu->entry_h=0;
560     menu->brush=NULL;
561     menu->entry_brush=NULL;
562     menu->entry_spacing=0;
563     menu->vis_entries=menu->n_entries;
564     menu->first_entry=0;
565     menu->submenu=NULL;
566     menu->typeahead=NULL;
567     
568     menu->gm_kcb=0;
569     menu->gm_state=0;
570     
571     if(!window_init((WWindow*)menu, par, fp))
572         goto fail;
573
574     win=menu->win.win;
575     
576     if(!menu_init_gr(menu, region_rootwin_of((WRegion*)par), win))
577         goto fail2;
578
579     menu_firstfit(menu, params->submenu_mode, &(params->refg));
580     
581     window_select_input(&(menu->win), IONCORE_EVENTMASK_NORMAL);
582     
583     region_add_bindmap((WRegion*)menu, mod_menu_menu_bindmap);
584     
585     region_register((WRegion*)menu);
586     
587     return TRUE;
588
589 fail2:
590     window_deinit((WWindow*)menu);
591 fail:
592     extl_unref_table(menu->tab);
593     extl_unref_fn(menu->handler);
594     free(menu->entries);
595     return FALSE;
596 }
597
598
599 WMenu *create_menu(WWindow *par, const WFitParams *fp, 
600                    const WMenuCreateParams *params)
601 {
602     CREATEOBJ_IMPL(WMenu, menu, (p, par, fp, params));
603 }
604
605
606
607 void menu_deinit(WMenu *menu)
608 {
609     int i;
610     
611     menu_typeahead_clear(menu);
612     
613     if(menu->submenu!=NULL)
614         destroy_obj((Obj*)menu->submenu);
615     
616     extl_unref_table(menu->tab);
617     extl_unref_fn(menu->handler);
618     
619     for(i=0; i<menu->n_entries; i++)
620         free(menu->entries[i].title);
621     free(menu->entries);
622     
623     menu_release_gr(menu);
624     window_deinit((WWindow*)menu);
625 }
626
627
628 /*}}}*/
629
630
631 /*{{{ Focus  */
632
633
634 static void menu_inactivated(WMenu *menu)
635 {
636     window_draw((WWindow*)menu, FALSE);
637 }
638
639
640 static void menu_activated(WMenu *menu)
641 {
642     window_draw((WWindow*)menu, FALSE);
643 }
644
645
646 /*}}}*/
647
648
649 /*{{{ Submenus */
650
651
652 static WMenu *menu_head(WMenu *menu)
653 {
654     WMenu *m=REGION_MANAGER_CHK(menu, WMenu);
655     return (m==NULL ? menu : menu_head(m));
656 }
657
658
659 static WMenu *menu_tail(WMenu *menu)
660 {
661     return (menu->submenu==NULL ? menu : menu_tail(menu->submenu));
662 }
663
664
665 static void menu_managed_remove(WMenu *menu, WRegion *sub)
666 {
667     bool mcf=region_may_control_focus((WRegion*)menu);
668     
669     if(sub!=(WRegion*)menu->submenu)
670         return;
671     
672     menu->submenu=NULL;
673
674     region_unset_manager(sub, (WRegion*)menu);
675     
676     if(mcf)
677         region_do_set_focus((WRegion*)menu, FALSE);
678 }
679
680
681 int get_sub_y_off(WMenu *menu, int n)
682 {
683     /* top + sum of above entries and spacings - top_of_sub */
684     return (menu->entry_h+menu->entry_spacing)*(n-menu->first_entry);
685 }
686
687
688 static void show_sub(WMenu *menu, int n)
689 {
690     WFitParams fp;
691     WMenuCreateParams fnp;
692     WMenu *submenu;
693     WWindow *par;
694     
695     par=REGION_PARENT(menu);
696     
697     if(par==NULL)
698         return;
699     
700     fp=menu->last_fp;
701
702     fnp.pmenu_mode=menu->pmenu_mode;
703     fnp.big_mode=menu->big_mode;
704     fnp.submenu_mode=TRUE;
705     
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);
709         fnp.refg.w=0;
710         fnp.refg.h=0;
711     }else{
712         fnp.refg=REGION_GEOM(menu);
713     }
714
715     fnp.tab=extl_table_none();
716     {
717         ExtlFn fn;
718         if(extl_table_getis(menu->tab, n+1, "submenu_fn", 'f', &fn)){
719             extl_protect(NULL);
720             extl_call(fn, NULL, "t", &(fnp.tab));
721             extl_unprotect(NULL);
722             extl_unref_fn(fn);
723         }else{
724             extl_table_getis(menu->tab, n+1, "submenu", 't', &(fnp.tab));
725         }
726         if(fnp.tab==extl_table_none())
727             return;
728     }
729     
730     fnp.handler=extl_ref_fn(menu->handler);
731     
732     fnp.initial=0;
733     {
734         ExtlFn fn;
735         if(extl_table_getis(menu->tab, n+1, "initial", 'f', &fn)){
736             extl_protect(NULL);
737             extl_call(fn, NULL, "i", &(fnp.initial));
738             extl_unprotect(NULL);
739             extl_unref_fn(fn);
740         }else{
741             extl_table_getis(menu->tab, n+1, "initial", 'i', &(fnp.initial));
742         }
743     }
744
745     submenu=create_menu(par, &fp, &fnp);
746     
747     if(submenu==NULL)
748         return;
749     
750     menu->submenu=submenu;
751     region_set_manager((WRegion*)submenu, (WRegion*)menu);
752         
753     region_restack((WRegion*)submenu, MENU_WIN(menu), Above);
754     region_map((WRegion*)submenu);
755     
756     if(!menu->pmenu_mode && region_may_control_focus((WRegion*)menu))
757         region_do_set_focus((WRegion*)submenu, FALSE);
758 }
759
760
761 static void menu_do_set_focus(WMenu *menu, bool warp)
762 {
763     if(menu->submenu!=NULL)
764         region_do_set_focus((WRegion*)menu->submenu, warp);
765     else
766         window_do_set_focus((WWindow*)menu, warp);
767 }
768
769
770 void menu_restack(WMenu *menu, Window other, int mode)
771 {
772     xwindow_restack(MENU_WIN(menu), other, mode);
773     if(menu->submenu!=NULL)
774         region_restack((WRegion*)(menu->submenu), MENU_WIN(menu), Above);
775 }
776
777
778 void menu_stacking(WMenu *menu, Window *bottomret, Window *topret)
779 {
780     *topret=None;
781     
782     if(menu->submenu!=NULL)
783         region_stacking((WRegion*)(menu->submenu), bottomret, topret);
784     
785     *bottomret=MENU_WIN(menu);
786     if(*topret==None)
787         *topret=MENU_WIN(menu);
788
789 }
790
791     
792 /*}}}*/
793
794
795 /*{{{ Exports */
796
797
798 static void menu_do_select_nth(WMenu *menu, int n)
799 {
800     int oldn=menu->selected_entry;
801     bool drawfull=FALSE;
802     
803     if(oldn==n)
804         return;
805     
806     if(menu->submenu!=NULL)
807         destroy_obj((Obj*)menu->submenu);
808     
809     assert(menu->submenu==NULL);
810
811     menu->selected_entry=n;
812
813     if(n>=0){
814         if(n<menu->first_entry){
815             menu->first_entry=n;
816             drawfull=TRUE;
817         }else if(n>=menu->first_entry+menu->vis_entries){
818             menu->first_entry=n-menu->vis_entries+1;
819             drawfull=TRUE;
820         }
821         
822         if(menu->entries[n].flags&WMENUENTRY_SUBMENU &&
823            menu->pmenu_mode){
824             show_sub(menu, n);
825         }
826     }
827     
828     if(drawfull){
829         menu_draw_entries(menu, TRUE);
830     }else{
831         /* redraw new and old selected entry */ 
832         WRectangle igeom;
833         get_inner_geom(menu, &igeom);
834
835         /* !!!BEGIN!!! */
836         if(oldn!=-1)
837             menu_draw_entry(menu, oldn, &igeom, TRUE);
838         if(n!=-1)
839             menu_draw_entry(menu, n, &igeom, TRUE);
840     }
841 }
842
843
844 /*EXTL_DOC
845  * Select \var{n}:th entry in menu.
846  */
847 EXTL_EXPORT_MEMBER
848 void menu_select_nth(WMenu *menu, int n)
849 {
850     if(n<0)
851         n=0;
852     if(n>=menu->n_entries)
853         n=menu->n_entries-1;
854
855     menu_typeahead_clear(menu);
856     menu_do_select_nth(menu, n);
857 }
858
859
860 /*EXTL_DOC
861  * Select previous entry in menu.
862  */
863 EXTL_EXPORT_MEMBER
864 void menu_select_prev(WMenu *menu)
865 {
866     menu_select_nth(menu, (menu->selected_entry<=0 
867                            ? menu->n_entries-1
868                            : menu->selected_entry-1));
869 }
870
871
872 /*EXTL_DOC
873  * Select next entry in menu.
874  */
875 EXTL_EXPORT_MEMBER
876 void menu_select_next(WMenu *menu)
877 {
878     menu_select_nth(menu, (menu->selected_entry+1)%menu->n_entries);
879 }
880
881
882 static void menu_do_finish(WMenu *menu)
883 {
884     ExtlFn handler;
885     ExtlTab tab;
886     bool ok;
887     WMenu *head=menu_head(menu);
888     
889     handler=menu->handler;
890     menu->handler=extl_fn_none();
891     
892     ok=extl_table_geti_t(menu->tab, menu->selected_entry+1, &tab);
893     
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);
898     
899     if(ok)
900         extl_call(handler, "t", NULL, tab);
901
902     extl_unref_fn(handler);
903     extl_unref_table(tab);
904 }
905
906
907 /*EXTL_DOC
908  * If selected entry is a submenu, display that.
909  * Otherwise destroy the menu and call handler for selected entry.
910  */
911 EXTL_EXPORT_MEMBER
912 void menu_finish(WMenu *menu)
913 {
914     menu_typeahead_clear(menu);
915
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);
919         return;
920     }
921     
922     mainloop_defer_action((Obj*)menu, (WDeferredAction*)menu_do_finish);
923 }
924
925
926
927 /*EXTL_DOC
928  * Close \var{menu} not calling any possible finish handlers.
929  */
930 EXTL_EXPORT_MEMBER
931 void menu_cancel(WMenu *menu)
932 {
933     if(region_manager_allows_destroying((WRegion*)menu))
934         mainloop_defer_destroy((Obj*)menu);
935 }
936
937
938 /*}}}*/
939
940
941 /*{{{ Scroll */
942
943 static int scroll_time=20;
944 static int scroll_amount=3;
945 static WTimer *scroll_timer=NULL;
946
947
948 static void reset_scroll_timer()
949 {
950     if(scroll_timer!=NULL){
951         destroy_obj((Obj*)scroll_timer);
952         scroll_timer=NULL;
953     }
954 }
955
956
957 /*EXTL_DOC
958  * Set module basic settings. The parameter table may contain the
959  * following fields:
960  * 
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 
968  *                        milliseconds.
969  * \end{tabularx}
970  */
971 EXTL_EXPORT
972 void mod_menu_set(ExtlTab tab)
973 {
974     int a, t;
975     
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);
980 }
981
982
983 /*EXTL_DOC
984  * Get module basic settings. For details, see \fnref{mod_menu.set}.
985  */
986 EXTL_SAFE
987 EXTL_EXPORT
988 ExtlTab mod_menu_get()
989 {
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);
993     return tab;
994 }
995
996
997 enum{
998     D_LEFT,
999     D_RIGHT,
1000     D_DOWN,
1001     D_UP
1002 };
1003
1004
1005 static int calc_diff(const WRectangle *mg, const WRectangle *pg, int d)
1006 {
1007     switch(d){
1008     case D_LEFT:
1009         return mg->x+mg->w-pg->w;
1010     case D_UP:
1011         return mg->y+mg->h-pg->h;
1012     case D_RIGHT:
1013         return -mg->x;
1014     case D_DOWN:
1015         return -mg->y;
1016     }
1017     return 0;
1018 }
1019
1020
1021 static int scrolld_subs(WMenu *menu, int d)
1022 {
1023     int diff=0;
1024     WRegion *p=REGION_PARENT_REG(menu);
1025     const WRectangle *pg;
1026     
1027     if(p==NULL)
1028         return 0;
1029
1030     pg=&REGION_GEOM(p);
1031     
1032     while(menu!=NULL){
1033         diff=maxof(diff, calc_diff(&REGION_GEOM(menu), pg, d));
1034         menu=menu->submenu;
1035     }
1036     
1037     return minof(maxof(0, diff), scroll_amount);
1038 }
1039
1040
1041 static void menu_select_entry_at(WMenu *menu, int px, int py);
1042
1043
1044 static void do_scroll(WMenu *menu, int xd, int yd)
1045 {
1046     WRectangle g;
1047     int px=-1, py=-1;
1048     
1049     xwindow_pointer_pos(region_root_of((WRegion*)menu), &px, &py);
1050
1051     while(menu!=NULL){
1052         g=REGION_GEOM(menu);
1053         g.x+=xd;
1054         g.y+=yd;
1055         
1056         window_do_fitrep((WWindow*)menu, NULL, &g);
1057         
1058         menu_select_entry_at(menu, px, py);
1059
1060         menu=menu->submenu;
1061     }
1062 }
1063
1064
1065 static void scroll_left(WTimer *timer, WMenu *menu)
1066 {
1067     if(menu!=NULL){
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,
1071                       (Obj*)menu);
1072             return;
1073         }
1074     }
1075 }
1076
1077
1078 static void scroll_up(WTimer *timer, WMenu *menu)
1079 {
1080     if(menu!=NULL){
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,
1084                       (Obj*)menu);
1085
1086             return;
1087         }
1088     }
1089 }
1090
1091
1092 static void scroll_right(WTimer *timer, WMenu *menu)
1093 {
1094     if(menu!=NULL){
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,
1098                       (Obj*)menu);
1099             return;
1100         }
1101     }
1102 }
1103
1104
1105 static void scroll_down(WTimer *timer, WMenu *menu)
1106 {
1107     if(menu!=NULL){
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,
1111                       (Obj*)menu);
1112             return;
1113         }
1114     }
1115 }
1116
1117
1118 static void end_scroll(WMenu *menu)
1119 {
1120     reset_scroll_timer();
1121 }
1122
1123 #define SCROLL_OFFSET 10
1124
1125 static void check_scroll(WMenu *menu, int x, int y)
1126 {
1127     WRegion *parent=REGION_PARENT_REG(menu);
1128     int rx, ry;
1129     WTimerHandler *fn=NULL;
1130
1131     if(!menu->pmenu_mode)
1132         return;
1133     
1134     if(parent==NULL){
1135         end_scroll(menu);
1136         return;
1137     }
1138
1139     region_rootpos(parent, &rx, &ry);
1140     x-=rx;
1141     y-=ry;
1142     
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;
1151     }else{
1152         end_scroll(menu);
1153         return;
1154     }
1155     
1156     assert(fn!=NULL);
1157     
1158     if(scroll_timer!=NULL){
1159         if(scroll_timer->handler==(WTimerHandler*)fn &&
1160            timer_is_set(scroll_timer)){
1161             return;
1162         }
1163     }else{
1164         scroll_timer=create_timer();
1165         if(scroll_timer==NULL)
1166             return;
1167     }
1168     
1169     fn(scroll_timer, (Obj*)menu_head(menu));
1170 }
1171
1172
1173 /*}}}*/
1174
1175
1176 /*{{{ Pointer handlers */
1177
1178
1179 int menu_entry_at_root(WMenu *menu, int root_x, int root_y)
1180 {
1181     int rx, ry, x, y, entry;
1182     WRectangle ig;
1183     region_rootpos((WRegion*)menu, &rx, &ry);
1184     
1185     get_inner_geom(menu, &ig);
1186     
1187     x=root_x-rx-ig.x;
1188     y=root_y-ry-ig.y;
1189     
1190     if(x<0 || x>=ig.w || y<0  || y>=ig.h)
1191         return -1;
1192     
1193     entry=y/(menu->entry_h+menu->entry_spacing);
1194     if(entry<0 || entry>=menu->vis_entries)
1195         return -1;
1196     return entry+menu->first_entry;
1197 }
1198
1199
1200 int menu_entry_at_root_tree(WMenu *menu, int root_x, int root_y, 
1201                             WMenu **realmenu)
1202 {
1203     int entry=-1;
1204     
1205     menu=menu_tail(menu);
1206     
1207     *realmenu=menu;
1208     
1209     if(!menu->pmenu_mode)
1210         return menu_entry_at_root(menu, root_x, root_y);
1211     
1212     while(menu!=NULL){
1213         entry=menu_entry_at_root(menu, root_x, root_y);
1214         if(entry>=0){
1215             *realmenu=menu;
1216             break;
1217         }
1218         menu=REGION_MANAGER_CHK(menu, WMenu);
1219     }
1220     
1221     return entry;
1222 }
1223
1224
1225 static void menu_select_entry_at(WMenu *menu, int px, int py)
1226 {
1227     int entry=menu_entry_at_root_tree(menu, px, py, &menu);
1228     if(entry>=0)
1229         menu_do_select_nth(menu, entry);
1230 }
1231
1232
1233 void menu_release(WMenu *menu, XButtonEvent *ev)
1234 {
1235     int entry=menu_entry_at_root_tree(menu, ev->x_root, ev->y_root, &menu);
1236     end_scroll(menu);
1237     if(entry>=0){
1238         menu_select_nth(menu, entry);
1239         menu_finish(menu);
1240     }else if(menu->pmenu_mode){
1241         menu_cancel(menu_head(menu));
1242     }
1243 }
1244
1245
1246 void menu_motion(WMenu *menu, XMotionEvent *ev, int dx, int dy)
1247 {
1248     menu_select_entry_at(menu, ev->x_root, ev->y_root);
1249     check_scroll(menu, ev->x_root, ev->y_root);
1250 }
1251
1252
1253 void menu_button(WMenu *menu, XButtonEvent *ev)
1254 {
1255     int entry=menu_entry_at_root_tree(menu, ev->x_root, ev->y_root, &menu);
1256     if(entry>=0)
1257         menu_select_nth(menu, entry);
1258 }
1259
1260
1261 int menu_press(WMenu *menu, XButtonEvent *ev, WRegion **reg_ret)
1262 {
1263     menu_button(menu, ev);
1264     menu=menu_head(menu);
1265     ioncore_set_drag_handlers((WRegion*)menu,
1266                         NULL,
1267                         (WMotionHandler*)menu_motion,
1268                         (WButtonHandler*)menu_release,
1269                         NULL, 
1270                         NULL);
1271     return 0;
1272 }
1273
1274
1275 /*}}}*/
1276
1277
1278 /*{{{ Typeahead find */
1279
1280
1281 static void menu_insstr(WMenu *menu, const char *buf, size_t n)
1282 {
1283     size_t oldlen=(menu->typeahead==NULL ? 0 : strlen(menu->typeahead));
1284     char *newta=(char*)malloc(oldlen+n+1);
1285     char *newta_orig;
1286     int entry;
1287     
1288     if(newta==NULL)
1289         return;
1290     
1291     if(oldlen!=0)
1292         memcpy(newta, menu->typeahead, oldlen);
1293     if(n!=0)
1294         memcpy(newta+oldlen, buf, n);
1295     newta[oldlen+n]='\0';
1296     newta_orig=newta;
1297     
1298     while(*newta!='\0'){
1299         bool found=FALSE;
1300         entry=menu->selected_entry;
1301         do{
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)){
1305                     found=TRUE;
1306                     break;
1307                 }
1308             }
1309             entry=(entry+1)%menu->n_entries;
1310         }while(entry!=menu->selected_entry);
1311         if(found){
1312             menu_do_select_nth(menu, entry);
1313             break;
1314         }
1315         newta++;
1316     }
1317     
1318     if(newta_orig!=newta){
1319         if(*newta=='\0'){
1320             free(newta_orig);
1321             newta=NULL;
1322         }else{
1323             char *p=scopy(newta);
1324             free(newta_orig);
1325             newta=p;
1326         }
1327     }
1328     if(menu->typeahead!=NULL)
1329         free(menu->typeahead);
1330     menu->typeahead=newta;
1331 }
1332
1333
1334 /*EXTL_DOC
1335  * Clear typeahead buffer.
1336  */
1337 EXTL_EXPORT_MEMBER
1338 void menu_typeahead_clear(WMenu *menu)
1339 {
1340     if(menu->typeahead!=NULL){
1341         free(menu->typeahead);
1342         menu->typeahead=NULL;
1343     }
1344 }
1345
1346
1347 /*}}}*/
1348
1349
1350 /*{{{ Dynamic function table and class implementation */
1351
1352
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},
1365     END_DYNFUNTAB
1366 };
1367
1368
1369 EXTL_EXPORT
1370 IMPLCLASS(WMenu, WWindow, menu_deinit, menu_dynfuntab);
1371
1372     
1373 /*}}}*/
1374
1375