]> git.decadent.org.uk Git - ion3.git/blob - ioncore/group.c
Merge commit '20070203' into HEAD
[ion3.git] / ioncore / group.c
1 /*
2  * ion/ioncore/group.c
3  *
4  * Copyright (c) Tuomo Valkonen 1999-2007. 
5  *
6  * Ion is free software; you can redistribute it and/or modify it under
7  * the terms of the GNU Lesser General Public License as published by
8  * the Free Software Foundation; either version 2.1 of the License, or
9  * (at your option) any later version.
10  */
11
12 #include <string.h>
13
14 #include <libtu/minmax.h>
15 #include <libtu/objp.h>
16 #include <libmainloop/defer.h>
17
18 #include "common.h"
19 #include "rootwin.h"
20 #include "focus.h"
21 #include "global.h"
22 #include "region.h"
23 #include "manage.h"
24 #include "screen.h"
25 #include "names.h"
26 #include "saveload.h"
27 #include "attach.h"
28 #include "regbind.h"
29 #include "extlconv.h"
30 #include "xwindow.h"
31 #include "resize.h"
32 #include "stacking.h"
33 #include "sizepolicy.h"
34 #include "bindmaps.h"
35 #include "navi.h"
36 #include "sizehint.h"
37 #include "llist.h"
38 #include "mplex.h"
39 #include "group.h"
40 #include "grouppholder.h"
41 #include "frame.h"
42 #include "float-placement.h"
43 #include "return.h"
44
45
46 static void group_place_stdisp(WGroup *ws, WWindow *parent,
47                                  int pos, WRegion *stdisp);
48
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         WStacking *stacking;
284         
285         if(!region_prepare_focus((WRegion*)ws, flags, res))
286             return FALSE;
287
288         stacking=group_get_stacking(ws);
289         st=find_to_focus(ws, st, FALSE);
290         
291 #warning "TODO: raise in some cases (not enter-window)?"
292         
293         if(st==NULL)
294             return FALSE;
295         
296         res->reg=st->reg;
297         res->flags=flags;
298         
299         return (res->reg==reg);
300     }
301 }
302
303
304 static bool group_essentially_empty(WGroup *ws)
305 {
306     WGroupIterTmp tmp;
307     WStacking *st;
308     
309     FOR_ALL_NODES_IN_GROUP(ws, st, tmp){
310         if(st!=ws->managed_stdisp)
311             return FALSE;
312     }
313     
314     return TRUE;
315 }
316
317
318 void group_managed_remove(WGroup *ws, WRegion *reg)
319 {
320     bool mcf=region_may_control_focus((WRegion*)ws);
321     bool ds=OBJ_IS_BEING_DESTROYED(ws);
322     WStacking *st, *next_st=NULL;
323     bool was_stdisp=FALSE, was_bottom=FALSE;
324     bool dest=FALSE;
325     bool cur=FALSE;
326     
327     st=group_find_stacking(ws, reg);
328
329     if(st!=NULL){
330         next_st=stacking_unstack(REGION_PARENT(ws), st);
331         
332         UNLINK_ITEM(ws->managed_list, st, mgr_next, mgr_prev);
333         
334         if(st==ws->managed_stdisp){
335             ws->managed_stdisp=NULL;
336             was_stdisp=TRUE;
337         }
338         
339         if(st==ws->bottom){
340             ws->bottom=NULL;
341             was_bottom=TRUE;
342             if(ws->bottom_last_close && group_essentially_empty(ws))
343                 dest=TRUE;
344         }
345             
346         if(st==ws->current_managed){
347             cur=TRUE;
348             ws->current_managed=NULL;
349         }
350         
351         stacking_unassoc(st);
352         stacking_free(st);
353     }
354     
355     region_unset_manager(reg, (WRegion*)ws);
356     
357     if(!dest && !ds){
358         if(was_bottom && !was_stdisp && ws->managed_stdisp==NULL){
359             /* We should probably be managing any stdisp, that 'bottom' 
360              * was managing.
361              */
362             WMPlex *mplex=OBJ_CAST(REGION_MANAGER(ws), WMPlex);
363             
364             if(mplex!=NULL 
365                && mplex->mx_current!=NULL 
366                && mplex->mx_current->st->reg==(WRegion*)ws){
367                 mplex_remanage_stdisp(mplex);
368             }
369         }
370         
371         if(cur){
372             /* This may still potentially cause problems when focus
373              * change is pending. Perhaps we should use region_await_focus,
374              * if it is pointing to our child (and region_may_control_focus 
375              * fail if it is pointing somewhere else).
376              */
377             WStacking *stf=find_to_focus(ws, next_st, TRUE);
378             if(stf!=NULL && mcf){
379                 region_maybewarp_now(stf->reg, FALSE);
380             }else{
381                 ws->current_managed=stf;
382             }
383         }
384     }else if(dest && !ds){
385         region_dispose((WRegion*)ws, mcf);
386     }
387 }
388
389
390 static void group_managed_notify(WGroup *ws, WRegion *reg, WRegionNotify how)
391 {
392     if(how==ioncore_g.notifies.activated || 
393        how==ioncore_g.notifies.pseudoactivated){
394         ws->current_managed=group_find_stacking(ws, reg);
395     }
396 }
397
398
399 /*}}}*/
400
401
402 /*{{{ Create/destroy */
403
404
405 bool group_init(WGroup *ws, WWindow *par, const WFitParams *fp)
406 {
407     ws->current_managed=NULL;
408     ws->managed_stdisp=NULL;
409     ws->bottom=NULL;
410     ws->managed_list=NULL;
411     
412     ws->dummywin=XCreateWindow(ioncore_g.dpy, par->win,
413                                 fp->g.x, fp->g.y, 1, 1, 0,
414                                 CopyFromParent, InputOnly,
415                                 CopyFromParent, 0, NULL);
416     if(ws->dummywin==None)
417         return FALSE;
418
419     region_init(&ws->reg, par, fp);
420     region_register(&ws->reg);
421
422     XSelectInput(ioncore_g.dpy, ws->dummywin,
423                  FocusChangeMask|KeyPressMask|KeyReleaseMask|
424                  ButtonPressMask|ButtonReleaseMask);
425     XSaveContext(ioncore_g.dpy, ws->dummywin, ioncore_g.win_context,
426                  (XPointer)ws);
427     
428     ((WRegion*)ws)->flags|=REGION_GRAB_ON_PARENT;
429     
430     region_add_bindmap((WRegion*)ws, ioncore_group_bindmap);
431     
432     return TRUE;
433 }
434
435
436 WGroup *create_group(WWindow *par, const WFitParams *fp)
437 {
438     CREATEOBJ_IMPL(WGroup, group, (p, par, fp));
439 }
440
441
442 void group_deinit(WGroup *ws)
443 {
444     WGroupIterTmp tmp;
445     WRegion *reg;
446
447     if(ws->managed_stdisp!=NULL && ws->managed_stdisp->reg!=NULL){
448         group_managed_remove(ws, ws->managed_stdisp->reg);
449         assert(ws->managed_stdisp==NULL);
450     }
451
452     FOR_ALL_MANAGED_BY_GROUP(ws, reg, tmp){
453         destroy_obj((Obj*)reg);
454     }
455
456     assert(ws->managed_list==NULL);
457
458     XDeleteContext(ioncore_g.dpy, ws->dummywin, ioncore_g.win_context);
459     XDestroyWindow(ioncore_g.dpy, ws->dummywin);
460     ws->dummywin=None;
461
462     region_deinit(&ws->reg);
463 }
464
465
466     
467 bool group_rescue_clientwins(WGroup *ws, WPHolder *ph)
468 {
469     WGroupIterTmp tmp;
470     
471     group_iter_init_nostdisp(&tmp, ws);
472     
473     return region_rescue_some_clientwins((WRegion*)ws, ph,
474                                          (WRegionIterator*)group_iter,
475                                          &tmp);
476 }
477
478
479 bool group_may_destroy(WGroup *ws)
480 {
481     bool ret=group_essentially_empty(ws);
482     if(!ret)
483         warn(TR("Workspace not empty - refusing to destroy."));
484     return ret;
485 }
486
487
488 static bool group_managed_may_destroy(WGroup *ws, WRegion *reg)
489 {
490     return TRUE;
491 }
492
493
494 /*}}}*/
495
496
497 /*{{{ attach */
498
499
500 WStacking *group_do_add_managed(WGroup *ws, WRegion *reg, int level,
501                                 WSizePolicy szplcy)
502 {
503     WStacking *st=NULL;
504     CALL_DYN_RET(st, WStacking*, group_do_add_managed, ws, 
505                  (ws, reg, level, szplcy));
506     return st;
507 }
508     
509
510 WStacking *group_do_add_managed_default(WGroup *ws, WRegion *reg, int level,
511                                         WSizePolicy szplcy)
512 {
513     WStacking *st=NULL, *tmp=NULL;
514     Window bottom=None, top=None;
515     WStacking **stackingp=group_get_stackingp(ws);
516     WFrame *frame;
517     
518     if(stackingp==NULL)
519         return NULL;
520     
521     st=create_stacking();
522     
523     if(st==NULL)
524         return NULL;
525     
526     if(!stacking_assoc(st, reg)){
527         stacking_free(st);
528         return  NULL;
529     }
530     
531     frame=OBJ_CAST(reg, WFrame);
532     if(frame!=NULL){
533         WFrameMode m=frame_mode(frame);
534         if(m!=FRAME_MODE_FLOATING && m!=FRAME_MODE_TRANSIENT)
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 bool group_do_attach_final(WGroup *ws, 
566                            WRegion *reg,
567                            const WGroupAttachParams *param)
568 {
569     WStacking *st, *stabove=NULL;
570     WSizePolicy szplcy;
571     WFitParams fp;
572     WRectangle g;
573     uint level;
574     int weak;
575     bool sw;
576     
577     /* Fit */
578     szplcy=(param->szplcy_set
579             ? param->szplcy
580             : (param->bottom
581                ? SIZEPOLICY_FULL_EXACT
582                : SIZEPOLICY_UNCONSTRAINED));
583     
584     weak=(param->geom_weak_set
585           ? param->geom_weak
586           : (param->geom_set
587              ? 0
588              : REGION_RQGEOM_WEAK_ALL));
589     
590     if(param->geom_set)
591         geom_group_to_parent(ws, &param->geom, &g);
592     else
593         g=REGION_GEOM(reg);
594     
595     /* If the requested geometry does not overlap the workspaces's geometry, 
596      * position request is never honoured.
597      */
598     if((g.x+g.w<=REGION_GEOM(ws).x) ||
599        (g.x>=REGION_GEOM(ws).x+REGION_GEOM(ws).w)){
600         weak|=REGION_RQGEOM_WEAK_X;
601     }
602        
603     if((g.y+g.h<=REGION_GEOM(ws).y) ||
604        (g.y>=REGION_GEOM(ws).y+REGION_GEOM(ws).h)){
605         weak|=REGION_RQGEOM_WEAK_Y;
606     }
607
608     if(weak&(REGION_RQGEOM_WEAK_X|REGION_RQGEOM_WEAK_Y) &&
609         (szplcy==SIZEPOLICY_UNCONSTRAINED ||
610          szplcy==SIZEPOLICY_FREE ||
611          szplcy==SIZEPOLICY_FREE_GLUE /* without flags */)){
612         /* TODO: use 'weak'? */
613         group_calc_placement(ws, &g);
614     }
615
616     fp.g=REGION_GEOM(ws);
617     fp.mode=REGION_FIT_EXACT;
618
619     sizepolicy(&szplcy, reg, &g, weak, &fp);
620
621     if(rectangle_compare(&fp.g, &REGION_GEOM(reg))!=RECTANGLE_SAME)
622         region_fitrep(reg, NULL, &fp);
623     
624     /* Stacking & add */
625     if(param->stack_above!=NULL)
626         stabove=group_find_stacking(ws, param->stack_above);
627
628     level=(stabove!=NULL
629            ? stabove->level
630            : (param->level_set 
631               ? param->level 
632               : STACKING_LEVEL_NORMAL));
633
634     st=group_do_add_managed(ws, reg, level, szplcy);
635     
636     if(st==NULL)
637         return FALSE;
638     
639     if(stabove!=NULL)
640         st->above=stabove;
641
642     /* Misc. */
643     if(param->bottom){
644         ws->bottom=st;
645         
646         if(HAS_DYN(reg, region_manage_stdisp) && ws->managed_stdisp!=NULL){
647             WMPlex *mplex=OBJ_CAST(REGION_MANAGER(ws), WMPlex);
648             if(mplex!=NULL){ /* should always hold */
649                 WMPlexSTDispInfo di;
650                 WRegion *stdisp=NULL;
651                 mplex_get_stdisp(mplex, &stdisp, &di);
652                 if(stdisp!=NULL){
653                     assert(stdisp==ws->managed_stdisp->reg);
654                     /* WARNING! Calls back to group code (managed_remove). */
655                     region_manage_stdisp(reg, stdisp, &di);
656                 }
657             }
658         }
659     }
660     
661     sw=(param->switchto_set ? param->switchto : ioncore_g.switchto_new);
662     
663     if(sw || st->level>=STACKING_LEVEL_MODAL1){
664         WStacking *stf=find_to_focus(ws, st, FALSE);
665         
666         if(stf==st){
667             /* Ok, the new region can be focused */
668             group_refocus_(ws, stf);
669         }
670     }
671     
672     return TRUE;
673 }
674
675
676 WRegion *group_do_attach(WGroup *ws, 
677                          /*const*/ WGroupAttachParams *param,
678                          WRegionAttachData *data)
679 {
680     WFitParams fp;
681     WWindow *par;
682     WRegion *reg;
683     
684     if(ws->bottom!=NULL && param->bottom){
685         warn(TR("'bottom' already set."));
686         return NULL;
687     }
688     
689     par=REGION_PARENT(ws);
690     assert(par!=NULL);
691
692     if(param->geom_set){
693         geom_group_to_parent(ws, &param->geom, &fp.g);
694         fp.mode=REGION_FIT_EXACT;
695     }else{
696         fp.g=REGION_GEOM(ws);
697         fp.mode=REGION_FIT_BOUNDS|REGION_FIT_WHATEVER;
698     }
699     
700     return region_attach_helper((WRegion*) ws, par, &fp, 
701                                 (WRegionDoAttachFn*)group_do_attach_final,
702                                 /*(const WRegionAttachParams*)*/param, data);
703     /*                            ^^^^ doesn't seem to work. */
704 }
705
706
707 static void get_params(WGroup *ws, ExtlTab tab, WGroupAttachParams *par)
708 {
709     int tmp;
710     char *tmps;
711     ExtlTab g;
712     
713     par->switchto_set=0;
714     par->level_set=0;
715     par->szplcy_set=0;
716     par->geom_set=0;
717     par->bottom=0;
718     
719     if(extl_table_gets_i(tab, "level", &tmp)){
720         if(tmp>=0){
721             par->level_set=STACKING_LEVEL_NORMAL;
722             par->level=tmp;
723         }
724     }
725     
726     if(extl_table_is_bool_set(tab, "bottom")){
727         par->level=STACKING_LEVEL_BOTTOM;
728         par->level_set=1;
729         par->bottom=1;
730     }
731     
732     if(!par->level_set && extl_table_is_bool_set(tab, "modal")){
733         par->level=STACKING_LEVEL_MODAL1;
734         par->level_set=1;
735     }
736     
737     if(extl_table_is_bool_set(tab, "switchto"))
738         par->switchto=1;
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     get_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} & Class name (a string) of the object to be created. Mandatory. \\
800  *  \var{name} & Name of the object to be created (a string). Optional. \\
801  *  \var{switchto} & Should the region be switched to (boolean)? Optional. \\
802  *  \var{level} & Stacking level; default is 1. \\
803  *  \var{modal} & Make object modal; ignored if level is set. \\
804  *  \var{sizepolicy} & Size policy. \\
805  * \end{tabularx}
806  * 
807  * In addition parameters to the region to be created are passed in this 
808  * same table.
809  */
810 EXTL_EXPORT_MEMBER
811 WRegion *group_attach_new(WGroup *ws, ExtlTab param)
812 {
813     WGroupAttachParams par=GROUPATTACHPARAMS_INIT;
814     WRegionAttachData data;
815
816     get_params(ws, param, &par);
817     
818     data.type=REGION_ATTACH_LOAD;
819     data.u.tab=param;
820     
821     return group_do_attach(ws, &par, &data);
822 }
823
824
825 /*}}}*/
826
827
828 /*{{{ Status display support */
829
830
831 static int stdisp_szplcy(const WMPlexSTDispInfo *di, WRegion *stdisp)
832 {
833     int pos=di->pos;
834     
835     if(di->fullsize){
836         if(region_orientation(stdisp)==REGION_ORIENTATION_VERTICAL){
837             if(pos==MPLEX_STDISP_TL || pos==MPLEX_STDISP_BL)
838                 return SIZEPOLICY_STRETCH_LEFT;
839             else
840                 return SIZEPOLICY_STRETCH_RIGHT;
841         }else{
842             if(pos==MPLEX_STDISP_TL || pos==MPLEX_STDISP_TR)
843                 return SIZEPOLICY_STRETCH_TOP;
844             else
845                 return SIZEPOLICY_STRETCH_BOTTOM;
846         }
847     }else{
848         if(pos==MPLEX_STDISP_TL)
849             return SIZEPOLICY_GRAVITY_NORTHWEST;
850         else if(pos==MPLEX_STDISP_BL)
851             return SIZEPOLICY_GRAVITY_SOUTHWEST;
852         else if(pos==MPLEX_STDISP_TR)
853             return SIZEPOLICY_GRAVITY_NORTHEAST;
854         else /*if(pos=MPLEX_STDISP_BR)*/
855             return SIZEPOLICY_GRAVITY_SOUTHEAST;
856     }
857 }
858
859
860 void group_manage_stdisp(WGroup *ws, WRegion *stdisp, 
861                          const WMPlexSTDispInfo *di)
862 {
863     WFitParams fp;
864     uint szplcy;
865     WRegion *b=(ws->bottom==NULL ? NULL : ws->bottom->reg);
866     
867     /* Check if 'bottom' wants to manage the stdisp. */
868     if(b!=NULL 
869        && !OBJ_IS_BEING_DESTROYED(b)
870        && HAS_DYN(b, region_manage_stdisp)){
871         region_manage_stdisp(b, stdisp, di);
872         if(REGION_MANAGER(stdisp)==b)
873             return;
874     }
875     
876     /* No. */
877     
878     szplcy=stdisp_szplcy(di, stdisp)|SIZEPOLICY_SHRUNK;
879     
880     if(ws->managed_stdisp!=NULL && ws->managed_stdisp->reg==stdisp){
881         if(ws->managed_stdisp->szplcy==szplcy)
882             return;
883         ws->managed_stdisp->szplcy=szplcy;
884     }else{
885         region_detach_manager(stdisp);
886         ws->managed_stdisp=group_do_add_managed(ws, stdisp, 
887                                                 STACKING_LEVEL_ON_TOP, 
888                                                 szplcy);
889     }
890
891     fp.g=REGION_GEOM(ws);
892     sizepolicy(&ws->managed_stdisp->szplcy, stdisp, NULL, 0, &fp);
893
894     region_fitrep(stdisp, NULL, &fp);
895 }
896
897
898 void group_managed_rqgeom(WGroup *ws, WRegion *reg,
899                           const WRQGeomParams *rq,
900                           WRectangle *geomret)
901 {
902     WFitParams fp;
903     WStacking *st;
904         
905     st=group_find_stacking(ws, reg);
906
907     if(st==NULL){
908         fp.g=rq->geom;
909         fp.mode=REGION_FIT_EXACT;
910     }else{
911         fp.g=REGION_GEOM(ws);
912         sizepolicy(&st->szplcy, reg, &rq->geom, rq->flags, &fp);
913     }
914     
915     if(geomret!=NULL)
916         *geomret=fp.g;
917     
918     if(!(rq->flags&REGION_RQGEOM_TRYONLY))
919         region_fitrep(reg, NULL, &fp);
920 }
921
922
923 void group_managed_rqgeom_absolute(WGroup *grp, WRegion *sub, 
924                                    const WRQGeomParams *rq,
925                                    WRectangle *geomret)
926 {
927     if(grp->bottom!=NULL && grp->bottom->reg==sub){
928         region_rqgeom((WRegion*)grp, rq, geomret);
929         if(!(rq->flags&REGION_RQGEOM_TRYONLY) && geomret!=NULL)
930             *geomret=REGION_GEOM(sub);
931     }else{
932         WRQGeomParams rq2=*rq;
933         rq2.flags&=~REGION_RQGEOM_ABSOLUTE;
934
935         region_managed_rqgeom((WRegion*)grp, sub, &rq2, geomret);
936     }
937 }
938
939
940 /*}}}*/
941
942
943 /*{{{ Navigation */
944
945
946 static WStacking *nxt(WGroup *ws, WStacking *st, bool wrap)
947 {
948     return (st->mgr_next!=NULL 
949             ? st->mgr_next 
950             : (wrap ? ws->managed_list : NULL));
951 }
952
953
954 static WStacking *prv(WGroup *ws, WStacking *st, bool wrap)
955 {
956     return (st!=ws->managed_list 
957             ? st->mgr_prev
958             : (wrap ? st->mgr_prev : NULL));
959 }
960
961
962 typedef WStacking *NxtFn(WGroup *ws, WStacking *st, bool wrap);
963
964
965 static bool mapped_filt(WStacking *st, void *unused)
966 {
967     return (st->reg!=NULL && REGION_IS_MAPPED(st->reg));
968 }
969
970
971 static bool focusable(WGroup *ws, WStacking *st, uint min_level)
972 {
973     return (st->reg!=NULL
974             && REGION_IS_MAPPED(st->reg)
975             && !(st->reg->flags&REGION_SKIP_FOCUS)
976             && st->level>=min_level);
977 }
978
979        
980 static WStacking *do_get_next(WGroup *ws, WStacking *sti,
981                               NxtFn *fn, bool wrap, bool sti_ok)
982 {
983     WStacking *st, *stacking;
984     uint min_level=0;
985     
986     stacking=group_get_stacking(ws);
987     
988     if(stacking!=NULL)
989         min_level=stacking_min_level(stacking, mapped_filt, NULL);
990
991     st=sti;
992     while(1){
993         st=fn(ws, st, wrap); 
994         
995         if(st==NULL || st==sti)
996             break;
997         
998         if(focusable(ws, st, min_level))
999             return st;
1000     }
1001
1002     if(sti_ok && focusable(ws, sti, min_level))
1003         return sti;
1004     
1005     return NULL;
1006 }
1007
1008
1009 static WStacking *group_do_navi_first(WGroup *ws, WRegionNavi nh)
1010 {
1011     WStacking *lst=ws->managed_list;
1012     
1013     if(lst==NULL)
1014         return NULL;
1015
1016     if(nh==REGION_NAVI_ANY && 
1017        ws->current_managed!=NULL &&
1018        ws->current_managed->reg!=NULL){
1019         return ws->current_managed;
1020     }
1021     
1022     if(nh==REGION_NAVI_ANY || nh==REGION_NAVI_END || 
1023        nh==REGION_NAVI_BOTTOM || nh==REGION_NAVI_RIGHT){
1024         return do_get_next(ws, lst, prv, TRUE, TRUE);
1025     }else{
1026         return do_get_next(ws, lst->mgr_prev, nxt, TRUE, TRUE);
1027     }
1028 }
1029
1030
1031 static WRegion *group_navi_first(WGroup *ws, WRegionNavi nh,
1032                                  WRegionNaviData *data)
1033 {
1034     WStacking *st=group_do_navi_first(ws, nh);
1035     
1036     return region_navi_cont(&ws->reg, (st!=NULL ? st->reg : NULL), data);
1037 }
1038
1039
1040 static WStacking *group_do_navi_next(WGroup *ws, WStacking *st, 
1041                                      WRegionNavi nh, bool wrap)
1042 {
1043     if(st==NULL)
1044         return group_do_navi_first(ws, nh);
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, st, nxt, wrap, FALSE);
1049     }else{
1050         return do_get_next(ws, st, prv, wrap, FALSE);
1051     }
1052 }
1053
1054 static WRegion *group_navi_next(WGroup *ws, WRegion *reg, 
1055                                 WRegionNavi nh, WRegionNaviData *data)
1056 {
1057     WStacking *st=group_find_stacking(ws, reg);
1058     
1059     st=group_do_navi_next(ws, st, nh, FALSE);
1060     
1061     return region_navi_cont(&ws->reg, (st!=NULL ? st->reg : NULL), data);
1062 }
1063
1064
1065 /*}}}*/
1066
1067
1068 /*{{{ Stacking */
1069
1070
1071 /* 
1072  * Note: Managed objects are considered to be stacked separately from the
1073  * group, slightly violating expectations.
1074  */
1075
1076 void group_stacking(WGroup *ws, Window *bottomret, Window *topret)
1077 {
1078     Window win=region_xwindow((WRegion*)ws);
1079     
1080     *bottomret=win;
1081     *topret=win;
1082 }
1083
1084
1085 void group_restack(WGroup *ws, Window other, int mode)
1086 {
1087     Window win;
1088     
1089     win=region_xwindow((WRegion*)ws);
1090     if(win!=None){
1091         xwindow_restack(win, other, mode);
1092         other=win;
1093         mode=Above;
1094     }
1095 }
1096
1097
1098 WStacking *group_find_stacking(WGroup *ws, WRegion *r)
1099 {
1100     if(r==NULL || REGION_MANAGER(r)!=(WRegion*)ws)
1101         return NULL;
1102     
1103     return ioncore_find_stacking(r);
1104 }
1105
1106
1107 static WStacking *find_stacking_if_not_on_ws(WGroup *ws, Window w)
1108 {
1109     WRegion *r=xwindow_region_of(w);
1110     WStacking *st=NULL;
1111     
1112     while(r!=NULL){
1113         if(REGION_MANAGER(r)==(WRegion*)ws)
1114             break;
1115         st=group_find_stacking(ws, r);
1116         if(st!=NULL)
1117             break;
1118         r=REGION_MANAGER(r);
1119     }
1120     
1121     return st;
1122 }
1123
1124
1125 bool group_managed_rqorder(WGroup *grp, WRegion *reg, WRegionOrder order)
1126 {
1127     WStacking **stackingp=group_get_stackingp(grp);
1128     WStacking *st;
1129
1130     if(stackingp==NULL || *stackingp==NULL)
1131         return FALSE;
1132
1133     st=group_find_stacking(grp, reg);
1134     
1135     if(st==NULL)
1136         return FALSE;
1137     
1138     stacking_restack(stackingp, st, None, NULL, NULL,
1139                      (order!=REGION_ORDER_FRONT));
1140     
1141     return TRUE;
1142 }
1143
1144
1145 /*}}}*/
1146
1147
1148 /*{{{ Misc. */
1149
1150
1151 /*EXTL_DOC
1152  * Returns the 'bottom' of \var{ws}.
1153  */
1154 EXTL_EXPORT_MEMBER
1155 WRegion *group_bottom(WGroup *ws)
1156 {
1157     return (ws->bottom!=NULL ? ws->bottom->reg : NULL);
1158 }
1159
1160
1161 /*EXTL_DOC
1162  * Iterate over managed regions of \var{ws} until \var{iterfn} returns
1163  * \code{false}.
1164  * The function itself returns \code{true} if it reaches the end of list
1165  * without this happening.
1166  */
1167 EXTL_SAFE
1168 EXTL_EXPORT_MEMBER
1169 bool group_managed_i(WGroup *ws, ExtlFn iterfn)
1170 {
1171     WGroupIterTmp tmp;
1172     group_iter_init(&tmp, ws);
1173     
1174     return extl_iter_objlist_(iterfn, (ObjIterator*)group_iter, &tmp);
1175 }
1176
1177
1178 WRegion* group_current(WGroup *ws)
1179 {
1180     return (ws->current_managed!=NULL ? ws->current_managed->reg : NULL);
1181 }
1182
1183
1184 void group_size_hints(WGroup *ws, WSizeHints *hints_ret)
1185 {
1186     if(ws->bottom==NULL || ws->bottom->reg==NULL){
1187         sizehints_clear(hints_ret);
1188     }else{
1189         region_size_hints(ws->bottom->reg, hints_ret);
1190         hints_ret->no_constrain=TRUE;
1191     }
1192 }
1193
1194
1195 Window group_xwindow(const WGroup *ws)
1196 {
1197     return ws->dummywin;
1198 }
1199
1200
1201 WRegion *region_group_if_bottom(WRegion *reg)
1202 {
1203     WGroup *grp=REGION_MANAGER_CHK(reg, WGroup);
1204     
1205     if(grp!=NULL && group_bottom(grp)==reg)
1206         return (WRegion*)grp;
1207     else
1208         return reg;
1209 }
1210
1211
1212 /*}}}*/
1213
1214
1215 /*{{{ Save/load */
1216
1217
1218 static ExtlTab group_get_configuration(WGroup *ws)
1219 {
1220     ExtlTab tab, mgds, subtab, g;
1221     WStacking *st;
1222     WGroupIterTmp tmp;
1223     WMPlex *par;
1224     int n=0;
1225     WRectangle tmpg;
1226     
1227     tab=region_get_base_configuration((WRegion*)ws);
1228     
1229     mgds=extl_create_table();
1230     
1231     extl_table_sets_t(tab, "managed", mgds);
1232     
1233     /* TODO: stacking order messed up */
1234     
1235     FOR_ALL_NODES_IN_GROUP(ws, st, tmp){
1236         if(st->reg==NULL)
1237             continue;
1238         
1239         subtab=region_get_configuration(st->reg);
1240
1241         if(subtab!=extl_table_none()){
1242             extl_table_sets_i(subtab, "sizepolicy", st->szplcy);
1243             extl_table_sets_i(subtab, "level", st->level);
1244         
1245             tmpg=REGION_GEOM(st->reg);
1246             tmpg.x-=REGION_GEOM(ws).x;
1247             tmpg.y-=REGION_GEOM(ws).y;
1248             
1249             g=extl_table_from_rectangle(&tmpg);
1250             extl_table_sets_t(subtab, "geom", g);
1251             extl_unref_table(g);
1252             
1253             if(ws->bottom==st)
1254                 extl_table_sets_b(subtab, "bottom", TRUE);
1255         
1256             extl_table_seti_t(mgds, ++n, subtab);
1257             extl_unref_table(subtab);
1258         }
1259     }
1260     
1261     extl_unref_table(mgds);
1262     
1263     return tab;
1264 }
1265
1266
1267 void group_do_load(WGroup *ws, ExtlTab tab)
1268 {
1269     ExtlTab substab, subtab;
1270     int i, n;
1271     
1272     if(extl_table_gets_t(tab, "managed", &substab)){
1273         n=extl_table_get_n(substab);
1274         for(i=1; i<=n; i++){
1275             if(extl_table_geti_t(substab, i, &subtab)){
1276                 group_attach_new(ws, subtab);
1277                 extl_unref_table(subtab);
1278             }
1279         }
1280         
1281         extl_unref_table(substab);
1282     }
1283
1284     ws->bottom_last_close=extl_table_is_bool_set(tab, "bottom_last_close");
1285 }
1286
1287
1288 WRegion *group_load(WWindow *par, const WFitParams *fp, ExtlTab tab)
1289 {
1290     WGroup *ws;
1291     
1292     ws=create_group(par, fp);
1293     
1294     if(ws==NULL)
1295         return NULL;
1296         
1297     group_do_load(ws, tab);
1298     
1299     return (WRegion*)ws;
1300 }
1301
1302
1303 /*}}}*/
1304
1305
1306 /*{{{ Dynamic function table and class implementation */
1307
1308
1309 static DynFunTab group_dynfuntab[]={
1310     {(DynFun*)region_fitrep,
1311      (DynFun*)group_fitrep},
1312
1313     {region_map, 
1314      group_map},
1315     
1316     {region_unmap, 
1317      group_unmap},
1318     
1319     {(DynFun*)region_managed_prepare_focus, 
1320      (DynFun*)group_managed_prepare_focus},
1321
1322     {region_do_set_focus, 
1323      group_do_set_focus},
1324     
1325     {region_managed_notify, 
1326      group_managed_notify},
1327     
1328     {region_managed_remove,
1329      group_managed_remove},
1330     
1331     {(DynFun*)region_get_configuration, 
1332      (DynFun*)group_get_configuration},
1333
1334     {(DynFun*)region_may_destroy,
1335      (DynFun*)group_may_destroy},
1336
1337     {(DynFun*)region_managed_may_destroy,
1338      (DynFun*)group_managed_may_destroy},
1339
1340     {(DynFun*)region_current,
1341      (DynFun*)group_current},
1342     
1343     {(DynFun*)region_rescue_clientwins,
1344      (DynFun*)group_rescue_clientwins},
1345     
1346     {region_restack,
1347      group_restack},
1348
1349     {region_stacking,
1350      group_stacking},
1351
1352     {(DynFun*)region_managed_get_pholder,
1353      (DynFun*)group_managed_get_pholder},
1354
1355     {region_managed_rqgeom,
1356      group_managed_rqgeom},
1357      
1358     {region_managed_rqgeom_absolute,
1359      group_managed_rqgeom_absolute},
1360     
1361     {(DynFun*)group_do_add_managed,
1362      (DynFun*)group_do_add_managed_default},
1363     
1364     {region_size_hints,
1365      group_size_hints},
1366     
1367     {(DynFun*)region_xwindow,
1368      (DynFun*)group_xwindow},
1369     
1370     {(DynFun*)region_navi_first,
1371      (DynFun*)group_navi_first},
1372     
1373     {(DynFun*)region_navi_next,
1374      (DynFun*)group_navi_next},
1375     
1376     {(DynFun*)region_managed_rqorder,
1377      (DynFun*)group_managed_rqorder},
1378     
1379     END_DYNFUNTAB
1380 };
1381
1382
1383 EXTL_EXPORT
1384 IMPLCLASS(WGroup, WRegion, group_deinit, group_dynfuntab);
1385
1386
1387 /*}}}*/
1388