]> git.decadent.org.uk Git - ion3.git/blob - ioncore/group.c
[svn-upgrade] Integrating new upstream version, ion3 (20070720)
[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     stdisp->flags|=REGION_SKIP_FOCUS;
923     
924     fp.g=REGION_GEOM(ws);
925     sizepolicy(&ws->managed_stdisp->szplcy, stdisp, NULL, 0, &fp);
926
927     region_fitrep(stdisp, NULL, &fp);
928 }
929
930
931 static void group_remanage_stdisp(WGroup *ws)
932 {
933     WMPlex *mplex=OBJ_CAST(REGION_MANAGER(ws), WMPlex);
934     
935     if(mplex!=NULL && 
936        mplex->mx_current!=NULL && 
937        mplex->mx_current->st->reg==(WRegion*)ws){
938         mplex_remanage_stdisp(mplex);
939     }
940 }
941
942
943 /*}}}*/
944
945
946 /*{{{ Geometry requests */
947
948
949 void group_managed_rqgeom(WGroup *ws, WRegion *reg,
950                           const WRQGeomParams *rq,
951                           WRectangle *geomret)
952 {
953     WFitParams fp;
954     WStacking *st;
955         
956     st=group_find_stacking(ws, reg);
957
958     if(st==NULL){
959         fp.g=rq->geom;
960         fp.mode=REGION_FIT_EXACT;
961     }else{
962         fp.g=REGION_GEOM(ws);
963         sizepolicy(&st->szplcy, reg, &rq->geom, rq->flags, &fp);
964     }
965     
966     if(geomret!=NULL)
967         *geomret=fp.g;
968     
969     if(!(rq->flags&REGION_RQGEOM_TRYONLY))
970         region_fitrep(reg, NULL, &fp);
971 }
972
973
974 void group_managed_rqgeom_absolute(WGroup *grp, WRegion *sub, 
975                                    const WRQGeomParams *rq,
976                                    WRectangle *geomret)
977 {
978     if(grp->bottom!=NULL && grp->bottom->reg==sub){
979         region_rqgeom((WRegion*)grp, rq, geomret);
980         if(!(rq->flags&REGION_RQGEOM_TRYONLY) && geomret!=NULL)
981             *geomret=REGION_GEOM(sub);
982     }else{
983         WRQGeomParams rq2=*rq;
984         rq2.flags&=~REGION_RQGEOM_ABSOLUTE;
985
986         region_managed_rqgeom((WRegion*)grp, sub, &rq2, geomret);
987     }
988 }
989
990
991 /*}}}*/
992
993
994 /*{{{ Navigation */
995
996
997 static WStacking *nxt(WGroup *ws, WStacking *st, bool wrap)
998 {
999     return (st->mgr_next!=NULL 
1000             ? st->mgr_next 
1001             : (wrap ? ws->managed_list : NULL));
1002 }
1003
1004
1005 static WStacking *prv(WGroup *ws, WStacking *st, bool wrap)
1006 {
1007     return (st!=ws->managed_list 
1008             ? st->mgr_prev
1009             : (wrap ? st->mgr_prev : NULL));
1010 }
1011
1012
1013 typedef WStacking *NxtFn(WGroup *ws, WStacking *st, bool wrap);
1014
1015
1016 static bool focusable(WGroup *ws, WStacking *st, uint min_level)
1017 {
1018     return (st->reg!=NULL
1019             && REGION_IS_MAPPED(st->reg)
1020             && !(st->reg->flags&REGION_SKIP_FOCUS)
1021             && st->level>=min_level);
1022 }
1023
1024        
1025 static WStacking *do_get_next(WGroup *ws, WStacking *sti,
1026                               NxtFn *fn, bool wrap, bool sti_ok)
1027 {
1028     WStacking *st, *stacking;
1029     uint min_level=0;
1030     
1031     stacking=group_get_stacking(ws);
1032     
1033     if(stacking!=NULL)
1034         min_level=stacking_min_level_mapped(stacking);
1035
1036     st=sti;
1037     while(1){
1038         st=fn(ws, st, wrap); 
1039         
1040         if(st==NULL || st==sti)
1041             break;
1042         
1043         if(focusable(ws, st, min_level))
1044             return st;
1045     }
1046
1047     if(sti_ok && focusable(ws, sti, min_level))
1048         return sti;
1049     
1050     return NULL;
1051 }
1052
1053
1054 static WStacking *group_do_navi_first(WGroup *ws, WRegionNavi nh)
1055 {
1056     WStacking *lst=ws->managed_list;
1057     
1058     if(lst==NULL)
1059         return NULL;
1060
1061     if(nh==REGION_NAVI_ANY && 
1062        ws->current_managed!=NULL &&
1063        ws->current_managed->reg!=NULL){
1064         return ws->current_managed;
1065     }
1066     
1067     if(nh==REGION_NAVI_ANY || nh==REGION_NAVI_END || 
1068        nh==REGION_NAVI_BOTTOM || nh==REGION_NAVI_RIGHT){
1069         return do_get_next(ws, lst, prv, TRUE, TRUE);
1070     }else{
1071         return do_get_next(ws, lst->mgr_prev, nxt, TRUE, TRUE);
1072     }
1073 }
1074
1075
1076 static WRegion *group_navi_first(WGroup *ws, WRegionNavi nh,
1077                                  WRegionNaviData *data)
1078 {
1079     WStacking *st=group_do_navi_first(ws, nh);
1080     
1081     return region_navi_cont(&ws->reg, (st!=NULL ? st->reg : NULL), data);
1082 }
1083
1084
1085 static WStacking *group_do_navi_next(WGroup *ws, WStacking *st, 
1086                                      WRegionNavi nh, bool wrap)
1087 {
1088     if(st==NULL)
1089         return group_do_navi_first(ws, nh);
1090     
1091     if(nh==REGION_NAVI_ANY || nh==REGION_NAVI_END || 
1092        nh==REGION_NAVI_BOTTOM || nh==REGION_NAVI_RIGHT){
1093         return do_get_next(ws, st, nxt, wrap, FALSE);
1094     }else{
1095         return do_get_next(ws, st, prv, wrap, FALSE);
1096     }
1097 }
1098
1099 static WRegion *group_navi_next(WGroup *ws, WRegion *reg, 
1100                                 WRegionNavi nh, WRegionNaviData *data)
1101 {
1102     WStacking *st=group_find_stacking(ws, reg);
1103     
1104     st=group_do_navi_next(ws, st, nh, FALSE);
1105     
1106     return region_navi_cont(&ws->reg, (st!=NULL ? st->reg : NULL), data);
1107 }
1108
1109
1110 /*}}}*/
1111
1112
1113 /*{{{ Stacking */
1114
1115
1116 /* 
1117  * Note: Managed objects are considered to be stacked separately from the
1118  * group, slightly violating expectations.
1119  */
1120
1121 void group_stacking(WGroup *ws, Window *bottomret, Window *topret)
1122 {
1123     Window win=region_xwindow((WRegion*)ws);
1124     
1125     *bottomret=win;
1126     *topret=win;
1127 }
1128
1129
1130 void group_restack(WGroup *ws, Window other, int mode)
1131 {
1132     Window win;
1133     
1134     win=region_xwindow((WRegion*)ws);
1135     if(win!=None){
1136         xwindow_restack(win, other, mode);
1137         other=win;
1138         mode=Above;
1139     }
1140 }
1141
1142
1143 WStacking *group_find_stacking(WGroup *ws, WRegion *r)
1144 {
1145     if(r==NULL || REGION_MANAGER(r)!=(WRegion*)ws)
1146         return NULL;
1147     
1148     return ioncore_find_stacking(r);
1149 }
1150
1151
1152 static WStacking *find_stacking_if_not_on_ws(WGroup *ws, Window w)
1153 {
1154     WRegion *r=xwindow_region_of(w);
1155     WStacking *st=NULL;
1156     
1157     while(r!=NULL){
1158         if(REGION_MANAGER(r)==(WRegion*)ws)
1159             break;
1160         st=group_find_stacking(ws, r);
1161         if(st!=NULL)
1162             break;
1163         r=REGION_MANAGER(r);
1164     }
1165     
1166     return st;
1167 }
1168
1169
1170 bool group_managed_rqorder(WGroup *grp, WRegion *reg, WRegionOrder order)
1171 {
1172     WStacking **stackingp=group_get_stackingp(grp);
1173     WStacking *st;
1174
1175     if(stackingp==NULL || *stackingp==NULL)
1176         return FALSE;
1177
1178     st=group_find_stacking(grp, reg);
1179     
1180     if(st==NULL)
1181         return FALSE;
1182     
1183     stacking_restack(stackingp, st, None, NULL, NULL,
1184                      (order!=REGION_ORDER_FRONT));
1185     
1186     return TRUE;
1187 }
1188
1189
1190 /*}}}*/
1191
1192
1193 /*{{{ Misc. */
1194
1195
1196 /*EXTL_DOC
1197  * Iterate over managed regions of \var{ws} until \var{iterfn} returns
1198  * \code{false}.
1199  * The function itself returns \code{true} if it reaches the end of list
1200  * without this happening.
1201  */
1202 EXTL_SAFE
1203 EXTL_EXPORT_MEMBER
1204 bool group_managed_i(WGroup *ws, ExtlFn iterfn)
1205 {
1206     WGroupIterTmp tmp;
1207     group_iter_init(&tmp, ws);
1208     
1209     return extl_iter_objlist_(iterfn, (ObjIterator*)group_iter, &tmp);
1210 }
1211
1212
1213 WRegion* group_current(WGroup *ws)
1214 {
1215     return (ws->current_managed!=NULL ? ws->current_managed->reg : NULL);
1216 }
1217
1218
1219 void group_size_hints(WGroup *ws, WSizeHints *hints_ret)
1220 {
1221     if(ws->bottom==NULL || ws->bottom->reg==NULL){
1222         sizehints_clear(hints_ret);
1223     }else{
1224         region_size_hints(ws->bottom->reg, hints_ret);
1225         hints_ret->no_constrain=TRUE;
1226     }
1227 }
1228
1229
1230 Window group_xwindow(const WGroup *ws)
1231 {
1232     return ws->dummywin;
1233 }
1234
1235
1236 /*EXTL_DOC
1237  * Returns the group of \var{reg}, if it is managed by one,
1238  * and \var{reg} itself otherwise.
1239  */
1240 /*EXTL_EXPORT_MEMBER
1241 WRegion *region_group_of(WRegion *reg)
1242 {
1243     WRegion *mgr=REGION_MANAGER(reg);
1244     
1245     return (OBJ_IS(mgr, WGroup) ? mgr : reg);
1246 }*/
1247
1248
1249 /*EXTL_DOC
1250  * Returns the group of \var{reg}, if \var{reg} is its bottom,
1251  * and \var{reg} itself otherwise.
1252  */
1253 EXTL_EXPORT_MEMBER
1254 WRegion *region_groupleader_of(WRegion *reg)
1255 {
1256     WGroup *grp=REGION_MANAGER_CHK(reg, WGroup);
1257     
1258     return ((grp!=NULL && group_bottom(grp)==reg)
1259             ? (WRegion*)grp
1260             : reg);
1261 }
1262
1263
1264 /*}}}*/
1265
1266
1267 /*{{{ Save/load */
1268
1269
1270 static ExtlTab group_get_configuration(WGroup *ws)
1271 {
1272     ExtlTab tab, mgds, subtab, g;
1273     WStacking *st;
1274     WGroupIterTmp tmp;
1275     WMPlex *par;
1276     int n=0;
1277     WRectangle tmpg;
1278     
1279     tab=region_get_base_configuration((WRegion*)ws);
1280     
1281     mgds=extl_create_table();
1282     
1283     extl_table_sets_t(tab, "managed", mgds);
1284     
1285     /* TODO: stacking order messed up */
1286     
1287     FOR_ALL_NODES_IN_GROUP(ws, st, tmp){
1288         if(st->reg==NULL)
1289             continue;
1290         
1291         subtab=region_get_configuration(st->reg);
1292
1293         if(subtab!=extl_table_none()){
1294             extl_table_sets_s(subtab, "sizepolicy", 
1295                               sizepolicy2string(st->szplcy));
1296             extl_table_sets_i(subtab, "level", st->level);
1297         
1298             tmpg=REGION_GEOM(st->reg);
1299             tmpg.x-=REGION_GEOM(ws).x;
1300             tmpg.y-=REGION_GEOM(ws).y;
1301             
1302             g=extl_table_from_rectangle(&tmpg);
1303             extl_table_sets_t(subtab, "geom", g);
1304             extl_unref_table(g);
1305             
1306             if(ws->bottom==st)
1307                 extl_table_sets_b(subtab, "bottom", TRUE);
1308         
1309             extl_table_seti_t(mgds, ++n, subtab);
1310             extl_unref_table(subtab);
1311         }
1312     }
1313     
1314     extl_unref_table(mgds);
1315     
1316     return tab;
1317 }
1318
1319
1320 void group_do_load(WGroup *ws, ExtlTab tab)
1321 {
1322     ExtlTab substab, subtab;
1323     int i, n;
1324     
1325     if(extl_table_gets_t(tab, "managed", &substab)){
1326         n=extl_table_get_n(substab);
1327         for(i=1; i<=n; i++){
1328             if(extl_table_geti_t(substab, i, &subtab)){
1329                 group_attach_new(ws, subtab);
1330                 extl_unref_table(subtab);
1331             }
1332         }
1333         
1334         extl_unref_table(substab);
1335     }
1336 }
1337
1338
1339 WRegion *group_load(WWindow *par, const WFitParams *fp, ExtlTab tab)
1340 {
1341     WGroup *ws;
1342     
1343     ws=create_group(par, fp);
1344     
1345     if(ws==NULL)
1346         return NULL;
1347         
1348     group_do_load(ws, tab);
1349     
1350     return (WRegion*)ws;
1351 }
1352
1353
1354 /*}}}*/
1355
1356
1357 /*{{{ Dynamic function table and class implementation */
1358
1359
1360 static DynFunTab group_dynfuntab[]={
1361     {(DynFun*)region_fitrep,
1362      (DynFun*)group_fitrep},
1363
1364     {region_map, 
1365      group_map},
1366     
1367     {region_unmap, 
1368      group_unmap},
1369     
1370     {(DynFun*)region_managed_prepare_focus, 
1371      (DynFun*)group_managed_prepare_focus},
1372
1373     {region_do_set_focus, 
1374      group_do_set_focus},
1375     
1376     {region_managed_notify, 
1377      group_managed_notify},
1378     
1379     {region_managed_remove,
1380      group_managed_remove},
1381     
1382     {(DynFun*)region_get_configuration, 
1383      (DynFun*)group_get_configuration},
1384
1385     {(DynFun*)region_managed_disposeroot,
1386      (DynFun*)group_managed_disposeroot},
1387
1388     {(DynFun*)region_current,
1389      (DynFun*)group_current},
1390     
1391     {(DynFun*)region_rescue_clientwins,
1392      (DynFun*)group_rescue_clientwins},
1393     
1394     {region_restack,
1395      group_restack},
1396
1397     {region_stacking,
1398      group_stacking},
1399
1400     {(DynFun*)region_managed_get_pholder,
1401      (DynFun*)group_managed_get_pholder},
1402
1403     {region_managed_rqgeom,
1404      group_managed_rqgeom},
1405      
1406     {region_managed_rqgeom_absolute,
1407      group_managed_rqgeom_absolute},
1408     
1409     {(DynFun*)group_do_add_managed,
1410      (DynFun*)group_do_add_managed_default},
1411     
1412     {region_size_hints,
1413      group_size_hints},
1414     
1415     {(DynFun*)region_xwindow,
1416      (DynFun*)group_xwindow},
1417     
1418     {(DynFun*)region_navi_first,
1419      (DynFun*)group_navi_first},
1420     
1421     {(DynFun*)region_navi_next,
1422      (DynFun*)group_navi_next},
1423     
1424     {(DynFun*)region_managed_rqorder,
1425      (DynFun*)group_managed_rqorder},
1426     
1427     END_DYNFUNTAB
1428 };
1429
1430
1431 EXTL_EXPORT
1432 IMPLCLASS(WGroup, WRegion, group_deinit, group_dynfuntab);
1433
1434
1435 /*}}}*/
1436