Spell 'serialize' with a 'z'
[notion.git] / ioncore / names.c
blob37731fe9f111c854c6334287bf6fc655728ecb01
1 /*
2 * ion/ioncore/names.c
4 * Copyright (c) Tuomo Valkonen 1999-2009.
6 * See the included file LICENSE for details.
7 */
10 #include <string.h>
11 #include <limits.h>
13 #include <libtu/rb.h>
14 #include <libtu/minmax.h>
15 #include <libtu/objp.h>
16 #include <libextl/extl.h>
18 #include "common.h"
19 #include "global.h"
20 #include "region.h"
21 #include "clientwin.h"
22 #include "names.h"
23 #include "strings.h"
24 #include "gr.h"
27 /*{{{ Implementation */
30 WNamespace ioncore_internal_ns={NULL, FALSE};
31 WNamespace ioncore_clientwin_ns={NULL, FALSE};
34 static bool initialise_ns(WNamespace *ns)
36 if(ns->initialised)
37 return TRUE;
39 if(ns->rb==NULL)
40 ns->rb=make_rb();
42 ns->initialised=(ns->rb!=NULL);
44 return ns->initialised;
48 static int parseinst(const char *name, const char **startinst)
50 const char *p2, *p3=NULL;
51 int inst;
52 int l=strlen(name);
54 *startinst=name+l;
56 if(name[l-1]!='>')
57 return -1;
59 p2=strrchr(name, '<');
60 if(p2==NULL)
61 return -1;
63 inst=strtoul(p2+1, (char**)&p3, 10);
65 if(inst<0 || p3!=name+l-1)
66 return -1;
68 *startinst=p2;
69 return inst;
73 static int parseinst_simple(const char *inststr)
75 const char *end=NULL;
76 int inst;
78 if(*inststr=='\0')
79 return 0;
81 if(*inststr=='<'){
82 inst=strtoul(inststr+1, (char**)&end, 10);
84 if(inst>=0 && end!=NULL && *end=='>')
85 return inst;
88 warn(TR("Corrupt instance number %s."), inststr);
89 return -1;
93 #define COMPARE_FN ((Rb_compfn*)compare_nameinfos)
95 static int compare_nameinfos(const WRegionNameInfo *ni1,
96 const WRegionNameInfo *ni2)
98 int l1=0, l2=0;
99 int i1=0, i2=0;
100 int mc=0;
102 /* Handle unnamed regions. */
104 if(ni1->name==NULL){
105 if(ni2->name!=NULL)
106 return 1;
107 return (ni1==ni2 ? 0 : (ni1<ni2 ? -1 : 1));
108 }else if(ni2->name==NULL){
109 return -1;
112 /* Special case: inst_off<0 means that -inst_off-1 is the actual
113 * instance number and the name does not contain it.
116 if(ni1->inst_off>=0)
117 l1=ni1->inst_off;
118 else
119 l1=strlen(ni1->name);
121 if(ni2->inst_off>=0)
122 l2=ni2->inst_off;
123 else
124 l2=strlen(ni2->name);
126 /* Check name part first */
128 mc=strncmp(ni1->name, ni2->name, minof(l1, l2));
130 if(mc!=0)
131 return mc;
133 if(l1!=l2)
134 return (l1<l2 ? -1 : 1);
136 /* Same name, different instance */
138 if(ni1->inst_off>=0)
139 i1=parseinst_simple(ni1->name+ni1->inst_off);
140 else
141 i1=-ni1->inst_off-1; /*???*/
143 if(ni2->inst_off>=0)
144 i2=parseinst_simple(ni2->name+ni2->inst_off);
145 else
146 i2=-ni2->inst_off-1; /*???*/
148 if(i1!=i2)
149 return (i1<i2 ? -1 : 1);
151 /* Same name and instance */
153 return 0;
156 static bool insert_reg(Rb_node tree, WRegion *reg)
158 assert(reg->ni.node==NULL);
159 reg->ni.node=(void*)rb_insertg(tree, &(reg->ni), reg, COMPARE_FN);
160 return (reg->ni.node!=NULL);
163 static bool separated(const WRegionNameInfo *ni1,
164 const WRegionNameInfo *ni2, int *i1ret)
166 int l1, l2;
167 int i1, i2;
169 assert(ni1->name!=NULL);
171 if(ni2->name==NULL){
172 /* Shouldn't happen the way this function is called below; unnamed
173 * regions are before others in the traversal order of the tree.
175 *i1ret=parseinst_simple(ni1->name+ni1->inst_off);
176 return TRUE;
179 assert(ni1->inst_off>=0 && ni2->inst_off>=0);
181 l1=ni1->inst_off;
182 l2=ni2->inst_off;
184 i1=parseinst_simple(ni1->name+ni1->inst_off);
185 *i1ret=i1;
187 if(l1!=l2)
188 return TRUE;
190 if(memcmp(ni1->name, ni2->name, l1)!=0)
191 return TRUE;
193 i2=parseinst_simple(ni2->name+ni2->inst_off);
195 return (i2>(i1+1));
200 void region_unregister(WRegion *reg)
202 if(reg->ni.node!=NULL){
203 rb_delete_node((Rb_node)reg->ni.node);
204 reg->ni.node=NULL;
207 if(reg->ni.name!=NULL){
208 free(reg->ni.name);
209 reg->ni.name=NULL;
210 reg->ni.inst_off=0;
215 static bool make_full_name(WRegionNameInfo *ni, const char *name, int inst,
216 bool append_always)
218 assert(ni->name==NULL);
220 if(inst==0 && !append_always)
221 ni->name=scopy(name);
222 else
223 libtu_asprintf(&(ni->name), "%s<%d>", name, inst);
225 if(ni->name==NULL)
226 return FALSE;
228 ni->inst_off=strlen(name);
230 return TRUE;
234 static bool do_use_name(WRegion *reg, WNamespace *ns, const char *name,
235 int instrq, bool failchange)
237 int parsed_inst=-1;
238 WRegionNameInfo ni={NULL, 0, NULL};
239 const char *dummy=NULL;
240 Rb_node node;
241 int inst=-1;
242 int found=0;
244 parsed_inst=parseinst(name, &dummy);
246 if(!ns->initialised)
247 return FALSE;
249 /* If there's something that looks like an instance at the end of
250 * name, we will append the instance number.
252 if(parsed_inst>=0 && inst==0 && failchange)
253 return FALSE;
255 if(instrq>=0){
256 WRegionNameInfo tmpni;
257 tmpni.name=(char*)name;
258 tmpni.inst_off=-instrq-1;
259 node=rb_find_gkey_n(ns->rb, &tmpni, COMPARE_FN, &found);
260 if(found){
261 if(rb_val(node)==(void*)reg){
262 /* The region already has the requested name */
263 return TRUE;
265 if(failchange)
266 return FALSE;
267 }else{
268 inst=instrq;
272 if(inst<0){
273 WRegionNameInfo tmpni;
275 found=0;
276 inst=0;
278 tmpni.name=(char*)name;
279 tmpni.inst_off=-1;
280 node=rb_find_gkey_n(ns->rb, &tmpni, COMPARE_FN, &found);
282 if(found){
283 while(1){
284 Rb_node next=rb_next(node);
286 if(rb_val(node)==(void*)reg){
287 /* The region already has a name of requested form */
288 return TRUE;
291 if(next==rb_nil(ns->rb) ||
292 separated((const WRegionNameInfo*)node->k.key,
293 (const WRegionNameInfo*)next->k.key, &inst)){
294 /* 'inst' should be next free instance after increment
295 * as separation was greater then one.
297 inst++;
298 break;
301 /* 'inst' should be instance of next after increment
302 * as separation was one.
304 inst++;
305 node=next;
310 if(!make_full_name(&ni, name, inst, parsed_inst>=0))
311 return FALSE;
314 rb_find_gkey_n(ns->rb, &ni, COMPARE_FN, &found);
316 assert(!found);
319 region_unregister(reg);
321 reg->ni.name=ni.name;
322 reg->ni.inst_off=ni.inst_off;
324 if(!insert_reg(ns->rb, reg)){
325 free(reg->ni.name);
326 reg->ni.name=NULL;
327 reg->ni.inst_off=0;
328 return FALSE;
331 return TRUE;
335 static bool use_name_anyinst(WRegion *reg, WNamespace *ns, const char *name)
337 return do_use_name(reg, ns, name, -1, FALSE);
341 static bool use_name_exact(WRegion *reg, WNamespace *ns, const char *name)
343 return do_use_name(reg, ns, name, 0, TRUE);
347 static bool use_name_parseany(WRegion *reg, WNamespace *ns, const char *name)
349 int inst;
350 const char *startinst;
352 inst=parseinst(name, &startinst);
353 if(inst>=0){
354 bool retval=FALSE;
355 int realnamelen=startinst-name;
356 char *realname=ALLOC_N(char, realnamelen+1);
357 if(realname!=NULL){
358 memcpy(realname, name, realnamelen);
359 realname[realnamelen]='\0';
360 retval=do_use_name(reg, ns, realname, inst, FALSE);
361 free(realname);
363 return retval;
366 return do_use_name(reg, ns, name, 0, FALSE);
371 /*}}}*/
374 /*{{{ Interface */
377 /*EXTL_DOC
378 * Returns the name for \var{reg}.
380 EXTL_SAFE
381 EXTL_EXPORT_MEMBER
382 const char *region_name(WRegion *reg)
384 return reg->ni.name;
388 static bool do_set_name(bool (*fn)(WRegion *reg, WNamespace *ns, const char *p),
389 WRegion *reg, WNamespace *ns, const char *p)
391 bool ret=TRUE;
392 char *nm=NULL;
394 if(!initialise_ns(ns))
395 return FALSE;
397 if(p!=NULL){
398 nm=scopy(p);
399 if(nm==NULL)
400 return FALSE;
401 str_stripws(nm);
404 if(nm==NULL || *nm=='\0'){
405 region_unregister(reg);
406 ret=insert_reg(ns->rb, reg);
407 }else{
408 ret=fn(reg, ns, nm);
411 if(nm!=NULL)
412 free(nm);
414 region_notify_change(reg, ioncore_g.notifies.name);
416 return ret;
420 bool region_register(WRegion *reg)
422 assert(reg->ni.name==NULL);
424 if(!initialise_ns(&ioncore_internal_ns))
425 return FALSE;
427 return use_name_anyinst(reg, &ioncore_internal_ns, OBJ_TYPESTR(reg));
431 bool clientwin_register(WClientWin *cwin)
433 WRegion *reg=(WRegion*)cwin;
435 assert(reg->ni.name==NULL);
437 if(!initialise_ns(&ioncore_clientwin_ns))
438 return FALSE;
440 return insert_reg(ioncore_clientwin_ns.rb, (WRegion*)cwin);
444 /*EXTL_DOC
445 * Set the name of \var{reg} to \var{p}. If the name is already in use,
446 * an instance number suffix \codestr{<n>} will be attempted. If \var{p} has
447 * such a suffix, it will be modified, otherwise such a suffix will be
448 * added. Setting \var{p} to nil will cause current name to be removed.
450 EXTL_EXPORT_MEMBER
451 bool region_set_name(WRegion *reg, const char *p)
453 /* return do_set_name(use_name_parseany, reg, &ioncore_internal_ns, p);*/
454 return do_set_name(use_name_parseany, reg,
455 OBJ_IS(reg, WClientWin) ? &ioncore_clientwin_ns : &ioncore_internal_ns,
460 /*EXTL_DOC
461 * Similar to \fnref{WRegion.set_name} except if the name is already in use,
462 * other instance numbers will not be attempted. The string \var{p} should
463 * not contain a \codestr{<n>} suffix or this function will fail.
465 EXTL_EXPORT_MEMBER
466 bool region_set_name_exact(WRegion *reg, const char *p)
468 return do_set_name(use_name_exact, reg, &ioncore_internal_ns, p);
472 bool clientwin_set_name(WClientWin *cwin, const char *p)
474 return do_set_name(use_name_anyinst, (WRegion*)cwin,
475 &ioncore_clientwin_ns, p);
479 /*}}}*/
482 /*{{{ Lookup and list */
485 static WRegion *do_lookup_region(WNamespace *ns, const char *cname,
486 const char *typenam)
488 WRegionNameInfo ni;
489 Rb_node node;
490 int found=0;
491 const char *instptr=NULL;
493 if(cname==NULL || !ns->initialised)
494 return NULL;
496 parseinst(cname, &instptr);
497 assert(instptr!=NULL);
499 ni.name=(char*)cname;
500 ni.inst_off=instptr-cname;
502 node=rb_find_gkey_n(ns->rb, &ni, COMPARE_FN, &found);
504 if(!found)
505 return NULL;
507 if(typenam!=NULL && !obj_is_str((Obj*)node->v.val, typenam))
508 return NULL;
510 return (WRegion*)node->v.val;
514 /*EXTL_DOC
515 * Attempt to find a non-client window region with name \var{name} and type
516 * inheriting \var{typenam}.
518 EXTL_SAFE
519 EXTL_EXPORT
520 WRegion *ioncore_lookup_region(const char *name, const char *typenam)
522 return do_lookup_region(&ioncore_internal_ns, name, typenam);
526 /*EXTL_DOC
527 * Attempt to find a client window with name \var{name}.
529 EXTL_SAFE
530 EXTL_EXPORT
531 WClientWin *ioncore_lookup_clientwin(const char *name)
533 return (WClientWin*)do_lookup_region(&ioncore_clientwin_ns, name,
534 "WClientWin");
538 static bool do_list(ExtlFn fn, WNamespace *ns, const char *typenam)
540 Rb_node node;
542 if(!ns->initialised)
543 return FALSE;
545 rb_traverse(node, ns->rb){
546 WRegion *reg=(WRegion*)node->v.val;
548 assert(reg!=NULL);
550 if(typenam!=NULL && !obj_is_str((Obj*)reg, typenam))
551 continue;
553 if(!extl_iter_obj(fn, (Obj*)reg))
554 return FALSE;
557 return TRUE;
561 /*EXTL_DOC
562 * Iterate over all non-client window regions with (inherited) class
563 * \var{typenam} until \var{iterfn} returns \code{false}.
564 * The function is called in protected mode.
565 * This routine returns \code{true} if it reaches the end of list
566 * without this happening.
568 EXTL_SAFE
569 EXTL_EXPORT
570 bool ioncore_region_i(ExtlFn fn, const char *typenam)
572 return do_list(fn, &ioncore_internal_ns, typenam);
576 /*EXTL_DOC
577 * Iterate over client windows until \var{iterfn} returns \code{false}.
578 * The function is called in protected mode.
579 * This routine returns \code{true} if it reaches the end of list
580 * without this happening.
582 EXTL_SAFE
583 EXTL_EXPORT
584 bool ioncore_clientwin_i(ExtlFn fn)
586 return do_list(fn, &ioncore_clientwin_ns, NULL);
590 /*}}}*/
593 /*{{{ Displayname */
596 const char *region_displayname(WRegion *reg)
598 const char *ret=NULL;
599 CALL_DYN_RET(ret, const char *, region_displayname, reg, (reg));
600 return ret;
604 char *region_make_label(WRegion *reg, int maxw, GrBrush *brush)
606 const char *name=region_displayname(reg);
608 if(name==NULL)
609 return NULL;
611 return grbrush_make_label(brush, name, maxw);
615 /*}}}*/