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