2 * ion/mod_tiling/tiling.c
4 * Copyright (c) Tuomo Valkonen 1999-2007.
6 * See the included file LICENSE for details.
11 #include <libtu/objp.h>
12 #include <libtu/minmax.h>
13 #include <libtu/ptrlist.h>
14 #include <libmainloop/defer.h>
15 #include <libmainloop/signal.h>
17 #include <ioncore/common.h>
18 #include <ioncore/rootwin.h>
19 #include <ioncore/focus.h>
20 #include <ioncore/global.h>
21 #include <ioncore/region.h>
22 #include <ioncore/manage.h>
23 #include <ioncore/screen.h>
24 #include <ioncore/names.h>
25 #include <ioncore/saveload.h>
26 #include <ioncore/attach.h>
27 #include <ioncore/resize.h>
28 #include <libextl/extl.h>
29 #include <ioncore/regbind.h>
30 #include <ioncore/extlconv.h>
31 #include <ioncore/xwindow.h>
32 #include <ioncore/navi.h>
33 #include "placement.h"
36 #include "splitfloat.h"
37 #include "split-stdisp.h"
42 static WTilingIterTmp tiling_iter_default_tmp;
45 /*{{{ Some helper routines */
48 static WSplitRegion *get_node_check(WTiling *ws, WRegion *reg)
55 node=splittree_node_of(reg);
57 if(node==NULL || REGION_MANAGER(reg)!=(WRegion*)ws)
64 static bool check_node(WTiling *ws, WSplit *split)
67 return check_node(ws, (WSplit*)split->parent);
69 if((split->ws_if_root!=(void*)ws)){
70 warn(TR("Split not on workspace."));
80 /*{{{ Dynfun implementations */
83 static void reparent_mgd(WRegion *sub, WWindow *par)
86 subfp.g=REGION_GEOM(sub);
87 subfp.mode=REGION_FIT_EXACT;
88 if(!region_fitrep(sub, par, &subfp)){
89 warn(TR("Error reparenting %s."), region_name(sub));
90 region_detach_manager(sub);
95 bool tiling_fitrep(WTiling *ws, WWindow *par, const WFitParams *fp)
101 if(!region_same_rootwin((WRegion*)ws, (WRegion*)par))
104 region_unset_parent((WRegion*)ws);
106 XReparentWindow(ioncore_g.dpy, ws->dummywin,
107 par->win, fp->g.x, fp->g.y);
109 region_set_parent((WRegion*)ws, par);
111 if(ws->split_tree!=NULL)
112 split_reparent(ws->split_tree, par);
115 REGION_GEOM(ws)=fp->g;
117 if(ws->split_tree!=NULL){
119 if(fp->mode®ION_FIT_ROTATE)
120 ok=split_rotate_to(ws->split_tree, &(fp->g), fp->rotation);
122 split_resize(ws->split_tree, &(fp->g), PRIMN_ANY, PRIMN_ANY);
129 void tiling_managed_rqgeom(WTiling *ws, WRegion *mgd,
130 const WRQGeomParams *rq,
133 WSplitRegion *node=get_node_check(ws, mgd);
134 if(node!=NULL && ws->split_tree!=NULL)
135 splittree_rqgeom((WSplit*)node, rq->flags, &rq->geom, geomret);
139 void tiling_map(WTiling *ws)
141 REGION_MARK_MAPPED(ws);
142 XMapWindow(ioncore_g.dpy, ws->dummywin);
144 if(ws->split_tree!=NULL)
145 split_map(ws->split_tree);
149 void tiling_unmap(WTiling *ws)
151 REGION_MARK_UNMAPPED(ws);
152 XUnmapWindow(ioncore_g.dpy, ws->dummywin);
154 if(ws->split_tree!=NULL)
155 split_unmap(ws->split_tree);
159 void tiling_fallback_focus(WTiling *ws, bool warp)
161 region_finalise_focusing((WRegion*)ws, ws->dummywin, warp);
165 void tiling_do_set_focus(WTiling *ws, bool warp)
167 WRegion *sub=tiling_current(ws);
170 tiling_fallback_focus(ws, warp);
174 region_do_set_focus(sub, warp);
178 static WTimer *restack_timer=NULL;
181 static void restack_handler(WTimer *tmr, Obj *obj)
184 WTiling *ws=(WTiling*)obj;
185 split_restack(ws->split_tree, ws->dummywin, Above);
190 bool tiling_managed_prepare_focus(WTiling *ws, WRegion *reg,
191 int flags, WPrepareFocusResult *res)
195 if(!region_prepare_focus((WRegion*)ws, flags, res))
198 node=get_node_check(ws, reg);
200 if(node!=NULL && node->split.parent!=NULL)
201 splitinner_mark_current(node->split.parent, &(node->split));
203 /* WSplitSplit uses activity based stacking as required on WAutoWS,
204 * so we must restack here.
206 if(ws->split_tree!=NULL){
207 int rd=mod_tiling_raise_delay;
208 bool use_timer=rd>0 && flags®ION_GOTO_ENTERWINDOW;
211 if(restack_timer!=NULL){
212 Obj *obj=restack_timer->objwatch.obj;
214 timer_reset(restack_timer);
215 restack_handler(restack_timer, obj);
218 restack_timer=create_timer();
222 if(use_timer && restack_timer!=NULL){
223 timer_set(restack_timer, rd, restack_handler, (Obj*)ws);
225 split_restack(ws->split_tree, ws->dummywin, Above);
236 void tiling_restack(WTiling *ws, Window other, int mode)
238 xwindow_restack(ws->dummywin, other, mode);
239 if(ws->split_tree!=NULL)
240 split_restack(ws->split_tree, ws->dummywin, Above);
244 void tiling_stacking(WTiling *ws, Window *bottomret, Window *topret)
246 Window sbottom=None, stop=None;
248 if(ws->split_tree!=None)
249 split_stacking(ws->split_tree, &sbottom, &stop);
251 *bottomret=ws->dummywin;
252 *topret=(stop!=None ? stop : ws->dummywin);
256 Window tiling_xwindow(const WTiling *ws)
265 /*{{{ Status display support code */
268 static bool regnodefilter(WSplit *split)
270 return OBJ_IS(split, WSplitRegion);
274 void tiling_unmanage_stdisp(WTiling *ws, bool permanent, bool nofocus)
276 WSplitRegion *tofocus=NULL;
280 if(ws->stdispnode==NULL)
283 od=ws->stdispnode->regnode.reg;
286 if(!nofocus && REGION_IS_ACTIVE(od) &&
287 region_may_control_focus((WRegion*)ws)){
289 tofocus=(WSplitRegion*)split_nextto((WSplit*)(ws->stdispnode),
290 PRIMN_ANY, PRIMN_ANY,
293 /* Reset node_of info here so tiling_managed_remove will not
296 splittree_set_node_of(od, NULL);
297 tiling_do_managed_remove(ws, od);
301 WSplit *node=(WSplit*)ws->stdispnode;
303 splittree_remove(node, TRUE);
308 region_set_focus(tofocus->reg);
310 tiling_fallback_focus(ws, FALSE);
315 static void tiling_create_stdispnode(WTiling *ws, WRegion *stdisp,
316 int corner, int orientation,
319 int flags=REGION_RQGEOM_WEAK_X|REGION_RQGEOM_WEAK_Y;
320 WRectangle *wg=®ION_GEOM(ws), dg;
321 WSplitST *stdispnode;
324 assert(ws->split_tree!=NULL);
326 if(orientation==REGION_ORIENTATION_HORIZONTAL){
330 dg.y=((corner==MPLEX_STDISP_BL || corner==MPLEX_STDISP_BR)
337 dg.x=((corner==MPLEX_STDISP_TR || corner==MPLEX_STDISP_BR)
342 stdispnode=create_splitst(&dg, stdisp);
344 if(stdispnode==NULL){
345 warn(TR("Unable to create a node for status display."));
349 stdispnode->corner=corner;
350 stdispnode->orientation=orientation;
351 stdispnode->fullsize=fullsize;
353 split=create_splitsplit(wg, (orientation==REGION_ORIENTATION_HORIZONTAL
355 : SPLIT_HORIZONTAL));
358 warn(TR("Unable to create new split for status display."));
359 stdispnode->regnode.reg=NULL;
360 destroy_obj((Obj*)stdispnode);
364 /* Set up new split tree */
365 ((WSplit*)stdispnode)->parent=(WSplitInner*)split;
366 ws->split_tree->parent=(WSplitInner*)split;
367 ws->split_tree->ws_if_root=NULL;
369 if((orientation==REGION_ORIENTATION_HORIZONTAL &&
370 (corner==MPLEX_STDISP_BL || corner==MPLEX_STDISP_BR)) ||
371 (orientation==REGION_ORIENTATION_VERTICAL &&
372 (corner==MPLEX_STDISP_TR || corner==MPLEX_STDISP_BR))){
373 split->tl=ws->split_tree;
374 split->br=(WSplit*)stdispnode;
375 split->current=SPLIT_CURRENT_TL;
377 split->tl=(WSplit*)stdispnode;
378 split->br=ws->split_tree;
379 split->current=SPLIT_CURRENT_BR;
382 ws->split_tree=(WSplit*)split;
383 ((WSplit*)split)->ws_if_root=ws;
384 ws->stdispnode=stdispnode;
388 void tiling_manage_stdisp(WTiling *ws, WRegion *stdisp,
389 const WMPlexSTDispInfo *di)
391 bool mcf=region_may_control_focus((WRegion*)ws);
392 int flags=REGION_RQGEOM_WEAK_X|REGION_RQGEOM_WEAK_Y;
393 int orientation=region_orientation(stdisp);
395 WRectangle dg, *stdg;
397 if(orientation!=REGION_ORIENTATION_VERTICAL /*&&
398 orientation!=REGION_ORIENTATION_HORIZONTAL*/){
399 orientation=REGION_ORIENTATION_HORIZONTAL;
402 if(ws->stdispnode==NULL || ws->stdispnode->regnode.reg!=stdisp)
403 region_detach_manager(stdisp);
405 /* Remove old stdisp if corner and orientation don't match.
407 if(ws->stdispnode!=NULL && (di->pos!=ws->stdispnode->corner ||
408 orientation!=ws->stdispnode->orientation)){
409 tiling_unmanage_stdisp(ws, TRUE, TRUE);
412 if(ws->stdispnode==NULL){
413 tiling_create_stdispnode(ws, stdisp, di->pos, orientation,
415 if(ws->stdispnode==NULL)
418 WRegion *od=ws->stdispnode->regnode.reg;
420 act=REGION_IS_ACTIVE(od);
421 splittree_set_node_of(od, NULL);
422 tiling_managed_remove(ws, od);
423 assert(ws->stdispnode->regnode.reg==NULL);
426 ws->stdispnode->fullsize=di->fullsize;
427 ws->stdispnode->regnode.reg=stdisp;
428 splittree_set_node_of(stdisp, &(ws->stdispnode->regnode));
431 if(!tiling_managed_add(ws, stdisp)){
432 tiling_unmanage_stdisp(ws, TRUE, TRUE);
436 dg=((WSplit*)(ws->stdispnode))->geom;
438 dg.h=stdisp_recommended_h(ws->stdispnode);
439 dg.w=stdisp_recommended_w(ws->stdispnode);
441 splittree_rqgeom((WSplit*)(ws->stdispnode), flags, &dg, FALSE);
443 stdg=&(((WSplit*)ws->stdispnode)->geom);
445 if(stdisp->geom.x!=stdg->x || stdisp->geom.y!=stdg->y ||
446 stdisp->geom.w!=stdg->w || stdisp->geom.h!=stdg->h){
447 region_fit(stdisp, stdg, REGION_FIT_EXACT);
450 /* Restack to ensure the split tree is stacked in the expected order. */
451 if(ws->split_tree!=NULL)
452 split_restack(ws->split_tree, ws->dummywin, Above);
455 region_set_focus(stdisp);
462 /*{{{ Create/destroy */
465 bool tiling_managed_add_default(WTiling *ws, WRegion *reg)
467 Window bottom=None, top=None;
470 if(TILING_STDISP_OF(ws)!=reg){
471 if(!ptrlist_insert_last(&(ws->managed_list), reg))
475 region_set_manager(reg, (WRegion*)ws);
477 frame=OBJ_CAST(reg, WFrame);
479 WFrameMode mode=frame_mode(frame);
480 if(mode!=FRAME_MODE_TILED && mode!=FRAME_MODE_TILED_ALT)
481 frame_set_mode(frame, FRAME_MODE_TILED);
484 if(REGION_IS_MAPPED(ws))
487 if(region_may_control_focus((WRegion*)ws)){
488 WRegion *curr=tiling_current(ws);
489 if(curr==NULL || !REGION_IS_ACTIVE(curr))
497 bool tiling_managed_add(WTiling *ws, WRegion *reg)
500 CALL_DYN_RET(ret, bool, tiling_managed_add, ws, (ws, reg));
505 bool tiling_do_attach_initial(WTiling *ws, WRegion *reg)
507 assert(ws->split_tree==NULL);
509 ws->split_tree=(WSplit*)create_splitregion(®ION_GEOM(reg), reg);
510 if(ws->split_tree==NULL)
513 ws->split_tree->ws_if_root=ws;
515 if(!tiling_managed_add(ws, reg)){
516 destroy_obj((Obj*)ws->split_tree);
525 static WRegion *create_frame_tiling(WWindow *parent, const WFitParams *fp)
527 return (WRegion*)create_frame(parent, fp, FRAME_MODE_TILED);
531 bool tiling_init(WTiling *ws, WWindow *parent, const WFitParams *fp,
532 WRegionSimpleCreateFn *create_frame_fn, bool ci)
535 ws->create_frame_fn=(create_frame_fn
537 : create_frame_tiling);
539 ws->managed_list=NULL;
542 ws->dummywin=XCreateWindow(ioncore_g.dpy, parent->win,
543 fp->g.x, fp->g.y, 1, 1, 0,
544 CopyFromParent, InputOnly,
545 CopyFromParent, 0, NULL);
546 if(ws->dummywin==None)
549 region_init(&(ws->reg), parent, fp);
551 ws->reg.flags|=(REGION_GRAB_ON_PARENT|
555 WRegionAttachData data;
558 data.type=REGION_ATTACH_NEW;
559 data.u.n.fn=(WRegionCreateFn*)ws->create_frame_fn;
562 res=region_attach_helper((WRegion*)ws, parent, fp,
563 (WRegionDoAttachFn*)tiling_do_attach_initial,
567 XDestroyWindow(ioncore_g.dpy, ws->dummywin);
572 XSelectInput(ioncore_g.dpy, ws->dummywin,
573 FocusChangeMask|KeyPressMask|KeyReleaseMask|
574 ButtonPressMask|ButtonReleaseMask);
575 XSaveContext(ioncore_g.dpy, ws->dummywin, ioncore_g.win_context,
578 region_register(&(ws->reg));
579 region_add_bindmap((WRegion*)ws, mod_tiling_tiling_bindmap);
585 WTiling *create_tiling(WWindow *parent, const WFitParams *fp,
586 WRegionSimpleCreateFn *create_frame_fn, bool ci)
588 CREATEOBJ_IMPL(WTiling, tiling, (p, parent, fp, create_frame_fn, ci));
592 WTiling *create_tiling_simple(WWindow *parent, const WFitParams *fp)
594 return create_tiling(parent, fp, NULL, TRUE);
598 void tiling_deinit(WTiling *ws)
602 WMPlex *remanage_mplex=NULL;
604 tiling_unmanage_stdisp(ws, FALSE, TRUE);
606 FOR_ALL_MANAGED_BY_TILING(reg, ws, tmp){
607 destroy_obj((Obj*)reg);
610 FOR_ALL_MANAGED_BY_TILING(reg, ws, tmp){
614 if(ws->split_tree!=NULL)
615 destroy_obj((Obj*)(ws->split_tree));
617 XDeleteContext(ioncore_g.dpy, ws->dummywin, ioncore_g.win_context);
618 XDestroyWindow(ioncore_g.dpy, ws->dummywin);
621 region_deinit(&(ws->reg));
625 WRegion *tiling_managed_disposeroot(WTiling *ws, WRegion *reg)
633 FOR_ALL_MANAGED_BY_TILING(mgd, ws, tmp){
634 if(mgd!=TILING_STDISP_OF(ws) && mgd!=reg)
638 return region_disposeroot((WRegion*)ws);
642 bool tiling_rescue_clientwins(WTiling *ws, WRescueInfo *info)
646 ptrlist_iter_init(&tmp, ws->managed_list);
648 return region_rescue_some_clientwins((WRegion*)ws, info,
649 (WRegionIterator*)ptrlist_iter,
654 void tiling_do_managed_remove(WTiling *ws, WRegion *reg)
656 if(TILING_STDISP_OF(ws)==reg){
657 ws->stdispnode->regnode.reg=NULL;
659 ptrlist_remove(&(ws->managed_list), reg);
662 region_unset_manager(reg, (WRegion*)ws);
666 static bool nostdispfilter(WSplit *node)
668 return (OBJ_IS(node, WSplitRegion) && !OBJ_IS(node, WSplitST));
672 void tiling_managed_remove(WTiling *ws, WRegion *reg)
674 bool act=REGION_IS_ACTIVE(reg);
675 bool mcf=region_may_control_focus((WRegion*)ws);
676 WSplitRegion *node=get_node_check(ws, reg);
677 bool norestore=(OBJ_IS_BEING_DESTROYED(ws) || ws->batchop);
681 other=tiling_do_navi_next(ws, reg, REGION_NAVI_ANY, TRUE, FALSE);
683 tiling_do_managed_remove(ws, reg);
685 if(node==(WSplitRegion*)(ws->stdispnode))
691 if(other==NULL && !norestore){
692 WWindow *par=REGION_PARENT(ws);
697 fp.g=node->split.geom;
698 fp.mode=REGION_FIT_EXACT;
700 other=(ws->create_frame_fn)(par, &fp);
704 tiling_managed_add(ws, other);
707 warn(TR("Tiling in useless state."));
712 splittree_remove((WSplit*)node, (!norestore && other!=NULL));
715 if(!norestore && other!=NULL && act && mcf)
720 static bool mplexfilter(WSplit *node)
722 WSplitRegion *regnode=OBJ_CAST(node, WSplitRegion);
724 return (regnode!=NULL && regnode->reg!=NULL &&
725 OBJ_IS(regnode->reg, WMPlex));
729 static WPHolder *find_ph_result=NULL;
730 static WRegion *find_ph_param=NULL;
733 static bool find_ph(WSplit *split)
735 WSplitRegion *sr=OBJ_CAST(split, WSplitRegion);
737 assert(find_ph_result==NULL);
739 if(sr==NULL || sr->reg==NULL)
742 find_ph_result=region_get_rescue_pholder_for(sr->reg, find_ph_param);
744 return (find_ph_result!=NULL);
748 WPHolder *tiling_get_rescue_pholder_for(WTiling *ws, WRegion *mgd)
750 WSplit *node=(WSplit*)get_node_check(ws, mgd);
757 if(ws->split_tree!=NULL){
758 split_current_todir(ws->split_tree, PRIMN_ANY, PRIMN_ANY,
763 split_nextto(node, PRIMN_ANY, PRIMN_ANY, find_ph);
764 if(find_ph_result!=NULL)
766 node=(WSplit*)node->parent;
784 static void navi_to_primn(WRegionNavi nh, WPrimn *hprimn, WPrimn *vprimn,
787 /* choice should be PRIMN_ANY or PRIMN_NONE */
790 case REGION_NAVI_BEG:
795 case REGION_NAVI_END:
800 case REGION_NAVI_LEFT:
805 case REGION_NAVI_RIGHT:
810 case REGION_NAVI_TOP:
815 case REGION_NAVI_BOTTOM:
821 case REGION_NAVI_ANY:
829 static WRegion *node_reg(WSplit *node)
831 WSplitRegion *rnode=OBJ_CAST(node, WSplitRegion);
832 return (rnode!=NULL ? rnode->reg : NULL);
836 WRegion *tiling_do_navi_next(WTiling *ws, WRegion *reg,
837 WRegionNavi nh, bool nowrap,
840 WSplitFilter *filter=(any ? NULL : nostdispfilter);
841 WPrimn hprimn, vprimn;
844 navi_to_primn(nh, &hprimn, &vprimn, PRIMN_NONE);
847 reg=tiling_current(ws);
850 WSplitRegion *node=get_node_check(ws, reg);
852 nxt=node_reg(split_nextto((WSplit*)node, hprimn, vprimn,
857 if(nxt==NULL && !nowrap){
858 nxt=node_reg(split_current_todir(ws->split_tree,
859 primn_none2any(primn_invert(hprimn)),
860 primn_none2any(primn_invert(vprimn)),
868 WRegion *tiling_do_navi_first(WTiling *ws, WRegionNavi nh, bool any)
870 WSplitFilter *filter=(any ? NULL : nostdispfilter);
871 WPrimn hprimn, vprimn;
873 navi_to_primn(nh, &hprimn, &vprimn, PRIMN_ANY);
875 return node_reg(split_current_todir(ws->split_tree,
876 hprimn, vprimn, filter));
880 WRegion *tiling_navi_next(WTiling *ws, WRegion *reg,
881 WRegionNavi nh, WRegionNaviData *data)
883 WRegion *nxt=tiling_do_navi_next(ws, reg, nh, TRUE, FALSE);
885 return region_navi_cont(&ws->reg, nxt, data);
889 WRegion *tiling_navi_first(WTiling *ws, WRegionNavi nh,
890 WRegionNaviData *data)
892 WRegion *reg=tiling_do_navi_first(ws, nh, FALSE);
894 return region_navi_cont(&ws->reg, reg, data);
901 /*{{{ Split/unsplit */
904 static bool get_split_dir_primn(const char *str, int *dir, int *primn)
906 WPrimn hprimn, vprimn;
909 if(!ioncore_string_to_navi(str, &nh))
912 navi_to_primn(nh, &hprimn, &vprimn, PRIMN_NONE);
914 if(hprimn==PRIMN_NONE){
917 }else if(vprimn==PRIMN_NONE){
918 *dir=SPLIT_HORIZONTAL;
921 warn(TR("Invalid direction"));
929 static bool get_split_dir_primn_float(const char *str, int *dir, int *primn,
932 if(strncmp(str, "floating:", 9)==0){
934 return get_split_dir_primn(str+9, dir, primn);
937 return get_split_dir_primn(str, dir, primn);
942 #define SPLIT_MINS 16 /* totally arbitrary */
945 static WFrame *tiling_do_split(WTiling *ws, WSplit *node,
946 const char *dirstr, int minw, int minh)
948 int dir, primn, mins;
953 if(node==NULL || ws->split_tree==NULL){
954 warn(TR("Invalid node."));
958 if(!get_split_dir_primn_float(dirstr, &dir, &primn, &floating))
961 mins=(dir==SPLIT_VERTICAL ? minh : minw);
964 nnode=splittree_split(node, dir, primn, mins,
968 nnode=splittree_split_floating(node, dir, primn, mins,
969 ws->create_frame_fn, ws);
973 warn(TR("Unable to split."));
977 /* We must restack here to ensure the split tree is stacked in the
980 if(ws->split_tree!=NULL)
981 split_restack(ws->split_tree, ws->dummywin, Above);
983 newframe=OBJ_CAST(nnode->reg, WFrame);
984 assert(newframe!=NULL);
986 if(!tiling_managed_add(ws, nnode->reg)){
988 destroy_obj((Obj*)nnode);
989 destroy_obj((Obj*)newframe);
994 if(ws->split_tree!=NULL)
995 split_restack(ws->split_tree, ws->dummywin, Above);
1002 * Create a new frame on \var{ws} \codestr{above}, \codestr{below}
1003 * \codestr{left} of, or \codestr{right} of \var{node} as indicated
1004 * by \var{dirstr}. If \var{dirstr} is prefixed with
1005 * \codestr{floating:} a floating split is created.
1008 WFrame *tiling_split(WTiling *ws, WSplit *node, const char *dirstr)
1010 if(!check_node(ws, node))
1013 return tiling_do_split(ws, node, dirstr,
1014 SPLIT_MINS, SPLIT_MINS);
1019 * Same as \fnref{WTiling.split} at the root of the split tree.
1022 WFrame *tiling_split_top(WTiling *ws, const char *dirstr)
1024 return tiling_do_split(ws, ws->split_tree, dirstr,
1025 SPLIT_MINS, SPLIT_MINS);
1030 * Split \var{frame} creating a new frame to direction \var{dirstr}
1031 * (one of \codestr{left}, \codestr{right}, \codestr{top} or
1032 * \codestr{bottom}) of \var{frame}.
1033 * If \var{attach_current} is set, the region currently displayed in
1034 * \var{frame}, if any, is moved to thenew frame.
1035 * If \var{dirstr} is prefixed with \codestr{floating:}, a floating
1039 WFrame *tiling_split_at(WTiling *ws, WFrame *frame, const char *dirstr,
1040 bool attach_current)
1049 node=get_node_check(ws, (WRegion*)frame);
1051 newframe=tiling_do_split(ws, (WSplit*)node, dirstr,
1052 region_min_w((WRegion*)frame),
1053 region_min_h((WRegion*)frame));
1058 curr=mplex_mx_current(&(frame->mplex));
1060 if(attach_current && curr!=NULL)
1061 mplex_attach_simple(&(newframe->mplex), curr, MPLEX_ATTACH_SWITCHTO);
1063 if(region_may_control_focus((WRegion*)frame))
1064 region_goto((WRegion*)newframe);
1070 void do_unsplit(WRegion *reg)
1072 WTiling *ws=REGION_MANAGER_CHK(reg, WTiling);
1079 ph=region_get_rescue_pholder_for((WRegion*)ws, reg);
1082 res=!region_rescue_needed(reg);
1084 res=region_rescue(reg, ph);
1085 destroy_obj((Obj*)ph);
1089 warn(TR("Unable to unsplit: Could not move client windows "
1090 "elsewhere within the tiling."));
1094 destroy_obj((Obj*)reg);
1099 * Try to relocate regions managed by \var{reg} to another frame
1100 * and, if possible, destroy it.
1103 void tiling_unsplit_at(WTiling *ws, WRegion *reg)
1105 if(reg==NULL || REGION_MANAGER(reg)!=(WRegion*)ws)
1108 mainloop_defer_action((Obj*)reg, (WDeferredAction*)do_unsplit);
1115 /*{{{ Navigation etc. exports */
1118 WRegion *tiling_current(WTiling *ws)
1120 WSplitRegion *node=NULL;
1121 if(ws->split_tree!=NULL){
1122 node=(WSplitRegion*)split_current_todir(ws->split_tree,
1123 PRIMN_ANY, PRIMN_ANY, NULL);
1125 return (node ? node->reg : NULL);
1130 * Iterate over managed regions of \var{ws} until \var{iterfn} returns
1132 * The function itself returns \code{true} if it reaches the end of list
1133 * without this happening.
1137 bool tiling_managed_i(WTiling *ws, ExtlFn iterfn)
1141 ptrlist_iter_init(&tmp, ws->managed_list);
1143 return extl_iter_objlist_(iterfn, (ObjIterator*)ptrlist_iter, &tmp);
1148 * Returns the root of the split tree.
1152 WSplit *tiling_split_tree(WTiling *ws)
1154 return ws->split_tree;
1159 * Return the most previously active region next to \var{reg} in
1160 * direction \var{dirstr} (\codestr{left}, \codestr{right}, \codestr{up},
1161 * or \codestr{down}). The region \var{reg}
1162 * must be managed by \var{ws}. If \var{any} is not set, the status display
1163 * is not considered.
1167 WRegion *tiling_nextto(WTiling *ws, WRegion *reg, const char *dirstr,
1172 if(!ioncore_string_to_navi(dirstr, &nh))
1175 return tiling_do_navi_next(ws, reg, nh, FALSE, any);
1180 * Return the most previously active region on \var{ws} with no
1181 * other regions next to it in direction \var{dirstr}
1182 * (\codestr{left}, \codestr{right}, \codestr{up}, or \codestr{down}).
1183 * If \var{any} is not set, the status display is not considered.
1187 WRegion *tiling_farthest(WTiling *ws, const char *dirstr, bool any)
1191 if(!ioncore_string_to_navi(dirstr, &nh))
1194 return tiling_do_navi_first(ws, nh, any);
1199 * For region \var{reg} managed by \var{ws} return the \type{WSplit}
1200 * a leaf of which \var{reg} is.
1204 WSplitRegion *tiling_node_of(WTiling *ws, WRegion *reg)
1207 warn(TR("Nil parameter."));
1211 if(REGION_MANAGER(reg)!=(WRegion*)ws){
1212 warn(TR("Manager doesn't match."));
1216 return splittree_node_of(reg);
1223 /*{{{ Flip and transpose */
1226 static WSplitSplit *get_at_split(WTiling *ws, WRegion *reg)
1232 split=OBJ_CAST(ws->split_tree, WSplitSplit);
1235 else if(split->br==(WSplit*)ws->stdispnode)
1236 return OBJ_CAST(split->tl, WSplitSplit);
1237 else if(split->tl==(WSplit*)ws->stdispnode)
1238 return OBJ_CAST(split->br, WSplitSplit);
1243 node=(WSplit*)get_node_check(ws, reg);
1248 if(node==(WSplit*)ws->stdispnode){
1249 warn(TR("The status display is not a valid parameter for "
1254 split=OBJ_CAST(node->parent, WSplitSplit);
1256 if(split!=NULL && (split->tl==(WSplit*)ws->stdispnode ||
1257 split->br==(WSplit*)ws->stdispnode)){
1258 split=OBJ_CAST(((WSplit*)split)->parent, WSplitSplit);
1266 * Flip \var{ws} at \var{reg} or root if nil.
1269 bool iowns_flip_at(WTiling *ws, WRegion *reg)
1271 WSplitSplit *split=get_at_split(ws, reg);
1276 splitsplit_flip(split);
1283 * Transpose \var{ws} at \var{reg} or root if nil.
1286 bool iowns_transpose_at(WTiling *ws, WRegion *reg)
1288 WSplitSplit *split=get_at_split(ws, reg);
1293 split_transpose((WSplit*)split);
1302 /*{{{ Floating toggle */
1305 static void replace(WSplitSplit *split, WSplitSplit *nsplit)
1307 WSplitInner *psplit=split->isplit.split.parent;
1309 nsplit->tl=split->tl;
1311 nsplit->tl->parent=(WSplitInner*)nsplit;
1313 nsplit->br=split->br;
1315 nsplit->br->parent=(WSplitInner*)nsplit;
1318 splitinner_replace((WSplitInner*)psplit, (WSplit*)split,
1321 splittree_changeroot((WSplit*)split, (WSplit*)nsplit);
1326 WSplitSplit *tiling_set_floating(WTiling *ws, WSplitSplit *split, int sp)
1328 bool set=OBJ_IS(split, WSplitFloat);
1329 bool nset=libtu_do_setparam(sp, set);
1330 const WRectangle *g=&((WSplit*)split)->geom;
1337 ns=(WSplitSplit*)create_splitfloat(g, ws, split->dir);
1339 if(OBJ_IS(split->tl, WSplitST) || OBJ_IS(split->br, WSplitST)){
1340 warn(TR("Refusing to float split directly containing the "
1341 "status display."));
1344 ns=create_splitsplit(g, split->dir);
1349 split_resize((WSplit*)ns, g, PRIMN_ANY, PRIMN_ANY);
1350 mainloop_defer_destroy((Obj*)split);
1358 * Toggle floating of a split's sides at \var{split} as indicated by the
1359 * parameter \var{how} (\codestr{set}, \codestr{unset}, or \codestr{toggle}).
1360 * A split of the appropriate is returned, if there was a change.
1362 EXTL_EXPORT_AS(WTiling, set_floating)
1363 WSplitSplit *tiling_set_floating_extl(WTiling *ws, WSplitSplit *split,
1366 if(!check_node(ws, (WSplit*)split))
1368 return tiling_set_floating(ws, split, libtu_string_to_setparam(how));
1373 * Toggle floating of the sides of a split containin \var{reg} as indicated
1374 * by the parameters \var{how} (\codestr{set}, \codestr{unset}, or
1375 * \codestr{toggle}) and \var{dirstr} (\codestr{left}, \codestr{right},
1376 * \codestr{up}, or \codestr{down}). The new status is returned
1377 * (and \code{false} also on error).
1379 EXTL_EXPORT_AS(WTiling, set_floating_at)
1380 bool tiling_set_floating_at_extl(WTiling *ws, WRegion *reg, const char *how,
1383 WPrimn hprimn=PRIMN_ANY, vprimn=PRIMN_ANY;
1384 WSplitSplit *split, *nsplit;
1387 node=(WSplit*)get_node_check(ws, reg);
1395 if(!ioncore_string_to_navi(dirstr, &nh))
1398 navi_to_primn(nh, &hprimn, &vprimn, PRIMN_NONE);
1402 split=OBJ_CAST(node->parent, WSplitSplit);
1404 warn(TR("No suitable split here."));
1408 if(!OBJ_IS(split->tl, WSplitST) && !OBJ_IS(split->br, WSplitST)){
1409 WPrimn tmp=(split->dir==SPLIT_VERTICAL ? vprimn : hprimn);
1411 || (node==split->tl && tmp==PRIMN_BR)
1412 || (node==split->br && tmp==PRIMN_TL)){
1417 node=(WSplit*)split;
1420 nsplit=tiling_set_floating(ws, split, libtu_string_to_setparam(how));
1422 return OBJ_IS((Obj*)(nsplit==NULL ? split : nsplit), WSplitFloat);
1432 ExtlTab tiling_get_configuration(WTiling *ws)
1434 ExtlTab tab, split_tree=extl_table_none();
1436 tab=region_get_base_configuration((WRegion*)ws);
1438 if(ws->split_tree!=NULL){
1439 if(!split_get_config(ws->split_tree, &split_tree))
1440 warn(TR("Could not get split tree."));
1443 extl_table_sets_t(tab, "split_tree", split_tree);
1444 extl_unref_table(split_tree);
1456 WSplit *load_splitst(WTiling *ws, const WRectangle *geom, ExtlTab tab)
1460 if(ws->stdispnode!=NULL){
1461 warn(TR("Workspace already has a status display node."));
1465 st=create_splitst(geom, NULL);
1471 static bool do_attach(WTiling *ws, WRegion *reg, void *p)
1473 WSplitRegion *node=create_splitregion(®ION_GEOM(reg), reg);
1478 if(!tiling_managed_add(ws, reg)){
1480 destroy_obj((Obj*)node);
1484 *(WSplitRegion**)p=node;
1490 WSplit *load_splitregion(WTiling *ws, const WRectangle *geom, ExtlTab tab)
1492 WWindow *par=REGION_PARENT(ws);
1493 WRegionAttachData data;
1498 if(!extl_table_gets_t(tab, "regparams", &rt)){
1499 warn(TR("Missing region parameters."));
1503 data.type=REGION_ATTACH_LOAD;
1508 fp.mode=REGION_FIT_EXACT;
1510 region_attach_helper((WRegion*)ws, par, &fp,
1511 (WRegionDoAttachFn*)do_attach, &node, &data);
1513 extl_unref_table(rt);
1521 WSplit *load_splitsplit(WTiling *ws, const WRectangle *geom, ExtlTab tab)
1523 WSplit *tl=NULL, *br=NULL;
1531 set+=(extl_table_gets_i(tab, "tls", &tls)==TRUE);
1532 set+=(extl_table_gets_i(tab, "brs", &brs)==TRUE);
1533 set+=(extl_table_gets_s(tab, "dir", &dir_str)==TRUE);
1538 if(strcmp(dir_str, "vertical")==0){
1540 }else if(strcmp(dir_str, "horizontal")==0){
1541 dir=SPLIT_HORIZONTAL;
1543 warn(TR("Invalid direction."));
1549 split=create_splitsplit(geom, dir);
1553 tls=maxof(tls, MINS);
1554 brs=maxof(brs, MINS);
1557 if(dir==SPLIT_HORIZONTAL){
1558 tls=maxof(0, geom->w)*tls/(tls+brs);
1561 tls=maxof(0, geom->h)*tls/(tls+brs);
1565 if(extl_table_gets_t(tab, "tl", &subtab)){
1566 tl=tiling_load_node(ws, &geom2, subtab);
1567 extl_unref_table(subtab);
1571 if(dir==SPLIT_HORIZONTAL){
1579 if(extl_table_gets_t(tab, "br", &subtab)){
1580 br=tiling_load_node(ws, &geom2, subtab);
1581 extl_unref_table(subtab);
1584 if(tl==NULL || br==NULL){
1585 /* PRIMN_TL/BR instead of ANY because of stdisp. */
1586 destroy_obj((Obj*)split);
1588 split_do_resize(tl, geom, PRIMN_BR, PRIMN_BR, FALSE);
1592 split_do_resize(br, geom, PRIMN_TL, PRIMN_TL, FALSE);
1598 tl->parent=(WSplitInner*)split;
1599 br->parent=(WSplitInner*)split;
1601 /*split->tmpsize=tls;*/
1605 return (WSplit*)split;
1609 WSplit *tiling_load_node_default(WTiling *ws, const WRectangle *geom,
1615 extl_table_gets_s(tab, "type", &typestr);
1618 warn(TR("No split type given."));
1622 if(strcmp(typestr, "WSplitRegion")==0)
1623 node=load_splitregion(ws, geom, tab);
1624 else if(strcmp(typestr, "WSplitSplit")==0)
1625 node=load_splitsplit(ws, geom, tab);
1626 else if(strcmp(typestr, "WSplitFloat")==0)
1627 node=load_splitfloat(ws, geom, tab);
1628 else if(strcmp(typestr, "WSplitST")==0)
1629 node=NULL;/*load_splitst(ws, geom, tab);*/
1631 warn(TR("Unknown split type."));
1639 WSplit *tiling_load_node(WTiling *ws, const WRectangle *geom, ExtlTab tab)
1642 CALL_DYN_RET(ret, WSplit*, tiling_load_node, ws, (ws, geom, tab));
1648 WRegion *tiling_load(WWindow *par, const WFitParams *fp, ExtlTab tab)
1654 if(extl_table_gets_t(tab, "split_tree", &treetab))
1657 ws=create_tiling(par, fp, NULL, ci);
1661 extl_unref_table(treetab);
1666 ws->split_tree=tiling_load_node(ws, ®ION_GEOM(ws), treetab);
1667 extl_unref_table(treetab);
1670 if(ws->split_tree==NULL){
1671 warn(TR("The workspace is empty."));
1672 destroy_obj((Obj*)ws);
1676 ws->split_tree->ws_if_root=ws;
1677 split_restack(ws->split_tree, ws->dummywin, Above);
1679 return (WRegion*)ws;
1686 /*{{{ Dynamic function table and class implementation */
1689 static DynFunTab tiling_dynfuntab[]={
1696 {region_do_set_focus,
1697 tiling_do_set_focus},
1699 {(DynFun*)region_fitrep,
1700 (DynFun*)tiling_fitrep},
1702 {region_managed_rqgeom,
1703 tiling_managed_rqgeom},
1705 {region_managed_remove,
1706 tiling_managed_remove},
1708 {(DynFun*)region_managed_prepare_focus,
1709 (DynFun*)tiling_managed_prepare_focus},
1711 {(DynFun*)region_prepare_manage,
1712 (DynFun*)tiling_prepare_manage},
1714 {(DynFun*)region_rescue_clientwins,
1715 (DynFun*)tiling_rescue_clientwins},
1717 {(DynFun*)region_get_rescue_pholder_for,
1718 (DynFun*)tiling_get_rescue_pholder_for},
1720 {(DynFun*)region_get_configuration,
1721 (DynFun*)tiling_get_configuration},
1723 {(DynFun*)region_managed_disposeroot,
1724 (DynFun*)tiling_managed_disposeroot},
1726 {(DynFun*)region_current,
1727 (DynFun*)tiling_current},
1729 {(DynFun*)tiling_managed_add,
1730 (DynFun*)tiling_managed_add_default},
1732 {region_manage_stdisp,
1733 tiling_manage_stdisp},
1735 {region_unmanage_stdisp,
1736 tiling_unmanage_stdisp},
1738 {(DynFun*)tiling_load_node,
1739 (DynFun*)tiling_load_node_default},
1747 {(DynFun*)region_navi_first,
1748 (DynFun*)tiling_navi_first},
1750 {(DynFun*)region_navi_next,
1751 (DynFun*)tiling_navi_next},
1753 {(DynFun*)region_xwindow,
1754 (DynFun*)tiling_xwindow},
1761 IMPLCLASS(WTiling, WRegion, tiling_deinit, tiling_dynfuntab);