]> git.decadent.org.uk Git - ion3.git/blob - ioncore/group.c
[svn-inject] Installing original source of ion3
[ion3.git] / ioncore / group.c
1 /*
2  * ion/ioncore/group.c
3  *
4  * Copyright (c) Tuomo Valkonen 1999-2006. 
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
44
45 static void group_place_stdisp(WGroup *ws, WWindow *parent,
46                                  int pos, WRegion *stdisp);
47
48
49
50 /*{{{ Stacking list stuff */
51
52
53 WStacking *group_get_stacking(WGroup *ws)
54 {
55     WWindow *par=REGION_PARENT(ws);
56     
57     return (par==NULL 
58             ? NULL
59             : window_get_stacking(par));
60 }
61
62
63 WStacking **group_get_stackingp(WGroup *ws)
64 {
65     WWindow *par=REGION_PARENT(ws);
66     
67     return (par==NULL 
68             ? NULL
69             : window_get_stackingp(par));
70 }
71
72
73 static bool wsfilt(WStacking *st, void *ws)
74 {
75     return (st->reg!=NULL && REGION_MANAGER(st->reg)==(WRegion*)ws);
76 }
77
78
79 static bool wsfilt_nostdisp(WStacking *st, void *ws)
80 {
81     return (wsfilt(st, ws) && ((WGroup*)ws)->managed_stdisp!=st);
82 }
83
84
85 void group_iter_init(WGroupIterTmp *tmp, WGroup *ws)
86 {
87     stacking_iter_mgr_init(tmp, ws->managed_list, NULL, ws);
88 }
89
90
91 void group_iter_init_nostdisp(WGroupIterTmp *tmp, WGroup *ws)
92 {
93     stacking_iter_mgr_init(tmp, ws->managed_list, wsfilt_nostdisp, ws);
94 }
95
96
97 WRegion *group_iter(WGroupIterTmp *tmp)
98 {
99     return stacking_iter_mgr(tmp);
100 }
101
102
103 WStacking *group_iter_nodes(WGroupIterTmp *tmp)
104 {
105     return stacking_iter_mgr_nodes(tmp);
106 }
107
108
109 WGroupIterTmp group_iter_default_tmp;
110
111
112 /*}}}*/
113
114
115 /*{{{ region dynfun implementations */
116
117
118 static void group_fit(WGroup *ws, const WRectangle *geom)
119 {
120     REGION_GEOM(ws)=*geom;
121 }
122
123
124 bool group_fitrep(WGroup *ws, WWindow *par, const WFitParams *fp)
125 {
126     WGroupIterTmp tmp;
127     WStacking *unweaved=NULL;
128     int xdiff=0, ydiff=0;
129     WStacking *st;
130     WWindow *oldpar;
131     WRectangle g;
132     
133     oldpar=REGION_PARENT(ws);
134     
135     if(par==NULL){
136         if(fp->mode&REGION_FIT_WHATEVER)
137             return TRUE;
138         REGION_GEOM(ws)=fp->g;
139     }else{
140         if(!region_same_rootwin((WRegion*)ws, (WRegion*)par))
141             return FALSE;
142         
143         if(ws->managed_stdisp!=NULL && ws->managed_stdisp->reg!=NULL)
144             region_detach_manager(ws->managed_stdisp->reg);
145         
146         assert(ws->managed_stdisp==NULL);
147         
148         xdiff=fp->g.x-REGION_GEOM(ws).x;
149         ydiff=fp->g.y-REGION_GEOM(ws).y;
150     
151         region_unset_parent((WRegion*)ws);
152         XReparentWindow(ioncore_g.dpy, ws->dummywin, par->win, -1, -1);
153         region_set_parent((WRegion*)ws, par);
154
155         REGION_GEOM(ws).x=fp->g.x;
156         REGION_GEOM(ws).y=fp->g.y;
157         if(!(fp->mode&REGION_FIT_WHATEVER)){
158             REGION_GEOM(ws).w=fp->g.w;
159             REGION_GEOM(ws).h=fp->g.h;
160         }
161         
162         if(oldpar!=NULL)
163             unweaved=stacking_unweave(&oldpar->stacking, wsfilt, (void*)ws);
164     }
165
166     FOR_ALL_NODES_IN_GROUP(ws, st, tmp){
167         WFitParams fp2=*fp;
168         
169         if(st->reg==NULL)
170             continue;
171         
172         g=REGION_GEOM(st->reg);
173         g.x+=xdiff;
174         g.y+=ydiff;
175
176         if(fp->mode&REGION_FIT_WHATEVER){
177             fp2.g=g;
178         }else{
179             fp2.g=REGION_GEOM(ws);
180             sizepolicy(&st->szplcy, st->reg, &g, REGION_RQGEOM_WEAK_ALL, &fp2);
181         }
182         
183         if(!region_fitrep(st->reg, par, &fp2)){
184             warn(TR("Error reparenting %s."), region_name(st->reg));
185             region_detach_manager(st->reg);
186         }
187     }
188     
189     if(unweaved!=NULL)
190         stacking_weave(&par->stacking, &unweaved, FALSE);
191
192     return TRUE;
193 }
194
195
196 static void group_map(WGroup *ws)
197 {
198     WRegion *reg;
199     WGroupIterTmp tmp;
200
201     REGION_MARK_MAPPED(ws);
202     XMapWindow(ioncore_g.dpy, ws->dummywin);
203     
204     FOR_ALL_MANAGED_BY_GROUP(ws, reg, tmp){
205         region_map(reg);
206     }
207 }
208
209
210 static void group_unmap(WGroup *ws)
211 {
212     WRegion *reg;
213     WGroupIterTmp tmp;
214
215     REGION_MARK_UNMAPPED(ws);
216     XUnmapWindow(ioncore_g.dpy, ws->dummywin);
217
218     FOR_ALL_MANAGED_BY_GROUP(ws, reg, tmp){
219         region_unmap(reg);
220     }
221 }
222
223
224 static WStacking *find_to_focus(WGroup *ws, WStacking *st, bool group_only)
225 {
226     WStacking *stacking=group_get_stacking(ws);
227     
228     if(stacking==NULL)
229         return st;
230                                 
231     return stacking_find_to_focus_mapped(stacking, st, 
232                                          (group_only ? (WRegion*)ws : NULL));
233 }
234
235
236 static bool group_refocus_(WGroup *ws, WStacking *st)
237 {
238     if(st!=ws->current_managed && st->reg!=NULL){
239         if(region_may_control_focus((WRegion*)ws))
240             region_set_focus(st->reg);
241         else
242             ws->current_managed=st;
243         return TRUE;
244     }
245     
246     return FALSE;
247 }
248
249
250 static void group_do_set_focus(WGroup *ws, bool warp)
251 {
252     WStacking *st=ws->current_managed;
253     
254     if(st==NULL || st->reg==NULL)
255         st=find_to_focus(ws, NULL, TRUE);
256     
257     if(st!=NULL && st->reg!=NULL)
258         region_do_set_focus(st->reg, warp);
259     else
260         region_finalise_focusing((WRegion*)ws, ws->dummywin, warp);
261 }
262
263
264 static bool group_managed_prepare_focus(WGroup *ws, WRegion *reg, 
265                                         int flags, WPrepareFocusResult *res)
266 {
267     WMPlex *mplex=OBJ_CAST(REGION_MANAGER(ws), WMPlex);
268     WStacking *st=group_find_stacking(ws, reg);    
269     
270     if(st==NULL)
271         return FALSE;
272
273     if(mplex!=NULL){
274         WStacking *node=mplex_find_stacking(mplex, (WRegion*)ws);
275         
276         if(node==NULL)
277             return FALSE;
278         
279         return mplex_do_prepare_focus(mplex, node, st,
280                                       flags, res);
281     }else{
282         WStacking *stacking;
283         
284         if(!region_prepare_focus((WRegion*)ws, flags, res))
285             return FALSE;
286
287         stacking=group_get_stacking(ws);
288         st=find_to_focus(ws, st, FALSE);
289         
290 #warning "TODO: raise in some cases (not enter-window)?"
291         
292         if(st==NULL)
293             return FALSE;
294         
295         res->reg=st->reg;
296         res->flags=flags;
297         
298         return (res->reg==reg);
299     }
300 }
301
302
303 static bool group_essentially_empty(WGroup *ws)
304 {
305     WGroupIterTmp tmp;
306     WStacking *st;
307     
308     FOR_ALL_NODES_IN_GROUP(ws, st, tmp){
309         if(st!=ws->managed_stdisp)
310             return FALSE;
311     }
312     
313     return TRUE;
314 }
315
316
317 void group_managed_remove(WGroup *ws, WRegion *reg)
318 {
319     bool mcf=region_may_control_focus((WRegion*)ws);
320     bool ds=OBJ_IS_BEING_DESTROYED(ws);
321     WStacking *st, *next_st=NULL;
322     bool was_stdisp=FALSE, was_bottom=FALSE;
323     bool dest=FALSE;
324     bool cur=FALSE;
325     
326     st=group_find_stacking(ws, reg);
327
328     if(st!=NULL){
329         next_st=stacking_unstack(REGION_PARENT(ws), st);
330         
331         UNLINK_ITEM(ws->managed_list, st, mgr_next, mgr_prev);
332         
333         if(st==ws->managed_stdisp){
334             ws->managed_stdisp=NULL;
335             was_stdisp=TRUE;
336         }
337         
338         if(st==ws->bottom){
339             ws->bottom=NULL;
340             was_bottom=TRUE;
341             if(ws->bottom_last_close && group_essentially_empty(ws))
342                 dest=TRUE;
343         }
344             
345         if(st==ws->current_managed){
346             cur=TRUE;
347             ws->current_managed=NULL;
348         }
349         
350         stacking_unassoc(st);
351         stacking_free(st);
352     }
353     
354     region_unset_manager(reg, (WRegion*)ws);
355
356     if(!dest && !ds){
357         if(was_bottom && !was_stdisp && ws->managed_stdisp==NULL){
358             /* We should probably be managing any stdisp, that 'bottom' 
359              * was managing.
360              */
361             WMPlex *mplex=OBJ_CAST(REGION_MANAGER(ws), WMPlex);
362             
363             if(mplex!=NULL 
364                && mplex->mx_current!=NULL 
365                && mplex->mx_current->st->reg==(WRegion*)ws){
366                 mplex_remanage_stdisp(mplex);
367             }
368         }
369         
370         if(cur){
371             WStacking *stf=find_to_focus(ws, next_st, TRUE);
372             if(stf!=NULL)
373                 region_warp(stf->reg);
374         }
375     }else if(dest && !ds){
376         mainloop_defer_destroy((Obj*)ws);
377     }
378 }
379
380
381 static void group_managed_activated(WGroup *ws, WRegion *reg)
382 {
383     ws->current_managed=group_find_stacking(ws, reg);
384 }
385
386
387 /*}}}*/
388
389
390 /*{{{ Create/destroy */
391
392
393 bool group_init(WGroup *ws, WWindow *par, const WFitParams *fp)
394 {
395     ws->current_managed=NULL;
396     ws->managed_stdisp=NULL;
397     ws->bottom=NULL;
398     ws->managed_list=NULL;
399     
400     ws->dummywin=XCreateWindow(ioncore_g.dpy, par->win,
401                                 fp->g.x, fp->g.y, 1, 1, 0,
402                                 CopyFromParent, InputOnly,
403                                 CopyFromParent, 0, NULL);
404     if(ws->dummywin==None)
405         return FALSE;
406
407     region_init(&ws->reg, par, fp);
408     region_register(&ws->reg);
409
410     XSelectInput(ioncore_g.dpy, ws->dummywin,
411                  FocusChangeMask|KeyPressMask|KeyReleaseMask|
412                  ButtonPressMask|ButtonReleaseMask);
413     XSaveContext(ioncore_g.dpy, ws->dummywin, ioncore_g.win_context,
414                  (XPointer)ws);
415     
416     ((WRegion*)ws)->flags|=REGION_GRAB_ON_PARENT;
417     
418     region_add_bindmap((WRegion*)ws, ioncore_group_bindmap);
419     
420     return TRUE;
421 }
422
423
424 WGroup *create_group(WWindow *par, const WFitParams *fp)
425 {
426     CREATEOBJ_IMPL(WGroup, group, (p, par, fp));
427 }
428
429
430 void group_deinit(WGroup *ws)
431 {
432     WGroupIterTmp tmp;
433     WRegion *reg;
434
435     if(ws->managed_stdisp!=NULL && ws->managed_stdisp->reg!=NULL){
436         group_managed_remove(ws, ws->managed_stdisp->reg);
437         assert(ws->managed_stdisp==NULL);
438     }
439
440     FOR_ALL_MANAGED_BY_GROUP(ws, reg, tmp){
441         destroy_obj((Obj*)reg);
442     }
443
444     assert(ws->managed_list==NULL);
445
446     XDeleteContext(ioncore_g.dpy, ws->dummywin, ioncore_g.win_context);
447     XDestroyWindow(ioncore_g.dpy, ws->dummywin);
448     ws->dummywin=None;
449
450     region_deinit(&ws->reg);
451 }
452
453
454     
455 bool group_rescue_clientwins(WGroup *ws, WPHolder *ph)
456 {
457     WGroupIterTmp tmp;
458     
459     group_iter_init_nostdisp(&tmp, ws);
460     
461     return region_rescue_some_clientwins((WRegion*)ws, ph,
462                                          (WRegionIterator*)group_iter,
463                                          &tmp);
464 }
465
466
467 bool group_may_destroy(WGroup *ws)
468 {
469     bool ret=group_essentially_empty(ws);
470     if(!ret)
471         warn(TR("Workspace not empty - refusing to destroy."));
472     return ret;
473 }
474
475
476 static bool group_managed_may_destroy(WGroup *ws, WRegion *reg)
477 {
478     return TRUE;
479 }
480
481
482 /*}}}*/
483
484
485 /*{{{ attach */
486
487
488 WStacking *group_do_add_managed(WGroup *ws, WRegion *reg, int level,
489                                 WSizePolicy szplcy)
490 {
491     WStacking *st=NULL;
492     CALL_DYN_RET(st, WStacking*, group_do_add_managed, ws, 
493                  (ws, reg, level, szplcy));
494     return st;
495 }
496     
497
498 WStacking *group_do_add_managed_default(WGroup *ws, WRegion *reg, int level,
499                                         WSizePolicy szplcy)
500 {
501     WStacking *st=NULL, *tmp=NULL;
502     Window bottom=None, top=None;
503     WStacking **stackingp=group_get_stackingp(ws);
504     WFrame *frame;
505     
506     if(stackingp==NULL)
507         return NULL;
508     
509     st=create_stacking();
510     
511     if(st==NULL)
512         return NULL;
513     
514     if(!stacking_assoc(st, reg)){
515         stacking_free(st);
516         return  NULL;
517     }
518     
519     frame=OBJ_CAST(reg, WFrame);
520     if(frame!=NULL){
521         WFrameMode m=frame_mode(frame);
522         if(m!=FRAME_MODE_FLOATING && m!=FRAME_MODE_TRANSIENT)
523             frame_set_mode(frame, FRAME_MODE_FLOATING);
524     }
525
526     st->level=level;
527     st->szplcy=szplcy;
528
529     LINK_ITEM_FIRST(tmp, st, next, prev);
530     stacking_weave(stackingp, &tmp, FALSE);
531     assert(tmp==NULL);
532
533     LINK_ITEM(ws->managed_list, st, mgr_next, mgr_prev);
534     region_set_manager(reg, (WRegion*)ws);
535
536     if(region_is_fully_mapped((WRegion*)ws))
537         region_map(reg);
538
539     return st;
540 }
541
542
543 static void geom_group_to_parent(WGroup *ws, const WRectangle *g, 
544                                  WRectangle *wg)
545 {
546     wg->x=g->x+REGION_GEOM(ws).x;
547     wg->y=g->y+REGION_GEOM(ws).y;
548     wg->w=maxof(1, g->w);
549     wg->h=maxof(1, g->h);
550 }
551
552
553 bool group_do_attach_final(WGroup *ws, 
554                            WRegion *reg,
555                            const WGroupAttachParams *param)
556 {
557     WStacking *st, *stabove=NULL;
558     WSizePolicy szplcy;
559     WFitParams fp;
560     WRectangle g;
561     uint level;
562     int weak;
563     bool sw;
564     
565     /* Fit */
566     szplcy=(param->szplcy_set
567             ? param->szplcy
568             : (param->bottom
569                ? SIZEPOLICY_FULL_EXACT
570                : SIZEPOLICY_UNCONSTRAINED));
571     
572     weak=(param->geom_weak_set
573           ? param->geom_weak
574           : (param->geom_set
575              ? 0
576              : REGION_RQGEOM_WEAK_ALL));
577     
578     if(param->geom_set)
579         geom_group_to_parent(ws, &param->geom, &g);
580     else
581         g=REGION_GEOM(reg);
582     
583     /* If the requested geometry does not overlap the workspaces's geometry, 
584      * position request is never honoured.
585      */
586     if((g.x+g.w<=REGION_GEOM(ws).x) ||
587        (g.y+g.h<=REGION_GEOM(ws).y) ||
588        (g.x>=REGION_GEOM(ws).x+REGION_GEOM(ws).w) ||
589        (g.y>=REGION_GEOM(ws).y+REGION_GEOM(ws).h)){
590         weak|=REGION_RQGEOM_WEAK_X|REGION_RQGEOM_WEAK_X;
591     }
592
593     if((weak&(REGION_RQGEOM_WEAK_X|REGION_RQGEOM_WEAK_Y))
594        ==(REGION_RQGEOM_WEAK_X|REGION_RQGEOM_WEAK_Y) &&
595         (szplcy==SIZEPOLICY_UNCONSTRAINED ||
596          szplcy==SIZEPOLICY_FREE ||
597          szplcy==SIZEPOLICY_FREE_GLUE /* without flags */)){
598         group_calc_placement(ws, &g);
599     }
600
601     fp.g=REGION_GEOM(ws);
602     fp.mode=REGION_FIT_EXACT;
603
604     sizepolicy(&szplcy, reg, &g, weak, &fp);
605
606     if(rectangle_compare(&fp.g, &REGION_GEOM(reg))!=RECTANGLE_SAME)
607         region_fitrep(reg, NULL, &fp);
608     
609     /* Stacking & add */
610     if(param->stack_above!=NULL)
611         stabove=group_find_stacking(ws, param->stack_above);
612
613     level=(stabove!=NULL
614            ? stabove->level
615            : (param->level_set 
616               ? param->level 
617               : STACKING_LEVEL_NORMAL));
618
619     st=group_do_add_managed(ws, reg, level, szplcy);
620     
621     if(st==NULL)
622         return FALSE;
623     
624     if(stabove!=NULL)
625         st->above=stabove;
626
627     /* Misc. */
628     if(param->bottom){
629         ws->bottom=st;
630         
631         if(HAS_DYN(reg, region_manage_stdisp) && ws->managed_stdisp!=NULL){
632             WMPlex *mplex=OBJ_CAST(REGION_MANAGER(ws), WMPlex);
633             if(mplex!=NULL){ /* should always hold */
634                 WMPlexSTDispInfo di;
635                 WRegion *stdisp=NULL;
636                 mplex_get_stdisp(mplex, &stdisp, &di);
637                 if(stdisp!=NULL){
638                     assert(stdisp==ws->managed_stdisp->reg);
639                     /* WARNING! Calls back to group code (managed_remove). */
640                     region_manage_stdisp(reg, stdisp, &di);
641                 }
642             }
643         }
644     }
645     
646     sw=(param->switchto_set ? param->switchto : ioncore_g.switchto_new);
647     
648     if(sw || st->level>=STACKING_LEVEL_MODAL1){
649         WStacking *stf=find_to_focus(ws, st, FALSE);
650         
651         if(stf==st){
652             /* Ok, the new region can be focused */
653             group_refocus_(ws, stf);
654         }
655     }
656     
657     return TRUE;
658 }
659
660
661 WRegion *group_do_attach(WGroup *ws, 
662                          /*const*/ WGroupAttachParams *param,
663                          WRegionAttachData *data)
664 {
665     WFitParams fp;
666     WWindow *par;
667     WRegion *reg;
668
669     if(ws->bottom!=NULL && param->bottom){
670         warn(TR("'bottom' already set."));
671         return NULL;
672     }
673     
674     par=REGION_PARENT(ws);
675     assert(par!=NULL);
676
677     if(param->geom_set){
678         geom_group_to_parent(ws, &param->geom, &fp.g);
679         fp.mode=REGION_FIT_EXACT;
680     }else{
681         fp.g=REGION_GEOM(ws);
682         fp.mode=REGION_FIT_BOUNDS|REGION_FIT_WHATEVER;
683     }
684
685     return region_attach_helper((WRegion*) ws, par, &fp, 
686                                 (WRegionDoAttachFn*)group_do_attach_final,
687                                 /*(const WRegionAttachParams*)*/param, data);
688     /*                            ^^^^ doesn't seem to work. */
689 }
690
691
692 static void get_params(WGroup *ws, ExtlTab tab, WGroupAttachParams *par)
693 {
694     int tmp;
695     char *tmps;
696     ExtlTab g;
697     
698     par->switchto_set=0;
699     par->level_set=0;
700     par->szplcy_set=0;
701     par->geom_set=0;
702     par->bottom=0;
703     
704     if(extl_table_gets_i(tab, "level", &tmp)){
705         if(tmp>=0){
706             par->level_set=STACKING_LEVEL_NORMAL;
707             par->level=tmp;
708         }
709     }
710     
711     if(extl_table_is_bool_set(tab, "bottom")){
712         par->level=STACKING_LEVEL_BOTTOM;
713         par->level_set=1;
714         par->bottom=1;
715     }
716     
717     if(!par->level_set && extl_table_is_bool_set(tab, "modal")){
718         par->level=STACKING_LEVEL_MODAL1;
719         par->level_set=1;
720     }
721     
722     if(extl_table_is_bool_set(tab, "switchto"))
723         par->switchto=1;
724
725     if(extl_table_gets_i(tab, "sizepolicy", &tmp)){
726         par->szplcy_set=1;
727         par->szplcy=tmp;
728     }else if(extl_table_gets_s(tab, "sizepolicy", &tmps)){
729         if(string2sizepolicy(tmps, &par->szplcy))
730             par->szplcy_set=1;
731         free(tmps);
732     }
733     
734     if(extl_table_gets_t(tab, "geom", &g)){
735         int n=0;
736         
737         if(extl_table_gets_i(g, "x", &(par->geom.x)))
738             n++;
739         if(extl_table_gets_i(g, "y", &(par->geom.y)))
740             n++;
741         if(extl_table_gets_i(g, "w", &(par->geom.w)))
742             n++;
743         if(extl_table_gets_i(g, "h", &(par->geom.h)))
744             n++;
745         
746         if(n==4)
747             par->geom_set=1;
748         
749         extl_unref_table(g);
750     }
751 }
752
753
754
755 /*EXTL_DOC
756  * Attach and reparent existing region \var{reg} to \var{ws}.
757  * The table \var{param} may contain the fields \var{index} and
758  * \var{switchto} that are interpreted as for \fnref{WMPlex.attach_new}.
759  */
760 EXTL_EXPORT_MEMBER
761 WRegion *group_attach(WGroup *ws, WRegion *reg, ExtlTab param)
762 {
763     WGroupAttachParams par=GROUPATTACHPARAMS_INIT;
764     WRegionAttachData data;
765
766     if(reg==NULL)
767         return NULL;
768     
769     get_params(ws, param, &par);
770     
771     data.type=REGION_ATTACH_REPARENT;
772     data.u.reg=reg;
773     
774     return group_do_attach(ws, &par, &data);
775 }
776
777
778 /*EXTL_DOC
779  * Create a new region to be managed by \var{ws}. At least the following
780  * fields in \var{param} are understood:
781  * 
782  * \begin{tabularx}{\linewidth}{lX}
783  *  \tabhead{Field & Description}
784  *  \var{type} & Class name (a string) of the object to be created. Mandatory. \\
785  *  \var{name} & Name of the object to be created (a string). Optional. \\
786  *  \var{switchto} & Should the region be switched to (boolean)? Optional. \\
787  *  \var{level} & Stacking level; default is 1. \\
788  *  \var{modal} & Make object modal; ignored if level is set. \\
789  *  \var{sizepolicy} & Size policy. \\
790  * \end{tabularx}
791  * 
792  * In addition parameters to the region to be created are passed in this 
793  * same table.
794  */
795 EXTL_EXPORT_MEMBER
796 WRegion *group_attach_new(WGroup *ws, ExtlTab param)
797 {
798     WGroupAttachParams par=GROUPATTACHPARAMS_INIT;
799     WRegionAttachData data;
800
801     get_params(ws, param, &par);
802     
803     data.type=REGION_ATTACH_LOAD;
804     data.u.tab=param;
805     
806     return group_do_attach(ws, &par, &data);
807 }
808
809
810 /*}}}*/
811
812
813 /*{{{ Status display support */
814
815
816 static int stdisp_szplcy(const WMPlexSTDispInfo *di, WRegion *stdisp)
817 {
818     int pos=di->pos;
819     
820     if(di->fullsize){
821         if(region_orientation(stdisp)==REGION_ORIENTATION_VERTICAL){
822             if(pos==MPLEX_STDISP_TL || pos==MPLEX_STDISP_BL)
823                 return SIZEPOLICY_STRETCH_LEFT;
824             else
825                 return SIZEPOLICY_STRETCH_RIGHT;
826         }else{
827             if(pos==MPLEX_STDISP_TL || pos==MPLEX_STDISP_TR)
828                 return SIZEPOLICY_STRETCH_TOP;
829             else
830                 return SIZEPOLICY_STRETCH_BOTTOM;
831         }
832     }else{
833         if(pos==MPLEX_STDISP_TL)
834             return SIZEPOLICY_GRAVITY_NORTHWEST;
835         else if(pos==MPLEX_STDISP_BL)
836             return SIZEPOLICY_GRAVITY_SOUTHWEST;
837         else if(pos==MPLEX_STDISP_TR)
838             return SIZEPOLICY_GRAVITY_NORTHEAST;
839         else /*if(pos=MPLEX_STDISP_BR)*/
840             return SIZEPOLICY_GRAVITY_SOUTHEAST;
841     }
842 }
843
844
845 void group_manage_stdisp(WGroup *ws, WRegion *stdisp, 
846                          const WMPlexSTDispInfo *di)
847 {
848     WFitParams fp;
849     uint szplcy;
850     WRegion *b=(ws->bottom==NULL ? NULL : ws->bottom->reg);
851     
852     /* Check if 'bottom' wants to manage the stdisp. */
853     if(b!=NULL 
854        && !OBJ_IS_BEING_DESTROYED(b)
855        && HAS_DYN(b, region_manage_stdisp)){
856         region_manage_stdisp(b, stdisp, di);
857         if(REGION_MANAGER(stdisp)==b)
858             return;
859     }
860     
861     /* No. */
862     
863     szplcy=stdisp_szplcy(di, stdisp)|SIZEPOLICY_SHRUNK;
864     
865     if(ws->managed_stdisp!=NULL && ws->managed_stdisp->reg==stdisp){
866         if(ws->managed_stdisp->szplcy==szplcy)
867             return;
868         ws->managed_stdisp->szplcy=szplcy;
869     }else{
870         region_detach_manager(stdisp);
871         ws->managed_stdisp=group_do_add_managed(ws, stdisp, 
872                                                 STACKING_LEVEL_ON_TOP, 
873                                                 szplcy);
874     }
875
876     fp.g=REGION_GEOM(ws);
877     sizepolicy(&ws->managed_stdisp->szplcy, stdisp, NULL, 0, &fp);
878
879     region_fitrep(stdisp, NULL, &fp);
880 }
881
882
883 void group_managed_rqgeom(WGroup *ws, WRegion *reg,
884                           const WRQGeomParams *rq,
885                           WRectangle *geomret)
886 {
887     WFitParams fp;
888     WStacking *st;
889         
890     st=group_find_stacking(ws, reg);
891
892     if(st==NULL){
893         fp.g=rq->geom;
894         fp.mode=REGION_FIT_EXACT;
895     }else{
896         fp.g=REGION_GEOM(ws);
897         sizepolicy(&st->szplcy, reg, &rq->geom, rq->flags, &fp);
898     }
899     
900     if(geomret!=NULL)
901         *geomret=fp.g;
902     
903     if(!(rq->flags&REGION_RQGEOM_TRYONLY))
904         region_fitrep(reg, NULL, &fp);
905 }
906
907
908 void group_managed_rqgeom_absolute(WGroup *grp, WRegion *sub, 
909                                    const WRQGeomParams *rq,
910                                    WRectangle *geomret)
911 {
912     if(grp->bottom!=NULL && grp->bottom->reg==sub){
913         region_rqgeom((WRegion*)grp, rq, geomret);
914         if(!(rq->flags&REGION_RQGEOM_TRYONLY) && geomret!=NULL)
915             *geomret=REGION_GEOM(sub);
916     }else{
917         WRQGeomParams rq2=*rq;
918         rq2.flags&=~REGION_RQGEOM_ABSOLUTE;
919
920         region_managed_rqgeom((WRegion*)grp, sub, &rq2, geomret);
921     }
922 }
923
924
925 /*}}}*/
926
927
928 /*{{{ Navigation */
929
930
931 static WStacking *nxt(WGroup *ws, WStacking *st, bool wrap)
932 {
933     return (st->mgr_next!=NULL 
934             ? st->mgr_next 
935             : (wrap ? ws->managed_list : NULL));
936 }
937
938
939 static WStacking *prv(WGroup *ws, WStacking *st, bool wrap)
940 {
941     return (st!=ws->managed_list 
942             ? st->mgr_prev
943             : (wrap ? st->mgr_prev : NULL));
944 }
945
946
947 typedef WStacking *NxtFn(WGroup *ws, WStacking *st, bool wrap);
948
949
950 static bool mapped_filt(WStacking *st, void *unused)
951 {
952     return (st->reg!=NULL && REGION_IS_MAPPED(st->reg));
953 }
954
955
956 static bool focusable(WGroup *ws, WStacking *st, uint min_level)
957 {
958     return (st->reg!=NULL
959             && REGION_IS_MAPPED(st->reg)
960             && !(st->reg->flags&REGION_SKIP_FOCUS)
961             && st->level>=min_level);
962 }
963
964        
965 static WStacking *do_get_next(WGroup *ws, WStacking *sti,
966                               NxtFn *fn, bool wrap, bool sti_ok)
967 {
968     WStacking *st, *stacking;
969     uint min_level=0;
970     
971     stacking=group_get_stacking(ws);
972     
973     if(stacking!=NULL)
974         min_level=stacking_min_level(stacking, mapped_filt, NULL);
975
976     st=sti;
977     while(1){
978         st=fn(ws, st, wrap); 
979         
980         if(st==NULL || st==sti)
981             break;
982         
983         if(focusable(ws, st, min_level))
984             return st;
985     }
986
987     if(sti_ok && focusable(ws, sti, min_level))
988         return st;
989     
990     return NULL;
991 }
992
993
994 static WStacking *group_do_navi_first(WGroup *ws, WRegionNavi nh)
995 {
996     WStacking *lst=ws->managed_list;
997     
998     if(lst==NULL)
999         return NULL;
1000
1001     if(nh==REGION_NAVI_ANY && 
1002        ws->current_managed!=NULL &&
1003        ws->current_managed->reg!=NULL){
1004         return ws->current_managed;
1005     }
1006     
1007     if(nh==REGION_NAVI_ANY || nh==REGION_NAVI_END || 
1008        nh==REGION_NAVI_BOTTOM || nh==REGION_NAVI_RIGHT){
1009         return do_get_next(ws, lst, prv, TRUE, TRUE);
1010     }else{
1011         return do_get_next(ws, lst->mgr_prev, nxt, TRUE, TRUE);
1012     }
1013 }
1014
1015
1016 static WRegion *group_navi_first(WGroup *ws, WRegionNavi nh,
1017                                  WRegionNaviData *data)
1018 {
1019     WStacking *st=group_do_navi_first(ws, nh);
1020     
1021     return region_navi_cont(&ws->reg, (st!=NULL ? st->reg : NULL), data);
1022 }
1023
1024
1025 static WStacking *group_do_navi_next(WGroup *ws, WStacking *st, 
1026                                      WRegionNavi nh, bool wrap)
1027 {
1028     if(st==NULL)
1029         return group_do_navi_first(ws, nh);
1030     
1031     if(nh==REGION_NAVI_ANY || nh==REGION_NAVI_END || 
1032        nh==REGION_NAVI_BOTTOM || nh==REGION_NAVI_RIGHT){
1033         return do_get_next(ws, st, nxt, wrap, FALSE);
1034     }else{
1035         return do_get_next(ws, st, prv, wrap, FALSE);
1036     }
1037 }
1038
1039 static WRegion *group_navi_next(WGroup *ws, WRegion *reg, 
1040                                 WRegionNavi nh, WRegionNaviData *data)
1041 {
1042     WStacking *st=group_find_stacking(ws, reg);
1043     
1044     st=group_do_navi_next(ws, st, nh, FALSE);
1045     
1046     return region_navi_cont(&ws->reg, (st!=NULL ? st->reg : NULL), data);
1047 }
1048
1049
1050 /*}}}*/
1051
1052
1053 /*{{{ Stacking */
1054
1055
1056 /* 
1057  * Note: Managed objects are considered to be stacked separately from the
1058  * group, slightly violating expectations.
1059  */
1060
1061 void group_stacking(WGroup *ws, Window *bottomret, Window *topret)
1062 {
1063     Window win=region_xwindow((WRegion*)ws);
1064     
1065     *bottomret=win;
1066     *topret=win;
1067 }
1068
1069
1070 void group_restack(WGroup *ws, Window other, int mode)
1071 {
1072     Window win;
1073     
1074     win=region_xwindow((WRegion*)ws);
1075     if(win!=None){
1076         xwindow_restack(win, other, mode);
1077         other=win;
1078         mode=Above;
1079     }
1080 }
1081
1082
1083 WStacking *group_find_stacking(WGroup *ws, WRegion *r)
1084 {
1085     WStacking *st;
1086     
1087     if(r==NULL || REGION_MANAGER(r)!=(WRegion*)ws)
1088         return NULL;
1089     
1090     return ioncore_find_stacking(r);
1091 }
1092
1093
1094 static WStacking *find_stacking_if_not_on_ws(WGroup *ws, Window w)
1095 {
1096     WRegion *r=xwindow_region_of(w);
1097     WStacking *st=NULL;
1098     
1099     while(r!=NULL){
1100         if(REGION_MANAGER(r)==(WRegion*)ws)
1101             break;
1102         st=group_find_stacking(ws, r);
1103         if(st!=NULL)
1104             break;
1105         r=REGION_MANAGER(r);
1106     }
1107     
1108     return st;
1109 }
1110
1111
1112 bool group_managed_rqorder(WGroup *grp, WRegion *reg, WRegionOrder order)
1113 {
1114     WStacking **stackingp=group_get_stackingp(grp);
1115     WStacking *st;
1116
1117     if(stackingp==NULL || *stackingp==NULL)
1118         return FALSE;
1119
1120     st=group_find_stacking(grp, reg);
1121     
1122     if(st==NULL)
1123         return FALSE;
1124     
1125     stacking_restack(stackingp, st, None, NULL, NULL,
1126                      (order!=REGION_ORDER_FRONT));
1127     
1128     return TRUE;
1129 }
1130
1131
1132 /*}}}*/
1133
1134
1135 /*{{{ Misc. */
1136
1137
1138 /*EXTL_DOC
1139  * Returns the 'bottom' of \var{ws}.
1140  */
1141 EXTL_EXPORT_MEMBER
1142 WRegion *group_bottom(WGroup *ws)
1143 {
1144     return (ws->bottom!=NULL ? ws->bottom->reg : NULL);
1145 }
1146
1147
1148 /*EXTL_DOC
1149  * Returns a list of regions managed by the workspace (frames, mostly).
1150  */
1151 EXTL_SAFE
1152 EXTL_EXPORT_MEMBER
1153 ExtlTab group_managed_list(WGroup *ws)
1154 {
1155     WGroupIterTmp tmp;
1156     group_iter_init(&tmp, ws);
1157     
1158     return extl_obj_iterable_to_table((ObjIterator*)group_iter, &tmp);
1159 }
1160
1161
1162 WRegion* group_current(WGroup *ws)
1163 {
1164     return (ws->current_managed!=NULL ? ws->current_managed->reg : NULL);
1165 }
1166
1167
1168 void group_size_hints(WGroup *ws, WSizeHints *hints_ret)
1169 {
1170     if(ws->bottom==NULL || ws->bottom->reg==NULL){
1171         sizehints_clear(hints_ret);
1172     }else{
1173         region_size_hints(ws->bottom->reg, hints_ret);
1174         hints_ret->no_constrain=TRUE;
1175     }
1176 }
1177
1178
1179 Window group_xwindow(const WGroup *ws)
1180 {
1181     return ws->dummywin;
1182 }
1183
1184
1185 /*}}}*/
1186
1187
1188 /*{{{ Save/load */
1189
1190
1191 static ExtlTab group_get_configuration(WGroup *ws)
1192 {
1193     ExtlTab tab, mgds, subtab, g;
1194     WStacking *st;
1195     WGroupIterTmp tmp;
1196     WMPlex *par;
1197     int n=0;
1198     WRectangle tmpg;
1199     
1200     tab=region_get_base_configuration((WRegion*)ws);
1201     
1202     mgds=extl_create_table();
1203     
1204     extl_table_sets_t(tab, "managed", mgds);
1205     
1206     /* TODO: stacking order messed up */
1207     
1208     FOR_ALL_NODES_IN_GROUP(ws, st, tmp){
1209         if(st->reg==NULL)
1210             continue;
1211         
1212         subtab=region_get_configuration(st->reg);
1213
1214         if(subtab!=extl_table_none()){
1215             extl_table_sets_i(subtab, "sizepolicy", st->szplcy);
1216             extl_table_sets_i(subtab, "level", st->level);
1217         
1218             tmpg=REGION_GEOM(st->reg);
1219             tmpg.x-=REGION_GEOM(ws).x;
1220             tmpg.y-=REGION_GEOM(ws).y;
1221             
1222             g=extl_table_from_rectangle(&tmpg);
1223             extl_table_sets_t(subtab, "geom", g);
1224             extl_unref_table(g);
1225             
1226             if(ws->bottom==st)
1227                 extl_table_sets_b(subtab, "bottom", TRUE);
1228         
1229             extl_table_seti_t(mgds, ++n, subtab);
1230             extl_unref_table(subtab);
1231         }
1232     }
1233     
1234     extl_unref_table(mgds);
1235     
1236     return tab;
1237 }
1238
1239
1240 void group_do_load(WGroup *ws, ExtlTab tab)
1241 {
1242     ExtlTab substab, subtab;
1243     int i, n;
1244     
1245     if(extl_table_gets_t(tab, "managed", &substab)){
1246         n=extl_table_get_n(substab);
1247         for(i=1; i<=n; i++){
1248             if(extl_table_geti_t(substab, i, &subtab)){
1249                 group_attach_new(ws, subtab);
1250                 extl_unref_table(subtab);
1251             }
1252         }
1253         
1254         extl_unref_table(substab);
1255     }
1256
1257     ws->bottom_last_close=extl_table_is_bool_set(tab, "bottom_last_close");
1258 }
1259
1260
1261 WRegion *group_load(WWindow *par, const WFitParams *fp, ExtlTab tab)
1262 {
1263     WGroup *ws;
1264     
1265     ws=create_group(par, fp);
1266     
1267     if(ws==NULL)
1268         return NULL;
1269         
1270     group_do_load(ws, tab);
1271     
1272     return (WRegion*)ws;
1273 }
1274
1275
1276 /*}}}*/
1277
1278
1279 /*{{{ Dynamic function table and class implementation */
1280
1281
1282 static DynFunTab group_dynfuntab[]={
1283     {(DynFun*)region_fitrep,
1284      (DynFun*)group_fitrep},
1285
1286     {region_map, 
1287      group_map},
1288     
1289     {region_unmap, 
1290      group_unmap},
1291     
1292     {(DynFun*)region_managed_prepare_focus, 
1293      (DynFun*)group_managed_prepare_focus},
1294
1295     {region_do_set_focus, 
1296      group_do_set_focus},
1297     
1298     {region_managed_activated, 
1299      group_managed_activated},
1300     
1301     {region_managed_remove,
1302      group_managed_remove},
1303     
1304     {(DynFun*)region_get_configuration, 
1305      (DynFun*)group_get_configuration},
1306
1307     {(DynFun*)region_may_destroy,
1308      (DynFun*)group_may_destroy},
1309
1310     {(DynFun*)region_managed_may_destroy,
1311      (DynFun*)group_managed_may_destroy},
1312
1313     {(DynFun*)region_current,
1314      (DynFun*)group_current},
1315     
1316     {(DynFun*)region_rescue_clientwins,
1317      (DynFun*)group_rescue_clientwins},
1318     
1319     {region_restack,
1320      group_restack},
1321
1322     {region_stacking,
1323      group_stacking},
1324
1325     {(DynFun*)region_managed_get_pholder,
1326      (DynFun*)group_managed_get_pholder},
1327
1328     {region_managed_rqgeom,
1329      group_managed_rqgeom},
1330      
1331     {region_managed_rqgeom_absolute,
1332      group_managed_rqgeom_absolute},
1333     
1334     {(DynFun*)group_do_add_managed,
1335      (DynFun*)group_do_add_managed_default},
1336     
1337     {region_size_hints,
1338      group_size_hints},
1339     
1340     {(DynFun*)region_xwindow,
1341      (DynFun*)group_xwindow},
1342     
1343     {(DynFun*)region_navi_first,
1344      (DynFun*)group_navi_first},
1345     
1346     {(DynFun*)region_navi_next,
1347      (DynFun*)group_navi_next},
1348     
1349     {(DynFun*)region_managed_rqorder,
1350      (DynFun*)group_managed_rqorder},
1351     
1352     END_DYNFUNTAB
1353 };
1354
1355
1356 EXTL_EXPORT
1357 IMPLCLASS(WGroup, WRegion, group_deinit, group_dynfuntab);
1358
1359
1360 /*}}}*/
1361