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