]> git.decadent.org.uk Git - ion3.git/blob - ioncore/group.c
[svn-upgrade] Integrating new upstream version, ion3 (20080103)
[ion3.git] / ioncore / group.c
1 /*
2  * ion/ioncore/group.c
3  *
4  * Copyright (c) Tuomo Valkonen 1999-2008. 
5  *
6  * See the included file LICENSE for details.
7  */
8
9 #include <string.h>
10
11 #include <libtu/minmax.h>
12 #include <libtu/objp.h>
13 #include <libmainloop/defer.h>
14
15 #include "common.h"
16 #include "rootwin.h"
17 #include "focus.h"
18 #include "global.h"
19 #include "region.h"
20 #include "manage.h"
21 #include "screen.h"
22 #include "names.h"
23 #include "saveload.h"
24 #include "attach.h"
25 #include "regbind.h"
26 #include "extlconv.h"
27 #include "xwindow.h"
28 #include "resize.h"
29 #include "stacking.h"
30 #include "sizepolicy.h"
31 #include "bindmaps.h"
32 #include "navi.h"
33 #include "sizehint.h"
34 #include "llist.h"
35 #include "mplex.h"
36 #include "group.h"
37 #include "grouppholder.h"
38 #include "frame.h"
39 #include "float-placement.h"
40 #include "return.h"
41
42
43 static void group_place_stdisp(WGroup *ws, WWindow *parent,
44                                  int pos, WRegion *stdisp);
45
46 static void group_remanage_stdisp(WGroup *ws);
47
48 static void group_do_set_bottom(WGroup *grp, WStacking *st);
49
50
51 /*{{{ Stacking list stuff */
52
53
54 WStacking *group_get_stacking(WGroup *ws)
55 {
56     WWindow *par=REGION_PARENT(ws);
57     
58     return (par==NULL 
59             ? NULL
60             : window_get_stacking(par));
61 }
62
63
64 WStacking **group_get_stackingp(WGroup *ws)
65 {
66     WWindow *par=REGION_PARENT(ws);
67     
68     return (par==NULL 
69             ? NULL
70             : window_get_stackingp(par));
71 }
72
73
74 static bool wsfilt(WStacking *st, void *ws)
75 {
76     return (st->reg!=NULL && REGION_MANAGER(st->reg)==(WRegion*)ws);
77 }
78
79
80 static bool wsfilt_nostdisp(WStacking *st, void *ws)
81 {
82     return (wsfilt(st, ws) && ((WGroup*)ws)->managed_stdisp!=st);
83 }
84
85
86 void group_iter_init(WGroupIterTmp *tmp, WGroup *ws)
87 {
88     stacking_iter_mgr_init(tmp, ws->managed_list, NULL, ws);
89 }
90
91
92 void group_iter_init_nostdisp(WGroupIterTmp *tmp, WGroup *ws)
93 {
94     stacking_iter_mgr_init(tmp, ws->managed_list, wsfilt_nostdisp, ws);
95 }
96
97
98 WRegion *group_iter(WGroupIterTmp *tmp)
99 {
100     return stacking_iter_mgr(tmp);
101 }
102
103
104 WStacking *group_iter_nodes(WGroupIterTmp *tmp)
105 {
106     return stacking_iter_mgr_nodes(tmp);
107 }
108
109
110 WGroupIterTmp group_iter_default_tmp;
111
112
113 /*}}}*/
114
115
116 /*{{{ region dynfun implementations */
117
118
119 static void group_fit(WGroup *ws, const WRectangle *geom)
120 {
121     REGION_GEOM(ws)=*geom;
122 }
123
124
125 bool group_fitrep(WGroup *ws, WWindow *par, const WFitParams *fp)
126 {
127     WGroupIterTmp tmp;
128     WStacking *unweaved=NULL;
129     int xdiff=0, ydiff=0;
130     WStacking *st;
131     WWindow *oldpar;
132     WRectangle g;
133     
134     oldpar=REGION_PARENT(ws);
135     
136     if(par==NULL){
137         if(fp->mode&REGION_FIT_WHATEVER)
138             return TRUE;
139         REGION_GEOM(ws)=fp->g;
140     }else{
141         if(!region_same_rootwin((WRegion*)ws, (WRegion*)par))
142             return FALSE;
143         
144         if(ws->managed_stdisp!=NULL && ws->managed_stdisp->reg!=NULL)
145             region_detach_manager(ws->managed_stdisp->reg);
146         
147         assert(ws->managed_stdisp==NULL);
148         
149         xdiff=fp->g.x-REGION_GEOM(ws).x;
150         ydiff=fp->g.y-REGION_GEOM(ws).y;
151     
152         region_unset_parent((WRegion*)ws);
153         XReparentWindow(ioncore_g.dpy, ws->dummywin, par->win, -1, -1);
154         region_set_parent((WRegion*)ws, par);
155
156         REGION_GEOM(ws).x=fp->g.x;
157         REGION_GEOM(ws).y=fp->g.y;
158         if(!(fp->mode&REGION_FIT_WHATEVER)){
159             REGION_GEOM(ws).w=fp->g.w;
160             REGION_GEOM(ws).h=fp->g.h;
161         }
162         
163         if(oldpar!=NULL)
164             unweaved=stacking_unweave(&oldpar->stacking, wsfilt, (void*)ws);
165     }
166
167     FOR_ALL_NODES_IN_GROUP(ws, st, tmp){
168         WFitParams fp2=*fp;
169         
170         if(st->reg==NULL)
171             continue;
172         
173         g=REGION_GEOM(st->reg);
174         g.x+=xdiff;
175         g.y+=ydiff;
176
177         if(fp->mode&REGION_FIT_WHATEVER){
178             fp2.g=g;
179         }else{
180             fp2.g=REGION_GEOM(ws);
181             sizepolicy(&st->szplcy, st->reg, &g, REGION_RQGEOM_WEAK_ALL, &fp2);
182         }
183         
184         if(!region_fitrep(st->reg, par, &fp2)){
185             warn(TR("Error reparenting %s."), region_name(st->reg));
186             region_detach_manager(st->reg);
187         }
188     }
189     
190     if(unweaved!=NULL)
191         stacking_weave(&par->stacking, &unweaved, FALSE);
192
193     return TRUE;
194 }
195
196
197 static void group_map(WGroup *ws)
198 {
199     WRegion *reg;
200     WGroupIterTmp tmp;
201
202     REGION_MARK_MAPPED(ws);
203     XMapWindow(ioncore_g.dpy, ws->dummywin);
204     
205     FOR_ALL_MANAGED_BY_GROUP(ws, reg, tmp){
206         region_map(reg);
207     }
208 }
209
210
211 static void group_unmap(WGroup *ws)
212 {
213     WRegion *reg;
214     WGroupIterTmp tmp;
215
216     REGION_MARK_UNMAPPED(ws);
217     XUnmapWindow(ioncore_g.dpy, ws->dummywin);
218
219     FOR_ALL_MANAGED_BY_GROUP(ws, reg, tmp){
220         region_unmap(reg);
221     }
222 }
223
224
225 static WStacking *find_to_focus(WGroup *ws, WStacking *st, bool group_only)
226 {
227     WStacking *stacking=group_get_stacking(ws);
228     
229     if(stacking==NULL)
230         return st;
231                                 
232     return stacking_find_to_focus_mapped(stacking, st, 
233                                          (group_only ? (WRegion*)ws : NULL));
234 }
235
236
237 static void group_do_set_focus(WGroup *ws, bool warp)
238 {
239     WStacking *st=find_to_focus(ws, ws->current_managed, FALSE);
240     
241     if(st!=NULL && st->reg!=NULL)
242         region_do_set_focus(st->reg, warp);
243     else
244         region_finalise_focusing((WRegion*)ws, ws->dummywin, warp);
245 }
246
247
248 static bool group_managed_prepare_focus(WGroup *ws, WRegion *reg, 
249                                         int flags, WPrepareFocusResult *res)
250 {
251     WMPlex *mplex=OBJ_CAST(REGION_MANAGER(ws), WMPlex);
252     WStacking *st=group_find_stacking(ws, reg);    
253     
254     if(st==NULL)
255         return FALSE;
256
257     if(mplex!=NULL){
258         WStacking *node=mplex_find_stacking(mplex, (WRegion*)ws);
259         
260         if(node==NULL)
261             return FALSE;
262         
263         return mplex_do_prepare_focus(mplex, node, st,
264                                       flags, res);
265     }else{
266         if(!region_prepare_focus((WRegion*)ws, flags, res))
267             return FALSE;
268
269         st=find_to_focus(ws, st, FALSE);
270         
271         if(st==NULL)
272             return FALSE;
273         
274         if(ioncore_g.autoraise && 
275            !(flags&REGION_GOTO_ENTERWINDOW) &&
276            st->level>STACKING_LEVEL_BOTTOM){
277             WStacking **stackingp=group_get_stackingp(ws);
278             stacking_restack(stackingp, st, None, NULL, NULL, FALSE);
279         }
280         
281         res->reg=st->reg;
282         res->flags=flags;
283         
284         return (res->reg==reg);
285     }
286 }
287
288
289 void group_managed_remove(WGroup *ws, WRegion *reg)
290 {
291     bool mcf=region_may_control_focus((WRegion*)ws);
292     WStacking *st, *next_st=NULL;
293     bool was_stdisp=FALSE, was_bottom=FALSE;
294     bool was_current=FALSE;
295     
296     st=group_find_stacking(ws, reg);
297
298     if(st!=NULL){
299         if(st==ws->bottom){
300             was_bottom=TRUE;
301             group_do_set_bottom(ws, NULL);
302         }
303         
304         if(st==ws->managed_stdisp){
305             ws->managed_stdisp=NULL;
306             was_stdisp=TRUE;
307         }
308             
309         if(st==ws->current_managed){
310             ws->current_managed=NULL;
311             was_current=TRUE;
312         }
313         
314         next_st=stacking_unstack(REGION_PARENT(ws), st);
315         UNLINK_ITEM(ws->managed_list, st, mgr_next, mgr_prev);
316         stacking_unassoc(st);
317         stacking_free(st);
318     }
319     
320     region_unset_manager(reg, (WRegion*)ws);
321     
322     if(!OBJ_IS_BEING_DESTROYED(ws) && was_current){
323         /* This may still potentially cause problems when focus
324          * change is pending. Perhaps we should use region_await_focus,
325          * if it is pointing to our child (and region_may_control_focus 
326          * fail if it is pointing somewhere else).
327          */
328         WStacking *stf=find_to_focus(ws, next_st, TRUE);
329         if(stf!=NULL && mcf){
330             region_maybewarp_now(stf->reg, FALSE);
331         }else{
332             ws->current_managed=stf;
333         }
334     }
335 }
336
337
338 void group_managed_notify(WGroup *ws, WRegion *reg, WRegionNotify how)
339 {
340     if(how==ioncore_g.notifies.activated || 
341        how==ioncore_g.notifies.pseudoactivated){
342         ws->current_managed=group_find_stacking(ws, reg);
343     }
344 }
345
346
347 /*}}}*/
348
349
350 /*{{{ Create/destroy */
351
352
353 bool group_init(WGroup *ws, WWindow *par, const WFitParams *fp)
354 {
355     ws->current_managed=NULL;
356     ws->managed_stdisp=NULL;
357     ws->bottom=NULL;
358     ws->managed_list=NULL;
359     ws->phs=NULL;
360     
361     ws->dummywin=XCreateWindow(ioncore_g.dpy, par->win,
362                                 fp->g.x, fp->g.y, 1, 1, 0,
363                                 CopyFromParent, InputOnly,
364                                 CopyFromParent, 0, NULL);
365     if(ws->dummywin==None)
366         return FALSE;
367
368     region_init(&ws->reg, par, fp);
369     region_register(&ws->reg);
370
371     XSelectInput(ioncore_g.dpy, ws->dummywin,
372                  FocusChangeMask|KeyPressMask|KeyReleaseMask|
373                  ButtonPressMask|ButtonReleaseMask);
374     XSaveContext(ioncore_g.dpy, ws->dummywin, ioncore_g.win_context,
375                  (XPointer)ws);
376     
377     ((WRegion*)ws)->flags|=REGION_GRAB_ON_PARENT;
378     
379     region_add_bindmap((WRegion*)ws, ioncore_group_bindmap);
380     
381     return TRUE;
382 }
383
384
385 WGroup *create_group(WWindow *par, const WFitParams *fp)
386 {
387     CREATEOBJ_IMPL(WGroup, group, (p, par, fp));
388 }
389
390
391 void group_deinit(WGroup *ws)
392 {
393     WGroupIterTmp tmp;
394     WRegion *reg;
395
396     if(ws->managed_stdisp!=NULL && ws->managed_stdisp->reg!=NULL){
397         group_managed_remove(ws, ws->managed_stdisp->reg);
398         assert(ws->managed_stdisp==NULL);
399     }
400
401     FOR_ALL_MANAGED_BY_GROUP(ws, reg, tmp){
402         destroy_obj((Obj*)reg);
403     }
404
405     assert(ws->managed_list==NULL);
406
407     XDeleteContext(ioncore_g.dpy, ws->dummywin, ioncore_g.win_context);
408     XDestroyWindow(ioncore_g.dpy, ws->dummywin);
409     ws->dummywin=None;
410     
411     while(ws->phs!=NULL)
412         grouppholder_do_unlink(ws->phs);
413     
414     region_deinit(&ws->reg);
415 }
416
417
418 bool group_rescue_clientwins(WGroup *ws, WRescueInfo *info)
419 {
420     WGroupIterTmp tmp;
421     
422     group_iter_init_nostdisp(&tmp, ws);
423     
424     return region_rescue_some_clientwins((WRegion*)ws, info,
425                                          (WRegionIterator*)group_iter,
426                                          &tmp);
427 }
428
429
430 WPHolder *group_get_rescue_pholder_for(WGroup *ws, 
431                                        WRegion *forwhat)
432 {
433     WGroupAttachParams ap=GROUPATTACHPARAMS_INIT;
434     WFramedParam fp=FRAMEDPARAM_INIT;
435     WPHolder *ph;
436     
437     ap.geom_set=TRUE;
438     ap.geom=REGION_GEOM(forwhat);
439
440     ap.geom_weak_set=1;
441     
442     if(REGION_PARENT(forwhat)==REGION_PARENT(ws)){
443         ap.geom.x-=REGION_GEOM(ws).x;
444         ap.geom.y-=REGION_GEOM(ws).y;
445     }else{
446         ap.geom_weak=REGION_RQGEOM_WEAK_X|REGION_RQGEOM_WEAK_Y;
447     }
448     
449     /* frame mode */
450     /*{
451         WFrame *frame=OBJ_CAST(forwhat, WFrame);
452         if(frame!=NULL)
453             fp.mode=frame->mode;
454     }*/
455     
456     ph=(WPHolder*)create_grouppholder(ws, NULL, &ap);
457     
458     return pholder_either((WPHolder*)create_framedpholder(ph, &fp), ph);
459 }
460
461
462
463 /*}}}*/
464
465
466 /*{{{ Bottom */
467
468
469 void group_bottom_set(WGroup *grp)
470 {
471     CALL_DYN(group_bottom_set, grp, (grp));
472 }
473
474
475 static void group_do_set_bottom(WGroup *grp, WStacking *st)
476 {
477     WStacking *was=grp->bottom;
478     WStacking *std=grp->managed_stdisp;
479     
480     grp->bottom=st;
481     
482     if(!OBJ_IS_BEING_DESTROYED(grp)){
483         bool noremanage=((was==st) ||
484                          (was==NULL && std==NULL) || 
485                          (st!=NULL && st==std) || 
486                          (st==NULL && was==std));
487         
488         if(!noremanage &&
489            (st==NULL || HAS_DYN(st->reg, region_manage_stdisp))){
490             group_remanage_stdisp(grp);
491         }
492         
493         group_bottom_set(grp);
494     }
495 }
496
497
498 /*EXTL_DOC
499  * Sets the `bottom' of \var{ws}. The region \var{reg} must already
500  * be managed by \var{ws}, unless \code{nil}.
501  */
502 EXTL_EXPORT_MEMBER
503 bool group_set_bottom(WGroup *ws, WRegion *reg)
504 {
505     WStacking *st=NULL;
506     
507     if(reg!=NULL){
508         st=group_find_stacking(ws, reg);
509         
510         if(st==NULL)
511             return FALSE;
512     }
513         
514     group_do_set_bottom(ws, st);
515     
516     return TRUE;
517 }
518
519
520 /*EXTL_DOC
521  * Returns the `bottom' of \var{ws}.
522  */
523 EXTL_SAFE
524 EXTL_EXPORT_MEMBER
525 WRegion *group_bottom(WGroup *ws)
526 {
527     return (ws->bottom!=NULL ? ws->bottom->reg : NULL);
528 }
529
530
531 /*}}}*/
532
533
534 /*{{{ Attach */
535
536
537 WStacking *group_do_add_managed(WGroup *ws, WRegion *reg, int level,
538                                 WSizePolicy szplcy)
539 {
540     WStacking *st=NULL;
541     CALL_DYN_RET(st, WStacking*, group_do_add_managed, ws, 
542                  (ws, reg, level, szplcy));
543     return st;
544 }
545     
546
547 WStacking *group_do_add_managed_default(WGroup *ws, WRegion *reg, int level,
548                                         WSizePolicy szplcy)
549 {
550     WStacking *st=NULL, *tmp=NULL;
551     Window bottom=None, top=None;
552     WStacking **stackingp=group_get_stackingp(ws);
553     WFrame *frame;
554     
555     if(stackingp==NULL)
556         return NULL;
557     
558     st=create_stacking();
559     
560     if(st==NULL)
561         return NULL;
562     
563     if(!stacking_assoc(st, reg)){
564         stacking_free(st);
565         return  NULL;
566     }
567     
568     frame=OBJ_CAST(reg, WFrame);
569     if(frame!=NULL){
570         if(framemode_unalt(frame_mode(frame))==FRAME_MODE_TILED)
571             frame_set_mode(frame, FRAME_MODE_FLOATING);
572     }
573
574     st->level=level;
575     st->szplcy=szplcy;
576
577     LINK_ITEM_FIRST(tmp, st, next, prev);
578     stacking_weave(stackingp, &tmp, FALSE);
579     assert(tmp==NULL);
580
581     LINK_ITEM(ws->managed_list, st, mgr_next, mgr_prev);
582     region_set_manager(reg, (WRegion*)ws);
583
584     if(region_is_fully_mapped((WRegion*)ws))
585         region_map(reg);
586
587     return st;
588 }
589
590
591 static void geom_group_to_parent(WGroup *ws, const WRectangle *g, 
592                                  WRectangle *wg)
593 {
594     wg->x=g->x+REGION_GEOM(ws).x;
595     wg->y=g->y+REGION_GEOM(ws).y;
596     wg->w=maxof(1, g->w);
597     wg->h=maxof(1, g->h);
598 }
599
600
601 static int group_must_focus(WGroup *ws, WStacking *st)
602 {
603     WStacking *stacking=group_get_stacking(ws);
604     
605     return (stacking!=NULL && stacking_must_focus(stacking, st));
606 }
607
608
609 bool group_do_attach_final(WGroup *ws, 
610                            WRegion *reg,
611                            const WGroupAttachParams *param)
612 {
613     WStacking *st, *stabove=NULL;
614     WSizePolicy szplcy;
615     WFitParams fp;
616     WRectangle g;
617     uint level;
618     int weak;
619     bool sw;
620     
621     /* Stacking  */
622     if(param->stack_above!=NULL)
623         stabove=group_find_stacking(ws, param->stack_above);
624
625     level=(stabove!=NULL
626            ? stabove->level
627            : (param->level_set 
628               ? param->level 
629               : STACKING_LEVEL_NORMAL));
630     
631     /* Fit */
632     szplcy=(param->szplcy_set
633             ? param->szplcy
634             : (param->bottom
635                ? SIZEPOLICY_FULL_EXACT
636                : SIZEPOLICY_UNCONSTRAINED));
637     
638     weak=(param->geom_weak_set
639           ? param->geom_weak
640           : (param->geom_set
641              ? 0
642              : REGION_RQGEOM_WEAK_ALL));
643     
644     if(param->geom_set)
645         geom_group_to_parent(ws, &param->geom, &g);
646     else
647         g=REGION_GEOM(reg);
648     
649     /* If the requested geometry does not overlap the workspaces's geometry, 
650      * position request is never honoured.
651      */
652     if((g.x+g.w<=REGION_GEOM(ws).x) ||
653        (g.x>=REGION_GEOM(ws).x+REGION_GEOM(ws).w)){
654         weak|=REGION_RQGEOM_WEAK_X;
655     }
656        
657     if((g.y+g.h<=REGION_GEOM(ws).y) ||
658        (g.y>=REGION_GEOM(ws).y+REGION_GEOM(ws).h)){
659         weak|=REGION_RQGEOM_WEAK_Y;
660     }
661
662     if(weak&(REGION_RQGEOM_WEAK_X|REGION_RQGEOM_WEAK_Y) &&
663         (szplcy==SIZEPOLICY_UNCONSTRAINED ||
664          szplcy==SIZEPOLICY_FREE ||
665          szplcy==SIZEPOLICY_FREE_GLUE /* without flags */)){
666         /* TODO: use 'weak'? */
667         group_calc_placement(ws, level, &g);
668     }
669
670     fp.g=REGION_GEOM(ws);
671     fp.mode=REGION_FIT_EXACT;
672
673     sizepolicy(&szplcy, reg, &g, weak, &fp);
674
675     if(rectangle_compare(&fp.g, &REGION_GEOM(reg))!=RECTANGLE_SAME)
676         region_fitrep(reg, NULL, &fp);
677     
678     /* Add */
679     st=group_do_add_managed(ws, reg, level, szplcy);
680     
681     if(st==NULL)
682         return FALSE;
683     
684     if(stabove!=NULL)
685         st->above=stabove;
686
687     if(param->bottom)
688         group_do_set_bottom(ws, st);
689     
690     /* Focus */
691     sw=((param->switchto_set ? param->switchto : ioncore_g.switchto_new)
692         ? st==find_to_focus(ws, st, FALSE)
693         : group_must_focus(ws, st));
694     
695     if(sw){
696         if(region_may_control_focus((WRegion*)ws))
697             region_set_focus(st->reg);
698         else
699             ws->current_managed=st;
700     }else if(region_is_fully_mapped(reg)){
701         region_pointer_focus_hack(reg);
702     }
703
704     return TRUE;
705 }
706
707
708 static void group_attach_fp(WGroup *ws, const WGroupAttachParams *param,
709                             WFitParams *fp)
710 {
711     if(param->geom_set){
712         geom_group_to_parent(ws, &param->geom, &fp->g);
713         fp->mode=REGION_FIT_EXACT;
714     }else{
715         fp->g=REGION_GEOM(ws);
716         fp->mode=REGION_FIT_BOUNDS|REGION_FIT_WHATEVER;
717     }
718 }
719
720
721 WRegion *group_do_attach(WGroup *ws, 
722                          /*const*/ WGroupAttachParams *param,
723                          WRegionAttachData *data)
724 {
725     WFitParams fp;
726     WRegion *reg;
727     
728     if(ws->bottom!=NULL && param->bottom){
729         warn(TR("'bottom' already set."));
730         return NULL;
731     }
732     
733     group_attach_fp(ws, param, &fp);
734     
735     return region_attach_helper((WRegion*) ws, REGION_PARENT(ws), &fp, 
736                                 (WRegionDoAttachFn*)group_do_attach_final,
737                                 /*(const WRegionAttachParams*)*/param, data);
738     /*                            ^^^^ doesn't seem to work. */
739 }
740
741
742 void group_get_attach_params(WGroup *ws, ExtlTab tab, 
743                              WGroupAttachParams *par)
744 {
745     int tmp;
746     bool tmpb;
747     char *tmps;
748     ExtlTab g;
749     
750     par->switchto_set=0;
751     par->level_set=0;
752     par->szplcy_set=0;
753     par->geom_set=0;
754     par->bottom=0;
755     
756     if(extl_table_is_bool_set(tab, "bottom")){
757         par->level=STACKING_LEVEL_BOTTOM;
758         par->level_set=1;
759         par->bottom=1;
760     }
761     
762     if(extl_table_gets_i(tab, "level", &tmp)){
763         if(tmp>=0){
764             par->level_set=1;
765             par->level=tmp;
766         }
767     }
768     
769     if(!par->level_set && extl_table_is_bool_set(tab, "modal")){
770         par->level=STACKING_LEVEL_MODAL1;
771         par->level_set=1;
772     }
773     
774     if(extl_table_gets_b(tab, "switchto", &tmpb)){
775         par->switchto=(tmpb!=0);
776         par->switchto_set=1;
777     }
778     
779     if(extl_table_gets_i(tab, "sizepolicy", &tmp)){
780         par->szplcy_set=1;
781         par->szplcy=tmp;
782     }else if(extl_table_gets_s(tab, "sizepolicy", &tmps)){
783         if(string2sizepolicy(tmps, &par->szplcy))
784             par->szplcy_set=1;
785         free(tmps);
786     }
787     
788     if(extl_table_gets_t(tab, "geom", &g)){
789         int n=0;
790         
791         if(extl_table_gets_i(g, "x", &(par->geom.x)))
792             n++;
793         if(extl_table_gets_i(g, "y", &(par->geom.y)))
794             n++;
795         if(extl_table_gets_i(g, "w", &(par->geom.w)))
796             n++;
797         if(extl_table_gets_i(g, "h", &(par->geom.h)))
798             n++;
799         
800         if(n==4)
801             par->geom_set=1;
802         
803         extl_unref_table(g);
804     }
805 }
806
807
808
809 /*EXTL_DOC
810  * Attach and reparent existing region \var{reg} to \var{ws}.
811  * The table \var{param} may contain the fields \var{index} and
812  * \var{switchto} that are interpreted as for \fnref{WMPlex.attach_new}.
813  */
814 EXTL_EXPORT_MEMBER
815 WRegion *group_attach(WGroup *ws, WRegion *reg, ExtlTab param)
816 {
817     WGroupAttachParams par=GROUPATTACHPARAMS_INIT;
818     WRegionAttachData data;
819
820     if(reg==NULL)
821         return NULL;
822     
823     group_get_attach_params(ws, param, &par);
824     
825     data.type=REGION_ATTACH_REPARENT;
826     data.u.reg=reg;
827     
828     return group_do_attach(ws, &par, &data);
829 }
830
831
832 /*EXTL_DOC
833  * Create a new region to be managed by \var{ws}. At least the following
834  * fields in \var{param} are understood:
835  * 
836  * \begin{tabularx}{\linewidth}{lX}
837  *  \tabhead{Field & Description}
838  *  \var{type} & (string) Class of the object to be created. Mandatory. \\
839  *  \var{name} & (string) Name of the object to be created. \\
840  *  \var{switchto} & (boolean) Should the region be switched to? \\
841  *  \var{level} & (integer) Stacking level; default is 1. \\
842  *  \var{modal} & (boolean) Make object modal; ignored if level is set. \\
843  *  \var{sizepolicy} & (string) Size policy; see Section \ref{sec:sizepolicies}. \\
844  *  \var{bottom} & (boolean) Mark the attached region as the
845  *                 ``bottom'' of \var{ws}. \\
846  * \end{tabularx}
847  * 
848  * In addition parameters to the region to be created are passed in this 
849  * same table.
850  */
851 EXTL_EXPORT_MEMBER
852 WRegion *group_attach_new(WGroup *ws, ExtlTab param)
853 {
854     WGroupAttachParams par=GROUPATTACHPARAMS_INIT;
855     WRegionAttachData data;
856
857     group_get_attach_params(ws, param, &par);
858     
859     data.type=REGION_ATTACH_LOAD;
860     data.u.tab=param;
861     
862     return group_do_attach(ws, &par, &data);
863 }
864
865
866 /*}}}*/
867
868
869 /*{{{ Status display support */
870
871
872 static int stdisp_szplcy(const WMPlexSTDispInfo *di, WRegion *stdisp)
873 {
874     int pos=di->pos;
875     int policy=0, gravity=0;
876     
877     if(di->fullsize){
878         if(region_orientation(stdisp)==REGION_ORIENTATION_VERTICAL){
879             if(pos==MPLEX_STDISP_TL || pos==MPLEX_STDISP_BL)
880                 policy=SIZEPOLICY_STRETCH_LEFT;
881             else
882                 policy=SIZEPOLICY_STRETCH_RIGHT;
883         }else{
884             if(pos==MPLEX_STDISP_TL || pos==MPLEX_STDISP_TR)
885                 policy=SIZEPOLICY_STRETCH_TOP;
886             else
887                 policy=SIZEPOLICY_STRETCH_BOTTOM;
888         }
889     }else{
890         policy=SIZEPOLICY_GRAVITY;
891     }
892     
893     if(pos==MPLEX_STDISP_TL)
894         gravity=SIZEPOLICY_VERT_TOP|SIZEPOLICY_HORIZ_LEFT;
895     else if(pos==MPLEX_STDISP_BL)
896         gravity=SIZEPOLICY_VERT_BOTTOM|SIZEPOLICY_HORIZ_LEFT;
897     else if(pos==MPLEX_STDISP_TR)
898         gravity=SIZEPOLICY_VERT_TOP|SIZEPOLICY_HORIZ_RIGHT;
899     else /*if(pos=MPLEX_STDISP_BR)*/
900         gravity=SIZEPOLICY_VERT_BOTTOM|SIZEPOLICY_HORIZ_RIGHT;
901     
902     return (policy|gravity);
903 }
904
905
906 void group_manage_stdisp(WGroup *ws, WRegion *stdisp, 
907                          const WMPlexSTDispInfo *di)
908 {
909     WFitParams fp;
910     uint szplcy;
911     WRegion *b=(ws->bottom==NULL ? NULL : ws->bottom->reg);
912     
913     /* Check if 'bottom' wants to manage the stdisp. */
914     if(b!=NULL 
915        && !OBJ_IS_BEING_DESTROYED(b)
916        && HAS_DYN(b, region_manage_stdisp)){
917         region_manage_stdisp(b, stdisp, di);
918         if(REGION_MANAGER(stdisp)==b)
919             return;
920     }
921     
922     /* No. */
923     
924     szplcy=stdisp_szplcy(di, stdisp)|SIZEPOLICY_SHRUNK;
925     
926     if(ws->managed_stdisp!=NULL && ws->managed_stdisp->reg==stdisp){
927         if(ws->managed_stdisp->szplcy==szplcy)
928             return;
929         ws->managed_stdisp->szplcy=szplcy;
930     }else{
931         region_detach_manager(stdisp);
932         ws->managed_stdisp=group_do_add_managed(ws, stdisp, 
933                                                 STACKING_LEVEL_ON_TOP, 
934                                                 szplcy);
935     }
936     
937     stdisp->flags|=REGION_SKIP_FOCUS;
938     
939     fp.g=REGION_GEOM(ws);
940     fp.mode=0;
941     
942     sizepolicy(&ws->managed_stdisp->szplcy, stdisp, NULL, 0, &fp);
943
944     region_fitrep(stdisp, NULL, &fp);
945 }
946
947
948 static void group_remanage_stdisp(WGroup *ws)
949 {
950     WMPlex *mplex=OBJ_CAST(REGION_MANAGER(ws), WMPlex);
951     
952     if(mplex!=NULL && 
953        mplex->mx_current!=NULL && 
954        mplex->mx_current->st->reg==(WRegion*)ws){
955         mplex_remanage_stdisp(mplex);
956     }
957 }
958
959
960 /*}}}*/
961
962
963 /*{{{ Geometry requests */
964
965
966 void group_managed_rqgeom(WGroup *ws, WRegion *reg,
967                           const WRQGeomParams *rq,
968                           WRectangle *geomret)
969 {
970     WFitParams fp;
971     WStacking *st;
972         
973     st=group_find_stacking(ws, reg);
974
975     if(st==NULL){
976         fp.g=rq->geom;
977         fp.mode=REGION_FIT_EXACT;
978     }else{
979         fp.g=REGION_GEOM(ws);
980         fp.mode=0;
981         sizepolicy(&st->szplcy, reg, &rq->geom, rq->flags, &fp);
982     }
983     
984     if(geomret!=NULL)
985         *geomret=fp.g;
986     
987     if(!(rq->flags&REGION_RQGEOM_TRYONLY))
988         region_fitrep(reg, NULL, &fp);
989 }
990
991
992 void group_managed_rqgeom_absolute(WGroup *grp, WRegion *sub, 
993                                    const WRQGeomParams *rq,
994                                    WRectangle *geomret)
995 {
996     if(grp->bottom!=NULL && grp->bottom->reg==sub){
997         region_rqgeom((WRegion*)grp, rq, geomret);
998         if(!(rq->flags&REGION_RQGEOM_TRYONLY) && geomret!=NULL)
999             *geomret=REGION_GEOM(sub);
1000     }else{
1001         WRQGeomParams rq2=*rq;
1002         rq2.flags&=~REGION_RQGEOM_ABSOLUTE;
1003
1004         region_managed_rqgeom((WRegion*)grp, sub, &rq2, geomret);
1005     }
1006 }
1007
1008
1009 /*}}}*/
1010
1011
1012 /*{{{ Navigation */
1013
1014
1015 static WStacking *nxt(WGroup *ws, WStacking *st, bool wrap)
1016 {
1017     return (st->mgr_next!=NULL 
1018             ? st->mgr_next 
1019             : (wrap ? ws->managed_list : NULL));
1020 }
1021
1022
1023 static WStacking *prv(WGroup *ws, WStacking *st, bool wrap)
1024 {
1025     return (st!=ws->managed_list 
1026             ? st->mgr_prev
1027             : (wrap ? st->mgr_prev : NULL));
1028 }
1029
1030
1031 typedef WStacking *NxtFn(WGroup *ws, WStacking *st, bool wrap);
1032
1033
1034 static bool focusable(WGroup *ws, WStacking *st, uint min_level)
1035 {
1036     return (st->reg!=NULL
1037             && REGION_IS_MAPPED(st->reg)
1038             && !(st->reg->flags&REGION_SKIP_FOCUS)
1039             && st->level>=min_level);
1040 }
1041
1042        
1043 static WStacking *do_get_next(WGroup *ws, WStacking *sti,
1044                               NxtFn *fn, bool wrap, bool sti_ok)
1045 {
1046     WStacking *st, *stacking;
1047     uint min_level=0;
1048     
1049     stacking=group_get_stacking(ws);
1050     
1051     if(stacking!=NULL)
1052         min_level=stacking_min_level_mapped(stacking);
1053
1054     st=sti;
1055     while(1){
1056         st=fn(ws, st, wrap); 
1057         
1058         if(st==NULL || st==sti)
1059             break;
1060         
1061         if(focusable(ws, st, min_level))
1062             return st;
1063     }
1064
1065     if(sti_ok && focusable(ws, sti, min_level))
1066         return sti;
1067     
1068     return NULL;
1069 }
1070
1071
1072 static WStacking *group_do_navi_first(WGroup *ws, WRegionNavi nh)
1073 {
1074     WStacking *lst=ws->managed_list;
1075     
1076     if(lst==NULL)
1077         return NULL;
1078
1079     if(nh==REGION_NAVI_ANY && 
1080        ws->current_managed!=NULL &&
1081        ws->current_managed->reg!=NULL){
1082         return ws->current_managed;
1083     }
1084     
1085     if(nh==REGION_NAVI_ANY || nh==REGION_NAVI_END || 
1086        nh==REGION_NAVI_BOTTOM || nh==REGION_NAVI_RIGHT){
1087         return do_get_next(ws, lst, prv, TRUE, TRUE);
1088     }else{
1089         return do_get_next(ws, lst->mgr_prev, nxt, TRUE, TRUE);
1090     }
1091 }
1092
1093
1094 static WRegion *group_navi_first(WGroup *ws, WRegionNavi nh,
1095                                  WRegionNaviData *data)
1096 {
1097     WStacking *st=group_do_navi_first(ws, nh);
1098     
1099     return region_navi_cont(&ws->reg, (st!=NULL ? st->reg : NULL), data);
1100 }
1101
1102
1103 static WStacking *group_do_navi_next(WGroup *ws, WStacking *st, 
1104                                      WRegionNavi nh, bool wrap)
1105 {
1106     if(st==NULL)
1107         return group_do_navi_first(ws, nh);
1108     
1109     if(nh==REGION_NAVI_ANY || nh==REGION_NAVI_END || 
1110        nh==REGION_NAVI_BOTTOM || nh==REGION_NAVI_RIGHT){
1111         return do_get_next(ws, st, nxt, wrap, FALSE);
1112     }else{
1113         return do_get_next(ws, st, prv, wrap, FALSE);
1114     }
1115 }
1116
1117 static WRegion *group_navi_next(WGroup *ws, WRegion *reg, 
1118                                 WRegionNavi nh, WRegionNaviData *data)
1119 {
1120     WStacking *st=group_find_stacking(ws, reg);
1121     
1122     st=group_do_navi_next(ws, st, nh, FALSE);
1123     
1124     return region_navi_cont(&ws->reg, (st!=NULL ? st->reg : NULL), data);
1125 }
1126
1127
1128 /*}}}*/
1129
1130
1131 /*{{{ Stacking */
1132
1133
1134 /* 
1135  * Note: Managed objects are considered to be stacked separately from the
1136  * group, slightly violating expectations.
1137  */
1138
1139 void group_stacking(WGroup *ws, Window *bottomret, Window *topret)
1140 {
1141     Window win=region_xwindow((WRegion*)ws);
1142     
1143     *bottomret=win;
1144     *topret=win;
1145 }
1146
1147
1148 void group_restack(WGroup *ws, Window other, int mode)
1149 {
1150     Window win;
1151     
1152     win=region_xwindow((WRegion*)ws);
1153     if(win!=None){
1154         xwindow_restack(win, other, mode);
1155         other=win;
1156         mode=Above;
1157     }
1158 }
1159
1160
1161 WStacking *group_find_stacking(WGroup *ws, WRegion *r)
1162 {
1163     if(r==NULL || REGION_MANAGER(r)!=(WRegion*)ws)
1164         return NULL;
1165     
1166     return ioncore_find_stacking(r);
1167 }
1168
1169
1170 static WStacking *find_stacking_if_not_on_ws(WGroup *ws, Window w)
1171 {
1172     WRegion *r=xwindow_region_of(w);
1173     WStacking *st=NULL;
1174     
1175     while(r!=NULL){
1176         if(REGION_MANAGER(r)==(WRegion*)ws)
1177             break;
1178         st=group_find_stacking(ws, r);
1179         if(st!=NULL)
1180             break;
1181         r=REGION_MANAGER(r);
1182     }
1183     
1184     return st;
1185 }
1186
1187
1188 bool group_managed_rqorder(WGroup *grp, WRegion *reg, WRegionOrder order)
1189 {
1190     WStacking **stackingp=group_get_stackingp(grp);
1191     WStacking *st;
1192
1193     if(stackingp==NULL || *stackingp==NULL)
1194         return FALSE;
1195
1196     st=group_find_stacking(grp, reg);
1197     
1198     if(st==NULL)
1199         return FALSE;
1200     
1201     stacking_restack(stackingp, st, None, NULL, NULL,
1202                      (order!=REGION_ORDER_FRONT));
1203     
1204     return TRUE;
1205 }
1206
1207
1208 /*}}}*/
1209
1210
1211 /*{{{ Misc. */
1212
1213
1214 /*EXTL_DOC
1215  * Iterate over managed regions of \var{ws} until \var{iterfn} returns
1216  * \code{false}.
1217  * The function is called in protected mode.
1218  * This routine returns \code{true} if it reaches the end of list
1219  * without this happening.
1220  */
1221 EXTL_SAFE
1222 EXTL_EXPORT_MEMBER
1223 bool group_managed_i(WGroup *ws, ExtlFn iterfn)
1224 {
1225     WGroupIterTmp tmp;
1226     group_iter_init(&tmp, ws);
1227     
1228     return extl_iter_objlist_(iterfn, (ObjIterator*)group_iter, &tmp);
1229 }
1230
1231
1232 WRegion* group_current(WGroup *ws)
1233 {
1234     return (ws->current_managed!=NULL ? ws->current_managed->reg : NULL);
1235 }
1236
1237
1238 void group_size_hints(WGroup *ws, WSizeHints *hints_ret)
1239 {
1240     if(ws->bottom==NULL || ws->bottom->reg==NULL){
1241         sizehints_clear(hints_ret);
1242     }else{
1243         region_size_hints(ws->bottom->reg, hints_ret);
1244         hints_ret->no_constrain=TRUE;
1245     }
1246 }
1247
1248
1249 Window group_xwindow(const WGroup *ws)
1250 {
1251     return ws->dummywin;
1252 }
1253
1254
1255 /*EXTL_DOC
1256  * Returns the group of \var{reg}, if it is managed by one,
1257  * and \var{reg} itself otherwise.
1258  */
1259 /*EXTL_EXPORT_MEMBER
1260 WRegion *region_group_of(WRegion *reg)
1261 {
1262     WRegion *mgr=REGION_MANAGER(reg);
1263     
1264     return (OBJ_IS(mgr, WGroup) ? mgr : reg);
1265 }*/
1266
1267
1268 /*EXTL_DOC
1269  * Returns the group of \var{reg}, if \var{reg} is its bottom,
1270  * and \var{reg} itself otherwise.
1271  */
1272 EXTL_EXPORT_MEMBER
1273 WRegion *region_groupleader_of(WRegion *reg)
1274 {
1275     WGroup *grp=REGION_MANAGER_CHK(reg, WGroup);
1276     
1277     return ((grp!=NULL && group_bottom(grp)==reg)
1278             ? (WRegion*)grp
1279             : reg);
1280 }
1281
1282
1283 /*}}}*/
1284
1285
1286 /*{{{ Save/load */
1287
1288
1289 static ExtlTab group_get_configuration(WGroup *ws)
1290 {
1291     ExtlTab tab, mgds, subtab, g;
1292     WStacking *st;
1293     WGroupIterTmp tmp;
1294     WMPlex *par;
1295     int n=0;
1296     WRectangle tmpg;
1297     
1298     tab=region_get_base_configuration((WRegion*)ws);
1299     
1300     mgds=extl_create_table();
1301     
1302     extl_table_sets_t(tab, "managed", mgds);
1303     
1304     /* TODO: stacking order messed up */
1305     
1306     FOR_ALL_NODES_IN_GROUP(ws, st, tmp){
1307         if(st->reg==NULL)
1308             continue;
1309         
1310         subtab=region_get_configuration(st->reg);
1311
1312         if(subtab!=extl_table_none()){
1313             extl_table_sets_s(subtab, "sizepolicy", 
1314                               sizepolicy2string(st->szplcy));
1315             extl_table_sets_i(subtab, "level", st->level);
1316         
1317             tmpg=REGION_GEOM(st->reg);
1318             tmpg.x-=REGION_GEOM(ws).x;
1319             tmpg.y-=REGION_GEOM(ws).y;
1320             
1321             g=extl_table_from_rectangle(&tmpg);
1322             extl_table_sets_t(subtab, "geom", g);
1323             extl_unref_table(g);
1324             
1325             if(ws->bottom==st)
1326                 extl_table_sets_b(subtab, "bottom", TRUE);
1327         
1328             extl_table_seti_t(mgds, ++n, subtab);
1329             extl_unref_table(subtab);
1330         }
1331     }
1332     
1333     extl_unref_table(mgds);
1334     
1335     return tab;
1336 }
1337
1338     
1339 void group_do_load(WGroup *ws, ExtlTab tab)
1340 {
1341     ExtlTab substab, subtab;
1342     int i, n;
1343     
1344     if(extl_table_gets_t(tab, "managed", &substab)){
1345         n=extl_table_get_n(substab);
1346         for(i=1; i<=n; i++){
1347             if(extl_table_geti_t(substab, i, &subtab)){
1348                 WGroupAttachParams par=GROUPATTACHPARAMS_INIT;
1349                 WRegionAttachData data;
1350                 WFitParams fp;
1351                 WPHolder *ph;
1352                 
1353                 group_get_attach_params(ws, subtab, &par);
1354                 group_attach_fp(ws, &par, &fp);
1355                 
1356                 ph=(WPHolder*)create_grouppholder(ws, NULL, &par);
1357                 
1358                 region_attach_load_helper((WRegion*)ws, REGION_PARENT(ws), &fp,
1359                                           (WRegionDoAttachFn*)group_do_attach_final,
1360                                           (void*)&par, subtab, &ph);
1361                                               
1362                 if(ph!=NULL)
1363                     destroy_obj((Obj*)ph);
1364                 
1365                 extl_unref_table(subtab);
1366             }
1367         }
1368         
1369         extl_unref_table(substab);
1370     }
1371 }
1372
1373
1374 WRegion *group_load(WWindow *par, const WFitParams *fp, ExtlTab tab)
1375 {
1376     WGroup *ws;
1377     
1378     ws=create_group(par, fp);
1379     
1380     if(ws==NULL)
1381         return NULL;
1382         
1383     group_do_load(ws, tab);
1384     
1385     return (WRegion*)ws;
1386 }
1387
1388
1389 /*}}}*/
1390
1391
1392 /*{{{ Dynamic function table and class implementation */
1393
1394
1395 static DynFunTab group_dynfuntab[]={
1396     {(DynFun*)region_fitrep,
1397      (DynFun*)group_fitrep},
1398
1399     {region_map, 
1400      group_map},
1401     
1402     {region_unmap, 
1403      group_unmap},
1404     
1405     {(DynFun*)region_managed_prepare_focus, 
1406      (DynFun*)group_managed_prepare_focus},
1407
1408     {region_do_set_focus, 
1409      group_do_set_focus},
1410     
1411     {region_managed_notify, 
1412      group_managed_notify},
1413     
1414     {region_managed_remove,
1415      group_managed_remove},
1416     
1417     {(DynFun*)region_get_configuration, 
1418      (DynFun*)group_get_configuration},
1419
1420     {(DynFun*)region_current,
1421      (DynFun*)group_current},
1422     
1423     {(DynFun*)region_rescue_clientwins,
1424      (DynFun*)group_rescue_clientwins},
1425     
1426     {region_restack,
1427      group_restack},
1428
1429     {region_stacking,
1430      group_stacking},
1431
1432     {(DynFun*)region_managed_get_pholder,
1433      (DynFun*)group_managed_get_pholder},
1434
1435     {region_managed_rqgeom,
1436      group_managed_rqgeom},
1437      
1438     {region_managed_rqgeom_absolute,
1439      group_managed_rqgeom_absolute},
1440     
1441     {(DynFun*)group_do_add_managed,
1442      (DynFun*)group_do_add_managed_default},
1443     
1444     {region_size_hints,
1445      group_size_hints},
1446     
1447     {(DynFun*)region_xwindow,
1448      (DynFun*)group_xwindow},
1449     
1450     {(DynFun*)region_navi_first,
1451      (DynFun*)group_navi_first},
1452     
1453     {(DynFun*)region_navi_next,
1454      (DynFun*)group_navi_next},
1455     
1456     {(DynFun*)region_managed_rqorder,
1457      (DynFun*)group_managed_rqorder},
1458      
1459     {(DynFun*)region_get_rescue_pholder_for,
1460      (DynFun*)group_get_rescue_pholder_for},
1461     
1462     END_DYNFUNTAB
1463 };
1464
1465
1466 EXTL_EXPORT
1467 IMPLCLASS(WGroup, WRegion, group_deinit, group_dynfuntab);
1468
1469
1470 /*}}}*/
1471