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