]> git.decadent.org.uk Git - ion3.git/blob - ioncore/group.c
[svn-upgrade] Integrating new upstream version, ion3 (20071130)
[ion3.git] / ioncore / group.c
1 /*
2  * ion/ioncore/group.c
3  *
4  * Copyright (c) Tuomo Valkonen 1999-2007. 
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     
360     ws->dummywin=XCreateWindow(ioncore_g.dpy, par->win,
361                                 fp->g.x, fp->g.y, 1, 1, 0,
362                                 CopyFromParent, InputOnly,
363                                 CopyFromParent, 0, NULL);
364     if(ws->dummywin==None)
365         return FALSE;
366
367     region_init(&ws->reg, par, fp);
368     region_register(&ws->reg);
369
370     XSelectInput(ioncore_g.dpy, ws->dummywin,
371                  FocusChangeMask|KeyPressMask|KeyReleaseMask|
372                  ButtonPressMask|ButtonReleaseMask);
373     XSaveContext(ioncore_g.dpy, ws->dummywin, ioncore_g.win_context,
374                  (XPointer)ws);
375     
376     ((WRegion*)ws)->flags|=REGION_GRAB_ON_PARENT;
377     
378     region_add_bindmap((WRegion*)ws, ioncore_group_bindmap);
379     
380     return TRUE;
381 }
382
383
384 WGroup *create_group(WWindow *par, const WFitParams *fp)
385 {
386     CREATEOBJ_IMPL(WGroup, group, (p, par, fp));
387 }
388
389
390 void group_deinit(WGroup *ws)
391 {
392     WGroupIterTmp tmp;
393     WRegion *reg;
394
395     if(ws->managed_stdisp!=NULL && ws->managed_stdisp->reg!=NULL){
396         group_managed_remove(ws, ws->managed_stdisp->reg);
397         assert(ws->managed_stdisp==NULL);
398     }
399
400     FOR_ALL_MANAGED_BY_GROUP(ws, reg, tmp){
401         destroy_obj((Obj*)reg);
402     }
403
404     assert(ws->managed_list==NULL);
405
406     XDeleteContext(ioncore_g.dpy, ws->dummywin, ioncore_g.win_context);
407     XDestroyWindow(ioncore_g.dpy, ws->dummywin);
408     ws->dummywin=None;
409
410     region_deinit(&ws->reg);
411 }
412
413
414     
415 bool group_rescue_clientwins(WGroup *ws, WRescueInfo *info)
416 {
417     WGroupIterTmp tmp;
418     
419     group_iter_init_nostdisp(&tmp, ws);
420     
421     return region_rescue_some_clientwins((WRegion*)ws, info,
422                                          (WRegionIterator*)group_iter,
423                                          &tmp);
424 }
425
426
427 /*}}}*/
428
429
430 /*{{{ Bottom */
431
432
433 void group_bottom_set(WGroup *grp)
434 {
435     CALL_DYN(group_bottom_set, grp, (grp));
436 }
437
438
439 static void group_do_set_bottom(WGroup *grp, WStacking *st)
440 {
441     WStacking *was=grp->bottom;
442     WStacking *std=grp->managed_stdisp;
443     
444     grp->bottom=st;
445     
446     if(!OBJ_IS_BEING_DESTROYED(grp)){
447         bool noremanage=((was==st) ||
448                          (was==NULL && std==NULL) || 
449                          (st!=NULL && st==std) || 
450                          (st==NULL && was==std));
451         
452         if(!noremanage &&
453            (st==NULL || HAS_DYN(st->reg, region_manage_stdisp))){
454             group_remanage_stdisp(grp);
455         }
456         
457         group_bottom_set(grp);
458     }
459 }
460
461
462 /*EXTL_DOC
463  * Sets the `bottom' of \var{ws}. The region \var{reg} must already
464  * be managed by \var{ws}, unless \code{nil}.
465  */
466 EXTL_EXPORT_MEMBER
467 bool group_set_bottom(WGroup *ws, WRegion *reg)
468 {
469     WStacking *st=NULL;
470     
471     if(reg!=NULL){
472         st=group_find_stacking(ws, reg);
473         
474         if(st==NULL)
475             return FALSE;
476     }
477         
478     group_do_set_bottom(ws, st);
479     
480     return TRUE;
481 }
482
483
484 /*EXTL_DOC
485  * Returns the `bottom' of \var{ws}.
486  */
487 EXTL_SAFE
488 EXTL_EXPORT_MEMBER
489 WRegion *group_bottom(WGroup *ws)
490 {
491     return (ws->bottom!=NULL ? ws->bottom->reg : NULL);
492 }
493
494
495 /*}}}*/
496
497
498 /*{{{ Attach */
499
500
501 WStacking *group_do_add_managed(WGroup *ws, WRegion *reg, int level,
502                                 WSizePolicy szplcy)
503 {
504     WStacking *st=NULL;
505     CALL_DYN_RET(st, WStacking*, group_do_add_managed, ws, 
506                  (ws, reg, level, szplcy));
507     return st;
508 }
509     
510
511 WStacking *group_do_add_managed_default(WGroup *ws, WRegion *reg, int level,
512                                         WSizePolicy szplcy)
513 {
514     WStacking *st=NULL, *tmp=NULL;
515     Window bottom=None, top=None;
516     WStacking **stackingp=group_get_stackingp(ws);
517     WFrame *frame;
518     
519     if(stackingp==NULL)
520         return NULL;
521     
522     st=create_stacking();
523     
524     if(st==NULL)
525         return NULL;
526     
527     if(!stacking_assoc(st, reg)){
528         stacking_free(st);
529         return  NULL;
530     }
531     
532     frame=OBJ_CAST(reg, WFrame);
533     if(frame!=NULL){
534         if(framemode_unalt(frame_mode(frame))==FRAME_MODE_TILED)
535             frame_set_mode(frame, FRAME_MODE_FLOATING);
536     }
537
538     st->level=level;
539     st->szplcy=szplcy;
540
541     LINK_ITEM_FIRST(tmp, st, next, prev);
542     stacking_weave(stackingp, &tmp, FALSE);
543     assert(tmp==NULL);
544
545     LINK_ITEM(ws->managed_list, st, mgr_next, mgr_prev);
546     region_set_manager(reg, (WRegion*)ws);
547
548     if(region_is_fully_mapped((WRegion*)ws))
549         region_map(reg);
550
551     return st;
552 }
553
554
555 static void geom_group_to_parent(WGroup *ws, const WRectangle *g, 
556                                  WRectangle *wg)
557 {
558     wg->x=g->x+REGION_GEOM(ws).x;
559     wg->y=g->y+REGION_GEOM(ws).y;
560     wg->w=maxof(1, g->w);
561     wg->h=maxof(1, g->h);
562 }
563
564
565 static int group_must_focus(WGroup *ws, WStacking *st)
566 {
567     WStacking *stacking=group_get_stacking(ws);
568     
569     return (stacking!=NULL && stacking_must_focus(stacking, st));
570 }
571
572
573 bool group_do_attach_final(WGroup *ws, 
574                            WRegion *reg,
575                            const WGroupAttachParams *param)
576 {
577     WStacking *st, *stabove=NULL;
578     WSizePolicy szplcy;
579     WFitParams fp;
580     WRectangle g;
581     uint level;
582     int weak;
583     bool sw;
584     
585     /* Stacking  */
586     if(param->stack_above!=NULL)
587         stabove=group_find_stacking(ws, param->stack_above);
588
589     level=(stabove!=NULL
590            ? stabove->level
591            : (param->level_set 
592               ? param->level 
593               : STACKING_LEVEL_NORMAL));
594     
595     /* Fit */
596     szplcy=(param->szplcy_set
597             ? param->szplcy
598             : (param->bottom
599                ? SIZEPOLICY_FULL_EXACT
600                : SIZEPOLICY_UNCONSTRAINED));
601     
602     weak=(param->geom_weak_set
603           ? param->geom_weak
604           : (param->geom_set
605              ? 0
606              : REGION_RQGEOM_WEAK_ALL));
607     
608     if(param->geom_set)
609         geom_group_to_parent(ws, &param->geom, &g);
610     else
611         g=REGION_GEOM(reg);
612     
613     /* If the requested geometry does not overlap the workspaces's geometry, 
614      * position request is never honoured.
615      */
616     if((g.x+g.w<=REGION_GEOM(ws).x) ||
617        (g.x>=REGION_GEOM(ws).x+REGION_GEOM(ws).w)){
618         weak|=REGION_RQGEOM_WEAK_X;
619     }
620        
621     if((g.y+g.h<=REGION_GEOM(ws).y) ||
622        (g.y>=REGION_GEOM(ws).y+REGION_GEOM(ws).h)){
623         weak|=REGION_RQGEOM_WEAK_Y;
624     }
625
626     if(weak&(REGION_RQGEOM_WEAK_X|REGION_RQGEOM_WEAK_Y) &&
627         (szplcy==SIZEPOLICY_UNCONSTRAINED ||
628          szplcy==SIZEPOLICY_FREE ||
629          szplcy==SIZEPOLICY_FREE_GLUE /* without flags */)){
630         /* TODO: use 'weak'? */
631         group_calc_placement(ws, level, &g);
632     }
633
634     fp.g=REGION_GEOM(ws);
635     fp.mode=REGION_FIT_EXACT;
636
637     sizepolicy(&szplcy, reg, &g, weak, &fp);
638
639     if(rectangle_compare(&fp.g, &REGION_GEOM(reg))!=RECTANGLE_SAME)
640         region_fitrep(reg, NULL, &fp);
641     
642     /* Add */
643     st=group_do_add_managed(ws, reg, level, szplcy);
644     
645     if(st==NULL)
646         return FALSE;
647     
648     if(stabove!=NULL)
649         st->above=stabove;
650
651     if(param->bottom)
652         group_do_set_bottom(ws, st);
653     
654     /* Focus */
655     sw=((param->switchto_set ? param->switchto : ioncore_g.switchto_new)
656         ? st==find_to_focus(ws, st, FALSE)
657         : group_must_focus(ws, st));
658     
659     if(sw){
660         if(region_may_control_focus((WRegion*)ws))
661             region_set_focus(st->reg);
662         else
663             ws->current_managed=st;
664     }else if(region_is_fully_mapped(reg)){
665         region_pointer_focus_hack(reg);
666     }
667
668     return TRUE;
669 }
670
671
672 WRegion *group_do_attach(WGroup *ws, 
673                          /*const*/ WGroupAttachParams *param,
674                          WRegionAttachData *data)
675 {
676     WFitParams fp;
677     WWindow *par;
678     WRegion *reg;
679     
680     if(ws->bottom!=NULL && param->bottom){
681         warn(TR("'bottom' already set."));
682         return NULL;
683     }
684     
685     par=REGION_PARENT(ws);
686     assert(par!=NULL);
687
688     if(param->geom_set){
689         geom_group_to_parent(ws, &param->geom, &fp.g);
690         fp.mode=REGION_FIT_EXACT;
691     }else{
692         fp.g=REGION_GEOM(ws);
693         fp.mode=REGION_FIT_BOUNDS|REGION_FIT_WHATEVER;
694     }
695     
696     return region_attach_helper((WRegion*) ws, par, &fp, 
697                                 (WRegionDoAttachFn*)group_do_attach_final,
698                                 /*(const WRegionAttachParams*)*/param, data);
699     /*                            ^^^^ doesn't seem to work. */
700 }
701
702
703 void group_get_attach_params(WGroup *ws, ExtlTab tab, 
704                              WGroupAttachParams *par)
705 {
706     int tmp;
707     bool tmpb;
708     char *tmps;
709     ExtlTab g;
710     
711     par->switchto_set=0;
712     par->level_set=0;
713     par->szplcy_set=0;
714     par->geom_set=0;
715     par->bottom=0;
716     
717     if(extl_table_is_bool_set(tab, "bottom")){
718         par->level=STACKING_LEVEL_BOTTOM;
719         par->level_set=1;
720         par->bottom=1;
721     }
722     
723     if(extl_table_gets_i(tab, "level", &tmp)){
724         if(tmp>=0){
725             par->level_set=1;
726             par->level=tmp;
727         }
728     }
729     
730     if(!par->level_set && extl_table_is_bool_set(tab, "modal")){
731         par->level=STACKING_LEVEL_MODAL1;
732         par->level_set=1;
733     }
734     
735     if(extl_table_gets_b(tab, "switchto", &tmpb)){
736         par->switchto=(tmpb!=0);
737         par->switchto_set=1;
738     }
739     
740     if(extl_table_gets_i(tab, "sizepolicy", &tmp)){
741         par->szplcy_set=1;
742         par->szplcy=tmp;
743     }else if(extl_table_gets_s(tab, "sizepolicy", &tmps)){
744         if(string2sizepolicy(tmps, &par->szplcy))
745             par->szplcy_set=1;
746         free(tmps);
747     }
748     
749     if(extl_table_gets_t(tab, "geom", &g)){
750         int n=0;
751         
752         if(extl_table_gets_i(g, "x", &(par->geom.x)))
753             n++;
754         if(extl_table_gets_i(g, "y", &(par->geom.y)))
755             n++;
756         if(extl_table_gets_i(g, "w", &(par->geom.w)))
757             n++;
758         if(extl_table_gets_i(g, "h", &(par->geom.h)))
759             n++;
760         
761         if(n==4)
762             par->geom_set=1;
763         
764         extl_unref_table(g);
765     }
766 }
767
768
769
770 /*EXTL_DOC
771  * Attach and reparent existing region \var{reg} to \var{ws}.
772  * The table \var{param} may contain the fields \var{index} and
773  * \var{switchto} that are interpreted as for \fnref{WMPlex.attach_new}.
774  */
775 EXTL_EXPORT_MEMBER
776 WRegion *group_attach(WGroup *ws, WRegion *reg, ExtlTab param)
777 {
778     WGroupAttachParams par=GROUPATTACHPARAMS_INIT;
779     WRegionAttachData data;
780
781     if(reg==NULL)
782         return NULL;
783     
784     group_get_attach_params(ws, param, &par);
785     
786     data.type=REGION_ATTACH_REPARENT;
787     data.u.reg=reg;
788     
789     return group_do_attach(ws, &par, &data);
790 }
791
792
793 /*EXTL_DOC
794  * Create a new region to be managed by \var{ws}. At least the following
795  * fields in \var{param} are understood:
796  * 
797  * \begin{tabularx}{\linewidth}{lX}
798  *  \tabhead{Field & Description}
799  *  \var{type} & (string) Class of the object to be created. Mandatory. \\
800  *  \var{name} & (string) Name of the object to be created. \\
801  *  \var{switchto} & (boolean) Should the region be switched to? \\
802  *  \var{level} & (integer) Stacking level; default is 1. \\
803  *  \var{modal} & (boolean) Make object modal; ignored if level is set. \\
804  *  \var{sizepolicy} & (string) Size policy; see Section \ref{sec:sizepolicies}. \\
805  *  \var{bottom} & (boolean) Mark the attached region as the
806  *                 ``bottom'' of \var{ws}. \\
807  * \end{tabularx}
808  * 
809  * In addition parameters to the region to be created are passed in this 
810  * same table.
811  */
812 EXTL_EXPORT_MEMBER
813 WRegion *group_attach_new(WGroup *ws, ExtlTab param)
814 {
815     WGroupAttachParams par=GROUPATTACHPARAMS_INIT;
816     WRegionAttachData data;
817
818     group_get_attach_params(ws, param, &par);
819     
820     data.type=REGION_ATTACH_LOAD;
821     data.u.tab=param;
822     
823     return group_do_attach(ws, &par, &data);
824 }
825
826
827 /*}}}*/
828
829
830 /*{{{ Status display support */
831
832
833 static int stdisp_szplcy(const WMPlexSTDispInfo *di, WRegion *stdisp)
834 {
835     int pos=di->pos;
836     int policy=0, gravity=0;
837     
838     if(di->fullsize){
839         if(region_orientation(stdisp)==REGION_ORIENTATION_VERTICAL){
840             if(pos==MPLEX_STDISP_TL || pos==MPLEX_STDISP_BL)
841                 policy=SIZEPOLICY_STRETCH_LEFT;
842             else
843                 policy=SIZEPOLICY_STRETCH_RIGHT;
844         }else{
845             if(pos==MPLEX_STDISP_TL || pos==MPLEX_STDISP_TR)
846                 policy=SIZEPOLICY_STRETCH_TOP;
847             else
848                 policy=SIZEPOLICY_STRETCH_BOTTOM;
849         }
850     }else{
851         policy=SIZEPOLICY_GRAVITY;
852     }
853     
854     if(pos==MPLEX_STDISP_TL)
855         gravity=SIZEPOLICY_VERT_TOP|SIZEPOLICY_HORIZ_LEFT;
856     else if(pos==MPLEX_STDISP_BL)
857         gravity=SIZEPOLICY_VERT_BOTTOM|SIZEPOLICY_HORIZ_LEFT;
858     else if(pos==MPLEX_STDISP_TR)
859         gravity=SIZEPOLICY_VERT_TOP|SIZEPOLICY_HORIZ_RIGHT;
860     else /*if(pos=MPLEX_STDISP_BR)*/
861         gravity=SIZEPOLICY_VERT_BOTTOM|SIZEPOLICY_HORIZ_RIGHT;
862     
863     return (policy|gravity);
864 }
865
866
867 void group_manage_stdisp(WGroup *ws, WRegion *stdisp, 
868                          const WMPlexSTDispInfo *di)
869 {
870     WFitParams fp;
871     uint szplcy;
872     WRegion *b=(ws->bottom==NULL ? NULL : ws->bottom->reg);
873     
874     /* Check if 'bottom' wants to manage the stdisp. */
875     if(b!=NULL 
876        && !OBJ_IS_BEING_DESTROYED(b)
877        && HAS_DYN(b, region_manage_stdisp)){
878         region_manage_stdisp(b, stdisp, di);
879         if(REGION_MANAGER(stdisp)==b)
880             return;
881     }
882     
883     /* No. */
884     
885     szplcy=stdisp_szplcy(di, stdisp)|SIZEPOLICY_SHRUNK;
886     
887     if(ws->managed_stdisp!=NULL && ws->managed_stdisp->reg==stdisp){
888         if(ws->managed_stdisp->szplcy==szplcy)
889             return;
890         ws->managed_stdisp->szplcy=szplcy;
891     }else{
892         region_detach_manager(stdisp);
893         ws->managed_stdisp=group_do_add_managed(ws, stdisp, 
894                                                 STACKING_LEVEL_ON_TOP, 
895                                                 szplcy);
896     }
897     
898     stdisp->flags|=REGION_SKIP_FOCUS;
899     
900     fp.g=REGION_GEOM(ws);
901     fp.mode=0;
902     
903     sizepolicy(&ws->managed_stdisp->szplcy, stdisp, NULL, 0, &fp);
904
905     region_fitrep(stdisp, NULL, &fp);
906 }
907
908
909 static void group_remanage_stdisp(WGroup *ws)
910 {
911     WMPlex *mplex=OBJ_CAST(REGION_MANAGER(ws), WMPlex);
912     
913     if(mplex!=NULL && 
914        mplex->mx_current!=NULL && 
915        mplex->mx_current->st->reg==(WRegion*)ws){
916         mplex_remanage_stdisp(mplex);
917     }
918 }
919
920
921 /*}}}*/
922
923
924 /*{{{ Geometry requests */
925
926
927 void group_managed_rqgeom(WGroup *ws, WRegion *reg,
928                           const WRQGeomParams *rq,
929                           WRectangle *geomret)
930 {
931     WFitParams fp;
932     WStacking *st;
933         
934     st=group_find_stacking(ws, reg);
935
936     if(st==NULL){
937         fp.g=rq->geom;
938         fp.mode=REGION_FIT_EXACT;
939     }else{
940         fp.g=REGION_GEOM(ws);
941         fp.mode=0;
942         sizepolicy(&st->szplcy, reg, &rq->geom, rq->flags, &fp);
943     }
944     
945     if(geomret!=NULL)
946         *geomret=fp.g;
947     
948     if(!(rq->flags&REGION_RQGEOM_TRYONLY))
949         region_fitrep(reg, NULL, &fp);
950 }
951
952
953 void group_managed_rqgeom_absolute(WGroup *grp, WRegion *sub, 
954                                    const WRQGeomParams *rq,
955                                    WRectangle *geomret)
956 {
957     if(grp->bottom!=NULL && grp->bottom->reg==sub){
958         region_rqgeom((WRegion*)grp, rq, geomret);
959         if(!(rq->flags&REGION_RQGEOM_TRYONLY) && geomret!=NULL)
960             *geomret=REGION_GEOM(sub);
961     }else{
962         WRQGeomParams rq2=*rq;
963         rq2.flags&=~REGION_RQGEOM_ABSOLUTE;
964
965         region_managed_rqgeom((WRegion*)grp, sub, &rq2, geomret);
966     }
967 }
968
969
970 /*}}}*/
971
972
973 /*{{{ Navigation */
974
975
976 static WStacking *nxt(WGroup *ws, WStacking *st, bool wrap)
977 {
978     return (st->mgr_next!=NULL 
979             ? st->mgr_next 
980             : (wrap ? ws->managed_list : NULL));
981 }
982
983
984 static WStacking *prv(WGroup *ws, WStacking *st, bool wrap)
985 {
986     return (st!=ws->managed_list 
987             ? st->mgr_prev
988             : (wrap ? st->mgr_prev : NULL));
989 }
990
991
992 typedef WStacking *NxtFn(WGroup *ws, WStacking *st, bool wrap);
993
994
995 static bool focusable(WGroup *ws, WStacking *st, uint min_level)
996 {
997     return (st->reg!=NULL
998             && REGION_IS_MAPPED(st->reg)
999             && !(st->reg->flags&REGION_SKIP_FOCUS)
1000             && st->level>=min_level);
1001 }
1002
1003        
1004 static WStacking *do_get_next(WGroup *ws, WStacking *sti,
1005                               NxtFn *fn, bool wrap, bool sti_ok)
1006 {
1007     WStacking *st, *stacking;
1008     uint min_level=0;
1009     
1010     stacking=group_get_stacking(ws);
1011     
1012     if(stacking!=NULL)
1013         min_level=stacking_min_level_mapped(stacking);
1014
1015     st=sti;
1016     while(1){
1017         st=fn(ws, st, wrap); 
1018         
1019         if(st==NULL || st==sti)
1020             break;
1021         
1022         if(focusable(ws, st, min_level))
1023             return st;
1024     }
1025
1026     if(sti_ok && focusable(ws, sti, min_level))
1027         return sti;
1028     
1029     return NULL;
1030 }
1031
1032
1033 static WStacking *group_do_navi_first(WGroup *ws, WRegionNavi nh)
1034 {
1035     WStacking *lst=ws->managed_list;
1036     
1037     if(lst==NULL)
1038         return NULL;
1039
1040     if(nh==REGION_NAVI_ANY && 
1041        ws->current_managed!=NULL &&
1042        ws->current_managed->reg!=NULL){
1043         return ws->current_managed;
1044     }
1045     
1046     if(nh==REGION_NAVI_ANY || nh==REGION_NAVI_END || 
1047        nh==REGION_NAVI_BOTTOM || nh==REGION_NAVI_RIGHT){
1048         return do_get_next(ws, lst, prv, TRUE, TRUE);
1049     }else{
1050         return do_get_next(ws, lst->mgr_prev, nxt, TRUE, TRUE);
1051     }
1052 }
1053
1054
1055 static WRegion *group_navi_first(WGroup *ws, WRegionNavi nh,
1056                                  WRegionNaviData *data)
1057 {
1058     WStacking *st=group_do_navi_first(ws, nh);
1059     
1060     return region_navi_cont(&ws->reg, (st!=NULL ? st->reg : NULL), data);
1061 }
1062
1063
1064 static WStacking *group_do_navi_next(WGroup *ws, WStacking *st, 
1065                                      WRegionNavi nh, bool wrap)
1066 {
1067     if(st==NULL)
1068         return group_do_navi_first(ws, nh);
1069     
1070     if(nh==REGION_NAVI_ANY || nh==REGION_NAVI_END || 
1071        nh==REGION_NAVI_BOTTOM || nh==REGION_NAVI_RIGHT){
1072         return do_get_next(ws, st, nxt, wrap, FALSE);
1073     }else{
1074         return do_get_next(ws, st, prv, wrap, FALSE);
1075     }
1076 }
1077
1078 static WRegion *group_navi_next(WGroup *ws, WRegion *reg, 
1079                                 WRegionNavi nh, WRegionNaviData *data)
1080 {
1081     WStacking *st=group_find_stacking(ws, reg);
1082     
1083     st=group_do_navi_next(ws, st, nh, FALSE);
1084     
1085     return region_navi_cont(&ws->reg, (st!=NULL ? st->reg : NULL), data);
1086 }
1087
1088
1089 /*}}}*/
1090
1091
1092 /*{{{ Stacking */
1093
1094
1095 /* 
1096  * Note: Managed objects are considered to be stacked separately from the
1097  * group, slightly violating expectations.
1098  */
1099
1100 void group_stacking(WGroup *ws, Window *bottomret, Window *topret)
1101 {
1102     Window win=region_xwindow((WRegion*)ws);
1103     
1104     *bottomret=win;
1105     *topret=win;
1106 }
1107
1108
1109 void group_restack(WGroup *ws, Window other, int mode)
1110 {
1111     Window win;
1112     
1113     win=region_xwindow((WRegion*)ws);
1114     if(win!=None){
1115         xwindow_restack(win, other, mode);
1116         other=win;
1117         mode=Above;
1118     }
1119 }
1120
1121
1122 WStacking *group_find_stacking(WGroup *ws, WRegion *r)
1123 {
1124     if(r==NULL || REGION_MANAGER(r)!=(WRegion*)ws)
1125         return NULL;
1126     
1127     return ioncore_find_stacking(r);
1128 }
1129
1130
1131 static WStacking *find_stacking_if_not_on_ws(WGroup *ws, Window w)
1132 {
1133     WRegion *r=xwindow_region_of(w);
1134     WStacking *st=NULL;
1135     
1136     while(r!=NULL){
1137         if(REGION_MANAGER(r)==(WRegion*)ws)
1138             break;
1139         st=group_find_stacking(ws, r);
1140         if(st!=NULL)
1141             break;
1142         r=REGION_MANAGER(r);
1143     }
1144     
1145     return st;
1146 }
1147
1148
1149 bool group_managed_rqorder(WGroup *grp, WRegion *reg, WRegionOrder order)
1150 {
1151     WStacking **stackingp=group_get_stackingp(grp);
1152     WStacking *st;
1153
1154     if(stackingp==NULL || *stackingp==NULL)
1155         return FALSE;
1156
1157     st=group_find_stacking(grp, reg);
1158     
1159     if(st==NULL)
1160         return FALSE;
1161     
1162     stacking_restack(stackingp, st, None, NULL, NULL,
1163                      (order!=REGION_ORDER_FRONT));
1164     
1165     return TRUE;
1166 }
1167
1168
1169 /*}}}*/
1170
1171
1172 /*{{{ Misc. */
1173
1174
1175 /*EXTL_DOC
1176  * Iterate over managed regions of \var{ws} until \var{iterfn} returns
1177  * \code{false}.
1178  * The function itself returns \code{true} if it reaches the end of list
1179  * without this happening.
1180  */
1181 EXTL_SAFE
1182 EXTL_EXPORT_MEMBER
1183 bool group_managed_i(WGroup *ws, ExtlFn iterfn)
1184 {
1185     WGroupIterTmp tmp;
1186     group_iter_init(&tmp, ws);
1187     
1188     return extl_iter_objlist_(iterfn, (ObjIterator*)group_iter, &tmp);
1189 }
1190
1191
1192 WRegion* group_current(WGroup *ws)
1193 {
1194     return (ws->current_managed!=NULL ? ws->current_managed->reg : NULL);
1195 }
1196
1197
1198 void group_size_hints(WGroup *ws, WSizeHints *hints_ret)
1199 {
1200     if(ws->bottom==NULL || ws->bottom->reg==NULL){
1201         sizehints_clear(hints_ret);
1202     }else{
1203         region_size_hints(ws->bottom->reg, hints_ret);
1204         hints_ret->no_constrain=TRUE;
1205     }
1206 }
1207
1208
1209 Window group_xwindow(const WGroup *ws)
1210 {
1211     return ws->dummywin;
1212 }
1213
1214
1215 /*EXTL_DOC
1216  * Returns the group of \var{reg}, if it is managed by one,
1217  * and \var{reg} itself otherwise.
1218  */
1219 /*EXTL_EXPORT_MEMBER
1220 WRegion *region_group_of(WRegion *reg)
1221 {
1222     WRegion *mgr=REGION_MANAGER(reg);
1223     
1224     return (OBJ_IS(mgr, WGroup) ? mgr : reg);
1225 }*/
1226
1227
1228 /*EXTL_DOC
1229  * Returns the group of \var{reg}, if \var{reg} is its bottom,
1230  * and \var{reg} itself otherwise.
1231  */
1232 EXTL_EXPORT_MEMBER
1233 WRegion *region_groupleader_of(WRegion *reg)
1234 {
1235     WGroup *grp=REGION_MANAGER_CHK(reg, WGroup);
1236     
1237     return ((grp!=NULL && group_bottom(grp)==reg)
1238             ? (WRegion*)grp
1239             : reg);
1240 }
1241
1242
1243 /*}}}*/
1244
1245
1246 /*{{{ Save/load */
1247
1248
1249 static ExtlTab group_get_configuration(WGroup *ws)
1250 {
1251     ExtlTab tab, mgds, subtab, g;
1252     WStacking *st;
1253     WGroupIterTmp tmp;
1254     WMPlex *par;
1255     int n=0;
1256     WRectangle tmpg;
1257     
1258     tab=region_get_base_configuration((WRegion*)ws);
1259     
1260     mgds=extl_create_table();
1261     
1262     extl_table_sets_t(tab, "managed", mgds);
1263     
1264     /* TODO: stacking order messed up */
1265     
1266     FOR_ALL_NODES_IN_GROUP(ws, st, tmp){
1267         if(st->reg==NULL)
1268             continue;
1269         
1270         subtab=region_get_configuration(st->reg);
1271
1272         if(subtab!=extl_table_none()){
1273             extl_table_sets_s(subtab, "sizepolicy", 
1274                               sizepolicy2string(st->szplcy));
1275             extl_table_sets_i(subtab, "level", st->level);
1276         
1277             tmpg=REGION_GEOM(st->reg);
1278             tmpg.x-=REGION_GEOM(ws).x;
1279             tmpg.y-=REGION_GEOM(ws).y;
1280             
1281             g=extl_table_from_rectangle(&tmpg);
1282             extl_table_sets_t(subtab, "geom", g);
1283             extl_unref_table(g);
1284             
1285             if(ws->bottom==st)
1286                 extl_table_sets_b(subtab, "bottom", TRUE);
1287         
1288             extl_table_seti_t(mgds, ++n, subtab);
1289             extl_unref_table(subtab);
1290         }
1291     }
1292     
1293     extl_unref_table(mgds);
1294     
1295     return tab;
1296 }
1297
1298
1299 void group_do_load(WGroup *ws, ExtlTab tab)
1300 {
1301     ExtlTab substab, subtab;
1302     int i, n;
1303     
1304     if(extl_table_gets_t(tab, "managed", &substab)){
1305         n=extl_table_get_n(substab);
1306         for(i=1; i<=n; i++){
1307             if(extl_table_geti_t(substab, i, &subtab)){
1308                 group_attach_new(ws, subtab);
1309                 extl_unref_table(subtab);
1310             }
1311         }
1312         
1313         extl_unref_table(substab);
1314     }
1315 }
1316
1317
1318 WRegion *group_load(WWindow *par, const WFitParams *fp, ExtlTab tab)
1319 {
1320     WGroup *ws;
1321     
1322     ws=create_group(par, fp);
1323     
1324     if(ws==NULL)
1325         return NULL;
1326         
1327     group_do_load(ws, tab);
1328     
1329     return (WRegion*)ws;
1330 }
1331
1332
1333 /*}}}*/
1334
1335
1336 /*{{{ Dynamic function table and class implementation */
1337
1338
1339 static DynFunTab group_dynfuntab[]={
1340     {(DynFun*)region_fitrep,
1341      (DynFun*)group_fitrep},
1342
1343     {region_map, 
1344      group_map},
1345     
1346     {region_unmap, 
1347      group_unmap},
1348     
1349     {(DynFun*)region_managed_prepare_focus, 
1350      (DynFun*)group_managed_prepare_focus},
1351
1352     {region_do_set_focus, 
1353      group_do_set_focus},
1354     
1355     {region_managed_notify, 
1356      group_managed_notify},
1357     
1358     {region_managed_remove,
1359      group_managed_remove},
1360     
1361     {(DynFun*)region_get_configuration, 
1362      (DynFun*)group_get_configuration},
1363
1364     {(DynFun*)region_current,
1365      (DynFun*)group_current},
1366     
1367     {(DynFun*)region_rescue_clientwins,
1368      (DynFun*)group_rescue_clientwins},
1369     
1370     {region_restack,
1371      group_restack},
1372
1373     {region_stacking,
1374      group_stacking},
1375
1376     {(DynFun*)region_managed_get_pholder,
1377      (DynFun*)group_managed_get_pholder},
1378
1379     {region_managed_rqgeom,
1380      group_managed_rqgeom},
1381      
1382     {region_managed_rqgeom_absolute,
1383      group_managed_rqgeom_absolute},
1384     
1385     {(DynFun*)group_do_add_managed,
1386      (DynFun*)group_do_add_managed_default},
1387     
1388     {region_size_hints,
1389      group_size_hints},
1390     
1391     {(DynFun*)region_xwindow,
1392      (DynFun*)group_xwindow},
1393     
1394     {(DynFun*)region_navi_first,
1395      (DynFun*)group_navi_first},
1396     
1397     {(DynFun*)region_navi_next,
1398      (DynFun*)group_navi_next},
1399     
1400     {(DynFun*)region_managed_rqorder,
1401      (DynFun*)group_managed_rqorder},
1402     
1403     END_DYNFUNTAB
1404 };
1405
1406
1407 EXTL_EXPORT
1408 IMPLCLASS(WGroup, WRegion, group_deinit, group_dynfuntab);
1409
1410
1411 /*}}}*/
1412