]> git.decadent.org.uk Git - ion3.git/blob - ioncore/group.c
ad38ee8dd5a2a8129a7240236b4393519c015b84
[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     if(!param->whatever){
639         weak=(param->geom_weak_set
640               ? param->geom_weak
641               : (param->geom_set
642                  ? 0
643                  : REGION_RQGEOM_WEAK_ALL));
644         
645         if(param->geom_set)
646             geom_group_to_parent(ws, &param->geom, &g);
647         else
648             g=REGION_GEOM(reg);
649         
650         /* If the requested geometry does not overlap the workspaces's geometry, 
651          * position request is never honoured.
652          */
653         if((g.x+g.w<=REGION_GEOM(ws).x) ||
654            (g.x>=REGION_GEOM(ws).x+REGION_GEOM(ws).w)){
655             weak|=REGION_RQGEOM_WEAK_X;
656         }
657            
658         if((g.y+g.h<=REGION_GEOM(ws).y) ||
659            (g.y>=REGION_GEOM(ws).y+REGION_GEOM(ws).h)){
660             weak|=REGION_RQGEOM_WEAK_Y;
661         }
662
663         if(weak&(REGION_RQGEOM_WEAK_X|REGION_RQGEOM_WEAK_Y) &&
664             (szplcy==SIZEPOLICY_UNCONSTRAINED ||
665              szplcy==SIZEPOLICY_FREE ||
666              szplcy==SIZEPOLICY_FREE_GLUE /* without flags */)){
667             /* TODO: use 'weak'? */
668             group_calc_placement(ws, level, &g);
669         }
670
671         fp.g=REGION_GEOM(ws);
672         fp.mode=REGION_FIT_EXACT;
673
674         sizepolicy(&szplcy, reg, &g, weak, &fp);
675
676         if(rectangle_compare(&fp.g, &REGION_GEOM(reg))!=RECTANGLE_SAME)
677             region_fitrep(reg, NULL, &fp);
678     }
679     
680     /* Add */
681     st=group_do_add_managed(ws, reg, level, szplcy);
682     
683     if(st==NULL)
684         return FALSE;
685     
686     if(stabove!=NULL)
687         st->above=stabove;
688
689     if(param->bottom)
690         group_do_set_bottom(ws, st);
691     
692     /* Focus */
693     sw=((param->switchto_set ? param->switchto : ioncore_g.switchto_new)
694         ? st==find_to_focus(ws, st, FALSE)
695         : group_must_focus(ws, st));
696     
697     if(sw){
698         if(region_may_control_focus((WRegion*)ws))
699             region_set_focus(st->reg);
700         else
701             ws->current_managed=st;
702     }else if(region_is_fully_mapped(reg)){
703         region_pointer_focus_hack(reg);
704     }
705
706     return TRUE;
707 }
708
709
710 static void group_attach_fp(WGroup *ws, const WGroupAttachParams *param,
711                             WFitParams *fp)
712 {
713     if(param->geom_set){
714         geom_group_to_parent(ws, &param->geom, &fp->g);
715         fp->mode=REGION_FIT_EXACT;
716     }else{
717         fp->g=REGION_GEOM(ws);
718         fp->mode=REGION_FIT_BOUNDS|REGION_FIT_WHATEVER;
719     }
720 }
721
722
723 WRegion *group_do_attach(WGroup *ws, 
724                          /*const*/ WGroupAttachParams *param,
725                          WRegionAttachData *data)
726 {
727     WFitParams fp;
728     WRegion *reg;
729     
730     if(ws->bottom!=NULL && param->bottom){
731         warn(TR("'bottom' already set."));
732         return NULL;
733     }
734     
735     group_attach_fp(ws, param, &fp);
736     
737     return region_attach_helper((WRegion*) ws, REGION_PARENT(ws), &fp, 
738                                 (WRegionDoAttachFn*)group_do_attach_final,
739                                 /*(const WRegionAttachParams*)*/param, data);
740     /*                            ^^^^ doesn't seem to work. */
741 }
742
743
744 void group_get_attach_params(WGroup *ws, ExtlTab tab, 
745                              WGroupAttachParams *par)
746 {
747     int tmp;
748     bool tmpb;
749     char *tmps;
750     ExtlTab g;
751     
752     par->switchto_set=0;
753     par->level_set=0;
754     par->szplcy_set=0;
755     par->geom_set=0;
756     par->bottom=0;
757     
758     if(extl_table_is_bool_set(tab, "bottom")){
759         par->level=STACKING_LEVEL_BOTTOM;
760         par->level_set=1;
761         par->bottom=1;
762     }
763     
764     if(extl_table_gets_i(tab, "level", &tmp)){
765         if(tmp>=0){
766             par->level_set=1;
767             par->level=tmp;
768         }
769     }
770     
771     if(!par->level_set && extl_table_is_bool_set(tab, "modal")){
772         par->level=STACKING_LEVEL_MODAL1;
773         par->level_set=1;
774     }
775     
776     if(extl_table_gets_b(tab, "switchto", &tmpb)){
777         par->switchto=(tmpb!=0);
778         par->switchto_set=1;
779     }
780     
781     if(extl_table_gets_i(tab, "sizepolicy", &tmp)){
782         par->szplcy_set=1;
783         par->szplcy=tmp;
784     }else if(extl_table_gets_s(tab, "sizepolicy", &tmps)){
785         if(string2sizepolicy(tmps, &par->szplcy))
786             par->szplcy_set=1;
787         free(tmps);
788     }
789     
790     if(extl_table_gets_t(tab, "geom", &g)){
791         int n=0;
792         
793         if(extl_table_gets_i(g, "x", &(par->geom.x)))
794             n++;
795         if(extl_table_gets_i(g, "y", &(par->geom.y)))
796             n++;
797         if(extl_table_gets_i(g, "w", &(par->geom.w)))
798             n++;
799         if(extl_table_gets_i(g, "h", &(par->geom.h)))
800             n++;
801         
802         if(n==4)
803             par->geom_set=1;
804         
805         extl_unref_table(g);
806     }
807 }
808
809
810
811 /*EXTL_DOC
812  * Attach and reparent existing region \var{reg} to \var{ws}.
813  * The table \var{param} may contain the fields \var{index} and
814  * \var{switchto} that are interpreted as for \fnref{WMPlex.attach_new}.
815  */
816 EXTL_EXPORT_MEMBER
817 WRegion *group_attach(WGroup *ws, WRegion *reg, ExtlTab param)
818 {
819     WGroupAttachParams par=GROUPATTACHPARAMS_INIT;
820     WRegionAttachData data;
821
822     if(reg==NULL)
823         return NULL;
824     
825     group_get_attach_params(ws, param, &par);
826     
827     data.type=REGION_ATTACH_REPARENT;
828     data.u.reg=reg;
829     
830     return group_do_attach(ws, &par, &data);
831 }
832
833
834 /*EXTL_DOC
835  * Create a new region to be managed by \var{ws}. At least the following
836  * fields in \var{param} are understood:
837  * 
838  * \begin{tabularx}{\linewidth}{lX}
839  *  \tabhead{Field & Description}
840  *  \var{type} & (string) Class of the object to be created. Mandatory. \\
841  *  \var{name} & (string) Name of the object to be created. \\
842  *  \var{switchto} & (boolean) Should the region be switched to? \\
843  *  \var{level} & (integer) Stacking level; default is 1. \\
844  *  \var{modal} & (boolean) Make object modal; ignored if level is set. \\
845  *  \var{sizepolicy} & (string) Size policy; see Section \ref{sec:sizepolicies}. \\
846  *  \var{bottom} & (boolean) Mark the attached region as the
847  *                 ``bottom'' of \var{ws}. \\
848  * \end{tabularx}
849  * 
850  * In addition parameters to the region to be created are passed in this 
851  * same table.
852  */
853 EXTL_EXPORT_MEMBER
854 WRegion *group_attach_new(WGroup *ws, ExtlTab param)
855 {
856     WGroupAttachParams par=GROUPATTACHPARAMS_INIT;
857     WRegionAttachData data;
858
859     group_get_attach_params(ws, param, &par);
860     
861     data.type=REGION_ATTACH_LOAD;
862     data.u.tab=param;
863     
864     return group_do_attach(ws, &par, &data);
865 }
866
867
868 /*}}}*/
869
870
871 /*{{{ Status display support */
872
873
874 static int stdisp_szplcy(const WMPlexSTDispInfo *di, WRegion *stdisp)
875 {
876     int pos=di->pos;
877     int policy=0, gravity=0;
878     
879     if(di->fullsize){
880         if(region_orientation(stdisp)==REGION_ORIENTATION_VERTICAL){
881             if(pos==MPLEX_STDISP_TL || pos==MPLEX_STDISP_BL)
882                 policy=SIZEPOLICY_STRETCH_LEFT;
883             else
884                 policy=SIZEPOLICY_STRETCH_RIGHT;
885         }else{
886             if(pos==MPLEX_STDISP_TL || pos==MPLEX_STDISP_TR)
887                 policy=SIZEPOLICY_STRETCH_TOP;
888             else
889                 policy=SIZEPOLICY_STRETCH_BOTTOM;
890         }
891     }else{
892         policy=SIZEPOLICY_GRAVITY;
893     }
894     
895     if(pos==MPLEX_STDISP_TL)
896         gravity=SIZEPOLICY_VERT_TOP|SIZEPOLICY_HORIZ_LEFT;
897     else if(pos==MPLEX_STDISP_BL)
898         gravity=SIZEPOLICY_VERT_BOTTOM|SIZEPOLICY_HORIZ_LEFT;
899     else if(pos==MPLEX_STDISP_TR)
900         gravity=SIZEPOLICY_VERT_TOP|SIZEPOLICY_HORIZ_RIGHT;
901     else /*if(pos=MPLEX_STDISP_BR)*/
902         gravity=SIZEPOLICY_VERT_BOTTOM|SIZEPOLICY_HORIZ_RIGHT;
903     
904     return (policy|gravity);
905 }
906
907
908 void group_manage_stdisp(WGroup *ws, WRegion *stdisp, 
909                          const WMPlexSTDispInfo *di)
910 {
911     WFitParams fp;
912     uint szplcy;
913     WRegion *b=(ws->bottom==NULL ? NULL : ws->bottom->reg);
914     
915     /* Check if 'bottom' wants to manage the stdisp. */
916     if(b!=NULL 
917        && !OBJ_IS_BEING_DESTROYED(b)
918        && HAS_DYN(b, region_manage_stdisp)){
919         region_manage_stdisp(b, stdisp, di);
920         if(REGION_MANAGER(stdisp)==b)
921             return;
922     }
923     
924     /* No. */
925     
926     szplcy=stdisp_szplcy(di, stdisp)|SIZEPOLICY_SHRUNK;
927     
928     if(ws->managed_stdisp!=NULL && ws->managed_stdisp->reg==stdisp){
929         if(ws->managed_stdisp->szplcy==szplcy)
930             return;
931         ws->managed_stdisp->szplcy=szplcy;
932     }else{
933         region_detach_manager(stdisp);
934         ws->managed_stdisp=group_do_add_managed(ws, stdisp, 
935                                                 STACKING_LEVEL_ON_TOP, 
936                                                 szplcy);
937     }
938     
939     stdisp->flags|=REGION_SKIP_FOCUS;
940     
941     fp.g=REGION_GEOM(ws);
942     fp.mode=0;
943     
944     sizepolicy(&ws->managed_stdisp->szplcy, stdisp, NULL, 0, &fp);
945
946     region_fitrep(stdisp, NULL, &fp);
947 }
948
949
950 static void group_remanage_stdisp(WGroup *ws)
951 {
952     WMPlex *mplex=OBJ_CAST(REGION_MANAGER(ws), WMPlex);
953     
954     if(mplex!=NULL && 
955        mplex->mx_current!=NULL && 
956        mplex->mx_current->st->reg==(WRegion*)ws){
957         mplex_remanage_stdisp(mplex);
958     }
959 }
960
961
962 /*}}}*/
963
964
965 /*{{{ Geometry requests */
966
967
968 void group_managed_rqgeom(WGroup *ws, WRegion *reg,
969                           const WRQGeomParams *rq,
970                           WRectangle *geomret)
971 {
972     WFitParams fp;
973     WStacking *st;
974         
975     st=group_find_stacking(ws, reg);
976
977     if(st==NULL){
978         fp.g=rq->geom;
979         fp.mode=REGION_FIT_EXACT;
980     }else{
981         fp.g=REGION_GEOM(ws);
982         fp.mode=0;
983         sizepolicy(&st->szplcy, reg, &rq->geom, rq->flags, &fp);
984     }
985     
986     if(geomret!=NULL)
987         *geomret=fp.g;
988     
989     if(!(rq->flags&REGION_RQGEOM_TRYONLY))
990         region_fitrep(reg, NULL, &fp);
991 }
992
993
994 void group_managed_rqgeom_absolute(WGroup *grp, WRegion *sub, 
995                                    const WRQGeomParams *rq,
996                                    WRectangle *geomret)
997 {
998     if(grp->bottom!=NULL && grp->bottom->reg==sub){
999         region_rqgeom((WRegion*)grp, rq, geomret);
1000         if(!(rq->flags&REGION_RQGEOM_TRYONLY) && geomret!=NULL)
1001             *geomret=REGION_GEOM(sub);
1002     }else{
1003         WRQGeomParams rq2=*rq;
1004         rq2.flags&=~REGION_RQGEOM_ABSOLUTE;
1005
1006         region_managed_rqgeom((WRegion*)grp, sub, &rq2, geomret);
1007     }
1008 }
1009
1010
1011 /*}}}*/
1012
1013
1014 /*{{{ Navigation */
1015
1016
1017 static WStacking *nxt(WGroup *ws, WStacking *st, bool wrap)
1018 {
1019     return (st->mgr_next!=NULL 
1020             ? st->mgr_next 
1021             : (wrap ? ws->managed_list : NULL));
1022 }
1023
1024
1025 static WStacking *prv(WGroup *ws, WStacking *st, bool wrap)
1026 {
1027     return (st!=ws->managed_list 
1028             ? st->mgr_prev
1029             : (wrap ? st->mgr_prev : NULL));
1030 }
1031
1032
1033 typedef WStacking *NxtFn(WGroup *ws, WStacking *st, bool wrap);
1034
1035
1036 static bool focusable(WGroup *ws, WStacking *st, uint min_level)
1037 {
1038     return (st->reg!=NULL
1039             && REGION_IS_MAPPED(st->reg)
1040             && !(st->reg->flags&REGION_SKIP_FOCUS)
1041             && st->level>=min_level);
1042 }
1043
1044        
1045 static WStacking *do_get_next(WGroup *ws, WStacking *sti,
1046                               NxtFn *fn, bool wrap, bool sti_ok)
1047 {
1048     WStacking *st, *stacking;
1049     uint min_level=0;
1050     
1051     stacking=group_get_stacking(ws);
1052     
1053     if(stacking!=NULL)
1054         min_level=stacking_min_level_mapped(stacking);
1055
1056     st=sti;
1057     while(1){
1058         st=fn(ws, st, wrap); 
1059         
1060         if(st==NULL || st==sti)
1061             break;
1062         
1063         if(focusable(ws, st, min_level))
1064             return st;
1065     }
1066
1067     if(sti_ok && focusable(ws, sti, min_level))
1068         return sti;
1069     
1070     return NULL;
1071 }
1072
1073
1074 static WStacking *group_do_navi_first(WGroup *ws, WRegionNavi nh)
1075 {
1076     WStacking *lst=ws->managed_list;
1077     
1078     if(lst==NULL)
1079         return NULL;
1080
1081     if(nh==REGION_NAVI_ANY && 
1082        ws->current_managed!=NULL &&
1083        ws->current_managed->reg!=NULL){
1084         return ws->current_managed;
1085     }
1086     
1087     if(nh==REGION_NAVI_ANY || nh==REGION_NAVI_END || 
1088        nh==REGION_NAVI_BOTTOM || nh==REGION_NAVI_RIGHT){
1089         return do_get_next(ws, lst, prv, TRUE, TRUE);
1090     }else{
1091         return do_get_next(ws, lst->mgr_prev, nxt, TRUE, TRUE);
1092     }
1093 }
1094
1095
1096 static WRegion *group_navi_first(WGroup *ws, WRegionNavi nh,
1097                                  WRegionNaviData *data)
1098 {
1099     WStacking *st=group_do_navi_first(ws, nh);
1100     
1101     return region_navi_cont(&ws->reg, (st!=NULL ? st->reg : NULL), data);
1102 }
1103
1104
1105 static WStacking *group_do_navi_next(WGroup *ws, WStacking *st, 
1106                                      WRegionNavi nh, bool wrap)
1107 {
1108     if(st==NULL)
1109         return group_do_navi_first(ws, nh);
1110     
1111     if(nh==REGION_NAVI_ANY || nh==REGION_NAVI_END || 
1112        nh==REGION_NAVI_BOTTOM || nh==REGION_NAVI_RIGHT){
1113         return do_get_next(ws, st, nxt, wrap, FALSE);
1114     }else{
1115         return do_get_next(ws, st, prv, wrap, FALSE);
1116     }
1117 }
1118
1119 static WRegion *group_navi_next(WGroup *ws, WRegion *reg, 
1120                                 WRegionNavi nh, WRegionNaviData *data)
1121 {
1122     WStacking *st=group_find_stacking(ws, reg);
1123     
1124     st=group_do_navi_next(ws, st, nh, FALSE);
1125     
1126     return region_navi_cont(&ws->reg, (st!=NULL ? st->reg : NULL), data);
1127 }
1128
1129
1130 /*}}}*/
1131
1132
1133 /*{{{ Stacking */
1134
1135
1136 /* 
1137  * Note: Managed objects are considered to be stacked separately from the
1138  * group, slightly violating expectations.
1139  */
1140
1141 void group_stacking(WGroup *ws, Window *bottomret, Window *topret)
1142 {
1143     Window win=region_xwindow((WRegion*)ws);
1144     
1145     *bottomret=win;
1146     *topret=win;
1147 }
1148
1149
1150 void group_restack(WGroup *ws, Window other, int mode)
1151 {
1152     Window win;
1153     
1154     win=region_xwindow((WRegion*)ws);
1155     if(win!=None){
1156         xwindow_restack(win, other, mode);
1157         other=win;
1158         mode=Above;
1159     }
1160 }
1161
1162
1163 WStacking *group_find_stacking(WGroup *ws, WRegion *r)
1164 {
1165     if(r==NULL || REGION_MANAGER(r)!=(WRegion*)ws)
1166         return NULL;
1167     
1168     return ioncore_find_stacking(r);
1169 }
1170
1171
1172 static WStacking *find_stacking_if_not_on_ws(WGroup *ws, Window w)
1173 {
1174     WRegion *r=xwindow_region_of(w);
1175     WStacking *st=NULL;
1176     
1177     while(r!=NULL){
1178         if(REGION_MANAGER(r)==(WRegion*)ws)
1179             break;
1180         st=group_find_stacking(ws, r);
1181         if(st!=NULL)
1182             break;
1183         r=REGION_MANAGER(r);
1184     }
1185     
1186     return st;
1187 }
1188
1189
1190 bool group_managed_rqorder(WGroup *grp, WRegion *reg, WRegionOrder order)
1191 {
1192     WStacking **stackingp=group_get_stackingp(grp);
1193     WStacking *st;
1194
1195     if(stackingp==NULL || *stackingp==NULL)
1196         return FALSE;
1197
1198     st=group_find_stacking(grp, reg);
1199     
1200     if(st==NULL)
1201         return FALSE;
1202     
1203     stacking_restack(stackingp, st, None, NULL, NULL,
1204                      (order!=REGION_ORDER_FRONT));
1205     
1206     return TRUE;
1207 }
1208
1209
1210 /*}}}*/
1211
1212
1213 /*{{{ Misc. */
1214
1215
1216 /*EXTL_DOC
1217  * Iterate over managed regions of \var{ws} until \var{iterfn} returns
1218  * \code{false}.
1219  * The function is called in protected mode.
1220  * This routine returns \code{true} if it reaches the end of list
1221  * without this happening.
1222  */
1223 EXTL_SAFE
1224 EXTL_EXPORT_MEMBER
1225 bool group_managed_i(WGroup *ws, ExtlFn iterfn)
1226 {
1227     WGroupIterTmp tmp;
1228     group_iter_init(&tmp, ws);
1229     
1230     return extl_iter_objlist_(iterfn, (ObjIterator*)group_iter, &tmp);
1231 }
1232
1233
1234 WRegion* group_current(WGroup *ws)
1235 {
1236     return (ws->current_managed!=NULL ? ws->current_managed->reg : NULL);
1237 }
1238
1239
1240 void group_size_hints(WGroup *ws, WSizeHints *hints_ret)
1241 {
1242     if(ws->bottom==NULL || ws->bottom->reg==NULL){
1243         sizehints_clear(hints_ret);
1244     }else{
1245         region_size_hints(ws->bottom->reg, hints_ret);
1246         hints_ret->no_constrain=TRUE;
1247     }
1248 }
1249
1250
1251 Window group_xwindow(const WGroup *ws)
1252 {
1253     return ws->dummywin;
1254 }
1255
1256
1257 /*EXTL_DOC
1258  * Returns the group of \var{reg}, if it is managed by one,
1259  * and \var{reg} itself otherwise.
1260  */
1261 /*EXTL_EXPORT_MEMBER
1262 WRegion *region_group_of(WRegion *reg)
1263 {
1264     WRegion *mgr=REGION_MANAGER(reg);
1265     
1266     return (OBJ_IS(mgr, WGroup) ? mgr : reg);
1267 }*/
1268
1269
1270 /*EXTL_DOC
1271  * Returns the group of \var{reg}, if \var{reg} is its bottom,
1272  * and \var{reg} itself otherwise.
1273  */
1274 EXTL_EXPORT_MEMBER
1275 WRegion *region_groupleader_of(WRegion *reg)
1276 {
1277     WGroup *grp=REGION_MANAGER_CHK(reg, WGroup);
1278     
1279     return ((grp!=NULL && group_bottom(grp)==reg)
1280             ? (WRegion*)grp
1281             : reg);
1282 }
1283
1284
1285 /*}}}*/
1286
1287
1288 /*{{{ Save/load */
1289
1290
1291 static ExtlTab group_get_configuration(WGroup *ws)
1292 {
1293     ExtlTab tab, mgds, subtab, g;
1294     WStacking *st;
1295     WGroupIterTmp tmp;
1296     WMPlex *par;
1297     int n=0;
1298     WRectangle tmpg;
1299     
1300     tab=region_get_base_configuration((WRegion*)ws);
1301     
1302     mgds=extl_create_table();
1303     
1304     extl_table_sets_t(tab, "managed", mgds);
1305     
1306     /* TODO: stacking order messed up */
1307     
1308     FOR_ALL_NODES_IN_GROUP(ws, st, tmp){
1309         if(st->reg==NULL)
1310             continue;
1311         
1312         subtab=region_get_configuration(st->reg);
1313
1314         if(subtab!=extl_table_none()){
1315             extl_table_sets_s(subtab, "sizepolicy", 
1316                               sizepolicy2string(st->szplcy));
1317             extl_table_sets_i(subtab, "level", st->level);
1318         
1319             tmpg=REGION_GEOM(st->reg);
1320             tmpg.x-=REGION_GEOM(ws).x;
1321             tmpg.y-=REGION_GEOM(ws).y;
1322             
1323             g=extl_table_from_rectangle(&tmpg);
1324             extl_table_sets_t(subtab, "geom", g);
1325             extl_unref_table(g);
1326             
1327             if(ws->bottom==st)
1328                 extl_table_sets_b(subtab, "bottom", TRUE);
1329         
1330             extl_table_seti_t(mgds, ++n, subtab);
1331             extl_unref_table(subtab);
1332         }
1333     }
1334     
1335     extl_unref_table(mgds);
1336     
1337     return tab;
1338 }
1339
1340     
1341 void group_do_load(WGroup *ws, ExtlTab tab)
1342 {
1343     ExtlTab substab, subtab;
1344     int i, n;
1345     
1346     if(extl_table_gets_t(tab, "managed", &substab)){
1347         n=extl_table_get_n(substab);
1348         for(i=1; i<=n; i++){
1349             if(extl_table_geti_t(substab, i, &subtab)){
1350                 WGroupAttachParams par=GROUPATTACHPARAMS_INIT;
1351                 WRegionAttachData data;
1352                 WFitParams fp;
1353                 WPHolder *ph;
1354                 
1355                 group_get_attach_params(ws, subtab, &par);
1356                 group_attach_fp(ws, &par, &fp);
1357                 
1358                 ph=(WPHolder*)create_grouppholder(ws, NULL, &par);
1359                 
1360                 region_attach_load_helper((WRegion*)ws, REGION_PARENT(ws), &fp,
1361                                           (WRegionDoAttachFn*)group_do_attach_final,
1362                                           (void*)&par, subtab, &ph);
1363                                               
1364                 if(ph!=NULL)
1365                     destroy_obj((Obj*)ph);
1366                 
1367                 extl_unref_table(subtab);
1368             }
1369         }
1370         
1371         extl_unref_table(substab);
1372     }
1373 }
1374
1375
1376 WRegion *group_load(WWindow *par, const WFitParams *fp, ExtlTab tab)
1377 {
1378     WGroup *ws;
1379     
1380     ws=create_group(par, fp);
1381     
1382     if(ws==NULL)
1383         return NULL;
1384         
1385     group_do_load(ws, tab);
1386     
1387     return (WRegion*)ws;
1388 }
1389
1390
1391 /*}}}*/
1392
1393
1394 /*{{{ Dynamic function table and class implementation */
1395
1396
1397 static DynFunTab group_dynfuntab[]={
1398     {(DynFun*)region_fitrep,
1399      (DynFun*)group_fitrep},
1400
1401     {region_map, 
1402      group_map},
1403     
1404     {region_unmap, 
1405      group_unmap},
1406     
1407     {(DynFun*)region_managed_prepare_focus, 
1408      (DynFun*)group_managed_prepare_focus},
1409
1410     {region_do_set_focus, 
1411      group_do_set_focus},
1412     
1413     {region_managed_notify, 
1414      group_managed_notify},
1415     
1416     {region_managed_remove,
1417      group_managed_remove},
1418     
1419     {(DynFun*)region_get_configuration, 
1420      (DynFun*)group_get_configuration},
1421
1422     {(DynFun*)region_current,
1423      (DynFun*)group_current},
1424     
1425     {(DynFun*)region_rescue_clientwins,
1426      (DynFun*)group_rescue_clientwins},
1427     
1428     {region_restack,
1429      group_restack},
1430
1431     {region_stacking,
1432      group_stacking},
1433
1434     {(DynFun*)region_managed_get_pholder,
1435      (DynFun*)group_managed_get_pholder},
1436
1437     {region_managed_rqgeom,
1438      group_managed_rqgeom},
1439      
1440     {region_managed_rqgeom_absolute,
1441      group_managed_rqgeom_absolute},
1442     
1443     {(DynFun*)group_do_add_managed,
1444      (DynFun*)group_do_add_managed_default},
1445     
1446     {region_size_hints,
1447      group_size_hints},
1448     
1449     {(DynFun*)region_xwindow,
1450      (DynFun*)group_xwindow},
1451     
1452     {(DynFun*)region_navi_first,
1453      (DynFun*)group_navi_first},
1454     
1455     {(DynFun*)region_navi_next,
1456      (DynFun*)group_navi_next},
1457     
1458     {(DynFun*)region_managed_rqorder,
1459      (DynFun*)group_managed_rqorder},
1460      
1461     {(DynFun*)region_get_rescue_pholder_for,
1462      (DynFun*)group_get_rescue_pholder_for},
1463     
1464     END_DYNFUNTAB
1465 };
1466
1467
1468 EXTL_EXPORT
1469 IMPLCLASS(WGroup, WRegion, group_deinit, group_dynfuntab);
1470
1471
1472 /*}}}*/
1473