2 * ion/mod_tiling/tiling.c
4 * Copyright (c) Tuomo Valkonen 1999-2009.
6 * See the included file LICENSE for details.
11 #include <X11/Xatom.h>
13 #include <libtu/objp.h>
14 #include <libtu/minmax.h>
15 #include <libtu/ptrlist.h>
16 #include <libmainloop/defer.h>
17 #include <libmainloop/signal.h>
19 #include <ioncore/common.h>
20 #include <ioncore/rootwin.h>
21 #include <ioncore/focus.h>
22 #include <ioncore/global.h>
23 #include <ioncore/region.h>
24 #include <ioncore/manage.h>
25 #include <ioncore/screen.h>
26 #include <ioncore/names.h>
27 #include <ioncore/saveload.h>
28 #include <ioncore/attach.h>
29 #include <ioncore/resize.h>
30 #include <libextl/extl.h>
31 #include <ioncore/regbind.h>
32 #include <ioncore/extlconv.h>
33 #include <ioncore/xwindow.h>
34 #include <ioncore/navi.h>
35 #include <ioncore/property.h>
36 #include "placement.h"
39 #include "splitfloat.h"
40 #include "split-stdisp.h"
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
)
100 if(!region_same_rootwin((WRegion
*)ws
, (WRegion
*)par
))
103 region_unset_parent((WRegion
*)ws
);
105 XReparentWindow(ioncore_g
.dpy
, ws
->dummywin
,
106 par
->win
, fp
->g
.x
, fp
->g
.y
);
108 region_set_parent((WRegion
*)ws
, par
);
110 if(ws
->split_tree
!=NULL
)
111 split_reparent(ws
->split_tree
, par
);
114 REGION_GEOM(ws
)=fp
->g
;
116 if(ws
->split_tree
!=NULL
){
117 if(fp
->mode
®ION_FIT_ROTATE
)
118 ok
=split_rotate_to(ws
->split_tree
, &(fp
->g
), fp
->rotation
);
120 split_resize(ws
->split_tree
, &(fp
->g
), PRIMN_ANY
, PRIMN_ANY
);
127 void tiling_managed_rqgeom(WTiling
*ws
, WRegion
*mgd
,
128 const WRQGeomParams
*rq
,
131 WSplitRegion
*node
=get_node_check(ws
, mgd
);
132 if(node
!=NULL
&& ws
->split_tree
!=NULL
)
133 splittree_rqgeom((WSplit
*)node
, rq
->flags
, &rq
->geom
, geomret
);
136 bool tiling_managed_maximize(WTiling
*ws
, WRegion
*mgd
, int dir
, int action
)
138 WSplitRegion
*node
=get_node_check(ws
, mgd
);
140 if(node
!=NULL
&& ws
->split_tree
!=NULL
){
141 ret
=split_maximize((WSplit
*)node
, dir
, action
);
142 if(action
==RESTORE
&& ret
)
143 split_regularise_stdisp(ws
->stdispnode
);
151 void tiling_map(WTiling
*ws
)
153 REGION_MARK_MAPPED(ws
);
154 XMapWindow(ioncore_g
.dpy
, ws
->dummywin
);
156 if(ws
->split_tree
!=NULL
)
157 split_map(ws
->split_tree
);
161 void tiling_unmap(WTiling
*ws
)
163 REGION_MARK_UNMAPPED(ws
);
164 XUnmapWindow(ioncore_g
.dpy
, ws
->dummywin
);
166 if(ws
->split_tree
!=NULL
)
167 split_unmap(ws
->split_tree
);
171 void tiling_fallback_focus(WTiling
*ws
, bool warp
)
173 region_finalise_focusing((WRegion
*)ws
, ws
->dummywin
, warp
, CurrentTime
, TRUE
);
177 void tiling_do_set_focus(WTiling
*ws
, bool warp
)
179 WRegion
*sub
=tiling_current(ws
);
182 tiling_fallback_focus(ws
, warp
);
186 region_do_set_focus(sub
, warp
);
190 static WTimer
*restack_timer
=NULL
;
193 static void restack_handler(WTimer
*tmr
, Obj
*obj
)
196 WTiling
*ws
=(WTiling
*)obj
;
197 split_restack(ws
->split_tree
, ws
->dummywin
, Above
);
202 bool tiling_managed_prepare_focus(WTiling
*ws
, WRegion
*reg
,
203 int flags
, WPrepareFocusResult
*res
)
207 if(!region_prepare_focus((WRegion
*)ws
, flags
, res
))
210 node
=get_node_check(ws
, reg
);
212 if(node
!=NULL
&& node
->split
.parent
!=NULL
)
213 splitinner_mark_current(node
->split
.parent
, &(node
->split
));
215 /* WSplitSplit uses activity based stacking as required on WAutoWS,
216 * so we must restack here.
218 if(ws
->split_tree
!=NULL
){
219 int rd
=mod_tiling_raise_delay
;
220 bool use_timer
=rd
>0 && flags
®ION_GOTO_ENTERWINDOW
;
223 if(restack_timer
!=NULL
){
224 Obj
*obj
=restack_timer
->objwatch
.obj
;
226 timer_reset(restack_timer
);
227 restack_handler(restack_timer
, obj
);
230 restack_timer
=create_timer();
234 if(use_timer
&& restack_timer
!=NULL
){
235 timer_set(restack_timer
, rd
, restack_handler
, (Obj
*)ws
);
237 split_restack(ws
->split_tree
, ws
->dummywin
, Above
);
248 void tiling_restack(WTiling
*ws
, Window other
, int mode
)
250 xwindow_restack(ws
->dummywin
, other
, mode
);
251 if(ws
->split_tree
!=NULL
)
252 split_restack(ws
->split_tree
, ws
->dummywin
, Above
);
256 void tiling_stacking(WTiling
*ws
, Window
*bottomret
, Window
*topret
)
258 Window sbottom
=None
, stop
=None
;
260 if(ws
->split_tree
!=None
)
261 split_stacking(ws
->split_tree
, &sbottom
, &stop
);
263 *bottomret
=ws
->dummywin
;
264 *topret
=(stop
!=None
? stop
: ws
->dummywin
);
268 Window
tiling_xwindow(const WTiling
*ws
)
277 /*{{{ Status display support code */
280 static bool regnodefilter(WSplit
*split
)
282 return OBJ_IS(split
, WSplitRegion
);
286 void tiling_unmanage_stdisp(WTiling
*ws
, bool permanent
, bool nofocus
)
288 WSplitRegion
*tofocus
=NULL
;
292 if(ws
->stdispnode
==NULL
)
295 od
=ws
->stdispnode
->regnode
.reg
;
298 if(!nofocus
&& REGION_IS_ACTIVE(od
) &&
299 region_may_control_focus((WRegion
*)ws
)){
301 tofocus
=(WSplitRegion
*)split_nextto((WSplit
*)(ws
->stdispnode
),
302 PRIMN_ANY
, PRIMN_ANY
,
305 /* Reset node_of info here so tiling_managed_remove will not
308 splittree_set_node_of(od
, NULL
);
309 tiling_do_managed_remove(ws
, od
);
313 WSplit
*node
=(WSplit
*)ws
->stdispnode
;
315 splittree_remove(node
, TRUE
);
320 region_set_focus(tofocus
->reg
);
322 tiling_fallback_focus(ws
, FALSE
);
327 static void tiling_create_stdispnode(WTiling
*ws
, WRegion
*stdisp
,
328 int corner
, int orientation
,
331 WRectangle
*wg
=®ION_GEOM(ws
), dg
;
332 WSplitST
*stdispnode
;
335 assert(ws
->split_tree
!=NULL
);
337 if(orientation
==REGION_ORIENTATION_HORIZONTAL
){
341 dg
.y
=((corner
==MPLEX_STDISP_BL
|| corner
==MPLEX_STDISP_BR
)
348 dg
.x
=((corner
==MPLEX_STDISP_TR
|| corner
==MPLEX_STDISP_BR
)
353 stdispnode
=create_splitst(&dg
, stdisp
);
355 if(stdispnode
==NULL
){
356 warn(TR("Unable to create a node for status display."));
360 stdispnode
->corner
=corner
;
361 stdispnode
->orientation
=orientation
;
362 stdispnode
->fullsize
=fullsize
;
364 split
=create_splitsplit(wg
, (orientation
==REGION_ORIENTATION_HORIZONTAL
366 : SPLIT_HORIZONTAL
));
369 warn(TR("Unable to create new split for status display."));
370 stdispnode
->regnode
.reg
=NULL
;
371 destroy_obj((Obj
*)stdispnode
);
375 /* Set up new split tree */
376 ((WSplit
*)stdispnode
)->parent
=(WSplitInner
*)split
;
377 ws
->split_tree
->parent
=(WSplitInner
*)split
;
378 ws
->split_tree
->ws_if_root
=NULL
;
380 if((orientation
==REGION_ORIENTATION_HORIZONTAL
&&
381 (corner
==MPLEX_STDISP_BL
|| corner
==MPLEX_STDISP_BR
)) ||
382 (orientation
==REGION_ORIENTATION_VERTICAL
&&
383 (corner
==MPLEX_STDISP_TR
|| corner
==MPLEX_STDISP_BR
))){
384 split
->tl
=ws
->split_tree
;
385 split
->br
=(WSplit
*)stdispnode
;
386 split
->current
=SPLIT_CURRENT_TL
;
388 split
->tl
=(WSplit
*)stdispnode
;
389 split
->br
=ws
->split_tree
;
390 split
->current
=SPLIT_CURRENT_BR
;
393 ws
->split_tree
=(WSplit
*)split
;
394 ((WSplit
*)split
)->ws_if_root
=ws
;
395 ws
->stdispnode
=stdispnode
;
399 void tiling_manage_stdisp(WTiling
*ws
, WRegion
*stdisp
,
400 const WMPlexSTDispInfo
*di
)
402 bool mcf
=region_may_control_focus((WRegion
*)ws
);
403 int flags
=REGION_RQGEOM_WEAK_X
|REGION_RQGEOM_WEAK_Y
;
404 int orientation
=region_orientation(stdisp
);
406 WRectangle dg
, *stdg
;
408 if(orientation
!=REGION_ORIENTATION_VERTICAL
/*&&
409 orientation!=REGION_ORIENTATION_HORIZONTAL*/){
410 orientation
=REGION_ORIENTATION_HORIZONTAL
;
413 if(ws
->stdispnode
==NULL
|| ws
->stdispnode
->regnode
.reg
!=stdisp
)
414 region_detach_manager(stdisp
);
416 /* Remove old stdisp if corner and orientation don't match.
418 if(ws
->stdispnode
!=NULL
&& (di
->pos
!=ws
->stdispnode
->corner
||
419 orientation
!=ws
->stdispnode
->orientation
)){
420 tiling_unmanage_stdisp(ws
, TRUE
, TRUE
);
423 if(ws
->stdispnode
==NULL
){
424 tiling_create_stdispnode(ws
, stdisp
, di
->pos
, orientation
,
426 if(ws
->stdispnode
==NULL
)
429 WRegion
*od
=ws
->stdispnode
->regnode
.reg
;
431 act
=REGION_IS_ACTIVE(od
);
432 splittree_set_node_of(od
, NULL
);
433 tiling_managed_remove(ws
, od
);
434 assert(ws
->stdispnode
->regnode
.reg
==NULL
);
437 ws
->stdispnode
->fullsize
=di
->fullsize
;
438 ws
->stdispnode
->regnode
.reg
=stdisp
;
439 splittree_set_node_of(stdisp
, &(ws
->stdispnode
->regnode
));
442 if(!tiling_managed_add(ws
, stdisp
)){
443 tiling_unmanage_stdisp(ws
, TRUE
, TRUE
);
447 stdisp
->flags
|=REGION_SKIP_FOCUS
;
449 dg
=((WSplit
*)(ws
->stdispnode
))->geom
;
451 dg
.h
=stdisp_recommended_h(ws
->stdispnode
);
452 dg
.w
=stdisp_recommended_w(ws
->stdispnode
);
454 splittree_rqgeom((WSplit
*)(ws
->stdispnode
), flags
, &dg
, FALSE
);
456 stdg
=&(((WSplit
*)ws
->stdispnode
)->geom
);
458 if(stdisp
->geom
.x
!=stdg
->x
|| stdisp
->geom
.y
!=stdg
->y
||
459 stdisp
->geom
.w
!=stdg
->w
|| stdisp
->geom
.h
!=stdg
->h
){
460 region_fit(stdisp
, stdg
, REGION_FIT_EXACT
);
463 /* Restack to ensure the split tree is stacked in the expected order. */
464 if(ws
->split_tree
!=NULL
)
465 split_restack(ws
->split_tree
, ws
->dummywin
, Above
);
468 region_set_focus(stdisp
);
475 /*{{{ Create/destroy */
478 bool tiling_managed_add_default(WTiling
*ws
, WRegion
*reg
)
482 if(TILING_STDISP_OF(ws
)!=reg
){
483 if(!ptrlist_insert_last(&(ws
->managed_list
), reg
))
487 region_set_manager(reg
, (WRegion
*)ws
);
489 frame
=OBJ_CAST(reg
, WFrame
);
491 if(framemode_unalt(frame_mode(frame
))!=FRAME_MODE_TILED
)
492 frame_set_mode(frame
, FRAME_MODE_TILED
);
495 if(REGION_IS_MAPPED(ws
))
498 if(region_may_control_focus((WRegion
*)ws
)){
499 WRegion
*curr
=tiling_current(ws
);
500 if(curr
==NULL
|| !REGION_IS_ACTIVE(curr
))
508 bool tiling_managed_add(WTiling
*ws
, WRegion
*reg
)
511 CALL_DYN_RET(ret
, bool, tiling_managed_add
, ws
, (ws
, reg
));
516 bool tiling_do_attach_initial(WTiling
*ws
, WRegion
*reg
)
518 assert(ws
->split_tree
==NULL
);
520 ws
->split_tree
=(WSplit
*)create_splitregion(®ION_GEOM(reg
), reg
);
521 if(ws
->split_tree
==NULL
)
524 ws
->split_tree
->ws_if_root
=ws
;
526 if(!tiling_managed_add(ws
, reg
)){
527 destroy_obj((Obj
*)ws
->split_tree
);
536 static WRegion
*create_frame_tiling(WWindow
*parent
, const WFitParams
*fp
)
538 return (WRegion
*)create_frame(parent
, fp
, FRAME_MODE_TILED
, "Tiling Frame");
542 bool tiling_init(WTiling
*ws
, WWindow
*parent
, const WFitParams
*fp
,
543 WRegionSimpleCreateFn
*create_frame_fn
, bool ci
)
548 ws
->create_frame_fn
=(create_frame_fn
550 : create_frame_tiling
);
552 ws
->managed_list
=NULL
;
555 ws
->dummywin
=XCreateWindow(ioncore_g
.dpy
, parent
->win
,
556 fp
->g
.x
, fp
->g
.y
, 1, 1, 0,
557 CopyFromParent
, InputOnly
,
558 CopyFromParent
, 0, NULL
);
559 if(ws
->dummywin
==None
)
562 p
[0] = "Notion WTiling dummy window";
563 xwindow_set_text_property(ws
->dummywin
, XA_WM_NAME
, p
, 1);
565 region_init(&(ws
->reg
), parent
, fp
);
567 ws
->reg
.flags
|=(REGION_GRAB_ON_PARENT
|
571 WRegionAttachData data
;
574 data
.type
=REGION_ATTACH_NEW
;
575 data
.u
.n
.fn
=(WRegionCreateFn
*)ws
->create_frame_fn
;
578 res
=region_attach_helper((WRegion
*)ws
, parent
, fp
,
579 (WRegionDoAttachFn
*)tiling_do_attach_initial
,
583 XDestroyWindow(ioncore_g
.dpy
, ws
->dummywin
);
588 XSelectInput(ioncore_g
.dpy
, ws
->dummywin
,
589 FocusChangeMask
|KeyPressMask
|KeyReleaseMask
|
590 ButtonPressMask
|ButtonReleaseMask
);
591 XSaveContext(ioncore_g
.dpy
, ws
->dummywin
, ioncore_g
.win_context
,
594 region_register(&(ws
->reg
));
595 region_add_bindmap((WRegion
*)ws
, mod_tiling_tiling_bindmap
);
601 WTiling
*create_tiling(WWindow
*parent
, const WFitParams
*fp
,
602 WRegionSimpleCreateFn
*create_frame_fn
, bool ci
)
604 CREATEOBJ_IMPL(WTiling
, tiling
, (p
, parent
, fp
, create_frame_fn
, ci
));
608 WTiling
*create_tiling_simple(WWindow
*parent
, const WFitParams
*fp
)
610 return create_tiling(parent
, fp
, NULL
, TRUE
);
614 void tiling_deinit(WTiling
*ws
)
619 tiling_unmanage_stdisp(ws
, FALSE
, TRUE
);
621 FOR_ALL_MANAGED_BY_TILING(reg
, ws
, tmp
){
622 destroy_obj((Obj
*)reg
);
625 FOR_ALL_MANAGED_BY_TILING(reg
, ws
, tmp
){
629 if(ws
->split_tree
!=NULL
)
630 destroy_obj((Obj
*)(ws
->split_tree
));
632 XDeleteContext(ioncore_g
.dpy
, ws
->dummywin
, ioncore_g
.win_context
);
633 XDestroyWindow(ioncore_g
.dpy
, ws
->dummywin
);
636 region_deinit(&(ws
->reg
));
640 WRegion
*tiling_managed_disposeroot(WTiling
*ws
, WRegion
*reg
)
648 FOR_ALL_MANAGED_BY_TILING(mgd
, ws
, tmp
){
649 if(mgd
!=TILING_STDISP_OF(ws
) && mgd
!=reg
)
653 return region_disposeroot((WRegion
*)ws
);
657 bool tiling_rescue_clientwins(WTiling
*ws
, WRescueInfo
*info
)
661 ptrlist_iter_init(&tmp
, ws
->managed_list
);
663 return region_rescue_some_clientwins((WRegion
*)ws
, info
,
664 (WRegionIterator
*)ptrlist_iter
,
669 void tiling_do_managed_remove(WTiling
*ws
, WRegion
*reg
)
671 if(TILING_STDISP_OF(ws
)==reg
){
672 ws
->stdispnode
->regnode
.reg
=NULL
;
674 ptrlist_remove(&(ws
->managed_list
), reg
);
677 region_unset_manager(reg
, (WRegion
*)ws
);
678 splittree_set_node_of(reg
, NULL
);
682 static bool nostdispfilter(WSplit
*node
)
684 return (OBJ_IS(node
, WSplitRegion
) && !OBJ_IS(node
, WSplitST
));
688 void tiling_managed_remove(WTiling
*ws
, WRegion
*reg
)
690 bool act
=REGION_IS_ACTIVE(reg
);
691 bool mcf
=region_may_control_focus((WRegion
*)ws
);
692 WSplitRegion
*node
=get_node_check(ws
, reg
);
693 bool norestore
=(OBJ_IS_BEING_DESTROYED(ws
) || ws
->batchop
);
697 other
=tiling_do_navi_next(ws
, reg
, REGION_NAVI_ANY
, TRUE
, FALSE
);
699 tiling_do_managed_remove(ws
, reg
);
701 if(node
==(WSplitRegion
*)(ws
->stdispnode
))
707 if(other
==NULL
&& !norestore
){
708 WWindow
*par
=REGION_PARENT(ws
);
713 fp
.g
=node
->split
.geom
;
714 fp
.mode
=REGION_FIT_EXACT
;
716 other
=(ws
->create_frame_fn
)(par
, &fp
);
720 splittree_set_node_of(other
, node
);
721 tiling_managed_add(ws
, other
);
724 warn(TR("Tiling in useless state."));
729 splittree_remove((WSplit
*)node
, (!norestore
&& other
!=NULL
));
732 if(!norestore
&& other
!=NULL
&& act
&& mcf
)
737 static bool mplexfilter(WSplit
*node
)
739 WSplitRegion
*regnode
=OBJ_CAST(node
, WSplitRegion
);
741 return (regnode
!=NULL
&& regnode
->reg
!=NULL
&&
742 OBJ_IS(regnode
->reg
, WMPlex
));
746 static WPHolder
*find_ph_result
=NULL
;
747 static WRegion
*find_ph_param
=NULL
;
750 static bool find_ph(WSplit
*split
)
752 WSplitRegion
*sr
=OBJ_CAST(split
, WSplitRegion
);
754 assert(find_ph_result
==NULL
);
756 if(sr
==NULL
|| sr
->reg
==NULL
)
759 find_ph_result
=region_get_rescue_pholder_for(sr
->reg
, find_ph_param
);
761 return (find_ph_result
!=NULL
);
765 WPHolder
*tiling_get_rescue_pholder_for(WTiling
*ws
, WRegion
*mgd
)
767 WSplit
*node
=(WSplit
*)get_node_check(ws
, mgd
);
774 if(ws
->split_tree
!=NULL
){
775 split_current_todir(ws
->split_tree
, PRIMN_ANY
, PRIMN_ANY
,
780 split_nextto(node
, PRIMN_ANY
, PRIMN_ANY
, find_ph
);
781 if(find_ph_result
!=NULL
)
783 node
=(WSplit
*)node
->parent
;
801 static void navi_to_primn(WRegionNavi nh
, WPrimn
*hprimn
, WPrimn
*vprimn
,
804 /* choice should be PRIMN_ANY or PRIMN_NONE */
807 case REGION_NAVI_BEG
:
812 case REGION_NAVI_END
:
817 case REGION_NAVI_LEFT
:
822 case REGION_NAVI_RIGHT
:
827 case REGION_NAVI_TOP
:
832 case REGION_NAVI_BOTTOM
:
838 case REGION_NAVI_ANY
:
846 static WRegion
*node_reg(WSplit
*node
)
848 WSplitRegion
*rnode
=OBJ_CAST(node
, WSplitRegion
);
849 return (rnode
!=NULL
? rnode
->reg
: NULL
);
853 WRegion
*tiling_do_navi_next(WTiling
*ws
, WRegion
*reg
,
854 WRegionNavi nh
, bool nowrap
,
857 WSplitFilter
*filter
=(any
? NULL
: nostdispfilter
);
858 WPrimn hprimn
, vprimn
;
861 navi_to_primn(nh
, &hprimn
, &vprimn
, PRIMN_NONE
);
864 reg
=tiling_current(ws
);
867 WSplitRegion
*node
=get_node_check(ws
, reg
);
869 nxt
=node_reg(split_nextto((WSplit
*)node
, hprimn
, vprimn
,
874 if(nxt
==NULL
&& !nowrap
){
875 nxt
=node_reg(split_current_todir(ws
->split_tree
,
876 primn_none2any(primn_invert(hprimn
)),
877 primn_none2any(primn_invert(vprimn
)),
885 WRegion
*tiling_do_navi_first(WTiling
*ws
, WRegionNavi nh
, bool any
)
887 WSplitFilter
*filter
=(any
? NULL
: nostdispfilter
);
888 WPrimn hprimn
, vprimn
;
890 navi_to_primn(nh
, &hprimn
, &vprimn
, PRIMN_ANY
);
892 return node_reg(split_current_todir(ws
->split_tree
,
893 hprimn
, vprimn
, filter
));
897 WRegion
*tiling_navi_next(WTiling
*ws
, WRegion
*reg
,
898 WRegionNavi nh
, WRegionNaviData
*data
)
900 WRegion
*nxt
=tiling_do_navi_next(ws
, reg
, nh
, TRUE
, FALSE
);
902 return region_navi_cont(&ws
->reg
, nxt
, data
);
906 WRegion
*tiling_navi_first(WTiling
*ws
, WRegionNavi nh
,
907 WRegionNaviData
*data
)
909 WRegion
*reg
=tiling_do_navi_first(ws
, nh
, FALSE
);
911 return region_navi_cont(&ws
->reg
, reg
, data
);
918 /*{{{ Split/unsplit */
921 static bool get_split_dir_primn(const char *str
, int *dir
, int *primn
)
923 WPrimn hprimn
, vprimn
;
926 if(!ioncore_string_to_navi(str
, &nh
))
929 navi_to_primn(nh
, &hprimn
, &vprimn
, PRIMN_NONE
);
931 if(hprimn
==PRIMN_NONE
){
934 }else if(vprimn
==PRIMN_NONE
){
935 *dir
=SPLIT_HORIZONTAL
;
938 warn(TR("Invalid direction"));
946 static bool get_split_dir_primn_float(const char *str
, int *dir
, int *primn
,
949 if(strncmp(str
, "floating:", 9)==0){
951 return get_split_dir_primn(str
+9, dir
, primn
);
954 return get_split_dir_primn(str
, dir
, primn
);
959 #define SPLIT_MINS 16 /* totally arbitrary */
962 static WFrame
*tiling_do_split(WTiling
*ws
, WSplit
*node
,
963 const char *dirstr
, int minw
, int minh
)
965 int dir
, primn
, mins
;
970 if(node
==NULL
|| ws
->split_tree
==NULL
){
971 warn(TR("Invalid node."));
975 if(!get_split_dir_primn_float(dirstr
, &dir
, &primn
, &floating
))
978 mins
=(dir
==SPLIT_VERTICAL
? minh
: minw
);
981 nnode
=splittree_split(node
, dir
, primn
, mins
,
985 nnode
=splittree_split_floating(node
, dir
, primn
, mins
,
986 ws
->create_frame_fn
, ws
);
990 warn(TR("Unable to split."));
994 /* We must restack here to ensure the split tree is stacked in the
997 if(ws
->split_tree
!=NULL
)
998 split_restack(ws
->split_tree
, ws
->dummywin
, Above
);
1000 newframe
=OBJ_CAST(nnode
->reg
, WFrame
);
1001 assert(newframe
!=NULL
);
1003 if(!tiling_managed_add(ws
, nnode
->reg
)){
1005 destroy_obj((Obj
*)nnode
);
1006 destroy_obj((Obj
*)newframe
);
1015 * Create a new frame on \var{ws} \codestr{above}, \codestr{below}
1016 * \codestr{left} of, or \codestr{right} of \var{node} as indicated
1017 * by \var{dirstr}. If \var{dirstr} is prefixed with
1018 * \codestr{floating:} a floating split is created.
1021 WFrame
*tiling_split(WTiling
*ws
, WSplit
*node
, const char *dirstr
)
1023 if(!check_node(ws
, node
))
1026 return tiling_do_split(ws
, node
, dirstr
,
1027 SPLIT_MINS
, SPLIT_MINS
);
1032 * Same as \fnref{WTiling.split} at the root of the split tree.
1035 WFrame
*tiling_split_top(WTiling
*ws
, const char *dirstr
)
1037 return tiling_do_split(ws
, ws
->split_tree
, dirstr
,
1038 SPLIT_MINS
, SPLIT_MINS
);
1043 * Split \var{frame} creating a new frame to direction \var{dirstr}
1044 * (one of \codestr{left}, \codestr{right}, \codestr{top} or
1045 * \codestr{bottom}) of \var{frame}.
1046 * If \var{attach_current} is set, the region currently displayed in
1047 * \var{frame}, if any, is moved to thenew frame.
1048 * If \var{dirstr} is prefixed with \codestr{floating:}, a floating
1052 WFrame
*tiling_split_at(WTiling
*ws
, WFrame
*frame
, const char *dirstr
,
1053 bool attach_current
)
1062 node
=get_node_check(ws
, (WRegion
*)frame
);
1064 newframe
=tiling_do_split(ws
, (WSplit
*)node
, dirstr
,
1065 region_min_w((WRegion
*)frame
),
1066 region_min_h((WRegion
*)frame
));
1071 curr
=mplex_mx_current(&(frame
->mplex
));
1073 if(attach_current
&& curr
!=NULL
)
1074 mplex_attach_simple(&(newframe
->mplex
), curr
, MPLEX_ATTACH_SWITCHTO
);
1076 if(region_may_control_focus((WRegion
*)frame
))
1077 region_goto((WRegion
*)newframe
);
1084 * Try to relocate regions managed by \var{reg} to another frame
1085 * and, if possible, destroy it.
1088 void tiling_unsplit_at(WTiling
*ws
, WRegion
*reg
)
1092 if(reg
==NULL
|| REGION_MANAGER(reg
)!=(WRegion
*)ws
)
1095 ph
=region_get_rescue_pholder_for((WRegion
*)ws
, reg
);
1098 region_rescue(reg
, ph
, REGION_RESCUE_NODEEP
|REGION_RESCUE_PHFLAGS_OK
);
1099 destroy_obj((Obj
*)ph
);
1102 region_defer_rqdispose(reg
);
1109 /*{{{ Navigation etc. exports */
1112 WRegion
*tiling_current(WTiling
*ws
)
1114 WSplitRegion
*node
=NULL
;
1115 if(ws
->split_tree
!=NULL
){
1116 node
=(WSplitRegion
*)split_current_todir(ws
->split_tree
,
1117 PRIMN_ANY
, PRIMN_ANY
, NULL
);
1119 return (node
? node
->reg
: NULL
);
1124 * Iterate over managed regions of \var{ws} until \var{iterfn} returns
1126 * The function is called in protected mode.
1127 * This routine returns \code{true} if it reaches the end of list
1128 * without this happening.
1132 bool tiling_managed_i(WTiling
*ws
, ExtlFn iterfn
)
1136 ptrlist_iter_init(&tmp
, ws
->managed_list
);
1138 return extl_iter_objlist_(iterfn
, (ObjIterator
*)ptrlist_iter
, &tmp
);
1143 * Returns the root of the split tree.
1147 WSplit
*tiling_split_tree(WTiling
*ws
)
1149 return ws
->split_tree
;
1154 * Return the most previously active region next to \var{reg} in
1155 * direction \var{dirstr} (\codestr{left}, \codestr{right}, \codestr{up},
1156 * or \codestr{down}). The region \var{reg}
1157 * must be managed by \var{ws}. If \var{any} is not set, the status display
1158 * is not considered.
1162 WRegion
*tiling_nextto(WTiling
*ws
, WRegion
*reg
, const char *dirstr
,
1167 if(!ioncore_string_to_navi(dirstr
, &nh
))
1170 return tiling_do_navi_next(ws
, reg
, nh
, FALSE
, any
);
1175 * Return the most previously active region on \var{ws} with no
1176 * other regions next to it in direction \var{dirstr}
1177 * (\codestr{left}, \codestr{right}, \codestr{up}, or \codestr{down}).
1178 * If \var{any} is not set, the status display is not considered.
1182 WRegion
*tiling_farthest(WTiling
*ws
, const char *dirstr
, bool any
)
1186 if(!ioncore_string_to_navi(dirstr
, &nh
))
1189 return tiling_do_navi_first(ws
, nh
, any
);
1194 * For region \var{reg} managed by \var{ws} return the \type{WSplit}
1195 * a leaf of which \var{reg} is.
1199 WSplitRegion
*tiling_node_of(WTiling
*ws
, WRegion
*reg
)
1202 warn(TR("Nil parameter."));
1206 if(REGION_MANAGER(reg
)!=(WRegion
*)ws
){
1207 warn(TR("Manager doesn't match."));
1211 return splittree_node_of(reg
);
1218 /*{{{ Flip and transpose */
1221 static WSplitSplit
*get_at_split(WTiling
*ws
, WRegion
*reg
)
1227 split
=OBJ_CAST(ws
->split_tree
, WSplitSplit
);
1230 else if(split
->br
==(WSplit
*)ws
->stdispnode
)
1231 return OBJ_CAST(split
->tl
, WSplitSplit
);
1232 else if(split
->tl
==(WSplit
*)ws
->stdispnode
)
1233 return OBJ_CAST(split
->br
, WSplitSplit
);
1238 node
=(WSplit
*)get_node_check(ws
, reg
);
1243 if(node
==(WSplit
*)ws
->stdispnode
){
1244 warn(TR("The status display is not a valid parameter for "
1249 split
=OBJ_CAST(node
->parent
, WSplitSplit
);
1251 if(split
!=NULL
&& (split
->tl
==(WSplit
*)ws
->stdispnode
||
1252 split
->br
==(WSplit
*)ws
->stdispnode
)){
1253 split
=OBJ_CAST(((WSplit
*)split
)->parent
, WSplitSplit
);
1261 * Flip \var{ws} at \var{reg} or root if nil.
1264 bool iowns_flip_at(WTiling
*ws
, WRegion
*reg
)
1266 WSplitSplit
*split
=get_at_split(ws
, reg
);
1271 splitsplit_flip(split
);
1278 * Transpose \var{ws} at \var{reg} or root if nil.
1281 bool iowns_transpose_at(WTiling
*ws
, WRegion
*reg
)
1283 WSplitSplit
*split
=get_at_split(ws
, reg
);
1288 split_transpose((WSplit
*)split
);
1297 /*{{{ Floating toggle */
1300 static void replace(WSplitSplit
*split
, WSplitSplit
*nsplit
)
1302 WSplitInner
*psplit
=split
->isplit
.split
.parent
;
1304 nsplit
->tl
=split
->tl
;
1306 nsplit
->tl
->parent
=(WSplitInner
*)nsplit
;
1308 nsplit
->br
=split
->br
;
1310 nsplit
->br
->parent
=(WSplitInner
*)nsplit
;
1313 splitinner_replace((WSplitInner
*)psplit
, (WSplit
*)split
,
1316 splittree_changeroot((WSplit
*)split
, (WSplit
*)nsplit
);
1321 WSplitSplit
*tiling_set_floating(WTiling
*ws
, WSplitSplit
*split
, int sp
)
1323 bool set
=OBJ_IS(split
, WSplitFloat
);
1324 bool nset
=libtu_do_setparam(sp
, set
);
1325 const WRectangle
*g
=&((WSplit
*)split
)->geom
;
1332 ns
=(WSplitSplit
*)create_splitfloat(g
, ws
, split
->dir
);
1334 if(OBJ_IS(split
->tl
, WSplitST
) || OBJ_IS(split
->br
, WSplitST
)){
1335 warn(TR("Refusing to float split directly containing the "
1336 "status display."));
1339 ns
=create_splitsplit(g
, split
->dir
);
1344 split_resize((WSplit
*)ns
, g
, PRIMN_ANY
, PRIMN_ANY
);
1345 mainloop_defer_destroy((Obj
*)split
);
1353 * Toggle floating of a split's sides at \var{split} as indicated by the
1354 * parameter \var{how} (\codestr{set}, \codestr{unset}, or \codestr{toggle}).
1355 * A split of the appropriate is returned, if there was a change.
1357 EXTL_EXPORT_AS(WTiling
, set_floating
)
1358 WSplitSplit
*tiling_set_floating_extl(WTiling
*ws
, WSplitSplit
*split
,
1361 if(!check_node(ws
, (WSplit
*)split
))
1363 return tiling_set_floating(ws
, split
, libtu_string_to_setparam(how
));
1368 * Toggle floating of the sides of a split containin \var{reg} as indicated
1369 * by the parameters \var{how} (\codestr{set}, \codestr{unset}, or
1370 * \codestr{toggle}) and \var{dirstr} (\codestr{left}, \codestr{right},
1371 * \codestr{up}, or \codestr{down}). The new status is returned
1372 * (and \code{false} also on error).
1374 EXTL_EXPORT_AS(WTiling
, set_floating_at
)
1375 bool tiling_set_floating_at_extl(WTiling
*ws
, WRegion
*reg
, const char *how
,
1378 WPrimn hprimn
=PRIMN_ANY
, vprimn
=PRIMN_ANY
;
1379 WSplitSplit
*split
, *nsplit
;
1382 node
=(WSplit
*)get_node_check(ws
, reg
);
1390 if(!ioncore_string_to_navi(dirstr
, &nh
))
1393 navi_to_primn(nh
, &hprimn
, &vprimn
, PRIMN_NONE
);
1397 split
=OBJ_CAST(node
->parent
, WSplitSplit
);
1399 warn(TR("No suitable split here."));
1403 if(!OBJ_IS(split
->tl
, WSplitST
) && !OBJ_IS(split
->br
, WSplitST
)){
1404 WPrimn tmp
=(split
->dir
==SPLIT_VERTICAL
? vprimn
: hprimn
);
1406 || (node
==split
->tl
&& tmp
==PRIMN_BR
)
1407 || (node
==split
->br
&& tmp
==PRIMN_TL
)){
1412 node
=(WSplit
*)split
;
1415 nsplit
=tiling_set_floating(ws
, split
, libtu_string_to_setparam(how
));
1417 return OBJ_IS((Obj
*)(nsplit
==NULL
? split
: nsplit
), WSplitFloat
);
1427 ExtlTab
tiling_get_configuration(WTiling
*ws
)
1429 ExtlTab tab
, split_tree
=extl_table_none();
1431 tab
=region_get_base_configuration((WRegion
*)ws
);
1433 if(ws
->split_tree
!=NULL
){
1434 if(!split_get_config(ws
->split_tree
, &split_tree
))
1435 warn(TR("Could not get split tree."));
1438 extl_table_sets_t(tab
, "split_tree", split_tree
);
1439 extl_unref_table(split_tree
);
1451 WSplit
*load_splitst(WTiling
*ws
, const WRectangle
*geom
, ExtlTab tab
)
1455 if(ws
->stdispnode
!=NULL
){
1456 warn(TR("Workspace already has a status display node."));
1460 st
=create_splitst(geom
, NULL
);
1466 static bool do_attach(WTiling
*ws
, WRegion
*reg
, void *p
)
1468 WSplitRegion
*node
=create_splitregion(®ION_GEOM(reg
), reg
);
1473 if(!tiling_managed_add(ws
, reg
)){
1475 destroy_obj((Obj
*)node
);
1479 *(WSplitRegion
**)p
=node
;
1485 WSplit
*load_splitregion(WTiling
*ws
, const WRectangle
*geom
, ExtlTab tab
)
1487 WWindow
*par
=REGION_PARENT(ws
);
1488 WRegionAttachData data
;
1493 if(!extl_table_gets_t(tab
, "regparams", &rt
)){
1494 warn(TR("Missing region parameters."));
1498 data
.type
=REGION_ATTACH_LOAD
;
1503 fp
.mode
=REGION_FIT_EXACT
;
1505 region_attach_helper((WRegion
*)ws
, par
, &fp
,
1506 (WRegionDoAttachFn
*)do_attach
, &node
, &data
);
1508 extl_unref_table(rt
);
1516 WSplit
*load_splitsplit(WTiling
*ws
, const WRectangle
*geom
, ExtlTab tab
)
1518 WSplit
*tl
=NULL
, *br
=NULL
;
1526 set
+=(extl_table_gets_i(tab
, "tls", &tls
)==TRUE
);
1527 set
+=(extl_table_gets_i(tab
, "brs", &brs
)==TRUE
);
1528 set
+=(extl_table_gets_s(tab
, "dir", &dir_str
)==TRUE
);
1533 if(strcmp(dir_str
, "vertical")==0){
1535 }else if(strcmp(dir_str
, "horizontal")==0){
1536 dir
=SPLIT_HORIZONTAL
;
1538 warn(TR("Invalid direction."));
1544 split
=create_splitsplit(geom
, dir
);
1548 tls
=maxof(tls
, MINS
);
1549 brs
=maxof(brs
, MINS
);
1552 if(dir
==SPLIT_HORIZONTAL
){
1553 tls
=maxof(0, geom
->w
)*tls
/(tls
+brs
);
1556 tls
=maxof(0, geom
->h
)*tls
/(tls
+brs
);
1560 if(extl_table_gets_t(tab
, "tl", &subtab
)){
1561 tl
=tiling_load_node(ws
, &geom2
, subtab
);
1562 extl_unref_table(subtab
);
1566 if(dir
==SPLIT_HORIZONTAL
){
1574 if(extl_table_gets_t(tab
, "br", &subtab
)){
1575 br
=tiling_load_node(ws
, &geom2
, subtab
);
1576 extl_unref_table(subtab
);
1579 if(tl
==NULL
|| br
==NULL
){
1580 /* PRIMN_TL/BR instead of ANY because of stdisp. */
1581 destroy_obj((Obj
*)split
);
1583 split_do_resize(tl
, geom
, PRIMN_BR
, PRIMN_BR
, FALSE
);
1587 split_do_resize(br
, geom
, PRIMN_TL
, PRIMN_TL
, FALSE
);
1593 tl
->parent
=(WSplitInner
*)split
;
1594 br
->parent
=(WSplitInner
*)split
;
1596 /*split->tmpsize=tls;*/
1600 return (WSplit
*)split
;
1604 WSplit
*tiling_load_node_default(WTiling
*ws
, const WRectangle
*geom
,
1610 extl_table_gets_s(tab
, "type", &typestr
);
1613 warn(TR("No split type given."));
1617 if(strcmp(typestr
, "WSplitRegion")==0)
1618 node
=load_splitregion(ws
, geom
, tab
);
1619 else if(strcmp(typestr
, "WSplitSplit")==0)
1620 node
=load_splitsplit(ws
, geom
, tab
);
1621 else if(strcmp(typestr
, "WSplitFloat")==0)
1622 node
=load_splitfloat(ws
, geom
, tab
);
1623 else if(strcmp(typestr
, "WSplitST")==0)
1624 node
=NULL
;/*load_splitst(ws, geom, tab);*/
1626 warn(TR("Unknown split type."));
1634 WSplit
*tiling_load_node(WTiling
*ws
, const WRectangle
*geom
, ExtlTab tab
)
1637 CALL_DYN_RET(ret
, WSplit
*, tiling_load_node
, ws
, (ws
, geom
, tab
));
1643 WRegion
*tiling_load(WWindow
*par
, const WFitParams
*fp
, ExtlTab tab
)
1649 if(extl_table_gets_t(tab
, "split_tree", &treetab
))
1652 ws
=create_tiling(par
, fp
, NULL
, ci
);
1656 extl_unref_table(treetab
);
1661 ws
->split_tree
=tiling_load_node(ws
, ®ION_GEOM(ws
), treetab
);
1662 extl_unref_table(treetab
);
1665 if(ws
->split_tree
==NULL
){
1666 warn(TR("The workspace is empty."));
1667 destroy_obj((Obj
*)ws
);
1671 ws
->split_tree
->ws_if_root
=ws
;
1672 split_restack(ws
->split_tree
, ws
->dummywin
, Above
);
1674 return (WRegion
*)ws
;
1681 /*{{{ Dynamic function table and class implementation */
1684 static DynFunTab tiling_dynfuntab
[]={
1691 {region_do_set_focus
,
1692 tiling_do_set_focus
},
1694 {(DynFun
*)region_fitrep
,
1695 (DynFun
*)tiling_fitrep
},
1697 {region_managed_rqgeom
,
1698 tiling_managed_rqgeom
},
1700 {(DynFun
*)region_managed_maximize
,
1701 (DynFun
*)tiling_managed_maximize
},
1703 {region_managed_remove
,
1704 tiling_managed_remove
},
1706 {(DynFun
*)region_managed_prepare_focus
,
1707 (DynFun
*)tiling_managed_prepare_focus
},
1709 {(DynFun
*)region_prepare_manage
,
1710 (DynFun
*)tiling_prepare_manage
},
1712 {(DynFun
*)region_rescue_clientwins
,
1713 (DynFun
*)tiling_rescue_clientwins
},
1715 {(DynFun
*)region_get_rescue_pholder_for
,
1716 (DynFun
*)tiling_get_rescue_pholder_for
},
1718 {(DynFun
*)region_get_configuration
,
1719 (DynFun
*)tiling_get_configuration
},
1721 {(DynFun
*)region_managed_disposeroot
,
1722 (DynFun
*)tiling_managed_disposeroot
},
1724 {(DynFun
*)region_current
,
1725 (DynFun
*)tiling_current
},
1727 {(DynFun
*)tiling_managed_add
,
1728 (DynFun
*)tiling_managed_add_default
},
1730 {region_manage_stdisp
,
1731 tiling_manage_stdisp
},
1733 {region_unmanage_stdisp
,
1734 tiling_unmanage_stdisp
},
1736 {(DynFun
*)tiling_load_node
,
1737 (DynFun
*)tiling_load_node_default
},
1745 {(DynFun
*)region_navi_first
,
1746 (DynFun
*)tiling_navi_first
},
1748 {(DynFun
*)region_navi_next
,
1749 (DynFun
*)tiling_navi_next
},
1751 {(DynFun
*)region_xwindow
,
1752 (DynFun
*)tiling_xwindow
},
1759 IMPLCLASS(WTiling
, WRegion
, tiling_deinit
, tiling_dynfuntab
);