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