trunk: changeset 1940
[notion/jeffpc.git] / ioncore / mplex.c
blobccc702bbc39783b375069be17b28e6d8a287f730
1 /*
2 * ion/ioncore/mplex.c
4 * Copyright (c) Tuomo Valkonen 1999-2005.
6 * Ion is free software; you can redistribute it and/or modify it under
7 * the terms of the GNU Lesser General Public License as published by
8 * the Free Software Foundation; either version 2.1 of the License, or
9 * (at your option) any later version.
12 #include <limits.h>
13 #include <string.h>
15 #include <libtu/objp.h>
16 #include <libtu/minmax.h>
17 #include <libtu/rb.h>
18 #include <libextl/extl.h>
19 #include <libmainloop/defer.h>
21 #include "common.h"
22 #include "window.h"
23 #include "global.h"
24 #include "rootwin.h"
25 #include "focus.h"
26 #include "event.h"
27 #include "attach.h"
28 #include "manage.h"
29 #include "resize.h"
30 #include "tags.h"
31 #include "sizehint.h"
32 #include "extlconv.h"
33 #include "genws.h"
34 #include "frame-pointer.h"
35 #include "bindmaps.h"
36 #include "regbind.h"
37 #include "region-iter.h"
38 #include "saveload.h"
39 #include "xwindow.h"
42 #define MPLEX_WIN(MPLEX) ((MPLEX)->win.win)
43 #define MPLEX_MGD_UNVIEWABLE(MPLEX) \
44 ((MPLEX)->flags&MPLEX_MANAGED_UNVIEWABLE)
47 /*{{{ Destroy/create mplex */
50 bool mplex_do_init(WMPlex *mplex, WWindow *parent, Window win,
51 const WFitParams *fp, bool create)
53 mplex->flags=0;
54 mplex->l1_count=0;
55 mplex->l1_list=NULL;
56 mplex->l1_current=NULL;
57 mplex->l2_count=0;
58 mplex->l2_list=NULL;
59 mplex->l2_current=NULL;
60 watch_init(&(mplex->stdispinfo.regwatch));
61 mplex->stdispinfo.pos=MPLEX_STDISP_BL;
63 if(create){
64 if(!window_init((WWindow*)mplex, parent, fp))
65 return FALSE;
66 }else{
67 if(!window_do_init((WWindow*)mplex, parent, win, fp))
68 return FALSE;
71 mplex->win.region.flags|=REGION_BINDINGS_ARE_GRABBED;
73 XSelectInput(ioncore_g.dpy, MPLEX_WIN(mplex), IONCORE_EVENTMASK_CWINMGR);
75 region_add_bindmap((WRegion*)mplex, ioncore_mplex_bindmap);
77 /* Call this to set MPLEX_MANAGED_UNVIEWABLE if necessary. */
78 mplex_fit_managed(mplex);
80 return TRUE;
84 bool mplex_init(WMPlex *mplex, WWindow *parent, const WFitParams *fp)
86 return mplex_do_init(mplex, parent, None, fp, TRUE);
90 WMPlex *create_mplex(WWindow *parent, const WFitParams *fp)
92 CREATEOBJ_IMPL(WMPlex, mplex, (p, parent, fp));
96 void mplex_deinit(WMPlex *mplex)
98 WRegion *reg, *next;
100 FOR_ALL_MANAGED_ON_LIST_W_NEXT(mplex->l1_list, reg, next){
101 destroy_obj((Obj*)reg);
104 FOR_ALL_MANAGED_ON_LIST_W_NEXT(mplex->l2_list, reg, next){
105 destroy_obj((Obj*)reg);
108 window_deinit((WWindow*)mplex);
112 /*}}}*/
115 /*{{{ Hidden L2 objects RB-tree */
117 #define MGD_L2_HIDDEN 0x0001
118 #define MGD_L2_PASSIVE 0x0002
120 static Rb_node mgd_flag_rb=NULL;
123 static bool mgd_set_flags(WRegion *reg, int flag)
125 Rb_node nd;
127 if(mgd_flag_rb==NULL){
128 mgd_flag_rb=make_rb();
129 if(mgd_flag_rb==NULL)
130 return FALSE;
131 }else{
132 int found=0;
133 nd=rb_find_pkey_n(mgd_flag_rb, reg, &found);
134 if(found){
135 nd->v.ival|=flag;
136 return TRUE;
140 nd=rb_insertp(mgd_flag_rb, reg, NULL);
141 if(nd!=NULL)
142 nd->v.ival=flag;
144 return (nd!=NULL);
148 static void mgd_unset_flags(WRegion *reg, int flag)
150 int found=0;
151 Rb_node nd;
153 if(mgd_flag_rb==NULL)
154 return;
156 nd=rb_find_pkey_n(mgd_flag_rb, reg, &found);
157 if(!found)
158 return;
160 nd->v.ival&=~flag;
162 if(nd->v.ival==0)
163 rb_delete_node(nd);
167 static int mgd_flags(WRegion *reg)
169 int found=0;
170 Rb_node nd;
172 if(mgd_flag_rb==NULL)
173 return 0;
175 nd=rb_find_pkey_n(mgd_flag_rb, reg, &found);
177 return (found ? nd->v.ival : 0);
181 static bool l2_is_hidden(WRegion *reg)
183 return mgd_flags(reg)&MGD_L2_HIDDEN;
187 static bool l2_mark_hidden(WRegion *reg)
189 return mgd_set_flags(reg, MGD_L2_HIDDEN);
193 static void l2_unmark_hidden(WRegion *reg)
195 mgd_unset_flags(reg, MGD_L2_HIDDEN);
199 /*}}}*/
202 /*{{{ Managed list management */
205 static bool on_list(WRegion *list, WRegion *reg)
207 WRegion *reg2;
209 FOR_ALL_MANAGED_ON_LIST(list, reg2){
210 if(reg2==reg)
211 return TRUE;
214 return FALSE;
218 static bool on_l1_list(WMPlex *mplex, WRegion *reg)
220 return on_list(mplex->l1_list, reg);
224 static bool on_l2_list(WMPlex *mplex, WRegion *reg)
226 return on_list(mplex->l2_list, reg);
230 static WRegion *nth_on_list(WRegion *list, uint n)
232 WRegion *reg=REGION_FIRST_MANAGED(list);
234 while(n-->0 && reg!=NULL)
235 reg=REGION_NEXT_MANAGED(list, reg);
237 return reg;
241 WRegion *mplex_current(WMPlex *mplex)
243 return (mplex->l2_current!=NULL ? mplex->l2_current : mplex->l1_current);
247 /*EXTL_DOC
248 * Returns the layer \var{reg} is on \var{mplex} or $-1$ if \var{reg}
249 * is not managed by \var{mplex}.
251 EXTL_EXPORT_MEMBER
252 int mplex_layer(WMPlex *mplex, WRegion *reg)
254 if(on_l1_list(mplex, reg))
255 return 1;
256 if(on_l2_list(mplex, reg))
257 return 2;
258 return -1;
262 /*EXTL_DOC
263 * Return the managed object currently active within layer \var{l} of
264 * \var{mplex}.
266 EXTL_EXPORT_MEMBER
267 WRegion *mplex_lcurrent(WMPlex *mplex, uint l)
269 return (l==1
270 ? mplex->l1_current
271 : (l==2
272 ? mplex->l2_current
273 : 0));
277 /*EXTL_DOC
278 * Returns the \var{n}:th object managed by \var{mplex} on the the
279 * \var{l}:th layer..
281 EXTL_EXPORT_MEMBER
282 WRegion *mplex_lnth(WMPlex *mplex, uint l, uint n)
284 return (l==1
285 ? nth_on_list(mplex->l1_list, n)
286 : (l==2
287 ? nth_on_list(mplex->l2_list, n)
288 : 0));
292 /*EXTL_DOC
293 * Returns a list of regions managed by \var{mplex} on layer \var{l}.
295 EXTL_EXPORT_MEMBER
296 ExtlTab mplex_llist(WMPlex *mplex, uint l)
298 return (l==1
299 ? managed_list_to_table(mplex->l1_list, NULL)
300 : (l==2
301 ? managed_list_to_table(mplex->l2_list, NULL)
302 : extl_table_none()));
306 /*EXTL_DOC
307 * Returns the number of regions managed by \var{mplex} on layer \var{l}.
309 EXTL_EXPORT_MEMBER
310 int mplex_lcount(WMPlex *mplex, uint l)
312 return (l==1
313 ? mplex->l1_count
314 : (l==2
315 ? mplex->l2_count
316 : 0));
320 static void link_at(WMPlex *mplex, WRegion *reg, int index)
322 WRegion *after=NULL;
324 if(index>0){
325 after=mplex_lnth(mplex, 1, index-1);
326 }else if(index<0){
327 if(!(mplex->flags&MPLEX_ADD_TO_END) &&
328 ioncore_g.opmode!=IONCORE_OPMODE_INIT){
329 after=mplex->l1_current;
333 if(after==reg)
334 after=NULL;
336 if(after!=NULL){
337 LINK_ITEM_AFTER(mplex->l1_list, after, reg, mgr_next, mgr_prev);
338 }else if(index==0){
339 LINK_ITEM_FIRST(mplex->l1_list, reg, mgr_next, mgr_prev);
340 }else{
341 LINK_ITEM(mplex->l1_list, reg, mgr_next, mgr_prev);
346 /*EXTL_DOC
347 * Set index of \var{reg} within the multiplexer to \var{index}.
349 EXTL_EXPORT_MEMBER
350 void mplex_set_index(WMPlex *mplex, WRegion *reg, int index)
352 if(index<0 || reg==NULL)
353 return;
355 if(!on_l1_list(mplex, reg))
356 return;
358 UNLINK_ITEM(mplex->l1_list, reg, mgr_next, mgr_prev);
359 link_at(mplex, reg, index);
360 mplex_managed_changed(mplex, MPLEX_CHANGE_REORDER, FALSE, reg);
364 /*EXTL_DOC
365 * Get index of \var{reg} within the multiplexer. The first region managed
366 * by \var{mplex} has index zero. If \var{reg} is not managed by \var{mplex},
367 * -1 is returned.
369 EXTL_EXPORT_MEMBER
370 int mplex_get_index(WMPlex *mplex, WRegion *reg)
372 WRegion *other;
373 int index=0;
375 FOR_ALL_MANAGED_ON_LIST(mplex->l1_list, other){
376 if(reg==other)
377 return index;
378 index++;
381 return -1;
385 /*EXTL_DOC
386 * Move \var{r} ''right'' within objects managed by \var{mplex}.
388 EXTL_EXPORT_MEMBER
389 void mplex_inc_index(WMPlex *mplex, WRegion *r)
391 if(r==NULL)
392 r=mplex_lcurrent(mplex, 1);
393 if(r!=NULL)
394 mplex_set_index(mplex, r, mplex_get_index(mplex, r)+1);
398 /*EXTL_DOC
399 * Move \var{r} ''right'' within objects managed by \var{mplex}.
401 EXTL_EXPORT_MEMBER
402 void mplex_dec_index(WMPlex *mplex, WRegion *r)
404 if(r==NULL)
405 r=mplex_lcurrent(mplex, 1);
406 if(r!=NULL)
407 mplex_set_index(mplex, r, mplex_get_index(mplex, r)-1);
411 /*}}}*/
414 /*{{{ Mapping */
417 static void mplex_map_mgd(WMPlex *mplex)
419 WRegion *reg;
421 if(mplex->l1_current!=NULL)
422 region_map(mplex->l1_current);
424 FOR_ALL_MANAGED_ON_LIST(mplex->l2_list, reg){
425 if(!l2_is_hidden(reg))
426 region_map(reg);
431 static void mplex_unmap_mgd(WMPlex *mplex)
433 WRegion *reg;
435 if(mplex->l1_current!=NULL)
436 region_unmap(mplex->l1_current);
438 FOR_ALL_MANAGED_ON_LIST(mplex->l2_list, reg){
439 region_unmap(reg);
445 void mplex_map(WMPlex *mplex)
447 window_map((WWindow*)mplex);
448 /* A lame requirement of the ICCCM is that client windows should be
449 * unmapped if the parent is unmapped.
451 if(!MPLEX_MGD_UNVIEWABLE(mplex))
452 mplex_map_mgd(mplex);
456 void mplex_unmap(WMPlex *mplex)
458 window_unmap((WWindow*)mplex);
459 /* A lame requirement of the ICCCM is that client windows should be
460 * unmapped if the parent is unmapped.
462 if(!MPLEX_MGD_UNVIEWABLE(mplex))
463 mplex_unmap_mgd(mplex);
467 /*}}}*/
470 /*{{{ Resize and reparent */
473 bool mplex_fitrep(WMPlex *mplex, WWindow *par, const WFitParams *fp)
475 bool wchg=(REGION_GEOM(mplex).w!=fp->g.w);
476 bool hchg=(REGION_GEOM(mplex).h!=fp->g.h);
478 window_do_fitrep(&(mplex->win), par, &(fp->g));
480 if(wchg || hchg){
481 mplex_fit_managed(mplex);
482 mplex_size_changed(mplex, wchg, hchg);
485 return TRUE;
489 void mplex_fit_managed(WMPlex *mplex)
491 WRectangle geom;
492 WRegion *sub;
493 WFitParams fp;
495 mplex_managed_geom(mplex, &(fp.g));
497 if(!MPLEX_MGD_UNVIEWABLE(mplex) && (fp.g.w<=1 || fp.g.h<=1)){
498 mplex->flags|=MPLEX_MANAGED_UNVIEWABLE;
499 if(REGION_IS_MAPPED(mplex))
500 mplex_unmap_mgd(mplex);
501 }else if(MPLEX_MGD_UNVIEWABLE(mplex) && !(fp.g.w<=1 || fp.g.h<=1)){
502 mplex->flags&=~MPLEX_MANAGED_UNVIEWABLE;
503 if(REGION_IS_MAPPED(mplex))
504 mplex_map_mgd(mplex);
507 if(!MPLEX_MGD_UNVIEWABLE(mplex)){
508 fp.mode=REGION_FIT_EXACT;
509 FOR_ALL_MANAGED_ON_LIST(mplex->l1_list, sub){
510 region_fitrep(sub, NULL, &fp);
513 fp.mode=REGION_FIT_BOUNDS;
514 FOR_ALL_MANAGED_ON_LIST(mplex->l2_list, sub){
515 region_fitrep(sub, NULL, &fp);
521 static void mplex_managed_rqgeom(WMPlex *mplex, WRegion *sub,
522 int flags, const WRectangle *geom,
523 WRectangle *geomret)
525 WRectangle mg, rg;
527 mplex_managed_geom(mplex, &mg);
529 if(on_l2_list(mplex, sub)){
530 /* allow changes but constrain with managed area */
531 rg=*geom;
532 rectangle_constrain(&rg, &mg);
533 }else{
534 rg=mg;
537 if(geomret!=NULL)
538 *geomret=rg;
540 if(!(flags&REGION_RQGEOM_TRYONLY))
541 region_fit(sub, &rg, REGION_FIT_EXACT);
545 /*}}}*/
548 /*{{{ Focus */
551 void mplex_do_set_focus(WMPlex *mplex, bool warp)
553 bool focset=FALSE;
554 bool warped=FALSE;
556 if(!MPLEX_MGD_UNVIEWABLE(mplex)){
557 if(mplex->l2_current!=NULL){
558 region_do_set_focus(mplex->l2_current, warp);
559 return;
560 }else if(mplex->l1_current!=NULL){
561 region_do_set_focus(mplex->l1_current, warp);
562 return;
566 window_do_set_focus((WWindow*)mplex, warp);
570 /*}}}*/
573 /*{{{ Managed region switching */
576 void mplex_managed_activated(WMPlex *mplex, WRegion *reg)
578 if(on_l2_list(mplex, reg)){
579 mplex->l2_current=reg;
580 }else if(mplex->l2_current!=NULL &&
581 mgd_flags(mplex->l2_current)&MGD_L2_PASSIVE){
582 mplex->l2_current=NULL;
587 /*EXTL_DOC
588 * Is \var{reg} on the layer2 of \var{mplex}, but hidden?
590 EXTL_EXPORT_MEMBER
591 bool mplex_l2_hidden(WMPlex *mplex, WRegion *reg)
593 if(reg==NULL)
594 return FALSE;
596 return (REGION_MANAGER(reg)==(WRegion*)mplex
597 && l2_is_hidden(reg));
601 /*EXTL_DOC
602 * If var \var{reg} is on the l2 list of \var{mplex} and currently shown,
603 * hide it. if \var{reg} is nil, hide all objects on the l2 list.
605 EXTL_EXPORT_MEMBER
606 bool mplex_l2_hide(WMPlex *mplex, WRegion *reg)
608 WRegion *reg2, *toact=NULL;
609 bool mcf=region_may_control_focus((WRegion*)mplex);
611 if(REGION_MANAGER(reg)!=(WRegion*)mplex)
612 return FALSE;
614 if(l2_is_hidden(reg))
615 return FALSE;
617 if(!l2_mark_hidden(reg))
618 return FALSE;
620 if(REGION_IS_MAPPED(mplex) && !MPLEX_MGD_UNVIEWABLE(mplex))
621 region_unmap(reg);
623 if(mplex->l2_current==reg){
624 mplex->l2_current=NULL;
625 FOR_ALL_MANAGED_ON_LIST(mplex->l2_list, reg2){
626 /*if(!l2_is_hidden(reg2))*/
627 if((mgd_flags(reg2)&(MGD_L2_HIDDEN|MGD_L2_PASSIVE))==0)
628 mplex->l2_current=reg2;
632 if(mcf)
633 region_warp((WRegion*)mplex);
635 return TRUE;
639 /*EXTL_DOC
640 * If var \var{reg} is on the l2 list of \var{mplex} and currently hidden,
641 * display it. if \var{reg} is nil, display all objects on the l2 list.
643 EXTL_EXPORT_MEMBER
644 bool mplex_l2_show(WMPlex *mplex, WRegion *reg)
646 WRegion *reg2, *toact=NULL;
647 bool mcf=region_may_control_focus((WRegion*)mplex);
649 if(REGION_MANAGER(reg)!=(WRegion*)mplex)
650 return FALSE;
652 if(!l2_is_hidden(reg))
653 return FALSE;
655 return mplex_managed_goto(mplex, reg, (mcf ? REGION_GOTO_FOCUS : 0));
659 static bool mplex_do_managed_display(WMPlex *mplex, WRegion *sub,
660 bool call_changed)
662 bool l2=FALSE;
663 WRegion *stdisp;
665 if(sub==mplex->l1_current)
666 return mplex->l2_current==NULL;
668 if(sub==mplex->l2_current)
669 return TRUE;
671 if(on_l2_list(mplex, sub))
672 l2=TRUE;
673 else if(!on_l1_list(mplex, sub))
674 return FALSE;
676 stdisp=(WRegion*)(mplex->stdispinfo.regwatch.obj);
678 if(!l2 && OBJ_IS(sub, WGenWS)){
679 if(stdisp!=NULL){
680 WRegion *mgr=REGION_MANAGER(stdisp);
681 if(mgr!=sub){
682 if(OBJ_IS(mgr, WGenWS)){
683 genws_unmanage_stdisp((WGenWS*)mgr, FALSE, FALSE);
684 region_detach_manager(stdisp);
687 genws_manage_stdisp((WGenWS*)sub, stdisp,
688 mplex->stdispinfo.pos);
690 }else{
691 genws_unmanage_stdisp((WGenWS*)sub, TRUE, FALSE);
695 if(REGION_IS_MAPPED(mplex) && !MPLEX_MGD_UNVIEWABLE(mplex))
696 region_map(sub);
697 else
698 region_unmap(sub);
700 if(!l2){
701 if(mplex->l1_current!=NULL && REGION_IS_MAPPED(mplex))
702 region_unmap(mplex->l1_current);
704 mplex->l1_current=sub;
706 /* Many programs will get upset if the visible, although only
707 * such, client window is not the lowest window in the mplex.
708 * xprop/xwininfo will return the information for the lowest
709 * window. 'netscape -remote' will not work at all if there are
710 * no visible netscape windows.
712 if(OBJ_IS(sub, WClientWin))
713 region_lower(sub);
715 /* This call should be unnecessary... */
716 mplex_managed_activated(mplex, sub);
717 }else{
718 int flags=mgd_flags(sub);
719 UNLINK_ITEM(mplex->l2_list, sub, mgr_next, mgr_prev);
720 region_raise(sub);
721 LINK_ITEM(mplex->l2_list, sub, mgr_next, mgr_prev);
722 if(flags&MGD_L2_HIDDEN)
723 l2_unmark_hidden(sub);
724 if(!(flags&MGD_L2_PASSIVE))
725 mplex->l2_current=sub;
728 if(!l2){
729 mplex_managed_changed(mplex, MPLEX_CHANGE_SWITCHONLY, TRUE, sub);
730 return mplex->l2_current==NULL;
731 }else{
732 return mplex->l2_current==sub;
737 static bool mplex_do_managed_goto(WMPlex *mplex, WRegion *sub,
738 bool call_changed, int flags)
740 if(!mplex_do_managed_display(mplex, sub, call_changed))
741 return FALSE;
743 if(flags&REGION_GOTO_FOCUS)
744 region_maybewarp((WRegion*)mplex, !(flags&REGION_GOTO_NOWARP));
746 return TRUE;
750 static bool mplex_do_managed_goto_sw(WMPlex *mplex, WRegion *sub,
751 bool call_changed)
753 bool mcf=region_may_control_focus((WRegion*)mplex);
754 return mplex_do_managed_goto(mplex, sub, FALSE,
755 (mcf ? REGION_GOTO_FOCUS : 0)
756 |REGION_GOTO_NOWARP);
760 bool mplex_managed_goto(WMPlex *mplex, WRegion *sub, int flags)
762 return mplex_do_managed_goto(mplex, sub, TRUE, flags);
766 static void do_switch(WMPlex *mplex, WRegion *sub)
768 if(sub!=NULL){
769 bool mcf=region_may_control_focus((WRegion*)mplex);
770 if(mcf)
771 ioncore_set_previous_of(sub);
772 region_managed_goto((WRegion*)mplex, sub,
773 (mcf ? REGION_GOTO_FOCUS : 0));
778 /*EXTL_DOC
779 * Have \var{mplex} display the \var{n}:th object managed by it.
781 EXTL_EXPORT_MEMBER
782 void mplex_switch_nth(WMPlex *mplex, uint n)
784 do_switch(mplex, mplex_lnth(mplex, 1, n));
788 /*EXTL_DOC
789 * Have \var{mplex} display next (wrt. currently selected) object managed
790 * by it.
792 EXTL_EXPORT_MEMBER
793 void mplex_switch_next(WMPlex *mplex)
795 do_switch(mplex, REGION_NEXT_MANAGED_WRAP(mplex->l1_list,
796 mplex->l1_current));
800 /*EXTL_DOC
801 * Have \var{mplex} display previous (wrt. currently selected) object
802 * managed by it.
804 EXTL_EXPORT_MEMBER
805 void mplex_switch_prev(WMPlex *mplex)
807 do_switch(mplex, REGION_PREV_MANAGED_WRAP(mplex->l1_list,
808 mplex->l1_current));
812 /*}}}*/
815 /*{{{ Attach */
818 typedef struct{
819 int flags;
820 int index;
821 } MPlexAttachParams;
824 static WRegion *mplex_do_attach(WMPlex *mplex, WRegionAttachHandler *hnd,
825 void *hnd_param, MPlexAttachParams *param)
827 WRegion *reg;
828 WFitParams fp;
829 bool sw=param->flags&MPLEX_ATTACH_SWITCHTO;
830 bool l2=param->flags&MPLEX_ATTACH_L2;
832 mplex_managed_geom(mplex, &(fp.g));
833 fp.mode=(l2 ? REGION_FIT_BOUNDS : REGION_FIT_EXACT);
835 reg=hnd((WWindow*)mplex, &fp, hnd_param);
837 if(reg==NULL)
838 return NULL;
840 if(l2){
841 LINK_ITEM(mplex->l2_list, reg, mgr_next, mgr_prev);
842 mplex->l2_count++;
843 }else{
844 link_at(mplex, reg, param->index);
845 mplex->l1_count++;
848 region_set_manager(reg, (WRegion*)mplex, NULL);
850 if(l2){
851 if(!sw)
852 sw=!l2_mark_hidden(reg);
853 if(param->flags&MPLEX_ATTACH_L2_PASSIVE)
854 mgd_set_flags(reg, MGD_L2_PASSIVE);
857 if(!l2 && mplex->l1_count==1)
858 sw=TRUE;
860 if(sw)
861 mplex_do_managed_goto_sw(mplex, reg, FALSE);
862 else
863 region_unmap(reg);
865 if(!l2)
866 mplex_managed_changed(mplex, MPLEX_CHANGE_ADD, sw, reg);
868 return reg;
872 WRegion *mplex_attach_simple(WMPlex *mplex, WRegion *reg, int flags)
874 MPlexAttachParams par;
876 if(reg==(WRegion*)mplex)
877 return FALSE;
879 par.index=-1;
880 par.flags=flags;
882 return region__attach_reparent((WRegion*)mplex, reg,
883 (WRegionDoAttachFn*)mplex_do_attach,
884 &par);
888 WRegion *mplex_attach_hnd(WMPlex *mplex, WRegionAttachHandler *hnd,
889 void *hnd_param, int flags)
891 MPlexAttachParams par;
893 par.index=-1;
894 par.flags=flags;
896 return mplex_do_attach(mplex, hnd, hnd_param, &par);
900 static void get_params(ExtlTab tab, MPlexAttachParams *par)
902 int layer=1;
904 par->flags=0;
905 par->index=-1;
907 extl_table_gets_i(tab, "layer", &layer);
908 if(layer==2)
909 par->flags|=MPLEX_ATTACH_L2;
911 if(extl_table_is_bool_set(tab, "switchto"))
912 par->flags|=MPLEX_ATTACH_SWITCHTO;
915 if(extl_table_is_bool_set(tab, "passive"))
916 par->flags|=MPLEX_ATTACH_L2_PASSIVE;
918 extl_table_gets_i(tab, "index", &(par->index));
922 /*EXTL_DOC
923 * Attach and reparent existing region \var{reg} to \var{mplex}.
924 * The table \var{param} may contain the fields \var{index} and
925 * \var{switchto} that are interpreted as for \fnref{WMPlex.attach_new}.
927 EXTL_EXPORT_MEMBER
928 WRegion *mplex_attach(WMPlex *mplex, WRegion *reg, ExtlTab param)
930 MPlexAttachParams par;
931 get_params(param, &par);
933 /* region__attach_reparent should do better checks. */
934 if(reg==NULL || reg==(WRegion*)mplex)
935 return FALSE;
937 return region__attach_reparent((WRegion*)mplex, reg,
938 (WRegionDoAttachFn*)mplex_do_attach,
939 &par);
943 /*EXTL_DOC
944 * Create a new region to be managed by \var{mplex}. At least the following
945 * fields in \var{param} are understood:
947 * \begin{tabularx}{\linewidth}{lX}
948 * \tabhead{Field & Description}
949 * \var{type} & Class name (a string) of the object to be created. Mandatory. \\
950 * \var{name} & Name of the object to be created (a string). Optional. \\
951 * \var{switchto} & Should the region be switched to (boolean)? Optional. \\
952 * \var{index} & Index of the new region in \var{mplex}'s list of
953 * managed objects (integer, 0 = first). Optional. \\
954 * \var{layer} & Layer to attach on; 1 (default) or 2. \\
955 * \var{passive} & Is a layer 2 object passive/skipped when deciding
956 * object to gives focus to (boolean)? Optional.
957 * \end{tabularx}
959 * In addition parameters to the region to be created are passed in this
960 * same table.
962 EXTL_EXPORT_MEMBER
963 WRegion *mplex_attach_new(WMPlex *mplex, ExtlTab param)
965 MPlexAttachParams par;
966 get_params(param, &par);
968 return region__attach_load((WRegion*)mplex, param,
969 (WRegionDoAttachFn*)mplex_do_attach,
970 &par);
974 /*EXTL_DOC
975 * Attach all tagged regions to \var{mplex}.
977 EXTL_EXPORT_MEMBER
978 void mplex_attach_tagged(WMPlex *mplex)
980 WRegion *reg;
982 while((reg=ioncore_tags_take_first())!=NULL)
983 mplex_attach_simple(mplex, reg, 0);
987 static bool mplex_handle_drop(WMPlex *mplex, int x, int y,
988 WRegion *dropped)
990 WRegion *curr=mplex_lcurrent(mplex, 1);
992 /* This code should handle dropping tabs on floating workspaces. */
993 if(curr && HAS_DYN(curr, region_handle_drop)){
994 int rx, ry;
995 region_rootpos(curr, &rx, &ry);
996 if(rectangle_contains(&REGION_GEOM(curr), x-rx, y-ry)){
997 if(region_handle_drop(curr, x, y, dropped))
998 return TRUE;
1002 return (NULL!=mplex_attach_simple(mplex, dropped, MPLEX_ATTACH_SWITCHTO));
1006 bool mplex_manage_clientwin(WMPlex *mplex, WClientWin *cwin,
1007 const WManageParams *param, int redir)
1009 int swf=(param->switchto ? MPLEX_ATTACH_SWITCHTO : 0);
1011 if(redir==MANAGE_REDIR_STRICT_YES || redir==MANAGE_REDIR_PREFER_YES){
1012 if(mplex->l2_current!=NULL){
1013 if(region_manage_clientwin(mplex->l2_current, cwin, param,
1014 MANAGE_REDIR_PREFER_YES))
1015 return TRUE;
1017 if(mplex->l1_current!=NULL){
1018 if(region_manage_clientwin(mplex->l1_current, cwin, param,
1019 MANAGE_REDIR_PREFER_YES))
1020 return TRUE;
1024 if(redir==MANAGE_REDIR_STRICT_YES)
1025 return FALSE;
1027 return (NULL!=mplex_attach_simple(mplex, (WRegion*)cwin, swf));
1031 bool mplex_manage_rescue(WMPlex *mplex, WClientWin *cwin, WRegion *from)
1033 return (NULL!=mplex_attach_simple(mplex, (WRegion*)cwin, 0));
1037 /*}}}*/
1040 /*{{{ Remove */
1043 void mplex_managed_remove(WMPlex *mplex, WRegion *sub)
1045 WRegion *next=NULL;
1046 bool l2=FALSE;
1047 bool sw=FALSE;
1048 WRegion *stdisp=(WRegion*)(mplex->stdispinfo.regwatch.obj);
1050 if(OBJ_IS(sub, WGenWS) && stdisp!=NULL && REGION_MANAGER(stdisp)==sub){
1051 genws_unmanage_stdisp((WGenWS*)sub, TRUE, TRUE);
1052 region_detach_manager(stdisp);
1055 if(mplex->l1_current==sub){
1056 next=REGION_PREV_MANAGED(mplex->l1_list, sub);
1057 if(next==NULL)
1058 next=REGION_NEXT_MANAGED(mplex->l1_list, sub);
1059 mplex->l1_current=NULL;
1060 sw=TRUE;
1061 }else if(mplex->l2_current==sub){
1062 WRegion *next2;
1063 l2=TRUE;
1064 mplex->l2_current=NULL;
1065 FOR_ALL_MANAGED_ON_LIST(mplex->l2_list, next2){
1066 if(next2!=sub &&
1067 (mgd_flags(next2)&(MGD_L2_HIDDEN|MGD_L2_PASSIVE))==0){
1068 mplex->l2_current=next2;
1071 }else{
1072 l2=on_l2_list(mplex, sub);
1075 mgd_unset_flags(sub, ~0);
1077 if(l2){
1078 region_unset_manager(sub, (WRegion*)mplex, &(mplex->l2_list));
1079 mplex->l2_count--;
1080 }else{
1081 region_unset_manager(sub, (WRegion*)mplex, &(mplex->l1_list));
1082 mplex->l1_count--;
1085 if(OBJ_IS_BEING_DESTROYED(mplex))
1086 return;
1088 if(next!=NULL && sw)
1089 mplex_do_managed_goto_sw(mplex, next, FALSE);
1090 else if(l2 && region_may_control_focus((WRegion*)mplex))
1091 region_warp((WRegion*)mplex);
1093 if(!l2)
1094 mplex_managed_changed(mplex, MPLEX_CHANGE_REMOVE, sw, sub);
1098 bool mplex_rescue_clientwins(WMPlex *mplex)
1100 bool ret1, ret2, ret3;
1102 ret1=region_rescue_managed_clientwins((WRegion*)mplex, mplex->l1_list);
1103 ret2=region_rescue_managed_clientwins((WRegion*)mplex, mplex->l2_list);
1104 ret3=region_rescue_child_clientwins((WRegion*)mplex);
1106 return (ret1 && ret2 && ret3);
1110 void mplex_child_removed(WMPlex *mplex, WRegion *sub)
1112 WMPlexSTDispInfo *di=&(mplex->stdispinfo);
1114 if(sub==(WRegion*)(di->regwatch.obj)){
1115 watch_reset(&(di->regwatch));
1116 mplex_set_stdisp(mplex, NULL, di->pos);
1121 /*}}}*/
1124 /*{{{ Status display support */
1126 #ifndef offsetof
1127 # define offsetof(T,F) ((size_t)((char*)&((T*)0L)->F-(char*)0L))
1128 #endif
1130 #define STRUCTOF(T, F, FADDR) \
1131 ((T*)((char*)(FADDR)-offsetof(T, F)))
1134 static void stdisp_watch_handler(Watch *watch, Obj *obj)
1136 /*WMPlex *mplex=STRUCTOF(WMPlex, stdispinfo,
1137 STRUCTOF(WMPlexSTDispInfo, regwatch, watch));
1138 WMPlexSTDispInfo *di=&(mplex->stdispinfo);
1139 WGenWS *ws=OBJ_CAST(REGION_MANAGER(obj), WGenWS);
1141 if(ioncore_g.opmode!=IONCORE_OPMODE_DEINIT && ws!=NULL)
1142 genws_unmanage_stdisp(ws, TRUE, FALSE);*/
1146 bool mplex_set_stdisp(WMPlex *mplex, WRegion *reg, int pos)
1148 WMPlexSTDispInfo *di=&(mplex->stdispinfo);
1149 WRegion *oldstdisp=(WRegion*)(di->regwatch.obj);
1150 WGenWS *mgr=NULL;
1152 assert(reg==NULL || (Obj*)reg==di->regwatch.obj ||
1153 (REGION_MANAGER(reg)==NULL &&
1154 REGION_PARENT(reg)==(WWindow*)mplex));
1156 if(oldstdisp!=NULL){
1157 mgr=OBJ_CAST(REGION_MANAGER(oldstdisp), WGenWS);
1159 if(oldstdisp!=reg){
1160 mainloop_defer_destroy(di->regwatch.obj);
1161 watch_reset(&(di->regwatch));
1165 di->pos=pos;
1167 if(reg==NULL){
1168 if(mgr!=NULL){
1169 genws_unmanage_stdisp((WGenWS*)mgr, TRUE, FALSE);
1170 region_detach_manager(oldstdisp);
1172 }else{
1173 watch_setup(&(di->regwatch), (Obj*)reg, stdisp_watch_handler);
1175 if(mplex->l1_current!=NULL && OBJ_IS(mplex->l1_current, WGenWS) &&
1176 mgr!=(WGenWS*)(mplex->l1_current)){
1177 if(mgr!=NULL){
1178 genws_unmanage_stdisp(mgr, FALSE, TRUE);
1179 region_detach_manager(oldstdisp);
1181 mgr=(WGenWS*)(mplex->l1_current);
1183 if(mgr!=NULL)
1184 genws_manage_stdisp(mgr, reg, di->pos);
1185 else
1186 region_unmap(reg);
1189 return TRUE;
1193 void mplex_get_stdisp(WMPlex *mplex, WRegion **reg, int *pos)
1195 WMPlexSTDispInfo *di=&(mplex->stdispinfo);
1197 *reg=(WRegion*)di->regwatch.obj;
1198 *pos=di->pos;
1202 static StringIntMap pos_map[]={
1203 {"tl", MPLEX_STDISP_TL},
1204 {"tr", MPLEX_STDISP_TR},
1205 {"bl", MPLEX_STDISP_BL},
1206 {"br", MPLEX_STDISP_BR},
1207 {NULL, 0}
1211 static WRegion *do_attach_stdisp(WMPlex *mplex, WRegionAttachHandler *handler,
1212 void *handlerparams, const WFitParams *fp)
1214 return handler((WWindow*)mplex, fp, handlerparams);
1215 /* We do not manage the stdisp, so manager is not set in this
1216 * function unlike a true "attach" function.
1221 /*EXTL_DOC
1222 * Set/create status display for \var{mplex}. Table is a standard
1223 * description of the object to be created (as passed to e.g.
1224 * \fnref{WMPlex.attach_new}). In addition, the following fields are
1225 * recognised:
1227 * \begin{tabularx}{\linewidth}{lX}
1228 * \tabhead{Field & Description}
1229 * \var{pos} & The corner of the screen to place the status display
1230 * in. One of \code{tl}, \code{tr}, \var{bl} or \var{br}. \\
1231 * \var{action} & If this field is set to \code{keep}, \var{corner}
1232 * and \var{orientation} are changed for the existing
1233 * status display. If this field is set to \var{remove},
1234 * the existing status display is removed. If this
1235 * field is not set or is set to \code{replace}, a
1236 * new status display is created and the old, if any,
1237 * removed. \\
1238 * \end{tabularx}
1240 EXTL_EXPORT_AS(WMPlex, set_stdisp)
1241 WRegion *mplex_set_stdisp_extl(WMPlex *mplex, ExtlTab t)
1243 WRegion *stdisp=NULL;
1244 int p=mplex->stdispinfo.pos;
1245 char *s;
1247 if(extl_table_gets_s(t, "pos", &s)){
1248 p=stringintmap_value(pos_map, s, -1);
1249 if(p<0){
1250 warn(TR("Invalid position setting."));
1251 return NULL;
1255 s=NULL;
1256 extl_table_gets_s(t, "action", &s);
1258 if(s==NULL || strcmp(s, "replace")==0){
1259 WFitParams fp;
1260 int o2;
1262 fp.g.x=0;
1263 fp.g.y=0;
1264 fp.g.w=REGION_GEOM(mplex).w;
1265 fp.g.h=REGION_GEOM(mplex).h;
1266 fp.mode=REGION_FIT_BOUNDS;
1268 /* Full mplex size is stupid so use saved geometry initially
1269 * if there's one.
1271 extl_table_gets_rectangle(t, "geom", &(fp.g));
1273 stdisp=region__attach_load((WRegion*)mplex, t,
1274 (WRegionDoAttachFn*)do_attach_stdisp,
1275 (void*)&fp);
1277 if(stdisp==NULL)
1278 return NULL;
1280 }else if(strcmp(s, "keep")==0){
1281 stdisp=(WRegion*)(mplex->stdispinfo.regwatch.obj);
1282 }else if(strcmp(s, "remove")!=0){
1283 warn(TR("Invalid action setting."));
1284 return FALSE;
1287 if(!mplex_set_stdisp(mplex, stdisp, p)){
1288 destroy_obj((Obj*)stdisp);
1289 return NULL;
1292 return stdisp;
1296 static ExtlTab mplex_do_get_stdisp_extl(WMPlex *mplex, bool fullconfig)
1298 WMPlexSTDispInfo *di=&(mplex->stdispinfo);
1299 ExtlTab t;
1301 if(di->regwatch.obj==NULL)
1302 return extl_table_none();
1304 if(fullconfig){
1305 t=region_get_configuration((WRegion*)di->regwatch.obj);
1306 extl_table_sets_rectangle(t, "geom", &REGION_GEOM(di->regwatch.obj));
1307 }else{
1308 t=extl_create_table();
1309 extl_table_sets_o(t, "reg", di->regwatch.obj);
1312 if(t!=extl_table_none())
1313 extl_table_sets_s(t, "pos", stringintmap_key(pos_map, di->pos, NULL));
1315 return t;
1319 /*EXTL_DOC
1320 * Get status display information. See \fnref{WMPlex.get_stdisp} for
1321 * information on the fields.
1323 EXTL_EXPORT_AS(WMPlex, get_stdisp)
1324 ExtlTab mplex_get_stdisp_extl(WMPlex *mplex)
1326 return mplex_do_get_stdisp_extl(mplex, FALSE);
1330 /*}}}*/
1333 /*{{{ Dynfuns */
1336 void mplex_managed_geom_default(const WMPlex *mplex, WRectangle *geom)
1338 geom->x=0;
1339 geom->y=0;
1340 geom->w=REGION_GEOM(mplex).w;
1341 geom->h=REGION_GEOM(mplex).h;
1345 void mplex_managed_geom(const WMPlex *mplex, WRectangle *geom)
1347 CALL_DYN(mplex_managed_geom, mplex, (mplex, geom));
1351 void mplex_size_changed(WMPlex *mplex, bool wchg, bool hchg)
1353 CALL_DYN(mplex_size_changed, mplex, (mplex, wchg, hchg));
1357 void mplex_managed_changed(WMPlex *mplex, int mode, bool sw, WRegion *mgd)
1359 CALL_DYN(mplex_managed_changed, mplex, (mplex, mode, sw, mgd));
1363 /*}}}*/
1366 /*{{{ Changed hook helper */
1369 static const char *mode2str(int mode)
1371 if(mode==MPLEX_CHANGE_SWITCHONLY)
1372 return "switchonly";
1373 else if(mode==MPLEX_CHANGE_REORDER)
1374 return "reorder";
1375 else if(mode==MPLEX_CHANGE_ADD)
1376 return "add";
1377 else if(mode==MPLEX_CHANGE_REMOVE)
1378 return "remove";
1379 return NULL;
1383 static bool mrsh_chg(ExtlFn fn, WMPlexChangedParams *p)
1385 ExtlTab t=extl_create_table();
1386 bool ret;
1388 extl_table_sets_o(t, "reg", (Obj*)p->reg);
1389 extl_table_sets_s(t, "mode", mode2str(p->mode));
1390 extl_table_sets_b(t, "sw", p->sw);
1391 extl_table_sets_o(t, "sub", (Obj*)p->sub);
1393 ret=extl_call(fn, "t", NULL, t);
1395 extl_unref_table(t);
1397 return ret;
1401 void mplex_call_changed_hook(WMPlex *mplex, WHook *hook,
1402 int mode, bool sw, WRegion *reg)
1404 WMPlexChangedParams p;
1406 p.reg=mplex;
1407 p.mode=mode;
1408 p.sw=sw;
1409 p.sub=reg;
1411 hook_call_p(hook, &p, (WHookMarshallExtl*)mrsh_chg);
1415 /*}}} */
1418 /*{{{ Save/load */
1421 ExtlTab mplex_get_configuration(WMPlex *mplex)
1423 WRegion *sub=NULL;
1424 int n=0;
1425 ExtlTab tab, subs, stdisptab;
1427 tab=region_get_base_configuration((WRegion*)mplex);
1429 subs=extl_create_table();
1430 extl_table_sets_t(tab, "subs", subs);
1432 FOR_ALL_MANAGED_ON_LIST(mplex->l1_list, sub){
1433 ExtlTab st=region_get_configuration(sub);
1434 if(st!=extl_table_none()){
1435 if(sub==mplex->l1_current)
1436 extl_table_sets_b(st, "switchto", TRUE);
1437 extl_table_seti_t(subs, ++n, st);
1438 extl_unref_table(st);
1442 FOR_ALL_MANAGED_ON_LIST(mplex->l2_list, sub){
1443 ExtlTab st=region_get_configuration(sub);
1444 if(st!=extl_table_none()){
1445 int flags=mgd_flags(sub);
1446 extl_table_sets_i(st, "layer", 2);
1447 extl_table_sets_b(st, "switchto", !(flags&MGD_L2_HIDDEN));
1448 extl_table_sets_b(st, "passive", flags&MGD_L2_PASSIVE);
1449 extl_table_seti_t(subs, ++n, st);
1450 extl_unref_table(st);
1454 extl_unref_table(subs);
1456 /*stdisptab=mplex_do_get_stdisp_extl(mplex, TRUE);
1457 if(stdisptab!=extl_table_none()){
1458 extl_table_sets_t(tab, "stdisp", stdisptab);
1459 extl_unref_table(stdisptab);
1462 return tab;
1466 void mplex_load_contents(WMPlex *mplex, ExtlTab tab)
1468 ExtlTab substab, subtab;
1469 int n, i;
1471 /*if(extl_table_gets_t(tab, "stdisp", &subtab)){
1472 mplex_set_stdisp_extl(mplex, subtab);
1473 extl_unref_table(subtab);
1476 if(extl_table_gets_t(tab, "subs", &substab)){
1477 n=extl_table_get_n(substab);
1478 for(i=1; i<=n; i++){
1479 if(extl_table_geti_t(substab, i, &subtab)){
1480 mplex_attach_new(mplex, subtab);
1481 extl_unref_table(subtab);
1484 extl_unref_table(substab);
1489 WRegion *mplex_load(WWindow *par, const WFitParams *fp, ExtlTab tab)
1491 WMPlex *mplex=create_mplex(par, fp);
1492 if(mplex!=NULL)
1493 mplex_load_contents(mplex, tab);
1494 return (WRegion*)mplex;
1498 /*}}}*/
1501 /*{{{ Dynfuntab and class info */
1504 static DynFunTab mplex_dynfuntab[]={
1505 {region_do_set_focus,
1506 mplex_do_set_focus},
1508 {region_managed_remove,
1509 mplex_managed_remove},
1511 {region_managed_rqgeom,
1512 mplex_managed_rqgeom},
1514 {(DynFun*)region_managed_goto,
1515 (DynFun*)mplex_managed_goto},
1517 {(DynFun*)region_handle_drop,
1518 (DynFun*)mplex_handle_drop},
1520 {region_map, mplex_map},
1521 {region_unmap, mplex_unmap},
1523 {(DynFun*)region_manage_clientwin,
1524 (DynFun*)mplex_manage_clientwin},
1526 {(DynFun*)region_current,
1527 (DynFun*)mplex_current},
1529 {(DynFun*)region_rescue_clientwins,
1530 (DynFun*)mplex_rescue_clientwins},
1532 {(DynFun*)region_manage_rescue,
1533 (DynFun*)mplex_manage_rescue},
1535 {(DynFun*)region_get_configuration,
1536 (DynFun*)mplex_get_configuration},
1538 {mplex_managed_geom,
1539 mplex_managed_geom_default},
1541 {(DynFun*)region_fitrep,
1542 (DynFun*)mplex_fitrep},
1544 {region_child_removed,
1545 mplex_child_removed},
1547 {region_managed_activated,
1548 mplex_managed_activated},
1550 END_DYNFUNTAB
1554 IMPLCLASS(WMPlex, WWindow, mplex_deinit, mplex_dynfuntab);
1557 /*}}}*/