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 static WTilingIterTmp tiling_iter_default_tmp
;
48 /*{{{ Some helper routines */
51 static WSplitRegion
*get_node_check(WTiling
*ws
, WRegion
*reg
)
58 node
=splittree_node_of(reg
);
60 if(node
==NULL
|| REGION_MANAGER(reg
)!=(WRegion
*)ws
)
67 static bool check_node(WTiling
*ws
, WSplit
*split
)
70 return check_node(ws
, (WSplit
*)split
->parent
);
72 if((split
->ws_if_root
!=(void*)ws
)){
73 warn(TR("Split not on workspace."));
83 /*{{{ Dynfun implementations */
86 static void reparent_mgd(WRegion
*sub
, WWindow
*par
)
89 subfp
.g
=REGION_GEOM(sub
);
90 subfp
.mode
=REGION_FIT_EXACT
;
91 if(!region_fitrep(sub
, par
, &subfp
)){
92 warn(TR("Error reparenting %s."), region_name(sub
));
93 region_detach_manager(sub
);
98 bool tiling_fitrep(WTiling
*ws
, WWindow
*par
, const WFitParams
*fp
)
104 if(!region_same_rootwin((WRegion
*)ws
, (WRegion
*)par
))
107 region_unset_parent((WRegion
*)ws
);
109 XReparentWindow(ioncore_g
.dpy
, ws
->dummywin
,
110 par
->win
, fp
->g
.x
, fp
->g
.y
);
112 region_set_parent((WRegion
*)ws
, par
);
114 if(ws
->split_tree
!=NULL
)
115 split_reparent(ws
->split_tree
, par
);
118 REGION_GEOM(ws
)=fp
->g
;
120 if(ws
->split_tree
!=NULL
){
122 if(fp
->mode
®ION_FIT_ROTATE
)
123 ok
=split_rotate_to(ws
->split_tree
, &(fp
->g
), fp
->rotation
);
125 split_resize(ws
->split_tree
, &(fp
->g
), PRIMN_ANY
, PRIMN_ANY
);
132 void tiling_managed_rqgeom(WTiling
*ws
, WRegion
*mgd
,
133 const WRQGeomParams
*rq
,
136 WSplitRegion
*node
=get_node_check(ws
, mgd
);
137 if(node
!=NULL
&& ws
->split_tree
!=NULL
)
138 splittree_rqgeom((WSplit
*)node
, rq
->flags
, &rq
->geom
, geomret
);
141 bool tiling_managed_maximize(WTiling
*ws
, WRegion
*mgd
, int dir
, int action
)
143 WSplitRegion
*node
=get_node_check(ws
, mgd
);
145 if(node
!=NULL
&& ws
->split_tree
!=NULL
){
146 ret
=split_maximize((WSplit
*)node
, dir
, action
);
147 if(action
==RESTORE
&& ret
)
148 split_regularise_stdisp(ws
->stdispnode
);
156 void tiling_map(WTiling
*ws
)
158 REGION_MARK_MAPPED(ws
);
159 XMapWindow(ioncore_g
.dpy
, ws
->dummywin
);
161 if(ws
->split_tree
!=NULL
)
162 split_map(ws
->split_tree
);
166 void tiling_unmap(WTiling
*ws
)
168 REGION_MARK_UNMAPPED(ws
);
169 XUnmapWindow(ioncore_g
.dpy
, ws
->dummywin
);
171 if(ws
->split_tree
!=NULL
)
172 split_unmap(ws
->split_tree
);
176 void tiling_fallback_focus(WTiling
*ws
, bool warp
)
178 region_finalise_focusing((WRegion
*)ws
, ws
->dummywin
, warp
, CurrentTime
, TRUE
);
182 void tiling_do_set_focus(WTiling
*ws
, bool warp
)
184 WRegion
*sub
=tiling_current(ws
);
187 tiling_fallback_focus(ws
, warp
);
191 region_do_set_focus(sub
, warp
);
195 static WTimer
*restack_timer
=NULL
;
198 static void restack_handler(WTimer
*tmr
, Obj
*obj
)
201 WTiling
*ws
=(WTiling
*)obj
;
202 split_restack(ws
->split_tree
, ws
->dummywin
, Above
);
207 bool tiling_managed_prepare_focus(WTiling
*ws
, WRegion
*reg
,
208 int flags
, WPrepareFocusResult
*res
)
212 if(!region_prepare_focus((WRegion
*)ws
, flags
, res
))
215 node
=get_node_check(ws
, reg
);
217 if(node
!=NULL
&& node
->split
.parent
!=NULL
)
218 splitinner_mark_current(node
->split
.parent
, &(node
->split
));
220 /* WSplitSplit uses activity based stacking as required on WAutoWS,
221 * so we must restack here.
223 if(ws
->split_tree
!=NULL
){
224 int rd
=mod_tiling_raise_delay
;
225 bool use_timer
=rd
>0 && flags
®ION_GOTO_ENTERWINDOW
;
228 if(restack_timer
!=NULL
){
229 Obj
*obj
=restack_timer
->objwatch
.obj
;
231 timer_reset(restack_timer
);
232 restack_handler(restack_timer
, obj
);
235 restack_timer
=create_timer();
239 if(use_timer
&& restack_timer
!=NULL
){
240 timer_set(restack_timer
, rd
, restack_handler
, (Obj
*)ws
);
242 split_restack(ws
->split_tree
, ws
->dummywin
, Above
);
253 void tiling_restack(WTiling
*ws
, Window other
, int mode
)
255 xwindow_restack(ws
->dummywin
, other
, mode
);
256 if(ws
->split_tree
!=NULL
)
257 split_restack(ws
->split_tree
, ws
->dummywin
, Above
);
261 void tiling_stacking(WTiling
*ws
, Window
*bottomret
, Window
*topret
)
263 Window sbottom
=None
, stop
=None
;
265 if(ws
->split_tree
!=None
)
266 split_stacking(ws
->split_tree
, &sbottom
, &stop
);
268 *bottomret
=ws
->dummywin
;
269 *topret
=(stop
!=None
? stop
: ws
->dummywin
);
273 Window
tiling_xwindow(const WTiling
*ws
)
282 /*{{{ Status display support code */
285 static bool regnodefilter(WSplit
*split
)
287 return OBJ_IS(split
, WSplitRegion
);
291 void tiling_unmanage_stdisp(WTiling
*ws
, bool permanent
, bool nofocus
)
293 WSplitRegion
*tofocus
=NULL
;
297 if(ws
->stdispnode
==NULL
)
300 od
=ws
->stdispnode
->regnode
.reg
;
303 if(!nofocus
&& REGION_IS_ACTIVE(od
) &&
304 region_may_control_focus((WRegion
*)ws
)){
306 tofocus
=(WSplitRegion
*)split_nextto((WSplit
*)(ws
->stdispnode
),
307 PRIMN_ANY
, PRIMN_ANY
,
310 /* Reset node_of info here so tiling_managed_remove will not
313 splittree_set_node_of(od
, NULL
);
314 tiling_do_managed_remove(ws
, od
);
318 WSplit
*node
=(WSplit
*)ws
->stdispnode
;
320 splittree_remove(node
, TRUE
);
325 region_set_focus(tofocus
->reg
);
327 tiling_fallback_focus(ws
, FALSE
);
332 static void tiling_create_stdispnode(WTiling
*ws
, WRegion
*stdisp
,
333 int corner
, int orientation
,
336 int flags
=REGION_RQGEOM_WEAK_X
|REGION_RQGEOM_WEAK_Y
;
337 WRectangle
*wg
=®ION_GEOM(ws
), dg
;
338 WSplitST
*stdispnode
;
341 assert(ws
->split_tree
!=NULL
);
343 if(orientation
==REGION_ORIENTATION_HORIZONTAL
){
347 dg
.y
=((corner
==MPLEX_STDISP_BL
|| corner
==MPLEX_STDISP_BR
)
354 dg
.x
=((corner
==MPLEX_STDISP_TR
|| corner
==MPLEX_STDISP_BR
)
359 stdispnode
=create_splitst(&dg
, stdisp
);
361 if(stdispnode
==NULL
){
362 warn(TR("Unable to create a node for status display."));
366 stdispnode
->corner
=corner
;
367 stdispnode
->orientation
=orientation
;
368 stdispnode
->fullsize
=fullsize
;
370 split
=create_splitsplit(wg
, (orientation
==REGION_ORIENTATION_HORIZONTAL
372 : SPLIT_HORIZONTAL
));
375 warn(TR("Unable to create new split for status display."));
376 stdispnode
->regnode
.reg
=NULL
;
377 destroy_obj((Obj
*)stdispnode
);
381 /* Set up new split tree */
382 ((WSplit
*)stdispnode
)->parent
=(WSplitInner
*)split
;
383 ws
->split_tree
->parent
=(WSplitInner
*)split
;
384 ws
->split_tree
->ws_if_root
=NULL
;
386 if((orientation
==REGION_ORIENTATION_HORIZONTAL
&&
387 (corner
==MPLEX_STDISP_BL
|| corner
==MPLEX_STDISP_BR
)) ||
388 (orientation
==REGION_ORIENTATION_VERTICAL
&&
389 (corner
==MPLEX_STDISP_TR
|| corner
==MPLEX_STDISP_BR
))){
390 split
->tl
=ws
->split_tree
;
391 split
->br
=(WSplit
*)stdispnode
;
392 split
->current
=SPLIT_CURRENT_TL
;
394 split
->tl
=(WSplit
*)stdispnode
;
395 split
->br
=ws
->split_tree
;
396 split
->current
=SPLIT_CURRENT_BR
;
399 ws
->split_tree
=(WSplit
*)split
;
400 ((WSplit
*)split
)->ws_if_root
=ws
;
401 ws
->stdispnode
=stdispnode
;
405 void tiling_manage_stdisp(WTiling
*ws
, WRegion
*stdisp
,
406 const WMPlexSTDispInfo
*di
)
408 bool mcf
=region_may_control_focus((WRegion
*)ws
);
409 int flags
=REGION_RQGEOM_WEAK_X
|REGION_RQGEOM_WEAK_Y
;
410 int orientation
=region_orientation(stdisp
);
412 WRectangle dg
, *stdg
;
414 if(orientation
!=REGION_ORIENTATION_VERTICAL
/*&&
415 orientation!=REGION_ORIENTATION_HORIZONTAL*/){
416 orientation
=REGION_ORIENTATION_HORIZONTAL
;
419 if(ws
->stdispnode
==NULL
|| ws
->stdispnode
->regnode
.reg
!=stdisp
)
420 region_detach_manager(stdisp
);
422 /* Remove old stdisp if corner and orientation don't match.
424 if(ws
->stdispnode
!=NULL
&& (di
->pos
!=ws
->stdispnode
->corner
||
425 orientation
!=ws
->stdispnode
->orientation
)){
426 tiling_unmanage_stdisp(ws
, TRUE
, TRUE
);
429 if(ws
->stdispnode
==NULL
){
430 tiling_create_stdispnode(ws
, stdisp
, di
->pos
, orientation
,
432 if(ws
->stdispnode
==NULL
)
435 WRegion
*od
=ws
->stdispnode
->regnode
.reg
;
437 act
=REGION_IS_ACTIVE(od
);
438 splittree_set_node_of(od
, NULL
);
439 tiling_managed_remove(ws
, od
);
440 assert(ws
->stdispnode
->regnode
.reg
==NULL
);
443 ws
->stdispnode
->fullsize
=di
->fullsize
;
444 ws
->stdispnode
->regnode
.reg
=stdisp
;
445 splittree_set_node_of(stdisp
, &(ws
->stdispnode
->regnode
));
448 if(!tiling_managed_add(ws
, stdisp
)){
449 tiling_unmanage_stdisp(ws
, TRUE
, TRUE
);
453 stdisp
->flags
|=REGION_SKIP_FOCUS
;
455 dg
=((WSplit
*)(ws
->stdispnode
))->geom
;
457 dg
.h
=stdisp_recommended_h(ws
->stdispnode
);
458 dg
.w
=stdisp_recommended_w(ws
->stdispnode
);
460 splittree_rqgeom((WSplit
*)(ws
->stdispnode
), flags
, &dg
, FALSE
);
462 stdg
=&(((WSplit
*)ws
->stdispnode
)->geom
);
464 if(stdisp
->geom
.x
!=stdg
->x
|| stdisp
->geom
.y
!=stdg
->y
||
465 stdisp
->geom
.w
!=stdg
->w
|| stdisp
->geom
.h
!=stdg
->h
){
466 region_fit(stdisp
, stdg
, REGION_FIT_EXACT
);
469 /* Restack to ensure the split tree is stacked in the expected order. */
470 if(ws
->split_tree
!=NULL
)
471 split_restack(ws
->split_tree
, ws
->dummywin
, Above
);
474 region_set_focus(stdisp
);
481 /*{{{ Create/destroy */
484 bool tiling_managed_add_default(WTiling
*ws
, WRegion
*reg
)
486 Window bottom
=None
, top
=None
;
489 if(TILING_STDISP_OF(ws
)!=reg
){
490 if(!ptrlist_insert_last(&(ws
->managed_list
), reg
))
494 region_set_manager(reg
, (WRegion
*)ws
);
496 frame
=OBJ_CAST(reg
, WFrame
);
498 if(framemode_unalt(frame_mode(frame
))!=FRAME_MODE_TILED
)
499 frame_set_mode(frame
, FRAME_MODE_TILED
);
502 if(REGION_IS_MAPPED(ws
))
505 if(region_may_control_focus((WRegion
*)ws
)){
506 WRegion
*curr
=tiling_current(ws
);
507 if(curr
==NULL
|| !REGION_IS_ACTIVE(curr
))
515 bool tiling_managed_add(WTiling
*ws
, WRegion
*reg
)
518 CALL_DYN_RET(ret
, bool, tiling_managed_add
, ws
, (ws
, reg
));
523 bool tiling_do_attach_initial(WTiling
*ws
, WRegion
*reg
)
525 assert(ws
->split_tree
==NULL
);
527 ws
->split_tree
=(WSplit
*)create_splitregion(®ION_GEOM(reg
), reg
);
528 if(ws
->split_tree
==NULL
)
531 ws
->split_tree
->ws_if_root
=ws
;
533 if(!tiling_managed_add(ws
, reg
)){
534 destroy_obj((Obj
*)ws
->split_tree
);
543 static WRegion
*create_frame_tiling(WWindow
*parent
, const WFitParams
*fp
)
545 return (WRegion
*)create_frame(parent
, fp
, FRAME_MODE_TILED
, "Tiling Frame");
549 bool tiling_init(WTiling
*ws
, WWindow
*parent
, const WFitParams
*fp
,
550 WRegionSimpleCreateFn
*create_frame_fn
, bool ci
)
555 ws
->create_frame_fn
=(create_frame_fn
557 : create_frame_tiling
);
559 ws
->managed_list
=NULL
;
562 ws
->dummywin
=XCreateWindow(ioncore_g
.dpy
, parent
->win
,
563 fp
->g
.x
, fp
->g
.y
, 1, 1, 0,
564 CopyFromParent
, InputOnly
,
565 CopyFromParent
, 0, NULL
);
566 if(ws
->dummywin
==None
)
569 p
[0] = "Notion WTiling dummy window";
570 xwindow_set_text_property(ws
->dummywin
, XA_WM_NAME
, p
, 1);
572 region_init(&(ws
->reg
), parent
, fp
);
574 ws
->reg
.flags
|=(REGION_GRAB_ON_PARENT
|
578 WRegionAttachData data
;
581 data
.type
=REGION_ATTACH_NEW
;
582 data
.u
.n
.fn
=(WRegionCreateFn
*)ws
->create_frame_fn
;
585 res
=region_attach_helper((WRegion
*)ws
, parent
, fp
,
586 (WRegionDoAttachFn
*)tiling_do_attach_initial
,
590 XDestroyWindow(ioncore_g
.dpy
, ws
->dummywin
);
595 XSelectInput(ioncore_g
.dpy
, ws
->dummywin
,
596 FocusChangeMask
|KeyPressMask
|KeyReleaseMask
|
597 ButtonPressMask
|ButtonReleaseMask
);
598 XSaveContext(ioncore_g
.dpy
, ws
->dummywin
, ioncore_g
.win_context
,
601 region_register(&(ws
->reg
));
602 region_add_bindmap((WRegion
*)ws
, mod_tiling_tiling_bindmap
);
608 WTiling
*create_tiling(WWindow
*parent
, const WFitParams
*fp
,
609 WRegionSimpleCreateFn
*create_frame_fn
, bool ci
)
611 CREATEOBJ_IMPL(WTiling
, tiling
, (p
, parent
, fp
, create_frame_fn
, ci
));
615 WTiling
*create_tiling_simple(WWindow
*parent
, const WFitParams
*fp
)
617 return create_tiling(parent
, fp
, NULL
, TRUE
);
621 void tiling_deinit(WTiling
*ws
)
625 WMPlex
*remanage_mplex
=NULL
;
627 tiling_unmanage_stdisp(ws
, FALSE
, TRUE
);
629 FOR_ALL_MANAGED_BY_TILING(reg
, ws
, tmp
){
630 destroy_obj((Obj
*)reg
);
633 FOR_ALL_MANAGED_BY_TILING(reg
, ws
, tmp
){
637 if(ws
->split_tree
!=NULL
)
638 destroy_obj((Obj
*)(ws
->split_tree
));
640 XDeleteContext(ioncore_g
.dpy
, ws
->dummywin
, ioncore_g
.win_context
);
641 XDestroyWindow(ioncore_g
.dpy
, ws
->dummywin
);
644 region_deinit(&(ws
->reg
));
648 WRegion
*tiling_managed_disposeroot(WTiling
*ws
, WRegion
*reg
)
656 FOR_ALL_MANAGED_BY_TILING(mgd
, ws
, tmp
){
657 if(mgd
!=TILING_STDISP_OF(ws
) && mgd
!=reg
)
661 return region_disposeroot((WRegion
*)ws
);
665 bool tiling_rescue_clientwins(WTiling
*ws
, WRescueInfo
*info
)
669 ptrlist_iter_init(&tmp
, ws
->managed_list
);
671 return region_rescue_some_clientwins((WRegion
*)ws
, info
,
672 (WRegionIterator
*)ptrlist_iter
,
677 void tiling_do_managed_remove(WTiling
*ws
, WRegion
*reg
)
679 if(TILING_STDISP_OF(ws
)==reg
){
680 ws
->stdispnode
->regnode
.reg
=NULL
;
682 ptrlist_remove(&(ws
->managed_list
), reg
);
685 region_unset_manager(reg
, (WRegion
*)ws
);
686 splittree_set_node_of(reg
, NULL
);
690 static bool nostdispfilter(WSplit
*node
)
692 return (OBJ_IS(node
, WSplitRegion
) && !OBJ_IS(node
, WSplitST
));
696 void tiling_managed_remove(WTiling
*ws
, WRegion
*reg
)
698 bool act
=REGION_IS_ACTIVE(reg
);
699 bool mcf
=region_may_control_focus((WRegion
*)ws
);
700 WSplitRegion
*node
=get_node_check(ws
, reg
);
701 bool norestore
=(OBJ_IS_BEING_DESTROYED(ws
) || ws
->batchop
);
705 other
=tiling_do_navi_next(ws
, reg
, REGION_NAVI_ANY
, TRUE
, FALSE
);
707 tiling_do_managed_remove(ws
, reg
);
709 if(node
==(WSplitRegion
*)(ws
->stdispnode
))
715 if(other
==NULL
&& !norestore
){
716 WWindow
*par
=REGION_PARENT(ws
);
721 fp
.g
=node
->split
.geom
;
722 fp
.mode
=REGION_FIT_EXACT
;
724 other
=(ws
->create_frame_fn
)(par
, &fp
);
728 splittree_set_node_of(other
, node
);
729 tiling_managed_add(ws
, other
);
732 warn(TR("Tiling in useless state."));
737 splittree_remove((WSplit
*)node
, (!norestore
&& other
!=NULL
));
740 if(!norestore
&& other
!=NULL
&& act
&& mcf
)
745 static bool mplexfilter(WSplit
*node
)
747 WSplitRegion
*regnode
=OBJ_CAST(node
, WSplitRegion
);
749 return (regnode
!=NULL
&& regnode
->reg
!=NULL
&&
750 OBJ_IS(regnode
->reg
, WMPlex
));
754 static WPHolder
*find_ph_result
=NULL
;
755 static WRegion
*find_ph_param
=NULL
;
758 static bool find_ph(WSplit
*split
)
760 WSplitRegion
*sr
=OBJ_CAST(split
, WSplitRegion
);
762 assert(find_ph_result
==NULL
);
764 if(sr
==NULL
|| sr
->reg
==NULL
)
767 find_ph_result
=region_get_rescue_pholder_for(sr
->reg
, find_ph_param
);
769 return (find_ph_result
!=NULL
);
773 WPHolder
*tiling_get_rescue_pholder_for(WTiling
*ws
, WRegion
*mgd
)
775 WSplit
*node
=(WSplit
*)get_node_check(ws
, mgd
);
782 if(ws
->split_tree
!=NULL
){
783 split_current_todir(ws
->split_tree
, PRIMN_ANY
, PRIMN_ANY
,
788 split_nextto(node
, PRIMN_ANY
, PRIMN_ANY
, find_ph
);
789 if(find_ph_result
!=NULL
)
791 node
=(WSplit
*)node
->parent
;
809 static void navi_to_primn(WRegionNavi nh
, WPrimn
*hprimn
, WPrimn
*vprimn
,
812 /* choice should be PRIMN_ANY or PRIMN_NONE */
815 case REGION_NAVI_BEG
:
820 case REGION_NAVI_END
:
825 case REGION_NAVI_LEFT
:
830 case REGION_NAVI_RIGHT
:
835 case REGION_NAVI_TOP
:
840 case REGION_NAVI_BOTTOM
:
846 case REGION_NAVI_ANY
:
854 static WRegion
*node_reg(WSplit
*node
)
856 WSplitRegion
*rnode
=OBJ_CAST(node
, WSplitRegion
);
857 return (rnode
!=NULL
? rnode
->reg
: NULL
);
861 WRegion
*tiling_do_navi_next(WTiling
*ws
, WRegion
*reg
,
862 WRegionNavi nh
, bool nowrap
,
865 WSplitFilter
*filter
=(any
? NULL
: nostdispfilter
);
866 WPrimn hprimn
, vprimn
;
869 navi_to_primn(nh
, &hprimn
, &vprimn
, PRIMN_NONE
);
872 reg
=tiling_current(ws
);
875 WSplitRegion
*node
=get_node_check(ws
, reg
);
877 nxt
=node_reg(split_nextto((WSplit
*)node
, hprimn
, vprimn
,
882 if(nxt
==NULL
&& !nowrap
){
883 nxt
=node_reg(split_current_todir(ws
->split_tree
,
884 primn_none2any(primn_invert(hprimn
)),
885 primn_none2any(primn_invert(vprimn
)),
893 WRegion
*tiling_do_navi_first(WTiling
*ws
, WRegionNavi nh
, bool any
)
895 WSplitFilter
*filter
=(any
? NULL
: nostdispfilter
);
896 WPrimn hprimn
, vprimn
;
898 navi_to_primn(nh
, &hprimn
, &vprimn
, PRIMN_ANY
);
900 return node_reg(split_current_todir(ws
->split_tree
,
901 hprimn
, vprimn
, filter
));
905 WRegion
*tiling_navi_next(WTiling
*ws
, WRegion
*reg
,
906 WRegionNavi nh
, WRegionNaviData
*data
)
908 WRegion
*nxt
=tiling_do_navi_next(ws
, reg
, nh
, TRUE
, FALSE
);
910 return region_navi_cont(&ws
->reg
, nxt
, data
);
914 WRegion
*tiling_navi_first(WTiling
*ws
, WRegionNavi nh
,
915 WRegionNaviData
*data
)
917 WRegion
*reg
=tiling_do_navi_first(ws
, nh
, FALSE
);
919 return region_navi_cont(&ws
->reg
, reg
, data
);
926 /*{{{ Split/unsplit */
929 static bool get_split_dir_primn(const char *str
, int *dir
, int *primn
)
931 WPrimn hprimn
, vprimn
;
934 if(!ioncore_string_to_navi(str
, &nh
))
937 navi_to_primn(nh
, &hprimn
, &vprimn
, PRIMN_NONE
);
939 if(hprimn
==PRIMN_NONE
){
942 }else if(vprimn
==PRIMN_NONE
){
943 *dir
=SPLIT_HORIZONTAL
;
946 warn(TR("Invalid direction"));
954 static bool get_split_dir_primn_float(const char *str
, int *dir
, int *primn
,
957 if(strncmp(str
, "floating:", 9)==0){
959 return get_split_dir_primn(str
+9, dir
, primn
);
962 return get_split_dir_primn(str
, dir
, primn
);
967 #define SPLIT_MINS 16 /* totally arbitrary */
970 static WFrame
*tiling_do_split(WTiling
*ws
, WSplit
*node
,
971 const char *dirstr
, int minw
, int minh
)
973 int dir
, primn
, mins
;
978 if(node
==NULL
|| ws
->split_tree
==NULL
){
979 warn(TR("Invalid node."));
983 if(!get_split_dir_primn_float(dirstr
, &dir
, &primn
, &floating
))
986 mins
=(dir
==SPLIT_VERTICAL
? minh
: minw
);
989 nnode
=splittree_split(node
, dir
, primn
, mins
,
993 nnode
=splittree_split_floating(node
, dir
, primn
, mins
,
994 ws
->create_frame_fn
, ws
);
998 warn(TR("Unable to split."));
1002 /* We must restack here to ensure the split tree is stacked in the
1005 if(ws
->split_tree
!=NULL
)
1006 split_restack(ws
->split_tree
, ws
->dummywin
, Above
);
1008 newframe
=OBJ_CAST(nnode
->reg
, WFrame
);
1009 assert(newframe
!=NULL
);
1011 if(!tiling_managed_add(ws
, nnode
->reg
)){
1013 destroy_obj((Obj
*)nnode
);
1014 destroy_obj((Obj
*)newframe
);
1023 * Create a new frame on \var{ws} \codestr{above}, \codestr{below}
1024 * \codestr{left} of, or \codestr{right} of \var{node} as indicated
1025 * by \var{dirstr}. If \var{dirstr} is prefixed with
1026 * \codestr{floating:} a floating split is created.
1029 WFrame
*tiling_split(WTiling
*ws
, WSplit
*node
, const char *dirstr
)
1031 if(!check_node(ws
, node
))
1034 return tiling_do_split(ws
, node
, dirstr
,
1035 SPLIT_MINS
, SPLIT_MINS
);
1040 * Same as \fnref{WTiling.split} at the root of the split tree.
1043 WFrame
*tiling_split_top(WTiling
*ws
, const char *dirstr
)
1045 return tiling_do_split(ws
, ws
->split_tree
, dirstr
,
1046 SPLIT_MINS
, SPLIT_MINS
);
1051 * Split \var{frame} creating a new frame to direction \var{dirstr}
1052 * (one of \codestr{left}, \codestr{right}, \codestr{top} or
1053 * \codestr{bottom}) of \var{frame}.
1054 * If \var{attach_current} is set, the region currently displayed in
1055 * \var{frame}, if any, is moved to thenew frame.
1056 * If \var{dirstr} is prefixed with \codestr{floating:}, a floating
1060 WFrame
*tiling_split_at(WTiling
*ws
, WFrame
*frame
, const char *dirstr
,
1061 bool attach_current
)
1070 node
=get_node_check(ws
, (WRegion
*)frame
);
1072 newframe
=tiling_do_split(ws
, (WSplit
*)node
, dirstr
,
1073 region_min_w((WRegion
*)frame
),
1074 region_min_h((WRegion
*)frame
));
1079 curr
=mplex_mx_current(&(frame
->mplex
));
1081 if(attach_current
&& curr
!=NULL
)
1082 mplex_attach_simple(&(newframe
->mplex
), curr
, MPLEX_ATTACH_SWITCHTO
);
1084 if(region_may_control_focus((WRegion
*)frame
))
1085 region_goto((WRegion
*)newframe
);
1092 * Try to relocate regions managed by \var{reg} to another frame
1093 * and, if possible, destroy it.
1096 void tiling_unsplit_at(WTiling
*ws
, WRegion
*reg
)
1100 if(reg
==NULL
|| REGION_MANAGER(reg
)!=(WRegion
*)ws
)
1103 ph
=region_get_rescue_pholder_for((WRegion
*)ws
, reg
);
1106 region_rescue(reg
, ph
, REGION_RESCUE_NODEEP
|REGION_RESCUE_PHFLAGS_OK
);
1107 destroy_obj((Obj
*)ph
);
1110 region_defer_rqdispose(reg
);
1117 /*{{{ Navigation etc. exports */
1120 WRegion
*tiling_current(WTiling
*ws
)
1122 WSplitRegion
*node
=NULL
;
1123 if(ws
->split_tree
!=NULL
){
1124 node
=(WSplitRegion
*)split_current_todir(ws
->split_tree
,
1125 PRIMN_ANY
, PRIMN_ANY
, NULL
);
1127 return (node
? node
->reg
: NULL
);
1132 * Iterate over managed regions of \var{ws} until \var{iterfn} returns
1134 * The function is called in protected mode.
1135 * This routine returns \code{true} if it reaches the end of list
1136 * without this happening.
1140 bool tiling_managed_i(WTiling
*ws
, ExtlFn iterfn
)
1144 ptrlist_iter_init(&tmp
, ws
->managed_list
);
1146 return extl_iter_objlist_(iterfn
, (ObjIterator
*)ptrlist_iter
, &tmp
);
1151 * Returns the root of the split tree.
1155 WSplit
*tiling_split_tree(WTiling
*ws
)
1157 return ws
->split_tree
;
1162 * Return the most previously active region next to \var{reg} in
1163 * direction \var{dirstr} (\codestr{left}, \codestr{right}, \codestr{up},
1164 * or \codestr{down}). The region \var{reg}
1165 * must be managed by \var{ws}. If \var{any} is not set, the status display
1166 * is not considered.
1170 WRegion
*tiling_nextto(WTiling
*ws
, WRegion
*reg
, const char *dirstr
,
1175 if(!ioncore_string_to_navi(dirstr
, &nh
))
1178 return tiling_do_navi_next(ws
, reg
, nh
, FALSE
, any
);
1183 * Return the most previously active region on \var{ws} with no
1184 * other regions next to it in direction \var{dirstr}
1185 * (\codestr{left}, \codestr{right}, \codestr{up}, or \codestr{down}).
1186 * If \var{any} is not set, the status display is not considered.
1190 WRegion
*tiling_farthest(WTiling
*ws
, const char *dirstr
, bool any
)
1194 if(!ioncore_string_to_navi(dirstr
, &nh
))
1197 return tiling_do_navi_first(ws
, nh
, any
);
1202 * For region \var{reg} managed by \var{ws} return the \type{WSplit}
1203 * a leaf of which \var{reg} is.
1207 WSplitRegion
*tiling_node_of(WTiling
*ws
, WRegion
*reg
)
1210 warn(TR("Nil parameter."));
1214 if(REGION_MANAGER(reg
)!=(WRegion
*)ws
){
1215 warn(TR("Manager doesn't match."));
1219 return splittree_node_of(reg
);
1226 /*{{{ Flip and transpose */
1229 static WSplitSplit
*get_at_split(WTiling
*ws
, WRegion
*reg
)
1235 split
=OBJ_CAST(ws
->split_tree
, WSplitSplit
);
1238 else if(split
->br
==(WSplit
*)ws
->stdispnode
)
1239 return OBJ_CAST(split
->tl
, WSplitSplit
);
1240 else if(split
->tl
==(WSplit
*)ws
->stdispnode
)
1241 return OBJ_CAST(split
->br
, WSplitSplit
);
1246 node
=(WSplit
*)get_node_check(ws
, reg
);
1251 if(node
==(WSplit
*)ws
->stdispnode
){
1252 warn(TR("The status display is not a valid parameter for "
1257 split
=OBJ_CAST(node
->parent
, WSplitSplit
);
1259 if(split
!=NULL
&& (split
->tl
==(WSplit
*)ws
->stdispnode
||
1260 split
->br
==(WSplit
*)ws
->stdispnode
)){
1261 split
=OBJ_CAST(((WSplit
*)split
)->parent
, WSplitSplit
);
1269 * Flip \var{ws} at \var{reg} or root if nil.
1272 bool iowns_flip_at(WTiling
*ws
, WRegion
*reg
)
1274 WSplitSplit
*split
=get_at_split(ws
, reg
);
1279 splitsplit_flip(split
);
1286 * Transpose \var{ws} at \var{reg} or root if nil.
1289 bool iowns_transpose_at(WTiling
*ws
, WRegion
*reg
)
1291 WSplitSplit
*split
=get_at_split(ws
, reg
);
1296 split_transpose((WSplit
*)split
);
1305 /*{{{ Floating toggle */
1308 static void replace(WSplitSplit
*split
, WSplitSplit
*nsplit
)
1310 WSplitInner
*psplit
=split
->isplit
.split
.parent
;
1312 nsplit
->tl
=split
->tl
;
1314 nsplit
->tl
->parent
=(WSplitInner
*)nsplit
;
1316 nsplit
->br
=split
->br
;
1318 nsplit
->br
->parent
=(WSplitInner
*)nsplit
;
1321 splitinner_replace((WSplitInner
*)psplit
, (WSplit
*)split
,
1324 splittree_changeroot((WSplit
*)split
, (WSplit
*)nsplit
);
1329 WSplitSplit
*tiling_set_floating(WTiling
*ws
, WSplitSplit
*split
, int sp
)
1331 bool set
=OBJ_IS(split
, WSplitFloat
);
1332 bool nset
=libtu_do_setparam(sp
, set
);
1333 const WRectangle
*g
=&((WSplit
*)split
)->geom
;
1340 ns
=(WSplitSplit
*)create_splitfloat(g
, ws
, split
->dir
);
1342 if(OBJ_IS(split
->tl
, WSplitST
) || OBJ_IS(split
->br
, WSplitST
)){
1343 warn(TR("Refusing to float split directly containing the "
1344 "status display."));
1347 ns
=create_splitsplit(g
, split
->dir
);
1352 split_resize((WSplit
*)ns
, g
, PRIMN_ANY
, PRIMN_ANY
);
1353 mainloop_defer_destroy((Obj
*)split
);
1361 * Toggle floating of a split's sides at \var{split} as indicated by the
1362 * parameter \var{how} (\codestr{set}, \codestr{unset}, or \codestr{toggle}).
1363 * A split of the appropriate is returned, if there was a change.
1365 EXTL_EXPORT_AS(WTiling
, set_floating
)
1366 WSplitSplit
*tiling_set_floating_extl(WTiling
*ws
, WSplitSplit
*split
,
1369 if(!check_node(ws
, (WSplit
*)split
))
1371 return tiling_set_floating(ws
, split
, libtu_string_to_setparam(how
));
1376 * Toggle floating of the sides of a split containin \var{reg} as indicated
1377 * by the parameters \var{how} (\codestr{set}, \codestr{unset}, or
1378 * \codestr{toggle}) and \var{dirstr} (\codestr{left}, \codestr{right},
1379 * \codestr{up}, or \codestr{down}). The new status is returned
1380 * (and \code{false} also on error).
1382 EXTL_EXPORT_AS(WTiling
, set_floating_at
)
1383 bool tiling_set_floating_at_extl(WTiling
*ws
, WRegion
*reg
, const char *how
,
1386 WPrimn hprimn
=PRIMN_ANY
, vprimn
=PRIMN_ANY
;
1387 WSplitSplit
*split
, *nsplit
;
1390 node
=(WSplit
*)get_node_check(ws
, reg
);
1398 if(!ioncore_string_to_navi(dirstr
, &nh
))
1401 navi_to_primn(nh
, &hprimn
, &vprimn
, PRIMN_NONE
);
1405 split
=OBJ_CAST(node
->parent
, WSplitSplit
);
1407 warn(TR("No suitable split here."));
1411 if(!OBJ_IS(split
->tl
, WSplitST
) && !OBJ_IS(split
->br
, WSplitST
)){
1412 WPrimn tmp
=(split
->dir
==SPLIT_VERTICAL
? vprimn
: hprimn
);
1414 || (node
==split
->tl
&& tmp
==PRIMN_BR
)
1415 || (node
==split
->br
&& tmp
==PRIMN_TL
)){
1420 node
=(WSplit
*)split
;
1423 nsplit
=tiling_set_floating(ws
, split
, libtu_string_to_setparam(how
));
1425 return OBJ_IS((Obj
*)(nsplit
==NULL
? split
: nsplit
), WSplitFloat
);
1435 ExtlTab
tiling_get_configuration(WTiling
*ws
)
1437 ExtlTab tab
, split_tree
=extl_table_none();
1439 tab
=region_get_base_configuration((WRegion
*)ws
);
1441 if(ws
->split_tree
!=NULL
){
1442 if(!split_get_config(ws
->split_tree
, &split_tree
))
1443 warn(TR("Could not get split tree."));
1446 extl_table_sets_t(tab
, "split_tree", split_tree
);
1447 extl_unref_table(split_tree
);
1459 WSplit
*load_splitst(WTiling
*ws
, const WRectangle
*geom
, ExtlTab tab
)
1463 if(ws
->stdispnode
!=NULL
){
1464 warn(TR("Workspace already has a status display node."));
1468 st
=create_splitst(geom
, NULL
);
1474 static bool do_attach(WTiling
*ws
, WRegion
*reg
, void *p
)
1476 WSplitRegion
*node
=create_splitregion(®ION_GEOM(reg
), reg
);
1481 if(!tiling_managed_add(ws
, reg
)){
1483 destroy_obj((Obj
*)node
);
1487 *(WSplitRegion
**)p
=node
;
1493 WSplit
*load_splitregion(WTiling
*ws
, const WRectangle
*geom
, ExtlTab tab
)
1495 WWindow
*par
=REGION_PARENT(ws
);
1496 WRegionAttachData data
;
1501 if(!extl_table_gets_t(tab
, "regparams", &rt
)){
1502 warn(TR("Missing region parameters."));
1506 data
.type
=REGION_ATTACH_LOAD
;
1511 fp
.mode
=REGION_FIT_EXACT
;
1513 region_attach_helper((WRegion
*)ws
, par
, &fp
,
1514 (WRegionDoAttachFn
*)do_attach
, &node
, &data
);
1516 extl_unref_table(rt
);
1524 WSplit
*load_splitsplit(WTiling
*ws
, const WRectangle
*geom
, ExtlTab tab
)
1526 WSplit
*tl
=NULL
, *br
=NULL
;
1534 set
+=(extl_table_gets_i(tab
, "tls", &tls
)==TRUE
);
1535 set
+=(extl_table_gets_i(tab
, "brs", &brs
)==TRUE
);
1536 set
+=(extl_table_gets_s(tab
, "dir", &dir_str
)==TRUE
);
1541 if(strcmp(dir_str
, "vertical")==0){
1543 }else if(strcmp(dir_str
, "horizontal")==0){
1544 dir
=SPLIT_HORIZONTAL
;
1546 warn(TR("Invalid direction."));
1552 split
=create_splitsplit(geom
, dir
);
1556 tls
=maxof(tls
, MINS
);
1557 brs
=maxof(brs
, MINS
);
1560 if(dir
==SPLIT_HORIZONTAL
){
1561 tls
=maxof(0, geom
->w
)*tls
/(tls
+brs
);
1564 tls
=maxof(0, geom
->h
)*tls
/(tls
+brs
);
1568 if(extl_table_gets_t(tab
, "tl", &subtab
)){
1569 tl
=tiling_load_node(ws
, &geom2
, subtab
);
1570 extl_unref_table(subtab
);
1574 if(dir
==SPLIT_HORIZONTAL
){
1582 if(extl_table_gets_t(tab
, "br", &subtab
)){
1583 br
=tiling_load_node(ws
, &geom2
, subtab
);
1584 extl_unref_table(subtab
);
1587 if(tl
==NULL
|| br
==NULL
){
1588 /* PRIMN_TL/BR instead of ANY because of stdisp. */
1589 destroy_obj((Obj
*)split
);
1591 split_do_resize(tl
, geom
, PRIMN_BR
, PRIMN_BR
, FALSE
);
1595 split_do_resize(br
, geom
, PRIMN_TL
, PRIMN_TL
, FALSE
);
1601 tl
->parent
=(WSplitInner
*)split
;
1602 br
->parent
=(WSplitInner
*)split
;
1604 /*split->tmpsize=tls;*/
1608 return (WSplit
*)split
;
1612 WSplit
*tiling_load_node_default(WTiling
*ws
, const WRectangle
*geom
,
1618 extl_table_gets_s(tab
, "type", &typestr
);
1621 warn(TR("No split type given."));
1625 if(strcmp(typestr
, "WSplitRegion")==0)
1626 node
=load_splitregion(ws
, geom
, tab
);
1627 else if(strcmp(typestr
, "WSplitSplit")==0)
1628 node
=load_splitsplit(ws
, geom
, tab
);
1629 else if(strcmp(typestr
, "WSplitFloat")==0)
1630 node
=load_splitfloat(ws
, geom
, tab
);
1631 else if(strcmp(typestr
, "WSplitST")==0)
1632 node
=NULL
;/*load_splitst(ws, geom, tab);*/
1634 warn(TR("Unknown split type."));
1642 WSplit
*tiling_load_node(WTiling
*ws
, const WRectangle
*geom
, ExtlTab tab
)
1645 CALL_DYN_RET(ret
, WSplit
*, tiling_load_node
, ws
, (ws
, geom
, tab
));
1651 WRegion
*tiling_load(WWindow
*par
, const WFitParams
*fp
, ExtlTab tab
)
1657 if(extl_table_gets_t(tab
, "split_tree", &treetab
))
1660 ws
=create_tiling(par
, fp
, NULL
, ci
);
1664 extl_unref_table(treetab
);
1669 ws
->split_tree
=tiling_load_node(ws
, ®ION_GEOM(ws
), treetab
);
1670 extl_unref_table(treetab
);
1673 if(ws
->split_tree
==NULL
){
1674 warn(TR("The workspace is empty."));
1675 destroy_obj((Obj
*)ws
);
1679 ws
->split_tree
->ws_if_root
=ws
;
1680 split_restack(ws
->split_tree
, ws
->dummywin
, Above
);
1682 return (WRegion
*)ws
;
1689 /*{{{ Dynamic function table and class implementation */
1692 static DynFunTab tiling_dynfuntab
[]={
1699 {region_do_set_focus
,
1700 tiling_do_set_focus
},
1702 {(DynFun
*)region_fitrep
,
1703 (DynFun
*)tiling_fitrep
},
1705 {region_managed_rqgeom
,
1706 tiling_managed_rqgeom
},
1708 {(DynFun
*)region_managed_maximize
,
1709 (DynFun
*)tiling_managed_maximize
},
1711 {region_managed_remove
,
1712 tiling_managed_remove
},
1714 {(DynFun
*)region_managed_prepare_focus
,
1715 (DynFun
*)tiling_managed_prepare_focus
},
1717 {(DynFun
*)region_prepare_manage
,
1718 (DynFun
*)tiling_prepare_manage
},
1720 {(DynFun
*)region_rescue_clientwins
,
1721 (DynFun
*)tiling_rescue_clientwins
},
1723 {(DynFun
*)region_get_rescue_pholder_for
,
1724 (DynFun
*)tiling_get_rescue_pholder_for
},
1726 {(DynFun
*)region_get_configuration
,
1727 (DynFun
*)tiling_get_configuration
},
1729 {(DynFun
*)region_managed_disposeroot
,
1730 (DynFun
*)tiling_managed_disposeroot
},
1732 {(DynFun
*)region_current
,
1733 (DynFun
*)tiling_current
},
1735 {(DynFun
*)tiling_managed_add
,
1736 (DynFun
*)tiling_managed_add_default
},
1738 {region_manage_stdisp
,
1739 tiling_manage_stdisp
},
1741 {region_unmanage_stdisp
,
1742 tiling_unmanage_stdisp
},
1744 {(DynFun
*)tiling_load_node
,
1745 (DynFun
*)tiling_load_node_default
},
1753 {(DynFun
*)region_navi_first
,
1754 (DynFun
*)tiling_navi_first
},
1756 {(DynFun
*)region_navi_next
,
1757 (DynFun
*)tiling_navi_next
},
1759 {(DynFun
*)region_xwindow
,
1760 (DynFun
*)tiling_xwindow
},
1767 IMPLCLASS(WTiling
, WRegion
, tiling_deinit
, tiling_dynfuntab
);