]> git.decadent.org.uk Git - ion3.git/blob - ioncore/names.c
Update cfg_kludge_flash for Flash 10
[ion3.git] / ioncore / names.c
1 /*
2  * ion/ioncore/names.c
3  *
4  * Copyright (c) Tuomo Valkonen 1999-2009. 
5  *
6  * See the included file LICENSE for details.
7  */
8
9
10 #include <string.h>
11 #include <limits.h>
12
13 #include <libtu/rb.h>
14 #include <libtu/minmax.h>
15 #include <libtu/objp.h>
16 #include <libextl/extl.h>
17
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"
25
26
27 /*{{{ Implementation */
28
29
30 WNamespace ioncore_internal_ns={NULL, FALSE};
31 WNamespace ioncore_clientwin_ns={NULL, FALSE};
32
33
34 static bool initialise_ns(WNamespace *ns)
35 {
36     if(ns->initialised)
37         return TRUE;
38     
39     if(ns->rb==NULL)
40         ns->rb=make_rb();
41     
42     ns->initialised=(ns->rb!=NULL);
43     
44     return ns->initialised;
45 }
46
47
48 static int parseinst(const char *name, const char **startinst)
49 {
50     const char *p2, *p3=NULL;
51     int inst;
52     int l=strlen(name);
53     
54     *startinst=name+l;
55
56     if(name[l-1]!='>')
57         return -1;
58     
59     p2=strrchr(name, '<');
60     if(p2==NULL)
61         return -1;
62     
63     inst=strtoul(p2+1, (char**)&p3, 10);
64     
65     if(inst<0 || p3!=name+l-1)
66         return -1;
67     
68     *startinst=p2;
69     return inst;
70 }
71
72
73 static int parseinst_simple(const char *inststr)
74 {
75     const char *end=NULL;
76     int inst;
77     
78     if(*inststr=='\0')
79         return 0;
80     
81     if(*inststr=='<'){
82         inst=strtoul(inststr+1, (char**)&end, 10);
83     
84         if(inst>=0 && end!=NULL && *end=='>')
85             return inst;
86     }
87
88     warn(TR("Corrupt instance number %s."), inststr);
89     return -1;
90 }
91
92
93 #define COMPARE_FN ((Rb_compfn*)compare_nameinfos)
94
95 static int compare_nameinfos(const WRegionNameInfo *ni1, 
96                              const WRegionNameInfo *ni2)
97 {
98     int l1=0, l2=0;
99     int i1=0, i2=0;
100     int mc=0;
101     
102     /* Handle unnamed regions. */
103
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;
110     }
111
112     /* Special case: inst_off<0 means that -inst_off-1 is the actual
113      * instance number and the name does not contain it.
114      */
115
116     if(ni1->inst_off>=0)
117         l1=ni1->inst_off;
118     else
119         l1=strlen(ni1->name);
120
121     if(ni2->inst_off>=0)
122         l2=ni2->inst_off;
123     else
124         l2=strlen(ni2->name);
125     
126     /* Check name part first */
127     
128     mc=strncmp(ni1->name, ni2->name, minof(l1, l2));
129     
130     if(mc!=0)
131         return mc;
132     
133     if(l1!=l2)
134         return (l1<l2 ? -1 : 1);
135
136     /* Same name, different instance */
137     
138     if(ni1->inst_off>=0)
139         i1=parseinst_simple(ni1->name+ni1->inst_off);
140     else
141         i1=-ni1->inst_off-1; /*???*/
142
143     if(ni2->inst_off>=0)
144         i2=parseinst_simple(ni2->name+ni2->inst_off);
145     else
146         i2=-ni2->inst_off-1; /*???*/
147
148     if(i1!=i2)
149         return (i1<i2 ? -1 : 1);
150     
151     /* Same name and instance */
152     
153     return 0;
154 }
155
156 static bool insert_reg(Rb_node tree, WRegion *reg)
157 {
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);
161 }
162
163 static bool separated(const WRegionNameInfo *ni1, 
164                       const WRegionNameInfo *ni2, int *i1ret)
165 {
166     int l1, l2;
167     int i1, i2;
168     int mc;
169     
170     assert(ni1->name!=NULL);
171     
172     if(ni2->name==NULL){
173         /* Shouldn't happen the way this function is called below; unnamed
174          * regions are before others in the traversal order of the tree.
175          */
176         *i1ret=parseinst_simple(ni1->name+ni1->inst_off);
177         return TRUE;
178     }
179     
180     assert(ni1->inst_off>=0 && ni2->inst_off>=0);
181
182     l1=ni1->inst_off;
183     l2=ni2->inst_off;
184
185     i1=parseinst_simple(ni1->name+ni1->inst_off);
186     *i1ret=i1;
187     
188     if(l1!=l2)
189         return TRUE;
190         
191     if(memcmp(ni1->name, ni2->name, l1)!=0)
192         return TRUE;
193     
194     i2=parseinst_simple(ni2->name+ni2->inst_off);
195     
196     return (i2>(i1+1));
197 }
198
199
200
201 void region_unregister(WRegion *reg)
202 {
203     if(reg->ni.node!=NULL){
204         rb_delete_node((Rb_node)reg->ni.node);
205         reg->ni.node=NULL;
206     }
207     
208     if(reg->ni.name!=NULL){
209         free(reg->ni.name);
210         reg->ni.name=NULL;
211         reg->ni.inst_off=0;
212     }
213 }
214
215
216 static bool make_full_name(WRegionNameInfo *ni, const char *name, int inst, 
217                            bool append_always)
218 {
219     assert(ni->name==NULL);
220     
221     if(inst==0 && !append_always)
222         ni->name=scopy(name);
223     else
224         libtu_asprintf(&(ni->name), "%s<%d>", name, inst);
225         
226     if(ni->name==NULL)
227         return FALSE;
228     
229     ni->inst_off=strlen(name);
230     
231     return TRUE;
232 }
233
234
235 static bool do_use_name(WRegion *reg, WNamespace *ns, const char *name,
236                         int instrq, bool failchange)
237 {
238     int parsed_inst=-1;
239     WRegionNameInfo ni={NULL, 0, NULL};
240     const char *dummy=NULL;
241     Rb_node node;
242     int inst=-1;
243     int found=0;
244
245     parsed_inst=parseinst(name, &dummy);
246     
247     if(!ns->initialised)
248         return FALSE;
249
250     /* If there's something that looks like an instance at the end of
251      * name, we will append the instance number.
252      */
253     if(parsed_inst>=0 && inst==0 && failchange)
254         return FALSE;
255     
256     if(instrq>=0){
257         WRegionNameInfo tmpni;
258         tmpni.name=(char*)name;
259         tmpni.inst_off=-instrq-1;
260         node=rb_find_gkey_n(ns->rb, &tmpni, COMPARE_FN, &found);
261         if(found){
262             if(rb_val(node)==(void*)reg){
263                 /* The region already has the requested name */
264                 return TRUE;
265             }
266             if(failchange)
267                 return FALSE;
268         }else{
269             inst=instrq;
270         }
271     }
272     
273     if(inst<0){
274         WRegionNameInfo tmpni;
275         
276         found=0;
277         inst=0;
278
279         tmpni.name=(char*)name;
280         tmpni.inst_off=-1;
281         node=rb_find_gkey_n(ns->rb, &tmpni, COMPARE_FN, &found);
282         
283         if(found){
284             while(1){
285                 Rb_node next=rb_next(node);
286                 
287                 if(rb_val(node)==(void*)reg){
288                     /* The region already has a name of requested form */
289                     return TRUE;
290                 }
291                 
292                 if(next==rb_nil(ns->rb) ||
293                    separated((const WRegionNameInfo*)node->k.key, 
294                              (const WRegionNameInfo*)next->k.key, &inst)){
295                     /* 'inst' should be next free instance after increment
296                      * as separation was greater then one.
297                      */
298                     inst++;
299                     break;
300                 }
301
302                 /* 'inst' should be instance of next after increment 
303                  * as separation was one.
304                  */
305                 inst++;
306                 node=next;
307             }
308         }
309     }
310
311     if(!make_full_name(&ni, name, inst, parsed_inst>=0))
312         return FALSE;
313
314     /*
315     rb_find_gkey_n(ns->rb, &ni, COMPARE_FN, &found);
316     
317     assert(!found);
318      */
319
320     region_unregister(reg);
321     
322     reg->ni.name=ni.name;
323     reg->ni.inst_off=ni.inst_off;
324     
325     if(!insert_reg(ns->rb, reg)){
326         free(reg->ni.name);
327         reg->ni.name=NULL;
328         reg->ni.inst_off=0;
329         return FALSE;
330     }
331
332     return TRUE;
333 }
334
335
336 static bool use_name_anyinst(WRegion *reg, WNamespace *ns, const char *name)
337 {
338     return do_use_name(reg, ns, name, -1, FALSE);
339 }
340
341
342 static bool use_name_exact(WRegion *reg, WNamespace *ns, const char *name)
343 {
344     return do_use_name(reg, ns, name, 0, TRUE);
345 }
346
347
348 static bool use_name_parseany(WRegion *reg, WNamespace *ns, const char *name)
349 {
350     int l, inst;
351     const char *startinst;
352     
353     l=strlen(name);
354     
355     inst=parseinst(name, &startinst);
356     if(inst>=0){
357         bool retval=FALSE;
358         int realnamelen=startinst-name;
359         char *realname=ALLOC_N(char, realnamelen+1);
360         if(realname!=NULL){
361             memcpy(realname, name, realnamelen);
362             realname[realnamelen]='\0';
363             retval=do_use_name(reg, ns, realname, inst, FALSE);
364             free(realname);
365         }
366         return retval;
367     }
368     
369     return do_use_name(reg, ns, name, 0, FALSE);
370 }
371
372
373
374 /*}}}*/
375
376
377 /*{{{ Interface */
378
379
380 /*EXTL_DOC
381  * Returns the name for \var{reg}.
382  */
383 EXTL_SAFE
384 EXTL_EXPORT_MEMBER
385 const char *region_name(WRegion *reg)
386 {
387     return reg->ni.name;
388 }
389
390
391 static bool do_set_name(bool (*fn)(WRegion *reg, WNamespace *ns, const char *p),
392                         WRegion *reg, WNamespace *ns, const char *p)
393 {
394     bool ret=TRUE;
395     char *nm=NULL;
396
397     if(!initialise_ns(ns))
398         return FALSE;
399     
400     if(p!=NULL){
401         nm=scopy(p);
402         if(nm==NULL)
403             return FALSE;
404         str_stripws(nm);
405     }
406
407     if(nm==NULL || *nm=='\0'){
408         region_unregister(reg);
409         ret=insert_reg(ns->rb, reg);
410     }else{
411         ret=fn(reg, ns, nm);
412     }
413     
414     if(nm!=NULL)
415         free(nm);
416
417     region_notify_change(reg, ioncore_g.notifies.name);
418     
419     return ret;
420 }
421
422
423 bool region_register(WRegion *reg)
424 {
425     assert(reg->ni.name==NULL);
426     
427     if(!initialise_ns(&ioncore_internal_ns))
428         return FALSE;
429     
430     return use_name_anyinst(reg, &ioncore_internal_ns, OBJ_TYPESTR(reg));
431 }
432
433
434 bool clientwin_register(WClientWin *cwin)
435 {
436     WRegion *reg=(WRegion*)cwin;
437     
438     assert(reg->ni.name==NULL);
439     
440     if(!initialise_ns(&ioncore_clientwin_ns))
441         return FALSE;
442     
443     return insert_reg(ioncore_clientwin_ns.rb, (WRegion*)cwin);
444 }
445
446
447 /*EXTL_DOC
448  * Set the name of \var{reg} to \var{p}. If the name is already in use,
449  * an instance number suffix \codestr{<n>} will be attempted. If \var{p} has
450  * such a suffix, it will be modified, otherwise such a suffix will be
451  * added. Setting \var{p} to nil will cause current name to be removed.
452  */
453 EXTL_EXPORT_MEMBER
454 bool region_set_name(WRegion *reg, const char *p)
455 {
456 /*    return do_set_name(use_name_parseany, reg, &ioncore_internal_ns, p);*/
457     return do_set_name(use_name_parseany, reg, 
458             OBJ_IS(reg, WClientWin) ? &ioncore_clientwin_ns : &ioncore_internal_ns,
459             p);
460 }
461
462
463 /*EXTL_DOC
464  * Similar to \fnref{WRegion.set_name} except if the name is already in use,
465  * other instance numbers will not be attempted. The string \var{p} should
466  * not contain a \codestr{<n>} suffix or this function will fail.
467  */
468 EXTL_EXPORT_MEMBER
469 bool region_set_name_exact(WRegion *reg, const char *p)
470 {
471     return do_set_name(use_name_exact, reg, &ioncore_internal_ns, p);
472 }
473
474
475 bool clientwin_set_name(WClientWin *cwin, const char *p)
476 {
477     return do_set_name(use_name_anyinst, (WRegion*)cwin,
478                        &ioncore_clientwin_ns, p);
479 }
480
481
482 /*}}}*/
483
484
485 /*{{{ Lookup and list */
486
487
488 static WRegion *do_lookup_region(WNamespace *ns, const char *cname,
489                                  const char *typenam)
490 {
491     WRegionNameInfo ni;
492     Rb_node node;
493     int found=0;
494     const char *instptr=NULL;
495
496     if(cname==NULL || !ns->initialised)
497         return NULL;
498     
499     parseinst(cname, &instptr);
500     assert(instptr!=NULL);
501     
502     ni.name=(char*)cname;
503     ni.inst_off=instptr-cname;
504     
505     node=rb_find_gkey_n(ns->rb, &ni, COMPARE_FN, &found);
506     
507     if(!found)
508         return NULL;
509     
510     return (WRegion*)node->v.val;
511 }
512
513
514 /*EXTL_DOC
515  * Attempt to find a non-client window region with name \var{name} and type
516  * inheriting \var{typenam}.
517  */
518 EXTL_SAFE
519 EXTL_EXPORT
520 WRegion *ioncore_lookup_region(const char *name, const char *typenam)
521 {
522     return do_lookup_region(&ioncore_internal_ns, name, typenam);
523 }
524
525
526 /*EXTL_DOC
527  * Attempt to find a client window with name \var{name}.
528  */
529 EXTL_SAFE
530 EXTL_EXPORT
531 WClientWin *ioncore_lookup_clientwin(const char *name)
532 {
533     return (WClientWin*)do_lookup_region(&ioncore_clientwin_ns, name, 
534                                          "WClientWin");
535 }
536
537
538 static bool do_list(ExtlFn fn, WNamespace *ns, const char *typenam)
539 {
540     Rb_node node;
541     int n=0;
542     
543     if(!ns->initialised)
544         return FALSE;
545     
546     rb_traverse(node, ns->rb){
547         WRegion *reg=(WRegion*)node->v.val;
548         
549         assert(reg!=NULL);
550
551         if(typenam!=NULL && !obj_is_str((Obj*)reg, typenam))
552             continue;
553
554         if(!extl_iter_obj(fn, (Obj*)reg))
555             return FALSE;
556     }
557     
558     return TRUE;
559 }
560
561
562 /*EXTL_DOC
563  * Iterate over all non-client window regions with (inherited) class
564  * \var{typenam} until \var{iterfn} returns \code{false}.
565  * The function is called in protected mode.
566  * This routine returns \code{true} if it reaches the end of list
567  * without this happening.
568  */
569 EXTL_SAFE
570 EXTL_EXPORT
571 bool ioncore_region_i(ExtlFn fn, const char *typenam)
572 {
573     return do_list(fn, &ioncore_internal_ns, typenam);
574 }
575
576
577 /*EXTL_DOC
578  * Iterate over client windows until \var{iterfn} returns \code{false}.
579  * The function is called in protected mode.
580  * This routine returns \code{true} if it reaches the end of list
581  * without this happening.
582  */
583 EXTL_SAFE
584 EXTL_EXPORT
585 bool ioncore_clientwin_i(ExtlFn fn)
586 {
587     return do_list(fn, &ioncore_clientwin_ns, NULL);
588 }
589
590
591 /*}}}*/
592
593
594 /*{{{ Displayname */
595
596
597 const char *region_displayname(WRegion *reg)
598 {
599     const char *ret=NULL;
600     CALL_DYN_RET(ret, const char *, region_displayname, reg, (reg));
601     return ret;
602 }
603
604
605 char *region_make_label(WRegion *reg, int maxw, GrBrush *brush)
606 {
607     const char *name=region_displayname(reg);
608
609     if(name==NULL)
610         return NULL;
611     
612     return grbrush_make_label(brush, name, maxw);
613 }
614
615
616 /*}}}*/